<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>L.O.K</title>
    <link>https://lordofkangs.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 15 Jul 2026 23:39:50 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>록흐</managingEditor>
    <item>
      <title>[Azure] Application Gateway 환경에서 Graceful Pod Termination 구현하기</title>
      <link>https://lordofkangs.tistory.com/735</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1780707720538&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;Graceful하게 Pod 종료하기&quot; data-og-description=&quot;graceful하게 pod를 종료하려면 큰 틀에서 두 가지 조건을 만족해야 한다. 1) 신규 트래픽이 종료 중인 파드로 전달되면 안된다. 2) 이미 들어온 트래픽이 처리가 될 때까지 파드는 종료되면 안된다. &quot; data-og-host=&quot;lordofkangs.tistory.com&quot; data-og-source-url=&quot;https://lordofkangs.tistory.com/734&quot; data-og-url=&quot;https://lordofkangs.tistory.com/734&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zNmC9/dJMb9b3ZDOJ/iefuDhzJGHDaUPQtisWPe1/img.png?width=431&amp;amp;height=381&amp;amp;face=0_0_431_381,https://scrap.kakaocdn.net/dn/cMOoZh/dJMb9lleKwh/q9XsGtBKZhqrS4ioffFIp1/img.png?width=431&amp;amp;height=381&amp;amp;face=0_0_431_381,https://scrap.kakaocdn.net/dn/I9FUb/dJMb9gxs4lr/xMIo3cA9OMQssaxbbyzsg0/img.png?width=431&amp;amp;height=381&amp;amp;face=0_0_431_381&quot;&gt;&lt;a href=&quot;https://lordofkangs.tistory.com/734&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://lordofkangs.tistory.com/734&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zNmC9/dJMb9b3ZDOJ/iefuDhzJGHDaUPQtisWPe1/img.png?width=431&amp;amp;height=381&amp;amp;face=0_0_431_381,https://scrap.kakaocdn.net/dn/cMOoZh/dJMb9lleKwh/q9XsGtBKZhqrS4ioffFIp1/img.png?width=431&amp;amp;height=381&amp;amp;face=0_0_431_381,https://scrap.kakaocdn.net/dn/I9FUb/dJMb9gxs4lr/xMIo3cA9OMQssaxbbyzsg0/img.png?width=431&amp;amp;height=381&amp;amp;face=0_0_431_381');&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;Graceful하게 Pod 종료하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;graceful하게 pod를 종료하려면 큰 틀에서 두 가지 조건을 만족해야 한다. 1) 신규 트래픽이 종료 중인 파드로 전달되면 안된다. 2) 이미 들어온 트래픽이 처리가 될 때까지 파드는 종료되면 안된다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;lordofkangs.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;이전 포스팅에서 K8s 환경에서 Graceful하게 pod 종료하는 방법을 다루어 보았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;요약하면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;graceful하게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;pod를 종료하려면 큰 틀에서 두 가지 조건을 만족해야 하는데,&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;1) 신규 트래픽이 종료 중인 파드로 전달되면 안된다.&amp;nbsp;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;2) 이미 들어온 트래픽이 처리가 될 때까지 파드는 종료되면 안된다.&amp;nbsp;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;신규 트래픽은 endpoint로 막히게 되고&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기존 트래픽은 Linux의 conntrack 모듈로 인해 &lt;span style=&quot;color: #222222; text-align: justify;&quot;&gt;[ ClientIP - PodIP ] TCP세션으로 유지되므로, 클라이언트와의 세션이 안전하게 종료될때까지 파드만 정상상태를 유지하면 된다는 것이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: justify;&quot;&gt;그래서 파드에 두 가지 설정이 필요했다.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: justify;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;1) 파드에 preStop 설정 추가&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;2) 파드에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;terminationGracePeriodSeconds 설정 추가&amp;nbsp;&lt;/i&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;여기서 만약 Azure 환경의 Application Gateway를 사용한다면 Graceful한 파드 종료를 위해서 Ingress에 한 가지 설정을 더 추가해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780707901652&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: nginx
  namespace: test
  annotations:
    kubernetes.io/ingress.class: azure-application-gateway
    # Connection Draining 활성화
    appgw.ingress.kubernetes.io/connection-draining: &quot;true&quot;
    # 기존 연결을 최대 90초간 유지
    appgw.ingress.kubernetes.io/connection-draining-timeout: &quot;90&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 어노테이션 설정이 필요한 이유는 Azure 환경의 Application Gateway이 동작하는 독특한 과정 때문이다.&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-filename=&quot;제목 없는 다이어그램-페이지-9.drawio (2).png&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;942&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cx6o1k/dJMcadvqyLO/4w2ekDMgoImWcAWqqnEcV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cx6o1k/dJMcadvqyLO/4w2ekDMgoImWcAWqqnEcV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cx6o1k/dJMcadvqyLO/4w2ekDMgoImWcAWqqnEcV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcx6o1k%2FdJMcadvqyLO%2F4w2ekDMgoImWcAWqqnEcV0%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;851&quot; height=&quot;942&quot; data-filename=&quot;제목 없는 다이어그램-페이지-9.drawio (2).png&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;942&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Azure 환경의 Application Gateway는 일반적인 K8s LB가 아니라 Azure에서 커스텀하게 만든 서비스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AKS는 AGIC라는 파드가 떠있는데, 이는 ingress 생성을 감지하여 ingress-&amp;gt; 서비스 -&amp;gt; endpoint -&amp;gt; 파드로의 정보를 수집하고 이를 토대로 Azure 환경에 Listener, Rule, BackendPool, BackendSetting을 생성하는 API를 호출한다. 이렇게 생성된 것들이 모여 트래픽을 파드로 라우팅하는 것이 Azure Application Gateway이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 K8S는 Client 와 파드 사이에 세션이 형성된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Azure에서는 Application Gateway가 Client와 Pod 사이의 Proxy 역할을 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Client -&amp;gt; Application Gateway -&amp;gt; Pod&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 가능한 이유는 Application Gateway Subnet과 AKS Subet이 동일한 vnet에 있기에 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780708463502&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# application-gateway subnet 정보
az network application-gateway show \
  -g &amp;lt;AG_RG&amp;gt;  \
  -n &amp;lt;AG_NAME&amp;gt; \
  --query &quot;gatewayIPConfigurations[].subnet.id&quot; \
  -o tsv


