<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Daramu</title>
    <link>https://daramu.tistory.com/</link>
    <description>다람어의 블로그</description>
    <language>ko</language>
    <pubDate>Wed, 3 Jun 2026 00:15:13 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Daramu</managingEditor>
    <item>
      <title>Spring Cloud Gateway 와 KeyCloak 의 구조</title>
      <link>https://daramu.tistory.com/92</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스팅으로 Spring Cloud Gateway(이하 SCG)에 대한 내용을 다뤘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://daramu.tistory.com/91&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://daramu.tistory.com/91&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780373153690&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;Spring boot - Spring Cloud Gateway(SCG) 란?&quot; data-og-description=&quot;MSA에서 코드를 구성하는 방식은 여러개가 있다. 그 중 피할 수 없는 건 프론트-백엔드간의 연계이다. 이렇게 생각해보자. 당신은 쇼핑몰 웹을 운영중이다.이 쇼핑몰 앱은 여러개의 백엔드 API가 &quot; data-og-host=&quot;daramu.tistory.com&quot; data-og-source-url=&quot;https://daramu.tistory.com/91&quot; data-og-url=&quot;https://daramu.tistory.com/91&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dOuro1/dJMb9dHvlu3/B11xO9801FaUBqhGzGAP60/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bomBPC/dJMb9c9E0Wq/sApTNoRiDDkKhKAL7B4kfk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/e1VVJ/dJMb9cBPbv1/gkzkn0ZqdfHUCjlnLs4vV1/img.png?width=700&amp;amp;height=346&amp;amp;face=509_129_561_185&quot;&gt;&lt;a href=&quot;https://daramu.tistory.com/91&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://daramu.tistory.com/91&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dOuro1/dJMb9dHvlu3/B11xO9801FaUBqhGzGAP60/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bomBPC/dJMb9c9E0Wq/sApTNoRiDDkKhKAL7B4kfk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/e1VVJ/dJMb9cBPbv1/gkzkn0ZqdfHUCjlnLs4vV1/img.png?width=700&amp;amp;height=346&amp;amp;face=509_129_561_185');&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 - Spring Cloud Gateway(SCG) 란?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;MSA에서 코드를 구성하는 방식은 여러개가 있다. 그 중 피할 수 없는 건 프론트-백엔드간의 연계이다. 이렇게 생각해보자. 당신은 쇼핑몰 웹을 운영중이다.이 쇼핑몰 앱은 여러개의 백엔드 API가&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;daramu.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;여기서는 프론트는 SCG라는 단일 진입점을 통해 관리와 통신에 대한 이점을 소개했지만,&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;바로 KeyCloak을 사용할때다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림처럼 브라우저(프론트)는 여러 백엔드에 대하여 SCG 하나만 바라보는게 맞다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여기서 KeyCloak은 예외로 두고 프론트가 직접 통신할 수 있게 할 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2038&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ECAC1/dJMcaf05bid/K0Qq7jkLF8K5k9hdmfe9R1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ECAC1/dJMcaf05bid/K0Qq7jkLF8K5k9hdmfe9R1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ECAC1/dJMcaf05bid/K0Qq7jkLF8K5k9hdmfe9R1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FECAC1%2FdJMcaf05bid%2FK0Qq7jkLF8K5k9hdmfe9R1%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;2038&quot; height=&quot;922&quot; data-origin-width=&quot;2038&quot; data-origin-height=&quot;922&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;왜 KeyCloak은&amp;nbsp; SCG로 제어하지 않는가?&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능은 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SCG의 라우팅 설정에서 /login 등의 호출이 있을시 KeyCloak으로 라우팅해줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 사용자는 KeyCloak자체에 들어가 로그인을 해야하기 때문에, 사용자는 어찌되었든 KeyCloak으로 접속을 해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 브라우저가 접속을 해야한다는 것은, KeyCloak자체가 외부에 노출되어 있어야 한다는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(물론&amp;nbsp;Keycloak에&amp;nbsp;허용된&amp;nbsp;클라이언트와&amp;nbsp;redirect&amp;nbsp;URI만&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;고로 SCG를 사용한다 하여도 중간에 통신 과정만 추가될 뿐이지 결과적으로 KeyCloak으로 접속하게 되니,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크의 복잡성을 줄이기 위해 KeyCloak만큼은 SCG로 관리하지 않는 예외로 둘 것이다.&lt;/p&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;싫다면&amp;nbsp;Keycloak을&amp;nbsp;SCG에&amp;nbsp;등록하는&amp;nbsp;방법도&amp;nbsp;있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그렇게 하려면 결국 Keycloak의 리다이렉트 방식을 포기하고 직접 JWT를 발급하는 auth 서비스를 만들어야 하는데, 그건 앞서 살펴보았듯이 Keycloak의 장점을 모두 삭제하는 행위이기에 본 포스팅에서는 KeyCloak을 사용할 것이며 SCG에 등록하지 않을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 이 모든 내용은 현대식 브라우저 기반 프론트엔드(React, Vue...etc..)에서 PKCE 플로우를 사용한다는 가정하에 하는 말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹페이지인 이상 브라우저 기반 프론트를 사용하면 모든 코드가 사용자에게 100%노출된다는 가정하에서 하는 말이며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각해보면 KeyCloak을 호출하는 것은 사용자(브라우저)뿐 아니라 백엔드 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;API to API 방식만 사용한다면 굳이 별도로 분리하는게 아닌, SCG로 관리하여도 무방하다.&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;차후 있을 포스팅에서 &quot;사용자 - KeyCloak&quot;과 &quot;백엔드 API - API&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;후자를 사용하는 경우는 SCG가 Keycloak의 공개키로 JWT를 자체 검증하거나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keycloak 인트로스펙션 엔드포인트에 직접 요청하는 방식 모두 SCG - Keycloak 간 API to 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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;93&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1VApe/dJMcadvnK1K/ri4zhKGKekyXlWxyNEkj0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1VApe/dJMcadvnK1K/ri4zhKGKekyXlWxyNEkj0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1VApe/dJMcadvnK1K/ri4zhKGKekyXlWxyNEkj0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1VApe%2FdJMcadvnK1K%2Fri4zhKGKekyXlWxyNEkj0K%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;725&quot; height=&quot;93&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;93&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>개발/KeyCloak</category>
      <category>SCG와 KeyCloak</category>
      <category>Spring Cloud Gateway KeyCloak</category>
      <category>스프링 클라우드 게이트웨이 키클록</category>
      <category>스프링게이트웨이 키클록</category>
      <category>스프링부트 키클록</category>
      <author>Daramu</author>
      <guid isPermaLink="true">https://daramu.tistory.com/92</guid>
      <comments>https://daramu.tistory.com/92#entry92comment</comments>
      <pubDate>Tue, 2 Jun 2026 13:22:14 +0900</pubDate>
    </item>
    <item>
      <title>Spring boot - Spring Cloud Gateway(SCG) 란?</title>
      <link>https://daramu.tistory.com/91</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;MSA에서 코드를 구성하는 방식은 여러개가 있다.&lt;/p&gt;
&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;이 쇼핑몰 앱은 여러개의 백엔드 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;/p&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를 제공하는 Product 라는 서비스가 있고, 또 결제를 담당하는 payment 라는 서비스가 있을 수 있고, 결제 완료나 안내사항을 작성하는 Notification 이라는 서비스도 있을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모든 것을 하나의 서비스에 몰아둘 수 있지만, 그건 MSA 철학에 맞지 않는다. 아주 높은 확률로 다른 서비스로 떠 있을 것이다.&lt;/p&gt;
&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;사용자가 처음 웹 페이지를 방문할 때는 제품 정보를 받아오기 위해 API를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const&amp;nbsp;response&amp;nbsp;=&amp;nbsp;await&amp;nbsp;fetch('&lt;a href=&quot;https://example.com');&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;http://localhost:8081');&lt;/a&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를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://example.com');&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt; const&amp;nbsp;response&amp;nbsp;=&amp;nbsp;await&amp;nbsp;fetch('&lt;/a&gt;&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://example.com');&quot;&gt;http://localhost:8082');&lt;/a&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를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const&amp;nbsp;response&amp;nbsp;=&amp;nbsp;await&amp;nbsp;fetch('&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://example.com');&quot;&gt;http://localhost:8083');&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;여기서 편의상 localhost라고 적었지만 실제 도메인을 사용한다면 더욱 복잡해질 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 payment의 주소가 바뀐다면?&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;JWT 방식으로 사용자에게 토큰을 주어 사용자 식별을 한다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 product 서비스는 jwt를 분석할 수 있는 로직이 있어야하고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 payment또한 jwt를 분석할 수 있는 로직이 있어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 notification또한 동일하다.&lt;/p&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 Cloud Gateway(이하 SCG)이다.&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;700&quot; data-origin-height=&quot;346&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdKdo1/dJMcacJ8pyr/1dUx9gQZQJPI9k4hBnc1F0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdKdo1/dJMcacJ8pyr/1dUx9gQZQJPI9k4hBnc1F0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdKdo1/dJMcacJ8pyr/1dUx9gQZQJPI9k4hBnc1F0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdKdo1%2FdJMcacJ8pyr%2F1dUx9gQZQJPI9k4hBnc1F0%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;700&quot; height=&quot;346&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;346&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 말해, 프론트(브라우저)와 백엔드 사이에 SCG를 둠으로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트의 입장에서 연결될 백엔드는 오로지 SCG하나밖에 없으니 모든 코드를 이렇게 바꿀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const&amp;nbsp;response&amp;nbsp;=&amp;nbsp;await&amp;nbsp;fetch('&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://example.com');&quot;&gt;http://localhost:8081'); -&amp;gt;&lt;/a&gt; const&amp;nbsp;response&amp;nbsp;=&amp;nbsp;await&amp;nbsp;fetch('&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://example.com');&quot;&gt;http://localhost:8080');&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const&amp;nbsp;response&amp;nbsp;=&amp;nbsp;await&amp;nbsp;fetch('&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://example.com');&quot;&gt;http://localhost:8082'); -&amp;gt;&lt;/a&gt; const&amp;nbsp;response&amp;nbsp;=&amp;nbsp;await&amp;nbsp;fetch('&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://example.com');&quot;&gt;http://localhost:8080');&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const&amp;nbsp;response&amp;nbsp;=&amp;nbsp;await&amp;nbsp;fetch('&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://example.com');&quot;&gt;http://localhost:8083'); -&amp;gt; const&amp;nbsp;response&amp;nbsp;=&amp;nbsp;await&amp;nbsp;fetch('&lt;/a&gt;&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://example.com');&quot;&gt;http://localhost:8080');&lt;/a&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;거기에 jwt에 대한 검증도 마찬가지다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 각기 모든 백엔드 서비스가 jwt에 대한 유효성 검사로 자체적인 jwt분석 로직이 있거나, 요청때마다 별도의 auth 서비스를 오고가야 했다면, 이제는 프론트 입장에서 단일 진입점은 SCG이므로, SCG에서 jwt의 유효성을 검사하고 맞는 사용자라면 별도의 헤더로 백엔드에 내려줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시로 test@test.com 이라는 유저가 결제 요청을 했다고 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 API요청에는 jwt토큰또한 들어있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 payment 서비스가 jwt를 분석하고, 유효성을 판단하며, jwt안에 있는 test@test.com 이라는 유저의 Id나 Email을 뽑아내어 사용했다면,&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;이제는 SCG가 jwt유효성을 검사하고 헤더값에 (X-User-Id: test@test.com) 이라는 헤더를 넣어 payment에 주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 payment 서비스는 SCG를 신뢰하여 jwt분석로직 없이 오로지 헤더의 test@test.com 이라는 값만 사용하여 비지니스 로직을 짜면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 백엔드는 SCG가 JWT검증 후 내려주는 별도의 헤더를 신뢰한다면 그것이 변조 되는 것(해킹)을 고려해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 자체에 jwt 분석 및 검증 로직이 있다면 상관없지만, SCG를 통해 헤더로 X-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;고로 백엔드가 외부에서 직접 접근 가능하다면 보안에 취약함으로, 백엔드 서비스는 SCG를 통한 접근만 가능하도록 내부망 격리, 혹은 네트워크 정책으로 막아야할 필요성은 분명 존재한다.&lt;/p&gt;</description>
      <category>개발/Spring boot</category>
      <category>scg</category>
      <category>Spring cloud gateway</category>
      <category>스프링 게이트웨이</category>
      <category>스프링 클라우드 게이트웨이</category>
      <category>스프링부트 게이트웨이</category>
      <author>Daramu</author>
      <guid isPermaLink="true">https://daramu.tistory.com/91</guid>
      <comments>https://daramu.tistory.com/91#entry91comment</comments>
      <pubDate>Tue, 2 Jun 2026 12:50:25 +0900</pubDate>
    </item>
    <item>
      <title>KeyCloak 설치</title>
      <link>https://daramu.tistory.com/90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;keycloak(이하 키클록)을 설치할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키클록에 대한 것과, 어떤 방식으로 구현할 것인지 궁금하다면 아래 포스팅 참고 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://daramu.tistory.com/89&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://daramu.tistory.com/89&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780294786665&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;Keycloak 이란?&quot; data-og-description=&quot;Keycloak(이하 키클록)이란 레드햇에서 개발한 오픈 소스 통합 인증 솔루션으로, 애플리케이션의 복잡한 로그인, 회원가입, 권한 부여 기능을 중앙에서 쉽게 통합하고 관리할 수 있게 해준다. MSA환&quot; data-og-host=&quot;daramu.tistory.com&quot; data-og-source-url=&quot;https://daramu.tistory.com/89&quot; data-og-url=&quot;https://daramu.tistory.com/89&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ILHa4/dJMb896aMio/lrFACmVIk3tE15QEBPId9k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/BcdGo/dJMb83Sp4aG/IRDTbr3gyiHffWGksXGmb1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/zFfiJ/dJMb89ykI7v/k47jIO5GNMCfkoS0GVQMIk/img.png?width=3840&amp;amp;height=2498&amp;amp;face=0_0_3840_2498&quot;&gt;&lt;a href=&quot;https://daramu.tistory.com/89&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://daramu.tistory.com/89&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ILHa4/dJMb896aMio/lrFACmVIk3tE15QEBPId9k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/BcdGo/dJMb83Sp4aG/IRDTbr3gyiHffWGksXGmb1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/zFfiJ/dJMb89ykI7v/k47jIO5GNMCfkoS0GVQMIk/img.png?width=3840&amp;amp;height=2498&amp;amp;face=0_0_3840_2498');&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;Keycloak 이란?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Keycloak(이하 키클록)이란 레드햇에서 개발한 오픈 소스 통합 인증 솔루션으로, 애플리케이션의 복잡한 로그인, 회원가입, 권한 부여 기능을 중앙에서 쉽게 통합하고 관리할 수 있게 해준다. MSA환&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;daramu.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 설치전에 여러가지 옵션이 있다.&lt;/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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1324&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vwlT7/dJMcaa6Bhzd/HrbnsDkroa8L3SGDfH8wxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vwlT7/dJMcaa6Bhzd/HrbnsDkroa8L3SGDfH8wxk/img.png&quot; data-alt=&quot;https://www.keycloak.org/guides&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vwlT7/dJMcaa6Bhzd/HrbnsDkroa8L3SGDfH8wxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvwlT7%2FdJMcaa6Bhzd%2FHrbnsDkroa8L3SGDfH8wxk%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;1324&quot; height=&quot;316&quot; data-origin-width=&quot;1324&quot; data-origin-height=&quot;316&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.keycloak.org/guides&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;만약 본인 로컬 환경이라면 OpenJDK나 Docker, 특히 Docker를 추천한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우 환경이라 하여도 Docker Desktop을 통해 충분히 올릴 수 있으며, 이미지화 되어있기에 설치도 한줄로 간단하게 진행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 본 포스팅에서는 NCP의 NKS(Naver Kubernetes Service)를 사용하고 있는 만큼, Kubernetes 를 사용해서 올릴 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두려워할 필요 없다. Docker라면 한줄로 하면 되고, 쿠버네티스라면 함께 구축하면 된다.&lt;/p&gt;
