<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>웅대 개발 블로그</title>
    <link>https://growth-coder.tistory.com/</link>
    <description>알고리즘과 백엔드를 중심으로 열심히 공부 중입니다!
같이 소통하며 공부해요!</description>
    <language>ko</language>
    <pubDate>Mon, 13 Apr 2026 15:40:26 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>웅대</managingEditor>
    <image>
      <title>웅대 개발 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/5782213/attach/7a2fd708c2e94cc0b3108a0adbb2cf6e</url>
      <link>https://growth-coder.tistory.com</link>
    </image>
    <item>
      <title>[Java] JDWP 프로토콜로 원격 디버깅 수행</title>
      <link>https://growth-coder.tistory.com/380</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&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;span&gt;예를 들어 서비스가 여러 의존성을 가진 경우 로컬 환경 구축하기 보다 개발 서버에 배포되어 있는 서버를 &lt;/span&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&gt;이번 포스팅에서는 JDWP라는 프로토콜을 통해 원격 디버깅을 수행하는 방법을 다뤄보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;JDWP (Java&amp;nbsp;Debug&amp;nbsp;Wire&amp;nbsp;Protocol)란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDWP는 JPDA라는 Java Platform Debug Architecture 라고 불리는 글로벌 java 디버깅 시스템의 구성 요소 중 하나입니다.&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;JDWP는 디버거 프로세스와 JVMDI/JVMTI 사이 정의된 통신 프로토콜입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1253&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs8UU9/dJMcadgmFjD/FKJ1MEGklk1OlHnwW6UyV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs8UU9/dJMcadgmFjD/FKJ1MEGklk1OlHnwW6UyV0/img.png&quot; data-alt=&quot;Java Debug Wire Protocol&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs8UU9/dJMcadgmFjD/FKJ1MEGklk1OlHnwW6UyV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs8UU9%2FdJMcadgmFjD%2FFKJ1MEGklk1OlHnwW6UyV0%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;1253&quot; height=&quot;686&quot; data-origin-width=&quot;1253&quot; data-origin-height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Java Debug Wire Protocol&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDWP를 사용하면 로컬 IDE와 원격 서버의 JVM을 연결하여 실시간으로&amp;nbsp;브레이크포인트를&amp;nbsp;설정하고&amp;nbsp;디버깅할&amp;nbsp;수&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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;JDWP 설정 방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 간단한 REST API를 가지고 있는 java/spring 기반 springio/gs-spring-boot-docker image를 사용했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767791279369&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -p 8080:8080 -p 5005:5005 \
  -e JAVA_TOOL_OPTIONS=&quot;-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005&quot; \
  springio/gs-spring-boot-docker&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;JAVA_OPTS에 대한 설명은 다음과 같습니다.&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; agentlib:jdwp&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JVM에 JDWP 에이전트 활성화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; transport=&lt;/span&gt;&lt;span&gt;dt_socket&lt;/span&gt; &lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TCP/IP 소켓 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;server=y&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JVM이 서버 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;suspend=n&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디버거 연결 없이도 애플리케이션 바로 실행&amp;nbsp;&lt;/li&gt;
&lt;li&gt;y로 설정하면 디버거 연결까지 대기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;address=5005&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디버그 포트로 5005번 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 후 로그에서 5005 포트에서 디버거 연결을 대기하는 메시지를 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767794093769&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Picked up JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
Listening for transport dt_socket at address: 5005&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;서버 실행 방식과 무관하게 환경 변수에 JAVA_TOOL_OPTIONS를 설정하면 됩니다.&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;IDE 원격 디버깅 설정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디버깅 할 서버의 프로젝트 코드를 Clone 한 뒤 IntelliJ를 실행합니다.&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;저는 gs-spring-boot-docker 프로젝트를 실행했기 때문에 &lt;a href=&quot;https://github.com/spring-guides/gs-spring-boot-docker.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/spring-guides/gs-spring-boot-docker.git&lt;/a&gt;에서 프로젝트를 clone했습니다.&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;IntelliJ IDE 기준으로 원격 디버깅 설정은 다음과 같습니다.&lt;b&gt;&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;792&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G47jh/dJMcacogj3i/zyfCDkZbQW9wVfMjyXgRP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G47jh/dJMcacogj3i/zyfCDkZbQW9wVfMjyXgRP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G47jh/dJMcacogj3i/zyfCDkZbQW9wVfMjyXgRP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG47jh%2FdJMcacogj3i%2FzyfCDkZbQW9wVfMjyXgRP0%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;792&quot; height=&quot;686&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;686&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;b&gt;run -&amp;gt; edit configuration&lt;/b&gt;에서 새 항목 추가를 누르면&lt;b&gt; remote jvm debug&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;795&quot; data-origin-height=&quot;684&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQ1NU8/dJMcabplCVt/PncSbaKFs42lX6cDOlIsu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQ1NU8/dJMcabplCVt/PncSbaKFs42lX6cDOlIsu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ1NU8/dJMcabplCVt/PncSbaKFs42lX6cDOlIsu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ1NU8%2FdJMcabplCVt%2FPncSbaKFs42lX6cDOlIsu1%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;795&quot; height=&quot;684&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;684&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 로컬에 서버를 띄웠기 때문에 호스트를 localhost로 입력했지만 실제로는 운영 중인 개발 서버 주소를 입력하면 됩니다.&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;426&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BXU4W/dJMcafrK6My/WDwMK6ZkVtWudkXk9vggQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BXU4W/dJMcafrK6My/WDwMK6ZkVtWudkXk9vggQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BXU4W/dJMcafrK6My/WDwMK6ZkVtWudkXk9vggQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBXU4W%2FdJMcafrK6My%2FWDwMK6ZkVtWudkXk9vggQk%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;426&quot; height=&quot;152&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;152&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;357&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZkoSV/dJMcagxqlIn/w2vyP1pJJbRB6BIwfjdLQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZkoSV/dJMcagxqlIn/w2vyP1pJJbRB6BIwfjdLQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZkoSV/dJMcagxqlIn/w2vyP1pJJbRB6BIwfjdLQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZkoSV%2FdJMcagxqlIn%2Fw2vyP1pJJbRB6BIwfjdLQ0%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;436&quot; height=&quot;181&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;357&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;타깃 VM에 연결되었다는 로그가 나오면 연결 성공입니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1855&quot; data-origin-height=&quot;1491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l5NFE/dJMcajt6lyc/CZy0Mjxmkbxodpeu18ovo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l5NFE/dJMcajt6lyc/CZy0Mjxmkbxodpeu18ovo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l5NFE/dJMcajt6lyc/CZy0Mjxmkbxodpeu18ovo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl5NFE%2FdJMcajt6lyc%2FCZy0Mjxmkbxodpeu18ovo1%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;1855&quot; height=&quot;1491&quot; data-origin-width=&quot;1855&quot; data-origin-height=&quot;1491&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;&quot;/&quot; API에서 break point를 걸고 localhost:8080으로 GET 요청을 날려보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1855&quot; data-origin-height=&quot;1491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvCadN/dJMcahiN1aJ/gmlBCNIkTvbjAx8yKbOys1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvCadN/dJMcahiN1aJ/gmlBCNIkTvbjAx8yKbOys1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvCadN/dJMcahiN1aJ/gmlBCNIkTvbjAx8yKbOys1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdvCadN%2FdJMcahiN1aJ%2FgmlBCNIkTvbjAx8yKbOys1%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;1855&quot; height=&quot;1491&quot; data-origin-width=&quot;1855&quot; data-origin-height=&quot;1491&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;요청한 API가 break point에 걸려 응답이 오지 않으며 로컬 개발 환경에서 원격 디버깅을 수행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;주의 사항 1 - 개발 환경에서만 사용&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 주의 사항으로는 JDWP로 원격 디버깅을 수행할 때는 원격 서버의 환경이 개발 환경이어야 합니다.&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;break point를 건 순간 해당 API의 일반 사용자 요청도 모두 break point에 걸리기 때문입니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주의 사항 2 - 원격 서버 프로젝트 코드와 로컬 프로젝트 코드 일치&lt;/h2&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;JDWP break point 같은 경우 특정 파일의 몇 번째 줄에 break point를 거는 방식으로 동작합니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1248&quot; data-origin-height=&quot;584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qOJJ3/dJMcahC53ht/jPc1VZH9yYUOGMfmwjLwI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qOJJ3/dJMcahC53ht/jPc1VZH9yYUOGMfmwjLwI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qOJJ3/dJMcahC53ht/jPc1VZH9yYUOGMfmwjLwI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqOJJ3%2FdJMcahC53ht%2FjPc1VZH9yYUOGMfmwjLwI1%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;1248&quot; height=&quot;584&quot; data-origin-width=&quot;1248&quot; data-origin-height=&quot;584&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;여기서 14번째 &lt;b&gt;return &quot;Hello Docker World&quot;&lt;/b&gt; 코드에서 break point를 걸면 당연히 &quot;/&quot; 경로로 API 요청이 왔을 때 응답을 주기 전에 break point에 걸리게 됩니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btMwWk/dJMcabCSMRW/Px53an8vvQfdNckMgf68s1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btMwWk/dJMcabCSMRW/Px53an8vvQfdNckMgf68s1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btMwWk/dJMcabCSMRW/Px53an8vvQfdNckMgf68s1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtMwWk%2FdJMcabCSMRW%2FPx53an8vvQfdNckMgf68s1%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;1242&quot; height=&quot;728&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;728&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;위 코드에서 우리가 원하는 상황은 &quot;/test&quot;라는 API의 응답을 보내기 전에 break point를 거는 상황입니다.&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;하지만 원격 서버 코드 기준으로 14번째 줄은 &quot;/&quot; API의 &lt;b&gt;return &quot;Hello Docker World&quot;&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;우리는 &quot;/test&quot; API에서 break point가 걸리기를 원했지만 실제로는 &quot;/&quot; API에 break point가 걸리게 됩니다.&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;물론 당연하게도 원격 서버에는 &quot;/test&quot; API가 존재하지 않기 때문에 &quot;/test&quot; API를 보내면 404 에러가 발생합니다.&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;그래서 JDWP로 원격 디버깅을 수행할 때는 로컬 프로젝트 코드와 원격 프로젝트 코드의 버전이 동일해야 합니다.&lt;/p&gt;</description>
      <category>intelliJ 원격 디버깅</category>
      <category>java 원격 디버깅</category>
      <category>JDWP</category>
      <category>원격 디버깅</category>
      <author>웅대</author>
      <guid isPermaLink="true">https://growth-coder.tistory.com/380</guid>
      <comments>https://growth-coder.tistory.com/380#entry380comment</comments>
      <pubDate>Wed, 7 Jan 2026 23:21:09 +0900</pubDate>
    </item>
    <item>
      <title>Grafana Alerting 개념 정리 (Grafana Alerting, Contact points, Notification Polices, Gruping)</title>
      <link>https://growth-coder.tistory.com/379</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 Grafana Alerting의 Alert rules, Contact points, Notification policies에 대해 알아보고 간단한 모니터링 알림 시스템을 구축해보려고 합니다.&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://growth-coder.tistory.com/377&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 포스팅&lt;/a&gt;에서 kube-prometheus-stack을 활용한 metric 수집 및 알림 시스템을 구축했기 때문에 kube-prometheus-stack의 values.yaml 파일을 override해서 알림 모니터링 시스템을 구현해보겠습니다.&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;p99 rate를 측정하여 1분 이상일 때 알림을 보내주는 모니터링 시스템을 구축해보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Grafana Alerting&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana Alerting은 통합 알림 시스템으로 다양한 데이터 소스 기반의 알림을 중앙에서 관리할 수 있게&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;1877&quot; data-origin-height=&quot;1066&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwd2sw/dJMcadAp1os/S70DMgnlaFu2lwjchwTPu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwd2sw/dJMcadAp1os/S70DMgnlaFu2lwjchwTPu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwd2sw/dJMcadAp1os/S70DMgnlaFu2lwjchwTPu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcwd2sw%2FdJMcadAp1os%2FS70DMgnlaFu2lwjchwTPu0%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;1877&quot; height=&quot;1066&quot; data-origin-width=&quot;1877&quot; data-origin-height=&quot;1066&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;Grafana Alerting의 핵심 구성 요소로 Alert rules, Contact points, Notification policies가 존재합니다.&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;Contact points:&lt;/b&gt; 알림 전송 방식 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Alert rules:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;알림이 발생하기 전에 충족되어야 하는 조건&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Notification policy:&lt;/b&gt; Contact points 라우팅 설정&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Contact points&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1870&quot; data-origin-height=&quot;485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdWnYb/dJMcai2NRhV/5d3WyX5QN8U9kayDVKXIR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdWnYb/dJMcai2NRhV/5d3WyX5QN8U9kayDVKXIR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdWnYb/dJMcai2NRhV/5d3WyX5QN8U9kayDVKXIR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdWnYb%2FdJMcai2NRhV%2F5d3WyX5QN8U9kayDVKXIR1%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;1870&quot; height=&quot;485&quot; data-origin-width=&quot;1870&quot; data-origin-height=&quot;485&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;알림을 전송할 방식으로 다양한 방식을 제공하고 있습니다. 저는 slack API와 호환이 되는 mattermost를 사용 중이라 slack을 선택하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1845&quot; data-origin-height=&quot;1020&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUuhwy/dJMcahJEqhi/TZe3Y2MKdd92f3C13EhtE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUuhwy/dJMcahJEqhi/TZe3Y2MKdd92f3C13EhtE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUuhwy/dJMcahJEqhi/TZe3Y2MKdd92f3C13EhtE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUuhwy%2FdJMcahJEqhi%2FTZe3Y2MKdd92f3C13EhtE1%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;1845&quot; height=&quot;1020&quot; data-origin-width=&quot;1845&quot; data-origin-height=&quot;1020&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;mattermost에서 incoming webhook을 설정하고 URL을 contact point로 등록했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1237&quot; data-origin-height=&quot;867&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s37yt/dJMcagYgsUx/3kZ0qPLKA1AkG1zlhDBwhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s37yt/dJMcagYgsUx/3kZ0qPLKA1AkG1zlhDBwhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s37yt/dJMcagYgsUx/3kZ0qPLKA1AkG1zlhDBwhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs37yt%2FdJMcagYgsUx%2F3kZ0qPLKA1AkG1zlhDBwhK%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;1237&quot; height=&quot;867&quot; data-origin-width=&quot;1237&quot; data-origin-height=&quot;867&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;Optional Slack settings에서 title과 body를 설정할 수도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;891&quot; data-origin-height=&quot;1272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6ypvU/dJMb99SnC42/dbOAmekkKGkzFycwG8VPK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6ypvU/dJMb99SnC42/dbOAmekkKGkzFycwG8VPK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6ypvU/dJMb99SnC42/dbOAmekkKGkzFycwG8VPK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6ypvU%2FdJMb99SnC42%2FdbOAmekkKGkzFycwG8VPK1%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;452&quot; height=&quot;645&quot; data-origin-width=&quot;891&quot; data-origin-height=&quot;1272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Alert rules&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;alert rule은 다음과 같은 설정을 해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;alert rule name&lt;/li&gt;
&lt;li&gt;query and alert&lt;/li&gt;
&lt;li&gt;evaluation behavior&lt;/li&gt;
&lt;li&gt;label and notifications&lt;/li&gt;
&lt;li&gt;annotations&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;alert rule name&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;alert rule을 식별하기 위한 이름입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;197&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2pnEB/dJMcabvS79L/BcBgEYdyi1eNHHkwF8Kok1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2pnEB/dJMcabvS79L/BcBgEYdyi1eNHHkwF8Kok1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2pnEB/dJMcabvS79L/BcBgEYdyi1eNHHkwF8Kok1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2pnEB%2FdJMcabvS79L%2FBcBgEYdyi1eNHHkwF8Kok1%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;506&quot; height=&quot;197&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;197&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;query&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행할 쿼리입니다. 저는 API의 p99를 측정하는 쿼리를 작성했습니다.&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;모든 pod의 요청 횟수를 합산하여 histogram을 만들고 service를 기준으로 p99 latency를 구하는 쿼리입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1765&quot; data-origin-height=&quot;805&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byD1eU/dJMcadAp29G/58JH6lSYZ5Q2UTJvCa6QyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byD1eU/dJMcadAp29G/58JH6lSYZ5Q2UTJvCa6QyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byD1eU/dJMcadAp29G/58JH6lSYZ5Q2UTJvCa6QyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyD1eU%2FdJMcadAp29G%2F58JH6lSYZ5Q2UTJvCa6QyK%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;1765&quot; height=&quot;805&quot; data-origin-width=&quot;1765&quot; data-origin-height=&quot;805&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;expressions&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 쿼리의 결과를 가공하는 reduce와 임계 값을 설정할 수 있는 Threshold입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1770&quot; data-origin-height=&quot;817&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkLXLE/dJMcafkKMg8/hH6KTSI1TLVvj4WwjyqKg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkLXLE/dJMcafkKMg8/hH6KTSI1TLVvj4WwjyqKg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkLXLE/dJMcafkKMg8/hH6KTSI1TLVvj4WwjyqKg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkLXLE%2FdJMcafkKMg8%2FhH6KTSI1TLVvj4WwjyqKg1%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;1770&quot; height=&quot;817&quot; data-origin-width=&quot;1770&quot; data-origin-height=&quot;817&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;지금 reduce를 보면 reduce operation이 필요하지 않다는 warning이 출력되고 있습니다.&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;reduce는 &lt;b&gt;시계열 데이터를 단일 값으로 압축하는 역할&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;reduce는 사용하지 않고 p99 query의 결과를 사용해서 조건을 설정해보겠습니다.&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;577&quot; data-origin-height=&quot;441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HJaDT/dJMcagcTX4x/mPD5XgN0DkjUHAXidK95lK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HJaDT/dJMcagcTX4x/mPD5XgN0DkjUHAXidK95lK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HJaDT/dJMcagcTX4x/mPD5XgN0DkjUHAXidK95lK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHJaDT%2FdJMcagcTX4x%2FmPD5XgN0DkjUHAXidK95lK%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;404&quot; height=&quot;309&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;input을 reduce의 결과 값(B)가 아닌 query의 결과 값(A)을 직접 사용했고 query의 결과 값이 60보다 클 때 알림을 전송하도록 설정했습니다.&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;1분 이상이 걸린 long-request API에 대해서만 조건을 충족(Firing)하고 있습니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;evluation behavior&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;evaluation behavior는 위에서 정의한 알림을 발송하기 위해 알림 조건을 만족하는 지속 시간을 설정할 수 있습니다.&amp;nbsp;&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;evaluation interval:&lt;/b&gt; query 실행 주기&lt;/li&gt;
&lt;li&gt;&lt;b&gt;pending period:&lt;/b&gt; 조건 만족 후 경고 상태로 전환되기 전까지 대기하는 시간&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;696&quot; data-origin-height=&quot;385&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGKQPY/dJMcabimh92/ytbHUhJ55ZoSliiyLUzfv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGKQPY/dJMcabimh92/ytbHUhJ55ZoSliiyLUzfv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGKQPY/dJMcabimh92/ytbHUhJ55ZoSliiyLUzfv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGKQPY%2FdJMcabimh92%2FytbHUhJ55ZoSliiyLUzfv0%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;486&quot; height=&quot;269&quot; data-origin-width=&quot;696&quot; data-origin-height=&quot;385&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;evaluation group을 생성하면 evaluation interval을 설정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;770&quot; data-origin-height=&quot;541&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/barnvC/dJMcaf57RcC/kpPNoU9wFum9HZmF0OYAI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/barnvC/dJMcaf57RcC/kpPNoU9wFum9HZmF0OYAI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/barnvC/dJMcaf57RcC/kpPNoU9wFum9HZmF0OYAI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbarnvC%2FdJMcaf57RcC%2FkpPNoU9wFum9HZmF0OYAI1%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;473&quot; height=&quot;332&quot; data-origin-width=&quot;770&quot; data-origin-height=&quot;541&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;저는 evaluation interval을 1분으로 설정했고 pending period를 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;제가 작성한&lt;b&gt; 쿼리를 1분 단위로 실행하고 3분 동안 조건을 계속 만족했을 때&lt;/b&gt; 알림을 전송합니다.&lt;/p&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;labels and notifications&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;label과 notification 설정입니다.&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;label은 alert rule을 구분하기 위한 설정 값이고 notification은 contact point를 선택하여 알림을 전송할 방법을 선택할 수 있습니다.&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;notification 설정은 두 가지 방법이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Select contact point: 직접 contact point를 정의&lt;/li&gt;
&lt;li&gt;Use notification policy: network policy를 통해 라우팅&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 직접 contact point를 설정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1175&quot; data-origin-height=&quot;522&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beYXwj/dJMcahpltWf/moIUEVVWTPouATs3kcwFxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beYXwj/dJMcahpltWf/moIUEVVWTPouATs3kcwFxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beYXwj/dJMcahpltWf/moIUEVVWTPouATs3kcwFxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeYXwj%2FdJMcahpltWf%2FmoIUEVVWTPouATs3kcwFxk%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;1175&quot; height=&quot;522&quot; data-origin-width=&quot;1175&quot; data-origin-height=&quot;522&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;저는 network policy를 사용해보겠습니다. network policy 설정은 Alert rule을 먼저 생성하고 진행하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baYwdV/dJMcajgkVkT/4e2oPdluEYsSq33oJZDCg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baYwdV/dJMcajgkVkT/4e2oPdluEYsSq33oJZDCg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baYwdV/dJMcajgkVkT/4e2oPdluEYsSq33oJZDCg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaYwdV%2FdJMcajgkVkT%2F4e2oPdluEYsSq33oJZDCg0%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;1182&quot; height=&quot;258&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;annotations&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 annotation을 생성하고 alert rule을 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;775&quot; data-origin-height=&quot;721&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eaQesY/dJMcaaX3dIq/qTvlOXGMbC6Z4k8UGV79E1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eaQesY/dJMcaaX3dIq/qTvlOXGMbC6Z4k8UGV79E1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eaQesY/dJMcaaX3dIq/qTvlOXGMbC6Z4k8UGV79E1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeaQesY%2FdJMcaaX3dIq%2FqTvlOXGMbC6Z4k8UGV79E1%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;472&quot; height=&quot;439&quot; data-origin-width=&quot;775&quot; data-origin-height=&quot;721&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Notification plicies&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Notification polices에서는 알림을 routing하여 원하는 contact point에 전달할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;775&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RMQ8G/dJMcah3Wmdo/iJSiu0Gx5ORfnEWgBwVn2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RMQ8G/dJMcah3Wmdo/iJSiu0Gx5ORfnEWgBwVn2K/img.png&quot; data-alt=&quot;https://grafana.com/docs/grafana/latest/alerting/fundamentals/notifications/notification-policies/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RMQ8G/dJMcah3Wmdo/iJSiu0Gx5ORfnEWgBwVn2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRMQ8G%2FdJMcah3Wmdo%2FiJSiu0Gx5ORfnEWgBwVn2K%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;677&quot; height=&quot;410&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;775&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://grafana.com/docs/grafana/latest/alerting/fundamentals/notifications/notification-policies/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Alert rule을 생성할 때 label을 작성해두었는데 이 label을 기반으로 알림을 라우팅 해보겠습니다.&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;Default policy에 New child policy를 생성합니다. child policy는 기본적으로 parent policy 설정이 적용됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btBh5U/dJMcacIkWTD/SbnpOovAGqmKsfcwDwjLRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btBh5U/dJMcacIkWTD/SbnpOovAGqmKsfcwDwjLRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btBh5U/dJMcacIkWTD/SbnpOovAGqmKsfcwDwjLRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtBh5U%2FdJMcacIkWTD%2FSbnpOovAGqmKsfcwDwjLRK%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;1244&quot; height=&quot;374&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;374&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;1003&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPp3Ju/dJMcahXbquy/tfFeYYsU6fKKhQ4TnC6hKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPp3Ju/dJMcahXbquy/tfFeYYsU6fKKhQ4TnC6hKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPp3Ju/dJMcahXbquy/tfFeYYsU6fKKhQ4TnC6hKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPp3Ju%2FdJMcahXbquy%2FtfFeYYsU6fKKhQ4TnC6hKk%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;603&quot; height=&quot;833&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;1003&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Matching labels:&lt;/b&gt; notification policy를 적용할 alert rule의 label&lt;/li&gt;
&lt;li&gt;&lt;b&gt;contact point:&lt;/b&gt; 알림 전송 수단&lt;/li&gt;
&lt;li&gt;&lt;b&gt;group by:&lt;/b&gt; group을 나누는 기준&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;group에 대해서는 조금 더 자세하게 알아봅시다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Grouping (그룹화)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개념&lt;/b&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;&lt;/li&gt;
&lt;li&gt;기본 그룹: grafana_folder + alertname&lt;/li&gt;
&lt;li&gt;커스텀 그룹: namespace, service 등 추가 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;필요한 이유&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;알림 폭탄(Alert Storm) 방지&lt;/li&gt;
&lt;li&gt;알림 관리의 효율성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;namespace와 service를 기준으로 그룹을 설정했다고 합시다.&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;이 안의 /users, /orders, /products API에 대해 p99 latency가 1분이 넘어가게 되면 각각 따로 알림을 보내는 것이 아니라 하나의 알람으로 묶어서 보내게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764427078630&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;그룹 설정: namespace + service

[Group 1] namespace=projectA, service=api-server
  - API /users 응답시간 초과
  - API /orders 응답시간 초과
  - API /products 응답시간 초과
  &amp;rarr; 3개 알림이 하나로 묶여서 전송&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시간이 바로 &lt;b&gt;Group Wait&lt;/b&gt;입니다.&lt;/p&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;2. Group Wait&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개념&lt;/b&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;&lt;/li&gt;