# aks subnet 정보
az aks show \
  -g &amp;lt;AKS_RG&amp;gt; \
  -n &amp;lt;AKS_NAME&amp;gt; \
  --query &quot;agentPoolProfiles[0].vnetSubnetId&quot; \
  -o tsv&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;위 커맨드로 subnet 정보를 조회하면 동일한 vnet에 있음을 확인할 수 있다.&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;u&gt;&lt;b&gt;그럼 여기서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;graceful하게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;pod를 종료하기 위한 두 가지 조건에 하나가 더 추가됨을 알 수 있다.&amp;nbsp;&lt;/b&gt;&lt;/u&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;i&gt;1) 신규 트래픽이 종료 중인 파드로 전달되면 안된다.&amp;nbsp;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;2) 이미 들어온 트래픽이 처리가 될 때까지 파드는 종료되면 안된다.&amp;nbsp;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;3) ApplicationGateway와 Pod 사이의 연결이 끊기면 안된다.&amp;nbsp;&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파드가 종료명령을 받으면 AGIC는 azure api를 호출하여 ApplicationGateway의 BackendPool 데이터에서 삭제된 Pod IP를 제거하여 더이상 client의 신규 요청이 종료된 pod로 보내지 않도록 해야한다. 이때 1번 조건을 만족하기 위해 제거된다.&lt;/p&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번 조건 만족을 위해 ApplicationGateway와 종료 요청 받은 Pod 사이의 세션을 유지해야 하는데, 별다른 설정이 없다면ApplicationGateway는 파드와의 세션 유지를 보장하지 않는다는 것이다. 그렇다면 파드에 preStop, terminationGracePeriodSeconds 설정을 하더라도 ApplicationGateway이 파드 사이와의 세션을 끊기에 세션은 유지가 되지 않고 Client는 영문 모를 502 에러를 마주하게 된다.&lt;/p&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;preStop,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;terminationGracePeriodSeconds&lt;span&gt; 과 더불어 &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;아래 설정도 ingress에 추가하여 세션유지를 보장해야 &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;graceful한 파드 종료를 보장할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780715005082&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    appgw.ingress.kubernetes.io/connection-draining: &quot;true&quot;
    appgw.ingress.kubernetes.io/connection-draining-timeout: &quot;90&quot;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Ops/PublicCloud</category>
      <author>록흐</author>
      <guid isPermaLink="true">https://lordofkangs.tistory.com/735</guid>
      <comments>https://lordofkangs.tistory.com/735#entry735comment</comments>
      <pubDate>Sat, 6 Jun 2026 12:04:33 +0900</pubDate>
    </item>
    <item>
      <title>Graceful하게 Pod 종료하기</title>
      <link>https://lordofkangs.tistory.com/734</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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;graceful하게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;pod를 종료하려면 큰 틀에서 두 가지 조건을 만족해야 한다.&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;i&gt;1) 신규 트래픽이 종료 중인 파드로 전달되면 안된다.&amp;nbsp;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;2) 이미 들어온 트래픽이 처리가 될 때까지 파드는 종료되면 안된다.&amp;nbsp;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;신규 트래픽이 종료 중인 파드로 전달되면 안된다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목 없는 다이어그램-페이지-8.drawio.png&quot; data-origin-width=&quot;431&quot; data-origin-height=&quot;381&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cK0kgu/dJMcageHpZ2/Ws3wlOWboDOa8aT7msaJ90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cK0kgu/dJMcageHpZ2/Ws3wlOWboDOa8aT7msaJ90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cK0kgu/dJMcageHpZ2/Ws3wlOWboDOa8aT7msaJ90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcK0kgu%2FdJMcageHpZ2%2FWs3wlOWboDOa8aT7msaJ90%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;414&quot; height=&quot;366&quot; data-filename=&quot;제목 없는 다이어그램-페이지-8.drawio.png&quot; data-origin-width=&quot;431&quot; data-origin-height=&quot;381&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;신규 트래픽이 노드로 들어오면 kube-proxy가 iptables로 기록해둔 경로를 따라 service에서 endpoint 중 하나로 로드밸런싱한다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;( 원리 :&amp;nbsp;&lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://lordofkangs.tistory.com/709&quot;&gt;https://lordofkangs.tistory.com/709&lt;/a&gt;&amp;nbsp;)&amp;nbsp;&lt;/i&gt;&lt;/span&gt;이때 endpoint는 service에게 3가지 상태를 공유한다.&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 data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;- ready : 트래픽을 받을 준비가 되었는가?&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;- serving : 요청 처리가 가능한가?&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;- terminating : &lt;span&gt;파드가&lt;/span&gt; &lt;span&gt;삭제중인가&lt;/span&gt;?&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780658598535&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 세 가지 플래그 확인하는 방법
kubectl get endpointslices -o json -l kubernetes.io/service-name=[서비스명]&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;파드가 종료되면 endpoint의 ready는 true에서 false로 변경되고 service는 ready가 false인 endpoint로 트래픽을 전달하지 않아 종료중인 파드에게 트래픽을 전달하지 않는다.&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;&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;size18&quot;&gt;&lt;b&gt;이미 들어온 트래픽이 처리가 될 때까지 파드는 종료되면 안된다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;신규 트래픽이 노드로 들어오면 kube-proxy가 iptables로 기록해둔 경로를 따라 파드로 전달하여 세션이 생성되지만 세션의 상태는&lt;b&gt; Linux의 conntrack 모듈&lt;/b&gt;로 관리된다. 한마디로 [ ClientIP - PodIP ]로 TCP 연결 상태를 추적 관리하므로, 파드가 종료 명령을 받아 endpoint의 ready 플래그가 false여도 &lt;u&gt;&lt;b&gt;파드 상태만 정상 상태로 지연시킬 수 있다면&lt;/b&gt;&lt;/u&gt; 종료 명령 받기전 생성된 세션은 트래픽이 처리될 때까지 유지할 수 있다는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 증명하기 위해 한 가지 테스트를 해보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Nginx Deployment 메니페스트&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;1) preStop 설정 추가&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;2)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;terminationGracePeriodSeconds 설정 추가&amp;nbsp;&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780712095458&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      terminationGracePeriodSeconds: 300
      containers:
      - name: nginx
        image: nginx:latest
        lifecycle:
          preStop:
            exec:
              command:
              - /bin/sh
              - -c
              - |
                echo &quot;[$(date)] preStop start&quot;
                sleep 240
                echo &quot;[$(date)] preStop end&quot;
        ports:
        - containerPort: 80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Nginx Service 메니페스트&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780712180107&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: nginx-service
spec:
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Client 파드 메니페스트&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780712417254&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: curl-client
  namespace: test
spec:
  restartPolicy: Never
  containers:
  - name: curl
    image: curlimages/curl:8.8.0
    command:
    - /bin/sh
    - -c
    - |
      echo &quot;curl client ready&quot;
      sleep infinity&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Client가 nginx 파드에 있는 파일을 다운로드 하여 TCP 세션을 유지하는 상황을 가정해보자.&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;우선 nginx 파드 내부로 exec하여 파일 하나를 설치한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780711179444&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dd if=/dev/zero of=/usr/share/nginx/html/1g.bin bs=1M count=1024&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;그리고 client 파드 내부로 exec하여 curl 명령어로 해당 파일을 다운로드 하여 세션을 유지한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780712522260&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl http://nginx-service.test/1g.bin --limit-rate 100k -o /dev/null&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;아래 명령어로 client 파드와 nginx 파드의 ip와 nginx 파드가 떠있는 노드를 확인한다.&lt;/p&gt;