&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_1780294912551&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create -f https://raw.githubusercontent.com/keycloak/keycloak-quickstarts/refs/heads/main/kubernetes/keycloak.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;위 명령어의 https URL로 직접 접속하면 yaml파일이 있으며, service 세개와 statefulset한개, 그리고 deployment 한개가있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 한개의 deployment와 service는 postgresql에 대한 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGszuS/dJMcaf7PV36/XHou0ME6yMl7ckjqv5gU8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGszuS/dJMcaf7PV36/XHou0ME6yMl7ckjqv5gU8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGszuS/dJMcaf7PV36/XHou0ME6yMl7ckjqv5gU8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGszuS%2FdJMcaf7PV36%2FXHou0ME6yMl7ckjqv5gU8K%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;478&quot; height=&quot;501&quot; data-origin-width=&quot;794&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;가장 위에 있는 주석처리된 경고문 처럼, 해당 deployment는 임시 테스트를 위한 저장소인 postgresql의 쿠버네티스 리소스로, 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;바로 밑에 두번째 주석으로 프로덕션 셋업시에는 지속가능한 DB로 변경하라 나와있기에 그렇게 진행할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머지 두개의 service와 한개의 statefulset은 keycloak에 대한 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;849&quot; data-origin-height=&quot;775&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A5arI/dJMcahEAbgr/RQ2Y74948fIbYFvOWslxm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A5arI/dJMcahEAbgr/RQ2Y74948fIbYFvOWslxm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A5arI/dJMcahEAbgr/RQ2Y74948fIbYFvOWslxm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA5arI%2FdJMcahEAbgr%2FRQ2Y74948fIbYFvOWslxm0%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;450&quot; height=&quot;411&quot; data-origin-width=&quot;849&quot; data-origin-height=&quot;775&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 가장 위의 keycloak, 밑에 keycloak-discovery 둘 모두 selector로 app: keycloak으로 된것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 statefulset인 keycloak을 바라보며 discovery는 내부에서 클러스터링을 위한 서비스이니, 실제 접속은 가장 상단의 keycloak service(port:8080)을 사용하게 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;statefulset은 익히 알듯이, 고유한 네트워크 식별자가 필요할때 사용하며, 이는 기존 파드가 죽으면 같은 이름과 볼륨으로 재생성된다는 것이다. (일반 deployment는 replicasname으로 무작위로 생성된다.)&lt;/p&gt;
&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;롤링 업데이트 시 Pod가 하나씩 순서대로 재시작되도록 보장함과 동시에 JGroups 클러스터링을 위한 안정적인 네트워크 식별자 확보를 위해 Statefulset을 사용한다(주석에 설명되어있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 statefulset의 세부항목을 보면 pod로 띄웠던 postgresql을 연결하는 항목도 찾을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;290&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blOwxt/dJMcabqTi5P/FUyPYmTERP03qguY7a2LIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blOwxt/dJMcabqTi5P/FUyPYmTERP03qguY7a2LIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blOwxt/dJMcabqTi5P/FUyPYmTERP03qguY7a2LIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblOwxt%2FdJMcabqTi5P%2FFUyPYmTERP03qguY7a2LIK%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;246&quot; height=&quot;200&quot; data-origin-width=&quot;290&quot; data-origin-height=&quot;236&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;이제 기본제공하는 yaml파일을 보았으니, 설치하기 전에 아까 말했듯 pod로 올린 postgresql이 아닌 서버나 rds등을 사용한 영속성이 있는 DB가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 포스팅에서는 NCP의 CDB(Cloud for DB) Postgresql을 사용할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS의 RDS postgresql도 동일하며, 어차피 URL로 로그인하는 것은 똑같기에 AWS로 진행해도 문제는 없다.&lt;/p&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/edAHPE/dJMcaijdWxu/rAVzyZSYyWKOXeLSNoV6I1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/edAHPE/dJMcaijdWxu/rAVzyZSYyWKOXeLSNoV6I1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/edAHPE/dJMcaijdWxu/rAVzyZSYyWKOXeLSNoV6I1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FedAHPE%2FdJMcaijdWxu%2FrAVzyZSYyWKOXeLSNoV6I1%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;517&quot; height=&quot;109&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;120&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;DB를 생성했다면, 이제 Postgresql의 설정을 진행할 차례다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keycloak은 DB연결시 테이블(스키마)를 자동으로 생성하지만 DB 자체는 직접 만들어야한다.&lt;/p&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을 통해 Postgresql에 접속한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 아래의 쿼리를 통해 DB를 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1780302604112&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE DATABASE keycloak;
CREATE USER keycloak WITH PASSWORD '실제패스워드';
GRANT ALL PRIVILEGES ON DATABASE keycloak TO keycloak;

\c keycloak
GRANT ALL ON SCHEMA public TO keycloak;&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;NCP의 경우 추가가 안될 수 있는데, 콘솔 -&amp;gt; CDB Postgresql -&amp;gt; Database 관리 에서 User와 Database를 추가하거나 삭제할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1851&quot; data-origin-height=&quot;255&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6N0BP/dJMcadB7Rhq/3106sosdWxxyKShWlP0fL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6N0BP/dJMcadB7Rhq/3106sosdWxxyKShWlP0fL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6N0BP/dJMcadB7Rhq/3106sosdWxxyKShWlP0fL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6N0BP%2FdJMcadB7Rhq%2F3106sosdWxxyKShWlP0fL0%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;1851&quot; height=&quot;255&quot; data-origin-width=&quot;1851&quot; data-origin-height=&quot;255&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;1737&quot; data-origin-height=&quot;269&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UZ0eZ/dJMcagyVS6N/tBVK4WbB05Vv6Zwft8kEYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UZ0eZ/dJMcagyVS6N/tBVK4WbB05Vv6Zwft8kEYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UZ0eZ/dJMcagyVS6N/tBVK4WbB05Vv6Zwft8kEYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUZ0eZ%2FdJMcagyVS6N%2FtBVK4WbB05Vv6Zwft8kEYK%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;1737&quot; height=&quot;269&quot; data-origin-width=&quot;1737&quot; data-origin-height=&quot;269&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;마지막으로 공식 yaml파일에 있듯이, secret을 사용하여 username와 password를 생성하여 db에 연결하라 권장하니 그렇게 따라준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연한 말이지만 keycloak이 아래 생성할 secret정보를 가지고 db에 접속하니, 만든 db의 username,password와 secret의 username,password는 같게 해야한다.&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;657&quot; data-origin-height=&quot;66&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buJGWy/dJMcajoOM4o/dAW08hV3FwJuI8uktfJEz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buJGWy/dJMcajoOM4o/dAW08hV3FwJuI8uktfJEz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buJGWy/dJMcajoOM4o/dAW08hV3FwJuI8uktfJEz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuJGWy%2FdJMcajoOM4o%2FdAW08hV3FwJuI8uktfJEz1%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;657&quot; height=&quot;66&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;66&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;pre id=&quot;code_1780361199517&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#패스워드에 특수문자 있을경우 따옴표 사용 '실제패스워드'
kubectl create secret generic keycloak-db-secret \
  --from-literal=username=keycloak \
  --from-literal=password=실제패스워드&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;yaml 파일에서 username, password부분만 아래와 같이 변경해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1780362570375&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;- name: 'KC_DB_USERNAME'
  valueFrom:
    secretKeyRef:
      name: keycloak-db-secret
      key: username
- name: 'KC_DB_PASSWORD'
  valueFrom:
    secretKeyRef:
      name: keycloak-db-secret
      key: password&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;그리고 DB URL 부분을 실제 host명으로 변경한다.&lt;/p&gt;
&lt;pre id=&quot;code_1780364638014&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;- name: 'KC_DB_URL_HOST'
  value: 'postgres'
  #실제 host로 변경. 기존은 postgres라는 service를 찾게 설정 되어있음&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;추가로 cdb(혹은 aws rdb, ec2...etc)를 사용 중이므로, 전체 yaml파일에서 가장 하단에 postgresql 에 대한 deployment와 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;&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;794&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGszuS/dJMcaf7PV36/XHou0ME6yMl7ckjqv5gU8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGszuS/dJMcaf7PV36/XHou0ME6yMl7ckjqv5gU8K/img.png&quot; data-alt=&quot;이 deployment와 service를 삭제한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGszuS/dJMcaf7PV36/XHou0ME6yMl7ckjqv5gU8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGszuS%2FdJMcaf7PV36%2FXHou0ME6yMl7ckjqv5gU8K%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;478&quot; height=&quot;501&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이 deployment와 service를 삭제한다.&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;그럼 최종적으로 아래와 같은 yaml파일이 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1780364990477&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...(생략)
            - name: 'KC_DB_URL_HOST'
              value: 실제 DB 호스트 입력(IP OR URL)
            - name: 'KC_DB'
              value: 'postgres'
            - name: 'KC_DB_PASSWORD'
              valueFrom:
                secretKeyRef:
                  name: keycloak-db-secret
                  key: password
            - name: 'KC_DB_USERNAME'
              valueFrom:
                secretKeyRef:
                  name: keycloak-db-secret
                  key: username
          ports:
            - name: http
              containerPort: 8080
            - name: jgroups
              containerPort: 7800
            - name: jgroups-fd
              containerPort: 57800
          startupProbe:
            httpGet:
              path: /health/started
              port: 9000
            periodSeconds: 1
            failureThreshold: 600
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 9000
            periodSeconds: 10
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /health/live
              port: 9000
            periodSeconds: 10
            failureThreshold: 3
          resources:
            limits:
              cpu: 2000m
              memory: 2000Mi
            requests:
              cpu: 500m
              memory: 1700Mi&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;해당 파일을 실행하면 pod와 service가 뜬 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;429&quot; data-origin-height=&quot;42&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqq9ut/dJMcahEABeR/bcMiWndKdKgW3hVzkL1Hc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqq9ut/dJMcahEABeR/bcMiWndKdKgW3hVzkL1Hc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqq9ut/dJMcahEABeR/bcMiWndKdKgW3hVzkL1Hc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbqq9ut%2FdJMcahEABeR%2FbcMiWndKdKgW3hVzkL1Hc1%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;429&quot; height=&quot;42&quot; data-origin-width=&quot;429&quot; data-origin-height=&quot;42&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;default namespaces가 거슬린다면 yaml파일에서 metadata.namespace 에서 원하는 네임스페이스를 지정하면 된다.&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;이제 이렇게 올렸다면 문제가 하나 있다.&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;yaml파일 최상단에 service가 있지만, 타입은 ClusterIP타입이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780367257295&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:
  name: keycloak
  labels:
    app: keycloak
spec:
  ports:
    - protocol: TCP
      port: 8080
      targetPort: http
      name: http
  selector:
    app: keycloak
  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;이제 접속을 위한 작업을 시작하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두가지 방법이 있는데, 하나는 저 위 코드의 type을 ClusterIP에서 LoadBalancer로 변경하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 하나는 ingress파일을 작성하여 alb나 nginx 등 ingresscontroller를 사용하여 접속하는 것이다.&lt;/p&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파일 방식으로 진행할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 ingress, ingressclass등 개념이 궁금하면 아래 포스팅 참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://daramu.tistory.com/71&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://daramu.tistory.com/71&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780367348898&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;Ingress와 IngressClass, 그리고 쿠버네티스의 관계&quot; data-og-description=&quot;본 포스팅은 NCP환경 기준으로 작성 되었으나, 쿠버네티스 환경 자체에 대한 설명이기도 하기에 NCP에 국한되지는 않는다. 앞으로 여러 포스팅을 통해 ingress와 ingressclass, 그리고 쿠버네티스의 여&quot; data-og-host=&quot;daramu.tistory.com&quot; data-og-source-url=&quot;https://daramu.tistory.com/71&quot; data-og-url=&quot;https://daramu.tistory.com/71&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cEN0xs/dJMb8Rj9gc8/g1cqvRnWGsL9mNLqSw7LiK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bq4TCs/dJMb8PGDmsj/lga1VzQQDdRJ9wJFsTTnr0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bDYx4G/dJMb84X5Wq6/SVAr17K4vD6c3ET93km1n1/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://daramu.tistory.com/71&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://daramu.tistory.com/71&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cEN0xs/dJMb8Rj9gc8/g1cqvRnWGsL9mNLqSw7LiK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bq4TCs/dJMb8PGDmsj/lga1VzQQDdRJ9wJFsTTnr0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bDYx4G/dJMb84X5Wq6/SVAr17K4vD6c3ET93km1n1/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&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;Ingress와 IngressClass, 그리고 쿠버네티스의 관계&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;본 포스팅은 NCP환경 기준으로 작성 되었으나, 쿠버네티스 환경 자체에 대한 설명이기도 하기에 NCP에 국한되지는 않는다. 앞으로 여러 포스팅을 통해 ingress와 ingressclass, 그리고 쿠버네티스의 여&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;daramu.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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 ingress파일을 작성해주겠다.&lt;/p&gt;
&lt;pre id=&quot;code_1780367475014&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: keycloak
  annotations:
    alb.ingress.kubernetes.io/network-type: 'private'
spec:
  ingressClassName: alb
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: keycloak
            port:
              number: 8080&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;annotaion에 &quot;alb.ingress.kubernetes.io/network-type: 'private'&quot; 이건 VPN환경이기에 공인IP가 필요없기에 설정했다. 공인IP를 사용해야 하는 환경이라면 annotation과 network-type 두개를 삭제하면 된다.(name만 남기라는 뜻.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 NCP에서는 ALB ingress 를 통해 노출시킬 서비스 type은 NodePort로 하라 되어있으니 service를 수정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zp4xG/dJMcajbhpwk/NJWXI2MUnvcFbTS0piuUP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zp4xG/dJMcajbhpwk/NJWXI2MUnvcFbTS0piuUP1/img.png&quot; data-alt=&quot;https://guide.ncloud-docs.com/docs/k8s-k8suse-albingress&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zp4xG/dJMcajbhpwk/NJWXI2MUnvcFbTS0piuUP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fzp4xG%2FdJMcajbhpwk%2FNJWXI2MUnvcFbTS0piuUP1%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;818&quot; height=&quot;308&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://guide.ncloud-docs.com/docs/k8s-k8suse-albingress&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780367589730&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:
  name: keycloak
  labels:
    app: keycloak
spec:
  ports:
    - protocol: TCP
      port: 8080
      targetPort: http
      name: http
  selector:
    app: keycloak
  type: NodePort
  .......(생략)&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;294&quot; data-origin-height=&quot;53&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chaYA9/dJMcaftgAmH/QdkIXdIKoYmOhzu4lygoO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chaYA9/dJMcaftgAmH/QdkIXdIKoYmOhzu4lygoO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chaYA9/dJMcaftgAmH/QdkIXdIKoYmOhzu4lygoO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchaYA9%2FdJMcaftgAmH%2FQdkIXdIKoYmOhzu4lygoO1%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;294&quot; height=&quot;53&quot; data-origin-width=&quot;294&quot; data-origin-height=&quot;53&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;그리고 생성된 LB에 접속하면, keycloak이 동작중이다!&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;615&quot; data-origin-height=&quot;517&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xpp6t/dJMcaiDxnzg/XbYEe7WLedDOBKxUsR4QtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xpp6t/dJMcaiDxnzg/XbYEe7WLedDOBKxUsR4QtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xpp6t/dJMcaiDxnzg/XbYEe7WLedDOBKxUsR4QtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxpp6t%2FdJMcaiDxnzg%2FXbYEe7WLedDOBKxUsR4QtK%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;615&quot; height=&quot;517&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;517&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;접속을 위한 접속계정은 yaml파일에 있듯 admin/admin이다. 바꾸고 싶다면 바꾸는 것도 좋다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;355&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/myHcT/dJMcadoCslV/ynfGbO0tjSVpaEmAACZGZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/myHcT/dJMcadoCslV/ynfGbO0tjSVpaEmAACZGZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/myHcT/dJMcadoCslV/ynfGbO0tjSVpaEmAACZGZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmyHcT%2FdJMcadoCslV%2FynfGbO0tjSVpaEmAACZGZ0%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;355&quot; height=&quot;159&quot; data-origin-width=&quot;355&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>개발/KeyCloak</category>
      <category>keycloak</category>
      <category>keycloak 설치</category>
      <category>NCP keycloak 설치</category>
      <category>쿠버네티스 keycloak</category>
      <author>Daramu</author>
      <guid isPermaLink="true">https://daramu.tistory.com/90</guid>
      <comments>https://daramu.tistory.com/90#entry90comment</comments>
      <pubDate>Tue, 2 Jun 2026 11:40:36 +0900</pubDate>
    </item>
    <item>
      <title>Keycloak 이란?</title>
      <link>https://daramu.tistory.com/89</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Keycloak(이하&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;중앙에서&amp;nbsp;쉽게&amp;nbsp;통합하고&amp;nbsp;관리할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;해준다. &lt;br /&gt;&lt;br /&gt;MSA환경에서는&amp;nbsp;여러개의&amp;nbsp;Pod가&amp;nbsp;떠있고,&amp;nbsp;Pod와&amp;nbsp;워커노드(서버)&amp;nbsp;모두&amp;nbsp;문제가&amp;nbsp;발생한다면&amp;nbsp;버리고&amp;nbsp;다시&amp;nbsp;생성해서&amp;nbsp;사용하는&amp;nbsp;것을&amp;nbsp;기본값으로&amp;nbsp;한다. &lt;br /&gt;&lt;br /&gt;그렇기에&amp;nbsp;서버가&amp;nbsp;사용자의&amp;nbsp;정보를&amp;nbsp;가지고&amp;nbsp;있는&amp;nbsp;세션&amp;nbsp;방식은&amp;nbsp;MSA에서는&amp;nbsp;사용이&amp;nbsp;어렵다. &lt;br /&gt;&lt;br /&gt;Pod&amp;nbsp;A에서&amp;nbsp;로그인했는데&amp;nbsp;다음&amp;nbsp;요청이&amp;nbsp;Pod&amp;nbsp;B로&amp;nbsp;라우팅되면,&amp;nbsp;Pod&amp;nbsp;B에는&amp;nbsp;세션이&amp;nbsp;없어&amp;nbsp;인증&amp;nbsp;실패가&amp;nbsp;발생한다.&amp;nbsp;수십&amp;nbsp;개의&amp;nbsp;Pod가&amp;nbsp;동시에&amp;nbsp;떠&amp;nbsp;있는&amp;nbsp;환경에서는&amp;nbsp;이&amp;nbsp;문제가&amp;nbsp;더욱&amp;nbsp;심각해진다. &lt;br /&gt;&lt;br /&gt;이때&amp;nbsp;사용하는&amp;nbsp;것이&amp;nbsp;JSON&amp;nbsp;Web&amp;nbsp;Token&amp;nbsp;(JWT)이다.&amp;nbsp;세션&amp;nbsp;방식이&amp;nbsp;사용자의&amp;nbsp;인증&amp;nbsp;정보를&amp;nbsp;서버가&amp;nbsp;가지고&amp;nbsp;있는&amp;nbsp;것이라면,&amp;nbsp;JWT는&amp;nbsp;사용자(브라우저)가&amp;nbsp;가지고&amp;nbsp;있다가&amp;nbsp;요청과&amp;nbsp;함께&amp;nbsp;제출하는&amp;nbsp;것이다. &lt;br /&gt;&lt;br /&gt;여기서 주목할 것은 사용자가 브라우저라는 것이다.&lt;br /&gt;이&amp;nbsp;말의&amp;nbsp;뜻은&amp;nbsp;무엇인가? &lt;br /&gt;&lt;br /&gt;일반적으로 MSA환경에서는 프론트와 백엔드가 분리되어 있는 경우가 많다.&lt;br /&gt;MSA철학으로도&amp;nbsp;그러하다. &lt;br /&gt;&lt;br /&gt;그런데 프론트는 html과 css, 그리고 js(혹은 ts)의 코드 뭉치일 뿐이다.&lt;br /&gt;이것을&amp;nbsp;화면에&amp;nbsp;표시해주는&amp;nbsp;것은&amp;nbsp;브라우저이다. &lt;br /&gt;&lt;br /&gt;그리고 브라우저는 기본적으로 모든것을 공개한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 당신의 모든 프론트 코드는 공개되어있다는 가정이 필요하다. &lt;br /&gt;&lt;br /&gt;실제로 크롬에서 F12를 통해 개발자 도구로 들어가면, Elements에서 html, css파일을, Sources에서 JS(TS는 JS로 변환되어 올라간다)를 확인할 수 있다. &lt;br /&gt;&lt;br /&gt;이 상황에서 토큰을 발급받을 때 필요한 고정된 비밀키를 JS에 넣을 수는 없을 것이다.(공개가 되어있기 때문에)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼&amp;nbsp;사용자&amp;nbsp;인증을&amp;nbsp;어떻게&amp;nbsp;진행해야&amp;nbsp;하는가? &lt;br /&gt;&lt;br /&gt;바로&amp;nbsp;PKCE&amp;nbsp;방식이다. &lt;br /&gt;&lt;br /&gt;OAuth2&amp;nbsp;인증에서&amp;nbsp;토큰을&amp;nbsp;발급받으려면&amp;nbsp;원래&amp;nbsp;서버가&amp;nbsp;client_secret이라는&amp;nbsp;고정된&amp;nbsp;비밀키를&amp;nbsp;인증&amp;nbsp;서버에&amp;nbsp;제출해야&amp;nbsp;한다.&amp;nbsp;그런데&amp;nbsp;아까도&amp;nbsp;말했지만&amp;nbsp;React,&amp;nbsp;Vue,&amp;nbsp;Angular같은&amp;nbsp;현대적&amp;nbsp;브라우저&amp;nbsp;기반&amp;nbsp;앱(SPA)은&amp;nbsp;소스&amp;nbsp;코드가&amp;nbsp;100%&amp;nbsp;노출된다는&amp;nbsp;가정이&amp;nbsp;있다. &lt;br /&gt;&lt;br /&gt;그래서&amp;nbsp;JS든&amp;nbsp;어디든&amp;nbsp;비밀키를&amp;nbsp;집어넣으면&amp;nbsp;곧바로&amp;nbsp;노출되어&amp;nbsp;문제가&amp;nbsp;발생한다. &lt;br /&gt;&lt;br /&gt;이때&amp;nbsp;사용하는&amp;nbsp;것이&amp;nbsp;PKCE&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;3840&quot; data-origin-height=&quot;2498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg6iC2/dJMcag6LW46/XqFPkJ0afIpMg1rOG0Hd1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg6iC2/dJMcag6LW46/XqFPkJ0afIpMg1rOG0Hd1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg6iC2/dJMcag6LW46/XqFPkJ0afIpMg1rOG0Hd1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg6iC2%2FdJMcag6LW46%2FXqFPkJ0afIpMg1rOG0Hd1k%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;520&quot; height=&quot;338&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;그림이&amp;nbsp;복잡해&amp;nbsp;보이지만&amp;nbsp;별거&amp;nbsp;없다. &lt;br /&gt;&lt;br /&gt;PKCE&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;더&amp;nbsp;있다.&amp;nbsp;인증&amp;nbsp;코드가&amp;nbsp;중간에&amp;nbsp;탈취되더라도,&amp;nbsp;code_verifier가&amp;nbsp;없으면&amp;nbsp;토큰&amp;nbsp;교환&amp;nbsp;자체가&amp;nbsp;불가능하게&amp;nbsp;만드는&amp;nbsp;것이다. &lt;br /&gt;&lt;br /&gt;브라우저는 일회용 난수(Verifier)를 생성하고, 이를 암호화한 챌린지(Challenge)를 생성한다.&lt;br /&gt;그리고 로그인 요청과 동시에 Challenge를 함께 전송한다.&lt;br /&gt;그리고 인증서버는 Challenge를 저장하며 로그인 완료 후 코드를 발급하고, 다시 브라우저는 코드에 원본 Verifier를 더해 토큰 교환을 요청한다.&lt;br /&gt;그럼&amp;nbsp;인증서버는&amp;nbsp;Challenge와&amp;nbsp;Verifier를&amp;nbsp;대조해서&amp;nbsp;검증이&amp;nbsp;성공하면&amp;nbsp;JWT를&amp;nbsp;주는&amp;nbsp;방식이다. &lt;br /&gt;&lt;br /&gt;말이 길었지만, 요점은 MSA환경에서, 특히 React등 SPA를 사용할 경우 고정된 비밀키를 숨길 수 없기에 매번 로그인할 때마다 동적으로 일회용 암호 장치를 만드는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설령 코드가 탈취되더라도 Verifier가 없으면 토큰 교환이 불가능하다는 보안 장치도 함께 갖추고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식을 PKCE라고 하며, 현재 SPA 등 Public Client에서의 OAuth2 인증의 사실상 표준이다. &lt;br /&gt;&lt;br /&gt;위의&amp;nbsp;방식으로&amp;nbsp;인증&amp;nbsp;서버를&amp;nbsp;직접&amp;nbsp;구축하려면&amp;nbsp;많은&amp;nbsp;인력과&amp;nbsp;시간이&amp;nbsp;소요되며,&amp;nbsp;관리도&amp;nbsp;어렵다. &lt;br /&gt;&lt;br /&gt;이때&amp;nbsp;사용하는&amp;nbsp;것이&amp;nbsp;Keycloak같은&amp;nbsp;오픈소스&amp;nbsp;인증&amp;nbsp;솔루션이다. &lt;br /&gt;&lt;br /&gt;Keycloak은&amp;nbsp;위의&amp;nbsp;절차를&amp;nbsp;내부적으로&amp;nbsp;탑재하여&amp;nbsp;관리에&amp;nbsp;필요한&amp;nbsp;리소스를&amp;nbsp;줄여준다.&lt;/p&gt;</description>
      <category>개발/KeyCloak</category>
      <category>keycloak</category>
      <author>Daramu</author>
      <guid isPermaLink="true">https://daramu.tistory.com/89</guid>
      <comments>https://daramu.tistory.com/89#entry89comment</comments>
      <pubDate>Mon, 1 Jun 2026 15:18:50 +0900</pubDate>
    </item>
    <item>
      <title>Spring boot Dockerfile</title>
      <link>https://daramu.tistory.com/88</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 부트를 이미지로 만들기 위한 도커 파일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트는 Gradle - Kotlin로 생성했고, JDK는 17을 사용중이다.&lt;/p&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 17 기준이며, 자바 버전이 다르다면 두개의 베이스 이미지(FROM)을 변경하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780038644155&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM eclipse-temurin:17-jdk-alpine AS builder
WORKDIR /build

COPY gradlew .
RUN chmod +x ./gradlew

COPY gradle gradle
COPY build.gradle.kts settings.gradle.kts ./

RUN ./gradlew dependencies --no-daemon || true

COPY src src
RUN ./gradlew bootJar -x test --no-daemon

RUN java -Djarmode=layertools -jar build/libs/*-SNAPSHOT.jar extract

FROM eclipse-temurin:17-jre-alpine
WORKDIR /app

RUN apk add --no-cache tzdata &amp;amp;&amp;amp; \
    cp /usr/share/zoneinfo/Asia/Seoul /etc/localtime &amp;amp;&amp;amp; \
    echo &quot;Asia/Seoul&quot; &amp;gt; /etc/timezone

RUN addgroup -S spring &amp;amp;&amp;amp; adduser -S spring -G spring
USER spring:spring

COPY --from=builder /build/dependencies/ ./
COPY --from=builder /build/spring-boot-loader/ ./
COPY --from=builder /build/snapshot-dependencies/ ./
COPY --from=builder /build/application/ ./

EXPOSE 8080

ENTRYPOINT [&quot;java&quot;, &quot;org.springframework.boot.loader.launch.JarLauncher&quot;]&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_1780038708005&quot; class=&quot;vbnet&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;# JDK 26이라면
FROM eclipse-temurin:26-jdk-alpine AS builder
FROM eclipse-temurin:26-jre-alpine&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/Spring boot</category>
      <author>Daramu</author>
      <guid isPermaLink="true">https://daramu.tistory.com/88</guid>
      <comments>https://daramu.tistory.com/88#entry88comment</comments>
      <pubDate>Fri, 29 May 2026 16:13:49 +0900</pubDate>
    </item>
    <item>
      <title>SSH 포트는 살아있지만 로그인이 안될때</title>
      <link>https://daramu.tistory.com/87</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;telnet이나 nc로 SSH 포트는 살아있고(Listen 중이고) 문제 없어 보이지만 로그인이 안될때가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 계정의 패스워드 만료인 경우가 많기에 예비 계정으로 접속하거나 NCP의 싱글모드 부팅, AWS의 직렬 콘솔로 GRUB단에 접속해서 패스워드 만료를 풀어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-copy-service-computed-style=&quot;font-family: monospace; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot;&gt;chage -M &lt;/span&gt;&lt;span data-copy-service-computed-style=&quot;font-family: monospace; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(180, 89, 8);&quot;&gt;999999&lt;/span&gt;&lt;span data-copy-service-computed-style=&quot;font-family: monospace; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);&quot;&gt;&amp;nbsp;root&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chage -d $(&lt;span&gt;date&lt;/span&gt; +%Y-%m-%d) {계정명}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) chage -d $(&lt;span&gt;date&lt;/span&gt; +%Y-%m-%d) 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;위 명령어로 패스워드 최종 변경일을 오늘로 변경할 수 있다.&lt;/p&gt;</description>
      <category>네트워크</category>
      <author>Daramu</author>
      <guid isPermaLink="true">https://daramu.tistory.com/87</guid>
      <comments>https://daramu.tistory.com/87#entry87comment</comments>
      <pubDate>Fri, 29 May 2026 15:59:45 +0900</pubDate>
    </item>
    <item>
      <title>스프링부트(Spring boot) 설정을 통해 LGTM으로 트레이스, 로그 등 여러 정보를 한번에 모니터링</title>
      <link>https://daramu.tistory.com/86</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 토스팅을 통해 LGTM + OTel 스택을 설치했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://daramu.tistory.com/84&quot;&gt;NCP(Naver Cloud Platform)에서 LGTM + Otel Collector 을 사용한 모니터링 설치 - dev :: Daramu&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1779953992101&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;NCP(Naver Cloud Platform)에서 LGTM + Otel  Collector 을 사용한 모니터링 설치 - dev&quot; data-og-description=&quot;LGTM 스택 사용을 통한 모니터링 구축을 진행할 것이다. 본 포스팅은 각 컴포넌트를 간소화 하여 설치한 버전을 할 것이며, Prod용을 원한다면 해당 포스팅 참고 바란다. LGTM은 Loki, Grafana, Tempo, Mimir&quot; data-og-host=&quot;daramu.tistory.com&quot; data-og-source-url=&quot;https://daramu.tistory.com/84&quot; data-og-url=&quot;https://daramu.tistory.com/84&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rPiNT/dJMb9aKLGfd/ttzLZElmw77VKEM5iYFsnK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cuchHi/dJMb84X5osI/f4ZzWjFhjpTVGnkJSaOSh0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/nu0vS/dJMb88GbDNO/RBxANU0BKL4UxwhKWPJSK1/img.png?width=920&amp;amp;height=417&amp;amp;face=0_0_920_417&quot;&gt;&lt;a href=&quot;https://daramu.tistory.com/84&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://daramu.tistory.com/84&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rPiNT/dJMb9aKLGfd/ttzLZElmw77VKEM5iYFsnK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cuchHi/dJMb84X5osI/f4ZzWjFhjpTVGnkJSaOSh0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/nu0vS/dJMb88GbDNO/RBxANU0BKL4UxwhKWPJSK1/img.png?width=920&amp;amp;height=417&amp;amp;face=0_0_920_417');&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;NCP(Naver Cloud Platform)에서 LGTM + Otel Collector 을 사용한 모니터링 설치 - dev&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;LGTM 스택 사용을 통한 모니터링 구축을 진행할 것이다. 본 포스팅은 각 컴포넌트를 간소화 하여 설치한 버전을 할 것이며, Prod용을 원한다면 해당 포스팅 참고 바란다. LGTM은 Loki, Grafana, Tempo, Mimir&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;daramu.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;&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;설치 단계에서 함께 했다면 &quot;opentelemetry-operator&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;오퍼레이터는 java agent를 자동으로 주입해 주지만, 트리거가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서는 operator 설치 &amp;rarr; Instrumentation 리소스 생성 &amp;rarr; 앱 Pod에 annotation 추가 이며, 이때 annotation 추가를 트리거로 agent를 자동으로 주입해준다.(init 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;그럼 default namespace에 있는 spring boot 어플리케이션에 자동으로 주입되는지 바로 테스트를 진행하겠다.&lt;/p&gt;
&lt;pre id=&quot;code_1779954194787&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# instrumentation.yaml
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: otel-instrumentation
  namespace: default
spec:
  exporter:
    endpoint: http://otel-daemonset-collector.monitoring.svc:4317
  propagators:
    - tracecontext
    - baggage
  sampler:
    type: parentbased_traceidratio
    argument: &quot;1.0&quot;
  java:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:latest
    env:
      - name: OTEL_EXPORTER_OTLP_PROTOCOL
        value: grpc&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1779954215204&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl apply -f instrumentation.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;그리고 deployment에 annotation을 추가해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1779954238097&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl patch deployment &amp;lt;앱이름&amp;gt; -n default -p '{
  &quot;spec&quot;: {
    &quot;template&quot;: {
      &quot;metadata&quot;: {
        &quot;annotations&quot;: {
          &quot;instrumentation.opentelemetry.io/inject-java&quot;: &quot;otel-instrumentation&quot;
        }
      }
    }
  }
}'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 이름이 &quot;cicd-demo-backend&quot; 라면 &amp;lt;앱이름&amp;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;그럼 deployment에 이렇게 추가된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;687&quot; data-origin-height=&quot;75&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cz6j27/dJMcabYG5nH/RvrNZzhLFoq9X0ZNHA7yr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cz6j27/dJMcabYG5nH/RvrNZzhLFoq9X0ZNHA7yr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cz6j27/dJMcabYG5nH/RvrNZzhLFoq9X0ZNHA7yr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcz6j27%2FdJMcabYG5nH%2FRvrNZzhLFoq9X0ZNHA7yr0%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;687&quot; height=&quot;75&quot; data-origin-width=&quot;687&quot; data-origin-height=&quot;75&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;물론 직접 spec.template.metadata.annotations.instrumentation.opentelemetry.io/inject-java: otel-instrumentation 을 넣어도 상관없다. 오히려 이게 더 좋아보이나, 현재 CI/CD 구조가 여러개를 테스트 중에 꼬여있어서 수정 전 kubectl로 임시 테스트 해보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 namepsace에 따라 Instrumentation 와 deployment에 주입되는 값은 달라져야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가령 backend네임스페이스에 배포 했다면, Instrumentation의 namespace: Instrumentation이고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;deployment의 annotation은 backend/otel-instrumentation 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 화면처럼 Explore -&amp;gt; Tempo에서 트레이스를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 간단한 스프링부트앱이라 / GET 요청밖에 없지만 복잡한 API나 DB연결이 있다면 해당 정보까지 한번에 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1936&quot; data-origin-height=&quot;913&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LxzjB/dJMcada2J2S/vXyR9C9ap7jD2BcJkOckNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LxzjB/dJMcada2J2S/vXyR9C9ap7jD2BcJkOckNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LxzjB/dJMcada2J2S/vXyR9C9ap7jD2BcJkOckNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLxzjB%2FdJMcada2J2S%2FvXyR9C9ap7jD2BcJkOckNk%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;1936&quot; height=&quot;913&quot; data-origin-width=&quot;1936&quot; data-origin-height=&quot;913&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;그런데 트레이스만 보는 것이 아닌, 해당 트레이스에 연계된 로그까지 볼 수 있는 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 otel operator으로 트레이스는 자동으로 수집했지만,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트레이스와 연계하기 위해서 로그에 Logback이 JSON 형식으로 로그를 출력해야한다. 이 설정을 진행할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링부트 버전에 따라 다른데, 만약 사용하는 스프링 부트 버전이 3.4 이상이라면 별도의 설정 없이 application.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;스프링 부트 3.4이후부터는 structured logging이 내장되어 있기에 그냥 로그 설정만 하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1780013958247&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#application.yaml
logging:
  structured:
    format:
      console: ecs&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;그럼 이제 어플리케이션이 올라올때 &quot;kubectl get pod&quot; 로 pod 네임 확인 후 &quot;kubectl logs {pod_name}&quot; 을 입력하면 아래처럼 span_id와 trace_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;참고로 Trace_id 는 시작부터 끝까지 동작하는 모든 작업의 관리 단위이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가령 누군가의 요청으로 스프링 부트가Controller 에서 요청을 받고 DB를 조회한다면, 이 일련의 모든 작업에는 동일한 Trace_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;그리고 span_id는 각 작업마다 붙는 식별자로, Controller에 요청을 보내는 span_id 한개, DB를 조회하는 다른 span_id한개 이런식으로 구성되며, 결과적으로 span_id는 trace_id의 하위에 존재하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXqc51/dJMcagFIOVb/PjgfX6hFKfqZOXfFlSHkV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXqc51/dJMcagFIOVb/PjgfX6hFKfqZOXfFlSHkV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXqc51/dJMcagFIOVb/PjgfX6hFKfqZOXfFlSHkV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXqc51%2FdJMcagFIOVb%2FPjgfX6hFKfqZOXfFlSHkV1%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;712&quot; height=&quot;72&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;72&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로 돌아가서 Tempo나 Loki로 들어간다. TraceID 기준이라 상관 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Search에서 원하는 트래픽의 Trace ID를 누르면 트레이스를 확인할 수 있다. 단순 GET 요청이라 한개의 span밖에 없지만, 만약 여러개의 복잡한 span으로 이루어진 trace라면 그렇게 뜰 것이다.&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;1889&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byMiPd/dJMcabEmuC9/FzBIEzeenDLCn9tc196dK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byMiPd/dJMcabEmuC9/FzBIEzeenDLCn9tc196dK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byMiPd/dJMcabEmuC9/FzBIEzeenDLCn9tc196dK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyMiPd%2FdJMcabEmuC9%2FFzBIEzeenDLCn9tc196dK0%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;1889&quot; height=&quot;660&quot; data-origin-width=&quot;1889&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 오른쪽에 Logs for this span을 누른다면, Loki에서 바로 로그를 확인할 수 있다.&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;2211&quot; data-origin-height=&quot;786&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCgcSF/dJMcajh4StT/H99KJKu7ZEYi3ZJtvtOH71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCgcSF/dJMcajh4StT/H99KJKu7ZEYi3ZJtvtOH71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCgcSF/dJMcajh4StT/H99KJKu7ZEYi3ZJtvtOH71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCgcSF%2FdJMcajh4StT%2FH99KJKu7ZEYi3ZJtvtOH71%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;2211&quot; height=&quot;786&quot; data-origin-width=&quot;2211&quot; data-origin-height=&quot;786&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;위의 로그는 GET요청밖에 없기에 모니터링 테스트를 위해 임의로 LoggerFactory로 생성한 콘솔 로그다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oS9XA/dJMcadhT2Ii/ZSGLBGZVN1DZ41XYjgtPSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oS9XA/dJMcadhT2Ii/ZSGLBGZVN1DZ41XYjgtPSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oS9XA/dJMcadhT2Ii/ZSGLBGZVN1DZ41XYjgtPSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoS9XA%2FdJMcadhT2Ii%2FZSGLBGZVN1DZ41XYjgtPSk%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;679&quot; height=&quot;142&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;142&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 트레이스, 로그, 매트릭 여러 종류의 로그를 한번에 볼 수 있는 LGTM + OTEL 스택을 마무리 하겠다.&lt;/p&gt;</description>
      <category>Monitoring/LGTM + Otel</category>
      <category>LGTM</category>
      <category>lgtm + otel</category>
      <category>lgtm 스택</category>
      <category>otel 모니터링</category>
      <category>모니터링</category>
      <category>트레이스 모니터링</category>
      <author>Daramu</author>
      <guid isPermaLink="true">https://daramu.tistory.com/86</guid>
      <comments>https://daramu.tistory.com/86#entry86comment</comments>
      <pubDate>Fri, 29 May 2026 11:10:42 +0900</pubDate>
    </item>
    <item>
      <title>NCP(Naver Cloud Platform)에서 LGTM + Otel  Collector 을 사용한 모니터링 설치 - prod용</title>
      <link>https://daramu.tistory.com/85</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;LGTM 스택 사용을 통한 모니터링 구축을 진행할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 포스팅은 각 컴포넌트를 권장사양으로 설치한 버전을 할 것이며, Dev용을 원한다면 해당 포스팅 참고 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://daramu.tistory.com/84&quot;&gt;NCP(Naver Cloud Platform)에서 LGTM + Otel Collector 을 사용한 모니터링 설치 - dev :: Daramu&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1779951981320&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;NCP(Naver Cloud Platform)에서 LGTM + Otel  Collector 을 사용한 모니터링 설치 - dev&quot; data-og-description=&quot;LGTM 스택 사용을 통한 모니터링 구축을 진행할 것이다. 본 포스팅은 각 컴포넌트를 간소화 하여 설치한 버전을 할 것이며, Prod용을 원한다면 해당 포스팅 참고 바란다. LGTM은 Loki, Grafana, Tempo, Mimir&quot; data-og-host=&quot;daramu.tistory.com&quot; data-og-source-url=&quot;https://daramu.tistory.com/84&quot; data-og-url=&quot;https://daramu.tistory.com/84&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rPiNT/dJMb9aKLGfd/ttzLZElmw77VKEM5iYFsnK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cuchHi/dJMb84X5osI/f4ZzWjFhjpTVGnkJSaOSh0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/nu0vS/dJMb88GbDNO/RBxANU0BKL4UxwhKWPJSK1/img.png?width=920&amp;amp;height=417&amp;amp;face=0_0_920_417&quot;&gt;&lt;a href=&quot;https://daramu.tistory.com/84&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://daramu.tistory.com/84&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rPiNT/dJMb9aKLGfd/ttzLZElmw77VKEM5iYFsnK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cuchHi/dJMb84X5osI/f4ZzWjFhjpTVGnkJSaOSh0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/nu0vS/dJMb88GbDNO/RBxANU0BKL4UxwhKWPJSK1/img.png?width=920&amp;amp;height=417&amp;amp;face=0_0_920_417');&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;NCP(Naver Cloud Platform)에서 LGTM + Otel Collector 을 사용한 모니터링 설치 - dev&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;LGTM 스택 사용을 통한 모니터링 구축을 진행할 것이다. 본 포스팅은 각 컴포넌트를 간소화 하여 설치한 버전을 할 것이며, Prod용을 원한다면 해당 포스팅 참고 바란다. LGTM은 Loki, Grafana, Tempo, Mimir&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;daramu.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;LGTM은 Loki, Grafana, Tempo, Mimir 로, 각각 logs, 시각화(UI), trace, temric 을 담당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가령 앱/서비스 가 있다고 한다면, 각 앱에 OTLP(Open Telemetry Protocol)을 보내도록 설정을 진행하고, OTC(OpenTelemetry Collector 설정을 통해 OTLP 입력을 받는다.&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;그리고 입력 받은 모든 데이터를 각각의 백엔드(Loki, Temp, Mimir)로 보내 데이터를 보관 및 처리하며, 결과적으로 Grafana를 통해 시각화를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도식화를 하면 아래와 같이 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱/서비스 &lt;br /&gt;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;OTLP&amp;nbsp;(gRPC:4317&amp;nbsp;/&amp;nbsp;HTTP:4318) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;OTel&amp;nbsp;Collector&amp;nbsp;(DaemonSet)&amp;nbsp;──►&amp;nbsp;Loki&amp;nbsp;&amp;nbsp;&amp;nbsp;(logs) &lt;br /&gt;&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;&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;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;──►&amp;nbsp;Tempo&amp;nbsp;&amp;nbsp;(traces) &lt;br /&gt;&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;&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;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;──►&amp;nbsp;Mimir&amp;nbsp;&amp;nbsp;(metrics) &lt;br /&gt;&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;&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;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;Grafana&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b46iGj/dJMcagllhSJ/khX7Ko2AtvnakPHCu7XU80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b46iGj/dJMcagllhSJ/khX7Ko2AtvnakPHCu7XU80/img.png&quot; data-alt=&quot;출처: Grafana Labs 공식 페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b46iGj/dJMcagllhSJ/khX7Ko2AtvnakPHCu7XU80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb46iGj%2FdJMcagllhSJ%2FkhX7Ko2AtvnakPHCu7XU80%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;742&quot; height=&quot;301&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;301&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: Grafana Labs 공식 페이지&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;백엔드가 Spring boot라고 했을때, OTLP 프로토콜을 사용하여 서버를 실행할때 JAR 파일만 주입하여 사용하는 방식을 주로 사용한다. 이 JAR파일을 통해 Spring boot내부의 모든 라이브러리(Web, JPA, Logback..etc..)를 자동으로 감지하여 트레이스, 메트릭, 로그를 OTLP로 수집한다.&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;여기에 쿠버네티스라면 OpenTelemetry Opertor(오텔 오퍼레이터)는 쿠버네티스 환경에서 Otel Collector를 자동으로 띄워 애플리케이션 코드를 건드리지 않고, Otel SDK를 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;그림에서 보이듯이, 총 6개의 Helm을 기본으로 설치해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mimir, Loki, Tempo, Grafana, Otel Operator(Pod 자동 수집), Otel Collector(노드 수집) 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거기에 내부 HTTPS 통신을 위한 cert-manager 까지 더해져 총 7개의 helm chat 설치가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오텔 오퍼레이터는 웹훅 통신시에 내부 HTTPS 인증서가 필수이기에 cert-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;cert-manager에 대한 내용이 궁금하면 이전 포스팅 참고바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://daramu.tistory.com/83&quot;&gt;Cert Manager 란 무엇인가? :: Daramu&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1779935987009&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;Cert Manager 란 무엇인가?&quot; data-og-description=&quot;HTTPS 통신을 위해서는 SSL/TLS 인증서 관리가 필요하다.만료일을 깜빡하면 HTTPS 통신이 안되어 서비스가 마비되는 대참사가 간혹 나오고 있는데, 쿠버네티스에서는 cert-manager를 사용하여 이 같은 &quot; data-og-host=&quot;daramu.tistory.com&quot; data-og-source-url=&quot;https://daramu.tistory.com/83&quot; data-og-url=&quot;https://daramu.tistory.com/83&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/OPC97/dJMb83SpAt8/PqhQ1RxUtdu6LKDuEMMJy0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/wTwhH/dJMb85WZYjP/0QwI1CvF36Frldhj70wRt0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/JYqze/dJMb84X5mj8/MkHRKoSioMAisKzbkKbtlK/img.png?width=862&amp;amp;height=508&amp;amp;face=0_0_862_508&quot;&gt;&lt;a href=&quot;https://daramu.tistory.com/83&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://daramu.tistory.com/83&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/OPC97/dJMb83SpAt8/PqhQ1RxUtdu6LKDuEMMJy0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/wTwhH/dJMb85WZYjP/0QwI1CvF36Frldhj70wRt0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/JYqze/dJMb84X5mj8/MkHRKoSioMAisKzbkKbtlK/img.png?width=862&amp;amp;height=508&amp;amp;face=0_0_862_508');&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;Cert Manager 란 무엇인가?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;HTTPS 통신을 위해서는 SSL/TLS 인증서 관리가 필요하다.만료일을 깜빡하면 HTTPS 통신이 안되어 서비스가 마비되는 대참사가 간혹 나오고 있는데, 쿠버네티스에서는 cert-manager를 사용하여 이 같은&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;daramu.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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana 공식 재단에서 grafana/lgtm-distributed 같은 통합 차트를 쓰면 하나의 명령어로 LGTM 컴포넌트를 한번에 띄울 수 있다. 하지만 이 방식을 사용하면 각기 다른 설정을 할때 어려움이 있으므로, 본 포스팅에서는 각각 설치하여 설정하는 것을 목표로 하겠다.&lt;/p&gt;
&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_1779935926950&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#helm 레포 등록
helm repo add grafana https://grafana.github.io/helm-charts
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm repo add jetstack https://charts.jetstack.io
helm repo update

#Namespace 생성
kubectl create namespace 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;Otel Operator의 Webhook 연결시 TLS인증서가 필수이기에 cert-manager또한 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1779936003600&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set crds.enabled=true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다음 단계로 오브젝트 스토리지 접근을 위한 Secert을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 로그는 OBJ에 저장될 것이므로, AWS라면 S3 연결을 위한 설정을 진행한다고 생각하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 포스팅은 NCP(Naver Cloud Platform)을 사용했다.&lt;/p&gt;
&lt;pre id=&quot;code_1779936058287&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create secret generic ncp-objstore-secret -n monitoring \
  --from-literal=ACCESS_KEY_ID=&amp;lt;실제값&amp;gt; \
  --from-literal=SECRET_ACCESS_KEY=&amp;lt;실제값&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;이제 매트릭을 담당할 Mimir를 설치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 key 부분은 secret으로 만들었으나, bucket_name은 실제 object storage 이름으로 맞춘다. endpoint의 경우 민간 수도권은 아래 예시와 같으며, 공공플랫폼일 경우 &quot;&lt;span style=&quot;background-color: #ffffff; color: #18181b; text-align: left;&quot;&gt;kr.object.gov-ncloudstorage.com&lt;/span&gt; &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;추가로 ncp nks의 경우, 쿠버네티스 내부 DNS로 coredns를 사용하고 있다.&amp;nbsp;&lt;br /&gt;&quot;kubectl get service -n kube-system&quot; 을 입력할 경우 아래 처럼 coredns라는 서비스를 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mimir는 최초 동작시 DNS에 질의를 해야하는데, 기본값으로 두면 kube-dns라는 과거 서비스를 바라보고 있기에 mimir gateway가 작동하지 않을 수 있다. global 설정에서 dns설정에 dnsservice와 namespace, clusterdomain을 넣어둔다. 아래 예시는 ncp의 nks기준이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1779936141507&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#mimir-values.yaml
mimir:
  structuredConfig:
    common:
      storage:
        backend: s3
        s3:
          endpoint: kr.object.ncloudstorage.com
          region: kr-standard
          access_key_id: &quot;${ACCESS_KEY_ID}&quot;
          secret_access_key: &quot;${SECRET_ACCESS_KEY}&quot;
          insecure: false

    blocks_storage:
      s3:
        bucket_name: mimir-blocks

    alertmanager_storage:
      s3:
        bucket_name: mimir-alertmanager

    ruler_storage:
      s3:
        bucket_name: mimir-ruler

global:
  extraEnvFrom:
    - secretRef:
        name: ncp-objstore-secret
  dnsService: &quot;coredns&quot;
  dnsNamespace: &quot;kube-system&quot;
  clusterDomain: &quot;cluster.local&quot;

# 소규모면 monolithic 모드 사용
# 프로덕션 규모면 distributed 권장
minio:
  enabled: false  # NCP Object Storage 사용하므로 비활성화

ingester:
  replicas: 2
  zoneAwareReplication:
    enabled: false

store_gateway:
  replicas: 1

compactor:
  replicas: 1
  
gateway:
  enabled: true
  nginx:
    config:
      enableIPv6: false&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1779936193113&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm install mimir grafana/mimir-distributed \
  --namespace monitoring \
  -f mimir-values.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 생성시 pod가 많이 죽을 수 있다. mimir는 내부적으로 kafka를 사용하는데, kafka pod가 뜨기 전에 다른 pod가 kafka를 사용하지 못해 Error 가 나는 것이다. kafka가 running 1/1로 바뀐 후 시간이 지나면 pod재시작으로 running으로 바뀐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 Log를 담당할 Loki를 설치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일하게 object storage 생성 후 만들어준다.&lt;/p&gt;
&lt;pre id=&quot;code_1779936221701&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;deploymentMode: SimpleScalable

loki:
  auth_enabled: false
  storage:
    type: s3
    bucketNames:
      chunks: loki-chunks
      chunks_cache: loki-chunks
      admin: loki-chunks
      ruler: loki-chunks
    s3:
      endpoint: https://kr.object.ncloudstorage.com
      region: kr-standard
      s3ForcePathStyle: true
      accessKeyId: ${ACCESS_KEY_ID}
      secretAccessKey: ${SECRET_ACCESS_KEY}

  schemaConfig:
    configs:
      - from: &quot;2024-01-01&quot;
        store: tsdb
        object_store: s3
        schema: v13
        index:
          prefix: loki_index_
          period: 24h

  limits_config:
    allow_structured_metadata: true
    otlp_config:
      resource_attributes:
        attributes_config:
          - action: index_label
            attributes:
              - k8s.namespace.name
              - k8s.pod.name
              - service.name

backend:
  replicas: 2
  persistence:
    storageClass: nks-block-storage

read:
  replicas: 2

write:
  replicas: 2

minio:
  enabled: false

gateway:
  enabled: true
  nginxConfig:
    enableIPv6: false

global:
  dnsService: &quot;coredns&quot;
  dnsNamespace: &quot;kube-system&quot;
  clusterDomain: &quot;cluster.local&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1779936228837&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm install loki grafana/loki \
  --namespace monitoring \
  -f loki-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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 trace를 담당할 Tempo를 설치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜인지 key값의 시크릿을 참조 못하는 경우가 있는데, 그때는 하드코딩해서 집어넣어야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1779936267140&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#tempo-values.yaml
storage:
  trace:
    backend: s3
    s3:
      bucket: tempo-traces
      endpoint: kr.object.ncloudstorage.com
      region: kr-standard
      access_key: ${ACCESS_KEY_ID}
      secret_key: ${SECRET_ACCESS_KEY}
      forcepathstyle: true

tempo:
  receivers:
    otlp:
      protocols:
        grpc:
          endpoint: 0.0.0.0:4317
        http:
          endpoint: 0.0.0.0:4318

  storage:
    trace:
      wal:
        path: /var/tempo/wal
      local:
        path: /var/tempo/blocks

  metricsGenerator:
    enabled: true
    remoteWriteUrl: &quot;http://mimir-nginx.monitoring.svc:80/api/v1/push&quot;

persistence:
  enabled: true
  storageClassName: nks-block-storage
  size: 10Gi

traces:
  otlp:
    grpc:
      enabled: true
    http:
      enabled: true&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1779936275681&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm install tempo grafana/tempo-distributed \
  --namespace monitoring \
  -f tempo-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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 시각화를 담당할 Grafana를 설치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPN&amp;nbsp; 환경이기에 annotations에서 private을 선언했다. 공인IP가 필요하면 해당 annotations를 삭제하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1779936306204&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#grafana-values.yaml
adminPassword: &quot;your-secure-password&quot;

persistence:
  enabled: true
  storageClassName: nks-block-storage
  size: 10Gi
  
service:
  type: NodePort

ingress:
  enabled: true
  ingressClassName: alb
  annotations:
    alb.ingress.kubernetes.io/network-type: &quot;private&quot;
  hosts:
    - grafana.yourdomain.com # 도메인으로 수정

datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
      - name: Mimir
        type: prometheus
        url: http://mimir-gateway.monitoring.svc:80/prometheus
        isDefault: true
        jsonData:
          httpMethod: POST
          httpHeaderName1: &quot;X-Scope-OrgID&quot;
          prometheusType: Mimir
        secureJsonData:
          httpHeaderValue1: &quot;anonymous&quot;

      - name: Loki
        type: loki
        url: http://loki-gateway.monitoring.svc:80
        jsonData:
          derivedFields:
            - datasourceUid: Tempo
              matcherType: label
              matcherRegex: trace_id
              name: TraceID
              url: &quot;${__value.raw}&quot;
              urlDisplayLabel: &quot;View in Tempo&quot;

      - name: Tempo
        type: tempo
        uid: Tempo
        url: http://tempo.monitoring.svc:3200
        jsonData:
          tracesToLogsV2:
            datasourceUid: Loki
            spanStartTimeShift: &quot;-1m&quot;
            spanEndTimeShift: &quot;1m&quot;
            filterByTraceID: true
            filterBySpanID: false
            customQuery: false
          tracesToMetrics:
            datasourceUid: Mimir
          serviceMap:
            datasourceUid: Mimir
          nodeGraph:
            enabled: true&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1779936314782&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm install grafana grafana/grafana \
  --namespace monitoring \
  -f grafana-values.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ingress(LB)생성 후 hosts 부분 수정하여 접속해보는것도 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 어플리케이션에 SDK를 Pod에 자동 주입하여 Pod의 정보를 가져오는 오텔 오퍼레이터를 설치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;admissionWebhooks.certManager.enabled=true&quot; 옵션을 통해 오텔 오퍼레이터가 가동으로 cert-manager를 통해 Issuer와 Certificate를 생성하여 Secret를 생성 및 주입한다.&lt;/p&gt;
&lt;pre id=&quot;code_1779936365134&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm install opentelemetry-operator open-telemetry/opentelemetry-operator \
  --namespace monitoring \
  --set &quot;manager.collectorImage.repository=otel/opentelemetry-collector-contrib&quot; \
  --set admissionWebhooks.certManager.enabled=true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 노트별 로그를 수집하며 중앙 집계 및 처리를 진행할 오텔 콜렉터를 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1779949970034&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create serviceaccount otel-collector-sa -n monitoring

cat &amp;lt;&amp;lt;EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: otel-collector-sa
  namespace: monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: otel-collector-role
rules:
  - apiGroups: [&quot;&quot;]
    resources: [&quot;nodes/stats&quot;, &quot;nodes/proxy&quot;, &quot;pods&quot;, &quot;namespaces&quot;, &quot;nodes&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;apps&quot;]
    resources: [&quot;replicasets&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: otel-collector-rolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: otel-collector-role
subjects:
  - kind: ServiceAccount
    name: otel-collector-sa
    namespace: monitoring
EOF&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1779936406514&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#otelcol-daemonset.yaml
apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
  name: otel-daemonset
  namespace: monitoring
spec:
  mode: daemonset
  serviceAccount: otel-collector-sa
  config:
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
          http:
            endpoint: 0.0.0.0:4318
      kubeletstats:
        collection_interval: 30s
        auth_type: serviceAccount
        endpoint: &quot;https://${env:K8S_NODE_IP}:10250&quot;
        insecure_skip_verify: true
      filelog:
        include:
          - /var/log/pods/*/*/*.log
        include_file_path: true
        operators:
          - type: container
            id: container-parser

    processors:
      batch:
        timeout: 5s
        send_batch_size: 10000
      k8sattributes:
        passthrough: false
        extract:
          metadata:
            - k8s.pod.name
            - k8s.namespace.name
            - k8s.deployment.name
            - k8s.node.name
            - service.name
      resourcedetection:
        detectors: [env, k8snode]
        timeout: 5s
      memory_limiter:
        check_interval: 1s
        limit_mib: 400
        spike_limit_mib: 100

    exporters:
      otlphttp/loki:
        endpoint: http://loki-gateway.monitoring.svc:80/otlp
      otlp/tempo:
        endpoint: tempo.monitoring.svc:4317
        tls:
          insecure: true
      prometheusremotewrite/mimir:
        endpoint: http://mimir-gateway.monitoring.svc:80/api/v1/push
        headers:
          X-Scope-OrgID: anonymous
 
    service:
      pipelines:
        logs:
          receivers: [otlp, filelog]
          processors: [memory_limiter, k8sattributes, batch]
          exporters: [otlphttp/loki]
        traces:
          receivers: [otlp]
          processors: [memory_limiter, k8sattributes, batch]
          exporters: [otlp/tempo, spanmetrics]
        metrics:
          receivers: [otlp, kubeletstats, spanmetrics] 
          processors: [memory_limiter, k8sattributes, resourcedetection, batch]
          exporters: [prometheusremotewrite/mimir]

  volumeMounts:
    - name: varlogpods
      mountPath: /var/log/pods
      readOnly: true

  volumes:
    - name: varlogpods
      hostPath:
        path: /var/log/pods

  env:
    - name: K8S_NODE_IP
      valueFrom:
        fieldRef:
          fieldPath: status.hostIP&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1779936414228&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl apply -f otelcol-daemonset.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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 설치가 끝났다면 &quot;kubectl get pod -n monitoring&quot; 시에 pending이나 Error가 없이 여러개의 Pod가 떠있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두 runnging 중이라면 Grafana 웹사이트에 접속하여 UI에서 실제 연결을 확인해 본다.&lt;/p&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 좌측 사이드바 -&amp;gt; Commections -&amp;gt; Data sources&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;417&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kw4gM/dJMcageBCkK/32RMNoEdB92CaxbECQ3HT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kw4gM/dJMcageBCkK/32RMNoEdB92CaxbECQ3HT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kw4gM/dJMcageBCkK/32RMNoEdB92CaxbECQ3HT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKw4gM%2FdJMcageBCkK%2F32RMNoEdB92CaxbECQ3HT0%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;741&quot; height=&quot;336&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;417&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;이곳에서 Loki, Mimir, Tempo를 모두 들어가 가장 하단에 &quot;Test&quot;를 통해 Connection 을 확인해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0VAB4/dJMcabRQSqy/ixfquR9LGhotTodLnDi2P0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0VAB4/dJMcabRQSqy/ixfquR9LGhotTodLnDi2P0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0VAB4/dJMcabRQSqy/ixfquR9LGhotTodLnDi2P0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0VAB4%2FdJMcabRQSqy%2FixfquR9LGhotTodLnDi2P0%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;427&quot; height=&quot;234&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;234&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;</description>
      <category>Monitoring/LGTM + Otel</category>
      <category>LGTM</category>
      <category>lgtm + otel</category>
      <category>lgtm 스택</category>
      <category>otel 모니터링</category>
      <category>모니터링</category>
      <category>모니터링 lgtm</category>
      <author>Daramu</author>
      <guid isPermaLink="true">https://daramu.tistory.com/85</guid>
      <comments>https://daramu.tistory.com/85#entry85comment</comments>
      <pubDate>Thu, 28 May 2026 16:06:23 +0900</pubDate>
    </item>
    <item>
      <title>NCP(Naver Cloud Platform)에서 LGTM + Otel  Collector 을 사용한 모니터링 설치 - dev</title>
      <link>https://daramu.tistory.com/84</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;LGTM 스택 사용을 통한 모니터링 구축을 진행할 것이다.&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;본 포스팅은 각 컴포넌트를 간소화 하여 설치한 버전을 할 것이며, Prod용을 원한다면 해당 포스팅 참고 바란다.&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://daramu.tistory.com/85&quot;&gt;NCP(Naver Cloud Platform)에서 LGTM + Otel Collector 을 사용한 모니터링 설치 - prod용 :: Daramu&lt;/a&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;&lt;br /&gt;LGTM은 Loki, Grafana, Tempo, Mimir 로, 각각 logs, 시각화(UI), trace, temric 을 담당한다.&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;가령 앱/서비스 가 있다고 한다면, 각 앱에 OTLP(Open Telemetry Protocol)을 보내도록 설정을 진행하고, OTC(OpenTelemetry Collector 설정을 통해 OTLP 입력을 받는다.&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;그리고 입력 받은 모든 데이터를 각각의 백엔드(Loki, Temp, Mimir)로 보내 데이터를 보관 및 처리하며, 결과적으로 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;도식화를 하면 아래와 같이 구성된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;앱/서비스&lt;br /&gt;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;OTLP&amp;nbsp;(gRPC:4317&amp;nbsp;/&amp;nbsp;HTTP:4318)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;OTel&amp;nbsp;Collector&amp;nbsp;(DaemonSet)&amp;nbsp;──►&amp;nbsp;Loki&amp;nbsp;&amp;nbsp;&amp;nbsp;(logs)&lt;br /&gt;&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;&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;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;──►&amp;nbsp;Tempo&amp;nbsp;&amp;nbsp;(traces)&lt;br /&gt;&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;&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;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;──►&amp;nbsp;Mimir&amp;nbsp;&amp;nbsp;(metrics)&lt;br /&gt;&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;&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;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└─&amp;nbsp;Grafana&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;그림으로 보면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b46iGj/dJMcagllhSJ/khX7Ko2AtvnakPHCu7XU80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b46iGj/dJMcagllhSJ/khX7Ko2AtvnakPHCu7XU80/img.png&quot; data-alt=&quot;출처: Grafana Labs 공식 페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b46iGj/dJMcagllhSJ/khX7Ko2AtvnakPHCu7XU80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb46iGj%2FdJMcagllhSJ%2FkhX7Ko2AtvnakPHCu7XU80%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;742&quot; height=&quot;301&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;301&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: Grafana Labs 공식 페이지&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;백엔드가 Spring boot라고 했을때, OTLP 프로토콜을 사용하여 서버를 실행할때 JAR 파일만 주입하여 사용하는 방식을 주로 사용한다. 이 JAR파일을 통해 Spring boot내부의 모든 라이브러리(Web, JPA, Logback..etc..)를 자동으로 감지하여 트레이스, 메트릭, 로그를 OTLP로 수집한다.&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;여기에 쿠버네티스라면 OpenTelemetry Opertor(오텔 오퍼레이터)는 쿠버네티스 환경에서 Otel Collector를 자동으로 띄워 애플리케이션 코드를 건드리지 않고, Otel SDK를 Pod에 자동 주입하는 쿠버네티스 전용 자동화 관리 도구이며, 이것을 사용할 것이다.&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;그림에서 보이듯이, 총 6개의 Helm을 기본으로 설치해야한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Mimir, Loki, Tempo, Grafana, Otel Operator(Pod 자동 수집), Otel Collector(노드 수집) 이다.&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;거기에 내부 HTTPS 통신을 위한 cert-manager 까지 더해져 총 7개의 helm chat 설치가 필요하다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;오텔 오퍼레이터는 웹훅 통신시에 내부 HTTPS 인증서가 필수이기에 cert-manager는 피하기 어렵다.&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;cert-manager에 대한 내용이 궁금하면 이전 포스팅 참고바란다.&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://daramu.tistory.com/83&quot;&gt;Cert Manager 란 무엇인가? :: Daramu&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1779944893569&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;Cert Manager 란 무엇인가?&quot; data-og-description=&quot;HTTPS 통신을 위해서는 SSL/TLS 인증서 관리가 필요하다.만료일을 깜빡하면 HTTPS 통신이 안되어 서비스가 마비되는 대참사가 간혹 나오고 있는데, 쿠버네티스에서는 cert-manager를 사용하여 이 같은 &quot; data-og-host=&quot;daramu.tistory.com&quot; data-og-source-url=&quot;https://daramu.tistory.com/83&quot; data-og-url=&quot;https://daramu.tistory.com/83&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/OPC97/dJMb83SpAt8/PqhQ1RxUtdu6LKDuEMMJy0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/wTwhH/dJMb85WZYjP/0QwI1CvF36Frldhj70wRt0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/JYqze/dJMb84X5mj8/MkHRKoSioMAisKzbkKbtlK/img.png?width=862&amp;amp;height=508&amp;amp;face=0_0_862_508&quot;&gt;&lt;a href=&quot;https://daramu.tistory.com/83&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://daramu.tistory.com/83&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/OPC97/dJMb83SpAt8/PqhQ1RxUtdu6LKDuEMMJy0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/wTwhH/dJMb85WZYjP/0QwI1CvF36Frldhj70wRt0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/JYqze/dJMb84X5mj8/MkHRKoSioMAisKzbkKbtlK/img.png?width=862&amp;amp;height=508&amp;amp;face=0_0_862_508');&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;Cert Manager 란 무엇인가?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;HTTPS 통신을 위해서는 SSL/TLS 인증서 관리가 필요하다.만료일을 깜빡하면 HTTPS 통신이 안되어 서비스가 마비되는 대참사가 간혹 나오고 있는데, 쿠버네티스에서는 cert-manager를 사용하여 이 같은&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;daramu.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 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;Grafana 공식 재단에서 grafana/lgtm-distributed 같은 통합 차트를 쓰면 하나의 명령어로 LGTM 컴포넌트를 한번에 띄울 수 있다. 하지만 이 방식을 사용하면 각기 다른 설정을 할때 어려움이 있으므로, 본 포스팅에서는 각각 설치하여 설정하는 것을 목표로 하겠다.&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_1779944966677&quot; class=&quot;livecodeserver&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 레포 등록
helm repo add grafana https://grafana.github.io/helm-charts
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm repo add jetstack https://charts.jetstack.io
helm repo update

#Namespace 생성
kubectl create namespace monitoring&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;Otel Operator의 Webhook 연결시 TLS인증서가 필수이기에 cert-manager또한 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1779944966678&quot; class=&quot;oxygene&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 install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set crds.enabled=true&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;이제 다음 단계로 오브젝트 스토리지 접근을 위한 Secert을 생성한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각 로그는 OBJ에 저장될 것이므로, AWS라면 S3 연결을 위한 설정을 진행한다고 생각하면 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;본 포스팅은 NCP(Naver Cloud Platform)을 사용했다.&lt;/p&gt;
&lt;pre id=&quot;code_1779944966679&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 create secret generic ncp-objstore-secret \
  --namespace monitoring \
  --from-literal=access_key_id=&amp;lt;NCP_ACCESS_KEY&amp;gt; \
  --from-literal=secret_access_key=&amp;lt;NCP_SECRET_KEY&amp;gt;&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;이제 매트릭을 담당할 Mimir를 설치한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 중 key 부분은 secret으로 만들었으나, bucket_name은 실제 object storage 이름으로 맞춘다. endpoint의 경우 민간 수도권은 아래 예시와 같으며, 공공플랫폼일 경우 &quot;&lt;span style=&quot;background-color: #ffffff; color: #18181b; text-align: left;&quot;&gt;kr.object.gov-ncloudstorage.com&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추가로 ncp nks의 경우, 쿠버네티스 내부 DNS로 coredns를 사용하고 있다.&amp;nbsp;&lt;br /&gt;&quot;kubectl get service -n kube-system&quot; 을 입력할 경우 아래 처럼 coredns라는 서비스를 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;mimir는 최초 동작시 DNS에 질의를 해야하는데, 기본값으로 두면 kube-dns라는 과거 서비스를 바라보고 있기에 mimir gateway가 작동하지 않을 수 있다. global 설정에서 dns설정에 dnsservice와 namespace, clusterdomain을 넣어둔다. 아래 예시는 ncp의 nks기준이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1779944979635&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mimir:
  structuredConfig:
    common:
      storage:
        backend: s3
        s3:
          endpoint: kr.object.gov-ncloudstorage.com
          region: kr-standard
          access_key_id: &quot;${ACCESS_KEY_ID}&quot;
          secret_access_key: &quot;${SECRET_ACCESS_KEY}&quot;
          insecure: false
    blocks_storage:
      s3:
        bucket_name: mimir-blocks-monitoring
    alertmanager_storage:
      s3:
        bucket_name: mimir-alertmanager-monitoring
    ruler_storage:
      s3:
        bucket_name: mimir-ruler-monitoring

global:
  extraEnvFrom:
    - secretRef:
        name: ncp-objstore-secret
  dnsService: &quot;coredns&quot;
  dnsNamespace: &quot;kube-system&quot;
  clusterDomain: &quot;cluster.local&quot;

minio:
  enabled: false

ingester:
  replicas: 1
  zoneAwareReplication:
    enabled: false
  resources:
    requests:
      cpu: &quot;100m&quot;
      memory: &quot;512Mi&quot;
    limits:
      cpu: &quot;500m&quot;
      memory: &quot;1Gi&quot;

store_gateway:
  replicas: 1
  zoneAwareReplication:
    enabled: false
  resources:
    requests:
      cpu: &quot;100m&quot;
      memory: &quot;256Mi&quot;
    limits:
      cpu: &quot;500m&quot;
      memory: &quot;512Mi&quot;

compactor:
  replicas: 1
  resources:
    requests:
      cpu: &quot;100m&quot;
      memory: &quot;256Mi&quot;
    limits:
      cpu: &quot;500m&quot;
      memory: &quot;512Mi&quot;

distributor:
  replicas: 1
  resources:
    requests:
      cpu: &quot;100m&quot;
      memory: &quot;256Mi&quot;
    limits:
      cpu: &quot;500m&quot;
      memory: &quot;512Mi&quot;

querier:
  replicas: 1
  resources:
    requests:
      cpu: &quot;100m&quot;
      memory: &quot;256Mi&quot;
    limits:
      cpu: &quot;500m&quot;
      memory: &quot;512Mi&quot;

query_frontend:
  replicas: 1
  resources:
    requests:
      cpu: &quot;100m&quot;
      memory: &quot;256Mi&quot;
    limits:
      cpu: &quot;500m&quot;
      memory: &quot;512Mi&quot;

ruler:
  replicas: 1
  resources:
    requests:
      cpu: &quot;100m&quot;
      memory: &quot;256Mi&quot;
    limits:
      cpu: &quot;500m&quot;
      memory: &quot;256Mi&quot;

alertmanager:
  replicas: 1
  resources:
    requests:
      cpu: &quot;100m&quot;
      memory: &quot;256Mi&quot;
    limits:
      cpu: &quot;500m&quot;
      memory: &quot;256Mi&quot;

gateway:
  enabled: true
  nginx:
    config:
      enableIPv6: false
  resources:
    requests:
      cpu: &quot;50m&quot;
      memory: &quot;64Mi&quot;
    limits:
      cpu: &quot;200m&quot;
      memory: &quot;256Mi&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1779944995505&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm install mimir grafana/mimir-distributed \
  --namespace monitoring \
  -f mimir-values.yaml&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;참고로 생성시 pod가 많이 죽을 수 있다. mimir는 내부적으로 kafka를 사용하는데, kafka pod가 뜨기 전에 다른 pod가 kafka를 사용하지 못해 Error 가 나는 것이다. kafka가 running 1/1로 바뀐 후 시간이 지나면 pod재시작으로 running으로 바뀐다.&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;다음으로 Log를 담당할 Loki를 설치한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동일하게 object storage 생성 후 만들어준다.&lt;/p&gt;
&lt;pre id=&quot;code_1779945127214&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;deploymentMode: SingleBinary

loki:
  auth_enabled: false
  storage:
    type: s3
    bucketNames:
      chunks: loki-chunks
      admin: loki-chunks
      ruler: loki-chunks
    s3:
      endpoint: https://kr.object.ncloudstorage.com
      region: kr-standard
      s3ForcePathStyle: true
      accessKeyId: ${ACCESS_KEY_ID}
      secretAccessKey: ${SECRET_ACCESS_KEY}
  schemaConfig:
    configs:
      - from: &quot;2024-01-01&quot;
        store: tsdb
        object_store: s3
        schema: v13
        index:
          prefix: loki_index_
          period: 24h
  limits_config:
    allow_structured_metadata: true
    otlp_config:
      resource_attributes:
        attributes_config:
          - action: index_label
            attributes:
              - k8s.namespace.name
              - k8s.pod.name
              - service.name

singleBinary:
  replicas: 1
  resources:
    requests:
      cpu: &quot;100m&quot;
      memory: &quot;256Mi&quot;
    limits:
      cpu: &quot;500m&quot;
      memory: &quot;1Gi&quot;
  persistence:
    storageClass: nks-block-storage

backend:
  replicas: 0
read:
  replicas: 0
write:
  replicas: 0

minio:
  enabled: false
  
chunksCache:
  enabled: false

resultsCache:
  enabled: false

gateway:
  enabled: true
  replicas: 1
  nginxConfig:
    enableIPv6: false

global:
  dnsService: &quot;coredns&quot;
  dnsNamespace: &quot;kube-system&quot;
  clusterDomain: &quot;cluster.local&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1779945201270&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm install loki grafana/loki \
  --namespace monitoring \
  -f loki-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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 trace를 담당할 Tempo를 설치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일하게 object storage 생성 후 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜인지 key값의 시크릿을 참조 못하는 경우가 있는데, 그때는 하드코딩해서 집어넣어야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1779947571067&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# tempo-values.yaml (monolithic)
extraEnvFrom:
  - secretRef:
      name: ncp-objstore-secret

tempo:
  resources:
    requests:
      cpu: &quot;100m&quot;
      memory: &quot;256Mi&quot;
    limits:
      cpu: &quot;500m&quot;
      memory: &quot;1Gi&quot;

  storage:
    trace:
      backend: s3
      s3:
        bucket: tempo-traces
        endpoint: kr.object.ncloudstorage.com
        region: kr-standard
        access_key: ${ACCESS_KEY_ID}
        secret_key: ${SECRET_ACCESS_KEY}
        forcepathstyle: true
      wal:
        path: /var/tempo/wal
      local:
        path: /var/tempo/blocks

  receivers:
    otlp:
      protocols:
        grpc:
          endpoint: 0.0.0.0:4317
        http:
          endpoint: 0.0.0.0:4318

  metricsGenerator:
    enabled: true
    remoteWriteUrl: &quot;http://mimir-gateway.monitoring.svc:80/api/v1/push&quot;

persistence:
  enabled: true
  storageClassName: nks-block-storage
  size: 10Gi&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1779947645174&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm install tempo grafana/tempo \
  --namespace monitoring \
  -f tempo-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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 시각화를 담당할 Grafana를 설치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPN&amp;nbsp; 환경이기에 annotations에서 private을 선언했다. 공인IP가 필요하면 해당 annotations를 삭제하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1779949043324&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;adminPassword: &quot;your-secure-password&quot;

persistence:
  enabled: true
  storageClassName: nks-block-storage
  size: 10Gi

resources:
  requests:
    cpu: &quot;100m&quot;
    memory: &quot;256Mi&quot;
  limits:
    cpu: &quot;500m&quot;
    memory: &quot;512Mi&quot;

service:
  type: NodePort

ingress:
  enabled: true
  annotations:
    alb.ingress.kubernetes.io/network-type: &quot;private&quot;
  ingressClassName: alb
  hosts:
    - grafana.yourdomain.com

datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
      - name: Mimir
        type: prometheus
        url: http://mimir-gateway.monitoring.svc:80/prometheus
        isDefault: true
        jsonData:
          httpMethod: POST
          httpHeaderName1: &quot;X-Scope-OrgID&quot;
          prometheusType: Mimir
        secureJsonData:
          httpHeaderValue1: &quot;anonymous&quot;


      - name: Loki
        type: loki
        url: http://loki-gateway.monitoring.svc:80
        jsonData:
          derivedFields:
            - datasourceUid: Tempo
              matcherType: label
              matcherRegex: trace_id
              name: TraceID
              url: &quot;${__value.raw}&quot;
              urlDisplayLabel: &quot;View in Tempo&quot;

      - name: Tempo
        type: tempo
        uid: Tempo
        url: http://tempo.monitoring.svc:3200
        jsonData:
          tracesToLogsV2:
            datasourceUid: Loki
            spanStartTimeShift: &quot;-1m&quot;
            spanEndTimeShift: &quot;1m&quot;
            filterByTraceID: true
            filterBySpanID: false
            customQuery: false
          tracesToMetrics:
            datasourceUid: Mimir
          serviceMap:
            datasourceUid: Mimir
          nodeGraph:
            enabled: true&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1779949493028&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm install grafana grafana/grafana \
  --namespace monitoring \
  -f grafana-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;ingress(LB)생성 후 hosts 부분 수정하여 접속해보는것도 좋다.&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;다음으로 어플리케이션에 SDK를 Pod에 자동 주입하여 Pod의 정보를 가져오는 오텔 오퍼레이터를 설치한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;admissionWebhooks.certManager.enabled=true&quot; 옵션을 통해 오텔 오퍼레이터가 가동으로 cert-manager를 통해 Issuer와 Certificate를 생성하여 Secret를 생성 및 주입한다.&lt;/p&gt;
&lt;pre id=&quot;code_1779949514324&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm install opentelemetry-operator open-telemetry/opentelemetry-operator \
  --namespace monitoring \
  --set &quot;manager.collectorImage.repository=otel/opentelemetry-collector-contrib&quot; \
  --set admissionWebhooks.certManager.enabled=true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 노트별 로그를 수집하며 중앙 집계 및 처리를 진행할 오텔 콜렉터를 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1779949978521&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create serviceaccount otel-collector-sa -n monitoring

cat &amp;lt;&amp;lt;EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: otel-collector-sa
  namespace: monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: otel-collector-role
rules:
  - apiGroups: [&quot;&quot;]
    resources: [&quot;nodes/stats&quot;, &quot;nodes/proxy&quot;, &quot;pods&quot;, &quot;namespaces&quot;, &quot;nodes&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;apps&quot;]
    resources: [&quot;replicasets&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: otel-collector-rolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: otel-collector-role
subjects:
  - kind: ServiceAccount
    name: otel-collector-sa
    namespace: monitoring
EOF&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1779949752573&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;##otelcol-daemonset.yaml
apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
  name: otel-daemonset
  namespace: monitoring
spec:
  mode: daemonset
  serviceAccount: otel-collector-sa
  resources:
    requests:
      cpu: &quot;100m&quot;
      memory: &quot;128Mi&quot;
    limits:
      cpu: &quot;500m&quot;
      memory: &quot;400Mi&quot;
  config:
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
          http:
            endpoint: 0.0.0.0:4318
      kubeletstats:
        collection_interval: 30s
        auth_type: serviceAccount
        endpoint: &quot;https://${env:K8S_NODE_IP}:10250&quot;
        insecure_skip_verify: true
      filelog:
        include:
          - /var/log/pods/*/*/*.log
        include_file_path: true
        operators:
          - type: container
            id: container-parser

    processors:
      batch:
        timeout: 5s
        send_batch_size: 10000
      k8sattributes:
        passthrough: false
        extract:
          metadata:
            - k8s.pod.name
            - k8s.namespace.name
            - k8s.deployment.name
            - k8s.node.name
            - service.name
      resourcedetection:
        detectors: [env, k8snode]
        timeout: 5s
      memory_limiter:
        check_interval: 1s
        limit_mib: 400
        spike_limit_mib: 100

    connectors:
      spanmetrics:
        namespace: traces
        histogram:
          explicit:
            buckets: [5ms, 10ms, 25ms, 50ms, 100ms, 250ms, 500ms, 1s]
        dimensions:
          - name: http.method
          - name: http.status_code

    exporters:
      otlphttp/loki:
        endpoint: http://loki-gateway.monitoring.svc:80/otlp
      otlp/tempo:
        endpoint: tempo.monitoring.svc:4317
        tls:
          insecure: true
      prometheusremotewrite/mimir:
        endpoint: http://mimir-gateway.monitoring.svc:80/api/v1/push
        headers:
          X-Scope-OrgID: anonymous

    service:
      pipelines:
        logs:
          receivers: [otlp, filelog]
          processors: [memory_limiter, k8sattributes, batch]
          exporters: [otlphttp/loki]
        traces:
          receivers: [otlp]
          processors: [memory_limiter, k8sattributes, batch]
          exporters: [otlp/tempo, spanmetrics]
        metrics:
          receivers: [otlp, kubeletstats, spanmetrics] 
          processors: [memory_limiter, k8sattributes, resourcedetection, batch]
          exporters: [prometheusremotewrite/mimir]

  volumeMounts:
    - name: varlogpods
      mountPath: /var/log/pods
      readOnly: true

  volumes:
    - name: varlogpods
      hostPath:
        path: /var/log/pods

  env:
    - name: K8S_NODE_IP
      valueFrom:
        fieldRef:
          fieldPath: status.hostIP&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1779949816943&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl apply -f otelcol-daemonset.yaml&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;정상적으로 설치가 끝났다면 &quot;kubectl get pod -n monitoring&quot; 시에 pending이나 Error가 없이 여러개의 Pod가 떠있을 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모두 runnging 중이라면 Grafana 웹사이트에 접속하여 UI에서 실제 연결을 확인해 본다.&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;Grafana 좌측 사이드바 -&amp;gt; Commections -&amp;gt; Data sources&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;417&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kw4gM/dJMcageBCkK/32RMNoEdB92CaxbECQ3HT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kw4gM/dJMcageBCkK/32RMNoEdB92CaxbECQ3HT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kw4gM/dJMcageBCkK/32RMNoEdB92CaxbECQ3HT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKw4gM%2FdJMcageBCkK%2F32RMNoEdB92CaxbECQ3HT0%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;741&quot; height=&quot;336&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;417&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;이곳에서 Loki, Mimir, Tempo를 모두 들어가 가장 하단에 &quot;Test&quot;를 통해 Connection 을 확인해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0VAB4/dJMcabRQSqy/ixfquR9LGhotTodLnDi2P0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0VAB4/dJMcabRQSqy/ixfquR9LGhotTodLnDi2P0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0VAB4/dJMcabRQSqy/ixfquR9LGhotTodLnDi2P0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0VAB4%2FdJMcabRQSqy%2FixfquR9LGhotTodLnDi2P0%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;427&quot; height=&quot;234&quot; data-origin-width=&quot;427&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;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;세개 모두 성공했다면 설치는 끝이다.&lt;/p&gt;</description>
      <category>Monitoring/LGTM + Otel</category>
      <category>LGTM</category>
      <category>lgtm + otel</category>
      <category>lgtm 모니터링</category>
      <category>lgtm 모니터링 구축</category>
      <category>lgtm 스택</category>
      <category>otel 모니터링</category>
      <author>Daramu</author>
      <guid isPermaLink="true">https://daramu.tistory.com/84</guid>
      <comments>https://daramu.tistory.com/84#entry84comment</comments>
      <pubDate>Thu, 28 May 2026 16:06:09 +0900</pubDate>
    </item>
    <item>
      <title>Cert Manager 란 무엇인가?</title>
      <link>https://daramu.tistory.com/83</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS 통신을 위해서는 SSL/TLS 인증서 관리가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만료일을 깜빡하면 HTTPS 통신이 안되어 서비스가 마비되는 대참사가 간혹 나오고 있는데, 쿠버네티스에서는 cert-manager를 사용하여 이 같은 상황을 막는 것이 거의 표준으로 자리 잡고 있다.&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;862&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNPR5u/dJMcaiwHHQI/GsSJ40NocbJESC3YkdJU21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNPR5u/dJMcaiwHHQI/GsSJ40NocbJESC3YkdJU21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNPR5u/dJMcaiwHHQI/GsSJ40NocbJESC3YkdJU21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNPR5u%2FdJMcaiwHHQI%2FGsSJ40NocbJESC3YkdJU21%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;862&quot; height=&quot;508&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;508&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;위 그림은 cert-manager가 인증서를 발급하고 관리하는 전체적인 흐름이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 4가지 컴포넌트로 구성된다.&lt;/p&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. Issures(Cluster Issures):&amp;nbsp; 인증서를 실제로 발급해주는 인증기관(CA)에 대한 정보이다. Let's Encrypt, Hashicorp Valut, 혹은 자체 생성한 CA등이 여기에 해당한다. Cluster가 붙는 것은 클러스 전체 공유 가능한 인증서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. cert-manager: 모든 과정을 컨트롤 하는 컨틀롤러(Controller)이다. 리소스를 감시하며 인증서를 자동으로 발급/갱신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Certificates: 어떤 도메인(ex. foo.bar.com)에 대해 어떤 Issuer를 사용해 인증서를 만들 것인지에 대한 명세서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Kubernetes Secret: cert-manager가 최종 발급 받은 개인키(Private Key)와 인증서(signed keypair)를 저장하는 공간이다. Ingress나 Application은 이 Secret을 마운트 하여&amp;nbsp; HTTPS 통신을 가능하게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;내부용 HTTPS vs 외부용 HTTPS&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증서를 발급할 때, 서비스가 클러스터 내부에서만 도메인을 쓰는지, 외부에 노출되는지에 따라 내부 설정이 달라진다.&lt;/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;578&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKOD1T/dJMcadPD1ip/uZmzZ0TBZeaC80O9hMFK0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKOD1T/dJMcadPD1ip/uZmzZ0TBZeaC80O9hMFK0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKOD1T/dJMcadPD1ip/uZmzZ0TBZeaC80O9hMFK0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKOD1T%2FdJMcadPD1ip%2FuZmzZ0TBZeaC80O9hMFK0k%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;578&quot; height=&quot;406&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;406&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;예시 전: cert-manager helm 설치&lt;/p&gt;
&lt;pre id=&quot;code_1779932364633&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set crds.enabled=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;내부용 HTTPS 인증서 발급&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터 내부 통신용이며, 사내 개발 환경등 내부에서만 접근이 필요할때 사용할 수 있는 구성이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cert-manager가 직접 자체 루트 CA(root 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;1. cert-manager를 통해 직접 root CA 인증서 발급: 자체서명(Self-Signed)기능을 사용하여 cert-manager가 직접 루트 CA용 인증서와 비밀키를 만들고, 그걸 internal-ca-key-pair라는 이름의 secret으로 저장하게 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1779932338065&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned-root-issuer
spec:
  selfSigned: {}

---

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: internal-root-ca
  namespace: cert-manager
spec:
  isCA: true # 중요: 이 인증서가 다른 인증서를 발행할 수 있는 'CA' 역할을 하겠다고 선언
  commonName: &quot;my-internal-root-ca&quot;
  secretName: internal-ca-key-pair # 여기에 입력한 이름으로 Secret이 자동 생성
  privateKey:
    algorithm: ECDSA
    size: 256
  issuerRef:
    name: selfsigned-root-issuer
    kind: ClusterIssuer&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;2. clusterIssuer: 먼저 내부 사설 CA 역할을 할 Issure를 정의한다.&lt;/p&gt;
&lt;pre id=&quot;code_1779931896495&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: internal-ca-issuer
spec:
  ca:
    # 미리 생성해둔 내부 CA의 비밀키가 담긴 Secret을 지정
    secretName: internal-ca-key-pair&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;3. Certificate 생성 및 Secret 자동 발급: 내부에서 사용할 내부 도메인(ex. foo.bar.com)에 대한 인증서를 요청한다.&lt;/p&gt;
&lt;pre id=&quot;code_1779931984097&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: internal-service-cert
  namespace: default
spec:
  # 위에서 만든 내부 CA Issuer를 지정
  issuerRef:
    name: internal-ca-issuer
    kind: ClusterIssuer
  # 최종 인증서(signed keypair)가 저장될 쿠버네티스 시크릿 이름
  secretName: internal-service-tls
  dnsNames:
    - &quot;foo.bar.com&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;이제 cert-manager가 internal-ca-issuer를 통해 서명한 뒤, internal-service-tls 라는 secret을 자동으로 만들어준다.&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;외부 도메인 연결 인증서 발급(Let's Encrypt)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷을 통해 실제 유저가 접속하는 서비스(ex. example.com)을 위한 구성이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글로벌 공인CA인 Let's Encrypt를 사용한다.&lt;/p&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. clusterIssuer: Let's Encrypt와 통신하여 도메인 유효성을 체크하는 Issuer을 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1779932133780&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    # Let's Encrypt 프로덕션 서버 주소
    server: https://acme-v02.api.letsencrypt.org/directory
    email: devops-team@example.com  # 만료 알림 등을 받을 이메일
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
    - http01:
        ingress:
          class: nginx  # 클러스터에서 사용 중인 Ingress Controller 지정&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;2. Certifacate 생성: 외부용 인증서는 Certificate 리소스를 따로 열어도 되지만, 사실 대부분 Ingress설정에 어노테이션을 설정하여 관리하는 것이 대부분의 사례이다.&lt;/p&gt;
&lt;pre id=&quot;code_1779932213066&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: wwww-service-ingress
  namespace: default
  annotations:
    # 이 어노테이션을 보고 cert-manager가 알아서 Certificate를 생성
    cert-manager.io/cluster-issuer: &quot;letsencrypt-prod&quot;
    kubernetes.io/ingress.class: &quot;nginx&quot;
spec:
  rules:
  - host: &quot;example.com&quot;
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-service
            port:
              number: 80
  tls:
  - hosts:
    - &quot;example.com&quot;
    # cert-manager가 Let's Encrypt에서 받아온 공인 인증서를 이 시크릿에 담는다
    secretName: example-com-tls&lt;/code&gt;&lt;/pre&gt;</description>
      <category>클라우드/Kubernetes</category>
      <category>cert manager</category>
      <category>cert-manager</category>
      <category>쿠버네티스 cert manager</category>
      <category>쿠버네티스 내부 인증서</category>
      <category>쿠버네티스 인증서</category>
      <author>Daramu</author>
      <guid isPermaLink="true">https://daramu.tistory.com/83</guid>
      <comments>https://daramu.tistory.com/83#entry83comment</comments>
      <pubDate>Thu, 28 May 2026 10:47:00 +0900</pubDate>
    </item>
  </channel>
</rss>