&lt;li&gt;이 시간 동안 발생한 알림들을 모아서 한 번에 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;필요한 이유&lt;/b&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;/li&gt;
&lt;li&gt;초기 알림 폭탄 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;default 값&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;30s&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1764427285098&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Group Wait = 30s

00:00:00 - API /users 조건 만족 (firing)
00:00:15 - API /orders 조건 만족 (firing)
00:00:30 - 2개 알림 묶어서 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;/users와 /orders에 대해 알림 조건을 만족하여 2개의 알림을 하나로 보낸 상황입니다.&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;이 상태에서 30초가 지났는데 두 API가 여전히 알림 조건을 만족하면 어떻게 될까요?&lt;/p&gt;
&lt;pre id=&quot;code_1764427483152&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Group Wait = 30s

00:00:00 - API /users 조건 만족 (firing)
00:00:15 - API /orders 조건 만족 (firing)
00:00:30 - 2개 알림 묶어서 1번 전송

/users와 /orders API가 계속 조건을 만족 중 (firing)
00:01:00 - 이 상황에 대한 알림은 이미 보냈기 때문에 중복 알림 전송 X&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여기서 /products API 또한 알림 조건을 만족한다면 group에 변화가 생겼기 때문에 알림을 보내야 합니다.&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;그렇다면 이것도 역시 group wait 시간에 맞춰 30초가 지났을 때 알림을 전송하게 되는 걸까요?&lt;/p&gt;
&lt;pre id=&quot;code_1764427636884&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Group Wait = 30s

00:00:00 - API /users 조건 만족 (firing)
00:00:15 - API /orders 조건 만족 (firing)
00:00:30 - 2개 알림 묶어서 1번 전송