&lt;pre id=&quot;code_1780712573012&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl get pods -n test -o wide&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;nginx 파드가 떠있는 node 내부로 접속하여 아래 명령어를 실행한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780712657963&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 10.233.111.16 : nginx 파드 ip
conntrack -L -p tcp | grep 10.233.111.16&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;출력 결과&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src=10.233.111.16&amp;nbsp; : nginx 파드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dst=10.233.111.56 : client 파드&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780712780082&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tcp      6 299 ESTABLISHED src=10.233.111.56 dst=10.233.16.192 sport=36916 dport=80 packets=911 bytes=47539 src=10.233.111.16 dst=10.233.111.56 sport=80 dport=36916 packets=5236 bytes=16801505 [ASSURED] mark=0 use=1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;최초에 10.233.111.56(Client Pod)에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;10.233.16.192(NGINX Service)으로 요청이 유입된다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;kube-proxy의 iptables 규칙에 의해 Service IP는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;10.233.111.16(NGINX Pod)으로 DNAT된다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이후 Client Pod(10.233.111.56)와 NGINX Pod(10.233.111.16) 사이의 TCP 연결이 ESTABLISHED 상태로 생성되며&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;해당 연결 정보는 Linux conntrack 테이블에 기록되어 관리된다.&lt;/p&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_1780713149917&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl get endpointslices -o json -l kubernetes.io/service-name=nginx-service&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1780713163313&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;                    &quot;conditions&quot;: {
                        &quot;ready&quot;: false,
                        &quot;serving&quot;: true,
                        &quot;terminating&quot;: 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;endpoint는 ready가 false로 변경되며 신규 트래픽 유입이 막힌다.&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;파드는 preStop 설정에 의해 종료명령을 받더라도 프로세스 종료작업을 진행하지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780713237951&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 10.233.111.16 : nginx 파드 ip
conntrack -L -p tcp | grep 10.233.111.16&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;출력결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780713260735&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tcp      6 299 ESTABLISHED src=10.233.111.56 dst=10.233.16.192 sport=36916 dport=80 packets=2731 bytes=142179 src=10.233.111.16 dst=10.233.111.56 sport=80 dport=36916 packets=15669 bytes=47039597 [ASSURED] mark=0 use=1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;conntrack으로 확인해보면 파드가 종료명령을 받았는데도 아직 TCP 세션은 ESTABLISHED 상태로 유지됨을 확인할 수 있다. TCP 세션은 client 와 파드 사이의 연결로, 파드만 유지된다면 세션은 끊기지 않기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;파드의 모든 세션이 자발적으로 종료될때까지 종료를 지연시키는 것이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;1) 파드에 preStop 설정 추가&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;2) 파드에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;terminationGracePeriodSeconds 설정 추가&amp;nbsp;&lt;/i&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 data-ke-size=&quot;size16&quot;&gt;pod가 종료 명령을 받으면 내부적으로 SIGTERM 명령을 받아 안전하게 프로세스를 종료하는 작업을 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;preStop은 SIGTERM 명령이 들어오는 시간을 지연시켜준다. 요청이 들어오면 프로세스가 살아 있어야 하기에, 종료시키지 않고 유지시켜주는 작업이다. preStop으로 설정된 시간이 지나가면 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;SIGTERM 명령이 들어온다. 그러면 프로세스 정리작업이 시작된다. 프로세스가 정리되는데 시간이 오래 걸리고 서비스가 Stateful해서 프로세스가 안전하게 정리되어야 한다면 terminationGracePeriodSeconds 시간을 길게 잡아야 한다.&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;terminationGracePeriodSeconds 가 120초이고 preStop 60초이면 pod 종료 명령이 들어오고 60초 동안은 SIGTERM이 내부에서실행되지 않는다. 60초가 지나면 SIGTERM명령이 실행되고 프로세스 종료작업이 진행된다. 그리고 60초가 흐르면 파드는 종료된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고자료&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kubernetes.io/docs/tutorials/services/pods-and-endpoint-termination-flow/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kubernetes.io/docs/tutorials/services/pods-and-endpoint-termination-flow/&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Ops/Kubernetes</category>
      <author>록흐</author>
      <guid isPermaLink="true">https://lordofkangs.tistory.com/734</guid>
      <comments>https://lordofkangs.tistory.com/734#entry734comment</comments>
      <pubDate>Fri, 5 Jun 2026 20:40:31 +0900</pubDate>
    </item>
    <item>
      <title>PostgreSQL DB와 PGBouncer ( + Prepared Statements )</title>
      <link>https://lordofkangs.tistory.com/733</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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Client가 PostgreSQL DB에서 Query를 실행한다는 것은 다음과 같다.&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;i&gt;1) ClientA가 PostgreSQL DB auth에 성공한다. ( 세션 생성 )&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;2) PostgreSQL DB 내부에&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;ClientA 전용 process가 생성된다. ( backend process ) ( idle )&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;3) ClientA가 ClientA 전용 process 공간에서 BEGIN 으로 트랜잭션을 시작한다. ( idle in transaction )&amp;nbsp;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;4) ClientA가 쿼리를 실행한다. ( active )&amp;nbsp;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;5) ClientA가 COMMIT/ROLLBACK 으로 트랜잭션을 종료한다.&amp;nbsp;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;6) &lt;span&gt;ClientA의 backend process/session은 유지되며 &lt;/span&gt;&lt;span&gt;다음 요청을 기다린다. ( idle )&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;7) &lt;span&gt;ClientA connection 이 종료되면 ClientA 전용 process 공간&lt;/span&gt;&lt;span&gt;도 제거된다. ( 세션 종료 )&lt;/span&gt;&lt;/i&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;span&gt;여기서 가장 큰 문제는 Client와 Process가 1 대 1로 매핑되므로 Client가 10개, 50개, 80개, 100개 이상으로 증가하게 되면, DB는 많은 프로세스를 관리함과 동시에 잦은 컨텍스트 스위칭에 시달려 성능 저하를 겪게 되는 것이다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그렇다면 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;PGBouncer는 이 문제를 어떻게 해결할까?&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목 없는 다이어그램-페이지-5.drawio.png&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;91&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmWHay/dJMcabjWCeo/fCUAFfB9fpjOHdFxxNWEMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmWHay/dJMcabjWCeo/fCUAFfB9fpjOHdFxxNWEMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmWHay/dJMcabjWCeo/fCUAFfB9fpjOHdFxxNWEMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmWHay%2FdJMcabjWCeo%2FfCUAFfB9fpjOHdFxxNWEMk%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;691&quot; height=&quot;91&quot; data-filename=&quot;제목 없는 다이어그램-페이지-5.drawio.png&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;91&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PGBouncer가 PostgresSQL DB에 미리 고정된 범위의 backend process 생성해놓는다. ( pool 단위 )&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;PGBouncer는 PostgresSQL DB 대신 Client와 연결되어, Client를 번갈아 미리 생성해놓은 Backend Process에 할당하여 트랜잭션을 처리한다. Client가 100 개인데, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;backend process&lt;span&gt; 가 10개라면 90개는 세션 연결 에러를 일으키지 않고 큐에서 차례를 기다리면 얌전히 대기한다. Client입장에서는 PGBouncer와 정상적으로 연결이 된 상태이기에 연결오류라고 인식하지 않는 것이다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;PGBouncer를 트랜잭션 모드로 동작시키면, 세션 단위가 아닌 트랜잭션 처리 단위 ( BEGIN - COMMIT/ROLLBACK )로 Backend Process를 할당한다.&amp;nbsp; PGBouncer가 없다면 한 개의 세션에 한 개의 backend process 이지만, PGBouncer를 통한 다면 한 개의 세션동안 처리되는 다수의 트랜잭션 마다 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;backend process 여러 곳에 할당이 될 수 있는 것이다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;결과적으로&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Client 개수가 아무리 많아져도 backend process는 고정된 범위 안의 개수로 유지가 되고 backend process가 종료되지도 않기 때문에 반복적인 컨텍스트 스위칭도 발생하지 않아 DB 리소스 부담이 덜해지는 것이다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Prepared&amp;nbsp;Statements&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PGBouncer에도 한 가지 단점이 있었다. &lt;span&gt;Client는 Prepared Statement를 사용하여 &lt;/span&gt;&lt;span&gt;반복적인 쿼리 파싱 및 실행 계획 생성을 방지할 수 있다. 하지만 PGBouncer를 쓰게 되면 문제가 생기는게, Prepared Statement는 backend process 내부에 저장되지만 트랜잭션 마다 backend process가 변경되니, Client가 다음 트랜잭션에서 Prepared Statement를 이용할 수가 없어 비활성화 해야만한다. &lt;span&gt;그 결과 Client는 쿼리의 컬럼 타입 조회와 같은 메타데이터 조회 쿼리를 반복적으로 수행하게 되고, &lt;/span&gt;&lt;span&gt;이는 실제 비즈니스 쿼리 외의 추가적인 DB 부하로 이어진다.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1595&quot; data-origin-height=&quot;367&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O1hGw/dJMcafT93za/Y5qL6c21WGTwQEiRhjJt41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O1hGw/dJMcafT93za/Y5qL6c21WGTwQEiRhjJt41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O1hGw/dJMcafT93za/Y5qL6c21WGTwQEiRhjJt41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO1hGw%2FdJMcafT93za%2FY5qL6c21WGTwQEiRhjJt41%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;1595&quot; height=&quot;367&quot; data-origin-width=&quot;1595&quot; data-origin-height=&quot;367&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;개인적으로도 PGBouncer 오래된 버전을 쓰고 있어 Prepared Statement 기능을 사용하지 못하니 asyncpg가 typeinfo_tree 쿼리를 비정상적으로 많이 호출하여 DB CPU 부하가 100% 까지 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;반복적으로&lt;/span&gt;&amp;nbsp;Spike를 쳤다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행이도&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PGBouncer는 1.21 버번 이후부터&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Prepared Statement&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;기능을 사용할 수 있게 되었다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.postgresql.org/about/news/pgbouncer-1210-released-now-with-prepared-statements-2735/&quot;&gt;https://www.postgresql.org/about/news/pgbouncer-1210-released-now-with-prepared-statements-2735/&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;&lt;span&gt;PgBouncer는 Prepared Statement 사용 정보를 추적하고 있다가,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;backend process가 변경되더라도 해당 backend에 Prepared Statement를 다시 준비(re-prepare)하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Prepared Statement를 지속적으로 재사용할 수 있도록 한다. &lt;/span&gt;asyncpg(Client)와 PgBouncer가&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Prepared Statement를 사용할 수 있도록 활성화를 하자 놀랍도록 DB CPU 부하는 감소하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ISPY6/dJMcaayylMS/m8tsrQA2Ms1rJ1BTsc4ztk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ISPY6/dJMcaayylMS/m8tsrQA2Ms1rJ1BTsc4ztk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ISPY6/dJMcaayylMS/m8tsrQA2Ms1rJ1BTsc4ztk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FISPY6%2FdJMcaayylMS%2Fm8tsrQA2Ms1rJ1BTsc4ztk%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;920&quot; height=&quot;264&quot; data-origin-width=&quot;920&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;PGBouncer는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;PostgresSQL DB가 안정적으로 프로세스를 관리할 수 있도록 함과 동시에 Prepared Statement 기능까지 갖추어진 효율적인 최적화 도구라 할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CS/DB</category>
      <author>록흐</author>
      <guid isPermaLink="true">https://lordofkangs.tistory.com/733</guid>
      <comments>https://lordofkangs.tistory.com/733#entry733comment</comments>
      <pubDate>Thu, 14 May 2026 20:38:30 +0900</pubDate>
    </item>
    <item>
      <title>K8s readonly kubeconfig 파일 생성하기</title>
      <link>https://lordofkangs.tistory.com/732</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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;default 네임스페이스에 K8s Cluster 전역 조회 권한이 있는 ServiceAccount 생성 ( Secret 리소스 읽기 제외 )&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1778748484350&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ServiceAccount
