<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>환이의 개발일지</title>
    <link>https://drg2524.tistory.com/</link>
    <description>JAVA 개발자로 일하며
실무에서 직접 부딪히며 배운 내용들을 기록하고 있습니다.

개발을 처음 시작하시는 분들께
조금 먼저 겪어본 사람의 길잡이가 되고 싶습니다  </description>
    <language>ko</language>
    <pubDate>Sun, 19 Jul 2026 02:39:21 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>환이s</managingEditor>
    <image>
      <title>환이의 개발일지</title>
      <url>https://tistory1.daumcdn.net/tistory/5762534/attach/04ad99d90067475cb609c23dabf2b39c</url>
      <link>https://drg2524.tistory.com</link>
    </image>
    <item>
      <title>[Data Platform] KEDA 오토스케일링 가이드 (멀티 트리거 설계 feat. Airflow&amp;middot;Spark)</title>
      <link>https://drg2524.tistory.com/253</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;1076&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWhMVR/dJMcadis45r/cXrH3RadzHLVf2x1AwOzyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWhMVR/dJMcadis45r/cXrH3RadzHLVf2x1AwOzyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWhMVR/dJMcadis45r/cXrH3RadzHLVf2x1AwOzyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWhMVR%2FdJMcadis45r%2FcXrH3RadzHLVf2x1AwOzyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1917&quot; height=&quot;1076&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;1076&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Intro&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #222222; text-align: start;&quot;&gt;안녕하세요. 환이s입니다  &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://drg2524.tistory.com/252&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 KEDA를 Harbor + ArgoCD 기반으로 구축하고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;외부 모듈 연동을 위한 RBAC까지 구성하는 과정을 정리해 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는 그렇게 설치한 KEDA를 실제로 활용해서,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;이벤트 기반 멀티 트리거 ScaledObject를 설계하는 방법&lt;/b&gt;을 정리해보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;실측으로 스케일 값을 계산하고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;성경이 다른 두 신호 (양&amp;middot;지연)를 조합하고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스케일업&amp;middot;다운을 비대칭으로 설계하는 이 원칙들은 특정 워크로드에 국한되지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다만 개념만 나열하면 와닿지 않으니,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 편에서는 &lt;b&gt;Airflow의 Celery Worker를 스케일 대상 예시&lt;/b&gt;로 삼아&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;설계 과정을 처음부터 끝까지 따라갑니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMw3pX/dJMcaixhWQG/UhJQtJJQbZYwYkkGATUUM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMw3pX/dJMcaixhWQG/UhJQtJJQbZYwYkkGATUUM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMw3pX/dJMcaixhWQG/UhJQtJJQbZYwYkkGATUUM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMw3pX%2FdJMcaixhWQG%2FUhJQtJJQbZYwYkkGATUUM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;512&quot; height=&quot;512&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;지금까지 구축한 파이프라인은 Airflow가 DAG를 돌리고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 안에서 Spark Job을 K8s에 제출하는 구조라,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Worker(KEDA)와 Spark Executor(DynamicAllocation)를&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;어떻게 같은 기준으로 움직이게 할지가 핵심 과제였고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 사례가 멀티 트리거 설계의 필요성을 잘 보여주기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;Spark를 KEDA로 직접 스케일할 수 없는 이유와 우회 설계&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;statsd extraMappings로 Airflow 메트릭을 표준화하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;ServiceMonitor로 statsd 메트릭을 Prometheus에 수집시키는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;worker_concurrency 실측으로 스케일 값을 정확히 계산하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;Redis + Prometheus 멀티 트리거 ScaledObject 설계 (v1 &amp;rarr; v2)&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;task_id 명명규칙으로 정규식 쿼리의 전제를 강제하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;실제 스케일 테스트에서 마주친 &quot;너무 빨리 줄어드는&quot; 현상의 정체&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;까지 순서대로 확인해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. 오토스케일링 설계 방향 - &quot;왜 Spark는 KEDA로 직접 못 하나&quot;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;구축 편에서 KEDA 설치를 마쳤으니, 이번 편은 본론인 오토스케일링입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;지금까지 구축한 파이프라인은&lt;b&gt; Airflow가 DAG을 돌리고, &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;그 안에서 Spark Job을 K8s에 제출하는 구조&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;(궁금하신 분들은 아래 링크 참고해 보시면 도움이 될 거 같습니다.)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1783925338955&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Data Platform] KEDA 구축 가이드(Operator 설치부터 API 연동용 RBAC까지)&quot; data-og-description=&quot;Intro 안녕하세요. 환이s입니다   지난 글에서는 데이터 레이크하우스 플랫폼을 GitOps 방식으로 관리하기 위해ArgoCD를 Kubernetes 위에 구축하고 App of Apps 패턴으로 전체 스택을 운영하는 과정을 정&quot; data-og-host=&quot;drg2524.tistory.com&quot; data-og-source-url=&quot;https://drg2524.tistory.com/252&quot; data-og-url=&quot;https://drg2524.tistory.com/252&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tPUHs/dJMb82eZafj/Lr56Ceb4kkfivwZZXZVluk/img.png?width=800&amp;amp;height=448&amp;amp;face=0_0_800_448,https://scrap.kakaocdn.net/dn/vzRqu/dJMb87N806g/RryQXLDDQankGquq6jPO3k/img.png?width=800&amp;amp;height=448&amp;amp;face=0_0_800_448,https://scrap.kakaocdn.net/dn/bafBQX/dJMb82MPlJJ/0uTxZmvGwFNyS2t8EWldNK/img.png?width=1918&amp;amp;height=1076&amp;amp;face=0_0_1918_1076&quot;&gt;&lt;a href=&quot;https://drg2524.tistory.com/252&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://drg2524.tistory.com/252&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tPUHs/dJMb82eZafj/Lr56Ceb4kkfivwZZXZVluk/img.png?width=800&amp;amp;height=448&amp;amp;face=0_0_800_448,https://scrap.kakaocdn.net/dn/vzRqu/dJMb87N806g/RryQXLDDQankGquq6jPO3k/img.png?width=800&amp;amp;height=448&amp;amp;face=0_0_800_448,https://scrap.kakaocdn.net/dn/bafBQX/dJMb82MPlJJ/0uTxZmvGwFNyS2t8EWldNK/img.png?width=1918&amp;amp;height=1076&amp;amp;face=0_0_1918_1076');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Data Platform] KEDA 구축 가이드(Operator 설치부터 API 연동용 RBAC까지)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Intro 안녕하세요. 환이s입니다   지난 글에서는 데이터 레이크하우스 플랫폼을 GitOps 방식으로 관리하기 위해ArgoCD를 Kubernetes 위에 구축하고 App of Apps 패턴으로 전체 스택을 운영하는 과정을 정&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;drg2524.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1783925353738&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Data Platform] Airflow 구축 가이드 (feat. Spark on Kubernetes)&quot; data-og-description=&quot;Intro 안녕하세요. 환이s입니다   지난 글에서는 데이터 레이크하우스 플랫폼의 네 번째 스택으로 Trino + Hive Metastore + PostgreSQL 3계층 구조로 Delta Lake SQL 조회 환경을 구축하는 과정을 정리해 봤습&quot; data-og-host=&quot;drg2524.tistory.com&quot; data-og-source-url=&quot;https://drg2524.tistory.com/246&quot; data-og-url=&quot;https://drg2524.tistory.com/246&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bCApFX/dJMb8XSh48T/baJVPX9G0Bc4jf6TEcwArK/img.png?width=800&amp;amp;height=449&amp;amp;face=0_0_800_449,https://scrap.kakaocdn.net/dn/76c2A/dJMb8QMoo0G/ZM7c7YyxhkTufOAmJgw5tk/img.png?width=800&amp;amp;height=449&amp;amp;face=0_0_800_449,https://scrap.kakaocdn.net/dn/9onHB/dJMb8PGIppi/lbKksVkwK29OrVpe9aXcwK/img.png?width=1919&amp;amp;height=1079&amp;amp;face=0_0_1919_1079&quot;&gt;&lt;a href=&quot;https://drg2524.tistory.com/246&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://drg2524.tistory.com/246&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bCApFX/dJMb8XSh48T/baJVPX9G0Bc4jf6TEcwArK/img.png?width=800&amp;amp;height=449&amp;amp;face=0_0_800_449,https://scrap.kakaocdn.net/dn/76c2A/dJMb8QMoo0G/ZM7c7YyxhkTufOAmJgw5tk/img.png?width=800&amp;amp;height=449&amp;amp;face=0_0_800_449,https://scrap.kakaocdn.net/dn/9onHB/dJMb8PGIppi/lbKksVkwK29OrVpe9aXcwK/img.png?width=1919&amp;amp;height=1079&amp;amp;face=0_0_1919_1079');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Data Platform] Airflow 구축 가이드 (feat. Spark on Kubernetes)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Intro 안녕하세요. 환이s입니다   지난 글에서는 데이터 레이크하우스 플랫폼의 네 번째 스택으로 Trino + Hive Metastore + PostgreSQL 3계층 구조로 Delta Lake SQL 조회 환경을 구축하는 과정을 정리해 봤습&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;drg2524.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이상적으로는 KEDA 하나로 Worker와 Spark Executor를 모두 제어하고 싶었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 방향을&lt;b&gt; KEDA 단일 제어&lt;/b&gt;로 검토했지만,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;SparkApplication 리소스가 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;/scale&lt;/span&gt; 서브리소스를 지원하지 않아&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;KEDA가 직접 스케일하는 것은 기술적으로 불가능했습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그래서 채택한 것이 &lt;b&gt;메트릭 표준화&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1076&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wbztf/dJMcahLSohV/7g1xAqQ9p6o62up503FE20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wbztf/dJMcahLSohV/7g1xAqQ9p6o62up503FE20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wbztf/dJMcahLSohV/7g1xAqQ9p6o62up503FE20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwbztf%2FdJMcahLSohV%2F7g1xAqQ9p6o62up503FE20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1919&quot; height=&quot;1076&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1076&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, Worker와 Executor가 &lt;b&gt;서로 다른 메커니즘으로 움직이더라도,&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;같은 메트릭 기준에 맞춰 동작하도록 통일&lt;/b&gt;하는 절충안입니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;  &lt;b&gt;참고&lt;br /&gt;&lt;/b&gt;공식 Airflow Helm chart에는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;workers.celery.keda.enabled=true&lt;/b&gt;&lt;/span&gt; 한 줄로 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Airflow metadata DB(&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;task_instance&lt;/b&gt;&lt;/span&gt; 테이블)를 직접 쿼리하는 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;KEDA 통합 기능이 내장돼 있습니다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다만 지금까지 구축한 파이프라인이 그동안 직접 구축한 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;ScaledObject의 검증&amp;middot;버그 수정 이력을 보존하기 위해 &lt;b&gt;기존 방식을 유지&lt;/b&gt;하고, &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;공식 기능은 참고만 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;⚠️ &lt;b&gt;주의&lt;br /&gt;&lt;/b&gt;공식 내장 기능과 직접 만든 ScaledObject를 &lt;b&gt;동시에 켜면 절대 안 됩니다&lt;/b&gt;. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;HPA 두 개가 같은 StatefulSet을 두고 경쟁하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;모든 것을 KEDA로 단일 제어하려 하지 말고,&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;기술적 제약(Spark &lt;span style=&quot;background-color: #dddddd;&quot;&gt;/scale&lt;/span&gt; 미지원)을 인정한 뒤,&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&quot;메트릭 기준을 통일&quot;하는 방향으로 우회&lt;/b&gt;했다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. 파일 구조 - &quot;오토스케일링 설정은 어디에 있나&quot;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;본격적인 설정에 들어가기 전에,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;파일이 어디에 있는지 부터 짚고 가겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;오토스케일링 관련 설정은 KEDA 디렉토리가 아니라&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;스케일 대상인 Airflow 쪽 manifests&lt;/b&gt;에 모여 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;services/airflow/manifests/
├── 00_rbac
├── 10_pvc
├── 20_ingress
├── 30_filebrowser
├── 40_monitoring
│   └── 10_servicemonitor-airflow-statsd.yaml   # Prometheus 수집 (섹션 4)
└── 50_autoscaling
    └── 10_scaledobject-airflow-worker.yaml      # ScaledObject 본체 (섹션 6)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;구축편에서 본 KEDA 엔진(&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;services/keda/&lt;/span&gt;&lt;/b&gt;)과 대비하면 역할이 이렇게 나뉩니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;KEDA 엔진&lt;/b&gt; (&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;services/keda/&lt;/b&gt;&lt;/span&gt;) &amp;mdash; Operator&amp;middot;CRD&amp;middot;RBAC 등 &quot;스케일을 수행하는 주체&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;스케일 규칙&lt;/b&gt; (&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;services/airflow/manifests/50_autoscaling/&lt;/b&gt;&lt;/span&gt;) &amp;mdash; ScaledObject 등 &quot;무엇을 어떻게 스케일 할지&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;메트릭 수집&lt;/b&gt; (&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;services/airflow/manifests/40_monitoring/&lt;/b&gt;&lt;/span&gt;) &amp;mdash; ServiceMonitor 등 &quot;판단 근거가 될 메트릭 파이프라인&quot;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;b&gt;&quot;스케일 하는 주체(KEDA)&quot;와 &quot;스케일 당하는 대상의 규칙(Airflow ScaledObject)&quot;을 경로상으로 분리해서, 각각 독립적으로 관리&lt;/b&gt;했다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3. 메트릭 표준화 - statsd extraMappings&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;멀티 트리거를 만들려면 Prometheus 쿼리가 필요한데,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;기존 Airflow 메트릭은 &lt;b&gt;DAG 이름별로 메트릭명 자체가 갈라져&lt;/b&gt; 있는 게 문제였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783926468138&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;airflow_dag_&amp;lt;dag_name_a&amp;gt;_submit_spark_job_queued_duration_sum
airflow_dag_&amp;lt;dag_name_b&amp;gt;_submit_spark_job_queued_duration_sum&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이러면 신규 DAG를 추가할 때마다 쿼리를 고쳐야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;services/airflow/helm/values.yaml&lt;/b&gt;&lt;/span&gt;에 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;statsd.extraMappings&lt;/b&gt;&lt;/span&gt;를 추가해 메트릭명을 통일했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;statsd:
  extraMappings:
    - match: airflow.dag.*.*.queued_duration
      name: &quot;airflow_task_queued_duration&quot;
      labels:
        dag_id: &quot;$1&quot;
        task_id: &quot;$2&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;적용 결과, 메트릭명이 하나로 통일되고 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;dag_id&lt;/b&gt;&lt;/span&gt;/&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;task_id&lt;/b&gt;&lt;/span&gt;가 레이블로 분리됐습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;airflow_task_queued_duration_sum{dag_id=&quot;&amp;lt;dag_name_a&amp;gt;&quot;, task_id=&quot;submit_spark_job&quot;}