/users와 /orders API가 계속 조건을 만족 중 (firing)
00:01:00 - 이 상황에 대한 알림은 이미 보냈기 때문에 중복 알림 전송 X
00:01:05 - API /products 조건 만족 (firing)
00:01:30 - 3개 알림 묶어서 전송?&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;이 때는 group wait 시간을 따르지 않고 group interval 시간을 따르게 됩니다.&lt;/b&gt;&lt;/p&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;3. Group Interval&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개념&lt;/b&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;&lt;/li&gt;
&lt;li&gt;새로운 알림 추가 또는 기존 알림 해결 시 적용&lt;/li&gt;
&lt;li&gt;그룹의 전체 상태 스냅샷을 전송 (현재 Firing/Resolved 상태 모두 포함)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;필요한 이유&lt;/b&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;/li&gt;
&lt;li&gt;너무 잦은 업데이트 방지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;변화가 있을 때만&lt;/b&gt; 알림 (중요!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;default 값&lt;/b&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;5m&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1764428074674&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Group Wait = 30s
Group Interval = 5m

00:00:00 - API /users 조건 만족 (Firing) (Group Wait 타이머 시작)
00:00:15 - API /orders 조건 만족 (Firing)
00:00:30 - [첫 알림] /users, /orders 묶어서 전송 (Group Wait 적용) (Group Interval 타이머 시작)

/users와 /orders API가 계속 조건을 만족 중 (Firing 상태 유지)
00:03:00 - API /products 조건 만족 (Firing) &amp;larr; 알림을 보내야 함
00:03:30 - Group Wait 30초 대기? NO! 이미 그룹의 첫 알림을 보냈으므로 Group Interval 적용
00:05:30 - [업데이트 알림] /users, /orders, /products 묶어서 전송
		   (Group Interval 타이머 재시작)

이후 /users, /orders, /products 모두 계속 조건을 만족하며 변화 없음 (Firing 상태 유지)
00:10:30 - 변화가 없어서 알림 전송 X
		   (Group Interval 타이머 재시작)
           
group에서 처음으로 firing이 발생하면 group wait동안 기다렸다가 알림을 한 번에 전송합니다.
group wait이 지나 알림을 전송했다면 group interval 타이머가 실행됩니다.
새로운 알림이 추가되면 Group Interval만큼 대기 후 그룹 전체 상태를 전송합니다.&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;group wait과 group interval은 너무 많은 알림이 전송되는 것을 방지하기 위한 설정입니다.&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;&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;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 필요한 설정이 Repeat Interval입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. Repeat Interval&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개념&lt;/b&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;&lt;/li&gt;
&lt;li&gt;해결되지 않은 알림에 대한 리마인드&lt;/li&gt;
&lt;li&gt;Group Interval과의 차이: &lt;b&gt;변화 없이 방치될 때&lt;/b&gt; 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;필요한 이유&lt;/b&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;/li&gt;
&lt;li&gt;너무 잦은 반복 알림으로 인한 피로도 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;default 값&lt;/b&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;4h&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1764428833513&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Repeat Interval = 4h

00:00:30 - [첫 알림] /users, /orders (Firing) (Repeat Interval 타이머)
00:30:00 - 변화 없음 (알림 X)
01:00:00 - 변화 없음 (알림 X)
02:00:00 - 변화 없음 (알림 X)
04:00:30 - [반복 알림] &quot;아직도 안 고쳤어?&quot; 리마인드
08:00:30 - [반복 알림] &quot;아직도 안 고쳤어?&quot; 또 리마인드&lt;/code&gt;&lt;/pre&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;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시에서 보았던 &quot;변화&quot;는 조건을 만족했다가 (firing 상태 진입) 조건을 만족하지 않았을 때 (resolved 상태 진입)도 포함이 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764429039716&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Group Wait = 30s
Group Interval = 5m

00:00:00 - API /users 조건 만족 (Firing)
00:00:15 - API /orders 조건 만족 (Firing)
00:00:30 - [첫 알림] /users, /orders 묶어서 전송 (Group Wait 적용)
           &amp;larr; Group Interval 타이머 시작!

/users와 /orders API가 계속 조건을 만족 중 (Firing 상태 유지)

00:03:00 - API /products 조건 만족 (Firing) &amp;larr; 변화 발생!
00:04:00 - API /orders 해결됨 (Resolved) &amp;larr; 변화 발생!
00:05:30 - [정기 체크] 변화 있음! 
           &amp;rarr; /users (Firing), /orders (Resolved), /products (Firing) 전송
           &amp;larr; Group Interval 타이머 리셋!

00:08:00 - API /users 해결됨 (Resolved) &amp;larr; 변화 발생!
00:10:30 - [정기 체크] 변화 있음!
           &amp;rarr; /users (Resolved), /products (Firing) 전송
           &amp;larr; Group Interval 타이머 리셋!

00:15:30 - [정기 체크] 변화 없음 &amp;rarr; 알림 전송 안 함
00:20:30 - [정기 체크] 변화 없음 &amp;rarr; 알림 전송 안 함

&amp;rarr; Group Interval마다 주기적으로 그룹 상태를 확인하고,
  변화가 있을 때만 현재 그룹의 전체 상태(Firing/Resolved)를 전송합니다.&lt;/code&gt;&lt;/pre&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;Grouping 개념 최종 정리&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Grouping&amp;nbsp;(그룹화)&lt;/b&gt; &lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동일한 특성(namespace, service 등)을 가진 알림들을 하나로 묶어 알림 폭탄을 방지하고 관련 알림을 한눈에 파악하기 위한 메커니즘&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Group&amp;nbsp;Wait&lt;/b&gt; &lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그룹의 첫 알림을 보내기 전 대기 시간으로, 짧은 시간 내 발생한 여러 알림을 모아서 한 번에 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&amp;nbsp;Group&amp;nbsp;Interval&lt;/b&gt; &lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 알림 이후 주기적으로 그룹 상태를 확인하여 변화가 있을 때만 업데이트 알림을 전송하는 주기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Repeat&amp;nbsp;Interval&lt;/b&gt; &lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그룹 상태에 변화 없이 해결되지 않은 채 방치될 때 주기적으로 리마인드 알림을 보내는 주기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Alert rule, Contact point, Notification policy에 대해 알아보았고 Alert rule을 생성해보았습니다.&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;일부러 1분 이상 걸리는 API를 호출해보았고 1분 단위로 쿼리를 호출하며 3분 이상 p99 latency의 값이 1분 이상으로 지속될 때 다음과 같이 알림이 오는 것을 확인해보았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;747&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpDjil/dJMcadf7x2h/tqP7EeZZh4C0SF4KmoQZZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpDjil/dJMcadf7x2h/tqP7EeZZh4C0SF4KmoQZZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpDjil/dJMcadf7x2h/tqP7EeZZh4C0SF4KmoQZZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpDjil%2FdJMcadf7x2h%2FtqP7EeZZh4C0SF4KmoQZZ1%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;660&quot; height=&quot;353&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;747&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;Grafana의 Alert Rule을 활용하면 알림 폭탄을 줄이고 그룹 별로 알림을 효과적으로 관리할 수 있습니다.&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>contact point</category>
      <category>grafana alert rule</category>
      <category>grarfana group</category>
      <category>group interval</category>
      <category>group wait</category>
      <category>notification policy</category>
      <category>repeat interval</category>
      <author>웅대</author>
      <guid isPermaLink="true">https://growth-coder.tistory.com/379</guid>
      <comments>https://growth-coder.tistory.com/379#entry379comment</comments>
      <pubDate>Sun, 30 Nov 2025 00:52:00 +0900</pubDate>
    </item>
    <item>
      <title>Helm-Charts로 Self-Hosting 메시징 플랫폼 Mattermost 구축</title>
      <link>https://growth-coder.tistory.com/378</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mattermost는 Slack을 대체할 수 있는 오픈 소스로 자체 인프라에 직접 호스팅할 수 있는 메시징 플랫폼입니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 Helm 차트를 사용해 Kubernetes에 Mattermost를 배포하는 방법을 알아보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Mattermost 아키텍처&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mattermost의 애플리케이션 아키텍처는 다음과 같습니다.&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;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBcw5Z/dJMcajm5wTD/GoQoA0QQMxuTE8xgdeKwU0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBcw5Z/dJMcajm5wTD/GoQoA0QQMxuTE8xgdeKwU0/img.jpg&quot; data-alt=&quot;https://docs.mattermost.com/deployment-guide/reference-architecture/application-architecture.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBcw5Z/dJMcajm5wTD/GoQoA0QQMxuTE8xgdeKwU0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBcw5Z%2FdJMcajm5wTD%2FGoQoA0QQMxuTE8xgdeKwU0%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;800&quot; height=&quot;600&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://docs.mattermost.com/deployment-guide/reference-architecture/application-architecture.html&lt;/figcaption&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;b&gt;Mattermost Server, Database, File Storage&lt;/b&gt;에 대해 알아봅시다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Mattermost Server&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션의 핵심으로, Go 언어로 작성된 단일 바이너리입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RESTful API를 통해 클라이언트 요청을 처리합니다.&lt;/li&gt;
&lt;li&gt;WebSocket을 통해 실시간 메시지 및 알림을 전달합니다.&lt;/li&gt;
&lt;li&gt;인증, 알림, 데이터 관리 등의 서비스를 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Database&lt;/b&gt;&lt;/h4&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;PostgreSQL을 기본으로 사용합니다.&lt;/li&gt;
&lt;li&gt;고가용성을 위해 복제(Replication) 및 클러스터링 구성이 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;File Storage&lt;/b&gt;&lt;/h4&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;Local Storage, NAS, Amazon S3, MinIO 등을 지원합니다.&lt;/li&gt;
&lt;li&gt;프로덕션 환경에서는 S3 또는 MinIO 사용을 권장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금부터 Database는 PostgreSQL을, File Storage는 MinIO를 사용해서 k8s cluster 위에 Mattermost를 구축해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;k8s Operator pattern&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mattermost는 Operator 방식으로 배포할 것이기 때문에 k8s Operator pattern에 대해 알아보겠습니다.&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;operator는 CRD(사용자 정의 리소스)를 사용하여 애플리케이션을 관리하는 kubernetes extension입니다.&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;operator는 배포, 설정, 백업, 장애 대응과 같은 운영 지식을 코드에 내장하여 자동으로 처리할 수 있게 도와줍니다.&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;operator를 통해 CRD를 정의하고 CR을 생성해야 실제 서비스를 배포할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Mattermost-operator Helm-Charts&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mattermost를 k8s cluster 위에 구축하기 위해 Mattermost-operator라는 Helm-Charts를 사용해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://artifacthub.io/packages/helm/mattermost/mattermost-operator&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://artifacthub.io/packages/helm/mattermost/mattermost-operator&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1764242316704&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;mattermost-operator 1.0.4 &amp;middot; mattermost/mattermost&quot; data-og-description=&quot;A Helm chart for Mattermost Operator&quot; data-og-host=&quot;artifacthub.io&quot; data-og-source-url=&quot;https://artifacthub.io/packages/helm/mattermost/mattermost-operator&quot; data-og-url=&quot;https://artifacthub.io/packages/helm/mattermost/mattermost-operator&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b7iNio/hyZNFh1KVD/e8TmzZHJbc7Gc8DGeKm1Mk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/jGqiO/hyZOKosLQw/KkhRpSPli0NyMzIjwdqLh0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://artifacthub.io/packages/helm/mattermost/mattermost-operator&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://artifacthub.io/packages/helm/mattermost/mattermost-operator&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b7iNio/hyZNFh1KVD/e8TmzZHJbc7Gc8DGeKm1Mk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/jGqiO/hyZOKosLQw/KkhRpSPli0NyMzIjwdqLh0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;mattermost-operator 1.0.4 &amp;middot; mattermost/mattermost&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A Helm chart for Mattermost Operator&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;artifacthub.io&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;Mattermost helm-charts는 v1.0.0 이상부터 MinIO Operator와 MySQL Operator 옵션이 제거되었습니다.&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;현 시점(2025-11-27) 기준 공식 문서는 Database 배포를 위해 CloudNativePG Operator를, File Storage 배포를 위해 MinIO Operator를 추천하고 있습니다.&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;table style=&quot;border-collapse: collapse; width: 66.0456%; height: 190px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 5.69767%; height: 22px;&quot;&gt;순서&lt;/td&gt;
&lt;td style=&quot;width: 26.5116%; height: 22px;&quot;&gt;구성 요소&lt;/td&gt;
&lt;td style=&quot;width: 10.6576%; height: 22px;&quot;&gt;배포 방식&lt;/td&gt;
&lt;td style=&quot;width: 23.0634%; height: 22px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 5.69767%; height: 21px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 26.5116%; height: 21px;&quot;&gt;NGINX Ingress Controller&lt;/td&gt;
&lt;td style=&quot;width: 10.6576%; height: 21px;&quot;&gt;Helm&lt;/td&gt;
&lt;td style=&quot;width: 23.0634%; height: 21px;&quot;&gt;트래픽 라우팅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 5.69767%; height: 21px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 26.5116%; height: 21px;&quot;&gt;CloudNativePG Operator&lt;/td&gt;
&lt;td style=&quot;width: 10.6576%; height: 21px;&quot;&gt;Helm&lt;/td&gt;
&lt;td style=&quot;width: 23.0634%; height: 21px;&quot;&gt;PostgreSQL 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 5.69767%; height: 21px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 26.5116%; height: 21px;&quot;&gt;PostgreSQL Cluster&lt;/td&gt;
&lt;td style=&quot;width: 10.6576%; height: 21px;&quot;&gt;CRD&lt;/td&gt;
&lt;td style=&quot;width: 23.0634%; height: 21px;&quot;&gt;DB 인스턴스 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 5.69767%; height: 21px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 26.5116%; height: 21px;&quot;&gt;MinIO Operator&lt;/td&gt;
&lt;td style=&quot;width: 10.6576%; height: 21px;&quot;&gt;Helm&lt;/td&gt;
&lt;td style=&quot;width: 23.0634%; height: 21px;&quot;&gt;Object Storage 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 5.69767%; height: 21px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 26.5116%; height: 21px;&quot;&gt;MinIO Tenant&lt;/td&gt;
&lt;td style=&quot;width: 10.6576%; height: 21px;&quot;&gt;CRD&lt;/td&gt;
&lt;td style=&quot;width: 23.0634%; height: 21px;&quot;&gt;스토리지 인스턴스 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 5.69767%; height: 21px;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;width: 26.5116%; height: 21px;&quot;&gt;Mattermost Operator&lt;/td&gt;
&lt;td style=&quot;width: 10.6576%; height: 21px;&quot;&gt;Helm&lt;/td&gt;
&lt;td style=&quot;width: 23.0634%; height: 21px;&quot;&gt;Mattermost 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 5.69767%; height: 21px;&quot;&gt;7&lt;/td&gt;
&lt;td style=&quot;width: 26.5116%; height: 21px;&quot;&gt;Database/Filestore Secret&lt;/td&gt;
&lt;td style=&quot;width: 10.6576%; height: 21px;&quot;&gt;YAML&lt;/td&gt;
&lt;td style=&quot;width: 23.0634%; height: 21px;&quot;&gt;인증정보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 5.69767%; height: 21px;&quot;&gt;8&lt;/td&gt;
&lt;td style=&quot;width: 26.5116%; height: 21px;&quot;&gt;Mattermost&lt;/td&gt;
&lt;td style=&quot;width: 10.6576%; height: 21px;&quot;&gt;CRD&lt;/td&gt;
&lt;td style=&quot;width: 23.0634%; height: 21px;&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;배포 과정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Nginx Ingress Controller를 배포해보겠습니다.&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;보통 ingress controller는 여러 서비스들이 함께 사용하는 경우가 많아 Mattermost와는 분리해서 배포하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764246591504&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# ingress-nginx/ingress-nginx.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: ingress-nginx
  namespace: argocd
spec:
  project: default
  sources:
    - repoURL: https://kubernetes.github.io/ingress-nginx
      chart: ingress-nginx
      targetRevision: 4.14.0
      helm:
        valueFiles:
          - $values/ingress-nginx/ingress-nginx-values.yaml
    - repoURL: https://github.com/ezcolin2/argocd-test.git
      targetRevision: main
      ref: values
  destination:
    server: https://kubernetes.default.svc
    namespace: ingress-nginx
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1764246599299&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# ingress-nginx/values.yaml
controller:
  replicaCount: 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;이제 Mattermost 사용을 위한 여러 구성 요소를 배포해야 하기 때문에 ArgoCD의 App of Apps 패턴을 사용해보겠습니다.&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;ArgoCD를 사용하지 않으신다면 application 생성은 제외하고 배포하시면 됩니다.&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;pre id=&quot;code_1764247293092&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mattermost/
├── app-of-apps.yaml
├── apps
│   ├── cloudnativepg.yaml
│   ├── mattermost.yaml
│   ├── minio.yaml
│   └── resources.yaml
├── cloudnativepg
│   ├── Chart.yaml
│   ├── templates
│   │   └── postgres-cluster.yaml
│   └── values.yaml
├── mattermost
│   ├── Chart.yaml
│   ├── templates
│   │   ├── mattermost-db-secret.yaml
│   │   ├── mattermost-filestore-secret.yaml
│   │   └── mattermost.yaml
│   └── values.yaml
├── minio
│   ├── Chart.yaml
│   ├── templates
│   │   ├── minio-secret.yaml
│   │   └── minio-tenant.yaml
│   └── values.yaml
└── resources
    └── namespace.yaml&lt;/code&gt;&lt;/pre&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;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.0233%;&quot;&gt;디렉토리&lt;/td&gt;
&lt;td style=&quot;width: 76.9767%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.0233%;&quot;&gt;apps/&lt;/td&gt;
&lt;td style=&quot;width: 76.9767%;&quot;&gt;ArgoCD Application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.0233%;&quot;&gt;cloudnativepg-operator/&lt;/td&gt;
&lt;td style=&quot;width: 76.9767%;&quot;&gt;CloudNativePG Helm Chart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.0233%;&quot;&gt;minio-operator/&lt;/td&gt;
&lt;td style=&quot;width: 76.9767%;&quot;&gt;MinIO Operator Helm Chart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.0233%;&quot;&gt;mattermost-operator/&lt;/td&gt;
&lt;td style=&quot;width: 76.9767%;&quot;&gt;Mattermost Operator Helm Chart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.0233%;&quot;&gt;mattermost-operator/&lt;/td&gt;
&lt;td style=&quot;width: 76.9767%;&quot;&gt;CRD, Secret 등 직접 적용할 YAML&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;namespace부터 정의하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764245105900&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# resources/namespaces.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: ingress-nginx
---
apiVersion: v1
kind: Namespace
metadata:
  name: cnpg-system
---
apiVersion: v1
kind: Namespace
metadata:
  name: minio-operator
---
apiVersion: v1
kind: Namespace
metadata:
  name: mattermost-operator
---
apiVersion: v1
kind: Namespace
metadata:
  name: mattermost&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;이제 application 하나씩 YAML 파일을 작성하겠습니다.&lt;/p&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;CloudNativePG&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CloudNativePG operator 사용을 위한 helm charts입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764405145677&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# cloudnativepg/Chart.yaml
apiVersion: v2
name: cloudnativepg
description: CloudNativePG Operator with PostgreSQL Cluster
type: application
version: 0.1.0
appVersion: &quot;1.25.0&quot;

dependencies:
  - name: cloudnative-pg
    version: &quot;0.26.1&quot;
    repository: &quot;https://cloudnative-pg.github.io/charts&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;override 할 values.yaml 파일입니다. 테스트를 위해 replica를 1로 설정했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764405239527&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# cloudnativepg/values.yaml
cloudnative-pg:
  replicaCount: 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;CloudNativePG Operator를 통해 CRD를 생성했으면 Cluster CR을 통해 postgres를 배포합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764405307757&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# cloudnativevpg/templates/postgres-cluster.yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: mattermost-postgres
  namespace: mattermost
spec:
  instances: 1
  imageName: ghcr.io/cloudnative-pg/postgresql:16.4
  
  bootstrap:
    initdb:
      database: mattermost
      owner: mattermost
  
  postgresql:
    parameters:
      max_connections: &quot;200&quot;
      shared_buffers: &quot;256MB&quot;
  
  storage:
    size: 5Gi
  
  resources:
    requests:
      memory: &quot;256Mi&quot;
      cpu: &quot;100m&quot;
    limits:
      memory: &quot;512Mi&quot;
      cpu: &quot;500m&quot;
  
  monitoring:
    enablePodMonitor: false&lt;/code&gt;&lt;/pre&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;MinIO&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MinIO operator 사용을 위한 helm charts입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764405622196&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mattermost/minio/Chart.yaml
apiVersion: v2
name: minio
description: MinIO Operator with Tenant
type: application
version: 0.1.0
appVersion: &quot;7.1.1&quot;

dependencies:
  - name: operator
    version: &quot;7.1.1&quot;
    repository: &quot;https://operator.min.io/&quot;&lt;/code&gt;&lt;/pre&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;override 할 values.yaml 파일입니다. 테스트를 위해 replica를 1로 설정했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764405630876&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mattermost/minio/values.yaml
operator:
  replicaCount: 1&lt;/code&gt;&lt;/pre&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;다음은 admin 계정 정보 설정을 위한 secret입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764405725417&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# minio/templates/
apiVersion: v1
kind: Secret
metadata:
  name: minio-env-configuration
  namespace: mattermost
type: Opaque
stringData:
  config.env: |
    export MINIO_ROOT_USER=&quot;minio&quot;
    export MINIO_ROOT_PASSWORD=&quot;minio123&quot;
    export MINIO_BROWSER=&quot;on&quot;&lt;/code&gt;&lt;/pre&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;color: #333333; text-align: start;&quot;&gt;MinIO&lt;span&gt; &lt;/span&gt;&lt;/span&gt;Operator를 통해 CRD를 생성했으면 Taint CR을 통해 MinIO를 배포합니다. 위에서 작성한 secret을 연결해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764405640940&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# minio/templates/minio-tenant.yaml
apiVersion: minio.min.io/v2
kind: Tenant
metadata:
  name: minio
  namespace: mattermost
  labels:
    app: minio
spec:
  image: quay.io/minio/minio:RELEASE.2024-10-02T17-50-41Z
  imagePullPolicy: IfNotPresent
  
  pools:
    - name: pool-0
      servers: 1
      volumesPerServer: 1
      volumeClaimTemplate:
        metadata:
          name: data
        spec:
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 5Gi
      resources:
        requests:
          cpu: &quot;100m&quot;
          memory: &quot;256Mi&quot;
        limits:
          cpu: &quot;500m&quot;
          memory: &quot;512Mi&quot;
  
  # TLS 비활성화 (테스트용)
  requestAutoCert: false
  
  configuration:
    name: minio-env-configuration
  
  buckets:
    - name: mattermost
  
  exposeServices:
    minio: true
    console: true&lt;/code&gt;&lt;/pre&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;Mattermost&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mattermost operator 배포를 위한 helm charts입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764406110595&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mattermost/Chart.yaml
apiVersion: v2
name: mattermost
description: Mattermost Operator with Mattermost Instance
type: application
version: 0.1.0
appVersion: &quot;10.0.0&quot;

dependencies:
  - name: mattermost-operator
    version: &quot;1.0.4&quot;
    repository: &quot;https://helm.mattermost.com&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p 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;override 할 values.yaml 파일입니다. 테스트를 위해 replica를 1로 설정했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764406129377&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mattermost/Chart.yaml
mattermost-operator:
  replicaCount: 1
  image:
    repository: mattermost/mattermost-operator
    tag: v1.24.0
    pullPolicy: IfNotPresent&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;다음은 mattermost에 연결하기 위한 postegres와 minio의 설정 파일입니다.&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;먼저 postgres 연결을 위한 secret입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764406243921&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mattermost/templates/mattermost-db-secret.yaml
# 주의: 배포 후 CloudNativePG가 생성한 password로 업데이트 필요
# kubectl get secret mattermost-postgres-app -n mattermost -o jsonpath='{.data.password}' | base64 -d
apiVersion: v1
kind: Secret
metadata:
  name: mattermost-db-secret
  namespace: mattermost
type: Opaque
stringData:
  DB_CONNECTION_STRING: &quot;postgres://mattermost:&amp;lt;비밀번호&amp;gt;@mattermost-postgres-rw.mattermost.svc.cluster.local:5432/mattermost?sslmode=disable&amp;amp;connect_timeout=10&quot;
  DB_CONNECTION_CHECK_URL: &quot;postgres://mattermost:&amp;lt;비밀번호&amp;gt;@mattermost-postgres-rw.mattermost.svc.cluster.local:5432/mattermost?sslmode=disable&amp;amp;connect_timeout=10&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;cloud native pg는 비밀번호를 자동 생성하기 때문에 cloud natvie pg를 배포하고 생성된 password를 이 secret 파일에 입력해줘야 합니다.&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;minio 연결을 위한 secret입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764406293155&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mattermost/templates/mattermost-filestore-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mattermost-filestore-secret
  namespace: mattermost
type: Opaque
stringData:
  accesskey: bWluaW8=
  secretkey: bWluaW8xMjM=&lt;/code&gt;&lt;/pre&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;b&gt;위에서 minio를 배포할 때 작성한 계정 정보와 비밀번호를 사용할 수 있는데 string을 그냥 입력하면 안 되고 base 64 encoding한 값을 입력해야 합니다.&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;Mattermost operator를 통해 CRD를 생성했으면 Mattermost CR을 통해 Mattermost를 배포합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764406406285&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mattermost/templates/mattermost.yaml
apiVersion: installation.mattermost.com/v1beta1
kind: Mattermost
metadata:
  name: mattermost
  namespace: mattermost
spec:
  version: &quot;10.0.0&quot;
  size: &quot;100users&quot;
  replicas: 1
  
  ingress:
    enabled: false
    host: &quot;mattermost.local&quot;
    ingressClass: &quot;nginx&quot;
    annotations:
      nginx.ingress.kubernetes.io/proxy-body-size: &quot;50m&quot;
      nginx.ingress.kubernetes.io/proxy-buffer-size: &quot;16k&quot;
  
  database:
    external:
      secret: mattermost-db-secret
  
  fileStore:
    external:
      url: &quot;minio-hl.mattermost.svc.cluster.local:9000&quot;
      bucket: &quot;mattermost&quot;
      secret: mattermost-filestore-secret
  
  mattermostEnv:
    - name: MM_FILESETTINGS_AMAZONS3SSL
      value: &quot;false&quot;
    - name: MM_FILESETTINGS_AMAZONS3PATHSTYLE
      value: &quot;true&quot;
    - name: MM_LOGSETTINGS_CONSOLELEVEL
      value: &quot;DEBUG&quot;
    - name: MM_LOGSETTINGS_ENABLECONSOLE
      value: &quot;true&quot;&lt;/code&gt;&lt;/pre&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;namespace&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;namespace 파일입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764406464244&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# resources/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: mattermost
  labels:
    app.kubernetes.io/name: mattermost&lt;/code&gt;&lt;/pre&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;application&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 ArgoCD를 통해 배포할 예정이기 때문에 application을 정의하겠습니다. git repo url은 자신의 repo url을 입력하면 됩니다.&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;CloudNativePG, Mattermost, Minio, resources 순서입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764406642599&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# apps/cloudnativepg.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: mattermost-cloudnativepg
  namespace: argocd
  annotations:
    argocd.argoproj.io/sync-wave: &quot;0&quot;
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: &amp;lt;git repo url&amp;gt;
    targetRevision: main
    path: mattermost/cloudnativepg
  destination:
    server: https://kubernetes.default.svc
    namespace: cnpg-system
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764406648252&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# apps/mattermost.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: mattermost-app
  namespace: argocd
  annotations:
    argocd.argoproj.io/sync-wave: &quot;2&quot;
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: &amp;lt;git repo url&amp;gt;
    targetRevision: main
    path: mattermost/mattermost
  destination:
    server: https://kubernetes.default.svc
    namespace: mattermost-operator
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764406653109&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# apps/minio.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: mattermost-minio
  namespace: argocd
  annotations:
    argocd.argoproj.io/sync-wave: &quot;1&quot;
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: &amp;lt;git repo url&amp;gt;
    targetRevision: main
    path: mattermost/minio
  destination:
    server: https://kubernetes.default.svc
    namespace: minio-operator
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764406659546&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# apps/resources.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: mattermost-resources
  namespace: argocd
  annotations:
    argocd.argoproj.io/sync-wave: &quot;-1&quot;
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: &amp;lt;git repo url&amp;gt;
    targetRevision: main
    path: mattermost/resources
  destination:
    server: https://kubernetes.default.svc
    namespace: default
  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;마지막으로 app of apps 설정입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764406704284&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# app-of-apps.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: mattermost-app-of-apps
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  
  source:
    repoURL: &amp;lt;git repo url&amp;gt;
    targetRevision: main
    path: mattermost/apps
  
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  
  syncPolicy:
    automated:
      prune: true
      selfHeal: 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;위 파일들을 모두 git에 올리고 app-of-apps.yaml 파일만 k8s에 적용하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764406811930&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl apply -f app-of-apps.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;app-of-apps를 적용하면 아래와 같이 mattermost-app-of-apps 아래로 설정했던 application들을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1517&quot; data-origin-height=&quot;817&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DnONH/dJMcahJElfT/l3Mt09aImFEdKSU1mt0g50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DnONH/dJMcahJElfT/l3Mt09aImFEdKSU1mt0g50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DnONH/dJMcahJElfT/l3Mt09aImFEdKSU1mt0g50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDnONH%2FdJMcahJElfT%2Fl3Mt09aImFEdKSU1mt0g50%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;1517&quot; height=&quot;817&quot; data-origin-width=&quot;1517&quot; data-origin-height=&quot;817&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;그리고 이 application들은 applications에서 확인하실 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1492&quot; data-origin-height=&quot;1716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u2wbv/dJMcafdZdMX/MJKsgI9eET7uLr7K9stGKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u2wbv/dJMcafdZdMX/MJKsgI9eET7uLr7K9stGKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u2wbv/dJMcafdZdMX/MJKsgI9eET7uLr7K9stGKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu2wbv%2FdJMcafdZdMX%2FMJKsgI9eET7uLr7K9stGKK%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;1492&quot; height=&quot;1716&quot; data-origin-width=&quot;1492&quot; data-origin-height=&quot;1716&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <author>웅대</author>
      <guid isPermaLink="true">https://growth-coder.tistory.com/378</guid>
      <comments>https://growth-coder.tistory.com/378#entry378comment</comments>
      <pubDate>Sat, 29 Nov 2025 18:06:03 +0900</pubDate>
    </item>
    <item>
      <title>Kube-Prometheus-Stack과 Loki-Stack을 활용한 monitoring system 구축</title>
      <link>https://growth-coder.tistory.com/377</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 k8s에서 모니터링 시스템을 구축하기 위해 prometheus stack과 lok stack을 사용해보려고 합니다.&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;k8s cluster 내부 메트릭 수집&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스프링 부트 Pod 내부 에러 로그 수집&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본격적인 monitoring system 구축에 앞서 다음 개념에 대해 알아보고자 합니다.&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;Prometheus&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Grafana &lt;/b&gt;&lt;b&gt;Loki&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Promtail&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Prometheus란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prometheus는 SoundCloud에서 개발한 오픈소스 시스템 모니터링및 알림 툴킷입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;metric 이름과 key, value로 식별할 수 있는 시계열 데이터 저장&lt;/li&gt;
&lt;li&gt;PromQL이라는 쿼리 언어 사용&lt;/li&gt;
&lt;li&gt;분산 스토리지에 의존하지 않음&lt;/li&gt;
&lt;li&gt;HTTP pull model을 통해 시계열 데이터 수집&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 Prometheus의 아키텍처와 Prometheus와 함께 사용되는 서비스들에 대한 아키텍처입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpZLnd/dJMcahpiRUA/igD790las7KBGatqbZ6po1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpZLnd/dJMcahpiRUA/igD790las7KBGatqbZ6po1/img.png&quot; data-alt=&quot;https://prometheus.io/docs/introduction/overview/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpZLnd/dJMcahpiRUA/igD790las7KBGatqbZ6po1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpZLnd%2FdJMcahpiRUA%2FigD790las7KBGatqbZ6po1%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;727&quot; height=&quot;436&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://prometheus.io/docs/introduction/overview/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Prometheus Server&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Promethues의 핵심 구성 요소로 메트릭 수집, 저장 및 제공을 담당합니다.&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;데이터 분석을 위한 쿼리 언어인 PromQL을 제공합니다.&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;Prometheus는 자체적으로 분산 스토리지를 제공하지는 않고 단일 노드 로컬 디스크의 TSDB에 데이터를 짧은 기간 동안 저장합니다.&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;만약 데이터를 장기간 보관하고 싶다면 Thanos와 같은 확장 솔루션을 사용해야 합니다.&lt;/p&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;Prometheus Targets&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prometheus Target은 Prometheus가 모니터링하는 대상을 의미합니다.&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;보통 HTTP endpoint이며 이 endpoint를 사용하여 prometheus가 데이터를 수집합니다.&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;여기서 Exporter는 Nginx나 Apache와 같은 다양한 시스템에서 제공받은 metric을 Prometheus가 읽을 수 있는 형태로 변환해주는 역할을 담당합니다.&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;다양한 종류의 exporter가 존재하며 원한다면 커스텀 exporter를 만들 수도 있습니다.&lt;/p&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;Push Gateway&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prometheus는 일반적으로 모니터링 타겟이 되는 서비스의 HTTP 엔드포인트를 통해 데이터를 수집합니다.&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;그러나 Push Gateway를 사용하면 외부에서 데이터를 push하는 방식으로 데이터를 수집할 수 있습니다.&lt;/p&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;Service Discovery&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Service Discovery는 새로운 모니터링 대상을 자동으로 찾고 모니터링 할 수 있게 해주는 컴포넌트입니다.&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;k8s처럼 기존 Pod가 사라지고 새로운 Pod가 생기는 동적 환경에서 중요한 컴포넌트입니다.&lt;/p&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;Alert Manager&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Alert Manager는 Prometheus에서 전송된 알림을 email, slack과 같은 다양한 서비스 전송하는 역할을 담당합니다.&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;미리 지정해 둔 alert rule의 조건을 만족하면 Alert Manager를 통해 알림을 보낼 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Kube-Prometheus-Stack이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kube-Prometheus-Stack은 Prometheus 운영에 필요한 다양한 컴포넌트들을 k8s에 쉽게 배포할 수 있게 도와주는 Helm Charts 패키지입니다.&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;Kube-Prometheus-Stack을 사용하면 Prometheus를 kubernetes 환경에 최적화된 형태로 구축하고 운영할 수 있습니다.&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;Kube-Prometheus-Stack의 아키텍처는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1474&quot; data-origin-height=&quot;873&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bidNaM/dJMcacnZylC/8YUDXj7UPD9k3qqkkFIkQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bidNaM/dJMcacnZylC/8YUDXj7UPD9k3qqkkFIkQK/img.png&quot; data-alt=&quot;https://picluster.ricsanfre.com/docs/prometheus/#kube-prometheus-stack-installation&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bidNaM/dJMcacnZylC/8YUDXj7UPD9k3qqkkFIkQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbidNaM%2FdJMcacnZylC%2F8YUDXj7UPD9k3qqkkFIkQK%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;1474&quot; height=&quot;873&quot; data-origin-width=&quot;1474&quot; data-origin-height=&quot;873&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://picluster.ricsanfre.com/docs/prometheus/#kube-prometheus-stack-installation&lt;/figcaption&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kube-Prometheus-Stack 패키지는 다음과 같은 component들을 포함하고 있습니다.&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;Prometheus Operator&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HA Prometheus&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HA AlertManager&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;prometheus-node-exporter&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;kube-state-metrics&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Grafana&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Prometheus Operator는 &lt;b&gt;k8s CRD(사용자 정의 리소스)&lt;/b&gt;를 사용하여 Prometheus와 Alert Manager의 배포와 구성을 담당합니다.&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;Kube-Prometheus-Stack에서 사용하는 k8s CRD는 다음과 같은 종류가 있습니다.&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;Prometheus, AlertManager CRD:&lt;/b&gt; k8s 클러스터에서 실행될 Prometheus/AlertManager 설정을 선언적으로 정의&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ServiceMonitor, PodMonitor, Probe, ScrapeConfig CRD:&lt;/b&gt; prometheus의 Service Discovery 설정을 정의&lt;/li&gt;
&lt;li&gt;&lt;b&gt; PrometheusRules CRD:&lt;/b&gt; prometheus의 알림이나 기록 rule을 정의&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AlertManagerConfig CRD:&lt;/b&gt; AlertManager 설정을 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kube-prometheus-stack의 helm charts 구조는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763864165699&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;./kube-prometheus-stack/
├── Chart.lock
├── Chart.yaml
├── README.md
├── charts
│   ├── crds
│   ├── grafana
│   ├── kube-state-metrics
│   ├── prometheus-node-exporter
│   └── prometheus-windows-exporter
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── alertmanager
│   ├── exporters
│   ├── extra-objects.yaml
│   ├── grafana
│   ├── prometheus
│   ├── prometheus-operator
│   └── thanos-ruler
└── values.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Grafana Loki란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana는 Grafana Labs에서 개발한 시각화 시스템으로 수집한 다양한 데이터를 시각화하여 dashboard를 제공해주는 &amp;nbsp;오픈소스입니다.&lt;/p&gt;
&lt;p 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;Prometheus가 수집한 데이터를 Prometheus가 제공해주는 UI를 사용해도 되지만 Grafana와 같은 오픈 소스를 연동하여 사용할 수도 있습니다.&lt;/p&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;Grafans는 Prometheus의 PromQL이라는 쿼리 언어를 사용하여 데이터를 가져와 dashboard를 구성합니다.&lt;/p&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;Loki는 Grafana Labs에서 개발한 중앙 로그 수집 시스템으로 Grafana와 연동하여 로그를 시각화할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;715&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sChDK/dJMcaaRfjNx/gNFgsI8UnkDkFatOVWwmM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sChDK/dJMcaaRfjNx/gNFgsI8UnkDkFatOVWwmM0/img.png&quot; data-alt=&quot;https://grafana.com/oss/grafana/?plcmt=oss-nav&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sChDK/dJMcaaRfjNx/gNFgsI8UnkDkFatOVWwmM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsChDK%2FdJMcaaRfjNx%2FgNFgsI8UnkDkFatOVWwmM0%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;695&quot; height=&quot;388&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;715&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://grafana.com/oss/grafana/?plcmt=oss-nav&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Loki의 아키텍처는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfHyoK/dJMcaiaFJt4/p6YD75pkejYz687CsJ347k/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfHyoK/dJMcaiaFJt4/p6YD75pkejYz687CsJ347k/tfile.svg&quot; data-alt=&quot;https://grafana.com/docs/loki/latest/get-started/architecture/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfHyoK/dJMcaiaFJt4/p6YD75pkejYz687CsJ347k/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfHyoK%2FdJMcaiaFJt4%2Fp6YD75pkejYz687CsJ347k%2Ftfile.svg&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;480&quot; height=&quot;360&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://grafana.com/docs/loki/latest/get-started/architecture/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;storage&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Loki는 모든 데이터를 S3, GCS와 같은 단일 객체 스토리지 백엔드에 저장합니다.&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;데이터 형식에는 index와 chunk가 있습니다.&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;index: 로그를 검색하기 위한 색인으로 label의 집합으로 이루어져 있습니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;chunk: 특정 label 집합의 log entry들을 저장한 컨테이너입니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana Loki가 log 데이터를 처리하고 저장하는 과정은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEEBZv/dJMcajm3ReR/4tWQtDBGTndkQhOm4laGqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEEBZv/dJMcajm3ReR/4tWQtDBGTndkQhOm4laGqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEEBZv/dJMcajm3ReR/4tWQtDBGTndkQhOm4laGqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEEBZv%2FdJMcajm3ReR%2F4tWQtDBGTndkQhOm4laGqK%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;750&quot; height=&quot;464&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;464&quot;/&gt;&lt;/span&gt;&lt;/figure&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;로그 전체를 인덱싱하지 않고 메타데이터(label)만 인덱싱한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;log entry는 label마다 존재하는 chunk에 모아서 압축해서 저장한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;저장 용량을 크게 줄일 수 있다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Loki의 특징&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Loki의 특징은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7k94O/dJMcahJBNSC/OGQByoxVRg1yIPMgfwqdD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7k94O/dJMcahJBNSC/OGQByoxVRg1yIPMgfwqdD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7k94O/dJMcahJBNSC/OGQByoxVRg1yIPMgfwqdD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7k94O%2FdJMcahJBNSC%2FOGQByoxVRg1yIPMgfwqdD1%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;771&quot; height=&quot;294&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;488&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;Kubernetes 환경에서 로깅 시스템으로 &lt;b&gt;Grafana Loki&lt;/b&gt;를 채택하면&lt;b&gt; Prometheus와 Grafana 기반의 모니터링 및 알림 시스템&lt;/b&gt;과 자연스럽게 통합된 &lt;b&gt;LMA(Log-Monitoring-Alerting)&lt;/b&gt; 환경을 구축할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Loki-Stack이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Loki-Stack은 Grafana Loki 운영에 필요한 다양한 컴포넌트들을 k8s에 쉽게 배포할 수 있게 도와주는 Helm Charts 패키지입니다.&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;Loki:&lt;/b&gt; 로그 집계&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Promtail:&lt;/b&gt; DaemonSet으로 존재하며 로그를 수집하여 Loki에 전송&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Grafana:&lt;/b&gt; 데이터 시각화&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjMPCg/dJMcai9wPuA/Ao2TJMfdqQT9zugGmlxlUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjMPCg/dJMcai9wPuA/Ao2TJMfdqQT9zugGmlxlUK/img.png&quot; data-alt=&quot;https://www.persistent.com/blogs/log-aggregation-using-grafana-loki-stack/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjMPCg/dJMcai9wPuA/Ao2TJMfdqQT9zugGmlxlUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjMPCg%2FdJMcai9wPuA%2FAo2TJMfdqQT9zugGmlxlUK%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;1024&quot; height=&quot;491&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;491&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.persistent.com/blogs/log-aggregation-using-grafana-loki-stack/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;loki-stack의 helm charts 구조는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763864287761&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;./loki-stack/
├── Chart.yaml
├── README.md
├── charts
│   ├── filebeat
│   ├── fluent-bit
│   ├── grafana
│   ├── logstash
│   ├── loki
│   ├── prometheus
│   └── promtail
├── requirements.lock
├── requirements.yaml
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── datasources.yaml
│   └── tests
└── values.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;모니터링 시스템 구축&lt;/b&gt;&amp;nbsp;&lt;/h2&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;k8s cluster&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;helm&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;에러 로그를 수집할 대상 Pod&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포는 monitoring namespace 안에서 수행하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763863766623&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create namsespace monitoring&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. kube-Prometheus-Stack 배포&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;helm charts의 저장소를 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763863781565&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm repo add prometheus-community https://prometheus-community.github.io/
helm repo update&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;values.yaml을 다운받습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763864371759&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mkdir kube-prometheus-stack
helm show values prometheus-community/kube-prometheus-stack &amp;gt; ./kube-prometheus-stack/override-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;편의를 위해 UI가 존재하는 prometheus와 grafana의 Service type을 NodePort로 변경하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;227&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beAxjd/dJMcagKG05D/Nr4PSo5Kgn1XnpvZ2rrFFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beAxjd/dJMcagKG05D/Nr4PSo5Kgn1XnpvZ2rrFFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beAxjd/dJMcagKG05D/Nr4PSo5Kgn1XnpvZ2rrFFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeAxjd%2FdJMcagKG05D%2FNr4PSo5Kgn1XnpvZ2rrFFK%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;383&quot; height=&quot;163&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;227&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEtoub/dJMcaaRfmtq/S3HZHZuQcRwaZP3ancxnf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEtoub/dJMcaaRfmtq/S3HZHZuQcRwaZP3ancxnf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEtoub/dJMcaaRfmtq/S3HZHZuQcRwaZP3ancxnf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEtoub%2FdJMcaaRfmtq%2FS3HZHZuQcRwaZP3ancxnf1%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;638&quot; height=&quot;206&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;270&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;또한 prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues의 값을 false로 바꿔줍니다.&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;default는 true인데 true로 설정하면 helm values를 통해 배포한 service monitor만 등록합니다.&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;다음 명령어로 prometheus custom resource의 설정을 보면 service monitor selector를 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764162806594&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl -n monitoring describe prometheus kube-prometheus-stack-prometheus&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;false로 바꾸어서 label을 자유롭게 설정할 수 있게 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1201&quot; data-origin-height=&quot;297&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctY51p/dJMcab3GW9c/JB100Zu35FeSbM6Okk0Dy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctY51p/dJMcab3GW9c/JB100Zu35FeSbM6Okk0Dy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctY51p/dJMcab3GW9c/JB100Zu35FeSbM6Okk0Dy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctY51p%2FdJMcab3GW9c%2FJB100Zu35FeSbM6Okk0Dy1%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;682&quot; height=&quot;169&quot; data-origin-width=&quot;1201&quot; data-origin-height=&quot;297&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;이제 변경한 values.yaml을 통해 kube-prometheus-stack helm charts를 배포합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763864677379&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack -f ./kube-prometheus-stack/override-values.yaml -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;spec.service monitor select.match labels을 보면 선택하는 service monitor의 label을 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764162857208&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spec:
.
.
.
  Service Monitor Selector:
    Match Labels:
      Release:  kube-prometheus-stack&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;임시 방편으로 이 label을 service monitor에 붙여 service monitor로 인식하게 할 수도 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Loki-Stack 배포&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;helm charts의 저장소를 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763864219395&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm repo add grafana https://grafana.github.io/helm-charts
helm repo update&lt;/code&gt;&lt;/pre&gt;
&lt;p 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;values.yaml을 다운받습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763866589019&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mkdir loki-stack
helm show values grafana/loki-stack &amp;gt; ./loki-stack/override-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;loki stack의 values.yaml은 비교적 간단합니다. 아래처럼 다양한 컴포넌트들이 있고 default 설정은 loki와 promtail만 활성화 되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;override-values.yaml&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763866683923&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;test_pod:
  enabled: true
...

loki:
  enabled: true
...

promtail:
  enabled: true
...

fluent-bit:
  enabled: false
...

grafana:
  enabled: false
...

prometheus:
  enabled: false
...

filebeat:
  enabled: false
...

logstash:
  enabled: false
...&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;로그 수집을 위한 promtail scrape config설정을 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763911626570&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;promtail:
  enabled: true
  config:
    logLevel: info
    serverPort: 3101
    clients:
      - url: http://{{ .Release.Name }}:3100/loki/api/v1/push
    scrape_configs:
      - job_name: springboot-logs
        kubernetes_sd_configs:
          - role: pod
        relabel_configs:
          - source_labels: [__meta_kubernetes_namespace]
            action: keep
            regex: test
          - source_labels: [__meta_kubernetes_pod_label_app]
            action: keep
            regex: monitoring-test&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;grafana와 prometheus는 prometheus-stack을 통해 배포했기 때문에 loki와 promtail만 활성화 한 뒤 배포합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763866808792&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm install loki-stack grafana/loki-stack -f ./loki-stack/override-values.yaml -n monitoring&lt;/code&gt;&lt;/pre&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;3. test용 서버 구축&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 테스트를 위해 error가 발생했을 때 로그를 남기고 에러를 throw하는 spring boot 서버 코드를 작성했습니다.&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;dependency는 다음 두 가지가 필요합니다.&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;Actuator: &lt;/b&gt;metric 제공을 위한 dependency&lt;/li&gt;
&lt;li&gt;&lt;b&gt;micrometer-registry-prometheus:&lt;/b&gt; prometheus metric 제공을 위한 dependency&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;springboot 설정은 다음과 같이 진행했습니다.&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;prometheus에서 메트릭을 수집하기 위해 actuator 설정 (health, metrics, prometheus, info)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;loki에서 error log를 수집하기 위해 에러가 발생했을 때 로깅&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 id 값이 1이나 2이면 예외 상황 발생 로그를 출력하고 3은 에러 발생, 그렇지 않으면 success를 반환하는 API를 만들었습니다.&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;&amp;lt;spring boot server&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763897737401&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.monitoring_test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class MonitoringTestApplication {

	public static void main(String[] args) {
		SpringApplication.run(MonitoringTestApplication.class, args);
	}
}

@RestController
class TestController {

    @GetMapping(&quot;/test&quot;)
    public ResponseEntity&amp;lt;String&amp;gt; testApi(@RequestParam(&quot;id&quot;) int id) {
        if (id == 1) {
            System.err.println(&quot;internal server error 1 occurred&quot;);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(&quot;internal server error 1 occurred&quot;);
        } else if (id == 2) {
            System.err.println(&quot;internal server error 2 occurred&quot;);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(&quot;internal server error 2 occurred&quot;);
        } else if (id == 3){
            throw new RuntimeException(&quot;Unexpected Error occurred&quot;);
        }
        return ResponseEntity.ok(&quot;Success&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;b&gt;&amp;lt;application.properties&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;actuator 설정을 통해 health, metrics, prometheus, info API를 열어두었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763898392561&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.application.name=monitoring-test
management.endpoints.web.exposure.include=health,metrics,prometheus,info
management.endpoint.health.show-details=always&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;k8s pod로 실행할 것이기 때문에 Dockerfile도 작성했습니다.&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;&amp;lt;Dockerfile&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763898606003&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Java 19 JDK 베이스
FROM eclipse-temurin:19-jdk-alpine

WORKDIR /app

# 프로젝트 전체 복사
COPY . .

# 권한 부여 (gradlew 실행 가능하게)
RUN chmod +x ./gradlew

# Gradle 빌드
RUN ./gradlew clean bootJar --no-daemon

# 포트 노출
EXPOSE 8080

# 컨테이너 실행 시 jar 실행
CMD [&quot;java&quot;, &quot;-jar&quot;, &quot;build/libs/monitoring-test-0.0.1-SNAPSHOT.jar&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;다음 명령어로 docker image를 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763898646315&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker build -t monitoring-test:latest .&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;만약 k8s cluster의 container runtime이 다를 경우 docker hub에 push 한 뒤 pull 하는 방식을 사용하면 됩니다.&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;k8s manifest를 정의합니다.&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;&amp;lt;monitoring-test.yaml&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763898720478&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: monitoring-test-pod
  namespace: test
  labels:
    app: monitoring-test
spec:
  containers:
    - name: monitoring-test
      image: monitoring-test:latest
      ports:
        - containerPort: 8080
  restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  name: monitoring-test-service
  namespace: test
spec:
  selector:
    app: monitoring-test
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
  type: ClusterIP&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;다음 명령어로 k8s manifest를 적용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763899371409&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create ns test
kubectl apply -f ./monitoring-test.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;다음은 ServiceMonitor를 생성해서 prometheus가 위에서 생성한 서비스의 metric을 수집하게 합니다.&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;endpoint를 보면 actuator를 통해 열어둔 prometheus metric을 가져오는 모습을 확인할 수 있습니다.&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;&amp;lt;service-monitor.yaml&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763908343539&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: monitoring-test-servicemonitor
  namespace: test
spec:
  selector:
    matchLabels: 
      app: monitoring-test
  namespaceSelector:
    matchNames:
      - test
  endpoints:
  - port: prometheus
    path: /actuator/prometheus
    scheme: http
    interval: 3s&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;k8s cluster에 적용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763908457543&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl apply -f ./service-monitor.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;prometheus UI에 접속해서 target에 접속했을 때 기본적으로 생성되는 k8s 관련 service monitor 말고 우리가 만든 service monitor가 생성되었다면 성공입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kEpgd/dJMcahpi8kZ/vkGp9JmBVMHM2YNpDMJVv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kEpgd/dJMcahpi8kZ/vkGp9JmBVMHM2YNpDMJVv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kEpgd/dJMcahpi8kZ/vkGp9JmBVMHM2YNpDMJVv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkEpgd%2FdJMcahpi8kZ%2FvkGp9JmBVMHM2YNpDMJVv0%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;1240&quot; height=&quot;540&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;540&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. prometheus, loki datasource 등록&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Connections -&amp;gt; Add new Connection -&amp;gt; prometheus 검색&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;따로 kube-prometheus-stack을 건드리지 않았다면 prometheus url은 다음과 같습니다.&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;http://kube-prometheus-stack-prometheus:9090&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1243&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sOq52/dJMcad1seJY/Kh09IKKKtKVFeEnXaSBtok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sOq52/dJMcad1seJY/Kh09IKKKtKVFeEnXaSBtok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sOq52/dJMcad1seJY/Kh09IKKKtKVFeEnXaSBtok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsOq52%2FdJMcad1seJY%2FKh09IKKKtKVFeEnXaSBtok%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;1243&quot; height=&quot;748&quot; data-origin-width=&quot;1243&quot; data-origin-height=&quot;748&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Connections -&amp;gt; Add new Connection에서 loki 검색&lt;/p&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;733&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4QPL5/dJMcahW8Xod/I3c8xxalKuiQlLqbVXVei1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4QPL5/dJMcahW8Xod/I3c8xxalKuiQlLqbVXVei1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4QPL5/dJMcahW8Xod/I3c8xxalKuiQlLqbVXVei1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4QPL5%2FdJMcahW8Xod%2FI3c8xxalKuiQlLqbVXVei1%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;733&quot; data-origin-width=&quot;1911&quot; data-origin-height=&quot;733&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;connection에는 loki 주소를 입력하면 됩니다. grafana, loki 모두 k8s에서 배포했기 때문에 규칙에 맞게 작성하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;899&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9QRgz/dJMcachdQSs/TrK17meKMyUpBxZAp6Ofd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9QRgz/dJMcachdQSs/TrK17meKMyUpBxZAp6Ofd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9QRgz/dJMcachdQSs/TrK17meKMyUpBxZAp6Ofd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9QRgz%2FdJMcachdQSs%2FTrK17meKMyUpBxZAp6Ofd1%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;1245&quot; height=&quot;899&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;899&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;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. grafana dashboard 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;datasource 등록을 완료했으면 dashboard를 생성합니다.&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;Dashboards -&amp;gt; New dashboard -&amp;gt; Import dashboard&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;1266&quot; data-origin-height=&quot;716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CEqTa/dJMcacasnbG/J7RplGi8wMt2Gs9OJqJdfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CEqTa/dJMcacasnbG/J7RplGi8wMt2Gs9OJqJdfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CEqTa/dJMcacasnbG/J7RplGi8wMt2Gs9OJqJdfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCEqTa%2FdJMcacasnbG%2FJ7RplGi8wMt2Gs9OJqJdfk%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;1266&quot; height=&quot;716&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;716&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;직접 custom해도 되지만 기존에 존재하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;spring boot observability&lt;/b&gt;라는 dashboard를 사용하려고 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://grafana.com/grafana/dashboards/17175-spring-boot-observability/&quot;&gt;https://grafana.com/grafana/dashboards/17175-spring-boot-observability/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1763912304135&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;Spring Boot Observability | Grafana Labs&quot; data-og-description=&quot;Import the dashboard template Copy ID to clipboard or Download JSON&quot; data-og-host=&quot;grafana.com&quot; data-og-source-url=&quot;https://grafana.com/grafana/dashboards/17175-spring-boot-observability/&quot; data-og-url=&quot;https://grafana.com/grafana/dashboards/17175-spring-boot-observability/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bU6o3d/hyZOqi4jtH/BLUiKc3PQ2TwAqGYSDKOFk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/b0MFri/hyZOwKmdrc/axtursvdRDQy6er8pQaIP0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://grafana.com/grafana/dashboards/17175-spring-boot-observability/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://grafana.com/grafana/dashboards/17175-spring-boot-observability/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bU6o3d/hyZOqi4jtH/BLUiKc3PQ2TwAqGYSDKOFk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/b0MFri/hyZOwKmdrc/axtursvdRDQy6er8pQaIP0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;Spring Boot Observability | Grafana Labs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Import the dashboard template Copy ID to clipboard or Download JSON&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;위 사이트에서 구한 id 값을 입력하거나 다운로드받은 JSON 값을 넣어주면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1898&quot; data-origin-height=&quot;1128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzx51G/dJMcad1shwT/4JbCukvOSEZkM7NzOXjaYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzx51G/dJMcad1shwT/4JbCukvOSEZkM7NzOXjaYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzx51G/dJMcad1shwT/4JbCukvOSEZkM7NzOXjaYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbzx51G%2FdJMcad1shwT%2F4JbCukvOSEZkM7NzOXjaYk%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;1898&quot; height=&quot;1128&quot; data-origin-width=&quot;1898&quot; data-origin-height=&quot;1128&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;대쉬보드를 생성하고 spring boot 서버로 요청을 계속 보내보면 아래와 같이 dashboard에 데이터를 시각화 한 상태로 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1892&quot; data-origin-height=&quot;1916&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdRi6s/dJMcaf55LGp/4ZkQEaKfqD0XKnoKpqvVGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdRi6s/dJMcaf55LGp/4ZkQEaKfqD0XKnoKpqvVGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdRi6s/dJMcaf55LGp/4ZkQEaKfqD0XKnoKpqvVGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdRi6s%2FdJMcaf55LGp%2F4ZkQEaKfqD0XKnoKpqvVGK%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;1892&quot; height=&quot;1916&quot; data-origin-width=&quot;1892&quot; data-origin-height=&quot;1916&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고 자료&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://grafana.com/docs/loki/latest/get-started/architecture/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://grafana.com/docs/loki/latest/get-started/architecture/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://devocean.sk.com/blog/techBoardDetail.do?ID=163964&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://devocean.sk.com/blog/techBoardDetail.do?ID=163964&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://prometheus.io/docs/introduction/overview&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://prometheus.io/docs/introduction/overview&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/14505/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://techblog.woowahan.com/14505/&lt;/a&gt;&lt;/p&gt;</description>
      <author>웅대</author>
      <guid isPermaLink="true">https://growth-coder.tistory.com/377</guid>
      <comments>https://growth-coder.tistory.com/377#entry377comment</comments>
      <pubDate>Mon, 24 Nov 2025 00:41:11 +0900</pubDate>
    </item>
    <item>
      <title>Groovy curl 요청 함수</title>
      <link>https://growth-coder.tistory.com/376</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 jenkins CI/CD 파이프라인 코드를 다루게 되면서 curl을 사용하여 HTTP 요청을 보내게 될 일이 종종 있었습니다.&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;다만 curl에 익숙하지 않아 다양한 형태의 HTTP 요청을 보내는 코드를 작성하는 것이 어려웠는데요.&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;이번 기회에 groovy로 다양한 형태의 curl 요청 함수를 작성해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;curl 함수&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 4가지 형태의 curl 요청 함수를 작성해보았습니다.&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&gt;&lt;b&gt;GET + query params&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;POST + request body&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;POST + form data&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;POST + form data + file&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;GET + query params&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;함수&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763384649325&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// GET 요청
def curlGet = { String url, Map params = [:], Map headers = [:] -&amp;gt;
    String qs = params.collect { k, v -&amp;gt; &quot;${k}=${v}&quot; }.join(&quot;&amp;amp;&quot;)
    String fullUrl = qs ? &quot;${url}?${qs}&quot; : url
    String headerStr = headers.collect { k, v -&amp;gt;
        &quot;-H \&quot;${k}: ${v}\&quot;&quot;
    }.join(&quot; &quot;)
    String cmd = &quot;&quot;&quot;
        curl -s -X GET \\
        ${headerStr} \\
        &quot;${fullUrl}&quot;
    &quot;&quot;&quot;
    return sh(script: cmd.trim(), returnStdout: true).trim()
}&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;&amp;lt;사용법&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763384901853&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;println &quot;===== GET TEST =====&quot;
println curlGet(
    &quot;https://httpbin.org/get&quot;,
    [q: &quot;hello&quot;],
    [&quot;Accept&quot;: &quot;application/json&quot;]
)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;POST + form data&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;함수&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763384747457&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// POST 요청 + form-data
def curlPostForm = { String url, Map form = [:], Map headers = [:] -&amp;gt;
    String headerStr = headers.collect { k, v -&amp;gt;
        &quot;-H \&quot;${k}: ${v}\&quot;&quot;
    }.join(&quot; &quot;)
    String formStr = form.collect { k, v -&amp;gt;
        &quot;-F \&quot;${k}=${v}\&quot;&quot;
    }.join(&quot; &quot;)
    String cmd = &quot;&quot;&quot;
        curl -s -X POST \\
        ${headerStr} \\
        ${formStr} \\
        &quot;${url}&quot;
    &quot;&quot;&quot;
    return sh(script: cmd.trim(), returnStdout: true).trim()
}&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;lt;사용법&amp;gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763384941795&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// POST 요청 + form-data
def curlPostForm = { String url, Map form = [:], Map headers = [:] -&amp;gt;
    String headerStr = headers.collect { k, v -&amp;gt;
        &quot;-H \&quot;${k}: ${v}\&quot;&quot;
    }.join(&quot; &quot;)
    String formStr = form.collect { k, v -&amp;gt;
        &quot;-F \&quot;${k}=${v}\&quot;&quot;
    }.join(&quot; &quot;)
    String cmd = &quot;&quot;&quot;
        curl -s -X POST \\
        ${headerStr} \\
        ${formStr} \\
        &quot;${url}&quot;
    &quot;&quot;&quot;
    return sh(script: cmd.trim(), returnStdout: true).trim()
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Post + request body&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;함수&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763384710287&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// POST 요청 + JSON
def curlPostJson = { String url, Map body, Map headers = [:] -&amp;gt;
    String json = groovy.json.JsonOutput.toJson(body)
    Map finalHeaders = [&quot;Content-Type&quot;: &quot;application/json&quot;] + headers
    String headerStr = finalHeaders.collect { k, v -&amp;gt;
        &quot;-H \&quot;${k}: ${v}\&quot;&quot;
    }.join(&quot; &quot;)
    String cmd = &quot;&quot;&quot;
        curl -s -X POST \\
        ${headerStr} \\
        -d '${json}' \\
        &quot;${url}&quot;
    &quot;&quot;&quot;
    return sh(script: cmd.trim(), returnStdout: true).trim()
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1763384959789&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;println &quot;===== POST JSON TEST =====&quot;
println curlPostJson(
    &quot;https://httpbin.org/post&quot;,
    [msg: &quot;hello&quot;, list: [1,2,3]],
    [&quot;Accept&quot;: &quot;application/json&quot;]
)&lt;/code&gt;&lt;/pre&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;Post + form data + file&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;함수&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763384853153&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// POST 요청 + form data + 파일 업로드
def curlPostFormWithFile = { String url, Map formData, String filePath, Map headers = [:] -&amp;gt;
    // 헤더 문자열 구성
    String headerStr = headers.collect { k, v -&amp;gt;
        &quot;-H \&quot;${k}: ${v}\&quot;&quot;
    }.join(&quot; &quot;)
    // form-data 문자열 구성
    String formStr = formData.collect { k, v -&amp;gt;
        &quot;-F \&quot;${k}=${v}\&quot;&quot;
    }.join(&quot; &quot;)
    // curl 명령
    String cmd = &quot;&quot;&quot;
        curl -s -X POST \\
        ${headerStr} \\
        ${formStr} \\
        -F &quot;file=@${filePath}&quot; \\
        &quot;${url}&quot;
    &quot;&quot;&quot;
    return sh(script: cmd.trim(), returnStdout: true).trim()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;사용법&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763385026286&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;println &quot;===== POST JSON + FILE UPLOAD TEST =====&quot;
println curlPostFormWithFile(
    &quot;https://httpbin.org/post&quot;,
    [user: &quot;hong&quot;, type: &quot;upload-test&quot;],
    &quot;test_upload.txt&quot;,
    [&quot;Accept&quot;: &quot;application/json&quot;]
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Jenkins pipeline 사용 예시&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1763382393509&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pipeline {
    agent {
        kubernetes {
            label 'curl-test-pod'
            defaultContainer 'tools'
            yaml &quot;&quot;&quot;
apiVersion: v1
kind: Pod
spec:
  containers:
    - name: tools
      image: alpine:latest
      command:
        - cat
      tty: true
&quot;&quot;&quot;
        }
    }

    stages {
        stage('Setup &amp;amp; Curl Tests') {
            steps {
                container('tools') {
                    script {

                        // curl 설치
                        sh &quot;&quot;&quot;
                            apk update
                            apk add --no-cache curl
                        &quot;&quot;&quot;

                        // GET 요청
                        def curlGet = { String url, Map params = [:], Map headers = [:] -&amp;gt;
                            String qs = params.collect { k, v -&amp;gt; &quot;${k}=${v}&quot; }.join(&quot;&amp;amp;&quot;)
                            String fullUrl = qs ? &quot;${url}?${qs}&quot; : url
                            String headerStr = headers.collect { k, v -&amp;gt;
                                &quot;-H \&quot;${k}: ${v}\&quot;&quot;
                            }.join(&quot; &quot;)
                            String cmd = &quot;&quot;&quot;
                                curl -s -X GET \\
                                ${headerStr} \\
                                &quot;${fullUrl}&quot;
                            &quot;&quot;&quot;
                            return sh(script: cmd.trim(), returnStdout: true).trim()
                        }

                        // POST 요청 + form-data
                        def curlPostForm = { String url, Map form = [:], Map headers = [:] -&amp;gt;
                            String headerStr = headers.collect { k, v -&amp;gt;
                                &quot;-H \&quot;${k}: ${v}\&quot;&quot;
                            }.join(&quot; &quot;)
                            String formStr = form.collect { k, v -&amp;gt;
                                &quot;-F \&quot;${k}=${v}\&quot;&quot;
                            }.join(&quot; &quot;)
                            String cmd = &quot;&quot;&quot;
                                curl -s -X POST \\
                                ${headerStr} \\
                                ${formStr} \\
                                &quot;${url}&quot;
                            &quot;&quot;&quot;
                            return sh(script: cmd.trim(), returnStdout: true).trim()
                        }

                        // POST 요청 + JSON
                        def curlPostJson = { String url, Map body, Map headers = [:] -&amp;gt;
                            String json = groovy.json.JsonOutput.toJson(body)
                            Map finalHeaders = [&quot;Content-Type&quot;: &quot;application/json&quot;] + headers
                            String headerStr = finalHeaders.collect { k, v -&amp;gt;
                                &quot;-H \&quot;${k}: ${v}\&quot;&quot;
                            }.join(&quot; &quot;)
                            String cmd = &quot;&quot;&quot;
                                curl -s -X POST \\
                                ${headerStr} \\
                                -d '${json}' \\
                                &quot;${url}&quot;
                            &quot;&quot;&quot;
                            return sh(script: cmd.trim(), returnStdout: true).trim()
                        }
                        
                        // POST 요청 + form data + 파일 업로드
                        def curlPostFormWithFile = { String url, Map formData, String filePath, Map headers = [:] -&amp;gt;
                        // 헤더 문자열 구성
                        String headerStr = headers.collect { k, v -&amp;gt;
                            &quot;-H \&quot;${k}: ${v}\&quot;&quot;
                        }.join(&quot; &quot;)
                        // form-data 문자열 구성
                        String formStr = formData.collect { k, v -&amp;gt;
                            &quot;-F \&quot;${k}=${v}\&quot;&quot;
                        }.join(&quot; &quot;)
                        // curl 명령
                        String cmd = &quot;&quot;&quot;
                            curl -s -X POST \\
                            ${headerStr} \\
                            ${formStr} \\
                            -F &quot;file=@${filePath}&quot; \\
                            &quot;${url}&quot;
                        &quot;&quot;&quot;
                        return sh(script: cmd.trim(), returnStdout: true).trim()
}

                        // 테스트용 파일 생성
                        sh &quot;echo '테스트 파일입니다.' &amp;gt; test_upload.txt&quot;

                        // 테스트 호출
                        println &quot;===== GET TEST =====&quot;
                        println curlGet(
                            &quot;https://httpbin.org/get&quot;,
                            [q: &quot;hello&quot;],
                            [&quot;Accept&quot;: &quot;application/json&quot;]
                        )

                        println &quot;===== POST FORM TEST =====&quot;
                        println curlPostForm(
                            &quot;https://httpbin.org/post&quot;,
                            [name: &quot;hong&quot;, age: &quot;20&quot;],
                            [&quot;Accept&quot;: &quot;application/json&quot;]
                        )

                        println &quot;===== POST JSON TEST =====&quot;
                        println curlPostJson(
                            &quot;https://httpbin.org/post&quot;,
                            [msg: &quot;hello&quot;, list: [1,2,3]],
                            [&quot;Accept&quot;: &quot;application/json&quot;]
                        )

                        println &quot;===== POST JSON + FILE UPLOAD TEST =====&quot;
                        println curlPostFormWithFile(
                            &quot;https://httpbin.org/post&quot;,
                            [user: &quot;hong&quot;, type: &quot;upload-test&quot;],
                            &quot;test_upload.txt&quot;,
                            [&quot;Accept&quot;: &quot;application/json&quot;]
                        )
                    }
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>groovy curl</category>
      <category>jenkins curl</category>
      <author>웅대</author>
      <guid isPermaLink="true">https://growth-coder.tistory.com/376</guid>
      <comments>https://growth-coder.tistory.com/376#entry376comment</comments>
      <pubDate>Mon, 17 Nov 2025 22:12:05 +0900</pubDate>
    </item>
    <item>
      <title>[DevOps] Jenkins 분산 빌드 아키텍처와 kubernetes</title>
      <link>https://growth-coder.tistory.com/375</link>
      <description>&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;226&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L5FQr/dJMcaajlXx1/NyngZZmfIa3ssQDNGeSkL1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L5FQr/dJMcaajlXx1/NyngZZmfIa3ssQDNGeSkL1/img.jpg&quot; data-alt=&quot;https://www.jenkins.io/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L5FQr/dJMcaajlXx1/NyngZZmfIa3ssQDNGeSkL1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL5FQr%2FdJMcaajlXx1%2FNyngZZmfIa3ssQDNGeSkL1%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;174&quot; height=&quot;240&quot; data-origin-width=&quot;226&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.jenkins.io/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jenkins는 소프트웨어 빌드, 테스트 또는 배포와 관련된 모든 종류의 작업을 자동화하는 데 사용할 수 있는 &lt;b&gt;오픈 소스 자동화 서버&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;Jenkins는 기본 시스템 패키지, Docker로 설치할 수 있고 JRE가 설치된 컴퓨터에서 독립적으로 실행할 수도 있습니다.&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;먼저 Jenkins의 아키텍처 먼저 알아봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Jenkins 분산 빌드 아키텍처&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jenkins controller는 빌드 환경을 관리하고 자체적으로 리소스를 사용하여 빌드를 수행할 수 있습니다.&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;하지만 이렇게 jenkins controller만 사용하게 된다면 다음과 같은 단점들이 존재합니다.&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;부하가 증가하게 될 경우 scale up을 하는 동안 jenkins의 작업이 중단됩니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;jenkins 사용자는 controller의 모든 권한을 갖게 됩니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 확장성이 떨어지고 사용자에게 너무 많은 권한을 주기 때문에&amp;nbsp;jenkins는&lt;b&gt; 분산 빌드 아키텍처&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;controller&lt;/b&gt;와 &lt;b&gt;agent&lt;/b&gt;로 나뉘는데 각자 다음과 같은 역할을 담당합니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 39.4182%; height: 110px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 18.3333%; height: 21px;&quot;&gt;&lt;b&gt;이름&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.2917%; 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;width: 18.3333%; height: 21px;&quot;&gt;controller&lt;/td&gt;
&lt;td style=&quot;width: 52.2917%; height: 21px;&quot;&gt;HTTP 요청 처리 및 빌드 환경 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 18.3333%; height: 21px;&quot;&gt;agent&lt;/td&gt;
&lt;td style=&quot;width: 52.2917%; height: 21px;&quot;&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;1840&quot; data-origin-height=&quot;1800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/50iIW/dJMcacO0qVY/jsbxREedNjKsXo8oLen2j0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/50iIW/dJMcacO0qVY/jsbxREedNjKsXo8oLen2j0/img.png&quot; data-alt=&quot;https://growth-coder.tistory.com/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/50iIW/dJMcacO0qVY/jsbxREedNjKsXo8oLen2j0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F50iIW%2FdJMcacO0qVY%2FjsbxREedNjKsXo8oLen2j0%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;419&quot; height=&quot;410&quot; data-origin-width=&quot;1840&quot; data-origin-height=&quot;1800&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://growth-coder.tistory.com/&lt;/figcaption&gt;
&lt;/figure&gt;
&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;Agent를 추가해 연결하면 쉽게 확장이 가능합니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다양한 OS를 사용하는 Agent를 연결할 수 있습니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Agent에게는 최소 권한을 줄 수 있습니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 빌드 아키텍처를 적용하려면 Controller와 Agent가 서로 통신할 수 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Controller-Agent 통신&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통신 방식에는&lt;b&gt; ssh connector&lt;/b&gt; 방식과 &lt;b&gt;in bound connector&lt;/b&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&gt;&lt;b&gt;ssh connector&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ssh를 통해 controller와 agent가 통신합니다.&lt;/li&gt;
&lt;li&gt;controller의 공개 키가 agent의 인증 키 집합에 포함되어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;in bound connector&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;JNLP&lt;/b&gt;를&amp;nbsp;통해 agent를 실행합니다.&lt;/li&gt;
&lt;li&gt;수동으로 실행한다면 agent를 중앙에서 관리할 수 없지만 방화벽 내부의 agent가 방화벽 외부의 controller에 연결할 때 유용합니다.&lt;/li&gt;
&lt;li&gt;수동으로 실행한 뒤 서비스로 등록하면 자동으로 재시작되게 만들 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;in bound HTTP connector&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;in bound connector와 유사하지만 agent가 headless로 실행되고 HTTP(s)를 통해 터널링 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;JNLP란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; JNLP(Java Network Launch Protocol)&lt;/b&gt;은 웹에서 java application을 실행할 수 있는 프로토콜입니다.&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; JWS(Java Web Start)&lt;/b&gt;는 JNLP를 사용하여 클라이언트 시스템의 웹 브라우저에서 한 번의 클릭으로 Java EE 애플리케이션 클라이언트를 원격 클라이언트 시스템에 배치할 수 있게 해주는 기술입니다&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;1022&quot; data-origin-height=&quot;337&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c84dmD/dJMcacIfjZl/hDSJQkcVmbWjvJA3eQa3pK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c84dmD/dJMcacIfjZl/hDSJQkcVmbWjvJA3eQa3pK/img.png&quot; data-alt=&quot;https://askmedawaa.wordpress.com/2021/03/24/using-java-web-start-with-oracle-e-business-suite/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c84dmD/dJMcacIfjZl/hDSJQkcVmbWjvJA3eQa3pK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc84dmD%2FdJMcacIfjZl%2FhDSJQkcVmbWjvJA3eQa3pK%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;651&quot; height=&quot;215&quot; data-origin-width=&quot;1022&quot; data-origin-height=&quot;337&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://askmedawaa.wordpress.com/2021/03/24/using-java-web-start-with-oracle-e-business-suite/&lt;/figcaption&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;사용자가 웹 브라우저에서 jnlp 파일을 클릭하면 JNLP 프로토콜을 통해 JAR 파일과 리소스를 웹 서버로부터 다운로드 받고 JVM 위에서 애플리케이션을 실행하게 됩니다.&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;이러한 JWS는 Java 11에서 제거되었지만 Jenkins에서는 여전히 JNLP 프로토콜을 활용한 통신은 제공하고 있습니다.&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;k8s와 kubernetes를 연동하면 미리 정의해둔 pod template을 보고 Agent를 pod로 실행합니다.&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;이 때, 정의하지 않아도 jnlp contianer도 pod에 함께 포함되어 Controller와 Agent 사이 통신을 담당하게 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Jenkins와 kubernetes&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jenkins 분산 빌드 아키텍처의 장점은 &lt;b&gt;확장성&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;자유롭게 agent를 추가하여 부하에 대응할 수 있습니다.&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;물리적인 서버를 Jenkins Agent로 사용하여 controller와 연결하는 방식이 전통적인 방식입니다.&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;그런데 k8s와 같은 container orchestrator와 함께 사용하면 확장성을 높일 수 있습니다.&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;Jenkins와 k8s를 함께 사용할 때의 장점은 다음과 같습니다.&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;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;빌드를 병렬로 수행할 수 있습니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;부하를 균등하게 분배할 수 있습니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드를 시작할 때, 동적으로 k8s 안에 pod를 띄우고 이 안에서 빌드를 수행하는 방식입니다.&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;1362&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/leArA/dJMcag4WQsu/HGwWQHN1ZvFFSTx3k1050k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/leArA/dJMcag4WQsu/HGwWQHN1ZvFFSTx3k1050k/img.png&quot; data-alt=&quot;https://www.betsol.com/blog/devops-using-jenkins-docker-and-kubernetes/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/leArA/dJMcag4WQsu/HGwWQHN1ZvFFSTx3k1050k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FleArA%2FdJMcag4WQsu%2FHGwWQHN1ZvFFSTx3k1050k%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;632&quot; height=&quot;402&quot; data-origin-width=&quot;1362&quot; data-origin-height=&quot;866&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.betsol.com/blog/devops-using-jenkins-docker-and-kubernetes/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jenkins pipeline을 실행하는 과정은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dp85te/dJMcacapmoy/PDLjQt0utDPTE4oOK1DmfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dp85te/dJMcacapmoy/PDLjQt0utDPTE4oOK1DmfK/img.png&quot; data-alt=&quot;https://blog.voidmainvoid.net/140&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dp85te/dJMcacapmoy/PDLjQt0utDPTE4oOK1DmfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdp85te%2FdJMcacapmoy%2FPDLjQt0utDPTE4oOK1DmfK%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;506&quot; height=&quot;320&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;474&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://blog.voidmainvoid.net/140&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Jenkins와 kubernetes 연동&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 개념에 대해 모두 이해했다면 실습을 통해 Jenkins와 kubernetes를 연동해보겠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;저는 local에서 master VM과 worker VM 한 대를 띄워서 k8s cluster를 구축했습니다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;로컬에서 minikube를 통해 k8s cluster를 구축했다면 진행 과정이 약간 다를 수 있습니다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;저는 Jenkins controller 자체를 k8s의 Deployment로 배포하였습니다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;k8s cluster 외부에 Jenkins를 배포하였다면 cluster 인증 정보를 입력하는 과정이 약간 다를 수 있습니다.&lt;/b&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;다음은 Jenkins controller를 배포하기 위한 Deployment와 NodePort Service입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763257188566&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: jenkins-deployment
  namespace: jenkins
  labels:
    app: jenkins
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jenkins
  template:
    metadata:
      labels:
        app: jenkins
    spec:
      serviceAccountName: jenkins
      securityContext:
        runAsUser: 0           # 전체 Pod에서 root로 실행
        runAsGroup: 0
      containers:
      - name: jenkins
        image: jenkins/jenkins:lts
        ports:
        - containerPort: 8080
        - containerPort: 50000
        volumeMounts:
        - name: jenkins-home
          mountPath: /var/jenkins_home
      volumes:
      - name: jenkins-home
        hostPath:
          path: /home/[유저 이름]/data
          type: DirectoryOrCreate
---
apiVersion: v1
kind: Service
metadata:
  name: jenkins-service
  namespace: jenkins
spec:
  type: NodePort
  selector:
    app: jenkins
  ports:
  - name: http
    port: 8080
    targetPort: 8080
    nodePort: 30080
  - name: jnlp
    port: 50000
    targetPort: 50000
    nodePort: 30050&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;security context&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중간에 security context의 runAsUser와 runAsGroup의 id를 0번, root로 설정하는 모습을 확인할 수 있습니다&lt;/li&gt;
&lt;li&gt;jenkins 같은 경우 container를 띄우면서 내부 디렉토리에 write 연산을 수행하게 됩니다.&lt;/li&gt;
&lt;li&gt;그런데 directory에 대한 write 권한이 허용되지 않은 경우 실행에 실패할 수 있기 때문에 root로 실행하는 설정을 해두었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;volume mount&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저는 worker node를 하나만 사용하기 때문에 host mount 방식을 사용했습니다.&lt;/li&gt;
&lt;li&gt;volume host path는 worker node 환경에 맞게 적절히 변경해주시면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위 manifest를 k8s에 적용하고 &amp;lt;node ip 주소 : 30080&amp;gt;으로 접속하면 admin password를 입력하라고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;737&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yJFvl/dJMcacnWukP/dJ98X7qgkvNBbjwK01JQPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yJFvl/dJMcacnWukP/dJ98X7qgkvNBbjwK01JQPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yJFvl/dJMcacnWukP/dJ98X7qgkvNBbjwK01JQPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyJFvl%2FdJMcacnWukP%2FdJ98X7qgkvNBbjwK01JQPk%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;1672&quot; height=&quot;737&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;737&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;초기 admin 계정의 비밀번호는&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;/var/jenkins_home/secrets/initialAdminPassword&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;pre id=&quot;code_1763258125406&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl exec [jenkins pod 이름] -n jenkins -- cat /var/jenkins_home/secrets/initialAdminPassword&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1866&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q4ebA/dJMcabWR9zc/LLqKqvEe8PauE6EK6QjXV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q4ebA/dJMcabWR9zc/LLqKqvEe8PauE6EK6QjXV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q4ebA/dJMcabWR9zc/LLqKqvEe8PauE6EK6QjXV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq4ebA%2FdJMcabWR9zc%2FLLqKqvEe8PauE6EK6QjXV0%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;1866&quot; height=&quot;848&quot; data-origin-width=&quot;1866&quot; data-origin-height=&quot;848&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;먼저 Jenkins Agent를 kubernetes에서 실행하기 위한 kubernetes plugin을 설치합니다.&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;Jenkins 관리 -&amp;gt; Plugins&lt;/b&gt;에서 kubernetes를 검색하고 설치하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1867&quot; data-origin-height=&quot;597&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GzN27/dJMcacuH5Fl/MkxRLDLQVogOfseOUWOf0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GzN27/dJMcacuH5Fl/MkxRLDLQVogOfseOUWOf0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GzN27/dJMcacuH5Fl/MkxRLDLQVogOfseOUWOf0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGzN27%2FdJMcacuH5Fl%2FMkxRLDLQVogOfseOUWOf0K%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;1867&quot; height=&quot;597&quot; data-origin-width=&quot;1867&quot; data-origin-height=&quot;597&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;설치가 완료되면 자동 재시작 버튼이 있는데 재시작을 해야 plugin 설치가 완료됩니다.&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;Jenkins 관리 -&amp;gt; clouds -&amp;gt; New Cloud&lt;/b&gt;에서 kubernetes type을 선택하고 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1291&quot; data-origin-height=&quot;613&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bM4S0G/dJMcabvNKwU/ykNk8XzXyAqsuKVmkxfmA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bM4S0G/dJMcabvNKwU/ykNk8XzXyAqsuKVmkxfmA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bM4S0G/dJMcabvNKwU/ykNk8XzXyAqsuKVmkxfmA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbM4S0G%2FdJMcabvNKwU%2FykNk8XzXyAqsuKVmkxfmA1%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;601&quot; height=&quot;285&quot; data-origin-width=&quot;1291&quot; data-origin-height=&quot;613&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Jenkins controller가 kubernetes에 접근하기 위한 정보&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Jenkins Agent가 Jenkins Controller와 통신하기 위한 정보&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1842&quot; data-origin-height=&quot;1575&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjSvoe/dJMb99Y3uTe/ucvuEOQ0oxMse1X0sd0dTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjSvoe/dJMb99Y3uTe/ucvuEOQ0oxMse1X0sd0dTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjSvoe/dJMb99Y3uTe/ucvuEOQ0oxMse1X0sd0dTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjSvoe%2FdJMb99Y3uTe%2FucvuEOQ0oxMse1X0sd0dTk%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;1842&quot; height=&quot;1575&quot; data-origin-width=&quot;1842&quot; data-origin-height=&quot;1575&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1827&quot; data-origin-height=&quot;1808&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bk9Ual/dJMcafEX9Yz/pfy52g7K3ZOEa01k1JxAE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bk9Ual/dJMcafEX9Yz/pfy52g7K3ZOEa01k1JxAE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bk9Ual/dJMcafEX9Yz/pfy52g7K3ZOEa01k1JxAE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbk9Ual%2FdJMcafEX9Yz%2Fpfy52g7K3ZOEa01k1JxAE1%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;1827&quot; height=&quot;1808&quot; data-origin-width=&quot;1827&quot; data-origin-height=&quot;1808&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;kubernetes URL:&lt;/b&gt; kubernetes API server의 주소&lt;/li&gt;
&lt;li&gt;&lt;b&gt;kubernetes server certificate key:&lt;/b&gt; X.509 PEM 인코딩 인증서 또는 base64 인코딩 인증서&lt;/li&gt;
&lt;li&gt;&lt;b&gt;kubernetes namespace:&lt;/b&gt; agent 배치 namespace&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Credentials:&lt;/b&gt; k8s 사용 권한&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Jenkins URL:&lt;/b&gt; Jenkins Controller server의 주소&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Jenkins: tunnel:&lt;/b&gt; Jenkins에 직접 접근을 대체할 주소&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;kubernetes URL&lt;/b&gt;은 다음 명령어로 구할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763213724614&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl cluster-info&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;아래처럼 API server의 주소를 구할 수 있습니다. (https://192.168.35.73:6443)&lt;/p&gt;
&lt;pre id=&quot;code_1763213736315&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Kubernetes control plane is running at https://192.168.35.73:6443
CoreDNS is running at https://192.168.35.73:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.&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;참고로 현재 Jenkins Controller가 k8s clsuter 내부에 배포되었기 때문에&lt;b&gt; kubernetes.default.svc.cluster.local&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;만약 Jenkins Controller가 k8s cluster 외부에 배포되었다면 위에서 구한 주소를 입력하면 됩니다.&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;kubernetes server certificate key&lt;/b&gt;는 다음 명령어로 구할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763213820886&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl config view&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;cluster.certificate-authority&lt;/b&gt;의 값이&lt;b&gt; kubernetes server certificate key&lt;/b&gt;입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763258778188&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://192.168.35.73:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
users:
- name: kubernetes-admin
  user:
    client-certificate-data: DATA+OMITTED
    client-key-data: DATA+OMITTED&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;지금은 데이터들이 DATA+OMITTED로 나와 있는데 이는 config를 볼 때 --raw 옵션을 붙여주면 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763258863959&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl config view --raw&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;만약 값이 아닌 주소가 입력되어 있다면 해당 주소에서 인증서를 확인하고 base64 인코딩이 되어 있지 않다면 인코딩을 수행하면 됩니다.&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;kubernetes namespace&lt;/b&gt;는 Agent를 실행할 k8s의 namespace를 의미합니다. Jenkins Controller를 jenkins namespace에 생성했으니까 Agent도 같은 namespace에 생성하겠습니다.&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;Credentials&lt;/b&gt;은 k8s API를 사용하기 위한 자격 증명입니다. Jenkins controller가 k8s cluster 내부에 배포되어 있다면 작성하지 않아도 되지만 외부에 존재한다면 작성해야 합니다.&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;지금은 cluster 내부에 jenkins controller가 배포되어 있기 때문에 하지 않아도 되지만 그래도 kubeconfig 파일을 사용해서 credentials를 등록해보겠습니다.&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;kubeconfig 파일은 certificate key를 구할 때 사용했던 명령어로 구할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763262137946&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl config view --raw&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;/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;credentials의&lt;b&gt; +ADD&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;948&quot; data-origin-height=&quot;788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br2rrY/dJMcaa4JnRF/nZYFjuTljT9O26f1cSjQTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br2rrY/dJMcaa4JnRF/nZYFjuTljT9O26f1cSjQTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br2rrY/dJMcaa4JnRF/nZYFjuTljT9O26f1cSjQTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr2rrY%2FdJMcaa4JnRF%2FnZYFjuTljT9O26f1cSjQTK%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;948&quot; height=&quot;788&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;788&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;이제 다음과 같이 Jenkins가 k8s에 접근하기 위한 정보를 모두 입력했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;946&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pb9o0/dJMcagYbicK/wqHNB05diOK6BzKfiwZosk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pb9o0/dJMcagYbicK/wqHNB05diOK6BzKfiwZosk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pb9o0/dJMcagYbicK/wqHNB05diOK6BzKfiwZosk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpb9o0%2FdJMcagYbicK%2FwqHNB05diOK6BzKfiwZosk%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;1232&quot; height=&quot;946&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;946&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Test Connection 버튼을 눌러 &lt;b&gt;&quot;Connected to Kubernetes&quot;&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;다음은 Agent가 Controller에게 접근하기 위한 정보입니다.&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;Jenins URL&lt;/b&gt;에는 현재 Jenkins UI의&lt;b&gt; host + port&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;Jenkins tunnel&lt;/b&gt;에는 protocol을 제외하고 host+port를 입력하면 됩니다.&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;disable https certificate check&lt;/b&gt; 같은 경우 self signed 인증서를 사용하고 있을 때 체크해줘야 합니다.&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;그러나 저는 Jenkins controller를 k8s cluster 내부에 배포했기 때문에 k8s CA 인증서를 신뢰해서 체크해주지 않아도 됩니다.&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;다음은 Agent가 Jenkins와 통신하기 위한 정보입니다. Jenkins tunnel 같은 경우 Jenkins URL과 같은 host + port를 입력하면 되는데 port의 기본 값은 50000입니다.&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;저는 NodePort 서비스로 30050 포트를 50000 포트로 연결을 했기 때문에 30050을 입력해주겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1610&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M3EXk/dJMcagjzIV3/whWGBKGoxv4WAhC4GdzkBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M3EXk/dJMcagjzIV3/whWGBKGoxv4WAhC4GdzkBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M3EXk/dJMcagjzIV3/whWGBKGoxv4WAhC4GdzkBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM3EXk%2FdJMcagjzIV3%2FwhWGBKGoxv4WAhC4GdzkBK%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;1610&quot; height=&quot;846&quot; data-origin-width=&quot;1610&quot; data-origin-height=&quot;846&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;cluster 생성을 완료했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1857&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBAM7z/dJMcabCzgxM/QKCnu9EYofqgJMPltxzKw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBAM7z/dJMcabCzgxM/QKCnu9EYofqgJMPltxzKw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBAM7z/dJMcabCzgxM/QKCnu9EYofqgJMPltxzKw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBAM7z%2FdJMcabCzgxM%2FQKCnu9EYofqgJMPltxzKw1%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;1857&quot; height=&quot;403&quot; data-origin-width=&quot;1857&quot; data-origin-height=&quot;403&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;이제 간단하게 CI 파이프라인을 작성해보면서 Agent들이 제대로 생성되고 있는지 확인하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CI 파이프라인 작성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pipeline plugin을 설치합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1863&quot; data-origin-height=&quot;461&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzz6VG/dJMcain9VsZ/Rm6ck1a27YCEaep5XmpnIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzz6VG/dJMcain9VsZ/Rm6ck1a27YCEaep5XmpnIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzz6VG/dJMcain9VsZ/Rm6ck1a27YCEaep5XmpnIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbzz6VG%2FdJMcain9VsZ%2FRm6ck1a27YCEaep5XmpnIK%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;1863&quot; height=&quot;461&quot; data-origin-width=&quot;1863&quot; data-origin-height=&quot;461&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;New Item에서 Pipeline을 선택합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1857&quot; data-origin-height=&quot;1232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3OxsJ/dJMcag4WSQN/Oqm5gTqCvJdzKTRlvCjMp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3OxsJ/dJMcag4WSQN/Oqm5gTqCvJdzKTRlvCjMp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3OxsJ/dJMcag4WSQN/Oqm5gTqCvJdzKTRlvCjMp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3OxsJ%2FdJMcag4WSQN%2FOqm5gTqCvJdzKTRlvCjMp1%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;1857&quot; height=&quot;1232&quot; data-origin-width=&quot;1857&quot; data-origin-height=&quot;1232&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;다음 파이프라인 코드를 작성합니다. 간단하게 프로젝트 클론 후 maven으로 빌드와 테스트를 진행하는 파이프라인입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763263907772&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pipeline {
    agent {
        kubernetes {
            yaml &quot;&quot;&quot;
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: git
    image: alpine/git:latest
    command:
    - cat
    tty: true
  - name: maven
    image: maven:3.9.9-eclipse-temurin-17
    command:
    - cat
    tty: true
&quot;&quot;&quot;
        }
    }
    
    stages {
        stage('Clone Repository') {
            steps {
                container('git') {
                    sh 'git clone https://github.com/spring-petclinic/spring-framework-petclinic.git .'
                }
            }
        }
        
        stage('Build') {
            steps {
                container('maven') {
                    sh 'mvn clean package -DskipTests'
                }
            }
        }
        
        stage('Test') {
            steps {
                container('maven') {
                    sh 'mvn test'
                }
            }
        }
    }
}&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;1867&quot; data-origin-height=&quot;1270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJpLmf/dJMcagKD0xR/861joK710bEy2JrecvxrA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJpLmf/dJMcagKD0xR/861joK710bEy2JrecvxrA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJpLmf/dJMcagKD0xR/861joK710bEy2JrecvxrA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJpLmf%2FdJMcagKD0xR%2F861joK710bEy2JrecvxrA0%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;1867&quot; height=&quot;1270&quot; data-origin-width=&quot;1867&quot; data-origin-height=&quot;1270&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;지금 실행을 눌러 성공한다면 성공적으로 Jenkins와 k8s를 연동한 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1253&quot; data-origin-height=&quot;369&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xZMPM/dJMcab3DFg0/4fGW15Jm0UcWTw9oNNYFwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xZMPM/dJMcab3DFg0/4fGW15Jm0UcWTw9oNNYFwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xZMPM/dJMcab3DFg0/4fGW15Jm0UcWTw9oNNYFwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxZMPM%2FdJMcab3DFg0%2F4fGW15Jm0UcWTw9oNNYFwK%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;1253&quot; height=&quot;369&quot; data-origin-width=&quot;1253&quot; data-origin-height=&quot;369&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Jenkins Agent를 pod로 실행하는 원리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프라인을 실행할 때 로그를 보면 아래와 같이 pod의 manifest를 확인할 수 있습니다.&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;파이프라인에 정의해 둔 pod template을 기반으로 pod manifest를 생성하고 적용하는 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1763265116137&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;---
apiVersion: &quot;v1&quot;
kind: &quot;Pod&quot;
metadata:
  annotations:
    kubernetes.jenkins.io/last-refresh: &quot;1763265042559&quot;
    buildUrl: &quot;http://192.168.35.52:30080/job/test/11/&quot;
    runUrl: &quot;job/test/11/&quot;
  labels:
    jenkins: &quot;slave&quot;
    jenkins/label-digest: &quot;bb9dc6c737d64f21044ecb4dde4f90bfd6996c1e&quot;
    jenkins/label: &quot;test_11-8fl9l&quot;
    kubernetes.jenkins.io/controller: &quot;http___192_168_35_52_30080x&quot;
  name: &quot;test-11-8fl9l-dm97q-8gn8d&quot;
  namespace: &quot;jenkins&quot;
spec:
  containers:
  - command:
    - &quot;cat&quot;
    image: &quot;alpine/git:latest&quot;
    name: &quot;git&quot;
    tty: true
    volumeMounts:
    - mountPath: &quot;/home/jenkins/agent&quot;
      name: &quot;workspace-volume&quot;
      readOnly: false
  - command:
    - &quot;cat&quot;
    image: &quot;maven:3.9.9-eclipse-temurin-17&quot;
    name: &quot;maven&quot;
    tty: true
    volumeMounts:
    - mountPath: &quot;/home/jenkins/agent&quot;
      name: &quot;workspace-volume&quot;
      readOnly: false
  - env:
    - name: &quot;JENKINS_SECRET&quot;
      value: &quot;********&quot;
    - name: &quot;JENKINS_TUNNEL&quot;
      value: &quot;192.168.35.52:30050&quot;
    - name: &quot;JENKINS_AGENT_NAME&quot;
      value: &quot;test-11-8fl9l-dm97q-8gn8d&quot;
    - name: &quot;REMOTING_OPTS&quot;
      value: &quot;-noReconnectAfter 1d&quot;
    - name: &quot;JENKINS_NAME&quot;
      value: &quot;test-11-8fl9l-dm97q-8gn8d&quot;
    - name: &quot;JENKINS_AGENT_WORKDIR&quot;
      value: &quot;/home/jenkins/agent&quot;
    - name: &quot;JENKINS_URL&quot;
      value: &quot;http://192.168.35.52:30080/&quot;
    image: &quot;jenkins/inbound-agent:3345.v03dee9b_f88fc-1&quot;
    name: &quot;jnlp&quot;
    resources:
      requests:
        memory: &quot;256Mi&quot;
        cpu: &quot;100m&quot;
    volumeMounts:
    - mountPath: &quot;/home/jenkins/agent&quot;
      name: &quot;workspace-volume&quot;
      readOnly: false
  nodeSelector:
    kubernetes.io/os: &quot;linux&quot;
  restartPolicy: &quot;Never&quot;
  volumes:
  - emptyDir:
      medium: &quot;&quot;
    name: &quot;workspace-volume&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;jnlp 컨테이너&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;volume mount&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jnlp 컨테이너는 jnlp라는 프로토콜을 통해 Agent와 Controller 사이 통신을 담당합니다.&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;pod template에 정의하지 않아도 자동으로 생성됩니다.&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;또한 container 간 데이터 공유를 위해 Empty Dir Volume을 통해 pod가 실행되는 동안만 container 간 데이터를 공유합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVJYa0/dJMcaa4Jpze/ZPXWW9iA5eJBU7B6KIG6q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVJYa0/dJMcaa4Jpze/ZPXWW9iA5eJBU7B6KIG6q1/img.png&quot; data-alt=&quot;https://growth-coder.tistory.com/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVJYa0/dJMcaa4Jpze/ZPXWW9iA5eJBU7B6KIG6q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVJYa0%2FdJMcaa4Jpze%2FZPXWW9iA5eJBU7B6KIG6q1%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;423&quot; height=&quot;254&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://growth-coder.tistory.com/&lt;/figcaption&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;Jenkins pipeline을 보면 clone을 git container에서, 빌드는 maven container에서 하는 모습을 확인할 수 있습니다.&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;이 empty dir volume을 mount 했기 때문에 container가 종료되더라도 이전 작업이 사라지지 않는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;1386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMG8zh/dJMcabo193p/qiTkMKEvpb7CcKaUm4RMQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMG8zh/dJMcabo193p/qiTkMKEvpb7CcKaUm4RMQk/img.png&quot; data-alt=&quot;https://growth-coder.tistory.com/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMG8zh/dJMcabo193p/qiTkMKEvpb7CcKaUm4RMQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMG8zh%2FdJMcabo193p%2FqiTkMKEvpb7CcKaUm4RMQk%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;433&quot; height=&quot;450&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;1386&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://growth-coder.tistory.com/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Trouble Shooting&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jenkins와 k8s를 연동하면서 발생할 수 있는 문제 상황들과 해결 방법들을 정리해보았습니다.&lt;/p&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;pod is offline&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1079&quot; data-origin-height=&quot;1329&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ct8AAG/dJMcacapmeV/KpF37qDqYz6jn1mOM1ZOK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ct8AAG/dJMcacapmeV/KpF37qDqYz6jn1mOM1ZOK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ct8AAG/dJMcacapmeV/KpF37qDqYz6jn1mOM1ZOK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fct8AAG%2FdJMcacapmeV%2FKpF37qDqYz6jn1mOM1ZOK0%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;547&quot; height=&quot;674&quot; data-origin-width=&quot;1079&quot; data-origin-height=&quot;1329&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pod는 생성되었지만 offline인 상황은 Jenkins Controller와 Jenkins Agent가 통신하지 못 하는 상황입니다.&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;저 pod 부분에는 링크가 걸려있어 들어가보면 로컬 cli로 수동으로 연결을 시도할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uJ3RX/dJMcagKD1vL/ueDVrcbzciK92jaYzh0vUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uJ3RX/dJMcagKD1vL/ueDVrcbzciK92jaYzh0vUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uJ3RX/dJMcagKD1vL/ueDVrcbzciK92jaYzh0vUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuJ3RX%2FdJMcagKD1vL%2FueDVrcbzciK92jaYzh0vUK%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;727&quot; height=&quot;337&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;862&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 명령어느 Jenkins Agent를 생성하는 명령어입니다.&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;Jenkins Controller로부터 직접 Agent를 실행하는 jar 파일을 가져와서 Agent를 pod로 실행합니다.&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;명령어를 그대로 실행하면 Agent 즉, pod를 생성하는 모습을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYUzMz/dJMcaap7PLW/mdVikMvTbuzCS7xkczAjTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYUzMz/dJMcaap7PLW/mdVikMvTbuzCS7xkczAjTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYUzMz/dJMcaap7PLW/mdVikMvTbuzCS7xkczAjTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYUzMz%2FdJMcaap7PLW%2FmdVikMvTbuzCS7xkczAjTk%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;519&quot; height=&quot;310&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;570&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;그런데 pod가 생성되자마자 종료가 되는 경우가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;997&quot; data-origin-height=&quot;1164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3RJl3/dJMcabWSbRz/wJDjcJOoCX0NrkZb8CdMPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3RJl3/dJMcabWSbRz/wJDjcJOoCX0NrkZb8CdMPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3RJl3/dJMcabWSbRz/wJDjcJOoCX0NrkZb8CdMPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3RJl3%2FdJMcabWSbRz%2FwJDjcJOoCX0NrkZb8CdMPK%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;505&quot; height=&quot;590&quot; data-origin-width=&quot;997&quot; data-origin-height=&quot;1164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;Jenkins Agent가 자동으로 실행되지 않습니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;pod 생성은 되지만 종료됩니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 가지 상황을 고려하면 &lt;b&gt;Jenkins Controller와 Jenkins Agent가 통신하지 못 해 발생하는 문제&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;Cloud config에서 Jenkins tunnel이 누락되어 있는지 확인합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;171&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eu3KSg/dJMb995O9NR/hHW9pDT4t7r94hu2gKywU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eu3KSg/dJMb995O9NR/hHW9pDT4t7r94hu2gKywU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eu3KSg/dJMb995O9NR/hHW9pDT4t7r94hu2gKywU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feu3KSg%2FdJMb995O9NR%2FhHW9pDT4t7r94hu2gKywU0%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;857&quot; height=&quot;171&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;171&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;tunnel port의 기본 값은 50000이며 저는 NodePort로 30050을 container 내부 50000 포트와 연결을 해두어서 30050 포트를 사용했습니다.&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;해당 포트가 inbound traffic을 허용하는지도 확인합니다.&lt;/p&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;write 권한 에러&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java.nio.file.AccessDeniedException이나 permission denied 처럼 권한 에러는 대부분 디렉토리에 쓰기 권한이 없을 때 발생하는 에러입니다.&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;앞서 Jenkins Pipeline은 Empty Dir Volume을 각 container마다 mount하여 데이터를 공유한다고 배웠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dHEHjR/dJMcab3DGhR/KAByksXlyZeMj6y6scI39K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dHEHjR/dJMcab3DGhR/KAByksXlyZeMj6y6scI39K/img.png&quot; data-alt=&quot;https://growth-coder.tistory.com/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dHEHjR/dJMcab3DGhR/KAByksXlyZeMj6y6scI39K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdHEHjR%2FdJMcab3DGhR%2FKAByksXlyZeMj6y6scI39K%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;542&quot; height=&quot;563&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1330&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://growth-coder.tistory.com/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 여기서 container간 user id가 다를 경우 문제가 발생할 수 있습니다.&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;대부분 image를 실행할 때는 uid가 0, 즉 root가 default라서 큰 문제는 없지만 간혹 user id가 다를 수 있습니다.&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;예를 들어 container 1은 uid 0으로 실행되고 container 2는 uid가 1000으로 실행되었다고 합시다.&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;container 1이 생성한 디렉토리나 파일의 소유자는 root가 됩니다.&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;그런데 이 상황에서 directory의 소유자나 그룹이 아닌 사람에게는 write 연산을 허용하지 않는다면 어떨까요?&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;다음 container 2는 uid가 1000인 일반 유저로 실행되기 때문에 해당 디렉토리에 write 연산을 할 수 없게 됩니다.&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;이럴 때는 pod의 security context의 runAsUser를 활용하여 container 실행 유저를 특정 값으로 고정하여 해결할 수 있습니다.&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;Pipeline을 실행할 때 외에도 Jenkins를 설치할 때도 이러한 오류가 발생할 수 있으니 주의하시기 바랍니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Jenkins와 k8s를 연동하는 방법에 대해 알아보았습니다.&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;설정을 하면서 여러 가지 문제가 발생했는데 이로 인해 Jenkins와 k8s가 연동되는 구조에 대해 이해할 수 있었던 것 같습니다.&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;Jenkins와 k8s를 연동하시려는 분들께 도움이 되었으면 좋겠습니다.&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>공부/DevOps</category>
      <category>jenkins agent</category>
      <category>jenkins architecture</category>
      <category>jenkins java.nio.file.AccessDeniedException</category>
      <category>jenkins jnlp</category>
      <category>jenkins k8s</category>
      <category>jenkins kubernetes</category>
      <category>jenkins permission denied</category>
      <category>jenkins pod is offline</category>
      <category>JNLP</category>
      <category>JWs</category>
      <author>웅대</author>
      <guid isPermaLink="true">https://growth-coder.tistory.com/375</guid>
      <comments>https://growth-coder.tistory.com/375#entry375comment</comments>
      <pubDate>Sun, 16 Nov 2025 13:45:12 +0900</pubDate>
    </item>
    <item>
      <title>[k8s] Nginx Ingress Controller에서 ingress 설정에 base url을 추가하는 법</title>
      <link>https://growth-coder.tistory.com/374</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k8s에서 Ingress는 다양한 서비스 타입에게 트래픽을 전달할 때 자주 사용됩니다.&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;Ingress를 적용하기 위해서는 Ingress Controller가 사용되고 그 중 Nginx Ingress Controller를 통해 base url을 추가하는 방법에 대해 알아보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;base url이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 base url에 대해 알아봅시다.&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; &lt;a href=&quot;https://www.example.com&quot;&gt;https://www.example.com&lt;/a&gt;&lt;/b&gt;이라는 사이트가 있다면 처음 접속했을 때 다음과 같은 html 파일을 받는다고 합시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;764&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbhril/dJMcagRnzgl/K8maAE3Oi99wPOdEKAK7F1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbhril/dJMcagRnzgl/K8maAE3Oi99wPOdEKAK7F1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbhril/dJMcagRnzgl/K8maAE3Oi99wPOdEKAK7F1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdbhril%2FdJMcagRnzgl%2FK8maAE3Oi99wPOdEKAK7F1%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;290&quot; height=&quot;290&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;764&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;여기서 url의 주소가 &lt;b&gt;/static/image.png&lt;/b&gt;라면 html 파일을 렌더링 한 뒤 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;https://www.example.com/static/image.png&lt;/b&gt;로 요청을 보내 static 파일들을 다운받게 됩니다.&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;여기서 base url을 설정하게 될 경우 html 파일에 존재하는 여러 링크들에 prefix를 부여할 수 있습니다.&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;html 파일 상단에 다음과 같은 base tag를 작성해봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1762865863355&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;base href=&quot;/prefix/&quot;&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;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;/static/image.png 일 때 &lt;/b&gt;https://www.example.com/&lt;span style=&quot;color: #ee2323;&quot;&gt;prefix&lt;/span&gt;/static/image.png로 요청을 보내게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;base url의 필요성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이 base url은 언제 사용하면 좋을까요?&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;저는 Ingress Controller가 prefix 기반으로 트래픽을 라우팅 할 때 사용하고자 합니다.&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;k8s cluster 내부에서 argocd, nexus, harbor를 배포했을 때 서비스의 dashboard에 접근하기 위해 아래처럼 ingress 설정을 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;804&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ma5Uk/dJMcagqji07/txV7Ycr5XrHWpv5lA5catk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ma5Uk/dJMcagqji07/txV7Ycr5XrHWpv5lA5catk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ma5Uk/dJMcagqji07/txV7Ycr5XrHWpv5lA5catk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fma5Uk%2FdJMcagqji07%2FtxV7Ycr5XrHWpv5lA5catk%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;437&quot; height=&quot;274&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;804&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;일반적인 백엔드 서비스를 배포할 때는 굳이 base url을 사용할 필요는 없겠지만 위 서비스들 같은 경우 dashboard가 존재하기 때문에 단순히 ingress만 사용한다면 문제가 발생할 수 있습니다.&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;이전 ArgoCD를 배포할 때도 발생한 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://growth-coder.tistory.com/372&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://growth-coder.tistory.com/372&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1762866538694&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;[k8s] argoCD 배포 총 정리&quot; data-og-description=&quot;ArgoCD란?오늘은 k8s를 통해 서비스를 운영할 때 자동 배포 도구로 많이 사용하는 ArgoCD를 사용해보려고 합니다. ArgoCD란 k8s를 위한 선언적 GitOps 지속적 배포 도구입니다. Git 저장소에 정의된 애플리&quot; data-og-host=&quot;growth-coder.tistory.com&quot; data-og-source-url=&quot;https://growth-coder.tistory.com/372&quot; data-og-url=&quot;https://growth-coder.tistory.com/372&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/byyuid/hyZNqLml20/HKKxVyEWmP65KGD3uKPqFK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/3E00Q/hyZNfQGVOu/21HVOv70lNkxG0NmwF1kyK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bwnTuT/hyZNAlN9sB/pG6oIjGrUAnHM7F2xl4JR1/img.png?width=2643&amp;amp;height=296&amp;amp;face=0_0_2643_296&quot;&gt;&lt;a href=&quot;https://growth-coder.tistory.com/372&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://growth-coder.tistory.com/372&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/byyuid/hyZNqLml20/HKKxVyEWmP65KGD3uKPqFK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/3E00Q/hyZNfQGVOu/21HVOv70lNkxG0NmwF1kyK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bwnTuT/hyZNAlN9sB/pG6oIjGrUAnHM7F2xl4JR1/img.png?width=2643&amp;amp;height=296&amp;amp;face=0_0_2643_296');&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;[k8s] argoCD 배포 총 정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;ArgoCD란?오늘은 k8s를 통해 서비스를 운영할 때 자동 배포 도구로 많이 사용하는 ArgoCD를 사용해보려고 합니다. ArgoCD란 k8s를 위한 선언적 GitOps 지속적 배포 도구입니다. Git 저장소에 정의된 애플리&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;growth-coder.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;ingress prefix를 &quot;/argocd&quot;로 둘 경우 dashboard에 접근하기 위해 html 파일을 받았을 때 &quot;/static&quot;으로 시작하는 url의 경우 &quot;/argocd&quot;로 향하지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2520&quot; data-origin-height=&quot;3480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bINTXz/dJMcah3PaFv/iDQLQMdMjQ7uDujxKZqMHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bINTXz/dJMcah3PaFv/iDQLQMdMjQ7uDujxKZqMHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bINTXz/dJMcah3PaFv/iDQLQMdMjQ7uDujxKZqMHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbINTXz%2FdJMcah3PaFv%2FiDQLQMdMjQ7uDujxKZqMHk%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;617&quot; height=&quot;852&quot; data-origin-width=&quot;2520&quot; data-origin-height=&quot;3480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여기서 만약 index.html 파일에서 base url이 설정되어 있었다면 추가 요청들에 prefix가 붙어 argocd로 향하게 만들 수 있습니다.&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;2420&quot; data-origin-height=&quot;1600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZdrMK/dJMcaiBE02v/PIDnE6z4PuQjKI7pz4dIVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZdrMK/dJMcaiBE02v/PIDnE6z4PuQjKI7pz4dIVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZdrMK/dJMcaiBE02v/PIDnE6z4PuQjKI7pz4dIVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZdrMK%2FdJMcaiBE02v%2FPIDnE6z4PuQjKI7pz4dIVK%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;598&quot; height=&quot;395&quot; data-origin-width=&quot;2420&quot; data-origin-height=&quot;1600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;base url 설정 방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;base url을 설정하기 위해서는 전달받는 html 파일에 다음과 같은 base 태그를 추가해야 합니다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762867446480&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&amp;lt;base href=&quot;/prefix/&quot;&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;b&gt;즉, index.html의 내용을 동적으로 변경해야 하는데 이는 nginx의 sub_filter 기능을 사용하여 구현할 수 있습니다.&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;sub filter 기능은 문자열을 치환하는 기능으로 head 태그 뒤에 base 태그를 추가하는 방식으로 구현할 수 있습니다.&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;그러나 저희는 k8s 위에 nginx를 직접 배포하여 관리하는 것이 아니라 Nginx Ingress Controller를 사용해서 간접적으로 배포해야 합니다.&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;&amp;nbsp;이 때 nginx ingress에서 제공해주는 annotation을 사용하면 이 sub filter 기능을 구현할 수 있습니다.&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;http://nginx.ingress.kubernetes.io/add-base-url&lt;/b&gt;을 제공했기 때문에 쉽게 구현할 수 있었으나 0.22.0 버전 이후 deprecated 되었습니다.&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;다음 PR에서 deprecated 되었다는 정보를 확인할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/kubernetes/ingress-nginx/pull/3174&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/kubernetes/ingress-nginx/pull/3174&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1762867749575&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;Generalize Rewrite Block Creation and Deprecate AddBaseUrl (not backwards compatible)  by zrdaley &amp;middot; Pull Request #3174 &amp;middot; kuber&quot; data-og-description=&quot;What this PR does / why we need it: When the rewrite-target annotation is used and the written path uses regex and does not end in / (ex. foo/bar/.+), rewrite does not work as expected. This provid...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/kubernetes/ingress-nginx/pull/3174&quot; data-og-url=&quot;https://github.com/kubernetes/ingress-nginx/pull/3174&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/DmOLN/hyZMGBaiZz/XUwi5Q0kDFHKhPkKMe0KP0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bSq87D/hyZNma8fn2/7KhqQu09RwcTmOAJ8Aszh1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/kubernetes/ingress-nginx/pull/3174&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/kubernetes/ingress-nginx/pull/3174&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/DmOLN/hyZMGBaiZz/XUwi5Q0kDFHKhPkKMe0KP0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bSq87D/hyZNma8fn2/7KhqQu09RwcTmOAJ8Aszh1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;Generalize Rewrite Block Creation and Deprecate AddBaseUrl (not backwards compatible) by zrdaley &amp;middot; Pull Request #3174 &amp;middot; kuber&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;What this PR does / why we need it: When the rewrite-target annotation is used and the written path uses regex and does not end in / (ex. foo/bar/.+), rewrite does not work as expected. This provid...&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;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;add-base-url&lt;/b&gt; annotation이 아닌&lt;b&gt; configuration-snippet&lt;/b&gt; annotation을 활용하려고 합니다.&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;ingress Object를 생성하면 nginx ingress controller가 ingress를 감지하여 nginx.conf에 location block을 추가하게 됩니다.&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;configuration-snippet은 nginx ingress controller가 nginx.conf에 생성한 location block에 원하는 custom 설정을 추가할 수 있습니다.&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;즉, configuration-snippet을 사용하면 location block에 sub_filter를 적용하여 base 태그를 적용할 수 있게 됩니다.&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;다만 일반적으로 nginx ingress controller에서는 configuration-snippet을 허용하지 않습니다.&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;ingress 생성 권한을 가지고 있는 누군가가 마음대로 html을 변경하여 악의적인 행동을 방지하기 위함입니다.&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;그래서 이 configuration-snippet을 사용하기 위해서는 다음 설정이 필요합니다.&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;allow-snippet-annotation: &quot;true&quot;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;annotations-risk-level:&amp;nbsp;Critical&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;configuration-snippet을 사용하는 것은 위험하기 때문에 risk level을 올리고 snippet-annotation을 허용해야 합니다.&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;다음 명령어를 통해 config map을 가져옵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762868176221&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl get configmap ingress-nginx-controller -n ingress-nginx -o yaml &amp;gt; nginx-config.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;만약 이름과 namespace가 다르다면 수정합니다.&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;다음과 같이 data.allow-snippet-annotation과 data.annotations-risk-level을 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762868231119&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
data:
  allow-snippet-annotations: &quot;true&quot;
  annotations-risk-level: Critical
kind: ConfigMap
...&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 id=&quot;code_1762868257374&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl apply -f ./nginx-config.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;이제 ingress에 configuration-snippet을 추가할 수 있습니다.&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;argocd-ingress를 다음과 같이 작성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762868299650&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: argocd-ingress
  namespace: argocd
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/backend-protocol: &quot;HTTPS&quot;
    nginx.ingress.kubernetes.io/configuration-snippet: |
      sub_filter_types text/html;
      sub_filter '&amp;lt;base href=&quot;/&quot;&amp;gt;' '';
      sub_filter '&amp;lt;head&amp;gt;' '&amp;lt;head&amp;gt;&amp;lt;base href=&quot;/argocd/&quot;&amp;gt;';
      sub_filter_once on;
      proxy_set_header Accept-Encoding &quot;&quot;;
spec:
  ingressClassName: nginx
  rules:
  - host: example.com
    http:
      paths:
      - path: /argocd(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: argocd-server
            port:
              number: 443&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;configuration-snippet 설정은 다음과 같습니다.&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;sub_filter_types text/html:&lt;/b&gt; html에만 적용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;sub_filter '&amp;lt;base href=&quot;/&quot;&amp;gt;' '':&lt;/b&gt; base url이 설정되어 있다면 제거합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;sub_filter '&amp;lt;head&amp;gt;' '&amp;lt;head&amp;gt;&amp;lt;base href=&quot;/argocd/&quot;&amp;gt;':&lt;/b&gt; head 태그를 찾아 뒤에 base 태그를 추가합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;sub_filter_once on:&lt;/b&gt; 처음 찾은 head 태그에만 적용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;proxy_set_header Accept-Encoding &quot;&quot;:&lt;/b&gt; 혹시 압축이 되어있다면 패턴을 못 찾을 수 있기 때문에 압축이 되지 않도록 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 html 파일의 url에 prefix를 지정하여 ingress controller가 요청을 제대로 전달할 수 있게 되었습니다.&lt;/p&gt;</description>
      <category>configuration snippet</category>
      <category>ingress add-base-url</category>
      <category>ingress prefix</category>
      <category>nginx ingress add-base-url</category>
      <category>nginx ingress controller</category>
      <author>웅대</author>
      <guid isPermaLink="true">https://growth-coder.tistory.com/374</guid>
      <comments>https://growth-coder.tistory.com/374#entry374comment</comments>
      <pubDate>Tue, 11 Nov 2025 22:44:35 +0900</pubDate>
    </item>
    <item>
      <title>[ArgoCD] Helm-Charts를 통해 Harbor를 GitOps 방식으로 배포</title>
      <link>https://growth-coder.tistory.com/373</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;폐쇄망 환경에서 작업하다보면 인터넷에 접근할 수 없기 때문에 당연하게 생각했던 서비스들을 사용하지 못 하는 경우가 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;특히, 개발을 하다보면 이미 누군가가 구현해놓은 라이브러리나 패키지들을 활용하게 되는데 이러한 것들을 인터넷을 통해 다운받을 수 없게 될 뿐만 아니라 개발 결과물을 저장하기도 어렵게 됩니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그래서 폐쇄망 환경에서는 다양한 open source를 활용하여 폐쇄망 내부에 이러한 저장소를 직접 구축해야 합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그 중 대표적인 open source가&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Nexus repository&lt;/b&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Harbor registry&lt;/b&gt;가 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이번 포스팅에서는 이 중 Harbor registry저장소를 배포하기 위해 ArgoCd를 사용해보겠습니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ArgoCD의 경우 이전 포스팅을 통해 배포할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://growth-coder.tistory.com/372&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://growth-coder.tistory.com/372&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1762674790612&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;[k8s] argoCD 배포 총 정리&quot; data-og-description=&quot;ArgoCD란?오늘은 k8s를 통해 서비스를 운영할 때 자동 배포 도구로 많이 사용하는 ArgoCD를 사용해보려고 합니다. ArgoCD란 k8s를 위한 선언적 GitOps 지속적 배포 도구입니다. Git 저장소에 정의된 애플리&quot; data-og-host=&quot;growth-coder.tistory.com&quot; data-og-source-url=&quot;https://growth-coder.tistory.com/372&quot; data-og-url=&quot;https://growth-coder.tistory.com/372&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gzQCA/hyZNyg0Uxc/xj5PQHp76lCUEeoCxAdwe1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/yTaZX/hyZNDJoiXV/nnOI8bhOgmNKLAhvm8lwmK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/M3Dbc/hyZM4hVAQQ/4K7O4AkRvRZkVI9YIf1fv1/img.png?width=2643&amp;amp;height=296&amp;amp;face=0_0_2643_296&quot;&gt;&lt;a href=&quot;https://growth-coder.tistory.com/372&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://growth-coder.tistory.com/372&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gzQCA/hyZNyg0Uxc/xj5PQHp76lCUEeoCxAdwe1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/yTaZX/hyZNDJoiXV/nnOI8bhOgmNKLAhvm8lwmK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/M3Dbc/hyZM4hVAQQ/4K7O4AkRvRZkVI9YIf1fv1/img.png?width=2643&amp;amp;height=296&amp;amp;face=0_0_2643_296');&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;[k8s] argoCD 배포 총 정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;ArgoCD란?오늘은 k8s를 통해 서비스를 운영할 때 자동 배포 도구로 많이 사용하는 ArgoCD를 사용해보려고 합니다. ArgoCD란 k8s를 위한 선언적 GitOps 지속적 배포 도구입니다. Git 저장소에 정의된 애플리&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;growth-coder.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;물론 Harbor와 같이 중요 데이터를 보관하는 저장소 서비스를 k8s로 배포하는 것은 best practice가 아닙니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다만, 로컬 테스트 목적이기 때문에 ArgoCD를 활용해보겠습니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Harbor registry란?&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;harbor는 container image registry입니다. contaienr image를 안전하게 관리할 수 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;주로 docker image나 helm charts를 저장할 registry를 구축할 때 사용하는 open source입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;harbor registry의 architecture는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;975&quot; data-origin-height=&quot;483&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kG2DG/dJMcabbrwE3/GRymxVapcySRd8susH1Kp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kG2DG/dJMcabbrwE3/GRymxVapcySRd8susH1Kp0/img.png&quot; data-alt=&quot;https://nangman14.tistory.com/78&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kG2DG/dJMcabbrwE3/GRymxVapcySRd8susH1Kp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkG2DG%2FdJMcabbrwE3%2FGRymxVapcySRd8susH1Kp0%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;975&quot; height=&quot;483&quot; data-origin-width=&quot;975&quot; data-origin-height=&quot;483&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://nangman14.tistory.com/78&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 118px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 50.0%; height: 21px; text-align: justify;&quot;&gt;&lt;b&gt;harbor-core&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50.0%; height: 21px; text-align: justify;&quot;&gt;API 요청 및 GUI 제공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 50.0%; height: 21px; text-align: justify;&quot;&gt;&lt;b&gt;harbor-database&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50.0%; height: 21px; text-align: justify;&quot;&gt;사용자 정보를 저장하는 데이터베이스 (Postgres)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 50.0%; height: 21px; text-align: justify;&quot;&gt;&lt;b&gt;harbor-jobservice&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50.0%; height: 21px; text-align: justify;&quot;&gt;image 스캔, 복제와 같은 백그라운드 작업&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 50.0%; height: 21px; text-align: justify;&quot;&gt;&lt;b&gt;harbor-portal&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50.0%; height: 21px; text-align: justify;&quot;&gt;dashboard 제공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50.0%; height: 17px; text-align: justify;&quot;&gt;&lt;b&gt;harbor-redis&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50.0%; height: 17px; text-align: justify;&quot;&gt;캐싱 및 세션 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50.0%; height: 17px; text-align: justify;&quot;&gt;&lt;b&gt;harbor-registry&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50.0%; height: 17px; text-align: justify;&quot;&gt;docker registry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50.0%; text-align: justify;&quot;&gt;&lt;b&gt;harbor-trivy&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50.0%; text-align: justify;&quot;&gt;image 취약점 스캐너&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;helm charts를 통해 Harbor registry를 k8s 위에 배포해보겠습니다.&lt;/p&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;우선 저는 이전 포스팅 argoCD를 배포했을 때처럼 harbor의 helm-chart template을 기반으로 values를 override하는 방식으로 배포해보겠습니다.&lt;/p&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;harbor는 직접 배포하는 것이 아니라 ArgoCD를 사용해서 GitOps 방식으로 배포해보겠습니다.&lt;/p&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;먼저 Helm-Charts는 패키지 개념이기 때문에 ArgoCD UI에서 직접 Helm repository를 연동하는 방식으로 배포할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpPaAx/dJMcagqiGBG/iXmDvuVHgxX8znkXX6Bth1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpPaAx/dJMcagqiGBG/iXmDvuVHgxX8znkXX6Bth1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpPaAx/dJMcagqiGBG/iXmDvuVHgxX8znkXX6Bth1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpPaAx%2FdJMcagqiGBG%2FiXmDvuVHgxX8znkXX6Bth1%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;968&quot; height=&quot;324&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&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;ArgoCD가 helm repository를 바라보기 때문에 &lt;b&gt;GitOps 방식이 아닌 HelmOps 방식&lt;/b&gt;이라고 볼 수 있겠습니다.&lt;/p&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;그런데 문제는 values.yaml을 커스터마이징해서 git에서 관리하는 상태이고 이 값을 override하고 싶을 때입니다.&lt;/p&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;ArgoCD UI에서는 여러 개의 source를 지정할 수 없기 때문에 배포하기 위해서는 두 가지 방식을 사용해야 합니다.&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;커스터마이징한 values.yaml을 다시 패키징하여 Helm repository 올려 HelmOps 방식으로 배포한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Application Object를 정의한 manifest를 Git에 올려 GitOps 방식으로 배포한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 두 번째의 GitOps 방식을 사용해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Harbor Helm-Charts values.yaml 커스터마이징&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;배포에 앞서 values.yaml을 override하여 제가 원하는 설정을 진행해보겠습니다.&lt;/p&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;Helm-Charts는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://helm.goharbor.io/&quot;&gt;https://helm.goharbor.io/&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;repository의 harbor를 사용할 계획입니다.&lt;/p&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;helm repoistory를 등록합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762696839554&quot; class=&quot;cs&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;helm repo add harbor https://helm.goharbor.io&lt;/code&gt;&lt;/pre&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;다음 명령어를 통해 values.yaml 파일을 다운받습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762696839554&quot; class=&quot;maxima&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;helm show values harbor/harbor &amp;gt; harbor-values.yaml&lt;/code&gt;&lt;/pre&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;/p&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;/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;host 설정&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;영속화 비활성화&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;override를 위한 values.yaml의 이름은 override-harbor-values.yaml로 정하겠습니다.&lt;/p&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;b&gt;&amp;lt;override-harbor-values.yaml&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762697673389&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;expose:
  # Set how to expose the service. Set the type as &quot;ingress&quot;, &quot;clusterIP&quot;, &quot;nodePort&quot; or &quot;loadBalancer&quot;
  # and fill the information in the corresponding section
  type: ingress
  tls:
    # Enable TLS or not.
    # Delete the &quot;ssl-redirect&quot; annotations in &quot;expose.ingress.annotations&quot; when TLS is disabled and &quot;expose.type&quot; is &quot;ingress&quot;
    # Note: if the &quot;expose.type&quot; is &quot;ingress&quot; and TLS is disabled,
    # the port must be included in the command when pulling/pushing images.
    # Refer to https://github.com/goharbor/harbor/issues/5291 for details.
    enabled: true
    # The source of the tls certificate. Set as &quot;auto&quot;, &quot;secret&quot;
    # or &quot;none&quot; and fill the information in the corresponding section
    # 1) auto: generate the tls certificate automatically
    # 2) secret: read the tls certificate from the specified secret.
    # The tls certificate can be generated manually or by cert manager
    # 3) none: configure no tls certificate for the ingress. If the default
    # tls certificate is configured in the ingress controller, choose this option
    certSource: auto
    auto:
      # The common name used to generate the certificate, it's necessary
      # when the type isn't &quot;ingress&quot;
      commonName: &quot;&quot;
    secret:
      # The name of secret which contains keys named:
      # &quot;tls.crt&quot; - the certificate
      # &quot;tls.key&quot; - the private key
      secretName: &quot;&quot;
  ingress:
    hosts:
      # core: example.com
      core: 192.168.35.52.nip.io
    controller: default
    className: &quot;nginx&quot;
    annotations:
      nginx.ingress.kubernetes.io/ssl-redirect: &quot;true&quot;
      nginx.ingress.kubernetes.io/proxy-body-size: &quot;0&quot;
    labels: {}


# externalURL: https://example.com
externalURL: https://192.168.35.52.nip.io

persistence:
  enabled: false&lt;/code&gt;&lt;/pre&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;host는 &amp;lt;노드 IP&amp;gt;.nip.io를 사용하겠습니다.&lt;/p&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;nip.io는 ip주소 그대로 돌려주는 DNS 서비스라서 Local DNS에 등록하지 않고 쉽게 사용이 가능합니다.&lt;/p&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;&amp;nbsp;&lt;/p&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;Helm-Chart를 GitOps 방식으로 사용하려면 두 개의 source를 지정해야 합니다.&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;helm-chart가 존재하는 helm repostiory&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;override를 위한 values가 존재하는 git repository&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ArgoCD UI에서는 불가능하므로 다음과 같이 Application Object yaml 파일을 생성합니다.&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;&amp;lt;harbor.yaml&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762695449848&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-harbor-registry # Argo CD에 표시될 애플리케이션 이름
  namespace: argocd # Argo CD가 설치된 네임스페이스
spec:
  project: default
  destination:
    namespace: harbor # Harbor가 배포될 네임스페이스
    server: https://kubernetes.default.svc # 배포 대상 클러스터 (자체 클러스터)
  
  sources:
  # 1. Harbor 공식 Helm Chart (첫 번째 소스)
  - repoURL: https://helm.goharbor.io # Helm repository 주소
    chart: harbor
    targetRevision: 1.18.0 # 사용할 Harbor Chart 버전
    helm:
      releaseName: harbor # Helm 릴리스 이름
      # Git에서 가져온 override 파일을 값으로 사용하도록 지정
      valueFiles:
      - $values/harbor/override-harbor-values.yaml 
  
  # 2. 오버라이드 Values 파일이 담긴 Git 저장소 (두 번째 소스)
  - repoURL: git@github.com:ezcolin2/argocd-test.git # 커스터마이징한 values.yaml이 존재하는 Git repository
    targetRevision: main # Git 브랜치
    ref: values # 이 소스에 'values'라는 참조를 부여&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;그리고 위 Application Object는 커스터마이징한 values.yaml 파일과 동일한 위치의 git repository에 생성했습니다.&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;git repository 구조는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1762695668953&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.
└── harbor
    ├── harbor.yaml
    └── override-harbor-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;다음과 같이 ArgoCD가 이 git repository의 harbor directory를 가리키도록 설정하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;1248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d5XZgR/dJMcajgf2Pf/4ZtTa2mOAcKTIEA95SXUVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d5XZgR/dJMcajgf2Pf/4ZtTa2mOAcKTIEA95SXUVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d5XZgR/dJMcajgf2Pf/4ZtTa2mOAcKTIEA95SXUVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd5XZgR%2FdJMcajgf2Pf%2F4ZtTa2mOAcKTIEA95SXUVk%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;984&quot; height=&quot;1248&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;1248&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;혹시 override-harbor-values.yaml도 인식하지 않을까하는 걱정은 하지 않으셔도 됩니다.&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;유효한 k8s manifest 파일만 인식하기 때문에 harbor.yaml만 인식하고 이 안에 적힌대로 override-harbor-values.yaml을 참조하여 values.yaml을 override하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7dfxU/dJMcafrn5Qc/RFz5Bwr1yrsE421TGDKAO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7dfxU/dJMcafrn5Qc/RFz5Bwr1yrsE421TGDKAO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7dfxU/dJMcafrn5Qc/RFz5Bwr1yrsE421TGDKAO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7dfxU%2FdJMcafrn5Qc%2FRFz5Bwr1yrsE421TGDKAO1%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;964&quot; height=&quot;309&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;309&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;ArgoCD repository 생성&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선 저는 github에 private repository를 만들었고 이 repository와 ArgoCD를 연동할 것입니다.&lt;/p&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;다양한 방식으로 private repository와 연동할 수 있는데 ssh 방식을 사용해보겠습니다.&lt;/p&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;우선 ssh key 쌍을 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762693346917&quot; class=&quot;cmake&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;ssh-keygen -f argo-test&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1762693346917&quot; class=&quot;gherkin&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again:
Your identification has been saved in private-repo
Your public key has been saved in private-repo.pub
The key fingerprint is:
SHA256:zsfBsuwp0H6KsPOcUiqdU6friIKrkUQ2gQwbrHVqDac user@DESKTOP-1CRV5S2
The key's randomart image is:
+---[RSA 3072]----+
|B.               |
|.=+ o            |
|o= B             |
|+ E .    .       |
| o   .  S o      |
|..  + o+ + .     |
|+..= =  = o      |
|+oB=.oo..o       |
|*oo*B..+o        |
+----[SHA256]-----+&lt;/code&gt;&lt;/pre&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;key 쌍이 생성이 되었다면 public key는 github에, private key는 ArgoCD에 등록합니다.&lt;/p&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;private repository settings -&amp;gt; Deploy keys -&amp;gt; Add deploy key로 접속해서 public key를 등록합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1742&quot; data-origin-height=&quot;1117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEVnTF/dJMcahJvHCi/WZU8YPT8StZ80uGknklDM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEVnTF/dJMcahJvHCi/WZU8YPT8StZ80uGknklDM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEVnTF/dJMcahJvHCi/WZU8YPT8StZ80uGknklDM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEVnTF%2FdJMcahJvHCi%2FWZU8YPT8StZ80uGknklDM1%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;1742&quot; height=&quot;1117&quot; data-origin-width=&quot;1742&quot; data-origin-height=&quot;1117&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&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;이제 ArgoCD의 settings -&amp;gt; Repositories로 들어갑니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1253&quot; data-origin-height=&quot;849&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nsB89/dJMcafx9XjR/CeBdH9bkOZ1jLcxJcp3Wg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nsB89/dJMcafx9XjR/CeBdH9bkOZ1jLcxJcp3Wg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nsB89/dJMcafx9XjR/CeBdH9bkOZ1jLcxJcp3Wg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnsB89%2FdJMcafx9XjR%2FCeBdH9bkOZ1jLcxJcp3Wg0%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;1253&quot; height=&quot;849&quot; data-origin-width=&quot;1253&quot; data-origin-height=&quot;849&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&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;connect repo로 들어갑니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;537&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCLRoL/dJMcabCwEnR/N6xCtJCI3qjk5lMOVFilLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCLRoL/dJMcabCwEnR/N6xCtJCI3qjk5lMOVFilLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCLRoL/dJMcabCwEnR/N6xCtJCI3qjk5lMOVFilLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCLRoL%2FdJMcabCwEnR%2FN6xCtJCI3qjk5lMOVFilLK%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;1260&quot; height=&quot;537&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;537&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&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;ssh key 방식으로 바꾸고 private repository의 ssh 주소를 입력합니다.&lt;/p&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;b&gt;HTTP/HTTPS repository 주소가 아닌 SSH repository 주소를 입력해야 합니다.&lt;/b&gt;&lt;/p&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;private key를 입력합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;899&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs4V52/dJMcaiuSRyJ/V81KHmYNCxdiVVSp3KMoU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs4V52/dJMcaiuSRyJ/V81KHmYNCxdiVVSp3KMoU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs4V52/dJMcaiuSRyJ/V81KHmYNCxdiVVSp3KMoU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs4V52%2FdJMcaiuSRyJ%2FV81KHmYNCxdiVVSp3KMoU1%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;977&quot; height=&quot;899&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;899&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&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;아래처럼 connection status가 Successful이면 성공합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1891&quot; data-origin-height=&quot;444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H7ryW/dJMcagX8FLM/7kKirFWJjayR5I9zN5W5Nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H7ryW/dJMcagX8FLM/7kKirFWJjayR5I9zN5W5Nk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H7ryW/dJMcagX8FLM/7kKirFWJjayR5I9zN5W5Nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH7ryW%2FdJMcagX8FLM%2F7kKirFWJjayR5I9zN5W5Nk%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;1891&quot; height=&quot;444&quot; data-origin-width=&quot;1891&quot; data-origin-height=&quot;444&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&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;b&gt;참고로 git repository에 k8s manifest 파일이 하나도 없다면 연결에 실패하기 때문에 먼저 k8s manifest를 생성하고 repository 연결을 진행해야 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h2&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;a href=&quot;https://192.168.35.52.nip.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://192.168.35.52.nip.io/&lt;/a&gt;를 host로 설정했고 이전 포스팅에서 배포했던 nginx ingress controller의 NodePort는 30443이기 때문에 &lt;a href=&quot;https://192.168.35.52.nip.io:30443/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://192.168.35.52.nip.io:30443/&lt;/a&gt;으로 접근하면 dashboard에 접근이 가능해야 합니다.&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;default admin 비밀번호는 Harbor12345이므로 접속해봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1906&quot; data-origin-height=&quot;1031&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJHbZL/dJMcacVJNMb/poIkdNukkNQNxSY5CFrek1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJHbZL/dJMcacVJNMb/poIkdNukkNQNxSY5CFrek1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJHbZL/dJMcacVJNMb/poIkdNukkNQNxSY5CFrek1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJHbZL%2FdJMcacVJNMb%2FpoIkdNukkNQNxSY5CFrek1%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;1906&quot; height=&quot;1031&quot; data-origin-width=&quot;1906&quot; data-origin-height=&quot;1031&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;Harbor registry에 image를 push 하는 명령어는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YEYDL/dJMcac2vnVR/WYxeyPyGkJp8zvCKRZHkS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YEYDL/dJMcac2vnVR/WYxeyPyGkJp8zvCKRZHkS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YEYDL/dJMcac2vnVR/WYxeyPyGkJp8zvCKRZHkS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYEYDL%2FdJMcac2vnVR%2FWYxeyPyGkJp8zvCKRZHkS1%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;543&quot; height=&quot;596&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;964&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;하지만 주의해야 할 점은 이전 포스팅대로 진행했다면 Nginx Ingress Controller가 NodePort 타입의 서비스로 되어 있습니다.&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;그렇기 때문에 docker tag를 설정할 때, 192.168.35.52.nip.io:30443으로 설정을 하셔야 합니다.&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;물론 NodePort 타입이 아니라면 굳이 포트를 붙이지 않아도 됩니다.&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;그리고 인증서를 신뢰하지 않기 때문에 Harbor의 CA 인증서를 신뢰하게 설정을 하거나 insecure-registry 설정을 하면 됩니다.&lt;/p&gt;</description>
      <category>공부/DevOps</category>
      <category>harbor gitops</category>
      <category>harbor helm charts</category>
      <category>harbor helm chart로 배포</category>
      <author>웅대</author>
      <guid isPermaLink="true">https://growth-coder.tistory.com/373</guid>
      <comments>https://growth-coder.tistory.com/373#entry373comment</comments>
      <pubDate>Mon, 10 Nov 2025 00:31:15 +0900</pubDate>
    </item>
    <item>
      <title>[k8s] argoCD 배포 총 정리</title>
      <link>https://growth-coder.tistory.com/372</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;ArgoCD란?&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 k8s를 통해 서비스를 운영할 때 자동 배포 도구로 많이 사용하는 ArgoCD를 사용해보려고 합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;ArgoCD란 k8s를 위한 선언적 GitOps 지속적 배포 도구입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Git 저장소에 정의된 애플리케이션 상태를 그대로 반영하는 것이 ArgoCD입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;실행 중인 애플리케이션을 지속적으로 모니터링하고 현재 상태와 Git에 저장된 상태를 비교하며 상태를 동기화 합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;ArgoCD의 아키텍처는 다음과 같습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&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;815&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vnx9W/dJMcadmNfJq/i6RSXoiPAQlpwwtF01JlN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vnx9W/dJMcadmNfJq/i6RSXoiPAQlpwwtF01JlN1/img.png&quot; data-alt=&quot;https://www.inflearn.com/blogs/6373?srsltid=AfmBOor1BcPxnTVJx3A_vyjFP4lf4b5MgRJKPpgeYnUKHOk8AkzMeBa5&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vnx9W/dJMcadmNfJq/i6RSXoiPAQlpwwtF01JlN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvnx9W%2FdJMcadmNfJq%2Fi6RSXoiPAQlpwwtF01JlN1%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;815&quot; height=&quot;436&quot; data-origin-width=&quot;815&quot; data-origin-height=&quot;436&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.inflearn.com/blogs/6373?srsltid=AfmBOor1BcPxnTVJx3A_vyjFP4lf4b5MgRJKPpgeYnUKHOk8AkzMeBa5&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;각 컴포넌트의 역할은 다음과 같습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 76.0467%; height: 133px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 22.1839%; height: 19px; text-align: left;&quot;&gt;&lt;b&gt;Server&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.9885%; height: 19px; text-align: left;&quot;&gt;API Server 및 dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 22.1839%; height: 19px; text-align: left;&quot;&gt;&lt;b&gt;Repo Server&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.9885%; height: 19px; text-align: left;&quot;&gt;Git의 yaml 파일을 바탕으로 yaml manifest 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 22.1839%; height: 19px; text-align: left;&quot;&gt;&lt;b&gt;Application Controller&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.9885%; height: 19px; text-align: left;&quot;&gt;k8s 리소스 모니터링 및 변경 사항 발생 시 Git의 내용을 k8s에 반영&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 22.1839%; height: 21px; text-align: left;&quot;&gt;&lt;b&gt;Notification&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.9885%; height: 21px; text-align: left;&quot;&gt;ArgoCD에서 발생하는 이벤트를 외부로 트리거&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 22.1839%; height: 19px; text-align: left;&quot;&gt;&lt;b&gt;Redis&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.9885%; height: 19px; text-align: left;&quot;&gt;Github와 kube--apiserver와의 통신에 캐시로 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 22.1839%; height: 19px; text-align: left;&quot;&gt;&lt;b&gt;ApplicationSet Controller&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.9885%; height: 19px; text-align: left;&quot;&gt;멀티 클러스터를 위한 App 패키징 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 22.1839%; height: 17px; text-align: left;&quot;&gt;&lt;b&gt;dex&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.9885%; height: 17px; text-align: left;&quot;&gt;외부 인증 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;ArgoCD는 대부분 statelss 방식입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;모든 데이터는 k8s object로 저장되고 k8s object는 k8s의 etcd에 저장됩니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;redis에 데이터를 저장하기는 하지만 redis는 캐싱 용도이기 때문에 손실되더라도 큰 상관이 없습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;ArgoCD는 k8s 전용 배포 툴이고 Git과 연동되기 때문에 설치하기 위해 k8s cluster가 구축되어 있어야 하고 Git 저장소가 존재해야 합니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;ArgoCD 배포 1 - NodePort 방식&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ArgoCD를 설치하는 방법은 굉장히 단순합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;k8s cluster에서 아래 명령어를 입력하면 됩니다. yaml 파일에 ArgoCD에 필요한 모든 Object가 작성되어 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762662465140&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;다음 명령어를 통해 component들이 잘 배포되었나 확인합니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1762656583633&quot; class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl get po -n argocd -o wide&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;2102&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chSDuQ/dJMcabJhsN8/o0gwjKOK68VuDaISIOlPWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chSDuQ/dJMcabJhsN8/o0gwjKOK68VuDaISIOlPWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chSDuQ/dJMcabJhsN8/o0gwjKOK68VuDaISIOlPWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchSDuQ%2FdJMcabJhsN8%2Fo0gwjKOK68VuDaISIOlPWK%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;2102&quot; height=&quot;234&quot; data-origin-width=&quot;2102&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;위에서 보았듯이 이 중 arcocd-server가 dashboard 역할을 하고 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이 argocd-server에 접근하려면 가장 단순하게는 NodePort Service를 사용해서 포트를 뚫으면 됩니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;linux 기준으로 다음 명령어를 입력하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&amp;lt;node ip :30080&amp;gt;&lt;/b&gt;으로 접근할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762656583634&quot; class=&quot;scilab&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl patch svc argocd-server -n argocd --type='json' -p='[{&quot;op&quot;: &quot;replace&quot;, &quot;path&quot;: &quot;/spec/type&quot;, &quot;value&quot;: &quot;NodePort&quot;}, {&quot;op&quot;: &quot;replace&quot;, &quot;path&quot;: &quot;/spec/ports/0/nodePort&quot;, &quot;value&quot;: 30080}]'&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;다음 명령어로 argocd-server Service 타입이 NodePort로 변경되었는지 확인합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762656583635&quot; class=&quot;armasm&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl get svc -n argocd -o wide&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;2643&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kGhxx/dJMcagqhNg9/DYi9qiKrvAMVgQ6I03BoE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kGhxx/dJMcagqhNg9/DYi9qiKrvAMVgQ6I03BoE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kGhxx/dJMcagqhNg9/DYi9qiKrvAMVgQ6I03BoE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkGhxx%2FdJMcagqhNg9%2FDYi9qiKrvAMVgQ6I03BoE1%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;2643&quot; height=&quot;296&quot; data-origin-width=&quot;2643&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&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;이제 &amp;lt;Node IP : Node Port&amp;gt;를 통해 argoCD dashboard에 접근할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3813&quot; data-origin-height=&quot;1889&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMemwe/dJMcad8arVU/ioaMzykh2BAc8fdLRK22d1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMemwe/dJMcad8arVU/ioaMzykh2BAc8fdLRK22d1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMemwe/dJMcad8arVU/ioaMzykh2BAc8fdLRK22d1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMemwe%2FdJMcad8arVU%2FioaMzykh2BAc8fdLRK22d1%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;692&quot; height=&quot;343&quot; data-origin-width=&quot;3813&quot; data-origin-height=&quot;1889&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&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;초기 admin 비밀번호는 다음 명령어로 알아낼 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762673843082&quot; class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath=&quot;{.data.password}&quot; | base64 -d; echo&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;dashboard에 로그인 하여 비밀번호를 변경합니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;ArgoCD 배포 2 - Ingress방식&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 NodePort 방식을 사용하지 않고 k8s object인 Ingress를 사용해보겠습니다.&lt;/p&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;NodePort 방식을 사용하게 되면 argoCd가 배포되어 있는 Node의 주소와 뚫려있는 포트 번호를 알아야 합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Ingress를 활용하면 요청을 Ingress Controller로 보내고 Ingress Controller가 트래픽을 라우팅하게 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;329&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bv6Pka/dJMcacIcAXs/TBklPveiuPkkKc2CLH2Bbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bv6Pka/dJMcacIcAXs/TBklPveiuPkkKc2CLH2Bbk/img.png&quot; data-alt=&quot;https://kubernetes.io/ko/docs/concepts/services-networking/ingress/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bv6Pka/dJMcacIcAXs/TBklPveiuPkkKc2CLH2Bbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbv6Pka%2FdJMcacIcAXs%2FTBklPveiuPkkKc2CLH2Bbk%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;1125&quot; height=&quot;329&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;329&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://kubernetes.io/ko/docs/concepts/services-networking/ingress/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;Ingress를 활용하면 단일 IP 주소로 모든 요청을 받을 수 있고 Node의 IP와 Port 정보를 외부에 노출하지 않기 때문에 보안 상 좋습니다.&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;여기서 Ingress는 k8s object이고 Ingress Controller는 Ingress에 정의한 규칙을 기반으로 라우팅을 수행하는 주체입니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. nginx-ingress-controller 배포&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ingress를 적용하기 전에 ingress controller를 배포해야 하는데 다음 명령어를 통해 Nginx Ingress Controller를 배포해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762658897434&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -O https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.14.0/deploy/static/provider/cloud/deploy.yaml
kubectl apply -f ./deploy.yaml&lt;/code&gt;&lt;/pre&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;버전 정보를 바꾸고 싶다면 url의 버전 정보를 바꾸면 되는데 아래 주소에서 최신 버전 정보를 확인할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/kubernetes/ingress-nginx&quot;&gt;https://github.com/kubernetes/ingress-nginx&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. NodePort Service로 변경&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그런데 위 nginx ingress controller는 기본적으로 Service Object들이 LoadBalancer type으로 세팅되어 있습니다.&lt;/p&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;저는 준비된 Load Balancer가 없기 때문에 NodePort 타입으로 바꿔보겠습니다.&lt;/p&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;patch 명령어로 바로 바꿀 수도 있지만 저는 원본 파일을 유지하고 원본에서 어떠한 부분만 변경이 되었는지 확인하고 싶기 때문에 override.yaml 파일을 다음과 같이 따로 만들어보겠습니다.&lt;/p&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;b&gt;&amp;lt;override.yaml&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762660220692&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.14.0
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  externalTrafficPolicy: Local
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - appProtocol: http
    name: http
    port: 80
    protocol: TCP
    targetPort: http
    nodePort: 30080
  - appProtocol: https
    name: https
    port: 443
    protocol: TCP
    targetPort: https
    nodePort: 30443
  selector:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  type: NodePort&lt;/code&gt;&lt;/pre&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;ingress-nginx-controller Service만 NodePort 타입으로 바꾸고 port 번호를 고정해주었습니다.&lt;/p&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;이 override.yaml도 apply 명령어로 적용해줍니다.&lt;/p&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;/p&gt;
&lt;pre id=&quot;code_1762659876824&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl get all -n ingress-nginx&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1762660312405&quot; class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;NAME                                           READY   STATUS    RESTARTS   AGE
pod/ingress-nginx-controller-cc68b44bd-j8d5s   1/1     Running   0          13m

NAME                                         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
service/ingress-nginx-controller             NodePort    10.104.39.83    &amp;lt;none&amp;gt;        80:30080/TCP,443:30443/TCP   13m
service/ingress-nginx-controller-admission   ClusterIP   10.110.50.119   &amp;lt;none&amp;gt;        443/TCP                      13m

NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ingress-nginx-controller   0/1     1            0           13m

NAME                                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/ingress-nginx-controller-cc68b44bd   1         1         0       13m&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. argocd ingress 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ingress 적용하기 전의 기본 세팅을 마무리 했습니다. 이제 argoCD로 트래픽을 향하게 하기 위한 ingress object를 다음과 같이 정의합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762660600214&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: argocd-ingress
  namespace: argocd
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/backend-protocol: &quot;HTTPS&quot;

spec:
  ingressClassName: nginx
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: argocd-server
            port:
              number: 443&lt;/code&gt;&lt;/pre&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;/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;ingress는 argocd name space에 생성한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;nginx라는 ingress controller에게 ingress를 적용한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ingress controller가 pod와 통신할 때 프로토콜은 https를 사용한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;example.com/&quot;로 요청이 들어왔을 때 argocd-service라는 이름의 service의 443 포트로 트래픽을 전달한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. Local DNS 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 host에 example.com이 있는데 실제 도메인을 가지고 있지 않기 때문에 local pc의 hosts 파일을 수정해서 로컬 DNS를 정의해보겠습니다.&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;저는 nginx ingress controller pod가 띄워진 노드의 주소가 192.168.35.52이기 때문에 다음과 같이 정의하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762660757614&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
192.168.35.52 example.com
...&lt;/code&gt;&lt;/pre&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;5. argoCD 접속&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 흐름은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3376&quot; data-origin-height=&quot;1084&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qH5ry/dJMcajAyCXN/khGnSrB0shTzFr1ixQQ3t1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qH5ry/dJMcajAyCXN/khGnSrB0shTzFr1ixQQ3t1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qH5ry/dJMcajAyCXN/khGnSrB0shTzFr1ixQQ3t1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqH5ry%2FdJMcajAyCXN%2FkhGnSrB0shTzFr1ixQQ3t1%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;836&quot; height=&quot;268&quot; data-origin-width=&quot;3376&quot; data-origin-height=&quot;1084&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;a href=&quot;https://example.com:30443으로&quot;&gt;https://example.com:30443&lt;/a&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; 고급 -&amp;gt; example.com(안전하지 않음)&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;1201&quot; data-origin-height=&quot;977&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ofKxQ/dJMcaiazVJQ/TK8W7DkkUXKeMp67oPa9w0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ofKxQ/dJMcaiazVJQ/TK8W7DkkUXKeMp67oPa9w0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ofKxQ/dJMcaiazVJQ/TK8W7DkkUXKeMp67oPa9w0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FofKxQ%2FdJMcaiazVJQ%2FTK8W7DkkUXKeMp67oPa9w0%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;488&quot; height=&quot;397&quot; data-origin-width=&quot;1201&quot; data-origin-height=&quot;977&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다음과 같이 argoCD dashboard에 접근할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3813&quot; data-origin-height=&quot;1889&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMemwe/dJMcad8arVU/ioaMzykh2BAc8fdLRK22d1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMemwe/dJMcad8arVU/ioaMzykh2BAc8fdLRK22d1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMemwe/dJMcad8arVU/ioaMzykh2BAc8fdLRK22d1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMemwe%2FdJMcad8arVU%2FioaMzykh2BAc8fdLRK22d1%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;692&quot; height=&quot;343&quot; data-origin-width=&quot;3813&quot; data-origin-height=&quot;1889&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&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;참고로 지금은 prefix가 &quot;/&quot;로 정의되어 있는데 &quot;/argocd&quot;로 변경할 경우 문제가 발생할 수 있습니다.&lt;/p&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 data-ke-size=&quot;size16&quot;&gt;바로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;argoCD dashboard Frontend의 API 요청&lt;/b&gt;&lt;span&gt;&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;우선 처음에 /argocd로 요청을 보내면 argocd-server Service로 요청이 전달이 되고 dashboard의 html 파일을 받고 여기서 추가적인 정적 파일들을 요청하게 됩니다.&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;그런데 이 때 정적 파일들을 요청할 때는 &quot;/argocd&quot;가 아닌 &quot;/&quot; 경로로 요청을 보내게 됩니다.&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;당연히 prefix를 &quot;/argocd&quot;로 정의하면 &quot;/&quot;로 들어오는 요청은 argocd에게 도달하지 못 합니다.&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;그래서 &quot;/argocd&quot;로 prefix를 정의하고 접속해보면 아래와 같이 정적 파일들을 찾지 못 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXU52T/dJMcaiuSLy3/Xt5uQT0AsRHKvUKBro6f2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXU52T/dJMcaiuSLy3/Xt5uQT0AsRHKvUKBro6f2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXU52T/dJMcaiuSLy3/Xt5uQT0AsRHKvUKBro6f2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXU52T%2FdJMcaiuSLy3%2FXt5uQT0AsRHKvUKBro6f2k%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;825&quot; height=&quot;378&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;378&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;404 에러가 뜬 요청을 확인해보면 아래와 같이 &quot;/argocd&quot; prefix가 없는 모습을 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762671229822&quot; class=&quot;awk&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;https://example.com:30443/assets/fonts.css&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;이 문제를 해결하기 위해서는 rootpath 옵션을 통해 root path 자체를 &quot;/argocd&quot;로 바꿔줘야 합니다.&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;ArgoCD 배포 3 - Helm 방식&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 살펴본 두 가지 방식은 모두 아래 주소에 존재하는 k8s manifest를 사용했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 파일을 살펴보면 하나의 파일 안에 모든 k8s object들이 정의되어 있는 모습을 확인할 수 있습니다.&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;이 파일 하나를 사용하고 override 하는 방식으로 argocd를 사용해도 되지만 이번에는 helm을 통해 패키지 관리를 해보겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;helm이란?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;helm&lt;/b&gt;은 k8s 애플리케이션 패키지 관리 도구입니다.&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;그리고 k8s 애플리케이션의 다양한 Object들을 쉽게 배포하기 위해 패키징을 한 것이 바로&lt;b&gt; helm chart&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p 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;helm charts는 k8s 리소스와 관련된 set이 정의된 파일의 모음으로 다음과 같은 디렉토리 안에 파일들이 존재합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762674671153&quot; class=&quot;1c&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;wordpress/
&amp;nbsp;&amp;nbsp;Chart.yaml&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 차트에 대한 정보를 가진 YAML 파일
&amp;nbsp;&amp;nbsp;LICENSE&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; # 옵션: 차트의 라이센스 정보를 가진 텍스트 파일
&amp;nbsp;&amp;nbsp;README.md&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; # 옵션: README 파일
&amp;nbsp;&amp;nbsp;values.yaml&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; # 차트에 대한 기본 환경설정 값들
&amp;nbsp;&amp;nbsp;values.schema.json&amp;nbsp;&amp;nbsp;# 옵션: values.yaml 파일의 구조를 제약하는 JSON 파일
&amp;nbsp;&amp;nbsp;charts/&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; # 이 차트에 종속된 차트들을 포함하는 디렉터리
&amp;nbsp;&amp;nbsp;crds/&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; # 커스텀 자원에 대한 정의
&amp;nbsp;&amp;nbsp;templates/&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# values와 결합될 때, 유효한 쿠버네티스 manifest 파일들이 생성될 템플릿들의 디렉터리
&amp;nbsp;&amp;nbsp;templates/NOTES.txt # 옵션: 간단한 사용법을 포함하는 텍스트 파일&lt;/code&gt;&lt;/pre&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;APT와 같은 패키지 관리 도구를 사용해서 linux package를 설치&lt;/b&gt;하거나&lt;b&gt; npm과 같은 패키지 관리 도구를 사용해서 node package를 설치&lt;/b&gt;하는 것처럼&lt;b&gt; helm이라는 패키지 관리 도구를 사용해서 k8s object package를 설치&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;helm-chart를 사용할 때 장점은 기존 template을 기반으로 &lt;b&gt;value만 수정을 해가면서 쉽게 애플리케이션을 배포할 수 있다는 점&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;위에서 사용했던 argoCd manifest 파일은 하나의 YAML 파일로 이루어져 있었고 커스터마이징을 위해 코드를 분석할 시간이 필요합니다.&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;즉, manifest 내부에 대한 이해가 필요합니다.&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;그런데 argoCD helm-chart의 경우 이미 template이 존재하기 때문에&lt;b&gt; template의 세세한 설정을 몰라도&amp;nbsp;values만 변경하여 쉽게 원하는 설정을 적용&lt;/b&gt;할 수 있습니다.&lt;/p&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;helm 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 helm을 사용해서 Nexus repository와 Harbor registry를 배포할 것이기 때문에 먼저 helm을 설치합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;아래 공식 문서에서 helm을 설치하는 다양한 방식을 확인할 수 있습니다.&lt;br /&gt;&lt;a href=&quot;https://helm.sh/ko/docs/intro/install&quot;&gt;https://helm.sh/ko/docs/intro/install&lt;/a&gt;&lt;/p&gt;
&lt;figure style=&quot;color: #333333; text-align: start;&quot; data-ke-type=&quot;opengraph&quot; data-og-title=&quot;헬름 설치하기 | Helm&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;헬름 설치하고 작동하는 방법 배우기.&quot; data-og-host=&quot;helm.sh&quot; data-og-source-url=&quot;https://helm.sh/ko/docs/intro/install&quot; data-og-image=&quot;&quot; data-og-url=&quot;http://localhost:3000/ko/docs/intro/install&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;http://localhost:3000/ko/docs/intro/install&quot; data-source-url=&quot;https://helm.sh/ko/docs/intro/install&quot;&gt;
&lt;div style=&quot;background-image: url('\'\'');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;헬름 설치하기 | Helm&lt;/p&gt;
&lt;p style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;헬름 설치하고 작동하는 방법 배우기.&lt;/p&gt;
&lt;p style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;helm.sh&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;저는 ubuntu를 사용하기 때문에 APT를 통해 설치하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762674654867&quot; class=&quot;vim&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;sudo apt-get install curl gpg apt-transport-https --yes
curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg &amp;gt; /dev/null
echo &quot;deb [signed-by=/usr/share/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main&quot; | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install helm&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;argoCD helm-chart로 배포&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 이제 helm-chart를 통해 argoCD를 배포해봅시다. 그 전에 nginx ingress controller를 제외하고 이전에 배포했던 argocd 리소스를 모두 정리합니다.&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;helm repo는 아래 helm repo를 사용할 예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://argoproj.github.io/argo-helm/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://argoproj.github.io/argo-helm/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1762666616388&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;Argo Helm Charts&quot; data-og-description=&quot;ArgoProj Helm Charts&quot; data-og-host=&quot;argoproj.github.io&quot; data-og-source-url=&quot;https://argoproj.github.io/argo-helm/&quot; data-og-url=&quot;https://argoproj.github.io/argo-helm/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://argoproj.github.io/argo-helm/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://argoproj.github.io/argo-helm/&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;Argo Helm Charts&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;ArgoProj Helm Charts&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;argoproj.github.io&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 repository를 등록해봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1762666638525&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm repo add argo https://argoproj.github.io/argo-helm
helm repo update&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;그리고 다음과 같이 커스터마이징을 할 수 있는 values.yaml을 다운받습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762666863479&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm show values argo/argo-cd &amp;gt; 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;values.yaml을 확인해보면 알겠지만 각 옵션마다 자세한 설명이 주석으로 달려있습니다.&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;이 파일을 수정해도 좋지만 4,000줄이 넘고 이 많은 옵션 중 필요한 것만 골라서 사용하기 위해 values.yaml을 따로 다운받지는 않겠습니다.&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;대신 values.yaml에서 필요한 항목을 골라서 override-values.yaml을 다음과 같이 정의하겠습니다.&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;&amp;lt; override-values.yaml&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762671173220&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server:
  extraArgs:
    - --rootpath
    - /argocd
  ingress:
    enabled: true
    hostname: example.com
    annotations:
      nginx.ingress.kubernetes.io/backend-protocol: HTTPS
    ingressClassName: nginx
    paths:
      - &quot;/argocd&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;여기서 rootpath를 지정하면 위에서 발생했던 404 Not Found 문제를 해결할 수 있습니다.&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;이제 다음과 같은 명령어를 통해 helm-chart로 배포를 하고 저희가 정의한 override 파일을 적용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762672849908&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm install argocd argo/argo-cd -n argocd -f ./override-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;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://example.com:30443&quot;&gt;https://example.com:30443/argocd&lt;/a&gt;경로로 dashboard에 접근이 가능하면 성공입니다.&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;3813&quot; data-origin-height=&quot;1889&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMemwe/dJMcad8arVU/ioaMzykh2BAc8fdLRK22d1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMemwe/dJMcad8arVU/ioaMzykh2BAc8fdLRK22d1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMemwe/dJMcad8arVU/ioaMzykh2BAc8fdLRK22d1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMemwe%2FdJMcad8arVU%2FioaMzykh2BAc8fdLRK22d1%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;698&quot; height=&quot;346&quot; data-origin-width=&quot;3813&quot; data-origin-height=&quot;1889&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MetalLB를 활용하여 LoadBalancer Service 배포&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx Ingress Controller의 Service는 기본적으로 LoadBalancer type입니다.&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;하지만 on premise 환경에서는 LoadBalancer type의 Service에게 IP를 할당할 수 없어서 위에서 실습을 진행할 때는 NodePort 타입으로 변경하여 사용했습니다.&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;그런데 MetalLB를 활용하면 on premise 환경에서도 LoadBalancer 타입의 Service에게 ip를 할당할 수 있습니다.&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;먼저 kube-proxy의 config map을 수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762704428636&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl edit configmap -n kube-system kube-proxy&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;data.config.conf.ipvs.strictARP 값을 true로 바꿔줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJUxpA/dJMcai2IPCO/P0gZTEFS9vthPRNLdgBTqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJUxpA/dJMcai2IPCO/P0gZTEFS9vthPRNLdgBTqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJUxpA/dJMcai2IPCO/P0gZTEFS9vthPRNLdgBTqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJUxpA%2FdJMcai2IPCO%2FP0gZTEFS9vthPRNLdgBTqk%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;672&quot; height=&quot;832&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;832&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;pre id=&quot;code_1762705157205&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl get configmap kube-proxy -n kube-system -o yaml | \
sed -e &quot;s/strictARP: false/strictARP: true/&quot; | \
kubectl apply -f - -n kube-system&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;다음 명령어로 metalLB를 적용합니다. 원한다면 버전 정보는 바꾸시길 바랍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762704585099&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.15.2/config/manifests/metallb-native.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;다음으로 다음과 같은 Object를 정의하고 적용합니다.&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;여기서 IPAdressPool의 spec.addresses는 cluster node ip의 subnet과 동일한 대역에서 선택해야 합니다.&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;제 master node의 CIDR은 192.168.35.73/24이기 때문에 이 대역 내에서 결정했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762705097355&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;---
apiVersion: metallb.io/v1beta1 
kind: IPAddressPool
metadata:
  name: ip-pool
  namespace: metallb-system
spec:
  addresses:
  - 192.168.35.77-192.168.35.77
  autoAssign: true

--- 

apiVersion: metallb.io/v1beta1 
kind: L2Advertisement 
metadata:
  name: l2-network
  namespace: metallb-system
spec:
  ipAddressPools:
    - ip-pool&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;다음 명령어를 통해 servic object를 확인했을 때 external ip가 부여되었다면 성공입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762771965593&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl get svc -n ingress-nginx&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;1644&quot; data-origin-height=&quot;115&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYlgyE/dJMcagRndpu/KLP9nmfpfkI6oFYxf6trlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYlgyE/dJMcagRndpu/KLP9nmfpfkI6oFYxf6trlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYlgyE/dJMcagRndpu/KLP9nmfpfkI6oFYxf6trlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYlgyE%2FdJMcagRndpu%2FKLP9nmfpfkI6oFYxf6trlK%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;1644&quot; height=&quot;115&quot; data-origin-width=&quot;1644&quot; data-origin-height=&quot;115&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;출처&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://argo-cd.readthedocs.io/en/stable/operator-manual/high_availability/&quot;&gt;https://argo-cd.readthedocs.io/en/stable/operator-manual/high_availability/&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://argo-cd.readthedocs.io/en/stable/getting_started/&quot;&gt;https://argo-cd.readthedocs.io/en/stable/getting_started/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developnote-blog.tistory.com/171&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developnote-blog.tistory.com/171&lt;/a&gt;&lt;/p&gt;</description>
      <category>공부/DevOps</category>
      <category>argocd</category>
      <category>argocd 404</category>
      <category>argocd helm</category>
      <category>argocd helm chart</category>
      <category>argocd ingress</category>
      <category>argocd login error</category>
      <category>argocd nodeport</category>
      <category>argocd rootpath</category>
      <category>k8s 위 argocd 배포</category>
      <author>웅대</author>
      <guid isPermaLink="true">https://growth-coder.tistory.com/372</guid>
      <comments>https://growth-coder.tistory.com/372#entry372comment</comments>
      <pubDate>Sun, 9 Nov 2025 16:32:35 +0900</pubDate>
    </item>
    <item>
      <title>k8s HA architecture 구축</title>
      <link>https://growth-coder.tistory.com/370</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;k8s HA Architecture&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 HA를 고려한 k8s architecture를 알아봅시다.&lt;/p&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;HA를 위해 control plane node와 worker node를 다중화하고 Load Balancer를 통해 control plane과 worker 간 통신을 진행합니다.&lt;/p&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;보통 etcd의 위치에 따라 architecture 구조가 달라집니다.&lt;/p&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;etcd가 각 control plane node 안에 존재한다면&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;stacked etcd topology&lt;/b&gt;이며 etcd cluster가 외부에 존재한다면&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;external etcd&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;topology입니다.&lt;/p&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chvfob/dJMb9jtTerJ/vsmrDJiSqMn0dJJJk7hsy0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chvfob/dJMb9jtTerJ/vsmrDJiSqMn0dJJJk7hsy0/img.jpg&quot; data-alt=&quot;https://kubernetes.io/ko/docs/setup/production-environment/tools/kubeadm/ha-topology/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chvfob/dJMb9jtTerJ/vsmrDJiSqMn0dJJJk7hsy0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fchvfob%2FdJMb9jtTerJ%2FvsmrDJiSqMn0dJJJk7hsy0%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;800&quot; height=&quot;600&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://kubernetes.io/ko/docs/setup/production-environment/tools/kubeadm/ha-topology/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sjhHe/dJMb9jtTerO/XlCPOyrr0VUCaQAan05puk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sjhHe/dJMb9jtTerO/XlCPOyrr0VUCaQAan05puk/img.jpg&quot; data-alt=&quot;https://kubernetes.io/ko/docs/setup/production-environment/tools/kubeadm/ha-topology/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sjhHe/dJMb9jtTerO/XlCPOyrr0VUCaQAan05puk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsjhHe%2FdJMb9jtTerO%2FXlCPOyrr0VUCaQAan05puk%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;800&quot; height=&quot;600&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://kubernetes.io/ko/docs/setup/production-environment/tools/kubeadm/ha-topology/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;HA를 고려한 k8s cluster 구축&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;저는 VM을 사용하여 다음과 같은 아키텍처를 구축해보려고 합니다.&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;1820&quot; data-origin-height=&quot;1284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LiHaH/dJMb9LD0Hed/ARydF49RA5nWN6aQZDEENk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LiHaH/dJMb9LD0Hed/ARydF49RA5nWN6aQZDEENk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LiHaH/dJMb9LD0Hed/ARydF49RA5nWN6aQZDEENk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLiHaH%2FdJMb9LD0Hed%2FARydF49RA5nWN6aQZDEENk%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;641&quot; height=&quot;452&quot; data-origin-width=&quot;1820&quot; data-origin-height=&quot;1284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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&gt;&lt;b&gt;control plane node 3개&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;worker node 3개&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;stacked etcd topology&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;worker node, load balancer, control plane node는 모두 VM을 통해 격리할 예정입니다.&lt;/p&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;이전 포스팅에서 on premise 환경에서 k8s cluster를 구축해보았습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://growth-coder.tistory.com/368&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://growth-coder.tistory.com/368&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1762091384072&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;VM으로 On-Premise 환경에서 k8s cluster 구축하기&quot; data-og-description=&quot;개요On-Premise 환경에서 k8s cluster를 구축해보려고 합니다. container 기술 개념 정리k8s에 대해 이해하기 위해 먼저 container 기술에 대한 개념 정리를 하려고 합니다. container 기술이란?container 기술이란&quot; data-og-host=&quot;growth-coder.tistory.com&quot; data-og-source-url=&quot;https://growth-coder.tistory.com/368&quot; data-og-url=&quot;https://growth-coder.tistory.com/368&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cY8GKB/hyZMLPS6B6/qj9ke9mkCuNfaK5oMKgsP1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b2hosF/hyZMMVzQCf/f7RvehwZRSBw3kELhkjzP0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/dnmQiG/hyZMK4vylN/0oaY549eOuXubmCLc58vc1/img.jpg?width=1280&amp;amp;height=805&amp;amp;face=0_0_1280_805&quot;&gt;&lt;a href=&quot;https://growth-coder.tistory.com/368&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://growth-coder.tistory.com/368&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cY8GKB/hyZMLPS6B6/qj9ke9mkCuNfaK5oMKgsP1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b2hosF/hyZMMVzQCf/f7RvehwZRSBw3kELhkjzP0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/dnmQiG/hyZMK4vylN/0oaY549eOuXubmCLc58vc1/img.jpg?width=1280&amp;amp;height=805&amp;amp;face=0_0_1280_805');&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;VM으로 On-Premise 환경에서 k8s cluster 구축하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개요On-Premise 환경에서 k8s cluster를 구축해보려고 합니다. container 기술 개념 정리k8s에 대해 이해하기 위해 먼저 container 기술에 대한 개념 정리를 하려고 합니다. container 기술이란?container 기술이란&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;growth-coder.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;해당 포스팅에서 control plane node, worker node 공통 세팅이 된 VM을 복제하여 총 7개의 복제본을 만들었습니다.&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;세팅했던 k8s는 초기화를 했고 다시 k8s cluster를 구축하기 전에 먼저 load balancer를 만들겠습니다.&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;kubeadm init을 할 때 load balancer 주소를 알려줘야 하기 때문에 load balancer부터 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Load Balancer 설치&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;worker node들이 control plane으로 요청을 보낼 때 여러 개의 control plane node로 부하를 분산해 줄 load balancer가 필요합니다.&lt;/p&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;load balancer로는 HA proxy를 사용하기로 결정했습니다.&lt;/p&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;VM을 하나 띄우고 haproxy를 설치합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762256190382&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apt install haproxy&lt;/code&gt;&lt;/pre&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;/etc/haproxy/haproxy.cfg 파일을 열어보면 아래와 같은 설정 파일이 존재합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762256258948&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;global
        log /dev/log    local0
        log /dev/log    local1 notice
        chroot /var/lib/haproxy
        stats socket /run/haproxy/admin.sock mode 660 level admin
        stats timeout 30s
        user haproxy
        group haproxy
        daemon

        # Default SSL material locations
        ca-base /etc/ssl/certs
        crt-base /etc/ssl/private

        # See: https://ssl-config.mozilla.org/#server=haproxy&amp;amp;server-version=2.0.3&amp;amp;config=intermediate
        ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
        ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
        ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets

defaults
        log     global
        mode    http
        option  httplog
        option  dontlognull
        timeout connect 5000
        timeout client  50000
        timeout server  50000
        errorfile 400 /etc/haproxy/errors/400.http
        errorfile 403 /etc/haproxy/errors/403.http
        errorfile 408 /etc/haproxy/errors/408.http
        errorfile 500 /etc/haproxy/errors/500.http
        errorfile 502 /etc/haproxy/errors/502.http
        errorfile 503 /etc/haproxy/errors/503.http
        errorfile 504 /etc/haproxy/errors/504.http&lt;/code&gt;&lt;/pre&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;여기에서 간단하게 6443 포트로 들어온 요청을 master node에게 넘겨주는 코드를 작성해봅시다.&lt;/p&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;저는 현재 master node의 ip 주소가 192.168.35.243입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762256307045&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;frontend apiserver
    bind *:6443
    mode tcp
    option tcplog
    default_backend apiserverbackend

backend apiserverbackend
    option httpchk

    http-check connect ssl
    http-check send meth GET uri /healthz
    http-check expect status 200

    mode tcp
    balance     roundrobin
    server master1 192.168.35.243:6443 check verify none&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;이 코드를 추가하고 haproxy 설정을 적용하기 위해 재시작합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762256389386&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;systemctl restart haproxy&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;간단하게 haproxy 설정에 대해 알아봅시다.&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;1. mode tcp&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mode tcp에서 확인할 수 있듯이 HA proxy를 L4 로드 밸런서로 사용하고 있습니다.&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;그리고 k8s는 보안을 위해 기본적으로 TLS를 사용하여 데이터 암호화를 제공합니다.&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;k8s API server가 가지고 있는 인증서와 CA(private CA 혹은 recognized CA)를 통해 클라이언트는 API 서버와 https 통신을 할 수 있게 됩니다.&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;이 때, L4 load balancer를 사용하면 이미 암호화된 데이터가 L4 load balancer에 도착했을 때 복호화를 수행하지 않고 바로 API 서버에게 전달할 수 있습니다.&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;2. http-check&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http-check는 헬스체크에 관련된 내용이고 다음과 같은 설정이 있습니다.&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&gt;&lt;b&gt;connect ssl:&lt;/b&gt; https 통신으로 헬스 체크를 수행합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;send meth GET uri /healthz:&lt;/b&gt; /healthz 경로로 GET 요청을 보내서 헬스 체크를 수행합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;expect status 200:&lt;/b&gt; 200 상태 코드가 반환되면 성공입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;k8s cluster 구축&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 생성한 load balancer의 ip는 &lt;b&gt;192.168.35.146&lt;/b&gt;이고 port는 위에서 설정한 것처럼 &lt;b&gt;6443&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;kubeadm init을 하는데 --control-plane-endpoint에 &lt;b&gt;&amp;lt;load balancer ip 주소 : load balancer port&amp;gt;&lt;/b&gt;를 입력하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762256710443&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubeadm init --control-plane-endpoint 192.168.35.146:6443 \
--pod-network-cidr=10.0.0.0/16 \
--cri-socket unix:///run/containerd/containerd.sock \
--upload-certs&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;--upload-certs 옵션을 사용하면 인증서를 업로드하고 이 인증서는 모든 control plane node들이 공유합니다.&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;pre id=&quot;code_1762261360921&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run &quot;kubectl apply -f [podnetwork].yaml&quot; with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of control-plane nodes running the following command on each as root:

  kubeadm join 192.168.35.146:6443 --token dgvyqy.wgmjmsx2wypdmihh \
        --discovery-token-ca-cert-hash sha256:ebcdb33e11c601519c20b9c85e36cb1749c663bd675cbbafd06092ceab0dfbeb \
        --control-plane --certificate-key 4fe72208927093785476db6b0278342b1a625f333bb03f09a69f24d2c832fa54

Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
&quot;kubeadm init phase upload-certs --upload-certs&quot; to reload certs afterward.

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.35.146:6443 --token dgvyqy.wgmjmsx2wypdmihh \
        --discovery-token-ca-cert-hash sha256:ebcdb33e11c601519c20b9c85e36cb1749c663bd675cbbafd06092ceab0dfbeb&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 id=&quot;code_1762263031245&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config&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;이제 명령어를 보고 control plane node와 worker node를 추가하면 됩니다.&lt;/p&gt;</description>
      <category>haproxy</category>
      <category>k8s ha architecture</category>
      <category>Stacked etcd</category>
      <author>웅대</author>
      <guid isPermaLink="true">https://growth-coder.tistory.com/370</guid>
      <comments>https://growth-coder.tistory.com/370#entry370comment</comments>
      <pubDate>Tue, 4 Nov 2025 22:39:34 +0900</pubDate>
    </item>
  </channel>
</rss>