metadata:
  name: read-only-no-secrets
  namespace: default
---
# Kubernetes RBAC는 명시적 허용만 가능(제외 불가)하므로
# core API group에서 secrets를 의도적으로 제외하고 나머지 리소스만 나열한다.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: read-only-no-secrets
rules:
  - apiGroups: [&quot;&quot;]
    resources:
      - pods
      - pods/log
      - pods/status
      - services
      - endpoints
      - configmaps
      - persistentvolumeclaims
      - persistentvolumes
      - namespaces
      - nodes
      - events
      - replicationcontrollers
      - resourcequotas
      - limitranges
      - serviceaccounts
      - bindings
      - componentstatuses
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;apps&quot;]
    resources: [&quot;*&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;batch&quot;]
    resources: [&quot;*&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;networking.k8s.io&quot;]
    resources: [&quot;*&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;rbac.authorization.k8s.io&quot;]
    resources: [&quot;*&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;autoscaling&quot;]
    resources: [&quot;*&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;policy&quot;]
    resources: [&quot;*&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;storage.k8s.io&quot;]
    resources: [&quot;*&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;discovery.k8s.io&quot;]
    resources: [&quot;*&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;apiextensions.k8s.io&quot;]
    resources: [&quot;*&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;scheduling.k8s.io&quot;]
    resources: [&quot;*&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;coordination.k8s.io&quot;]
    resources: [&quot;*&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;events.k8s.io&quot;]
    resources: [&quot;*&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;metrics.k8s.io&quot;]
    resources: [&quot;*&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;admissionregistration.k8s.io&quot;]
    resources: [&quot;*&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;certificates.k8s.io&quot;]
    resources: [&quot;*&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;node.k8s.io&quot;]
    resources: [&quot;*&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  - apiGroups: [&quot;flowcontrol.apiserver.k8s.io&quot;]
    resources: [&quot;*&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: read-only-no-secrets
subjects:
  - kind: ServiceAccount
    name: read-only-no-secrets
    namespace: default
roleRef:
  kind: ClusterRole
  name: read-only-no-secrets
  apiGroup: rbac.authorization.k8s.io&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;생성한 SA의 장기(만료 없음) 토큰 Secret 생성하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1778748500211&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Secret
metadata:
  name: read-only-no-secrets-token
  namespace: default
  annotations:
    kubernetes.io/service-account.name: read-only-no-secrets
type: kubernetes.io/service-account-token&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;kubeconfig 파일 생성하기 &lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1778748513050&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/usr/bin/env bash
set -euo pipefail

NAMESPACE=&quot;${NAMESPACE:-default}&quot;
SECRET_NAME=&quot;${SECRET_NAME:-read-only-no-secrets-token}&quot;
SA_NAME=&quot;${SA_NAME:-read-only-no-secrets}&quot;
KUBECONFIG_OUT=&quot;${KUBECONFIG_OUT:-./readonly-kubeconfig.yaml}&quot;

# 토큰 컨트롤러가 비동기로 채우므로 최대 ~10초 대기
TOKEN_B64=&quot;&quot;
for i in 1 2 3 4 5; do
  TOKEN_B64=$(kubectl get secret &quot;$SECRET_NAME&quot; -n &quot;$NAMESPACE&quot; \
    -o jsonpath='{.data.token}' 2&amp;gt;/dev/null || true)
  if [ -n &quot;$TOKEN_B64&quot; ]; then break; fi
  echo &quot;토큰이 아직 채워지지 않음 ($i/5), 2초 대기...&quot; &amp;gt;&amp;amp;2
  sleep 2
done

if [ -z &quot;$TOKEN_B64&quot; ]; then
  echo &quot;ERROR: $NAMESPACE/$SECRET_NAME 에 토큰이 없습니다.&quot; &amp;gt;&amp;amp;2
  echo &quot;  - Secret이 존재하는지: kubectl get secret $SECRET_NAME -n $NAMESPACE&quot; &amp;gt;&amp;amp;2
  echo &quot;  - 어노테이션이 SA를 가리키는지 확인하세요.&quot; &amp;gt;&amp;amp;2
  exit 1
fi

TOKEN=$(printf '%s' &quot;$TOKEN_B64&quot; | base64 -d)
CA=$(kubectl get secret &quot;$SECRET_NAME&quot; -n &quot;$NAMESPACE&quot; -o jsonpath='{.data.ca\.crt}')

SERVER=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.server}')
CLUSTER=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].name}')