airflow_task_queued_duration_sum{dag_id=&quot;&amp;lt;dag_name_b&amp;gt;&quot;, task_id=&quot;submit_spark_job&quot;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이제 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;sum()&lt;/b&gt;&lt;/span&gt; 합산 쿼리 하나로 전체 DAG를 커버할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;  &lt;b&gt;참고&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;match&lt;/b&gt;&lt;/span&gt; 패턴(&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;airflow.dag.*.*.queued_duration&lt;/b&gt;&lt;/span&gt;)의 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;와일드카드 두 개가 각각 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;dag_id($1)&lt;/b&gt;&lt;/span&gt;&amp;middot;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;task_id($2)&lt;/b&gt;&lt;/span&gt;로 매핑됩니다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;적용 전에 실제 statsd 메트릭 경로가 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;airflow.dag.&amp;lt;dag&amp;gt;.&amp;lt;task&amp;gt;.queued_duration&lt;/b&gt; 형태가 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;맞는지(중간에 다른 세그먼트가 끼지 않는지) 한 번 확인하세요. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;세그먼트 수가 어긋나면 레이블이 엉뚱한 값으로 잡힙니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;⚠️ &lt;b&gt;주의&lt;br /&gt;&lt;/b&gt;이 ConfigMap을&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; kubectl edit&lt;/b&gt;&lt;/span&gt;으로 직접 고치면 안 됩니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Helm이 관리하는 리소스라 ArgoCD sync 시 원복됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;b&gt;메트릭명을 값(레이블)으로 빼내면, &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;신규 DAG가 추가돼도 쿼리를 고칠 필요가 없어진다&lt;/b&gt;는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;4. Prometheus 수집 연결 - ServiceMonitor&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;statsd로 메트릭명을 표준화했어도,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 메트릭이 Prometheus에 &lt;b&gt;실제로 수집되어야&lt;/b&gt; KEDA 트리거가 값을 읽을 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Airflow의 statsd exporter가 노출하는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;/metrics&lt;/b&gt;&lt;/span&gt;를 Prometheus가 긁어가도록&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;ServiceMonitor를 구성했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783926917900&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: airflow-statsd
  namespace: monitoring
  labels:
    release: prometheus-helm      # prometheus의 serviceMonitorSelector와 일치해야 함
spec:
  namespaceSelector:
    matchNames:
      - airflow
  selector:
    matchLabels:
      component: statsd
      release: airflow-helm
  endpoints:
    - port: statsd-scrape          # 9102/TCP &amp;mdash; Prometheus metrics 포트
      interval: 15s
      path: /metrics&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;  &lt;b&gt;참고&lt;br /&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;metadata.labels.release: prometheus-helm&lt;/b&gt;&lt;/span&gt;이 가장 자주 실수하는 부분입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Prometheus Operator는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;serviceMonitorSelector&lt;/b&gt;&lt;/span&gt;에 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;매칭되는 레이블을 가진 ServiceMonitor만 인식합니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 값이 안 맞으면 ServiceMonitor를 만들어도 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Prometheus가 무시해서 메트릭이 안 들어옵니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;kubectl get prometheus -n monitoring -o yaml | grep -A5 serviceMonitorSelector&lt;/b&gt;&lt;/span&gt;로 실제 셀렉터를 먼저 확인하세요.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;⚠️ &lt;b&gt;주의&lt;br /&gt;statsd exporter&lt;/b&gt;는 &lt;u&gt;&lt;b&gt;포트가 두 개&lt;/b&gt;&lt;/u&gt;입니다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;수집(scrape)은 9102/TCP&lt;/b&gt; &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;statsd-scrape&lt;/b&gt;&lt;/span&gt; 이고,&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Airflow가 메트릭을 보내는 ingest는 9125/UDP입니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;ServiceMonitor의 port는 반드시 TCP metrics 포트를 가리켜야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;statsd 표준화(생산) &amp;rarr; ServiceMonitor 수집(Prometheus) &amp;rarr; KEDA 트리거(소비)로 이어지는 파이프라인이 전부 연결돼야 비로소 Prometheus 트리거가 동작한다&lt;/b&gt;는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;5. worker_concurrency 실측 - &quot;추정치를 실측값으로&quot;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스케일 파라미터를 정하려면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Worker 하나가 동시에 처리하는 task 수(worker_concurrency)을 알아야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;추정하지 않고 직접 조회했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783927314290&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl exec -n airflow airflow-helm-worker-0 -c worker -- \
  airflow config get-value celery worker_concurrency
# &amp;rarr; 16&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;운영 예상 동시 트리거 규모(상한 300 기준)로 재계산했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783927339611&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;300 / 16 &amp;asymp; 18.75  &amp;rarr;  maxReplicaCount: 19
listLength: 16     (worker_concurrency와 동일하게 맞춤)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;maxReplicaCount&lt;/span&gt;나 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;listLength&lt;/span&gt; 같은 값은 감이 아니라 실측된 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;worker_concurrency&lt;/span&gt;를 기준으로 계산해야 한다&lt;/b&gt;는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;6. ScaledObject v1 &amp;rarr; v2 변경 요약&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;초기 버전(v1)에서 운영 규모(v2)로 재설계한 내용입니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 40.3489%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 19.4185%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;v1&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40.2326%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;v2&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 40.3489%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;minReplicaCount&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 19.4185%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40.2326%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 40.3489%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;maxReplicaCount&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 19.4185%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;5&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40.2326%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;19 (실측 기반)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 40.3489%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;listLength&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 19.4185%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40.2326%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;16 (실측 기반)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 40.3489%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;scaleUp stabilization&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 19.4185%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;30s&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40.2326%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;15s&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 40.3489%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;scaleUp policy&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 19.4185%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;+2/30s&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40.2326%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;+4 또는 +100%/30s (Max)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 40.3489%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;restoreToOriginalReplicaCount&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 19.4185%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;true&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40.2326%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;false&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 40.3489%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;trigger&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 19.4185%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Redis 단일&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40.2326%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Redis + Prometheus 멀티 트리거&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;restoreToOriginalReplicaCount&lt;/span&gt;&lt;/b&gt;는 ScaledObject가 삭제될 때,&lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;대상 워크로드를 원래 replica 수로 되돌릴지를 정하는 옵션입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;v1은 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;true&lt;/b&gt;&lt;/span&gt;(원래값 복귀)였지만,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;v2에서는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;false&lt;/b&gt;&lt;/span&gt;로 두어 &lt;b&gt;삭제 시점에 강제 복귀시키지 않고 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;HPA의 자율 판단에 맡기도록&lt;/b&gt; 변경했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;멀티 트리거를 포함한 ScaledObject 실물 전체는 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;TriggerAuthentication&lt;/b&gt;과 &lt;b&gt;Redis&lt;/b&gt; 비밀번호 &lt;b&gt;Secret&lt;/b&gt;도 같은 파일에 함께 정의했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783928171157&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# ── Redis 접근 비밀번호 Secret ──
apiVersion: v1
kind: Secret
metadata:
  name: airflow-redis-secret
  namespace: airflow
type: Opaque
stringData:
  redis-password: &quot;&amp;lt;REDIS_PASSWORD&amp;gt;&quot;   # 실제 비밀번호로 교체
---
# ── KEDA가 Secret을 참조하기 위한 TriggerAuthentication ──
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
  name: airflow-redis-auth
  namespace: airflow
spec:
  secretTargetRef:
    - parameter: password
      name: airflow-redis-secret
      key: redis-password
---
# ── 본체: ScaledObject ──
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: airflow-worker-scaler
  namespace: airflow
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: StatefulSet
    name: airflow-helm-worker

  minReplicaCount: 2        # 기본 가용성 확보 (단일 장애점 방지)
  maxReplicaCount: 19       # worker_concurrency=16 기준 실측 재계산 (300/16&amp;asymp;19)
  cooldownPeriod: 60

  advanced:
    restoreToOriginalReplicaCount: false   # 운영에선 원래값 강제 복귀 X, HPA 자율 판단
    horizontalPodAutoscalerConfig:
      behavior:
        scaleUp:
          stabilizationWindowSeconds: 15    # 폭주 대응 위해 빠르게 반응
          policies:
            - type: Pods
              value: 4                      # 한 번에 4개씩 확장
              periodSeconds: 30
            - type: Percent
              value: 100                    # 또는 현재 replica의 100%
              periodSeconds: 30
          selectPolicy: Max                 # 둘 중 큰 값 적용
        scaleDown:
          stabilizationWindowSeconds: 600   # 축소는 신중하게 (10분)
          policies:
            - type: Pods
              value: 2
              periodSeconds: 120

  triggers:
    # Trigger 1: Redis Celery 큐 길이 &amp;mdash; 즉각적인 폭주 감지
    - type: redis
      authenticationRef:
        name: airflow-redis-auth
      metadata:
        address: airflow-helm-redis.airflow.svc.cluster.local:6379
        listName: celery
        listLength: &quot;16&quot;           # worker_concurrency(16)와 동일 (실측값)
        databaseIndex: &quot;0&quot;
        enableTLS: &quot;false&quot;

    # Trigger 2: Prometheus 큐 대기시간 &amp;mdash; 후행 추세 감지
    #   순간적으로 큐가 비어도 누적 지연이 길었다면 스케일 유지
    - type: prometheus
      metadata:
        serverAddress: http://prometheus-helm-kube-prome-prometheus.monitoring:80
        query: |
          sum(rate(airflow_task_queued_duration_sum{task_id=~&quot;submit_spark.*&quot;}[5m]))
          /
          sum(rate(airflow_task_queued_duration_count{task_id=~&quot;submit_spark.*&quot;}[5m]))
        threshold: &quot;3&quot;             # 평균 큐 대기시간 3초 이상이면 스케일업 신호
        activationThreshold: &quot;1&quot;   # 이 값(1초)을 넘어야 트리거가 '활성'으로 잡힘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;설계 의도를 정리하면 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;두 트리거의 역할 분담&lt;/b&gt; &lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Redis(큐 길이)는 &quot;지금 당장 쌓인 양&quot;을 보는 &lt;b&gt;선행 지표&lt;/b&gt;, Prometheus(대기시간)는 &quot;누적된 지연 추세&quot;를 보는 &lt;b&gt;후행 지표&lt;/b&gt;입니다. &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;큐가 순간적으로 비어도 대기시간이 길었다면 스케일을 유지합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;비대칭 스케일 정책&lt;/b&gt; &lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;scaleUp은 15초 창 + 4개/100%로 &lt;b&gt;공격적&lt;/b&gt;, scaleDown은 600초 창 + 2개로 &lt;b&gt;보수적&lt;/b&gt;입니다. &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;폭주엔 빠르게 대응하되, 축소는 천천히 해서 플래핑(flapping)을 막습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;인증 분리&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Redis 비밀번호를 ScaledObject에 직접 넣지 않고 Secret + TriggerAuthentication으로 분리해, 여러 트리거가 같은 인증을 재사용할 수 있게 했습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;참고&lt;/b&gt; &lt;br /&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;threshold&lt;/b&gt;&lt;/span&gt;와 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;activationThreshold&lt;/b&gt;&lt;/span&gt;의 차이: &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;threshold&lt;/b&gt;&lt;/span&gt;(3초)는&lt;br /&gt;&quot;몇 개로 스케일 할지&quot; 계산에 쓰는 목표값이고, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;activationThreshold&lt;/b&gt;&lt;/span&gt;(1초)는&lt;br /&gt;&quot;이 트리거를 활성으로 볼지 말지&quot;를 가르는 경계입니다. &lt;br /&gt;&lt;br /&gt;지표가 1초 이하이면 이 Prometheus 트리거는 비활성으로 간주됩니다. &lt;br /&gt;다만 minReplicaCount: 2라서 실제로 0까지 내려가진 않고, 최소 2개는 유지됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Prometheus 트리거의 쿼리는 &quot;submit_spark로 시작하는 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;태스크들의 평균 큐 대기시간(대기시간 합 &amp;divide; 건수)&quot;을 계산합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;결과적으로 Redis 큐 길이(양)와 Prometheus 대기시간(지연)을 함께 보게 되므로, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;큐가 짧아도 처리가 밀리면 스케일 업되는 구조입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&quot;양(Redis)&quot;과 &quot;지연(Prometheus)&quot;이라는 성격이 다른 두 신호를 조합하고, 스케일업&amp;middot;다운을 비대칭으로 설계해 폭주 대응력과 안정성을 동시에 잡았다&lt;/b&gt;는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;7. 명명규칙 수립 - task_id 표준화&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위 쿼리의 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;task_id=~&quot;submit_spark.*&quot;&lt;/b&gt;&lt;/span&gt; 정규식이 동작하려면 운영 규칙이 전제되어야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;SparkKubernetesOperator를 사용하는 모든 태스크는&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;task_id&lt;/span&gt;를 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;submit_spark_&lt;/span&gt;로 시작&lt;/b&gt;해야 KEDA가 누락 없이 감지합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;✅ submit_spark_job &amp;rarr; 감지됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;✅ submit_spark_daily &amp;rarr; 감지됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;⬜ 모니터링/PythonOperator 등 비-Spark 태스크 &amp;rarr; 패턴 밖 (정상, 규칙 대상 아님)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;주의&amp;nbsp;&lt;/b&gt;&lt;br /&gt;쿼리에서 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;task_id=&quot;submit_spark.*&quot;&lt;/b&gt;&lt;/span&gt;처럼 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;=&lt;/b&gt;&lt;/span&gt;(등호)를 쓰면 안 됩니다.&lt;br /&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;=&lt;/b&gt;&lt;/span&gt;는 리터럴 매칭이라 정규식으로 동작하지 않아 데이터가 영구 누락될 수 있습니다. 정규식 연산자 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;=~&lt;/b&gt;&lt;/span&gt;를 사용해야 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;KEDA 쿼리가 정규식에 의존한다면, 그 정규식이 성립하도록 하는 &quot;명명규칙&quot;이 반드시 팀 차원에서 강제되어야 한다&lt;/b&gt;는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;8. 스케일 테스트 - Worker 5개 규모 검증&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;v2 적용 후 실제 동작을 검증했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;전체 19개를 한 번에 돌리기 전에, 1단계로 5개 규모부터 확인했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;listLength=16 기준으로 Worker 5개를 목표로 큐 길이 80(= 16 x 5)을 밀어 넣었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783929357498&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl exec -n airflow airflow-helm-redis-0 -- \
  redis-cli -a '&amp;lt;REDIS_PASSWORD&amp;gt;' -n 0 rpush celery $(seq 1 80 | sed 's/^/dummy/')&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1911&quot; data-origin-height=&quot;793&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DbbEH/dJMcaf1yZeC/ssnRsXeEKp2nH8nzK6qKWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DbbEH/dJMcaf1yZeC/ssnRsXeEKp2nH8nzK6qKWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DbbEH/dJMcaf1yZeC/ssnRsXeEKp2nH8nzK6qKWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDbbEH%2FdJMcaf1yZeC%2FssnRsXeEKp2nH8nzK6qKWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1911&quot; height=&quot;793&quot; data-origin-width=&quot;1911&quot; data-origin-height=&quot;793&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위 이미지를 확인해 보시면 정상적으로 스케일업에 성공했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;⚠️ &lt;b&gt;주의&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; Airflow values.yaml에서 worker의 podAntiAffinity는 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/b&gt;&lt;/span&gt;, 즉 &lt;b&gt;&quot;같은 노드에 worker Pod 두 개 금지&quot;를 강제&lt;/b&gt;하는 설정입니다. 이 때문에 worker replica 수는 사실상 &lt;b&gt;노드 수를 넘을 수 없습니다&lt;/b&gt;. &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;maxReplicaCount: 19&lt;/b&gt;&lt;/span&gt;를 채우려면 그만큼의 노드가 필요하다는 뜻이고, 이 제약이 &quot;풀스케일 테스트를 못 한 이유&quot;와도 직결됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;9. 예상치 못한 현상 - 5에서 곧바로 줄어듦&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;진행하면서 겪은 문제를 발견했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그건 바로 5/5에 도달하자마자 곧바로 줄어든 사례입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783929639075&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;5/5 &amp;rarr; 5/1 &amp;rarr; 4/1 &amp;rarr; 3/1 ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스케일다운 안정화 기간(stabilizationWindowSeconds: 600, 10분)을 설정했는데도&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;너무 빨리 줄어드는 것처럼 보여 원인을 조사했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;1차 의심 &amp;mdash; KEDA operator 에러 로그&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;ERROR scale_handler error getting metric spec for the scaler
error: &quot;scaler with id 0 not found. Len = 0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;처음엔 멀티 트리거 구조 결함으로 의심했지만, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;전체 로그를 다시 보니 이 에러는 &lt;b&gt;단 1회만&lt;/b&gt; 발생하고 재발이 없었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;같은 grep 결과에 섞여 있던 다른 에러들은 &lt;b&gt;과거 날짜의 무관한 기존 리소스&lt;/b&gt; 에러였고, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;grep 결과를 잘못 해석한 것이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;2차 확인 &amp;mdash; HPA current 값&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;&quot;s0-redis-celery&quot; (target average value): 0 / 16
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스케일다운 시점의 current 값이 &lt;b&gt;0&lt;/b&gt;이었습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;KEDA가 잘못 읽은 게 아니라 실제로 큐가 비어 있었던 겁니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;3차 확인 &amp;mdash; Redis 직접 조회&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;kubectl exec -n airflow airflow-helm-redis-0 -- redis-cli -a '&amp;lt;REDIS_PASSWORD&amp;gt;' -n 0 llen celery
# &amp;rarr; 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;더미 데이터 80개가 실제로 큐에서 사라진 것을 직접 확인했습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;KEDA 설정(주소, DB index)과 Celery 실제 broker_url도 정확히 일치했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;b&gt;&quot;stabilizationWindow가 안 먹은 것&quot;이 아니라, 더미 데이터가 Worker에 의해 소비되어 큐가 예상보다 빨리 비워졌고, KEDA는 그 변화를 정확히 감지해 최솟값(2)으로 정상 축소한 것&lt;/b&gt;이라는 점입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;현상은 정상이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;  &lt;b&gt;참고&lt;br /&gt;&lt;/b&gt;더미 문자열은 Celery Worker가 처리 시도 후 실패/폐기하면서 곧바로 큐에서 사라집니다. 그래서 실제 부하와 달리 큐가 순식간에 비워진 것입니다. 더 정밀한 검증이 필요하다면 더미 문자열이 아닌 &lt;b&gt;실제 Celery 태스크&lt;/b&gt;로 재테스트하는 것을 권장합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;10. 검증 완료 항목&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1~5개 규모 기준으로 검증이 끝난 항목입니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 167px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;스케일업 (2&amp;rarr;5)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정상&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;podAntiAffinity 노드 분산&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정상&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;스케일다운 (5&amp;rarr;2)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정상 (큐 소진에 따른 정당한 반응)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;KEDA 멀티 트리거(Redis+Prometheus) 구조 안정성&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;결함 없음 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Spark DynamicAllocation 동시 다중 실행&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3개 SparkApplication 동시 RUNNING &amp;rarr; 충돌 없이 COMPLETED&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;statsd extraMappings 메트릭 표준화&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;단일 메트릭 통합 완료&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Prometheus 멀티 트리거 쿼리 실측&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;평균 큐 대기시간 0.66초 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;최종 검증 결과 요약입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Max Replica Count: 19
Min Replica Count: 2
List Length: 16
External Metric Names: s0-redis-celery, s1-prometheus
Triggers Types: redis, prometheus
Triggers Activity: 둘 다 Is Active: false (현재 부하 낮아 정상, 에러 없음)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;메트릭 표준화 목표를 달성했고,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Worker(KEDA, Redis+Prometheus 멀티 트리거)와 Executor(Spark DynamicAllocation)가&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;동일 기준 메트릭(&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;airflow_task_queued_duration&lt;/b&gt;&lt;/span&gt;)에 정렬되어 동작합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;11. 오토스케일링 트러블슈팅 정리&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;지금까지 오토스케일링 작업 진행하면서 트러블슈팅 내용을 공유하고자 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;증상&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;해결&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;rate() 쿼리만 빈 응답, sum()은 정상&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;curl URL의 {, [ 특수문자가 셸에서 깨짐&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;--data-urlencode 사용&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Prometheus 쿼리 결과 NaN&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;5분 윈도우 내 활동 없어 0/0 (정상 idle)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;DAG 재트리거 후 정상값(0.6초대) 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;task_id=&quot;submit_spark.*&quot; (등호) 사용&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;=는 리터럴 매칭, 정규식 아님 &amp;mdash; 데이터 영구 누락 위험&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;=~(정규식 연산자)로 수정&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;describe 결과가 v1 값(max=5) 그대로&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;sync 완료 직전 타이밍에 조회&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1~2분 후 재확인 시 v2 값(max=19) 정상&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;KEDAScalerFailed: scaler with id 0 not found&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;트리거 재구성 시 1회성 과도기 메시지&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이후 미반복, 무해 판정&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;  마무리&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는 1편에서 구축한 KEDA 위에,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Redis + Prometheus 멀티 트리거 ScaledObject를 설계하는 과정을 정리해 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스케일 대상으로는 Airflow Celery Worker를 예시로 삼았고,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Spark Executor는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;/scale&lt;/b&gt;&lt;/span&gt; 미지원 제약 때문에 DynamicAllocation으로 우회하되,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;statsd &amp;rarr; ServiceMonitor &amp;rarr; KEDA로 이어지는 파이프라인을 통해 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Worker와 같은 메트릭 기준에 정렬시키는 &quot;메트릭 표준화&quot; 전략을 택했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;특히 여기서 정리한 세 가지 설계 원칙은 Airflow가 아닌 다른 워크로드에도 그대로 적용됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;maxReplicaCount&amp;middot;listLength 같은 값은 감이 아니라 실측(worker_concurrency=16)을 기준으로 계산한다&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Redis(선행: 큐 길이) + Prometheus(후행: 대기시간) 멀티 트리거로, 스케일업은 공격적&amp;middot;다운은 보수적으로 비대칭 설계한다&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;정규식 쿼리(=~&quot;submit_spark.*&quot;)에 의존한다면, 그 전제가 되는 명명규칙을 팀 차원에서 강제한다&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정도가 되겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;참고로 이번에 남은 과제였던 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;maxReplicaCount=19&lt;/b&gt;&lt;/span&gt; 풀스케일 테스트는,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;노드 자원 포화와 podAntiAffinity(&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;required&lt;/b&gt;&lt;/span&gt;) 제약 때문에 &lt;b&gt;자원 여유를 확보한 뒤로 미뤄둔 상태&lt;/b&gt;입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 부분은 자원 확보 후 별도로 정리하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다음 글에서는 플랫폼의 다음 스택으로,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;시크릿(Secret)을 안전하게 관리하기 위한 &lt;b&gt;Sealed Secrets&lt;/b&gt;와 &lt;b&gt;Infisical&lt;/b&gt; 구축 과정을 다뤄보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서 Redis 비밀번호를 매니페스트에 평문으로 넣었던 부분(마스킹 처리한 그 값)을,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다음 편에서 제대로 된 시크릿 관리 체계로 옮기는 이야기를 이어가겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;궁금한&amp;nbsp;점이나&amp;nbsp;추가로&amp;nbsp;다뤄줬으면&amp;nbsp;하는&amp;nbsp;내용이&amp;nbsp;있다면&amp;nbsp;언제든지&amp;nbsp;편하게&amp;nbsp;말씀해&amp;nbsp;주세요!&amp;nbsp; &lt;/span&gt;&lt;/p&gt;</description>
      <category>[ Infra ]/Data Platform</category>
      <category>Data Platform</category>
      <category>Infra</category>
      <category>Kubernetes</category>
      <author>환이s</author>
      <guid isPermaLink="true">https://drg2524.tistory.com/253</guid>
      <comments>https://drg2524.tistory.com/253#entry253comment</comments>
      <pubDate>Mon, 13 Jul 2026 17:13:27 +0900</pubDate>
    </item>
    <item>
      <title>[Data Platform] KEDA 구축 가이드(Operator 설치부터 API 연동용 RBAC까지)</title>
      <link>https://drg2524.tistory.com/252</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;1076&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdlJo7/dJMcadWZg3h/cKqfXvRurNE1bybkF916e0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdlJo7/dJMcadWZg3h/cKqfXvRurNE1bybkF916e0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdlJo7/dJMcadWZg3h/cKqfXvRurNE1bybkF916e0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdlJo7%2FdJMcadWZg3h%2FcKqfXvRurNE1bybkF916e0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1918&quot; height=&quot;1076&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;1076&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Intro&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #222222; text-align: start;&quot;&gt; 안녕하세요. 환이s입니다   &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://drg2524.tistory.com/250&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 데이터 레이크하우스 플랫폼을 GitOps 방식으로 관리하기 위해&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;ArgoCD를 Kubernetes 위에 구축하고 App of Apps 패턴으로 전체 스택을 운영하는 과정을 정리해 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는 그 위에 얹을 오토스케일링 스택,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt; KEDA(Kubernetes Event-driven Autoscaling)&lt;/b&gt; 를 Harbor 레지스트리와 ArgoCD 기반으로 구축하는 과정을 정리해보려고 합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S3MvL/dJMcaiRxC6x/jfVWZsdNvjMRNPzK1LKWM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S3MvL/dJMcaiRxC6x/jfVWZsdNvjMRNPzK1LKWM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S3MvL/dJMcaiRxC6x/jfVWZsdNvjMRNPzK1LKWM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS3MvL%2FdJMcaiRxC6x%2FjfVWZsdNvjMRNPzK1LKWM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;512&quot; height=&quot;512&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;단순히 CPU/Memory 기반으로 동작하는 HPA와 달리,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;KEDA&lt;/b&gt;는 &lt;b&gt;Redis 큐 길이&lt;/b&gt;나 &lt;b&gt;Prometheus 메트릭&lt;/b&gt; 같은 실제 이벤트를 기준으로 Pod를 0~N개까지 스케일 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Airflow처럼 &quot;작업이 몰릴 때만 Worker가 필요한&quot; 워크로드에 딱 맞는 도구죠.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;KEDA 이미지&amp;middot;차트를 공식 소스(또는 사내 Harbor)로 준비하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;Helm OCI 방식으로 KEDA 본체를 설치하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;CRD를 클러스터에서 추출해 Git + ArgoCD로 별도 관리하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;662KB짜리 CRD가 ArgoCD 적용에 실패할 때 대응하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;ArgoCD sync-wave로 CRD&amp;middot;Operator&amp;middot;RBAC 기동 순서를 제어하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;외부 모듈에서 K8s API로 KEDA 리소스를 조회하기 위한 RBAC&amp;middot;Secret 구성&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;까지 순서대로 확인해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt; 이번 포스팅의 전제&lt;/b&gt;: KEDA를 완전히 백지에서 올린다기보다,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;최초엔 Helm으로 한 번 설치해 클러스터에 띄운 KEDA를, CRD만 떼어내 Git + ArgoCD로 편입(흡수)&lt;/b&gt;하는 관점으로 정리했습니다. 즉 &quot;이미 클러스터에 떠 있는 CRD를 GitOps 소스로 역이관하는&quot; 흐름이 섹션 4의 핵심 맥락입니다. &lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 편은 &lt;b&gt;&quot;설치&quot;&lt;/b&gt;에 집중하고, 실제로 Worker를 오토스케일링하는 방법은 &lt;u&gt;다음 포스팅에서 다뤄보겠습니다.&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. KEDA 아키텍처 개요&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;본격적인 설치에 앞서, 이번 구성의 전체 그림을 먼저 이해하는 것이 중요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1704&quot; data-origin-height=&quot;1025&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lLW5C/dJMcacRgEkE/XC95klV9FdzeVsxNFyxbd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lLW5C/dJMcacRgEkE/XC95klV9FdzeVsxNFyxbd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lLW5C/dJMcacRgEkE/XC95klV9FdzeVsxNFyxbd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlLW5C%2FdJMcacRgEkE%2FXC95klV9FdzeVsxNFyxbd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1704&quot; height=&quot;1025&quot; data-origin-width=&quot;1704&quot; data-origin-height=&quot;1025&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;KEDA는 크게 세 가지 컴포넌트로 구성됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;keda-operator&lt;/b&gt; : ScaledObject를 감시하고 HPA를 생성/조정하는 핵심 컨트롤러&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;keda-operator-metrics-apiserver&lt;/b&gt; : 외부 메트릭(Redis, Prometheus)을 K8s 메트릭 API로 노출&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;keda-admission-webhooks&lt;/b&gt; : ScaledObject 리소스 유효성 검증&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;KEDA 자체가 스케일을 직접 수행하는 게 아니라,&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;이벤트를 읽어서 표준 HPA를 만들어주는 &quot;브릿지&quot; 역할&lt;/b&gt;을 한다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;실제 저장소의 &lt;b&gt;KEDA&lt;/b&gt; 디렉토리 구조는 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783665510646&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;services/keda/
├── crds/
│   ├── keda-crds-only.yaml         # CRD 5개 (클러스터에서 추출)
│   └── keda-crds-scaledjobs.yaml   # scaledjobs CRD 1개 (662KB, 별도 분리)
├── helm/
│   ├── install.sh                  # OCI 방식 Helm 설치 스크립트
│   └── values.yaml                 # Harbor 이미지 + nodeSelector 설정
└── manifests/
    └── rbac/
        ├── 10_keda_api_rbac.yaml   # ClusterRole + Binding (API 연동 권한)
        └── 20_keda_api_secret.yaml # keda-operator SA 토큰 Secret&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정리하면 &lt;b&gt;KEDA&lt;/b&gt;는 크게 세 갈래로 구성했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;crds/&lt;/b&gt; &amp;mdash; CRD는 Helm에서 빼내 클러스터에서 추출한 yaml로 별도 관리 (섹션 4)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;helm/&lt;/b&gt; &amp;mdash; KEDA 본체는 Helm OCI 방식으로 설치 (섹션 3)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;manifests/rbac/&lt;/b&gt; &amp;mdash; 외부 모듈에서 K8s API로 KEDA 리소스를 조회하기 위한 RBAC + Secret 구성 (섹션 7)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; &lt;b&gt;참고&lt;/b&gt;: &lt;b&gt;maxReplicaCount&lt;/b&gt;:&lt;b&gt;4&lt;/b&gt; 같은 스케일 값은 KEDA 설치 디렉토리(&lt;b&gt;services/keda/&lt;/b&gt;)가 아니라, &lt;b&gt;Airflow Worker를 대상으로 하는 ScaledObject 리소스&lt;/b&gt;에 들어갑니다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ScaledObject&lt;/b&gt;는 스케일 대상(Airflow)과 함께 두는 것이 관리상 자연스러워, &lt;b&gt;services/airflow/&lt;/b&gt;쪽에 위치합니다. 즉 &quot;&lt;b&gt;KEDA 엔진&lt;/b&gt;&quot;과 &quot;&lt;b&gt;스케일 규칙(ScaledObject)&lt;/b&gt;&quot;의 경로가 분리되어 있는 구조입니다. &lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. 환경 준비 - 이미지&amp;middot;차트 소스 정하기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;KEDA는 공식 Helm 저장소와 컨테이너 이미지를 제공하므로,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;기본적으로는 공식 소스에서 바로 받아 설치하는 것이 가장 간단&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;따라 하시는 분들은 별도 레지스트리 없이 아래 공식 소스만으로 충분합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783665799877&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 공식 Helm 저장소 (KEDA)
helm repo add kedacore https://kedacore.github.io/charts
helm repo update

# 공식 이미지 (ghcr.io)
ghcr.io/kedacore/keda:2.19.0
ghcr.io/kedacore/keda-metrics-apiserver:2.19.0
ghcr.io/kedacore/keda-admission-webhooks:2.19.0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다만 &lt;b&gt;글쓴이는 이미지를 사내 Harbor로 미러링해 관리&lt;/b&gt;하고 있어,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 글의 이후 예시는 Harbor 기준으로 작성돼 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;버전 고정&amp;middot;보안 스캔&amp;middot;내부 배포 일관성 측면에서 유리해서 택한 방식이고,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;공식 소스를 쓰신다면 뒤에 나오는 &lt;b&gt;harbor.local/...&lt;/b&gt; 경로만 위 공식 경로로 바꿔 읽으시면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;먼저 K8s 버전과 KEDA 호환성부터 확인했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;K8s 버전: &lt;b&gt;v1.30.14&lt;/b&gt; &amp;rarr; KEDA &lt;b&gt;2.19.0&lt;/b&gt; 호환 확인&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;특정 worker 노드에 &lt;b&gt;keda.role=keda&lt;/b&gt; 레이블 추가 (KEDA Pod 전용 배치)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;글쓴이 환경 기준으로는, 공식 이미지&amp;middot;차트를 사내 Harbor로 옮겨(mirror) 아래처럼 두고 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783665878526&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Helm Chart (OCI 형식으로 push)
harbor.local/helm-chart/keda:2.19.0

# 이미지 3종 (ghcr.io &amp;rarr; Harbor)
harbor.local/library/keda/keda:2.19.0
harbor.local/library/keda/keda-metrics-apiserver:2.19.0
harbor.local/library/keda/keda-admission-webhooks:2.19.0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; 즉, 여기서 확인할 수 있는 핵심은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;KEDA 이미지&amp;middot;차트는 공식 소스에서 바로 받아도 되고, 레지스트리를 사내에서 운영한다면 Harbor 등으로 미러링해 내부 경로만 바라보게 통일할 수도 있다&lt;/b&gt;는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3. Helm 설정 - values.yaml&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Harbor 이미지를 바라보도록 하고, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;keda.role=keda&lt;/b&gt; 노드에만 배치되도록 nodeSelector를 지정한 yaml입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783665994463&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;operator:
  nodeSelector:
    keda.role: keda
  image:
    registry: harbor.local
    repository: library/keda/keda
    tag: &quot;2.19.0&quot;

metricsServer:
  nodeSelector:
    keda.role: keda
  image:
    registry: harbor.local
    repository: library/keda/keda-metrics-apiserver
    tag: &quot;2.19.0&quot;

webhooks:
  nodeSelector:
    keda.role: keda
  image:
    registry: harbor.local
    repository: library/keda/keda-admission-webhooks
    tag: &quot;2.19.0&quot;

logging:
  operator:
    level: info
  metricServer:
    level: 0
  webhooks:
    level: info

crds:
  install: false&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;  참고&lt;/b&gt;: &lt;b&gt;crds.install: false&lt;/b&gt;가 핵심 포인트입니다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;KEDA Helm Chart는 기본적으로 CRD를 함께 설치하지만, 포스팅 작성할 때는 CRD를 ArgoCD로 별도 관리하기 위해 여기서 비활성화했습니다. 이유는 4&amp;middot;5번 섹션에서 이어집니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;설치 스크립트는 OCI 레지스트리에서 차트를 가져오도록 구성했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783666078240&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash

NAMESPACE=keda
RELEASE_NAME=keda
REGISTRY=harbor.local
CHART_VERSION=2.19.0

helm registry login $REGISTRY

helm upgrade --install $RELEASE_NAME \
  oci://$REGISTRY/helm-chart/keda \
  --version $CHART_VERSION \
  --namespace $NAMESPACE \
  --create-namespace \
  -f values.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;4. CRD 설치 - 클러스터에서 추출해 GitOps로 이관하기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;앞서&lt;b&gt; values.yaml&lt;/b&gt;에서 &lt;b&gt;crds.install: false&lt;/b&gt;로 CRD를 Helm 설치 대상에서 뺐습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;목적은 CRD를 Operator 라이프사이클과 분리해 &lt;b&gt;Git + ArgoCD로 독립 관리&lt;/b&gt;하기 위함입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Helm이 CRD까지 함께 관리하면 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;차트를 업그레이드&amp;middot;삭제할 때 CRD가 영향을 받을 수 있고,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;sync-wave로 기동 순서를 제어하기도 어렵기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;방식은 이렇습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;글쓴이 환경은 &lt;b&gt;KEDA가 이미 Helm으로 한 번 설치돼 CRD가 클러스터에 등록된 상태&lt;/b&gt;였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그래서 신규로 CRD yaml을 어디선가 받아오는 대신,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;클러스터에 이미 떠 있는 CRD를 kubectl로 그대로 추출&lt;/b&gt;해 Git 저장소에 올리고,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이후부터는 Helm이 아닌 ArgoCD가 이 CRD를 관리하도록 소유권을 넘겼습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;이미 잘 돌던 리소스를 그대로 Git으로 역이관&quot;하니, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;외부에서 새로 받아온 매니페스트와의 버전&amp;middot;필드 불일치 걱정 없이 깔끔했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;  &lt;b&gt;참고(순서 정리)&lt;/b&gt;: &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;① 최초 1회 Helm으로 CRD 포함 설치(&lt;b&gt;crds.install: true 또는 --include-crds&lt;/b&gt;) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;② 클러스터에서 CRD 추출&amp;middot;정제 후 Git 커밋 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;③ &lt;b&gt;crds.install: false&lt;/b&gt;로 전환해 Helm에서 CRD를 떼어냄 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;④ 이후 CRD 소유권은 ArgoCD가 전담. 이 글의&lt;b&gt; values.yaml&lt;/b&gt;(섹션 3)은 ③ 이후 상태 기준입니다. 완전 신규(그린필드) 구축이라면, 클러스터 추출 대신 &lt;b&gt;helm template --include-crds&lt;/b&gt;로 순수 yaml을 렌더링해 Git에 올리는 방법도 동일하게 가능합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Step 1. 클러스터에서 KEDA CRD 목록 확인&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;kubectl get crd | grep keda.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;cloudeventsources.eventing.keda.sh
clustercloudeventsources.eventing.keda.sh
clustertriggerauthentications.keda.sh
scaledjobs.keda.sh
scaledobjects.keda.sh
triggerauthentications.keda.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Step 2. scaledjobs를 제외한 CRD 5개 추출&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;for crd in \
  cloudeventsources.eventing.keda.sh \
  clustercloudeventsources.eventing.keda.sh \
  clustertriggerauthentications.keda.sh \
  scaledobjects.keda.sh \
  triggerauthentications.keda.sh; do
    kubectl get crd $crd -o yaml &amp;gt;&amp;gt; keda-crds-only.yaml
    echo &quot;---&quot; &amp;gt;&amp;gt; keda-crds-only.yaml
done
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Step 3. scaledjobs CRD 단독 추출&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;# scaledjobs는 662KB로 용량이 커서 ArgoCD 적용 실패 &amp;rarr; 별도 파일로 분리
kubectl get crd scaledjobs.keda.sh -o yaml &amp;gt; keda-crds-scaledjobs.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Step 4. 런타임 필드 정제 &lt;span style=&quot;color: #ee2323;&quot;&gt;(중요)&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;kubectl get -o yaml&lt;/b&gt;로 뽑은 CRD에는 클러스터가 실행 중에 붙인 런타임 메타데이터가 섞여 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이걸 그대로 Git에 올리면 ArgoCD가 계속 &lt;b&gt;OutOfSync&lt;/b&gt;로 감지하거나 적용에 실패하므로,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아래 필드들을 제거해 &quot;선언적 스펙&quot;만 남겨야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #dddddd; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;metadata.annotations&lt;/b&gt;의 &lt;b&gt;kubectl.kubernetes.io/last-applied-configuration&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #dddddd; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;metadata.managedFields&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #dddddd; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;metadata.resourceVersion, metadata.uid, metadata.generation, metadata.creationTimestamp&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;status&lt;/b&gt; &lt;/span&gt;블록 전체&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;가장 간편한 건 &lt;b&gt;kubectl-neat&lt;/b&gt;(krew 플러그인)로, 추출과 동시에 런타임 필드를 알아서 걷어내 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 방법 A) kubectl-neat 사용 (권장) &amp;mdash; 추출과 동시에 정제
kubectl get crd scaledjobs.keda.sh -o yaml | kubectl neat &amp;gt; keda-crds-scaledjobs.yaml

# krew/neat 설치가 어려운 환경이라면, --show-managed-fields=false로 managedFields만이라도 제외
kubectl get crd scaledjobs.keda.sh -o yaml --show-managed-fields=false &amp;gt; keda-crds-scaledjobs.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;kubectl-neat&lt;/b&gt;&lt;/span&gt; 없이 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;yq&lt;/b&gt;&lt;/span&gt;로 직접 정제한다면,&lt;b&gt; keda-crds-only.yaml&lt;/b&gt;은&lt;b&gt; ---&lt;/b&gt;로 이어진&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;멀티도큐먼트(5개 문서)&lt;/b&gt;&lt;/span&gt;라는 점에 주의해야 합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;일반 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;yq eval ... -i&lt;/b&gt;&lt;/span&gt;는 첫 문서만 처리될 수 있어&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;eval-all&lt;/b&gt;&lt;/span&gt;로 모든 문서를 순회해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;# 방법 B) yq로 정제 &amp;mdash; eval-all로 멀티도큐먼트 전체 순회
yq eval-all '
  del(.metadata.annotations.&quot;kubectl.kubernetes.io/last-applied-configuration&quot;) |
  del(.metadata.managedFields) |
  del(.metadata.resourceVersion) |
  del(.metadata.uid) |
  del(.metadata.generation) |
  del(.metadata.creationTimestamp) |
  del(.status)
' -i keda-crds-only.yaml

# scaledjobs 단일 파일도 동일하게 정제
yq eval-all '
  del(.metadata.annotations.&quot;kubectl.kubernetes.io/last-applied-configuration&quot;) |
  del(.metadata.managedFields) |
  del(.metadata.resourceVersion) |
  del(.metadata.uid) |
  del(.metadata.generation) |
  del(.metadata.creationTimestamp) |
  del(.status)
' -i keda-crds-scaledjobs.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;  &lt;b&gt;참고&lt;/b&gt;: 정제와 &lt;b&gt;ServerSideApply&lt;/b&gt;는 역할이 다릅니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정제(Step 4)는 Git에 올라가는 소스 파일을 깨끗하게 만드는 작업이고(OutOfSync 방지),&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;뒤 섹션 5의 &lt;b&gt;ServerSideApply=true&lt;/b&gt;는 ArgoCD가 apply 하는 시점에 &lt;b&gt;last-applied-configuration&lt;/b&gt; 어노테이션으로 스펙 전체를 다시 써넣지 않게 하는 장치입니다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하나는 소스 정리, 하나는 적용 방식이므로, 둘 다 있어야&lt;b&gt; 662KB scaledjobs&lt;/b&gt;가 안정적으로 처리됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;결과적으로 CRD 파일은 두 개로 나뉘었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;keda-crds-only.yaml&lt;/b&gt; &amp;mdash; CRD 5개 (&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;cloudeventsources, clustercloudeventsources, clustertriggerauthentications, scaledobjects, triggerauthentications&lt;/b&gt;&lt;/span&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;keda-crds-scaledjobs.yaml&lt;/b&gt; &amp;mdash; &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;scaledjobs&lt;/b&gt; &lt;/span&gt;CRD 1개 (용량 이슈로 별도 분리)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;⚠️ &lt;b&gt;주의&lt;/b&gt;: &lt;b&gt;scaledjobs&lt;/b&gt; &lt;b&gt;CRD&lt;/b&gt;는 파일 크기가 &lt;b&gt;662KB&lt;/b&gt;에 달합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;특히 &lt;b&gt;last-applied-configuration&lt;/b&gt; 어노테이션은 CRD 스펙 전체를 문자열로 담기 때문에, 이걸 정제하지 않으면 이 어노테이션만으로도 Kubernetes의 어노테이션 크기 제한(262144 B)을 넘겨 적용에 실패합니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; Step 4의 정제와, 뒤에서 다룰 &lt;b&gt;ServerSideApply=true&lt;/b&gt;를 함께 써야 &lt;b&gt;662KB&lt;/b&gt; CRD가 안정적으로 적용됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;CRD는 Helm 관리에서 떼어내 클러스터에서 추출한 순수 선언 yaml로 Git에 두되, &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;last-applied-configuration 같은 런타임 필드는 반드시 정제하고, 용량이 큰 CRD는 쪼개야 한다&lt;/b&gt;는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;설치 후 CRD 6개가 정상 등록됐는지 확인했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;cloudeventsources.eventing.keda.sh
clustercloudeventsources.eventing.keda.sh
clustertriggerauthentications.keda.sh
scaledjobs.keda.sh
scaledobjects.keda.sh
triggerauthentications.keda.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;5. ArogCD App 등록 - sync-wave로 기동 순서 제어&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이제 앞서 준비한 KEDA를 &lt;b&gt;이전 포스팅에서 구축한 ArgoCD에서 App으로 관리할 수 있게 설정&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;지난 글에서 ArgoCD를 App of Apps 패턴으로 올려뒀으니, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;KEDA도 그 아래에 Application으로 등록해 GitOps 흐름에 편입시키는 것이죠.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;여기가 이번 설치에서 가장 중요한 부분입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;KEDA Operator는 기동 시점에 CRD가 이미 존재해야 정상 동작합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;CRD보다 Operator가 먼저 뜨면 CrashLoopBackOff에 빠집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그래서 KEDA를 &lt;b&gt;3개의 ArgoCD Application으로 쪼개고&lt;/b&gt;, sync-wave로 순서를 명시적으로 갈랐습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;gitops/apps/keda/
├── keda_crds.yaml   (sync-wave: 130)  &amp;rarr; CRD 먼저
├── keda_helm.yaml   (sync-wave: 131)  &amp;rarr; Operator 나중
└── keda_rbac.yaml   (sync-wave: 132)  &amp;rarr; RBAC 마지막 (namespace 생성 후)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;  &lt;b&gt;참고: sync-wave&lt;/b&gt; 숫자가 낮을수록 먼저 적용됩니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;CRD(130) &amp;rarr; Helm(131) &amp;rarr; RBAC(132)&lt;/b&gt; 순서로 배치해서 &quot;&lt;b&gt;CRD가 완전히 준비된 뒤 Operator가 기동 되고, namespace/SA가 생긴 뒤 RBAC이 붙도록&lt;/b&gt;&quot; 보장한 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;① keda_crds &amp;mdash; CRD 먼저 (wave 130)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;CRD는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;services/keda/crds&lt;/b&gt;&lt;/span&gt; 디렉토리를 통째로(&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;recurse: true&lt;/b&gt;&lt;/span&gt;) 적용합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;662KB짜리 &lt;b&gt;scaledjobs&lt;/b&gt; 대응을 위해 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;ServerSideApply=true&lt;/b&gt;&lt;/span&gt;가 핵심입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: keda-crds
  namespace: argocd
  annotations:
    argocd.argoproj.io/sync-wave: &quot;130&quot;
spec:
  project: default
  sources:
    - repoURL: 'http://://data-platform.git'
      targetRevision: main
      path: services/keda/crds
      directory:
        recurse: true
  destination:
    server: '&amp;lt;https://kubernetes.default.svc&amp;gt;'
    namespace: keda
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true      # 662KB scaledjobs 어노테이션 제한 회피
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;⚠️ &lt;b&gt;주의&lt;/b&gt;: &lt;b&gt;ServerSideApply=true&lt;/b&gt;는 섹션 4 정제와 짝을 이루는 &lt;b&gt;적용 시점의 안전장치&lt;/b&gt;입니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정제(Step 4)로 소스 파일에서 어노테이션을 걷어내도,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; ArgoCD가 &lt;b&gt;client-side apply&lt;/b&gt;로 붙이면 apply 과정에서 &lt;b&gt;last-applied-configuration&lt;/b&gt;에 스펙 전체를 다시 써넣습니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; &lt;b&gt;ServerSideApply&lt;/b&gt;를 켜면 이 어노테이션을 아예 만들지 않고 서버 측에서 필드를 병합하므로, &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;662KB scaledjobs처럼&lt;/b&gt; 큰 CRD도 어노테이션 크기 제한에 걸리지 않고 안정적으로 적용됩니다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉 &lt;b&gt;&quot;파일 분리(섹션 4) + 소스 정제(Step 4) + ServerSideApply(적용)&quot; &lt;/b&gt;세 겹이 함께 작동합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;② keda_helm &amp;mdash; Operator (wave 131, multi-source)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;KEDA 본체는 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Harbor OCI 차트 + Git values.yaml을 분리 참조&lt;/b&gt;하는 multi-source 패턴으로 설치합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;차트는 Harbor에서, 설정값은 Git 저장소에서 각각 가져와 조합하는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: keda-helm
  namespace: argocd
  annotations:
    argocd.argoproj.io/sync-wave: &quot;131&quot;
spec:
  project: default
  sources:
    - repoURL: 'harbor.local/helm-chart'   # Harbor OCI 레지스트리의 차트
      chart: keda
      targetRevision: 2.19.0
      helm:
        valueFiles:
          - $values/services/keda/helm/values.yaml   # &amp;darr; Git values 참조
    - repoURL: 'http://://data-platform.git'
      targetRevision: main
      ref: values                          # $values 로 위에서 참조됨
  destination:
    server: '&amp;lt;https://kubernetes.default.svc&amp;gt;'
    namespace: keda
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;  &lt;b&gt;참고&lt;/b&gt;: multi-source의 &lt;b&gt;ref: values&lt;/b&gt; + &lt;b&gt;$values/...&lt;/b&gt; 조합이 포인트입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;차트는 Harbor에, values.yaml은 Git에&quot; 두면서도 하나의 Application으로 묶을 수 있어, 차트 버전과 설정을 각자의 저장소에서 독립적으로 관리할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;③ keda_rbac &amp;mdash; API 연동용 RBAC (wave 132)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;앞서 만든 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;services/keda/manifests/rbac&lt;/b&gt;&lt;/span&gt;를 마지막 wave로 적용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;namespace와 &lt;b&gt;keda-operator&lt;/b&gt; ServiceAccount가 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;생성된 뒤에 붙어야 하므로 132로 뒤에 뒀고, 타이밍 이슈에 대비해 retry를 넣었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: keda-rbac
  namespace: argocd
  annotations:
    argocd.argoproj.io/sync-wave: &quot;132&quot;   # namespace 생성 후 rbac
spec:
  project: default
  source:
    repoURL: http://://data-platform.git
    targetRevision: main
    path: services/keda/manifests/rbac
  destination:
    server: &amp;lt;https://kubernetes.default.svc&amp;gt;
    namespace: keda
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    retry:
      limit: 5
      backoff:
        duration: 15s
        factor: 2
        maxDuration: 2m
    syncOptions:
      - CreateNamespace=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3개 App이 모두 &lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;Healthy&lt;/span&gt;&lt;/b&gt; + &lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;Synced&lt;/span&gt;&lt;/b&gt; 상태가 되고,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Pod 3개(&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;keda-operator&lt;/b&gt;&lt;/span&gt;, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;keda-admission-webhooks&lt;/b&gt;&lt;/span&gt;, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;keda-operator-metrics-apiserver&lt;/b&gt;&lt;/span&gt;)가 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;Running&lt;/b&gt;&lt;/span&gt;이면 설치 완료입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;CRD&amp;middot;Operator&amp;middot;RBAC를 한 App에 묶지 말고 3개로 쪼갠 뒤 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;sync-wave(130&amp;rarr;131&amp;rarr;132)로 순서를 강제해야, &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;CrashLoop과 SA 미존재 같은 순서 의존 문제를 피할 수 있다&lt;/b&gt;는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;6. 설치 트러블슈팅 정리&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;글쓴이도 여러 착오 끝에 구축할 수 있었는데, 오는 과정에서 마주친 이슈들을 표로 정리했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 227px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 19.8837%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 45.3488%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.6512%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;해결&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px; width: 19.8837%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;keda-operator &lt;br /&gt;CrashLoopBackOff&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 45.3488%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;CRD보다 Operator가 먼저 기동&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px; width: 34.6512%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;CRD App(wave 130) &amp;rarr; Helm App(wave 131) 순서 분리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px; width: 19.8837%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;scaledjobs &lt;br /&gt;CRD 미설치&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 45.3488%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;파일 크기 662KB로 ArgoCD 적용 실패&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 34.6512%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;별도 파일로 분리 + ServerSideApply=true 적용&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px; width: 19.8837%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;CRD 적용 후 계속 &lt;br /&gt;OutOfSync&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 45.3488%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;kubectl get -o yaml 추출물에 런타임 필드(managedFields, resourceVersion 등) 잔존&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 34.6512%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Step 4 정제로 런타임 필드 제거 후 커밋&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px; width: 19.8837%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;scaledjobs &lt;br /&gt;어노테이션 제한 초과&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 45.3488%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;last-applied-configuration에 CRD 스펙 전체가 문자열로 포함&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 34.6512%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;해당 어노테이션 제거 + ServerSideApply=true&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px; width: 19.8837%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;RBAC 적용 시 &lt;br /&gt;SA 미존재 에러&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 45.3488%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;keda-operator SA 생성 전 RBAC 먼저 적용&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 34.6512%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;RBAC을 wave 132로 뒤에 두고 retry 설정&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;7. 외부 모듈에서 KEDA 리소스를 조회하기 위한 RBAC&amp;middot;Secret 구성&lt;/span&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;앞선 섹션들에서 KEDA 리소스는 전부 kubectl이나 ArgoCD를 통해 다뤘습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그런데 플랫폼을 운영하다 보면, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;kubectl이 아니라 별도 모듈(백오피스&amp;middot;대시보드&amp;middot;운영 자동화 등)에서 KEDA 리소스 상태를 직접 조회하고 싶은 상황&lt;/b&gt;이 생깁니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;예를 들면,&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;사내 백오피스/대시보드에서 현재 ScaledObject 목록과 스케일 상태를 보여주고 싶을 때&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;운영 자동화 모듈이 특정 조건에서 ScaledObject 상태를 확인하고 싶을 때&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;CI 파이프라인에서 KEDA 리소스 상태를 점검하고 싶을 때&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Kubernetes API 서버는 &lt;b&gt;모든 리소스를 API 엔드포인트로 노출&lt;/b&gt;하므로, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;모듈이 kubectl 없이 HTTP 요청 + 토큰만으로 KEDA 리소스(scaledobjects 등)를 조회할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;문제는 &lt;b&gt;인증/인가&lt;/b&gt;입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아무 토큰이나 API 서버에 보낸다고 되는 게 아니라, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;이 토큰이 keda.sh 리소스에 접근할 권한이 있는가&quot;를 RBAC으로 열어주고, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 권한을 담은 토큰(Secret)을 발급해줘야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그래서 이번에는 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 &lt;b&gt;연동에 앞서 필요한 전용 RBAC + SA 토큰(Secret)을 추가로 구성&lt;/b&gt;해봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아직 실제 모듈 연동까지 붙인 단계는 아니고, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;모듈이 붙으면 쓸 권한&amp;middot;토큰을 미리 열어두고, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그게 의도대로 동작하는지 검증&quot;하는 데까지가 이번 범위입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;OSS 데이터 플랫폼 리소스를 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;API로 연동하려는 분들도 아래 패턴을 그대로 적용하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;파일은 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;services/keda/manifests/rbac/&lt;/b&gt;&lt;/span&gt; 아래 두 개, ArgoCD &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;keda-rbac&lt;/b&gt;&lt;/span&gt; App(wave 132)으로 배포됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;① ClusterRole + Binding &amp;mdash; 권한 열기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;keda.sh&lt;/b&gt; API 그룹의 리소스에 대한 권한을 정의하고, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;keda-operator ServiceAccount&lt;/b&gt;에 바인딩합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: keda-api-reader
rules:
  - apiGroups: [&quot;keda.sh&quot;]
    resources: [&quot;scaledobjects&quot;, &quot;scaledjobs&quot;, &quot;triggerauthentications&quot;, &quot;clustertriggerauthentications&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;, &quot;create&quot;, &quot;update&quot;, &quot;patch&quot;, &quot;delete&quot;]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: keda-api-reader-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: keda-api-reader
subjects:
  - kind: ServiceAccount
    name: keda-operator
    namespace: keda
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;⚠️ &lt;b&gt;주의&lt;/b&gt;: 예시는 &lt;b&gt;create/update/patch/delete&lt;/b&gt;까지 전부 열려 있어 KEDA 리소스를 &lt;b&gt;변경&lt;/b&gt;할 수 있는 강한 권한입니다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;조회만 필요하다면 &lt;b&gt;verbs&lt;/b&gt;를&lt;b&gt; [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]&lt;/b&gt;로 최소화하세요. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;최소 권한 원칙(least privilege)&lt;/b&gt;이 보안의 기본입니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;② SA 토큰 Secret &amp;mdash; 인증 수단 발급&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;keda-operator ServiceAccount&lt;/b&gt;에 대한 &lt;b&gt;장기 토큰(long-lived token)&lt;/b&gt; 을 발급받기 위한 &lt;b&gt;Secret&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Secret
metadata:
  name: keda-operator-token
  namespace: keda
  annotations:
    kubernetes.io/service-account.name: keda-operator
type: kubernetes.io/service-account-token
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;③ 토큰 꺼내기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;발급된 토큰과 API 서버 주소를 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;# SA 토큰 추출
TOKEN=$(kubectl get secret keda-operator-token -n keda \
  -o jsonpath='{.data.token}' | base64 -d)

# API 서버 주소 확인
APISERVER=$(kubectl config view --minify \
  -o jsonpath='{.clusters[0].cluster.server}')

echo $APISERVER   # 예: https://&amp;lt;APISERVER_HOST&amp;gt;:6443
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;④ 권한이 제대로 열렸는지 검증 (Postman)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;모듈을 붙이기 전에, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;발급한 RBAC&amp;middot;토큰으로 실제 조회가 되는지부터 확인해 봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;모듈 코드를 짜기 전에 권한 설정만 먼저 검증해 두면,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;나중에 연동할 때&lt;b&gt; &quot;권한 문제인지 코드 문제인지&quot;&lt;/b&gt; 헷갈릴 일이 줄어듭니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Postman으로 확인한 설정은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Method / URL&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1783668596780&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET https://&amp;lt;APISERVER_HOST&amp;gt;:6443/apis/keda.sh/v1alpha1/namespaces/airflow/scaledobjects&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Authorization 탭&lt;/b&gt; &amp;rarr; Type: &lt;b&gt;Bearer Token&lt;/b&gt; &amp;rarr; Token 칸에 위에서 꺼낸 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;$TOKEN&lt;/b&gt;&lt;/span&gt; 붙여 넣기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Settings&lt;/b&gt; &amp;rarr; 사내 클러스터가 자체 서명 인증서를 쓰면 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;SSL certificate verification&lt;/b&gt;&lt;/span&gt;을 끄거나, CA 인증서를 등록&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정상이라면 &lt;b&gt;airflow&lt;/b&gt; 네임스페이스의 ScaledObject 목록이 JSON으로 돌아옵니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;apiVersion&quot;: &quot;keda.sh/v1alpha1&quot;,
  &quot;kind&quot;: &quot;ScaledObjectList&quot;,
  &quot;items&quot;: [
    {
      &quot;metadata&quot;: { &quot;name&quot;: &quot;airflow-worker-scaler&quot;, &quot;namespace&quot;: &quot;airflow&quot; },
      &quot;spec&quot;: { &quot;minReplicaCount&quot;: 2, &quot;maxReplicaCount&quot;: 19 }
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1902&quot; data-origin-height=&quot;1071&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cu3oZn/dJMcajpgOkZ/hzsAwXK99aWjUaAZeVLub1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cu3oZn/dJMcajpgOkZ/hzsAwXK99aWjUaAZeVLub1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cu3oZn/dJMcajpgOkZ/hzsAwXK99aWjUaAZeVLub1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcu3oZn%2FdJMcajpgOkZ%2FhzsAwXK99aWjUaAZeVLub1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1902&quot; height=&quot;1071&quot; data-origin-width=&quot;1902&quot; data-origin-height=&quot;1071&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;curl로도 동일하게 확인할 수 있습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;실제 모듈에서 호출할 때도 결국 이 형태(엔드포인트 + Bearer 토큰)를 그대로 쓰게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;curl -sk -H &quot;Authorization: Bearer $TOKEN&quot; \
  &quot;$APISERVER/apis/keda.sh/v1alpha1/namespaces/airflow/scaledobjects&quot; | jq .
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; &lt;b&gt;K8s API 서버가 모든 리소스를 API로 노출하므로, 외부 모듈이 KEDA 리소스를 조회하려면 전용 RBAC + SA 토큰(Secret)만 미리 열어두면 되고, 연동 전에 Postman/curl로 그 권한이 제대로 동작하는지 검증해 둘 수 있다&lt;/b&gt;는 점입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그리고 그 편리함만큼 &lt;b&gt;권한 최소화와 토큰 관리가 보안의 핵심&lt;/b&gt;이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;  마무리&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는 &lt;b&gt;KEDA&lt;/b&gt;를 &lt;b&gt;Harbor + ArgoCD&lt;/b&gt; 기반으로 구축하는 과정을 정리해 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정리하면,&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;CRD는 Helm 관리에서 떼어내 클러스터에서 추출한 뒤 런타임 필드를 정제해 Git에 두고, 용량 큰 CRD(scaledjobs 662KB)는 쪼갠 뒤 ServerSideApply로 적용한다&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;CRD&amp;middot;Operator&amp;middot;RBAC를 3개 App으로 나누고 sync-wave(130&amp;rarr;131&amp;rarr;132)로 순서를 강제해야 CrashLoop&amp;middot;SA 미존재를 피한다&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;외부 모듈에서 KEDA 리소스를 조회하려면 전용 RBAC + SA 토큰(Secret)을 미리 열어두면 되고, 연동 전 Postman/curl로 권한이 제대로 동작하는지 검증해 둘 수 있다&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정도가 되겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다음 글에서는 이렇게 설치한 KEDA를 실제로 활용해서,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Airflow Celery Worker를 Redis + Prometheus 멀티 트리거로 오토스케일링&lt;/b&gt;하는 과정을 다뤄보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;궁금한 점이나 추가로 다뤄줬으면 하는 내용이 있다면 언제든지 편하게 말씀해 주세요 &lt;/span&gt;&lt;/p&gt;</description>
      <category>[ Infra ]/Data Platform</category>
      <category>Data Platform</category>
      <category>Infra</category>
      <category>Kubernetes</category>
      <author>환이s</author>
      <guid isPermaLink="true">https://drg2524.tistory.com/252</guid>
      <comments>https://drg2524.tistory.com/252#entry252comment</comments>
      <pubDate>Fri, 10 Jul 2026 16:38:40 +0900</pubDate>
    </item>
    <item>
      <title>『생각이 너무 많은 어른들을 위한 심리학』 리뷰 | 생각을 멈출 순 없어도 바라볼 순 있다</title>
      <link>https://drg2524.tistory.com/251</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;663&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/epg0xz/dJMcaiYa8Wm/MpINoTKBQiidhZ19HHeoL0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/epg0xz/dJMcaiYa8Wm/MpINoTKBQiidhZ19HHeoL0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/epg0xz/dJMcaiYa8Wm/MpINoTKBQiidhZ19HHeoL0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fepg0xz%2FdJMcaiYa8Wm%2FMpINoTKBQiidhZ19HHeoL0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;458&quot; height=&quot;663&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;663&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;i&gt;&lt;b&gt;시작하기 전에&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그날은 읽던 책을 다 끝내서&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;새 책을 한 권 사러 교보문고에 갔다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;목적이 있는 걸음이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;사려던 책은 따로 있었고,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 앞으로 곧장 가던 길이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그런데 매대를 지나치다&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;제목 하나가 눈에 걸렸다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; 『생각이 너무 많은 어른들을 위한 심리학』 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;생각이 너무 많은 어른.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 한 줄이&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;괜히 발이 멈췄다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 자리에 선 채로&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;몇 장을 넘겨봤다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;몇 문장 읽지 않았는데&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이상하게 계속 읽고 싶어져서,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;원래 사려던 책과 함께&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 책도 집어 들고 나왔다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;생각이 많은 게 나쁜 거냐고?&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;집에 돌아와&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;의자에 앉아 책을 펼쳤다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;읽다 보니&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;문득 스무 살의 내가 스쳐 지나갔다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;고향을 떠나&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;낯선 도시에서 혼자 살기 시작했던 그때.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;전공은 진작에 접어두고&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;해보고 싶은 건 다 해봐야 직성이 풀렸다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이것도 궁금하고&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;저것도 해보고 싶어서&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;한자리에 오래 머무는 법이 없었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그때마다 나는&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;머릿속으로 수십 번을 굴렸다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이게 맞을까,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;지금 이걸 시작해도 될까,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;남들은 벌써 자리를 잡았는데&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;나만 늦은 건 아닐까.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;책은 조용히 짚어준다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;문제는 생각의 양이 아니라&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 생각이 흘러가는 방향이라고.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;생각이 많아질수록&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;긍정적인 생각보다&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;부정적인 생각을 더 많이 하게 되고,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;한번 시작하면&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;좀처럼 멈출 줄을 모른다는 것.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;가장 오래 마음에 남았던 이야기&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;페이지를 넘기다&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;'슬플 때는 굳이 강한 척하지 말 것'&lt;/b&gt;에서 손이 멈췄다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;411&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6l5vd/dJMcah6bj2v/nFgPhBiUV46wPv0CXskl5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6l5vd/dJMcah6bj2v/nFgPhBiUV46wPv0CXskl5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6l5vd/dJMcah6bj2v/nFgPhBiUV46wPv0CXskl5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6l5vd%2FdJMcah6bj2v%2FnFgPhBiUV46wPv0CXskl5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;411&quot; height=&quot;546&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;411&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;저자는 말한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;우리는 어릴 때부터&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;울면 나약한 사람이라고 배우며 자라서,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;어른이 되면&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아파도 괜찮은 척,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;잘 견디는 척하게 된다고.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그럴수록&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;마음의 상처만 깊어질 뿐이라고.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 문장을 읽는데&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;문득 영등포가 떠올랐다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;대전으로 내려오기 직전까지&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;마지막으로 자취하던 동네였다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 시절 나는&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;혼자 참 많이도 돌아다녔다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;특별한 목적도 없이&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;거리를 걷고,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;지나가는 사람들을 이유 없이 구경하고,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;가끔은 한강에 나가&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;혼자 맥주 한 캔을 들고&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아무 생각 없이 멍하니 앉아 있곤 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그때는 그게&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;자유로운 거라고 생각했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그런데 지금 와서 보면&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;나는 그냥&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;혼자 견디고 있었던 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;힘들다는 말을&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;어떻게 꺼내는지 몰라서,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그저 혼자 걷고&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;혼자 마시며&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;괜찮은 척을 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;저자는 슬픔은&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;관계 속에서 태어난다고 말한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그러니 혼자 삼키지 말고&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;곁에 있는 사람과 나누라고.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 대목에서&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;한강에 혼자 앉아 있던 서른 살 전의 내가,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;조금 안쓰러워졌다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;읽고 나서 드는 솔직한 생각&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;마지막 장을 덮었을 때는&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이미 창밖이 어둑해져 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;서점에는 잠깐 훑고 나왔을 뿐인데&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;어느새 한 권을 다 읽어버린 셈이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;물론 오래된 습관이&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;곧바로 바뀌진 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스무 살부터 십 년을&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;혼자 견디는 데 익숙해진 사람이&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;책 한 권으로 달라질 리 없다는 걸 안다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다만 하나는 달라졌다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이제는 힘들 때&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;'괜찮은 척'을 하려는 나를&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;한 번쯤 멈춰 세우게 됐다는 것.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;멈추진 못해도&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그런 나를 바라볼 수는 있게 됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;늘 새로운 곳을 향해 달리던 내가&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;서른이 되어서야&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;한자리에 정착한 것처럼,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;마음을 꺼내는 법도&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이제는 조금씩&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;배워가는 중이다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이런 분께 권하고 싶다&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;생각이 너무 많아서&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;매번 시작 앞에서 머뭇거리는 사람,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;힘들어도 괜찮은 척&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;혼자 삼키는 데 익숙해진 사람,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;머릿속이 늘&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;쉬지 못하고 돌아가는 것 같은 사람.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그런 분들껜&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;서점에서 잠깐이라도&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 책을 펼쳐보라고 권하고 싶다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;나처럼&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 자리에서 발이 멈출지도 모르니까.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;마무리하며&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;원래 사려던 책은&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아직도 책장에 그대로 꽂혀 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정작 그날 나를 붙잡은 건&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;계획에도 없던 이 책이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;생각을 없애주는 책은 아니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;대신 이렇게 묻는다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 많은 생각을&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;혼자 끌어안고 있을 필요가 있냐고.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;여전히 나는 생각이 많은 사람이지만,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;새로운 곳에 정착한 지금,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이제는 그 생각을 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;조금씩 꺼내놓으며&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;살아보려 한다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>[ My Bookshelf ]</category>
      <category>book</category>
      <author>환이s</author>
      <guid isPermaLink="true">https://drg2524.tistory.com/251</guid>
      <comments>https://drg2524.tistory.com/251#entry251comment</comments>
      <pubDate>Tue, 7 Jul 2026 16:13:40 +0900</pubDate>
    </item>
    <item>
      <title>[Data Platform] ArgoCD GitOps 운영 가이드(App of Apps 패턴으로 플랫폼 관리)</title>
      <link>https://drg2524.tistory.com/250</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1078&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/osKco/dJMcadinTD0/V7pKrk0Fy8zCRc83XRaSNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/osKco/dJMcadinTD0/V7pKrk0Fy8zCRc83XRaSNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/osKco/dJMcadinTD0/V7pKrk0Fy8zCRc83XRaSNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FosKco%2FdJMcadinTD0%2FV7pKrk0Fy8zCRc83XRaSNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1919&quot; height=&quot;1078&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1078&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Intro&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #222222; text-align: start;&quot;&gt;안녕하세요. 환이s입니다  &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://drg2524.tistory.com/249&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 데이터 레이크하우스 플랫폼의 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;여덟 번째 스택으로 ArgoCD를 Kubernetes 위에 구축하고 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Harbor private registry와 연동하는 과정을 정리해 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ArgoCD&lt;/b&gt;의 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;App of Apps&lt;/b&gt;&lt;/span&gt; 패턴을 활용해서&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;Kafka, Airflow, Spark 등 플랫폼 전체 스택을 GitOps로 관리하는 방법&lt;/u&gt;을 정리해보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;531&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pgWqr/dJMcahd0sFV/TsjybthAp03EHmuj9tb3Ok/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pgWqr/dJMcahd0sFV/TsjybthAp03EHmuj9tb3Ok/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pgWqr/dJMcahd0sFV/TsjybthAp03EHmuj9tb3Ok/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpgWqr%2FdJMcahd0sFV%2FTsjybthAp03EHmuj9tb3Ok%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;531&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;531&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ArgoCD&lt;/b&gt;를 설치했다고 해서 &lt;b&gt;GitOps&lt;/b&gt;가 완성된 것은 아닙니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;어떤 패턴으로 앱을 관리할지 설계하는 것이 핵심입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;App of Apps&lt;/span&gt; 패턴의 개념과 왜 사용하는지&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;Git 저장소의 디렉토리 구조 설계 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;Root Application 설정 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;실제 앱 추가/삭제 워크플로우&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;Web UI에서 배포 상태를 확인하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;까지 순서대로 확인해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. App of Apps 패턴이란?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;일반적인 &lt;b&gt;ArgoCD&lt;/b&gt; 사용 방식은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;앱마다 Application 리소스를 하나씩 직접 만드는 것&lt;/u&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;App of Apps&lt;/b&gt;&lt;/span&gt;는 &lt;u&gt;&lt;b&gt;&quot;Application들을 관리하는 Application&quot;&lt;/b&gt;&lt;/u&gt;을 두는 패턴입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783386088359&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;platform-root (Root Application)
  └── gitops/apps/platform/ 디렉토리를 감시
        ├── airflow.yaml    &amp;rarr; platform-airflow (Application)
        ├── kafka.yaml      &amp;rarr; platform-kafka (Application)
        ├── spark.yaml      &amp;rarr; platform-spark (Application)
        ├── prometheus.yaml &amp;rarr; platform-prometheus (Application)
        └── ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;앱을 추가하려면 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;gitops/apps/platform/&lt;/b&gt; &lt;/span&gt;에 yaml 파일 하나만 추가하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ArgoCD&lt;/b&gt;가 &lt;b&gt;&lt;u&gt;자동으로 감지하고 배포&lt;/u&gt;&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;App of Apps&lt;/span&gt;&lt;/b&gt; 패턴에서는 &lt;u&gt;&lt;b&gt;Root Application 하나만 수동으로 배포&lt;/b&gt;&lt;/u&gt;하면,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이후&lt;u&gt;&lt;b&gt; 모든 앱은 Git에 파일을 추가하는 것만으로 자동 배포&lt;/b&gt;&lt;/u&gt;된다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. 디렉토리 구조&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 구축에서 사용하는 디렉토리 구조는 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783386371010&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data-platform/
├── bootstrap/                              # 수동으로 한 번만 배포하는 리소스
│   └── 03_argocd/
│       └── manifests/
│           ├── 30_root_app.yaml            # platform-root Application
│           └── 35_webapps_root.yaml        # webapps-root Application
│
└── gitops/                                 # ArgoCD가 지속적으로 감시하는 영역
    └── apps/
        ├── platform/                       # platform-root가 감시하는 디렉토리
        │   ├── airflow.yaml
        │   ├── prometheus.yaml
        │   ├── spark.yaml
        │   ├── kafka.yaml
        │   ├── ...
        │
        ├── airflow/                        # 각 앱의 실제 배포 리소스
        ├── prometheus/
        ├── spark/
        ├── kafka/
        └── ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Root Application은 &lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;bootstrap/&lt;/span&gt;&lt;/b&gt; 에서 한 번만 배포합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이후 앱 추가/수정은 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;gitops/apps/platform/&lt;/b&gt;&lt;/span&gt; 을 Git으로 관리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;bootstrap/&lt;/b&gt;&lt;/span&gt; 은 사람이 직접 한 번 실행하는 영역이고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;gitops/&lt;/b&gt;&lt;/span&gt; 는 ArgoCD가 자동으로 감시하고 동기화하는 영역이라는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3. Root Application 설정&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;✔️ platform-root&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1783387084565&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# bootstrap/03_argocd/manifests/30_root_app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: platform-root
  namespace: argocd
spec:
  project: default

  source:
    repoURL: http://&amp;lt;GIT_SERVER&amp;gt;/&amp;lt;ORG&amp;gt;/&amp;lt;REPO&amp;gt;.git
    targetRevision: main
    path: gitops/apps/platform
    directory:
      recurse: true   # 하위 디렉토리까지 재귀 탐색

  destination:
    server: https://kubernetes.default.svc
    namespace: argocd

  syncPolicy:
    automated:
      prune: true       # Git에서 삭제된 리소스를 클러스터에서도 삭제
      selfHeal: true    # 클러스터 상태가 Git과 달라지면 자동 복원

    syncOptions:
      - CreateNamespace=true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;핵심 설정 설명은 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.6047%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;설정&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 66.3953%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.6047%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;directory.recurse: true&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 66.3953%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;디렉토리 내 모든 yaml을 재귀적으로 읽음&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.6047%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;automated.prune: true&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 66.3953%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Git에서 yaml 삭제 시 클러스터 리소스도 삭제&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.6047%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;automated.selfHeal: true&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 66.3953%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;누군가 kubectl로 직접 수정해도 Git 상태로 자동 복원&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.6047%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;CreateNamespace=true&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 66.3953%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;네임스페이스가 없으면 자동 생성&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;⚠️&lt;b&gt; prune: true와 selfHeal: true는 강력한 설정&lt;/b&gt;입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Git이 항상 올바른 상태여야 하며, 실수로 파일을 삭제하면 클러스터에서도 리소스가 삭제&lt;/b&gt;됩니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;4. 각 앱 Application 설정&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;gitops/apps/platform/&lt;/span&gt;&lt;/b&gt; 아래의 각 yaml은 개별 앱의 &lt;b&gt;ArgoCD Application&lt;/b&gt;을 정의합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;✔️ kafka.yaml 예시&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1783387478930&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: platform-kafka
  namespace: argocd
spec:
  project: default
  source:
    repoURL: http://&amp;lt;GIT_SERVER&amp;gt;/&amp;lt;ORG&amp;gt;/&amp;lt;REPO&amp;gt;.git
    targetRevision: main
    path: gitops/apps/kafka        # 실제 kafka 배포 yaml이 있는 경로
    directory:
      recurse: true
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;모든 앱 yaml이 동일한 구조를 가집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;name&lt;/b&gt;과 &lt;b&gt;path&lt;/b&gt;만 앱마다 다릅니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; &lt;b&gt;참고&lt;/b&gt; : &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;destination.namespace: argocd&lt;/b&gt;&lt;/span&gt;는 &lt;b&gt;Applino 리소스 자체&lt;/b&gt;가 생성되는 네임스페이스입니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;실제 앱(Kafka, Airflow 등)이 배포되는 네임스페이스는 각 앱의 배포 yaml 내부에서 별도로 정의됩니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;CreateNamespace=true&lt;/b&gt;&lt;/span&gt; 옵션으로 해당 네임스페이스가 없으면 자동 생성됩니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;5. 실제 배포 흐름&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;✔️ 새 앱 추가하기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;gitops/apps/platform/&lt;/span&gt;&lt;/b&gt; 에 새 yaml 파일 작성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Git commit &amp;amp; push&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;ArgoCD가 변경 감지(기본 polling 3분)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;자동으로 새 Application 생성 및 배포&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1783387782846&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 예시: OpenSearch 추가
vim gitops/apps/platform/opensearch.yaml
git add .
git commit -m &quot;feat: add opensearch application&quot;
git push origin main&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;✔️ 앱 삭제하기&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1783387803440&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# yaml 파일 삭제
git rm gitops/apps/platform/opensearch.yaml
git commit -m &quot;chore: remove opensearch application&quot;
git push origin main
# prune: true 설정으로 ArgoCD가 자동으로 클러스터에서도 삭제&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;✔️ 롤백하기&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1783387822089&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 이전 커밋으로 되돌리기
git revert HEAD
git push origin main
# ArgoCD가 자동으로 이전 상태로 복원&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;모든 배포/삭제/롤백이 &lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;git push&lt;/span&gt;&lt;/b&gt; 하나로 완결된다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;kubectl로 직접 수정하더라도 ArgoCD가 Git 상태로 자동 복원합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;6. Web UI에서 확인하기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Ingress 설정 URL&lt;/b&gt;로 접속 후 Applications 화면에서 상태를 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;platform-root&lt;/span&gt;&lt;/b&gt;를 클릭하면 하위 Application 목록이 트리 형태로 표시됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;✔️ Sync Status&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 56.2792%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25.6649%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;상태&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.5892%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;의미&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25.6649%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Synced&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.5892%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Git과 클러스터 상태 일치&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25.6649%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;OutOfSync&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.5892%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Git과 클러스터 상태 불일치&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;✔️ Health Status&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 64.0698%; height: 105px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 26.2791%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;상태&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.9624%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;의미&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 26.2791%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Healthy&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.9624%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;모든 리소스 정상&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 26.2791%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Progressing&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.9624%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;배포 진행 중&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 26.2791%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Degraded&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.9624%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이상 상태(Pod CrashLoop 등)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 26.2791%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Missing&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.9624%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;리소스가 클러스터에 없음&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1461&quot; data-origin-height=&quot;819&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/co3sP8/dJMcafmYMCq/h3VbrbKeeGhLw1bhUksTN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/co3sP8/dJMcafmYMCq/h3VbrbKeeGhLw1bhUksTN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/co3sP8/dJMcafmYMCq/h3VbrbKeeGhLw1bhUksTN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fco3sP8%2FdJMcafmYMCq%2Fh3VbrbKeeGhLw1bhUksTN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1461&quot; height=&quot;819&quot; data-origin-width=&quot;1461&quot; data-origin-height=&quot;819&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; &lt;b&gt;참고:&lt;/b&gt; &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;OutOfSync&lt;/b&gt;&lt;/span&gt; 상태가 지속된다면 Git에 변경이 있는지, 또는 클러스터에 수동 변경이 있었는지 확인해 보세요. &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;selfHeal: true&lt;/b&gt;&lt;/span&gt; 설정이 있다면 수 분 내로 자동 복원됩니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;  마무리&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;App of Apps&lt;/b&gt;&lt;/span&gt; 패턴으로 데이터 플랫폼 전체를 &lt;b&gt;GitOps&lt;/b&gt; 방식으로 관리하는 과정을 정리해 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정리하면,&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;App of Apps 패턴&lt;/b&gt; &amp;rarr; Root Application 하나가 나머지 모든 앱을 자동 관리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;디렉토리 구조&lt;/b&gt; &amp;rarr; bootstrap/(수동 1회) vs gitops/(ArgoCD 자동 감시) 영역 분리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;prune + selfHeal&lt;/b&gt; &amp;rarr; Git 삭제 시 클러스터 자동 삭제, kubectl 수정 시 자동 복원&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;앱 추가&lt;/b&gt; &amp;rarr; yaml 파일 하나를 Git에 push 하는 것으로 완료&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;롤백&lt;/b&gt; &amp;rarr; git revert 하나로 이전 상태로 복원&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;라고 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;특히 이번 구축에서 주의해야 할 포인트는&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;prune: true&lt;/span&gt;는 Git에서 파일을 실수로 삭제하면 클러스터 리소스도 삭제되므로 주의&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Root Application은 bootstrap/에서 수동으로 한 번만 배포하고, 이후는 Git으로만 관리&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;앱별 yaml의 path는 실제 배포 리소스가 있는 경로를 정확히 지정해야 함&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 3가지였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이것으로 데이터 레이크하우스 플랫폼의 핵심 스택 구축 시리즈를 마무리합니다.  &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다음 포스팅부터는 &lt;u&gt;&lt;b&gt;플랫폼의 완성도를 높여주는 OSS&lt;/b&gt;&lt;/u&gt;들을 다뤄보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Kafka, Spark, Trino 같은 핵심 스택을 더 안정적이고 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;효율적으로 운영하기 위한 &lt;b&gt;&lt;u&gt;오토스케일링, 시크릿 관리, 모니터링 고도화&lt;/u&gt;&lt;/b&gt; 등&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;플랫폼 운영 디테일을 잡아주는 도구&lt;/b&gt;&lt;/u&gt;들을 하나씩 정리해 나갈 예정입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;궁금한 점이나 추가로 다뤄줬으면 하는 내용이 있다면 언제든지 편하게 말씀해 주세요!  &lt;/span&gt;&lt;/p&gt;</description>
      <category>[ Infra ]/Data Platform</category>
      <category>Data Platform</category>
      <category>Infra</category>
      <category>Kubernetes</category>
      <author>환이s</author>
      <guid isPermaLink="true">https://drg2524.tistory.com/250</guid>
      <comments>https://drg2524.tistory.com/250#entry250comment</comments>
      <pubDate>Tue, 7 Jul 2026 10:43:18 +0900</pubDate>
    </item>
    <item>
      <title>[Data Platform] ArgoCD 구축 가이드 (Helm + Private Registry + Ingress 구성)</title>
      <link>https://drg2524.tistory.com/249</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;1072&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfTrQz/dJMcahLMWyR/kFjkDifOv8oiahQqsV1xjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfTrQz/dJMcahLMWyR/kFjkDifOv8oiahQqsV1xjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfTrQz/dJMcahLMWyR/kFjkDifOv8oiahQqsV1xjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfTrQz%2FdJMcahLMWyR%2FkFjkDifOv8oiahQqsV1xjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1917&quot; height=&quot;1072&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;1072&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Intro&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start; font-family: 'Noto Serif KR';&quot;&gt;안녕하세요. 환이s입니다   &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://drg2524.tistory.com/248&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 데이터 레이크하우스 플랫폼의 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;일곱 번째 스택으로 Superset을 Kubernetes 위에 구축하고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Trino와 연동해서 Delta Lake 데이터를 시각화하는 과정을 정리해 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는 여덟 번째 스택,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ArgoCD&lt;/b&gt;를 &lt;b&gt;Kubernetes&lt;/b&gt; 환경에 구축하고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;u&gt;플랫폼 전체를 GitOps 방식으로 관리하기 위한 기반을 구성&lt;/u&gt;&lt;/b&gt;하는 과정을 정리해보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;531&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1flBr/dJMcabkzlzP/eGkmhU1mKmhpNdkLpqSYtk/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1flBr/dJMcabkzlzP/eGkmhU1mKmhpNdkLpqSYtk/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1flBr/dJMcabkzlzP/eGkmhU1mKmhpNdkLpqSYtk/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1flBr%2FdJMcabkzlzP%2FeGkmhU1mKmhpNdkLpqSYtk%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;531&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;531&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;지금까지 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt; Kafka &amp;rarr; Spark &amp;rarr; Delta Lake &amp;rarr; Trino &amp;rarr; Superset&lt;/b&gt; 까지 구축했다면,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이제 이 모든 스택을 코드로 관리하고 자동으로 배포하는 도구가 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 역할을 담당하는 것이 바로 &lt;b&gt;ArgoCD&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;22:1-22:8;455-462&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;24:1-28:15;464-670&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;24:1-24:53;464-516&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;Harbor private registry에서 이미지와 Helm Chart를 가져오는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;25:1-25:25;517-541&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;Helm으로 ArgoCD를 설치하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;26:1-26:54;542-595&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;MetalLB + ingress-nginx + sslip.io로 Web UI에 접근하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;27:1-27:60;596-655&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;Secret과 ConfigMap으로 저장소 인증, 계정, CRD health check를 구성하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;28:1-28:15;656-670&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;설치 후 검증하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;30:1-30:19;672-690&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;까지 순서대로 확인해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. ArgoCD 아키텍처 개요&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;본격적인 설치에 앞서,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 구성의 전체 아키텍처를 먼저 이해하는 것이 중요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;[웹 접근 경로]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783295864722&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;브라우저
  &amp;darr;
argocd.192.168.2.240.sslip.io
  &amp;darr; (MetalLB &amp;rarr; ingress-nginx)
argocd-server:80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;[GitOps 동기화 경로]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783295888217&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ArgoCD (application-controller)
  &amp;darr; polling (3분 간격)
Git 저장소 (Gitea)
  &amp;darr; 변경 감지 시
클러스터 자동 동기화&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;각 컴포넌트의 역할은 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 90.1148%; height: 81px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 21.5117%; text-align: center; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;컴포넌트&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 75.8842%; text-align: center; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 14px;&quot;&gt;
&lt;td style=&quot;width: 21.5117%; height: 14px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;MetalLB&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 75.8842%; height: 14px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;ingress-nginx에 External IP(192.168.2.240) 할당&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;width: 21.5117%; height: 16px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ingress-nginx&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 75.8842%; height: 16px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;sslip.io 도메인을 ArgoCD 서버로 라우팅&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 21.5117%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Harbor&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 75.8842%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Private 이미지 및 Helm Chart 레지스트리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 21.5117%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ArgoCD&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 75.8842%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Git 저장소를 기반으로 클러스터 상태를 자동 동기화&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ArgoCD&lt;/b&gt;는 Git 저장소를&lt;u&gt;&lt;b&gt; 단일 진실의 원천(Single Source of Truth)&lt;/b&gt;&lt;/u&gt;으로 삼아 클러스터 상태를 코드와 항상 일치시킨다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. 설치 순서&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 구축에서 사용하는 디렉토리 구조는 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1783296185725&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bootstrap/
└── 03_argocd/
    ├── helm/
    │   ├── install.sh                      # 설치 스크립트
    │   └── values.yaml                     # Helm values
    └── manifests/
        ├── 10_ingress_argocd.yaml          # Ingress 설정
        ├── 20_repo_secret.yaml             # Git 저장소 인증 Secret
        ├── 30_root_app.yaml                # platform-root Application
        ├── 35_webapps_root.yaml            # webapps-root Application
        ├── 40_argocd_cm.yaml               # ConfigMap (계정, CephCluster health check)
        ├── 50_argocd_tls_certs_cm.yaml     # Harbor TLS 인증서 신뢰 설정
        ├── 60_harbor_repo_secret.yaml      # Harbor OCI 레지스트리 인증 Secret
        └── 70_argocd_secret.yaml           # RBAC 권한 설정&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;설치 순서는 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt; values.yaml 작성 &amp;rarr; Helm 설치 &amp;rarr; manifests 적용 (Ingress, Secret, ConfigMap) &amp;rarr; 설치 후 검증&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3. values.yaml 설정&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;먼저 공식 이미지를 기준으로 기본 설정을 살펴보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1783297315272&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;global:
  image:
    repository: quay.io/argoproj/argocd
    tag: v3.3.4

redis:
  image:
    repository: redis
    tag: 7

dex:
  enabled: false   # SSO 인증관련 서비스 사용여부

server:
  service:
    type: NodePort
    nodePortHttp: 30010

configs:
  params:
    server.insecure: true       # ArgoCD 서버를 HTTP 모드로 실행&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;주요 설정 포인트는 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 30.1163%; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;설정&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 69.8837%; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 30.1163%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;dex.enabled: false&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 69.8837%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- LDAP/OAuth SSO를 쓰지 않는 경우 비활성화&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 30.1163%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;server.insecure: true&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 69.8837%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- ArgoCD 서버가 HTTP로 동작. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- TLS 없이 Ingress에서 직접 80 포트로 수신&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;글쓴이는 외부 인터넷이 차단된 &lt;b&gt;온프레미스 환경&lt;/b&gt;에서 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Harbor private registry&lt;/b&gt;를 운영하는 목적 기반으로 하고 있어, &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아래와 같이 이미지 경로와&lt;b&gt; Helm Chart&lt;/b&gt; 레지스트리를 &lt;b&gt;Harbor&lt;/b&gt; 기준으로 변경해서 사용하고 있습니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1783297534709&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;global:
  image:
    repository: harbor.local/library/argocd/argocd
    tag: v3.3.4

redis:
  image:
    repository: harbor.local/library/redis/redis
    tag: 7

dex:
  enabled: false   # SSO 인증관련 서비스 사용여부

server:
  service:
    type: NodePort
    nodePortHttp: 30010

configs:
  params:
    helm.oci.enabled: true      # OCI 형식 Helm 차트 사용
    server.insecure: true       # ArgoCD 서버를 HTTP 모드로 실행

repoServer:
  env:
    - name: HELM_EXPERIMENTAL_OCI
      value: &quot;1&quot;

  cm:
    url: http://argocd.192.168.2.240.sslip.io

  repositories:
    harbor-helm:
      url: harbor.local/helm-chart
      type: helm
      name: harbor

  tls:
    certificates:
      harbor.local: |
        -----BEGIN CERTIFICATE-----
        # Harbor CA 인증서 내용
        -----END CERTIFICATE-----&lt;/code&gt;&lt;/pre&gt;
&lt;p data-sourcepos=&quot;195:1-195:176;4092-4267&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;195:1-195:176;4092-4267&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;197:1-200:29;4269-4392&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;197:1-200:29;4269-4392&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Git&lt;/b&gt; 저장소 인증과 &lt;b&gt;Harbor OCI&lt;/b&gt; &lt;b&gt;레지스트리&lt;/b&gt; &lt;b&gt;인증&lt;/b&gt;은 &lt;b&gt;values.yaml&lt;/b&gt; 외에 별도 &lt;b&gt;Secret&lt;/b&gt;으로도 등록한다는 점입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;197:1-200:29;4269-4392&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;197:1-200:29;4269-4392&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 부분은 이후 섹션에서 하나씩 확인해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;4. Helm 설치 스크립트&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1783297635528&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash

set -e

NAMESPACE=argocd
CHART=oci://harbor.local/helm-chart/argo-cd
VERSION=9.4.14

echo &quot;== namespace 생성 ==&quot;
kubectl create namespace $NAMESPACE --dry-run=client -o yaml | kubectl apply -f -

echo &quot;== harbor login ==&quot;
helm registry login harbor.local

echo &quot;== argocd 설치 ==&quot;
helm upgrade --install argocd $CHART \
  --version $VERSION \
  -n $NAMESPACE \
  -f values.yaml

echo &quot;== manifests 적용 ==&quot;
kubectl apply -f ../manifests/

echo &quot;== 완료 ==&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스크립트 구조 설명은 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 89px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 39.5349%; height: 21px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;명령어&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 60.4651%; height: 21px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 39.5349%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;--dry-run=client -o yaml | kubectl apply -f -&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 60.4651%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;namespace가 이미 존재해도 에러 없이 처리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 39.5349%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;helm registry login&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 60.4651%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Harbor OCI 레지스트리 인증&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 39.5349%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;helm upgrade --install&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 60.4651%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;없으면 설치, 있으면 업그레이드(idempotent)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 39.5349%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;kubectl apply -f ../manifests/&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 60.4651%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Secret, ConfigMap, Ingress 등 추가 리소스 일괄 적용&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;   &lt;b&gt;참고:&lt;/b&gt; &lt;b&gt;set -e&lt;/b&gt; &lt;b&gt;옵션으로 중간에 에러가 발생하면 스크립트가 즉시 종료됩니다. 설치 실패 시 어느 단계에서 멈췄는지 확인하세요.&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;5. Ingress 설정&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1783298061793&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: argocd
  namespace: argocd
  annotations:
    nginx.ingress.kubernetes.io/enable-cors: &quot;true&quot;
    nginx.ingress.kubernetes.io/cors-allow-origin: &quot;*&quot;
    nginx.ingress.kubernetes.io/cors-allow-methods: &quot;GET, POST, PUT, DELETE, OPTIONS&quot;
    nginx.ingress.kubernetes.io/cors-allow-headers: &quot;Authorization, Content-Type&quot;
spec:
  ingressClassName: nginx
  rules:
  - host: argocd.192.168.2.240.sslip.io
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: argocd-server
            port:
              number: 80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;sslip.io&lt;/b&gt;는 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;별도 DNS 설정 없이 IP가 포함된 도메인을 &lt;/b&gt;&lt;/u&gt;&lt;u&gt;&lt;b&gt;자동으로 해당 IP로 해석해주는 서비스&lt;/b&gt;&lt;/u&gt; 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; &lt;b&gt; 참고: TLS 설정이 없기 때문에 브라우저에서 주의 요함이 표시될 수 있습니다. 내부망 구성에서는 HTTP로 운영하고, 운영 환경에서는 cert-manager로 인증서를 발급하는 것을 권장합니다.&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;6. Git 저장도 인증 Secret 설정&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ArgoCD&lt;/b&gt;가 &lt;u&gt;&lt;b&gt;Gitea 저장소에 접근하기 위한 인증 정보를 Secret&lt;/b&gt;&lt;/u&gt;으로 등록합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783298664445&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 20_repo_secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: repo-data-platform
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: repository
type: Opaque
stringData:
  url: http://192.168.2.8:37000/eluna/data-platform.git
  username: argocd
  password: &amp;lt;GITEA_ACCESS_TOKEN&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;argocd.argoproj.io/secret-type: repository&lt;/b&gt; 라벨을 붙이면 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ArgoCD&lt;/b&gt;가 이 &lt;b&gt;Secret&lt;/b&gt;을 &lt;u&gt;&lt;b&gt;저장소 인증 정보로 자동 인식&lt;/b&gt;&lt;/u&gt;한다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;7. Helm Chart 레지스트리 인증 Secret 설정&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;공식 &lt;b&gt;Helm 레지스트리(ArtifactHub 등 공인 레지스트리)&lt;/b&gt;를 사용하는 경우,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;별도 &lt;b&gt;Secret&lt;/b&gt; 없이 &lt;b&gt;values.yaml&lt;/b&gt;의 &lt;b&gt;configs.repositories&lt;/b&gt;에 직접 등록하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783299030890&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# values.yaml - 공식 Helm 레지스트리 사용 시
configs:
  repositories:
    argo-helm:
      url: https://argoproj.github.io/argo-helm
      type: helm
      name: argo&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;인증이 필요한 &lt;b&gt;private&lt;/b&gt; 레지스트리라면 &lt;b&gt;Secret&lt;/b&gt;으로 등록합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783299054235&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Secret
metadata:
  name: repo-private-helm
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: repository
stringData:
  type: helm
  name: private-helm
  url: https://your-private-helm-repo
  username: &amp;lt;USERNAME&amp;gt;
  password: &amp;lt;PASSWORD&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;글쓴이는 온프레미스 환경에서 &lt;b&gt;Harbor&lt;/b&gt;를 &lt;b&gt;OCI&lt;/b&gt; 레지스트리로 사용하고 있어, 아래와 같이 &lt;b&gt;OCI&lt;/b&gt; 전용 &lt;b&gt;Secret&lt;/b&gt;으로 등록합니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1783301351017&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 60_harbor_repo_secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: repo-harbor-oci
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: repository
stringData:
  type: helm
  name: harbor-oci
  url: harbor.local
  enableOCI: &quot;true&quot;
  username: admin
  password: &amp;lt;HARBOR_PASSWORD&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 84px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;필드&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;type: helm&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Helm 레지스트리 타입으로 등록&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;enableOCI: &quot;true&quot;&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;OCI 프로토콜 사용 활성화&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;url : harbor.local&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Harbor 도메인 (포트 없이 도메인만)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;argocd.argoproj.io/secret-type: repository&lt;/b&gt; 라벨을 붙이면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ArgoCD&lt;/b&gt;가 이 &lt;b&gt;Secret&lt;/b&gt;을 &lt;b&gt;Helm&lt;/b&gt; 레지스트리 인증 정보로 자동 인식한다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;8. TLS 인증서 신뢰 설정&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Let`s Encrypt&lt;/b&gt;나 &lt;b&gt;공인 CA에서 발급한 인증서&lt;/b&gt;를 사용하는 경우,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;ArgoCD가 기본적으로 신뢰&lt;/b&gt;&lt;/u&gt;하므로 별도 설정이 필요하지 않습니다,.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;글쓴이는 사내 &lt;b&gt;self-signed CA 인증서&lt;/b&gt;를 사용하고 있어, &lt;b&gt;ArgoCD repo-server&lt;/b&gt;가 &lt;b&gt;Harbor&lt;/b&gt;에 접근할 때 인증서를 신뢰하도록 아래와 같이 등록합니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1783301626023&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 50_argocd_tls_certs_cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-tls-certs-cm
  namespace: argocd
data:
  harbor.local: |
    -----BEGIN CERTIFICATE-----
    # Harbor CA 인증서 내용
    -----END CERTIFICATE-----&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;⚠️ 이 설정이 없으면 &lt;b&gt;repo-server&lt;/b&gt;가 &lt;b&gt;Harbor&lt;/b&gt;에서 &lt;b&gt;Helm&lt;/b&gt; 차트를 &lt;b&gt;pull&lt;/b&gt;할 때 아래와 같은&lt;b&gt; &lt;/b&gt;&lt;u&gt;&lt;b&gt;TLS 인증서 검증 오류&lt;/b&gt;&lt;/u&gt;가 발생합니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1783301662282&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;x509: certificate signed by unknown authority&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;공인 인증서 환경에서는 이 설정이 불필요하지만,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;self-signed&lt;/b&gt; 인증서를 사용하는 경우 반드시 &lt;b&gt;argocd-tls-certs-cm&lt;/b&gt;에 CA 인증서를 등록해야 한다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;9.ConfigMap 설정&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1783301812746&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 40_argocd_cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cm
  namespace: argocd
data:
  # API 토큰 발급용 계정 생성
  accounts.monitoring: apiKey

  # Rook-Ceph CephCluster 커스텀 Health Check
  resource.customizations.health.ceph.rook.io_CephCluster: |
    hs = {}
    if obj.status ~= nil then
      local state = obj.status.state or &quot;&quot;
      local health = &quot;&quot;
      if obj.status.ceph ~= nil then
        health = obj.status.ceph.health or &quot;&quot;
      end
      if state == &quot;Created&quot; and health == &quot;HEALTH_OK&quot; then
        hs.status = &quot;Healthy&quot;
        hs.message = &quot;Ceph cluster is healthy&quot;
        return hs
      end
      hs.status = &quot;Progressing&quot;
      hs.message = &quot;State=&quot; .. state .. &quot; Health=&quot; .. health
      return hs
    end
    hs.status = &quot;Progressing&quot;
    hs.message = &quot;Waiting for CephCluster status&quot;
    return hs&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;주요 설정 포인트는 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;accounts.monitoring: apiKey&lt;/b&gt;&amp;nbsp;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; CI/CD 파이프라인 등에서 API 토큰으로 ArgoCD를 호출할 계정&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;CephCluster health check&lt;/b&gt;&amp;nbsp; &lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;ArgoCD는 기본적으로 CRD 리소스의 Health를 판단할 수 없습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Lua 스크립트를 통해 Rook-Ceph의 상태를 직접 파악하도록 커스터마이징 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ArgoCD&lt;/b&gt;는 &lt;u&gt;기본 리소스(Deployment,Pod 등)의 Health는 자동으로 판단&lt;/u&gt;하지만, &lt;b&gt;CephCluster&lt;/b&gt; 같은 &lt;u&gt;CRD는 Lua 스크립트로 직접 Health 로직을 정의&lt;/u&gt;해줘야 한다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;10. RBAC 권한 설정&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1783302299294&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 70_argocd_secret.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
data:
  policy.csv: |
    g, monitoring, role:readonly&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;monitoring&lt;/b&gt; 계정에 읽기 전용 권한을 부여합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;API 토큰을 발급받아 외부 시스템에서 &lt;b&gt;ArgoCD 상태를 조회&lt;/b&gt;할 때 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;11. 설치 후 검증&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;[Pod 상태 확인]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783302366336&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl get pods -n argocd

NAME                                                READY   STATUS    RESTARTS
argocd-application-controller-0                     1/1     Running   0
argocd-applicationset-controller-xxx                1/1     Running   0
argocd-notifications-controller-xxx                 1/1     Running   0
argocd-redis-xxx                                    1/1     Running   0
argocd-repo-server-xxx                              1/1     Running   0
argocd-server-xxx                                   1/1     Running   0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;[Service 확인]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783302411314&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl get svc -n argocd&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;argocd-server&lt;/b&gt;가 NodePort(&lt;b&gt;30010&lt;/b&gt;)로 노출되어 있는지 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;[초기 admin 비밀번호 확인]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783302453422&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl get secret argocd-initial-admin-secret -n argocd \
  -o jsonpath=&quot;{.data.password}&quot; | base64 -d&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;[Web UI 접속]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Ingress 설정 URL로 접속 후 로그인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;510:1-510:24;11175-11198&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Username&lt;/b&gt;: admin&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;511:1-511:29;11199-11227&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Password&lt;/b&gt;: 위 명령어로 확인한 값&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-sourcepos=&quot;513:1-513:29;11229-11257&quot; data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;⚠️ 초기 비밀번호는 로그인 후 반드시 변경하세요.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-sourcepos=&quot;515:1-515:40;11259-11298&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;515:1-515:40;11259-11298&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Pod가 정상 상태가 되지 않는 경우 아래 명령어로 원인을 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783302521961&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Pod 상태 상세 확인
kubectl describe pod &amp;lt;pod-name&amp;gt; -n argocd

# Pod 로그 확인
kubectl logs &amp;lt;pod-name&amp;gt; -n argocd&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;자주 발생하는 원인은 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.6356%; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;증상&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.9147%; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.4496%; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;조치&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.6356%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Pending 상태 지속&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.9147%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이미지 pull 실패&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.4496%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Harbor 로그인 상태 및 이미지 경로 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.6356%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;CrashLoopBackOff&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.9147%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;values.yaml 설정 오류&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.4496%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;kubectl logs로 argocd-server 로그 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.6356%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;repo-server TLS 오류&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.9147%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Harbor 인증서 미등록&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.4496%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;argocd-tls-certs-cm ConfigMap 적용 여부 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.6356%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Harbor Helm Chart pull 실패&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.9147%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;OCI Secret 미등록&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.4496%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;repo-harbor-oci Secret 적용 여부 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1904&quot; data-origin-height=&quot;929&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cu14XC/dJMcag0mLus/HfOeZIzX6rpUUTGo79rBLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cu14XC/dJMcag0mLus/HfOeZIzX6rpUUTGo79rBLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cu14XC/dJMcag0mLus/HfOeZIzX6rpUUTGo79rBLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcu14XC%2FdJMcag0mLus%2FHfOeZIzX6rpUUTGo79rBLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1904&quot; height=&quot;929&quot; data-origin-width=&quot;1904&quot; data-origin-height=&quot;929&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-sourcepos=&quot;536:1-536:10;11788-11797&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;  마무리&lt;/span&gt;&lt;/h3&gt;
&lt;p data-sourcepos=&quot;538:1-538:57;11799-11855&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는 온프레미스 &lt;b&gt;Kubernetes&lt;/b&gt; 환경에서 &lt;b&gt;ArgoCD&lt;/b&gt;를 구축하는 과정을 정리해 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;538:1-538:57;11799-11855&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;540:1-540:6;11857-11862&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정리하면,&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;542:1-548:42;11864-12294&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;542:1-542:58;11864-11921&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Helm 설치&lt;/b&gt; &amp;rarr; helm upgrade --install로 idempotent 하게 설치&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;543:1-543:59;11922-11980&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Ingress&lt;/b&gt; &amp;rarr; MetalLB + ingress-nginx + sslip.io로 도메인 접근&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;544:1-544:83;11981-12063&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Git 저장소 인증&lt;/b&gt; &amp;rarr; argocd.argoproj.io/secret-type: repository 라벨을 붙인 Secret으로 등록&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;545:1-545:57;12064-12120&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Harbor OCI 인증&lt;/b&gt; &amp;rarr; enableOCI: &quot;true&quot; Secret으로 별도 등록&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;546:1-546:69;12121-12189&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;TLS 신뢰&lt;/b&gt; &amp;rarr; Harbor self-signed CA 인증서를 argocd-tls-certs-cm으로 등록&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;547:1-547:63;12190-12252&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ConfigMap&lt;/b&gt; &amp;rarr; API 계정 생성 및 CRD health check Lua 스크립트 커스터마이징&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;548:1-548:42;12253-12294&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;RBAC&lt;/b&gt; &amp;rarr; argocd-rbac-cm으로 계정별 권한 설정&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;550:1-550:13;12296-12308&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;라고 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;550:1-550:13;12296-12308&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;552:1-552:23;12310-12332&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;특히 이번 구축에서 주의해야 할 포인트는&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;554:1-556:54;12334-12530&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;554:1-554:66;12334-12399&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;Git 저장소 인증과 Harbor OCI 인증은 values.yaml이 아닌 별도 Secret으로 관리해야 함&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;555:1-555:77;12400-12476&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;Harbor self-signed 인증서는 반드시 argocd-tls-certs-cm에 등록해야 repo-server가 정상 동작&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;556:1-556:54;12477-12530&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;server.insecure: true는 TLS 없이 HTTP로 운영할 때 사용하는 설정&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;558:1-558:11;12532-12542&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 3가지였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;558:1-558:11;12532-12542&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;560:1-560:67;12544-12610&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다음 편에서는&lt;b&gt; App of Apps&lt;/b&gt; 패턴을 활용해 플랫폼 전체 앱을 &lt;b&gt;GitOps&lt;/b&gt;로 관리하는 방법을 다뤄보겠습니다.  &lt;/span&gt;&lt;/p&gt;
&lt;p data-sourcepos=&quot;560:1-560:67;12544-12610&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;562:1-562:50;12612-12661&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;궁금한 점이나 추가로 다뤄줬으면 하는 내용이 있다면 언제든지 편하게 말씀해 주세요!  &lt;/span&gt;&lt;/p&gt;</description>
      <category>[ Infra ]/Data Platform</category>
      <category>Data Platform</category>
      <category>Infra</category>
      <category>Kubernetes</category>
      <author>환이s</author>
      <guid isPermaLink="true">https://drg2524.tistory.com/249</guid>
      <comments>https://drg2524.tistory.com/249#entry249comment</comments>
      <pubDate>Mon, 6 Jul 2026 10:55:56 +0900</pubDate>
    </item>
    <item>
      <title>[Data Platform] Superset 구축 가이드 (feat. Trino 연동)</title>
      <link>https://drg2524.tistory.com/248</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1079&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zVIEM/dJMcacKd8hy/to0ApyEMcIyBgerSGWrcYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zVIEM/dJMcacKd8hy/to0ApyEMcIyBgerSGWrcYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zVIEM/dJMcacKd8hy/to0ApyEMcIyBgerSGWrcYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzVIEM%2FdJMcacKd8hy%2Fto0ApyEMcIyBgerSGWrcYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1919&quot; height=&quot;1079&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1079&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;Intro&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요. 환이s입니다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://drg2524.tistory.com/247&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 데이터 레이크하우스 플랫폼의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여섯 번째 스택으로 Prometheus + Grafana를 Kubernetes 위에 구축하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터 모니터링 환경을 구성하는 과정을 정리해 봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 일곱 번째 스택,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Superset&lt;/b&gt;을 &lt;b&gt;Kubernetes&lt;/b&gt; 환경에 구축하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Trino&lt;/b&gt;와 연동해서 &lt;b&gt;Delta Lake&lt;/b&gt; 데이터를 시각화하는 과정을 정리해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2841&quot; data-origin-height=&quot;1457&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0vOoe/dJMcahSc4ah/GG9yrekqHYhD1Jhg1q0o7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0vOoe/dJMcahSc4ah/GG9yrekqHYhD1Jhg1q0o7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0vOoe/dJMcahSc4ah/GG9yrekqHYhD1Jhg1q0o7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0vOoe%2FdJMcahSc4ah%2FGG9yrekqHYhD1Jhg1q0o7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2841&quot; height=&quot;1457&quot; data-origin-width=&quot;2841&quot; data-origin-height=&quot;1457&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 구축한 파이프라인에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kafka &amp;rarr; Spark &amp;rarr; Delta Lake &amp;rarr; Trino&lt;/b&gt;까지 데이터가 흘러왔다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 데이터를 비즈니스 관점으로 시각화할 도구가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 역할을 담당하는 것이 바로 &lt;b&gt;Superset&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;u&gt;Trino 드라이버가 포함된 커스텀 이미지를 빌드하고 Harbor에 푸시하는 방법&lt;/u&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;u&gt;OCI 방식으로 Superset Helm Chart를 설치하는 방법&lt;/u&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;u&gt;Superset에서 Trino DB 연결을 등록하고 Delta Lake 데이터를 조회하는 방법&lt;/u&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;까지 순서대로 확인해 보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;1. Superset 아키텍처 개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본격적인 설치에 앞서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 구성의 전체 아키텍처를 먼저 이해하는 것이 중요합니다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Superset (시각화)
      │  SQLAlchemy URI로 연결
      ▼
Trino (쿼리 엔진)
      │  메타데이터 조회
      ▼
Hive Metastore
      │  실제 데이터 읽기
      ▼
Ceph S3 (Delta Lake)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 컴포넌트의 역할은 아래와 같습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 105px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;컴포넌트&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;Superset&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;Trino를 통해 Delta Lake 데이터를 SQL로 조회하고 차트/대시보드로 시각화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;Trino&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;Superset의 쿼리를 받아 Ceph S3의 실제 데이터를 조회하고 결과 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;PostgreSQL&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;Superset의 메타데이터 (대시보드, 차트, 유저 등) 영구 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;Redis&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;Superset Celery Worker의 캐시 및 비동기 작업 큐&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Superset&lt;/b&gt;은 데이터를 직접 저장하지 않고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Trino&lt;/b&gt;를 통해&lt;b&gt; Delta Lake&lt;/b&gt; 데이터를 &lt;u&gt;SQL로 조회한 뒤 시각화&lt;/u&gt;한다는 점입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;2. 설치 순서&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 구축에서 사용하는 디렉토리 구조는 아래와 같습니다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;superset/
├── helm/
│   └── 10_superset_values.yaml   # Helm 커스텀 값
└── yaml/
    ├── 10_superset_namespace.yaml  # Namespace 생성
    └── 20_superset_ingress.yaml    # Ingress 설정
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 순서는 아래와 같습니다.&lt;/p&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;커스텀 이미지 빌드 및 푸시 &amp;rarr; Namespace 생성 &amp;rarr; Helm 설치 &amp;rarr; Ingress 설정 &amp;rarr; Trino 연동
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;3. 커스텀 이미지 빌드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Superset&lt;/b&gt; 공식 이미지에는 &lt;u&gt;Trino 드라이버가 포함되어 있지 않습니다. &lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Trino 연동을 위해 trino 패키지를 추가한 커스텀 이미지를 빌드합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# Dockerfile
FROM apache/superset:5.0.0
USER root
RUN /app/.venv/bin/python -m pip install --no-cache-dir psycopg2-binary trino
USER superset
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 후 이미지 레지스트리에 푸시합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Harbor를 사용하는 경우&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;docker build -t harbor.local/library/superset-custom:5.0.1 .
docker push harbor.local/library/superset-custom:5.0.1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Harbor가 없는 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker Hub 또는 다른 레지스트리를 사용할 수 있습니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #14181f;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# Docker Hub
docker build -t &amp;lt;your-dockerhub-id&amp;gt;/superset-custom:5.0.1 .
docker push &amp;lt;your-dockerhub-id&amp;gt;/superset-custom:5.0.1

# 또는 직접 클러스터 노드에 이미지를 로드하는 방법 (폐쇄망 환경)
docker save &amp;lt;your-dockerhub-id&amp;gt;/superset-custom:5.0.1 -o superset-custom.tar
scp superset-custom.tar &amp;lt;node-ip&amp;gt;:/tmp/
ssh &amp;lt;node-ip&amp;gt; docker load -i /tmp/superset-custom.tar&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  &lt;b&gt;참고&lt;/b&gt;: 클러스터가 외부 레지스트리에 접근할 수 없는 폐쇄망 환경이라면 docker save &amp;rarr; docker load 방식으로 각 노드에 직접 이미지를 로드할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;/app/.venv/bin/python&lt;/b&gt;으로&lt;b&gt; pip install&lt;/b&gt;을 실행해야 한다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Superset&lt;/b&gt; 이미지는&lt;b&gt; /app/.venv&lt;/b&gt; 가상환경 안에 패키지가 설치되어 있기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 &lt;b&gt;pip install&lt;/b&gt;로는 반영이 되지 않습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;4. Namespace 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Superset 전용 Namespace를 먼저 생성합니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;# yaml/10_superset_namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: superset
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;kubectl apply -f yaml/10_superset_namespace.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;5. Helm으로 Superset 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OCI&lt;/b&gt; 방식으로 &lt;b&gt;Harbor&lt;/b&gt;에서 &lt;b&gt;Superset Helm Chart&lt;/b&gt;를 가져옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Harbor가 없는 환경이라면 공식 Chart를 사용할 수 있습니다.)&lt;/p&gt;
&lt;figure id=&quot;og_1781060811161&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;superset/helm/superset at master &amp;middot; apache/superset&quot; data-og-description=&quot;Apache Superset is a Data Visualization and Data Exploration Platform - apache/superset&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/apache/superset/tree/master/helm/superset&quot; data-og-url=&quot;https://github.com/apache/superset/tree/master/helm/superset&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/7Tuib/dJMb86n5KjU/fwiBih6HVdSZIfKgfnOXWk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/9rPA9/dJMb82eU86J/ruW9IHAypIcy0okBi8E1U1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://github.com/apache/superset/tree/master/helm/superset&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/apache/superset/tree/master/helm/superset&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/7Tuib/dJMb86n5KjU/fwiBih6HVdSZIfKgfnOXWk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/9rPA9/dJMb82eU86J/ruW9IHAypIcy0okBi8E1U1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;superset/helm/superset at master &amp;middot; apache/superset&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Apache Superset is a Data Visualization and Data Exploration Platform - apache/superset&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;helm upgrade --install superset-helm \\
  oci://harbor.local/helm-chart/superset \\
  --version 0.15.2 \\
  --namespace superset \\
  -f helm/10_superset_values.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  &lt;b&gt;참고&lt;/b&gt;: 이전에는 로컬 .tgz 파일로 설치했다가 현재는 OCI 방식으로 변경했습니다. 로컬 tgz 방식으로 설치할 경우 아래와 같이 실행하면 됩니다.&lt;/blockquote&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;helm upgrade --install superset-helm helm/superset-0.15.2.tgz \\
  --namespace superset \\
  -f helm/10_superset_values.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;values.yaml&lt;/b&gt;의 핵심 설정은 아래와 같습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;이미지 설정&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;image:
  repository: harbor.local/library/superset-custom
  tag: &quot;5.0.1&quot;
  pullPolicy: IfNotPresent
initImage:
  repository: harbor.local/library/superset-dockerize
  tag: &quot;dockerize&quot;
  pullPolicy: IfNotPresent
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3번에서 빌드한 커스텀 이미지를 사용합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;리소스 설정&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;resources:
  requests:
    cpu: 500m
    memory: 512Mi
  limits:
    cpu: 1000m
    memory: 1Gi
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Superset 설정&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;configOverrides:
  secret: |
    SECRET_KEY = 'UEp10tAn2qzHK2wzng2f4Q6EKv26aBsDYKfrg5le9qWhihn3aPDMb+1i'
    SQLALCHEMY_DATABASE_URI = &quot;postgresql+psycopg2://admin:admin123@superset-helm-postgresql:5432/superset&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 운영 환경에서는 SECRET_KEY와 DB 접속 정보를 Kubernetes Secret으로 분리해서 관리하는 것을 권장합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;PostgreSQL 설정&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;postgresql:
  enabled: true
  image:
    registry: harbor.local
    repository: library/postgresql
    tag: &quot;16&quot;
  auth:
    username: admin
    password: admin123
    database: superset
  primary:
    persistence:
      enabled: true
      storageClass: rook-ceph-block
      size: 8Gi
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Superset&lt;/b&gt; 메타데이터를 &lt;u&gt;Ceph 블록 스토리지에 영구 저장&lt;/u&gt;합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Redis 설정&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;redis:
  enabled: true
  image:
    registry: harbor.local
    repository: library/redis
    tag: &quot;7.2&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Pod&lt;/b&gt;가 &lt;b&gt;Running&lt;/b&gt; 상태인지 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get pods -n superset
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적인 경우 아래처럼 보입니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;NAME                                        READY   STATUS    RESTARTS   AGE
superset-helm-xxxxxxxxxx-xxxxx              1/1     Running   0          ...
superset-helm-celerybeat-xxxxxxxxxx-xxxxx   1/1     Running   0          ...
superset-helm-postgresql-0                  1/1     Running   0          ...
superset-helm-redis-master-0                1/1     Running   0          ...
superset-helm-worker-xxxxxxxxxx-xxxxx       1/1     Running   0          ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Pod&lt;/b&gt;가 &lt;b&gt;Running&lt;/b&gt; 상태가 되지 않는 경우 아래 명령어로 원인을 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;# Pod 상태 상세 확인
kubectl describe pod &amp;lt;pod-name&amp;gt; -n superset

# Pod 로그 확인
kubectl logs &amp;lt;pod-name&amp;gt; -n superset
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 발생하는 원인은 아래와 같습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;증상&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;조치&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Pending 상태 지속&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;PVC 바인딩 실패&lt;/td&gt;
&lt;td&gt;kubectl get pvc -n superset으로 PVC 상태 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;CrashLoopBackOff&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;SECRET_KEY 또는 DB URI 오류&lt;/td&gt;
&lt;td&gt;kubectl logs로 Superset 컨테이너 로그 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;ImagePullBackOff&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;이미지 pull 실패&lt;/td&gt;
&lt;td&gt;Harbor 로그인 상태 또는 커스텀 이미지 빌드 여부 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;6. Ingress 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# yaml/20_superset_ingress.yaml
spec:
  ingressClassName: nginx
  rules:
  - host: superset.&amp;lt;your-ip&amp;gt;.sslip.io
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: superset-helm
            port:
              number: 8088
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;kubectl apply -f yaml/20_superset_ingress.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Superset UI는 아래 주소로 접근할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;http://superset.&amp;lt;your-ip&amp;gt;.sslip.io
ID: admin
PW: (values.yaml에서 설정한 패스워드)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;7. Trino 연동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1.Trino 연결 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Superset Pod에서 Trino로 연결이 되는지 먼저 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;kubectl exec -n superset deploy/superset-helm -c superset -- \\
  python3 -c &quot;
import trino
conn = trino.dbapi.connect(
  host='trino-service.trino.svc',
  port=8080,
  user='admin'
)
cur = conn.cursor()
cur.execute('SHOW CATALOGS')
print(cur.fetchall())
&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적인 경우 아래처럼 보입니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[['delta'], ['hive'], ['jmx'], ['memory'], ['system'], ['tpcds'], ['tpch']]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Superset&lt;/b&gt;과 &lt;b&gt;Trino&lt;/b&gt;가 서로 다른&lt;b&gt; Namespace(superset vs trino)&lt;/b&gt;에 있기 때문에 클러스터 내부 &lt;b&gt;DNS&lt;/b&gt; 형식인&lt;b&gt; trino-service.trino.svc&lt;/b&gt;로 접근해야 한다는 점입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.Superset UI에서 DB 연결 등록&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Superset 접속 &amp;rarr; 상단 메뉴 &lt;b&gt;Settings &amp;rarr; Database Connections&lt;/b&gt; 클릭&lt;/li&gt;
&lt;li&gt;우측 상단 &lt;b&gt;+ Database&lt;/b&gt; 클릭&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Trino&lt;/b&gt; 선택&lt;/li&gt;
&lt;li&gt;SQLAlchemy URI 입력&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;trino://admin@trino-service.trino.svc:8080/delta&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URI 구조는 아래와 같습니다.&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 126px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;부분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;의미&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;trino://&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;SQLAlchemy Trino 드라이버&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;admin&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;Trino 접속 유저명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;trino-service.trino.svc&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;서비스명.네임스페이스.svc (클러스터 내부 DNS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;8080&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;Trino 서비스 내부 포트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;/delta&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;카탈로그명&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 포트를 &lt;b&gt;NodePort&lt;/b&gt;로 넣으면 안 됩니다. &lt;br /&gt;NodePort는 클러스터 &lt;b&gt;외부&lt;/b&gt;에서 접근할 때 사용하는 포트입니다. &lt;br /&gt;Superset은 클러스터 &lt;b&gt;내부&lt;/b&gt;에 있기 때문에 반드시 &lt;b&gt;서비스 내부 포트&lt;/b&gt;인 8080을 사용해야 합니다.&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #14181f;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;클러스터 외부 (브라우저 등) &amp;rarr; NodePort 사용
클러스터 내부 (Superset Pod) &amp;rarr; 서비스 포트 8080 사용&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;5.&lt;b&gt;Test Connection&lt;/b&gt; 클릭 &amp;rarr; 연결 성공 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;6.&lt;b&gt;Connect&lt;/b&gt; 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1079&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2bkUt/dJMcabxIc9W/71nt0913hBArLyTZkvytu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2bkUt/dJMcabxIc9W/71nt0913hBArLyTZkvytu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2bkUt/dJMcabxIc9W/71nt0913hBArLyTZkvytu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2bkUt%2FdJMcabxIc9W%2F71nt0913hBArLyTZkvytu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1919&quot; height=&quot;1079&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1079&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URI에서 마지막 &lt;b&gt;/delta&lt;/b&gt;가 카탈로그명을 의미한다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;delta&lt;/b&gt; 카탈로그를 통해&lt;b&gt; Delta Lake&lt;/b&gt; 테이블을 조회할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;8. 차트 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 연결이 완료되면 Delta Lake 데이터를 기반으로 차트를 만들 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Dataset 등록&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;상단 메뉴 &lt;b&gt;Datasets&lt;/b&gt; 클릭&lt;/li&gt;
&lt;li&gt;&lt;b&gt;+ Dataset&lt;/b&gt; 클릭&lt;/li&gt;
&lt;li&gt;Database: Trino, Schema: default, Table 선택&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Add Dataset&lt;/b&gt; 클릭&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;차트 생성&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;상단 메뉴 &lt;b&gt;Charts&lt;/b&gt; 클릭&lt;/li&gt;
&lt;li&gt;&lt;b&gt;+ Chart&lt;/b&gt; 클릭&lt;/li&gt;
&lt;li&gt;Dataset 선택 &amp;rarr; 차트 유형 선택 (Bar Chart, Line Chart 등)&lt;/li&gt;
&lt;li&gt;쿼리 조건 설정 후 &lt;b&gt;Run&lt;/b&gt; 클릭&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1079&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwHUTo/dJMcahLuB1X/X6pNpoMNg2JDUEldWaGtg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwHUTo/dJMcahLuB1X/X6pNpoMNg2JDUEldWaGtg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwHUTo/dJMcahLuB1X/X6pNpoMNg2JDUEldWaGtg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwHUTo%2FdJMcahLuB1X%2FX6pNpoMNg2JDUEldWaGtg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1919&quot; height=&quot;1079&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1079&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dataset&lt;/b&gt;을 한 번 등록해 두면 이후 여러 차트와 대시보드에서 동일한 &lt;b&gt;Dataset&lt;/b&gt;을 &lt;u&gt;재사용할 수 있다는 점&lt;/u&gt;입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  마무리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kubernetes&lt;/b&gt; 위에서 &lt;b&gt;Superset&lt;/b&gt;을 구축하고 &lt;b&gt;Trino&lt;/b&gt;와 연동해서 &lt;b&gt;Delta Lake&lt;/b&gt; 데이터를 시각화하는 과정을 정리해 봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;커스텀 이미지&lt;/b&gt; &amp;rarr; 공식 Superset 이미지에 trino 드라이버 추가 후 Harbor에 푸시&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Helm OCI 방식&lt;/b&gt; &amp;rarr; Harbor에서 Chart를 가져와서 설치&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PostgreSQL + Redis&lt;/b&gt; &amp;rarr; Superset 메타데이터 영구 저장 + Celery 작업 큐&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Trino 연동&lt;/b&gt; &amp;rarr; trino-service.trino.svc 클러스터 내부 DNS로 연결&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Delta Lake 시각화&lt;/b&gt; &amp;rarr; Dataset 등록 &amp;rarr; 차트 생성 &amp;rarr; 대시보드 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 이번 구축에서 주의해야 할 포인트는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;pip install 시 /app/.venv/bin/python -m pip 경로로 실행해야 가상환경에 반영&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;Superset과 Trino가 다른 Namespace에 있으므로 trino-service.trino.svc 형식으로 접근&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;SECRET_KEY와 DB 접속 정보는 운영 환경에서 반드시 Kubernetes Secret으로 분리 권장&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 3가지였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 편에서는 지금까지 구축한 전체 스택을 GitOps 방식으로 관리하는 &lt;b&gt;ArgoCD&lt;/b&gt; 구축 과정을 다뤄보겠습니다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;궁금한 점이나 추가로 다뤄줬으면 하는 내용이 있다면 언제든지 편하게 말씀해 주세요!  &lt;/p&gt;</description>
      <category>[ Infra ]/Data Platform</category>
      <category>Data Platform</category>
      <category>Infra</category>
      <category>Kubernetes</category>
      <author>환이s</author>
      <guid isPermaLink="true">https://drg2524.tistory.com/248</guid>
      <comments>https://drg2524.tistory.com/248#entry248comment</comments>
      <pubDate>Wed, 10 Jun 2026 15:29:15 +0900</pubDate>
    </item>
    <item>
      <title>[Data Platform] Prometheus + Grafana 구축 가이드 (feat. kube-prometheus-stack)</title>
      <link>https://drg2524.tistory.com/247</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1079&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vXtys/dJMcaiwQyRK/kjKgvVSnUbk4oL8eQSS94k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vXtys/dJMcaiwQyRK/kjKgvVSnUbk4oL8eQSS94k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vXtys/dJMcaiwQyRK/kjKgvVSnUbk4oL8eQSS94k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvXtys%2FdJMcaiwQyRK%2FkjKgvVSnUbk4oL8eQSS94k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1919&quot; height=&quot;1079&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1079&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Intro&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;안녕하세요. 환이s입니다  &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://drg2524.tistory.com/246&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 데이터 레이크하우스 플랫폼의 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다섯 번째 스택으로 Airflow를 Kubernetes 위에 구축하고 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Spark Job을 스케줄링하는 전체 파이프라인을 정리해 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는 여섯 번째 스택, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Prometheus + Grafana&lt;/b&gt;를 &lt;b&gt;Kubernetes&lt;/b&gt; 환경에 구축하는 과정을 정리해보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;지금까지 구축한 &lt;u&gt;&lt;b&gt;Kafka, Spark, Ceph, Trino, Airflow&lt;/b&gt;&lt;/u&gt;가 실제로 잘 동작하고 있는지 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;한눈에 확인하려면 모니터링 환경이 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 역할을 담당하는 것이 &lt;b&gt;Prometheus&lt;/b&gt;와 &lt;b&gt;Grafana&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QjWTz/dJMcadIYM59/ieq0DmlzbobMBVCLH5Zxkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QjWTz/dJMcadIYM59/ieq0DmlzbobMBVCLH5Zxkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QjWTz/dJMcadIYM59/ieq0DmlzbobMBVCLH5Zxkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQjWTz%2FdJMcadIYM59%2Fieq0DmlzbobMBVCLH5Zxkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;241&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;kube-prometheus-stack으로 Prometheus + Grafana + Alertmanager + NodeExporter를 한 번에 설치하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;Prometheus와 Grafana 각각 Ingress로 외부 접근하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;Grafana 대시보드를 JSON 파일로 Import하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;까지 순서대로 확인해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. kube-prometheus-stack 아키텍처 개요&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;본격적인 설치에 앞서, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 구성의 전체 아키텍처를 먼저 이해하는 것이 중요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Kubernetes 클러스터
        │
        ▼
  NodeExporter (각 노드 메트릭 수집)
        │
        ▼
   Prometheus (메트릭 수집 및 저장)
        │
        ▼
    Grafana (메트릭 시각화 대시보드)
        │
  Alertmanager (알림 관리)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;각 컴포넌트의 역할은 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 104px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;컴포넌트&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Prometheus&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;클러스터 및 애플리케이션 메트릭 수집 및 저장&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Grafana&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Prometheus 데이터를 시각화하는 대시보드&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Alertmanager&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;알림 규칙 기반으로 알림 발송 관리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;NodeExporter&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;각 노드의 CPU, 메모리, 디스크 등 시스템 메트릭 수집&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;kube-prometheus-stack&lt;/b&gt; 하나로 이 &lt;u&gt;4가지 컴포넌트를 한 번에 설치&lt;/u&gt;하고 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ServiceMonitor&lt;/b&gt;까지 자동으로 연결된다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;개별 설치 방식과 비교하면 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;방식&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;개별 설치&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;버전 호환성 직접 관리, ServiceMonitor 별도 설정 필요&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;kube-prometheus-stack&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;버전 호환성 자동 관리, ServiceMonitor 자동 연결&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. 설치 순서&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 구축에서 사용하는 디렉토리 구조는 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;prometheus/
├── helm/
│   ├── install.sh              # Helm 설치 스크립트
│   └── values.yaml             # Helm 커스텀 값
├── manifests/
│   ├── 10_ingress_prometheus.yaml
│   └── 20_ingress_grafana.yaml
└── grafana_dashboards/
    ├── clusterOverview.json    # 클러스터 전체 현황 대시보드
    └── nodeOverview.json       # 노드별 리소스 현황 대시보드
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;설치 순서는 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Helm 설치 &amp;rarr; Ingress 설정 &amp;rarr; Grafana 대시보드 Import
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3. Helm으로 kube-prometheus-stack 설치&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Harbor&lt;/b&gt; 내부 레지스트리에서 &lt;b&gt;OCI&lt;/b&gt; 방식으로 Helm Chart를 가져옵니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #666666;&quot;&gt; (Harbor가 없는 환경이라면 아래 공식 이미지를 확인해서 대체하시면 됩니다.)&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780970676636&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;prom/alertmanager - Docker Image&quot; data-og-description=&quot;⁠Alertmanager The Alertmanager handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver integrations⁠ such as email, PagerDuty, OpsGenie, or many other &quot; data-og-host=&quot;hub.docker.com&quot; data-og-source-url=&quot;https://hub.docker.com/r/prom/alertmanager&quot; data-og-url=&quot;https://hub.docker.com/r/prom/alertmanager&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://hub.docker.com/r/prom/alertmanager&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hub.docker.com/r/prom/alertmanager&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;prom/alertmanager - Docker Image&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;⁠Alertmanager The Alertmanager handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver integrations⁠ such as email, PagerDuty, OpsGenie, or many other&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hub.docker.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1780970675342&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;prom/node-exporter - Docker Image&quot; data-og-description=&quot;...&quot; data-og-host=&quot;hub.docker.com&quot; data-og-source-url=&quot;https://hub.docker.com/r/prom/node-exporter&quot; data-og-url=&quot;https://hub.docker.com/r/prom/node-exporter&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://hub.docker.com/r/prom/node-exporter&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hub.docker.com/r/prom/node-exporter&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;prom/node-exporter - Docker Image&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hub.docker.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1780970674175&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;grafana/grafana - Docker Image&quot; data-og-description=&quot;Security Internet of things Monitoring &amp;amp; observability&quot; data-og-host=&quot;hub.docker.com&quot; data-og-source-url=&quot;https://hub.docker.com/r/grafana/grafana&quot; data-og-url=&quot;https://hub.docker.com/r/grafana/grafana&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://hub.docker.com/r/grafana/grafana&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hub.docker.com/r/grafana/grafana&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;grafana/grafana - Docker Image&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Security Internet of things Monitoring &amp;amp; observability&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hub.docker.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1780970672643&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;prom/prometheus - Docker Image&quot; data-og-description=&quot;⁠ Prometheus Visit prometheus.io⁠ for the full documentation, examples and guides. Prometheus, a Cloud Native Computing Foundation⁠ project, is a systems and service monitoring system. It collects metrics from configured targets at given intervals, e&quot; data-og-host=&quot;hub.docker.com&quot; data-og-source-url=&quot;https://hub.docker.com/r/prom/prometheus&quot; data-og-url=&quot;https://hub.docker.com/r/prom/prometheus&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://hub.docker.com/r/prom/prometheus&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hub.docker.com/r/prom/prometheus&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;prom/prometheus - Docker Image&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;⁠ Prometheus Visit prometheus.io⁠ for the full documentation, examples and guides. Prometheus, a Cloud Native Computing Foundation⁠ project, is a systems and service monitoring system. It collects metrics from configured targets at given intervals, e&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hub.docker.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# helm/install.sh
NAMESPACE=monitoring
RELEASE_NAME=prometheus
REGISTRY=harbor.local
CHART_VERSION=82.10.5

helm registry login $REGISTRY

helm upgrade --install $RELEASE_NAME \
  oci://$REGISTRY/helm-chart/kube-prometheus-stack \
  --version $CHART_VERSION \
  --namespace $NAMESPACE \
  --create-namespace \
  -f values.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;values.yaml&lt;/b&gt;의 핵심 설정은 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;NodeExporter 설정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;nodeExporter:
  enabled: true
  image:
    registry: harbor.local
    repository: library/monitoring/node-exporter
    tag: v1.8.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;각 노드에 &lt;b&gt;DaemonSet&lt;/b&gt;으로 배포되어 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;CPU, 메모리, 디스크 등 시스템 메트릭을 수집&lt;/span&gt;&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Prometheus 설정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;prometheus:
  service:
    type: NodePort
    port: 80
    targetPort: 9090
    nodePort: 31010
  prometheusSpec:
    storageSpec:
      volumeClaimTemplate:
        spec:
          storageClassName: rook-ceph-block
          accessModes: [&quot;ReadWriteOnce&quot;]
          resources:
            requests:
              storage: 20Gi
    image:
      registry: harbor.local
      repository: library/monitoring/prometheus
      tag: v2.50.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;수집된 메트릭&lt;/u&gt;을 &lt;u&gt;Ceph 블록 스토리지에 영구 저장&lt;/u&gt;합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;rook-ceph-block StorageClass&lt;/b&gt;를 사용해서 &lt;b&gt;RWO&lt;/b&gt; 방식의 &lt;u&gt;PVC를 생성&lt;/u&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Grafana 설정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;grafana:
  adminPassword: &quot;secure123!&quot;
  service:
    type: NodePort
    port: 80
    targetPort: 3000
    nodePort: 31020
  image:
    registry: harbor.local
    repository: library/monitoring/grafana
    tag: 12.4.0
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;⚠️ &lt;b&gt;adminPassword&lt;/b&gt;를 values.yaml에 고정값으로 설정한 이유는 &lt;b&gt;ArgoCD GitOps&lt;/b&gt; 배포 환경에서 &lt;b&gt;OutOfSync&lt;/b&gt; 방지를 위해서입니다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;운영 환경에서는&lt;b&gt; Kubernetes Secret&lt;/b&gt;으로 분리해서 관리하는 것을 권장합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Alertmanager 설정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;alertmanager:
  alertmanagerSpec:
    storage:
      volumeClaimTemplate:
        spec:
          storageClassName: rook-ceph-block
          accessModes: [&quot;ReadWriteOnce&quot;]
          resources:
            requests:
              storage: 5Gi
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Alertmanager&lt;/b&gt;는 이번 구성에서 &lt;u&gt;설치만 되어 있는 상태&lt;/u&gt;입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;실제 알림 receiver 설정(Slack, Email 등)은 추후 별도 가이드에서 다루겠습니다.&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Pod&lt;/b&gt;가 &lt;b&gt;Running&lt;/b&gt; 상태인지 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get pods -n monitoring
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정상적인 경우 아래처럼 보입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;NAME                                                      READY   STATUS    RESTARTS   AGE
prometheus-helm-kube-prome-prometheus-0                   2/2     Running   0          ...
prometheus-helm-grafana-xxxxxxxxxx-xxxxx                  3/3     Running   0          ...
prometheus-helm-kube-prome-alertmanager-0                 2/2     Running   0          ...
prometheus-helm-prometheus-node-exporter-xxxxx            1/1     Running   0          ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Pod&lt;/b&gt;가 &lt;b&gt;Running&lt;/b&gt; 상태가 되지 않는 경우 아래 명령어로 원인을 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;# Pod 상태 상세 확인
kubectl describe pod &amp;lt;pod-name&amp;gt; -n monitoring

# Pod 로그 확인
kubectl logs &amp;lt;pod-name&amp;gt; -n monitoring
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;자주 발생하는 원인은 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;증상&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;조치&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Pending&lt;/b&gt; 상태 지속&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;PVC 바인딩 실패&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;kubectl get pvc -n monitoring&lt;/b&gt;으로 PVC 상태 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;CrashLoopBackOff&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Grafana 설정 오류&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;kubectl logs&lt;/b&gt;로 Grafana 컨테이너 로그 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ImagePullBackOff&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이미지 pull 실패&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Harbor 로그인 상태 또는 공식 이미지 URL 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;4. Ingress 설정&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Prometheus&lt;/b&gt;와 &lt;b&gt;Grafana&lt;/b&gt; 각각 Ingress를 설정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Prometheus Ingress&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# manifests/10_ingress_prometheus.yaml
spec:
  ingressClassName: nginx
  rules:
  - host: prometheus.&amp;lt;your-ip&amp;gt;.sslip.io
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: prometheus-helm-kube-prome-prometheus
            port:
              number: 80
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Grafana Ingress&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# manifests/20_ingress_grafana.yaml
spec:
  ingressClassName: nginx
  rules:
  - host: grafana.&amp;lt;your-ip&amp;gt;.sslip.io
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: prometheus-helm-grafana
            port:
              number: 80
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;kubectl apply -f manifests/
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;접근 주소는 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;Prometheus : http://prometheus.&amp;lt;your-ip&amp;gt;.sslip.io
Grafana    : http://grafana.&amp;lt;your-ip&amp;gt;.sslip.io
             ID: admin
             PW: (values.yaml에서 설정한 adminPassword)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Prometheus&lt;/b&gt; 서비스명이&lt;b&gt; prometheus-helm-kube-prome-prometheus&lt;/b&gt;처럼 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;길게 자동 생성되기 때문에 &lt;b&gt;kubectl get svc -n monitoring&lt;/b&gt;으로 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;실제 서비스명을 먼저 확인하고 &lt;b&gt;Ingress&lt;/b&gt;에 적용해야 한다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;kubectl get svc -n monitoring
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;5. Grafana Dashboard Import&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Grafana&lt;/b&gt;에 &lt;u&gt;대시보드를 등록하는 방법은 두 가지&lt;/u&gt;가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;방법&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;UI Import&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;JSON 파일을 Grafana에서 직접 업로드, 즉시 반영&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;ConfigMap 프로비저닝&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;values.yaml에 설정해서 배포 시 자동 등록&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 구성에서는 &lt;u&gt;UI Import 방식을 사용&lt;/u&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;   이번 구성에서 사용하는 JSON 파일은 Grafana 공식 대시보드를 기반으로 직접 커스텀한 파일입니다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;공식 대시보드는 &lt;a href=&quot;https://grafana.com/grafana/dashboards/&quot;&gt;Grafana Dashboard&lt;/a&gt;에서 찾을 수 있으며, 다운로드한 JSON을 환경에 맞게 수정한 뒤 아래 방법으로 Import 하면 됩니다. &lt;/span&gt;&lt;/blockquote&gt;
&lt;figure id=&quot;og_1780972833379&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Grafana dashboards | Grafana Labs&quot; data-og-description=&quot;No results found. Please clear one or more filters.&quot; data-og-host=&quot;grafana.com&quot; data-og-source-url=&quot;https://grafana.com/grafana/dashboards/&quot; data-og-url=&quot;https://grafana.com/grafana/dashboards/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/EN0Kj/dJMb8TChN4W/r5L4PcsP5nSJCc8fsViqIk/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/VV5QG/dJMb8Xkntbe/DkBIDAAEc2KXCl0GdMTgl0/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://grafana.com/grafana/dashboards/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://grafana.com/grafana/dashboards/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/EN0Kj/dJMb8TChN4W/r5L4PcsP5nSJCc8fsViqIk/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/VV5QG/dJMb8Xkntbe/DkBIDAAEc2KXCl0GdMTgl0/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Grafana dashboards | Grafana Labs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;No results found. Please clear one or more filters.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;grafana.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Import 순서&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Grafana 접속 &amp;rarr; 좌측 메뉴 &lt;b&gt;Dashboards&lt;/b&gt; 클릭&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;우측 상단 &lt;b&gt;New &amp;rarr; Import&lt;/b&gt; 클릭&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;JSON 파일 업로드&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Import&lt;/b&gt; 버튼 클릭&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;1002&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beGtkz/dJMcabdmBUf/1oUrFbk1kWzj13le5rX1F1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beGtkz/dJMcabdmBUf/1oUrFbk1kWzj13le5rX1F1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beGtkz/dJMcabdmBUf/1oUrFbk1kWzj13le5rX1F1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeGtkz%2FdJMcabdmBUf%2F1oUrFbk1kWzj13le5rX1F1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1916&quot; height=&quot;1002&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;1002&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; 등록하면 아래 두 개의 대시보드를 확인할 수 있습니다. &lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;대시보드&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;내용&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;00_Cluster Overview&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;클러스터 전체 CPU, 메모리, 네트워크 현황&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;00_Node Overview&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;노드별 리소스 사용량 상세 현황&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; 즉, 여기서 확인할 수 있는 핵심은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;JSON 파일을 그대로 Import하면 &lt;b&gt;Prometheus&lt;/b&gt;에서 수집한 메트릭이 &lt;b&gt;Grafana&lt;/b&gt; 대시보드에 즉시 연동된다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1732&quot; data-origin-height=&quot;1076&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kS8g5/dJMcab5yAV1/0lgcs1EOIF5kYEKrvxnQJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kS8g5/dJMcab5yAV1/0lgcs1EOIF5kYEKrvxnQJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kS8g5/dJMcab5yAV1/0lgcs1EOIF5kYEKrvxnQJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkS8g5%2FdJMcab5yAV1%2F0lgcs1EOIF5kYEKrvxnQJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1732&quot; height=&quot;1076&quot; data-origin-width=&quot;1732&quot; data-origin-height=&quot;1076&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;  마무리&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Kubernetes&lt;/b&gt; 위에서 &lt;b&gt;kube-prometheus-stack&lt;/b&gt;으로&lt;span style=&quot;color: #ee2323;&quot;&gt; &lt;b&gt;Prometheus + Grafana + Alertmanager + NodeExporter&lt;/b&gt;&lt;/span&gt;를 한 번에 구축하는 과정을 정리해 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정리하면,&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;kube-prometheus-stack&lt;/b&gt; &amp;rarr; 4가지 컴포넌트를 한 번에 설치, 버전 호환성 자동 관리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Ceph 블록 스토리지&lt;/b&gt; &amp;rarr; Prometheus / Alertmanager 메트릭 영구 저장&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Ingress&lt;/b&gt; &amp;rarr; Prometheus / Grafana 각각 외부 접근&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;JSON Import&lt;/b&gt; &amp;rarr; 대시보드를 파일로 관리하고 UI에서 바로 등록&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;라고 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;특히 이번 구축에서 주의해야 할 포인트는&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;Ingress 서비스명은 반드시 kubectl get svc -n monitoring으로 확인 후 적용&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;adminPassword 고정값 설정은 ArgoCD OutOfSync 방지 목적 &amp;mdash; 운영 환경에서는 Secret으로 분리 권장&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;Alertmanager는 설치만 된 상태 &amp;mdash; 실제 알림 receiver 설정은 추후 별도 가이드에서 다룰 예정&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 3가지였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다음 편에서는 데이터를 비즈니스 관점으로 시각화하는 &lt;b&gt;Superset&lt;/b&gt; 구축 과정을 다뤄보겠습니다.  &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;궁금한 점이나 추가로 다뤄줬으면 하는 내용이 있다면 언제든지 편하게 말씀해 주세요!  &lt;/span&gt;&lt;/p&gt;</description>
      <category>[ Infra ]/Data Platform</category>
      <category>Data Platform</category>
      <category>Infra</category>
      <category>Kubernetes</category>
      <author>환이s</author>
      <guid isPermaLink="true">https://drg2524.tistory.com/247</guid>
      <comments>https://drg2524.tistory.com/247#entry247comment</comments>
      <pubDate>Tue, 9 Jun 2026 11:45:58 +0900</pubDate>
    </item>
    <item>
      <title>[Data Platform] Airflow 구축 가이드 (feat. Spark on Kubernetes)</title>
      <link>https://drg2524.tistory.com/246</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1079&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdXGWD/dJMcabxGdti/qeXKtUs8yyiSeGcDzdSkJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdXGWD/dJMcabxGdti/qeXKtUs8yyiSeGcDzdSkJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdXGWD/dJMcabxGdti/qeXKtUs8yyiSeGcDzdSkJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdXGWD%2FdJMcabxGdti%2FqeXKtUs8yyiSeGcDzdSkJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1919&quot; height=&quot;1079&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1079&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Intro&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;안녕하세요. 환이s입니다  &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://drg2524.tistory.com/244&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 데이터 레이크하우스 플랫폼의 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;네 번째 스택으로 Trino + Hive Metastore + PostgreSQL 3계층 구조로 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Delta Lake SQL 조회 환경을 구축하는 과정을 정리해 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는 다섯 번째 스택, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Airflow&lt;/b&gt;를 &lt;b&gt;Kubernetes&lt;/b&gt; 환경에 구축하는 과정을 정리해보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Spark Streaming&lt;/b&gt;으로 처리된 데이터는 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;Delta Lake 포맷으로 Ceph S3&lt;/u&gt;에 쌓이게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 흐름을 &lt;u&gt;주기적으로 실행하고 스케줄링하려면 &lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;워크플로우 오케스트레이터&lt;/u&gt;가 필요한데,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 역할을 담당하는 것이 바로 &lt;b&gt;Airflow&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QhOxW/dJMcahLsBZ8/Cl1dPMyrf9x3sivxBIad11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QhOxW/dJMcahLsBZ8/Cl1dPMyrf9x3sivxBIad11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QhOxW/dJMcahLsBZ8/Cl1dPMyrf9x3sivxBIad11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQhOxW%2FdJMcahLsBZ8%2FCl1dPMyrf9x3sivxBIad11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;Helm으로 Airflow를 Kubernetes 위에 설치하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;CeleryExecutor + Redis 기반 분산 실행 구조&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;FileBrowser로 DAG 파일을 Web UI에서 직접 관리하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;SparkKubernetesOperator로 Spark Job을 K8s에 제출하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;PythonOperator로 S3 이벤트 로그를 파티셔닝하고 Trino와 동기화하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;까지 순서대로 확인해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. Airflow 아키텍처 개요&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;본격적인 설치에 앞서, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 구성의 전체 아키텍처를 먼저 이해하는 것이 중요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;FileBrowser (Web UI)
      │  DAG 파일 업로드/편집
      ▼
CephFS PVC (airflow-helm-dags)
      │  공유 볼륨 마운트
      ▼
Airflow Scheduler ──► Airflow Worker (CeleryExecutor)
                              │
                    SparkKubernetesOperator
                              │
                    SparkApplication YAML 제출
                              ▼
                    Spark Driver / Executor Pod
                              │
                    Ceph S3 (Delta Lake) 적재
                              │
                    Trino 파티션 동기화
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;각 컴포넌트의 역할은 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 122px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;컴포넌트&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Airflow Scheduler&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;DAG 스케줄 감지 및 Task 실행 지시&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Airflow Worker&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;CeleryExecutor 기반으로 Task를 실제 실행&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Redis&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Celery 브로커 역할 &amp;mdash; Scheduler와 Worker 간 Task 큐&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;CephFS PVC&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Scheduler, Worker, Spark Pod가 공유하는 DAG 파일 저장소&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;FileBrowser&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Web UI에서 DAG 파일을 직접 업로드/편집&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;DAG&lt;/b&gt; 파일이 저장된 &lt;b&gt;CephFS PVC&lt;/b&gt;를 &lt;b&gt;Scheduler, Worker, Spark Pod&lt;/b&gt; 모두가 동시에 마운트하기 때문에 &lt;b&gt;ReadWriteMany&lt;/b&gt; 모드가 반드시 필요하다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. 설치 순서&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 구축에서 사용하는 디렉토리 구조는 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;airflow/
├── helm/
│   ├── install.sh        # Helm 설치 스크립트
│   └── values.yaml       # Helm 커스텀 값
└── manifests/
    ├── 00_rbac/
    │   ├── 10_airflow-spark-role.yaml      # Spark 제어 권한 정의
    │   └── 20_airflow-spark-binding.yaml   # Worker SA에 권한 바인딩
    ├── 10_pvc/
    │   └── 10_airflow-pvc.yaml             # 로그 PVC (수동 생성)
    ├── 20_ingress/
    │   └── 10_airflow-ingress-webserver.yaml
    └── 30_filebrowser/
        ├── 10_airflow_file_browser.yaml    # FileBrowser Deployment
        └── 20_airflow-ingress-filebrowser.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;설치 순서는 &lt;b&gt;반드시&lt;/b&gt; 아래 순서를 지켜야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;PVC 수동 생성 &amp;rarr; RBAC 설정 &amp;rarr; Helm 설치 &amp;rarr; Ingress &amp;rarr; FileBrowser
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;반드시 지켜야 하는 포인트가 1곳 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;로그 PVC(airflow-logs-manual-pvc)&lt;/b&gt;는 &lt;b&gt;Helm&lt;/b&gt; 설치 전에 수동으로 먼저 생성해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;values.yaml&lt;/b&gt;에서 &lt;b&gt;existingClaim&lt;/b&gt;으로 참조하기 때문에, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Helm&lt;/b&gt;이 먼저 실행되면 &lt;b&gt;PVC&lt;/b&gt;를 찾지 못해 오류가 발생합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3. PVC 사전 생성&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Airflow&lt;/b&gt; 로그 PVC를 먼저 생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;# manifests/10_pvc/10_airflow-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: airflow-logs-manual-pvc
  namespace: airflow
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: rook-cephfs
  resources:
    requests:
      storage: 5Gi
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;kubectl apply -f manifests/10_pvc/10_airflow-pvc.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;DAG&lt;/b&gt; 파일용 &lt;b&gt;PVC&lt;/b&gt;는 &lt;b&gt;Helm&lt;/b&gt;이 자동으로 생성하지만, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;로그용 PVC는 반드시 Helm 설치 전에 수동으로 먼저 만들어야 한다는 점&lt;/u&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;4. RBAC 설정 (Spark 연동 권한)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Airflow Worker&lt;/b&gt;가 &lt;b&gt;Kubernetes&lt;/b&gt;에 &lt;b&gt;SparkApplication&lt;/b&gt;을 제출하려면 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;airflow-helm-worker ServiceAccount&lt;/span&gt;&lt;/b&gt;에 권한이 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;# manifests/00_rbac/10_airflow-spark-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: airflow-spark-manager-role
  namespace: airflow
rules:
- apiGroups: [&quot;sparkoperator.k8s.io&quot;]
  resources: [&quot;sparkapplications&quot;, &quot;sparkapplications/status&quot;]
  verbs: [&quot;create&quot;, &quot;get&quot;, &quot;watch&quot;, &quot;list&quot;, &quot;update&quot;, &quot;patch&quot;, &quot;delete&quot;]
- apiGroups: [&quot;&quot;]
  resources: [&quot;pods&quot;, &quot;pods/log&quot;, &quot;pods/status&quot;, &quot;services&quot;,
              &quot;configmaps&quot;, &quot;persistentvolumeclaims&quot;]
  verbs: [&quot;create&quot;, &quot;get&quot;, &quot;watch&quot;, &quot;list&quot;, &quot;update&quot;, &quot;patch&quot;,
          &quot;delete&quot;, &quot;deletecollection&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# manifests/00_rbac/20_airflow-spark-binding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: airflow-worker-spark-binding
  namespace: airflow
subjects:
- kind: ServiceAccount
  name: airflow-helm-worker
  namespace: airflow
roleRef:
  kind: Role
  name: airflow-spark-manager-role
  apiGroup: rbac.authorization.k8s.io
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;kubectl apply -f manifests/00_rbac/
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;5. Helm으로 Airflow 설치&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt; Harbor&lt;/b&gt; 내부 레지스트리에서 &lt;b&gt;OCI&lt;/b&gt; 방식으로 Helm Chart를 가져옵니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;(Harbor가 없는 환경이라면 아래 공식 이미지를 확인해서 대체하시면 됩니다.)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780882383469&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;bitnami/postgresql - Docker Image&quot; data-og-description=&quot;POSTGRESQL_BASE_DIRPostgreSQL installation directory/opt/bitnami/postgresqlPOSTGRESQL_DEFAULT_CONF_DIRPostgreSQL configuration directory$POSTGRESQL_BASE_DIR/conf.defaultPOSTGRESQL_CONF_DIRPostgreSQL configuration directory$POSTGRESQL_BASE_DIR/confPOSTGRESQ&quot; data-og-host=&quot;hub.docker.com&quot; data-og-source-url=&quot;https://hub.docker.com/r/bitnami/postgresql&quot; data-og-url=&quot;https://hub.docker.com/r/bitnami/postgresql&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c1q5Xc/dJMb82eURLt/Xr47JkJoi9kF8bfBdqWSuk/img.jpg?width=400&amp;amp;height=350&amp;amp;face=0_0_400_350&quot;&gt;&lt;a href=&quot;https://hub.docker.com/r/bitnami/postgresql&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hub.docker.com/r/bitnami/postgresql&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c1q5Xc/dJMb82eURLt/Xr47JkJoi9kF8bfBdqWSuk/img.jpg?width=400&amp;amp;height=350&amp;amp;face=0_0_400_350');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;bitnami/postgresql - Docker Image&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;POSTGRESQL_BASE_DIRPostgreSQL installation directory/opt/bitnami/postgresqlPOSTGRESQL_DEFAULT_CONF_DIRPostgreSQL configuration directory$POSTGRESQL_BASE_DIR/conf.defaultPOSTGRESQL_CONF_DIRPostgreSQL configuration directory$POSTGRESQL_BASE_DIR/confPOSTGRESQ&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hub.docker.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1780882378708&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;filebrowser/filebrowser - Docker Image&quot; data-og-description=&quot;File Browser provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files. It allows the creation of multiple users and each user can have its own directory. Please check the main&quot; data-og-host=&quot;hub.docker.com&quot; data-og-source-url=&quot;https://hub.docker.com/r/filebrowser/filebrowser&quot; data-og-url=&quot;https://hub.docker.com/r/filebrowser/filebrowser&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://hub.docker.com/r/filebrowser/filebrowser&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hub.docker.com/r/filebrowser/filebrowser&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;filebrowser/filebrowser - Docker Image&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;File Browser provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files. It allows the creation of multiple users and each user can have its own directory. Please check the main&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hub.docker.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1780882361673&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;redis - Official Image | Docker Hub&quot; data-og-description=&quot;⁠Quick reference ⁠Supported tags and respective Dockerfile links 8.8.0, 8.8, 8, 8.8.0-trixie, 8.8-trixie, 8-trixie, latest, trixie⁠ 8.8.0-alpine, 8.8-alpine, 8-alpine, 8.8.0-alpine3.23, 8.8-alpine3.23, 8-alpine3.23, alpine, alpine3.23⁠ 8.6.4, 8.6, &quot; data-og-host=&quot;hub.docker.com&quot; data-og-source-url=&quot;https://hub.docker.com/_/redis&quot; data-og-url=&quot;https://hub.docker.com/_/redis&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://hub.docker.com/_/redis&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hub.docker.com/_/redis&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;redis - Official Image | Docker Hub&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;⁠Quick reference ⁠Supported tags and respective Dockerfile links 8.8.0, 8.8, 8, 8.8.0-trixie, 8.8-trixie, 8-trixie, latest, trixie⁠ 8.8.0-alpine, 8.8-alpine, 8-alpine, 8.8.0-alpine3.23, 8.8-alpine3.23, 8-alpine3.23, alpine, alpine3.23⁠ 8.6.4, 8.6,&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hub.docker.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1780882359043&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;apache/airflow - Docker Image&quot; data-og-description=&quot;⁠Apache Airflow Apache Airflow⁠ (or simply Airflow) is a platform to programmatically author, schedule, and monitor workflows. When workflows are defined as code, they become more maintainable, versionable, testable, and collaborative. Use Airflow to a&quot; data-og-host=&quot;hub.docker.com&quot; data-og-source-url=&quot;https://hub.docker.com/r/apache/airflow&quot; data-og-url=&quot;https://hub.docker.com/r/apache/airflow&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://hub.docker.com/r/apache/airflow&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hub.docker.com/r/apache/airflow&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;apache/airflow - Docker Image&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;⁠Apache Airflow Apache Airflow⁠ (or simply Airflow) is a platform to programmatically author, schedule, and monitor workflows. When workflows are defined as code, they become more maintainable, versionable, testable, and collaborative. Use Airflow to a&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hub.docker.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# helm/install.sh
NAMESPACE=airflow
RELEASE_NAME=airflow
REGISTRY=harbor.local
CHART_VERSION=1.15.0

helm registry login $REGISTRY

helm upgrade --install $RELEASE_NAME \
  oci://$REGISTRY/helm-chart/airflow \
  --version $CHART_VERSION \
  --namespace $NAMESPACE \
  --create-namespace \
  -f values.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;values.yaml&lt;/b&gt;의 핵심 설정은 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Executor 설정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;executor: &quot;CeleryExecutor&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;CeleryExecutor&lt;/b&gt;는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Redis를 브로커로 사용해 Worker를 분산 실행합니다&lt;/b&gt;&lt;/span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;이미지 설정 (Harbor 내부 레지스트리)&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Noto Serif KR';&quot;&gt; &lt;b&gt;(Harbor가 없는 환경이라면 위 공식 이미지를 확인해서 대체하시면 됩니다.)&lt;/b&gt; &lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;images:
  airflow:
    repository: harbor.local/library/airflow
    tag: 2.10.5
  redis:
    repository: harbor.local/library/redis
    tag: '7.2'
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;스토리지 설정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;dags:
  persistence:
    enabled: true
    storageClassName: &quot;rook-cephfs&quot;
    accessMode: ReadWriteMany   # Scheduler + Worker + Spark Pod 동시 마운트
    size: 2Gi

logs:
  persistence:
    enabled: true
    storageClassName: &quot;rook-cephfs&quot;
    existingClaim: airflow-logs-manual-pvc  # 3번에서 수동 생성한 PVC 참조
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ArgoCD 연동 설정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;migrateDatabaseJob:
  useHelmHooks: false
  jobAnnotations:
    &quot;argocd.argoproj.io/hook&quot;: Sync

createUserJob:
  useHelmHooks: false
  jobAnnotations:
    &quot;argocd.argoproj.io/hook&quot;: Sync
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;ArgoCD GitOps&lt;/span&gt; &lt;/b&gt;배포 환경에서는 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;Helm Hook 대신 ArgoCD Hook으로 Job을 제어&lt;/u&gt;합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;useHelmHooks: false&lt;/b&gt;&lt;/span&gt;로 설정하지 않으면 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;ArgoCD&lt;/b&gt;&lt;/span&gt;와 충돌이 발생할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;ArgoCD 자체 구축 및 설정에 대한 내용은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;추후 ArgoCD 구축 가이드에서 더 자세하게 다루겠습니다. &lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;ArgoCD를 사용하지 않는 환경이라면 이 설정은 생략하셔도 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Airflow Pod가 Running 상태인지 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get pods -n airflow
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정상적인 경우 아래처럼 보입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780883014815&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;NAME                                        READY   STATUS    RESTARTS   AGE
airflow-helm-scheduler-xxxxxxxxxx-xxxxx     1/1     Running   0          ...
airflow-helm-webserver-xxxxxxxxxx-xxxxx     1/1     Running   0          ...
airflow-helm-worker-xxxxxxxxxx-xxxxx        1/1     Running   0          ...
airflow-helm-redis-0                        1/1     Running   0          ...
airflow-helm-postgresql-0                   1/1     Running   0          ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;만약 Pod가 Running 상태가 되지 않는 경우 아래 명령어로 원인을 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780883045673&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Pod 상태 상세 확인
kubectl describe pod &amp;lt;pod-name&amp;gt; -n airflow

# Pod 로그 확인
kubectl logs &amp;lt;pod-name&amp;gt; -n airflow&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;자주 발생하는 원인은 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 83px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;증상&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;조치&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Pending 상태 지속&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;PVC 바인딩 실패&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;kubectl get pvc -n airflow&lt;/b&gt;로 PVC 상태 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;CrashLoopBackOff&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;로그 PVC를 찾지 못함&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;airflow-logs-manual-pvc&lt;/b&gt; 사전 생성 여부 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ImagePullBackOff&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이미지 pull 실패&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Harbor 로그인 상태 또는 공식 이미지 URL 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;6. Ingress 설정&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;Airflow Webserver Ingress&lt;/u&gt;를 설정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# manifests/20_ingress/10_airflow-ingress-webserver.yaml
spec:
  ingressClassName: nginx
  rules:
  - host: airflow.&amp;lt;your-ip&amp;gt;.sslip.io
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: airflow-helm-webserver
            port:
              number: 8080
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;kubectl apply -f manifests/20_ingress/
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;7. FileBrowser 구성&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;DAG&lt;/b&gt; 파일을 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Web UI에서 직접 올리고 편집할 수 있도록 FileBrowser를 구성&lt;/span&gt;&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Airflow&lt;/b&gt;에서 &lt;b&gt;DAG&lt;/b&gt; 파일을 반영하는 방법은 다양하지만 크게 두 가지가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 63px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;방법&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Git Sync&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;GitOps 방식, PR/Commit 기반 반영&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;FileBrowser&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Web UI에서 직접 업로드/편집, 즉시 반영&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 구성에서는 &lt;b&gt;FileBrowser&lt;/b&gt;를 선택했습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;DAG PVC(airflow-helm-dags)&lt;/b&gt;를 직접 마운트해서, &lt;u&gt;파일을 업로드하는 즉시 &lt;b&gt;Scheduler&lt;/b&gt;가 감지&lt;/u&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;# manifests/30_filebrowser/10_airflow_file_browser.yaml
containers:
- name: explorer
  image: harbor.local/library/filebrowser:latest
  args: [&quot;--database&quot;, &quot;/config/database.db&quot;, &quot;--noauth&quot;]
  volumeMounts:
  - name: dags-data
    mountPath: /srv/airflow_dags   # DAG PVC 마운트
  - name: fb-config
    mountPath: /config
volumes:
- name: dags-data
  persistentVolumeClaim:
    claimName: airflow-helm-dags   # Airflow DAG PVC 공유
- name: fb-config
  persistentVolumeClaim:
    claimName: airflow-filebrowser-config-pvc
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;kubectl apply -f manifests/30_filebrowser/
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;FileBrowser&lt;/b&gt;가 &lt;b&gt;Airflow&lt;/b&gt;의 &lt;b&gt;DAG&lt;/b&gt; &lt;b&gt;PVC&lt;/b&gt;를 &lt;u&gt;직접 마운트하기 때문에 Web UI에서 파일을 올리는 즉시 &lt;b&gt;Scheduler&lt;/b&gt;가 새 &lt;b&gt;DAG&lt;/b&gt;를 인식한다는 점&lt;/u&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;8. DAG 작성 - Spark Job 제출&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Airflow&lt;/b&gt;에서&lt;b&gt; Spark Job&lt;/b&gt;을 실행하는 방법은 두 가지로 나눌 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;방법 1. SparkKubernetesOperator (YAML 기반)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;K8s&lt;/b&gt;에 &lt;b&gt;SparkApplication YAML&lt;/b&gt;을 직접 제출하는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# spark_s3_daily_job_v2.4
from airflow.providers.cncf.kubernetes.operators.spark_kubernetes import SparkKubernetesOperator

submit_spark = SparkKubernetesOperator(
    task_id='submit_spark_s3_job',
    namespace='airflow',
    application_file='yaml/spark-s3-reader.yaml',  # DAG PVC 내 YAML 참조
    kubernetes_conn_id='kubernetes_default',
    do_xcom_push=False,
    params={
        'interval_min': INTERVAL_MINUTES
    }
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;SparkApplication&lt;/b&gt; &lt;b&gt;YAML&lt;/b&gt;에서 핵심 설정은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;perl&quot;&gt;&lt;code&gt;# yaml/spark-s3-reader.yaml (주요 부분)
spec:
  mainApplicationFile: &quot;local:///opt/airflow/dags/scripts/filter_review.py&quot;
  arguments:
    - &quot;--start_date&quot;
    - &quot;{{ data_interval_start.in_timezone('Asia/Seoul').strftime('%Y-%m-%d %H:%M:%S') }}&quot;
    - &quot;--end_date&quot;
    - &quot;{{ data_interval_end.in_timezone('Asia/Seoul').strftime('%Y-%m-%d %H:%M:%S') }}&quot;
  timeToLiveSeconds: 300  # 완료 후 5분 뒤 Pod 자동 삭제
  restartPolicy:
    type: Never
  sparkConf:
    # DAG PVC를 Driver/Executor Pod에도 마운트
    &quot;spark.kubernetes.driver.volumes.persistentVolumeClaim.airflow-dags-vol.options.claimName&quot;: &quot;airflow-helm-dags&quot;
    &quot;spark.kubernetes.driver.volumes.persistentVolumeClaim.airflow-dags-vol.mount.path&quot;: &quot;/opt/airflow/dags&quot;
    &quot;spark.kubernetes.executor.volumes.persistentVolumeClaim.airflow-dags-vol.options.claimName&quot;: &quot;airflow-helm-dags&quot;
    &quot;spark.kubernetes.executor.volumes.persistentVolumeClaim.airflow-dags-vol.mount.path&quot;: &quot;/opt/airflow/dags&quot;
    # 이벤트 로그 &amp;rarr; Ceph S3 저장
    &quot;spark.eventLog.enabled&quot;: &quot;true&quot;
    &quot;spark.eventLog.dir&quot;: &quot;s3a://spark-event-logs/&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Spark Driver/Executor Pod&lt;/b&gt;도 &lt;b&gt;DAG PVC&lt;/b&gt;를 마운트해야 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;local:///opt/airflow/dags/scripts/filter_review.py&lt;/span&gt; &lt;/b&gt;경로의 스크립트를 실행할 수 있다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;방법 2. PythonOperator (S3 직접 처리 + Trino 파티션 동기화)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Ceph S3&lt;/b&gt;의 &lt;b&gt;Spark&lt;/b&gt; &lt;u&gt;이벤트 로그를 직접 읽어 처리하는 방식&lt;/u&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# spark_s3_optimizer_v1.2
def add_regdt_and_partition(**context):
    s3 = boto3.client('s3',
        endpoint_url='http://&amp;lt;ceph-rgw-endpoint&amp;gt;',
        ...
    )

    # dt= 접두사가 이미 있는 파일은 스킵 (중복 처리 방지)
    if key.startswith('dt='):
        skipped_count += 1
        continue

    # 처리 완료 후 Trino 파티션 자동 동기화
    query = &quot;CALL hive.system.sync_partition_metadata('spark_logs', 'raw_logs_partitioned', 'ADD')&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 &lt;b&gt;DAG&lt;/b&gt;는 &lt;u&gt;30분 주기로 실행&lt;/u&gt;되며, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;Spark Job이 쌓은 이벤트 로그를 날짜별 파티션 구조로 정리하고 &lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;Trino에서 바로 조회할 수 있도록 파티션을 동기화&lt;/u&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Airflow&lt;/b&gt;가 &lt;u&gt;단순 스케줄링에 그치지 않고&lt;/u&gt;, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;S3 데이터 정리부터 Trino 파티션 동기화까지 파이프라인 전체를 자동화&lt;/u&gt;한다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;  마무리&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Kubernetes&lt;/b&gt; 위에서 &lt;b&gt;Airflow&lt;/b&gt;를 구축하고 &lt;b&gt;Spark Job&lt;/b&gt;을 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;스케줄링하는 전체 파이프라인&lt;/u&gt;&lt;/span&gt;&lt;/b&gt;을 정리해 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정리하면,&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;CeleryExecutor + Redis&lt;/b&gt; &amp;rarr; Worker 분산 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;CephFS ReadWriteMany PVC&lt;/b&gt; &amp;rarr; Scheduler, Worker, Spark Pod 간 DAG 파일 공유&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;FileBrowser&lt;/b&gt; &amp;rarr; Web UI에서 DAG 직접 업로드/편집, 즉시 반영&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;SparkKubernetesOperator&lt;/b&gt; &amp;rarr; YAML 기반으로 K8s에 Spark Job 제출&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;PythonOperator + Trino 연동&lt;/b&gt; &amp;rarr; S3 이벤트 로그 파티셔닝 후 쿼리 자동 동기화&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;라고 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;특히 이번 구축에서 주의해야 할 포인트는&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;로그 PVC는 Helm 설치 전에 수동 생성 필수&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;DAG PVC는 반드시 ReadWriteMany &amp;mdash; Spark Pod까지 동시 마운트 필요&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;ArgoCD 환경에서는 useHelmHooks: false + ArgoCD Hook 설정 필수&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;SparkApplication YAML에서 DAG PVC를 Driver/Executor 양쪽에 마운트해야 스크립트 실행 가능&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 4가지였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다음 편에서는 지금까지 구축한 전체 파이프라인을 모니터링하는 &lt;b&gt;Prometheus + Grafana&lt;/b&gt; 구성을 다뤄보겠습니다.  &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;궁금한 점이나 추가로 다뤄줬으면 하는 내용이 있다면 언제든지 편하게 말씀해 주세요!  &lt;/span&gt;&lt;/p&gt;</description>
      <category>[ Infra ]/Data Platform</category>
      <category>Data Platform</category>
      <category>Infra</category>
      <category>Kubernetes</category>
      <author>환이s</author>
      <guid isPermaLink="true">https://drg2524.tistory.com/246</guid>
      <comments>https://drg2524.tistory.com/246#entry246comment</comments>
      <pubDate>Mon, 8 Jun 2026 10:52:48 +0900</pubDate>
    </item>
    <item>
      <title>『행복한 이기주의자』 리뷰 | 흔들리지 않는 사람이 되고 싶었다</title>
      <link>https://drg2524.tistory.com/245</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;1200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C3HxW/dJMcabxEHDn/p3kYbcAHypkaGgpeltSY51/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C3HxW/dJMcabxEHDn/p3kYbcAHypkaGgpeltSY51/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C3HxW/dJMcabxEHDn/p3kYbcAHypkaGgpeltSY51/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC3HxW%2FdJMcabxEHDn%2Fp3kYbcAHypkaGgpeltSY51%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;828&quot; height=&quot;1200&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;1200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;시작하기 전에&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;마음이 단단한 사람을 보면&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;가끔 궁금해진다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;저 사람은 어떻게 저렇게&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;쉽게 흔들리지 않을 수 있을까.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;누군가의 말 한마디에도,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;뜻대로 풀리지 않는 하루에도,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;크게 무너지지 않고&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;자기 자리를 지키는 사람.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;나도 그런 사람이 되고 싶었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;감정에 쉽게 끌려다니지 않고,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;남의 시선보다 내 기준으로 살아가는 사람.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 마음 하나를 붙들고&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 책을 펼쳤다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt; 웨인 다이어의 『행복한 이기주의자』&lt;/b&gt;는 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그렇게 시작된 독서였다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote style=&quot;text-align: center;&quot; data-ke-style=&quot;style1&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이기주의자라는 말이 불편하지 않았냐고?&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;처음 제목을 봤을 때 솔직히 조금 낯설었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이기주의자.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;우리는 그 단어를 대개 부정적으로 배운다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;자기만 아는 사람,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;타인을 배려하지 않는 사람.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그런데 이 책에서 말하는 이기주의는&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그런 이기주의가 아니었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;자신을 먼저 온전히 아끼고,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;자신의 감정을 솔직하게 인정하며,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;타인의 시선이 아닌 자신의 기준으로 살아가는 것.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그것이 이 책이 말하는&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;진짜 이기주의였다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;가장 오래 마음에 남았던 챕터&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;책을 읽는 내낸 여러 문장들이 마음에 걸렸지만,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그중에서도 챕터 5,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&quot;미지의 세계를 탐험하라&quot;&lt;/b&gt;는&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;유독 오래 머물렀다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;남들의 눈치를 보지 말고,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;오직 내가 원한다는 이유만으로&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;새로운 경험과 모험을 즐겨라.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 문장 앞에서 잠깐 멈췄다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;얼마나 많은 것들을&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;내가 원해서가 아니라&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;남들이 어떻게 볼까 두려워서&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;시작하지 못했는지.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;혹은 시작했어도&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;주변의 반응에 흔들려&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;금세 포기해 버렸는지.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 챕터는 조용하지만 분명하게 말하고 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;당신의 인생은&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;당신이 원한다는 이유 하나만으로도&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;충분히 시작할 수 있다고.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;읽고 나서 드는 솔직한 생각&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;책을 다 읽고 나서도&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;한동안 이런 생각을 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;나는 정말 더 단단해졌을까.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;사실 잘 모르겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;책 한 권이 사람을 단번에 바꿔놓지는 않는다는 걸 안다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그리고 단단해진다는 것이&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;어떤 감각으로 느껴지는 건지도&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아직은 잘 모르겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다만 한 가지는 알 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 책을 읽기 전의 나와&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;일고 난 후의 나 사이에&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아주 작은 질문 하나가 생겼다는 것.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;지금 나는 내 기준으로 살고 있는가.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 질문이 남아있다는 것만으로도&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 책은 충분히 제 역할을 했다고 생각한다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이런 분께 권하고 싶다&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;감정적으로 쉽게 휘둘린다고 느끼는 사람,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;남의 말 한마디에 하루가 무너지는 것 같은 사람,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정작 자신이 뭘 원하는지 잘 모르겠다는 사람.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그런 분들께 한 번쯤 낭독해 보라고 권하고 싶다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;소리 내어 읽어보면 더 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;눈으로 훑는 것과&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;목소리로 내뱉는 것은&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;마음에 닿는 깊이가 다르다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;마무리하며&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt; 『행복한 이기주의자』&lt;/b&gt;는 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;나를 사랑하라고 크게 외치는 책이 아니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;대신 아주 조용하고 차분하게,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그러나 분명하게 묻는다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;당신은 지금 당신 자신의 편인가.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 질문이&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;마음 어딘가에 오래 머물렀다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;곧 후속편인 두 번째 이야기도 다 읽게 되면&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이어서 또 정리해보려 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아마 그때는 조금 더 다른 생각을 하게 될지도 모른다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>[ My Bookshelf ]</category>
      <category>book</category>
      <author>환이s</author>
      <guid isPermaLink="true">https://drg2524.tistory.com/245</guid>
      <comments>https://drg2524.tistory.com/245#entry245comment</comments>
      <pubDate>Fri, 5 Jun 2026 13:53:34 +0900</pubDate>
    </item>
    <item>
      <title>[Data Platform] Trino 구축 가이드 (feat. Hive Metastore + Delta Lake)</title>
      <link>https://drg2524.tistory.com/244</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1079&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tdZQu/dJMcaaFAyLg/e9sS6gNZE08FYeluUTSkvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tdZQu/dJMcaaFAyLg/e9sS6gNZE08FYeluUTSkvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tdZQu/dJMcaaFAyLg/e9sS6gNZE08FYeluUTSkvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtdZQu%2FdJMcaaFAyLg%2Fe9sS6gNZE08FYeluUTSkvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1919&quot; height=&quot;1079&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;1079&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;Intro&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;안녕하세요. 환이s입니다  &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://drg2524.tistory.com/243&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 데이터 레이크하우스 플랫폼의 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;세 번째 스택으로 Strimzi Operator 기반의 Kafka 클러스터를 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Kubernetes 위에 구축하는 과정을 정리해 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는 네 번째 스택, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Trino&lt;/b&gt;를 Kubernetes 환경에 구축하는 과정을 정리해보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JHsv0/dJMcaci0qH9/qWgME1AlQ7EukK2zbLy8D1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JHsv0/dJMcaci0qH9/qWgME1AlQ7EukK2zbLy8D1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JHsv0/dJMcaci0qH9/qWgME1AlQ7EukK2zbLy8D1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJHsv0%2FdJMcaci0qH9%2FqWgME1AlQ7EukK2zbLy8D1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;600&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Kafka에서 수집되어 Spark Streaming으로 처리된 데이터는 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Delta Lake 포맷으로 Ceph S3에 쌓이게 됩니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 데이터를 SQL로 조회하려면 쿼리 엔진이 필요한데, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 역할을 담당하는 것이 바로 Trino입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Trino를 Kubernetes 위에서 운영하다 보면 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;단순히 쿼리 엔진 하나를 띄우는 것에서 끝나는 게 아니라,&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Hive Metastore가 Delta Lake 테이블의 메타데이터를 관리하고&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;PostgreSQL이 Hive Metastore의 백엔드 DB 역할을 하고&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Trino가 이 메타데이터를 참조해서 Ceph S3의 실제 데이터를 SQL로 조회하는&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3계층 구조가 유기적으로 연결되어야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Trino + Hive Metastore + PostgreSQL 3계층 아키텍처가 어떻게 동작하는지&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;실제로 어떤 순서로 설치하면 되는지&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Delta Lake 커넥터와 Hive 커넥터를 어떻게 구성하는지&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Delta Lake 테이블을 등록하고 SQL로 조회하는 방법&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;까지 순서대로 확인해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;1. Trino 아키텍처 개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;본격적인 설치에 앞서, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 구성의 전체 아키텍처를 먼저 이해하는 것이 중요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Ceph S3 (Delta Lake 데이터)
        &amp;uarr; 실제 데이터 읽기
      Trino 468
        &amp;uarr; 메타데이터 조회 (Thrift 프로토콜)
  Hive Metastore 3.1.3
        &amp;uarr; 메타데이터 저장
    PostgreSQL 13
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;각 컴포넌트의 역할은 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;컴포넌트&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Trino&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;SQL 쿼리 실행 엔진 &amp;mdash; Ceph S3의 실제 데이터를 읽고 결과 반환&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Hive Metastore (HMS)&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;테이블 스키마, 위치, 파티션 정보 등 메타데이터 관리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;PostgreSQ&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;L&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;HMS의 메타데이터를 영구 저장하는 백엔드 DB&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Trino&lt;/b&gt;는 데이터를 저장하지 않고, &lt;u&gt;Hive Metastore를 통해 메타데이터를 참조&lt;/u&gt;한 뒤 &lt;u&gt;Ceph S3에서 직접 데이터를 읽어 쿼리 결과를 반환&lt;/u&gt;한다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;2. 설치 순서&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 구축에서 사용하는 매니페스트 디렉토리 구조는 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;manifests/
├── ms/       # PostgreSQL (Hive Metastore 백엔드 DB)
│   ├── 00_ms_pvc.yaml
│   ├── 10_ms_deployment.yaml
│   └── 20_ms_service.yaml
│
├── hms/      # Hive Metastore
│   ├── 10_hms_configmap.yaml
│   ├── 20_hms_deployment.yaml
│   └── 30_hms_service.yaml
│
└── trino/    # Trino
    ├── 10_trino_namespace.yaml
    ├── 20_trino_configmap.yaml
    ├── 30_trino_deployment.yaml
    ├── 40_trino_service.yaml
    └── 50_trino_secret.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;  &lt;b&gt;참고&lt;/b&gt;: ms 디렉토리는 이름만 보면 MariaDB처럼 보이지만 실제로는 &lt;b&gt;PostgreSQL&lt;/b&gt;입니다. Hive Metastore의 백엔드 DB로 PostgreSQL을 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;설치 순서는 반드시 아래 순서를 지켜야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;trino(namespace) &amp;rarr; ms(PostgreSQL) &amp;rarr; ⏳ &amp;rarr; hms(Hive Metastore) &amp;rarr; ⏳ &amp;rarr; trino(Trino)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;반드시 지켜야 하는 대기 구간이 2곳&lt;/b&gt; 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;첫 번째&lt;/b&gt;로 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;PostgreSQL이 완전히 &lt;b&gt;Running&lt;/b&gt; 상태가 된 이후에 &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Hive Metastore를 &lt;b&gt;apply &lt;/b&gt;해야 합니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;HMS가 기동 시 &lt;u&gt;PostgreSQL에 스키마를 초기화하는 작업을 수행&lt;/u&gt;하기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;두 번째&lt;/b&gt;로 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Hive Metastore가 완전히 &lt;b&gt;Running&lt;/b&gt; 상태가 된 이후에 Trino를 &lt;b&gt;apply &lt;/b&gt;해야 합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Trino가 기동 시 &lt;u&gt;HMS에 연결을 시도&lt;/u&gt;하기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;3. Namespace 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;먼저 trino Namespace를 생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;# 10_trino_namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: trino
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;kubectl apply -f trino/10_trino_namespace.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;PostgreSQL, Hive Metastore, Trino&lt;/b&gt; 세 컴포넌트 모두 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;trino Namespace&lt;/b&gt; 하나에서 운영합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;4. PostgreSQL 설치 (Hive Metastore Back-End DB)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;PostgreSQL&lt;/b&gt;은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Hive Metastore&lt;/b&gt;가 &lt;u&gt;테이블 메타데이터를 영구 저장하는 백엔드 DB&lt;/u&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;PVC 생성&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;# ms/00_ms_pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: metastore-db-pvc
  namespace: trino
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: rook-ceph-block
  resources:
    requests:
      storage: 10Gi
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;rook-ceph-block StorageClass&lt;/b&gt;를 사용해서 &lt;b&gt;RWO 방식의 PVC를 생성&lt;/b&gt;합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;DB 데이터는 단일 노드에서만 읽고 쓰기&lt;/u&gt; 때문에 &lt;b&gt;RWO 모드가 적합&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Deployment&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# ms/10_ms_deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: metastore-db
  namespace: trino
spec:
  replicas: 1
  selector:
    matchLabels:
      app: metastore-db
  template:
    metadata:
      labels:
        app: metastore-db
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              preference:
                matchExpressions:
                  - key: eluna.role
                    operator: In
                    values:
                      - trino
      containers:
        - name: postgres
          image: harbor.local/library/metastore/postgres:13
          env:
            - name: POSTGRES_DB
              value: metastore
            - name: POSTGRES_USER
              value: hive
            - name: POSTGRES_PASSWORD
              value: YOUR_PASSWORD
          ports:
            - containerPort: 5432
          volumeMounts:
            - name: postgres-data-vol
              mountPath: /var/lib/postgresql/data
              subPath: pgdata
      volumes:
        - name: postgres-data-vol
          persistentVolumeClaim:
            claimName: metastore-db-pvc
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;⚠️ &lt;b&gt;운영 환경에서는 POSTGRES_PASSWORD를 Kubernetes Secret으로 분리해서 관리하는 것을 권장합니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;nodeAffinity&lt;/b&gt;에서 &lt;b&gt;eluna.role=trino&lt;/b&gt; 레이블이 있는 노드를 선호하도록 설정했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Service&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# ms/20_ms_service.yaml
apiVersion: v1
kind: Service
metadata:
  name: metastore-db
  namespace: trino
spec:
  type: NodePort
  ports:
    - port: 5432
      targetPort: 5432
      nodePort: 30999
  selector:
    app: metastore-db
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;kubectl apply -f ms/
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;PostgreSQL Pod가 &lt;b&gt;Running&lt;/b&gt; 상태인지 확인합니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;kubectl get pods -n trino | grep metastore-db
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;metastore-db-xxxxxxxxxx-xxxxx   1/1   Running   0   ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;PostgreSQL&lt;/b&gt;이 완전히 &lt;b&gt;Running&lt;/b&gt; 상태가 된 이후에만 &lt;u&gt;Hive Metastore가 정상적으로 스키마를 초기화할 수 있다는 점&lt;/u&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;5. Hive Metastore 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Hive Metastore&lt;/b&gt;는&lt;b&gt; Delta Lake 테이블의 스키마, 위치, 파티션 정보&lt;/b&gt;를 &lt;u&gt;PostgreSQL에 저장하고 Trino에 제공하는 메타데이터 서버&lt;/u&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;ConfigMap&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;vbnet&quot; data-ke-language=&quot;vbnet&quot;&gt;&lt;code&gt;# hms/10_hms_configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: hive-metastore-conf
  namespace: trino
data:
  hive-site.xml: |

        javax.jdo.option.ConnectionURL
        jdbc:postgresql://metastore-db:5432/metastore

        javax.jdo.option.ConnectionDriverName
        org.postgresql.Driver

        javax.jdo.option.ConnectionUserName
        hive

        javax.jdo.option.ConnectionPassword
        YOUR_PASSWORD
      
        fs.s3a.impl
        org.apache.hadoop.fs.s3a.S3AFileSystem

        fs.s3a.endpoint
        http://rook-ceph-rgw-ceph-store.rook-ceph.svc:80

        fs.s3a.access.key
        YOUR_ACCESS_KEY

        fs.s3a.secret.key
        YOUR_SECRET_KEY

        fs.s3a.path.style.access
        true

        hive.metastore.warehouse.dir
        /tmp/hive/warehouse

        hive.metastore.schema.verification
        false&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Deployment&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;# hms/20_hms_deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hive-metastore
  namespace: trino
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hive-metastore
  template:
    metadata:
      labels:
        app: hive-metastore
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              preference:
                matchExpressions:
                  - key: eluna.role
                    operator: In
                    values:
                      - trino
      containers:
        - name: hive-metastore
          image: harbor.local/library/hive:3.1.3
          securityContext:
            runAsUser: 0
          command: [&quot;/bin/sh&quot;, &quot;-c&quot;]
          args:
            - |
              set -e

              # S3 라이브러리 복사
              cp /opt/hadoop/share/hadoop/tools/lib/hadoop-aws-3.1.0.jar /opt/hive/lib/
              cp /opt/hadoop/share/hadoop/tools/lib/aws-java-sdk-bundle-1.11.271.jar /opt/hive/lib/

              # 스키마 초기화 (최초 기동 시에만 실행)
              /opt/hive/bin/schematool -dbType postgres -info || \
              /opt/hive/bin/schematool -dbType postgres -initSchema

              # Hive Metastore 기동
              exec /opt/hive/bin/hive --service metastore
          ports:
            - containerPort: 9083
          volumeMounts:
            - name: hive-config
              mountPath: /opt/hive/conf/hive-site.xml
              subPath: hive-site.xml
      volumes:
        - name: hive-config
          configMap:
            name: hive-metastore-conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;HMS&lt;/b&gt; 기동 시 &lt;b&gt;schematool&lt;/b&gt; 명령어로 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;PostgreSQL에 스키마가 있는지 확인하고, 없으면 자동으로 초기화합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;u&gt;&lt;b&gt;|| initSchema&lt;/b&gt; 패턴 덕분에 최초 기동과 재기동 모두 안전하게 처리&lt;/u&gt;됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Service&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# hms/30_hms_service.yaml
apiVersion: v1
kind: Service
metadata:
  name: hive-metastore
  namespace: trino
spec:
  ports:
    - port: 9083
      targetPort: 9083
  selector:
    app: hive-metastore
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;kubectl apply -f hms/
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;HMS&lt;/b&gt; Pod가 &lt;b&gt;Running&lt;/b&gt; 상태인지 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get pods -n trino | grep hive-metastore
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;hive-metastore-xxxxxxxxxx-xxxxx   1/1   Running   0   ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;HMS&lt;/b&gt; 로그에서 스키마 초기화가 완료됐는지도 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;kubectl logs -n trino deploy/hive-metastore | grep -i &quot;schema&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Initialization script completed
schemaTool completed
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;HMS&lt;/b&gt;가 &lt;b&gt;Running&lt;/b&gt; 상태이고 &lt;u&gt;스키마 초기화가 완료된 이후에만 Trino를 apply 해야 한다는 점&lt;/u&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;6. Trino 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Secret (패스워드 인증)&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Trino&lt;/b&gt;는 &lt;u&gt;파일 기반 패스워드 인증&lt;/u&gt;을 사용합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;bcrypt&lt;/b&gt;로 &lt;u&gt;해시된 패스워드를 &lt;b&gt;Secret&lt;/b&gt;으로 관리&lt;/u&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# trino/50_trino_secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: trino-password-file
  namespace: trino
type: Opaque
stringData:
  password.db: |
    admin:$2y$05$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;⚠️ &lt;b&gt;운영 환경에서는 반드시 패스워드를 변경해서 사용하세요.&lt;/b&gt; &lt;br /&gt;bcrypt 해시는 아래 명령어로 생성할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;htpasswd -nbBC 10 admin YOUR_PASSWORD
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;ConfigMap&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ConfigMap&lt;/b&gt;에는 Trino의 전체 설정이 담겨 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# trino/20_trino_configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: trino-config
  namespace: trino
data:
  # 코디네이터 설정
  config.properties: |
    coordinator=true
    node-scheduler.include-coordinator=true
    http-server.http.port=8080
    discovery.uri=http://localhost:8080

    # 패스워드 인증 설정
    http-server.authentication.type=PASSWORD
    http-server.authentication.allow-insecure-over-http=true
    internal-communication.shared-secret=YOUR_INTERNAL_SECRET

  # JVM 설정
  jvm.config: |
    -server
    -Xmx2G
    -XX:+UseG1GC
    -XX:G1HeapRegionSize=32M
    -XX:+UseGCOverheadLimit
    -XX:+ExplicitGCInvokesConcurrent
    -XX:+HeapDumpOnOutOfMemoryError
    -XX:+ExitOnOutOfMemoryError

  # 파일 기반 패스워드 인증
  password-authenticator.properties: |
    password-authenticator.name=file
    file.password-file=/etc/trino/auth/password.db

  # 노드 설정
  node.properties: |
    node.environment=production
    node.data-dir=/data/trino
    plugin.dir=/usr/lib/trino/plugin

  # tpch 커넥터 (테스트용)
  tpch.properties: |
    connector.name=tpch

  # Hive 커넥터
  hive.properties: |
    connector.name=hive
    hive.metastore.uri=thrift://hive-metastore:9083

    fs.native-s3.enabled=true
    s3.endpoint=http://rook-ceph-rgw-ceph-store.rook-ceph.svc:80
    s3.aws-access-key=YOUR_ACCESS_KEY
    s3.aws-secret-key=YOUR_SECRET_KEY
    s3.region=us-east-1
    s3.path-style-access=true

  # Delta Lake 커넥터
  delta.properties: |
    connector.name=delta_lake
    hive.metastore.uri=thrift://hive-metastore:9083

    delta.register-table-procedure.enabled=true

    fs.native-s3.enabled=true
    s3.endpoint=http://rook-ceph-rgw-ceph-store.rook-ceph.svc:80
    s3.aws-access-key=YOUR_ACCESS_KEY
    s3.aws-secret-key=YOUR_SECRET_KEY
    s3.region=us-east-1
    s3.path-style-access=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;주요 설정 포인트는 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;커넥터 2개 구성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;hive.properties    &amp;rarr; Hive 커넥터 (일반 Hive 테이블 조회)
delta.properties   &amp;rarr; Delta Lake 커넥터 (Delta Lake 테이블 조회)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;두 커넥터 모두 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;hive.metastore.uri=thrift://hive-metastore:9083&lt;/b&gt;으로 동일한 &lt;b&gt;Hive Metastore&lt;/b&gt;를 참조합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;delta.register-table-procedure.enabled=true&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;delta.register-table-procedure.enabled=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 설정이 핵심입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Spark Streaming&lt;/b&gt;이 &lt;b&gt;Ceph S3&lt;/b&gt;에 쌓은&lt;b&gt; Delta Lake&lt;/b&gt; 데이터를 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Trino&lt;/b&gt;에서&lt;u&gt; CALL 프로시저로 테이블로 등록&lt;/u&gt;할 수 있게 해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;s3.path-style-access=true&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Ceph RGW는 경로 방식 S3 URL을 사용하기 때문에 이 설정이 없으면 S3 연결이 실패합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Deployment&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# trino/30_trino_deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: trino-deployment
  namespace: trino
spec:
  replicas: 1
  selector:
    matchLabels:
      app: trino
  template:
    metadata:
      labels:
        app: trino
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              preference:
                matchExpressions:
                  - key: eluna.role
                    operator: In
                    values:
                      - trino
            - weight: 40
              preference:
                matchExpressions:
                  - key: role
                    operator: NotIn
                    values:
                      - kafka
      containers:
        - name: trino-container
          image: harbor.local/library/trino:468
          resources:
            requests:
              memory: &quot;4Gi&quot;
              cpu: &quot;1&quot;
          securityContext:
            runAsUser: 0
          ports:
            - containerPort: 8080
          volumeMounts:
            - name: config-vol
              mountPath: /etc/trino/config.properties
              subPath: config.properties
            - name: config-vol
              mountPath: /etc/trino/jvm.config
              subPath: jvm.config
            - name: config-vol
              mountPath: /etc/trino/node.properties
              subPath: node.properties
            - name: config-vol
              mountPath: /etc/trino/catalog/tpch.properties
              subPath: tpch.properties
            - name: config-vol
              mountPath: /etc/trino/catalog/hive.properties
              subPath: hive.properties
            - name: config-vol
              mountPath: /etc/trino/catalog/delta.properties
              subPath: delta.properties
            - name: config-vol
              mountPath: /etc/trino/password-authenticator.properties
              subPath: password-authenticator.properties
            - name: trino-password-secret
              mountPath: /etc/trino/auth/password.db
              subPath: password.db
              readOnly: true
            - name: data-vol
              mountPath: /data/trino
      volumes:
        - name: config-vol
          configMap:
            name: trino-config
        - name: trino-password-secret
          secret:
            secretName: trino-password-file
        - name: data-vol
          hostPath:
            path: /home/cys/trino
            type: DirectoryOrCreate
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ConfigMap&lt;/b&gt;의 &lt;u&gt;각 파일을 &lt;b&gt;subPath&lt;/b&gt;로 하나씩 정확한 경로에 마운트 합니다.&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;catalog&lt;/b&gt; 디렉토리 아래에 마운트 된&lt;b&gt;. properties &lt;/b&gt;파일이 &lt;b&gt;Trino&lt;/b&gt;에서 커넥터로 자동 인식됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;  &lt;b&gt;참고&lt;/b&gt;: data-vol은 hostPath(/home/cys/trino)를 사용하고 있습니다. &lt;br /&gt;운영 환경에서는 PVC로 교체하는 것을 권장합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;nodeAffinity 설정 포인트&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# eluna.role=trino 노드 선호 (weight 100)
# role=kafka 노드 기피 (weight 40, NotIn)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Kafka&lt;/b&gt; 노드에 &lt;b&gt;Trino&lt;/b&gt;가 올라가지 않도록 기피 설정을 함께 적용했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Service&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# trino/40_trino_service.yaml
apiVersion: v1
kind: Service
metadata:
  name: trino-service
  namespace: trino
spec:
  type: NodePort
  selector:
    app: trino
  ports:
    - port: 8080
      targetPort: 8080
      nodePort: 30140
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;kubectl apply -f trino/
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Trino&lt;/b&gt; Pod가 &lt;b&gt;Running&lt;/b&gt; 상태인지 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;kubectl get pods -n trino
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;NAME                                  READY   STATUS    RESTARTS   AGE
metastore-db-xxxxxxxxxx-xxxxx         1/1     Running   0          ...
hive-metastore-xxxxxxxxxx-xxxxx       1/1     Running   0          ...
trino-deployment-xxxxxxxxxx-xxxxx     1/1     Running   0          ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Trino UI&lt;/b&gt;는 아래 주소로 접근할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;http://&amp;lt;노드IP&amp;gt;:30140
ID: admin
PW: (50_trino_secret.yaml에서 설정한 패스워드)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;ConfigMap&lt;/b&gt;의 각 설정 파일이 &lt;b&gt;subPath&lt;/b&gt;로 &lt;b&gt;정확한 경로에 마운트 되어야&lt;/b&gt; &lt;b&gt;Trino&lt;/b&gt;가 &lt;u&gt;커넥터와 인증 설정을 정상적으로 읽어올 수 있다는 점&lt;/u&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;7. Delta Lake 테이블 등록 및 쿼리 테스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Trino&lt;/b&gt;가 &lt;b&gt;Running&lt;/b&gt; 상태가 되면 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Spark Streaming&lt;/b&gt;이 &lt;b&gt;Ceph S3&lt;/b&gt;에 쌓은 &lt;b&gt;Delta Lake&lt;/b&gt; 데이터를 테이블로 등록합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;Table Call&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;delta.register-table-procedure.enabled=true&lt;/b&gt; 설정 덕분에 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아래 &lt;b&gt;CALL&lt;/b&gt; 프로시저로 &lt;b&gt;S3&lt;/b&gt;의 &lt;b&gt;Delta Lake&lt;/b&gt; 데이터를 테이블로 등록할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;-- Delta Lake 테이블 등록
CALL delta.system.register_table(
  schema_name =&amp;gt; 'default',
  table_name  =&amp;gt; 'syslog',
  table_location =&amp;gt; 's3a://버킷명/syslog/'
);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Query Test&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;테이블이 정상적으로 등록되면 SQL로 조회할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- 전체 스키마 확인
SHOW SCHEMAS FROM delta;

-- 테이블 목록 확인
SHOW TABLES FROM delta.default;

-- 데이터 조회 (전체)
SELECT * FROM delta.default.syslog LIMIT 10;

-- 파티션 기준 조회 (dt=날짜 파티셔닝 적용 시)
SELECT * FROM delta.default.syslog
WHERE dt = '2026-06-04'
LIMIT 100;

-- 커넥터 목록 확인
SHOW CATALOGS;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;파티션 기준 조회 시 Trino가 해당 날짜 폴더만 읽기 때문에 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;전체 데이터를 스캔하지 않고 쿼리 성능이 크게 향상됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;register_table&lt;/b&gt; 프로시저로 테이블을 등록하면 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이후부터는 &lt;u&gt;일반 SQL로 Delta Lake 데이터를 조회할 수 있다는 점&lt;/u&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-left: 10px solid #4c8fff; border-bottom: 2px solid #4c8fff; border-top: 10px solid #fff; background: #fff; color: #000; font-weight: bold; margin: 0.5em 0em; padding: 0.2em 1em 0.4em 0.5em; border-radius: 0px 0px 0px 0px;&quot; data-ke-size=&quot;size23&quot;&gt;8. 전체 구성 검증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;모든 설치가 완료된 후 최종 상태를 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 전체 Pod 상태
kubectl get pods -n trino

# 서비스 확인
kubectl get svc -n trino

# PVC 확인
kubectl get pvc -n trino
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정상적인 경우 아래처럼 보입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;NAME                                  READY   STATUS    RESTARTS   AGE
metastore-db-xxxxxxxxxx-xxxxx         1/1     Running   0          ...
hive-metastore-xxxxxxxxxx-xxxxx       1/1     Running   0          ...
trino-deployment-xxxxxxxxxx-xxxxx     1/1     Running   0          ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;커넥터 연결도 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;# Trino CLI로 커넥터 확인
kubectl exec -n trino deploy/trino-deployment -- \
  trino --server http://localhost:8080 \
        --user admin \
        --execute &quot;SHOW CATALOGS;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;Catalog
--------
delta
hive
tpch
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 여기서 확인할 수 있는 핵심은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt; delta / hive / tpch&lt;/b&gt; 세 가지 카탈로그가 모두 보이면 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Trino + Hive Metastore + PostgreSQL 전체 연동이 완료&lt;/b&gt;된 것이라는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;  마무리&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 글에서는 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Kubernetes&lt;/b&gt; 위에서 &lt;b&gt;Trino + Hive Metastore + PostgreSQL&lt;/b&gt; 3계층 구조로 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Delta Lake SQL&lt;/b&gt; 조회 환경을 구축하는 과정을 정리해 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정리하면,&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;PostgreSQL&lt;/b&gt; &amp;rarr; Hive Metastore의 메타데이터 영구 저장&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Hive Metastore&lt;/b&gt; &amp;rarr; Delta Lake 테이블 스키마 및 위치 관리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Trino&lt;/b&gt; &amp;rarr; HMS 참조 + Ceph S3 직접 접근으로 SQL 쿼리 실행&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;라고 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;특히 이번 구축에서 주의해야 할 포인트는&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;PostgreSQL Running 확인 후 &amp;rarr; Hive Metastore apply&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Hive Metastore 스키마 초기화 완료 후 &amp;rarr; Trino apply&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;delta.register-table-procedure.enabled=true 설정 필수&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;s3.path-style-access=true 설정 필수 (Ceph RGW 연동 시)&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;ConfigMap 각 파일을 subPath로 정확한 경로에 마운트&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 5가지였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다음 편에서는 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번에 구축한 Trino 위에서 &lt;b&gt;Airflow&lt;/b&gt;로 데이터 파이프라인을 스케줄링하는 방법을 다뤄보겠습니다.  &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;궁금한 점이나 추가로 다뤄줬으면 하는 내용이 있다면 언제든지 편하게 말씀해 주세요!  &lt;/span&gt;&lt;/p&gt;</description>
      <category>[ Infra ]/Data Platform</category>
      <category>Data Platform</category>
      <category>Infra</category>
      <category>Kubernetes</category>
      <author>환이s</author>
      <guid isPermaLink="true">https://drg2524.tistory.com/244</guid>
      <comments>https://drg2524.tistory.com/244#entry244comment</comments>
      <pubDate>Fri, 5 Jun 2026 12:12:00 +0900</pubDate>
    </item>
  </channel>
</rss>