if [ -z &quot;$CA&quot; ] || [ -z &quot;$SERVER&quot; ] || [ -z &quot;$CLUSTER&quot; ]; then
  echo &quot;ERROR: CA / server / cluster 중 일부를 가져오지 못했습니다.&quot; &amp;gt;&amp;amp;2
  echo &quot;  CA len=${#CA} SERVER=$SERVER CLUSTER=$CLUSTER&quot; &amp;gt;&amp;amp;2
  exit 1
fi

umask 077
cat &amp;gt; &quot;$KUBECONFIG_OUT&quot; &amp;lt;&amp;lt;EOF
apiVersion: v1
kind: Config
clusters:
- name: ${CLUSTER}
  cluster:
    server: ${SERVER}
    certificate-authority-data: ${CA}
users:
- name: ${SA_NAME}
  user:
    token: ${TOKEN}
contexts:
- name: read-only
  context:
    cluster: ${CLUSTER}
    user: ${SA_NAME}
    namespace: ${NAMESPACE}
current-context: read-only
EOF
chmod 600 &quot;$KUBECONFIG_OUT&quot;

echo &quot;✓ kubeconfig 생성: $KUBECONFIG_OUT&quot;
echo &quot;&quot;
echo &quot;테스트:&quot;
echo &quot;  KUBECONFIG=$KUBECONFIG_OUT kubectl get pods -A      # OK&quot;
echo &quot;  KUBECONFIG=$KUBECONFIG_OUT kubectl get secrets      # Forbidden 정상&quot;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Ops/Kubernetes</category>
      <author>록흐</author>
      <guid isPermaLink="true">https://lordofkangs.tistory.com/732</guid>
      <comments>https://lordofkangs.tistory.com/732#entry732comment</comments>
      <pubDate>Thu, 14 May 2026 17:48:41 +0900</pubDate>
    </item>
    <item>
      <title>Claude와 함께하는 개발환경 구축하기</title>
      <link>https://lordofkangs.tistory.com/731</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;CICD 구축&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목 없는 다이어그램-페이지-2.drawio.png&quot; data-origin-width=&quot;1111&quot; data-origin-height=&quot;741&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yFjst/dJMcaffqpjh/wuM3k6qO0sHDCXflydBqRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yFjst/dJMcaffqpjh/wuM3k6qO0sHDCXflydBqRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yFjst/dJMcaffqpjh/wuM3k6qO0sHDCXflydBqRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyFjst%2FdJMcaffqpjh%2FwuM3k6qO0sHDCXflydBqRk%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;1111&quot; height=&quot;741&quot; data-filename=&quot;제목 없는 다이어그램-페이지-2.drawio.png&quot; data-origin-width=&quot;1111&quot; data-origin-height=&quot;741&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Claude Agent들은 feauture 단위로 컨텍스트를 소비할거라, feature 개발이 끝나면 MR을 생성해 기능테스트를 하기로 계획했다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dev&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;MR이 생성되면 ArgoEvents로 구성된 CICD 파이프라인이 동작하면서 이미지를 빌드 및 푸시하고 HelmRepo의 dev-values.yaml의 이미지 태그를 변경한다. Dev K8s는 변경사항을 감지하고 Sync를 맞추어 배포를 진행하고 테스트를 진행한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Prod&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;테스트가 완료되면 Merge 이벤트를 발생시켜 CICD 파이프라인을 동작시키고 HelmRepo의 prod-values.yaml의 이미지 태그를 변경한다. Prod K8s는 변경사항을 감지하고 Sync를 맞추어 배포를 진행하고 테스트를 진행한다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;App of Apps&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;CICD App들은 app of apps 패턴으로 생성하여, Argocd Application CRD도 형상관리가 가능하게 하면서 migration에 용이하도록 하였다. 한가지 주의할 점은 Auto Sync를 막아놓고 eventbus -&amp;gt; eventsource, eventsensor 순으로 sync를 맞추고 제거시에는 eventsource,eventsensor -&amp;gt; eventbus 순으로 제거해야 한다. 동시에 sync를 맞추고 제거하면 eventbus가 hang에 걸려 제대로 삭제 되지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Claude 설계&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;k8s_report_app-claude 아키텍처.drawio (1).png&quot; data-origin-width=&quot;1581&quot; data-origin-height=&quot;732&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c44NJN/dJMb990zqev/CKBfyhBN2e60HYLIaw9bcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c44NJN/dJMb990zqev/CKBfyhBN2e60HYLIaw9bcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c44NJN/dJMb990zqev/CKBfyhBN2e60HYLIaw9bcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc44NJN%2FdJMb990zqev%2FCKBfyhBN2e60HYLIaw9bcK%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;966&quot; height=&quot;732&quot; data-filename=&quot;k8s_report_app-claude 아키텍처.drawio (1).png&quot; data-origin-width=&quot;1581&quot; data-origin-height=&quot;732&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Plan&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Main Claude에게 구현할 서비스 아키텍처를 주고 Planning을 시켜 계획을 세우면 Gemini로 한번 더 계획을 리뷰하도록 하였다. 이 과정은 Skill로 구현하고 리뷰가 완료되면 Todo 리스트를 작성시켜 단계적으로 개발에 들어갈 수 있도록 하였다. Todo 리스트는 크게 Feature 단위와 SubTask 단위로 나누고 SubTask 작업이 끝나면 Git Push를, Feature가 끝나면 Git MR을 생성하도록 하였다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문법검사&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Bash 툴을 써서 git push 커맨드가 동작하면 PreToolUse Hook을 동작시켜 문법검사 쉘스크립트를 동작시켰다. 만약 문법에 문제가 있다면 git push는 중단되고 문법검사 내용을 Lint-Runner Agent에게 던져서 원인 분석을 시킨 뒤, 원인 분석이 끝나면 그 결과를 개발 담당 Agent에게 던져서 코드를 수정하도록 구현하였다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;오류분석&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;기능 테스트 중 오류가 발생하면 Grafana MCP로 메트릭과 로그를, Kuberenetes MCP로 k8s 상태 정보를 조회하여 오류 데이터를 Error Manager Agent에게 던져서 원인 및 조치 방법을 분석하고 분석 결과가 나오면 그 결과를 개발 담당 Agent에게 던져서 코드를 수정하도록 구현하였다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문서기록&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;개발&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;담당 Agent들이 Write,Edit 툴을 수행할 때마다 PostToolUse Hook을 걸어 그 기록을 docs 디렉토리의 activity.log로 기록하고 docs keeper Agent가 update-docs skill로 트리거되어 activity.log를 기준으로 git 명령어를 변경사항을 조회해, 사람이 이해할 수 있는 문서로 CHAGNELOG.md(무엇을 변경했는지)와 DESIONS.md(무엇을 결정했는지)를 기록하도록 하도록 하였다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트</category>
      <author>록흐</author>
      <guid isPermaLink="true">https://lordofkangs.tistory.com/731</guid>
      <comments>https://lordofkangs.tistory.com/731#entry731comment</comments>
      <pubDate>Fri, 1 May 2026 09:41:08 +0900</pubDate>
    </item>
    <item>
      <title>k8s Secret을 Git으로 관리하는 방법</title>
      <link>https://lordofkangs.tistory.com/730</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;&amp;nbsp;&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;af78c4ca6889&quot; data-ke-size=&quot;size16&quot;&gt;k8s &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Secret&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;은 Base64형식으로 민감한 정보가 그대로 노출되어 있어 git에 올려 관리할 수 없지만 한 가지 방법이 있다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Secret&lt;/span&gt;을 SealedSecret이라는 CRD로 암호화하여 Git에 올리고 SealedSecret가 배포되면 sealed-secrets 서비스가 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;SealedSecret 생성을 감지하고&amp;nbsp;&lt;/span&gt;복호화하여&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Secret을 생성하여, Secret을 Git으로 관리할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;af78c4ca6889&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;mediaSingle&quot; data-prosemirror-content-type=&quot;node&quot; data-media-vc-wrapper=&quot;true&quot;&gt;
&lt;div data-media-vc-wrapper=&quot;true&quot; data-width-type=&quot;pixel&quot; data-width=&quot;851&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;
&lt;div style=&quot;color: #000000;&quot;&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목 없는 다이어그램.drawio (2).png&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tHYbp/dJMcaayoqHF/f3DH9tTiwdHYDvFN4UAicK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tHYbp/dJMcaayoqHF/f3DH9tTiwdHYDvFN4UAicK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tHYbp/dJMcaayoqHF/f3DH9tTiwdHYDvFN4UAicK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtHYbp%2FdJMcaayoqHF%2Ff3DH9tTiwdHYDvFN4UAicK%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;851&quot; height=&quot;481&quot; data-filename=&quot;제목 없는 다이어그램.drawio (2).png&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;ced288445ebc&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;ced288445ebc&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;sealed-secrets 서비스 K8S 환경에 배포하기&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1777541743303&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm repo add bitnami https://charts.bitnami.com/bitnami 

helm upgrade --install sealed-secrets bitnami/sealed-secrets --namespace sealed-secrets --create-namespace&lt;/code&gt;&lt;/pre&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;69ca4b2da0dc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;69ca4b2da0dc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;69ca4b2da0dc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;69ca4b2da0dc&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;로컬에 kubeseal 설치하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;69ca4b2da0dc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;69ca4b2da0dc&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MacOS&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1777541788184&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew install kubeseal&lt;/code&gt;&lt;/pre&gt;
&lt;div data-prosemirror-mark-name=&quot;breakout&quot; data-prosemirror-content-type=&quot;mark&quot; data-width=&quot;1800&quot; data-mode=&quot;wide&quot;&gt;&lt;b&gt;Linux&lt;/b&gt;&lt;/div&gt;
&lt;div data-prosemirror-mark-name=&quot;breakout&quot; data-prosemirror-content-type=&quot;mark&quot; data-width=&quot;1800&quot; data-mode=&quot;wide&quot;&gt;
&lt;pre id=&quot;code_1777541814849&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;VERSION=0.36.6

wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v${VERSION}/kubeseal-${VERSION}-linux-amd64.tar.gz
tar -xvzf kubeseal-${VERSION}-linux-amd64.tar.gz
sudo mv kubeseal /usr/local/bin/

kubeseal --version&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;dfdd7247e0fe&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;dfdd7247e0fe&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;dfdd7247e0fe&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;secret 암호화 &lt;/b&gt;&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;dfdd7247e0fe&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;dfdd7247e0fe&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방법1) k8s api server로 암호화&lt;/b&gt;&lt;/p&gt;
&lt;div data-prosemirror-mark-name=&quot;breakout&quot; data-prosemirror-content-type=&quot;mark&quot; data-width=&quot;1800&quot; data-mode=&quot;wide&quot;&gt;
&lt;pre id=&quot;code_1777541837451&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubeseal --controller-name=sealed-secrets --controller-namespace=sealed-secrets --format yaml &amp;lt; secret.yaml &amp;gt; sealed-secret.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 [&amp;quot;bulletList&amp;quot;,{&amp;quot;localId&amp;quot;:&amp;quot;9a690cd9-06f5-4822-a053-72db65f454c9&amp;quot;},&amp;quot;listItem&amp;quot;,{&amp;quot;localId&amp;quot;:&amp;quot;c6b5812d-45f5-4f40-9bb1-a87d05a25af2&amp;quot;}]&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;8739e7957869&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방법2) PEM키로 암호화&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1777541854982&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# public-cert.pem 추출
kubeseal --fetch-cert --controller-name sealed-secrets --controller-namespace sealed-secrets &amp;gt; pub-cert.pem

# 암호화
kubeseal --format yaml --cert pub-cert.pem &amp;lt; elice-file-api-secret.yaml &amp;gt; ks-elice-file-api-secret.yaml # Using Pem file&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;c47877f53174&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Git으로 PUSH!&lt;/b&gt;&lt;/p&gt;</description>
      <category>Ops/Kubernetes</category>
      <author>록흐</author>
      <guid isPermaLink="true">https://lordofkangs.tistory.com/730</guid>
      <comments>https://lordofkangs.tistory.com/730#entry730comment</comments>
      <pubDate>Thu, 30 Apr 2026 18:42:09 +0900</pubDate>
    </item>
    <item>
      <title>GRE 터널링 + NAT 환경으로 외부통신 구현</title>
      <link>https://lordofkangs.tistory.com/729</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;이슈&lt;/b&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;K8S 를 특정 클라우드 환경에 구현하다가 NAT 기능이 없는 것을 보고 NAT 기능을 해줄 VM이 필요하여 생성하였는데 이슈가 있었다. &lt;span style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot;&gt;Client 서버(K8S Node)에서 NAT 서버로 정적 라우팅으로 트래픽을 foward하면 클라우드 환경 내부에서 비정상 패킷으로 감지하고 DROP 시키는 이슈가 있었다. &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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot;&gt;&lt;b&gt;조치&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot;&gt;NAT서버와 Client 서버 사이에 GRE 터널링을 적용하여 패킷을 캡슐화하고, 목적지 IP와 NIC의 MAC 주소 일관성을 유지함으로써 비정상 패킷으로 인한 DROP을 방지하고 NAT 기능을 구현하였다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot;&gt;NAT 서버 설정&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1776754600177&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# IP 포워딩 영구 활성화 +.  리눅스가 &amp;ldquo;패킷 경로 이상하다&amp;rdquo;면서 버리는 걸 막는 설정(rp_filter)
echo &quot;net.ipv4.ip_forward=1&quot; &amp;gt;&amp;gt; /etc/sysctl.conf
echo &quot;net.ipv4.conf.all.rp_filter=0&quot; &amp;gt;&amp;gt; /etc/sysctl.conf
echo &quot;net.ipv4.conf.default.rp_filter=0&quot; &amp;gt;&amp;gt; /etc/sysctl.conf
echo &quot;net.ipv4.conf.enp0s3.rp_filter=0&quot; &amp;gt;&amp;gt; /etc/sysctl.conf
sysctl -p

# NAT 규칙 ( enp0s3 = 외부 인터페이스 )
# SNAT ( 나가는 인터페이스(enp0s3) IP로 출발지 IP 변경 )
iptables -t nat -A POSTROUTING -o enp0s3 -j MASQUERADE 
iptables -A FORWARD -j ACCEPT

# iptables 영구 저장
apt install iptables-persistent -y  # ubuntu
netfilter-persistent save&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot;&gt;NAT 서버 netplan&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1776754687147&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;network:
    bonds: {}
    ethernets:
        ens1:
            addresses:
            - 192.168.0.5/24
            match:
                macaddress: 02:00:f9:80:19:4c
            mtu: 8950
            nameservers:
                addresses:
                - 8.8.8.8
            routes:
            -   to: default
                via: 192.168.0.1
    tunnels:
      gre1:
        mode: gre
        local: 192.168.0.5
        remote: 192.168.0.3
        ttl: 255
        addresses:
          - 172.16.100.1/30
      gre2:
        mode: gre
        local: 192.168.0.5
        remote: 192.168.0.4
        ttl: 255
        addresses:
          - 172.16.101.1/30
    version: 2&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Client 서버1 netplan ( gre1 )&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1776754722123&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;network:
    bonds: {}
    ethernets:
        ens1:
            addresses:
            - 192.168.0.3/24
            match:
                macaddress: 02:00:d4:67:47:37
            mtu: 8950
            nameservers:
                addresses:
                - 8.8.8.8
    tunnels:
      gre1:
        mode: gre
        local: 192.168.0.3
        remote: 192.168.0.5
        ttl: 255
        addresses:
          - 172.16.100.2/30
        routes:
          - to: default
            via: 172.16.100.1
    version: 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Client 서버1 netplan ( gre2 )&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1776754750479&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;network:
    bonds: {}
    ethernets:
        ens1:
            addresses:
            - 192.168.0.4/24
            match:
                macaddress: 02:00:75:42:ce:24
            mtu: 8950
            nameservers:
                addresses:
                - 8.8.8.8
    tunnels:
      gre1:
        mode: gre
        local: 192.168.0.4
        remote: 192.168.0.5
        ttl: 255
        addresses:
          - 172.16.101.2/30
        routes:
          - to: default
            via: 172.16.101.1
    version: 2&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-indent-level=&quot;1&quot; data-local-id=&quot;cf95baecca4a&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부로 ping이 가고 apt update가 잘 되는 것을 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cu5w3P/dJMcagyrzNd/Bhmj9PaA9eMHQTbUDGv5M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cu5w3P/dJMcagyrzNd/Bhmj9PaA9eMHQTbUDGv5M1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cu5w3P/dJMcagyrzNd/Bhmj9PaA9eMHQTbUDGv5M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcu5w3P%2FdJMcagyrzNd%2FBhmj9PaA9eMHQTbUDGv5M1%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;541&quot; height=&quot;260&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;260&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;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-indent-level=&quot;1&quot; data-local-id=&quot;b8324b16fe8e&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pod 내부에서도 외부 통신 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zq0gg/dJMcaa56bsL/hFDxz74Jtnodx35Mq9SqS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zq0gg/dJMcaa56bsL/hFDxz74Jtnodx35Mq9SqS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zq0gg/dJMcaa56bsL/hFDxz74Jtnodx35Mq9SqS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZq0gg%2FdJMcaa56bsL%2FhFDxz74Jtnodx35Mq9SqS0%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;592&quot; height=&quot;339&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;339&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Ops/Linux</category>
      <author>록흐</author>
      <guid isPermaLink="true">https://lordofkangs.tistory.com/729</guid>
      <comments>https://lordofkangs.tistory.com/729#entry729comment</comments>
      <pubDate>Tue, 21 Apr 2026 16:01:34 +0900</pubDate>
    </item>
    <item>
      <title>영구적인 Service Account Token 만들기</title>
      <link>https://lordofkangs.tistory.com/728</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;아래처럼 ServiceAccount에서 토큰을 추출하면 토큰만료기간이 정해져있어 보안에는 좋지만 관리가 쉽지 않다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1770180712425&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 한시적인 Token 생성 
kubectl create token &amp;lt;ServiceAccount명&amp;gt; -n &amp;lt;namespace명&amp;gt; --duration=24h&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;i&gt;kubernetes.io/service-account-token 타입의&amp;nbsp;&lt;/i&gt;Secret을 생성하여 ControlPlane이 etcd에 저장된 Token을 만료시간을 reconciliation 하도록 해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;To create a non-expiring, persisted API token for a ServiceAccount, create a Secret of type&amp;nbsp;kubernetes.io/service-account-token&amp;nbsp;with an annotation referencing the ServiceAccount. The control plane then generates a long-lived token and updates that Secret with that generated token data. ( &lt;a style=&quot;color: #666666;&quot; href=&quot;https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/&lt;/a&gt; )&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Secret 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770180920363&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Secret
metadata:
  name: &amp;lt;SA이름&amp;gt;-token
  namespace: &amp;lt;네임스페이스&amp;gt;
  annotations:
    kubernetes.io/service-account.name: &quot;&amp;lt;SA이름&amp;gt;&quot;
type: kubernetes.io/service-account-token&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Token 추출&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770180979141&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ARGO_TOKEN=&quot;Bearer $(kubectl get secret &amp;lt;ServiceAccount명&amp;gt;.service-account-token -o=jsonpath='{.data.token}' | base64 --decode)&quot;
echo $ARGO_TOKEN&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Ops/Kubernetes</category>
      <author>록흐</author>
      <guid isPermaLink="true">https://lordofkangs.tistory.com/728</guid>
      <comments>https://lordofkangs.tistory.com/728#entry728comment</comments>
      <pubDate>Wed, 4 Feb 2026 14:01:37 +0900</pubDate>
    </item>
    <item>
      <title>node-core-dns 란?</title>
      <link>https://lordofkangs.tistory.com/727</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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;K8s에 떠있는 파드 하나에 내부로 접속해서 resolv.conf 파일을 열어보면 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1767170969325&quot; class=&quot;pgsql&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;bash-4.4$ cat /etc/resolv.conf
search mongodb.svc.cluster.local svc.cluster.local cluster.local default.svc.cluster.local
nameserver 192.254.25.10
options ndots:5&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;kube-proxy는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;svc -&amp;gt; pod 트래픽을 이동시킨다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 만약 다른 파드로 트래픽을 보내고 싶다면 svc로 트래픽을 보내면 된다. svc의 도메인과 ip 매핑 정보는 CoreDNS에 저장되어 있는데, DNS 쿼리 성능을 높히기 위해, CoreDNS의 캐시를 가지고 있는 파드를 daemonset으로 배포하는데, 그것이 바로,&lt;b&gt; 'node-core-dns'&lt;/b&gt; 이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;501&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nsJxT/dJMcaacRPq3/mPD66KnZp39oorMcmXpdi1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nsJxT/dJMcaacRPq3/mPD66KnZp39oorMcmXpdi1/img.jpg&quot; data-alt=&quot;https://kubernetes.io/docs/tasks/administer-cluster/nodelocaldns/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nsJxT/dJMcaacRPq3/mPD66KnZp39oorMcmXpdi1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnsJxT%2FdJMcaacRPq3%2FmPD66KnZp39oorMcmXpdi1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;362&quot; height=&quot;302&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;501&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://kubernetes.io/docs/tasks/administer-cluster/nodelocaldns/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 파드는 nameserver ip로 192.254.25.10 설정되어 있는데, 192.254.25.10로 드러온 트래픽은 node-core-dns 파드로 전달되도록 kube-proxy가 설정해놓는다. &lt;i&gt;( iptables or ipvs )&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1767169999461&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ip addr | grep nodelocaldns
7: nodelocaldns: &amp;lt;BROADCAST,NOARP&amp;gt; mtu 1500 qdisc noop state DOWN group default
    inet 192.254.25.10/32 scope global nodelocaldns&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;그러므로 각 파드는 nodlocaldns에 질의를 날려야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1767170253299&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bash-4.4$ cat /etc/resolv.conf
search mongodb.svc.cluster.local svc.cluster.local cluster.local default.svc.cluster.local
nameserver 192.254.25.10
options ndots:5&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;각 파드는 mongodb.svc.cluster.local svc.cluster.local cluster.local default.svc.cluster.local와 같이 일종의 Postfix(?)를 다양한 범위로 가지고 있다. 그래서 svc의 모든 도메인을 치지 않아도, systemd-resolved가 여러 시도를 수행한다.&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;monitoring 네임스페이스에 있는 grafana svc에 트래픽을 보내고 싶다면 [서비스명].[네임스페이스]만 설정해도 서비스의 ip를 dns로 부터 얻을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1767171299772&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ nc -zv monitoring-stack-grafana.monitoring 80
monitoring-stack-grafana.monitoring (10.233.13.144:80) open&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고자료&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kubernetes.io/docs/tasks/administer-cluster/nodelocaldns/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kubernetes.io/docs/tasks/administer-cluster/nodelocaldns/&lt;/a&gt;&lt;/p&gt;</description>
      <category>Ops/Kubernetes</category>
      <author>록흐</author>
      <guid isPermaLink="true">https://lordofkangs.tistory.com/727</guid>
      <comments>https://lordofkangs.tistory.com/727#entry727comment</comments>
      <pubDate>Wed, 31 Dec 2025 17:56:49 +0900</pubDate>
    </item>
    <item>
      <title>[Terraform] 테라폼 동작원리 ( + Ansible )</title>
      <link>https://lordofkangs.tistory.com/726</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Terraform이 강력한 이유는 &lt;b&gt;tfstate 파일&lt;/b&gt;로 인프라를 리소스 단위로&lt;b&gt; 상태 &lt;/b&gt;관리를 하기 때문이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;AWS, AZURE 같은 Public Cloud 환경은 여러 리소스 ( VPC, EC2 등 )이 서로 유기적으로 연동되어 있는데, 이를 '코드'로 구현하고 구현된 &lt;b&gt;'상태'&lt;/b&gt;를 비교하여 어떤 인프라 요소가 생성, 변경, 삭제 되는지를 예측할 수 있다. 반면 대표적인 또 다른 배포도구인 &lt;b&gt;Ansible&lt;/b&gt;은 상태를 관리하지 않는다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;&lt;i&gt;상태를 관리하는 Terraform, 상태를 관리하지 않는 Ansible.&lt;/i&gt;&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;두 도구의 철학은 다르다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Terraform은 &lt;b&gt;비교&lt;/b&gt;로 인프라를 관리하고&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Ansible은 &lt;b&gt;명령&lt;/b&gt;으로 인프라를 관리한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Terraform은&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;내가 원하는 상태는 &lt;i&gt;'EC2가 t3.small이야'&lt;/i&gt; 라고 말하면&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;테라폼은 실제 환경과 API로 통신하여 &lt;i&gt;'실제 환경은 EC2가 t3.large네'&lt;/i&gt;라고 상태를 비교하여 변경,생성,삭제 지점을 파악한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Ansible은&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;'EC2를 &lt;i&gt;t3.small&lt;/i&gt;로 변경해!' 라고 그냥 명령을 내리는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Ansible은 직접 ssh로 접속해서 생성,변경,삭제 명령을 수행한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그러므로&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Code를 절차적으로 실행해서 인프라 환경을 구성하는 작업은 Ansible이 유리하고 ( OS 설정 같은 내부작업 )&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;AWS, Azure 인프라 환경 같이 여러 리소스가 유기적으로 연결되어 있는 인프라의 생성 및 관리는 테라폼이 유리하다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;한마디로 정리하면,&amp;nbsp; &amp;nbsp;&lt;i&gt;테라폼은 &lt;b&gt;'과거'&lt;/b&gt;를 기억한다.&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;테라폼 동작 원리&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;테라폼은 과거를 '장부'에 기록하는데, 그것이 tfstate 파일이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u&gt;&lt;i&gt;왜 장부(tfstate)가 필요할까?&lt;/i&gt;&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목 없는 다이어그램.drawio (1).png&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;291&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTrulA/dJMcaajBN1y/jBoAUhvXFqBg94FXGIF2nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTrulA/dJMcaajBN1y/jBoAUhvXFqBg94FXGIF2nk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTrulA/dJMcaajBN1y/jBoAUhvXFqBg94FXGIF2nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTrulA%2FdJMcaajBN1y%2FjBoAUhvXFqBg94FXGIF2nk%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;701&quot; height=&quot;291&quot; data-filename=&quot;제목 없는 다이어그램.drawio (1).png&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;291&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 Engineer가 good_ec2라는 이름을 가진 EC2 서버의 설정을 변경하는 코드를 작성했다 가정하자. 그러나 실제환경인 AWS는 good_ec2가 뭔지 모른다. good_ec2는 사람이 이해할 수 있는 이름이므로 EC2를 특정지을 수 없다. 또 다른 누군가가 특정 EC2를 good_ec2로 네이밍할 수도 있는것이다. 특정 지을수 없다면 추적되지 못하므로 지속적 관리가 불가능해진다. &lt;i&gt;( Ansible처럼 일회성으로 생성, 변경, 삭제가 목적이라면 모를까... )&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목 없는 다이어그램.drawio (3).png&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;291&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZQfiZ/dJMcaaw84Es/HKNFD11cEfeGEAuWbHt8u0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZQfiZ/dJMcaaw84Es/HKNFD11cEfeGEAuWbHt8u0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZQfiZ/dJMcaaw84Es/HKNFD11cEfeGEAuWbHt8u0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZQfiZ%2FdJMcaaw84Es%2FHKNFD11cEfeGEAuWbHt8u0%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;851&quot; height=&quot;291&quot; data-filename=&quot;제목 없는 다이어그램.drawio (3).png&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;291&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;미래와 현재 사이에 과거를 기억하는 tfstate를 두어보자.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Engineer가 good_ec2를 최초 생성할때 tfstate에는 good_ec2가 어떤 EC2인지 특정지을 수 있는 ID와 매핑되어 기록된다. Engineer가 good_ec2를 변경하려한다면 테라폼은&amp;nbsp; tfstate를 읽어 해당 EC2의 상태를 API로 호출하여 비교한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 tfstate의 기록을 현재(실제환경)에서 API로 호출하여 최신화하는 작업을 &lt;b&gt;refresh&lt;/b&gt; 라고한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;'terraform plan'&lt;/i&gt;을 실행하면 미래 와 현재를 비교한다. tfstate에 기록된 대상들을 임시로 refresh하여 현재상태로 만들고 수정된 코드와 비교한다 . &lt;i&gt;( apply가 아닌 plan이므로 실제 tfstate가 최신화되는 것은 아님, 임시로... )&lt;/i&gt; &amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;현재에는 없는데 미래에 있으면 생성(Create)&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;현재에도 있고 미래에도 있으면 변경(Update)&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;현재에는 있는데 미래에 없으면 삭제(Delete)&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;모든 것은 상태가 기록된 tfstate를 기준으로 동작하므로, &lt;i&gt;&lt;u&gt;&lt;b&gt;tfstate는 정말 중요하다.&amp;nbsp;&lt;/b&gt;&lt;/u&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;실제 환경에 인프라가 구성되어 있는데, tfstate를 지워버리면 어떻게 될까?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;테라폼은 tfstate를 기준으로 refresh하여 현재 코드와 실제환경을 비교하므로, tfstate가 없다면 코드로 작성된 모든 리소스는 신규 생성 대상이 된다. 이미 인프라가 모두 구성되어 있는데, 동일한 인프라가 중복되어 또 다시 생성되는 것이다. 이렇듯 tfstate는 항상 최신상태를 유지하도록 관리되어야 하므로, 여러 사람이 동시에 테라폼코드를 수정하는 상황을 잘 제어해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;테라폼 배포과정&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드 작성(tf파일 생성) &amp;gt; init &amp;gt; validate &amp;gt; plan &amp;gt; apply&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1766752820606&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;terraform init&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;init은 테라폼 초기환경을 구성한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;코드에 작성한 API를 호출하는 프로바이더에 대한 정보가 명시된 .terraform.lock.hcl 파일을 생성하고 프로바이더 바이너리 파일을 설치한다.&amp;nbsp; &lt;i&gt;.terraform.lock.hcl &lt;/i&gt;파일은 설치한 프로바이더에 대한 정보가 명시되어 있다. &lt;i&gt;.terraform.lock.hcl&lt;/i&gt; 파일을 git으로 공유하면 다른 팀원이 init 시, &lt;span style=&quot;color: #333333; text-align: justify;&quot;&gt;&lt;i&gt;.terraform.lock.hcl&lt;/i&gt;&lt;span&gt; 파일에 명시된 프로바이더를 설치한다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: justify;&quot;&gt;&lt;span&gt;이외에도 선언한 모듈들의 소스를 로컬로 가져오거나 tfstate를 어디에 저장할 것인가에 대한 여러 설정을 초기화 한다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1766753257714&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;terraform validate&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;작성된 소스들의 테라폼 문법을 검사하는 과정이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1766753318463&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;terraform plan&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;tfstate 파일을 임시로 refresh하여 테라폼 코드와 상태를 비교하는 과정이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1766753359256&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;terraform apply&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;실제 환경으로 배포가 되는 과정이다. 배포가 완료되면 tfstate 파일은 refresh되어 현재 상태가 기록된다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Ops/CICD</category>
      <author>록흐</author>
      <guid isPermaLink="true">https://lordofkangs.tistory.com/726</guid>
      <comments>https://lordofkangs.tistory.com/726#entry726comment</comments>
      <pubDate>Fri, 26 Dec 2025 21:50:50 +0900</pubDate>
    </item>
  </channel>
</rss>