<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Kubernetes 실무 가이드 – Blog</title><link>https://k8s-km.metacog.co.kr/blog/</link><description>Recent content in Blog on Kubernetes 실무 가이드</description><generator>Hugo -- gohugo.io</generator><language>ko</language><lastBuildDate>Sat, 04 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://k8s-km.metacog.co.kr/blog/index.xml" rel="self" type="application/rss+xml"/><item><title>내 손으로 뚫고 막는다 — 쿠버네티스 보안 10대 과제 CTF를 만들며</title><link>https://k8s-km.metacog.co.kr/blog/kubernetes-security-ctf-top10/</link><pubDate>Sat, 04 Jul 2026 00:00:00 +0000</pubDate><guid>https://k8s-km.metacog.co.kr/blog/kubernetes-security-ctf-top10/</guid><description>
&lt;p&gt;&amp;ldquo;내 클러스터가 진짜 안전한가?&amp;ldquo;라는 질문에는 문서를 아무리 읽어도 자신 있게 답하기 어렵습니다. RBAC 개념을 안다는 것과, 와일드카드 권한을 실제로 손에 쥐고 &lt;code&gt;kubectl get secrets&lt;/code&gt;로 플래그를 훔쳐본 경험은 완전히 다른 층위의 이해이기 때문입니다. 그래서 &lt;a
href="https://k8s-km.metacog.co.kr/docs/security/"&gt;Docs의 보안 섹션&lt;/a&gt;에 이론(Concept)과 실습(Hands-on)만으로는 채워지지 않는 한 조각, &lt;strong&gt;공격자가 되어 침투하고 다시 방어자로 돌아와 패치하는&lt;/strong&gt; CTF(Capture The Flag) 미션 10개를 추가했습니다.&lt;/p&gt;
&lt;h2&gt;왜 뚫어보는 게 먼저인가&lt;span class="hx:absolute hx:-mt-20" id="왜-뚫어보는-게-먼저인가"&gt;&lt;/span&gt;
&lt;a href="#%ec%99%9c-%eb%9a%ab%ec%96%b4%eb%b3%b4%eb%8a%94-%ea%b2%8c-%eb%a8%bc%ec%a0%80%ec%9d%b8%ea%b0%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;쿠버네티스 보안 사고의 대다수는 &amp;ldquo;몰라서&amp;quot;가 아니라 &lt;strong&gt;기본값을 그대로 쓰거나, 여러 방어선 중 하나만 잠그고 나머지를 방치해서&lt;/strong&gt; 일어납니다. &lt;code&gt;securityContext&lt;/code&gt; 하나 빠뜨린 것, RBAC에 박아둔 &lt;code&gt;*&lt;/code&gt; 하나, 환경 변수에 그대로 적어둔 비밀번호 하나 — 글로 읽을 때는 사소해 보이지만, 그 설정이 없을 때 실제로 무엇이 뚫리는지 직접 겪어보면 다시는 잊히지 않습니다.&lt;/p&gt;
&lt;p&gt;그래서 &lt;a
href="https://github.com/hac01/Owasp-top-10-k8s-2025"target="_blank" rel="noopener"&gt;OWASP Kubernetes Top 10 (2025)&lt;/a&gt; 기준을 뼈대로 삼아 가상의 이커머스 기업 &lt;strong&gt;NimbusMart&lt;/strong&gt;의 취약한 클러스터를 배경으로, 각 미션을 &amp;ldquo;취약한 설정 → 침투 → 패치 → 재검증&amp;quot;의 사이클로 구성했습니다.&lt;/p&gt;
&lt;h2&gt;10개 미션이 그리는 지도&lt;span class="hx:absolute hx:-mt-20" id="10개-미션이-그리는-지도"&gt;&lt;/span&gt;
&lt;a href="#10%ea%b0%9c-%eb%af%b8%ec%85%98%ec%9d%b4-%ea%b7%b8%eb%a6%ac%eb%8a%94-%ec%a7%80%eb%8f%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;단계&lt;/th&gt;
&lt;th&gt;미션&lt;/th&gt;
&lt;th&gt;무엇을 배우는가&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1~3&lt;/td&gt;
&lt;td&gt;&lt;a
href="https://k8s-km.metacog.co.kr/docs/security/ctf-top10/k01-insecure-workload/"&gt;K01 Insecure Workload&lt;/a&gt; · &lt;a
href="https://k8s-km.metacog.co.kr/docs/security/ctf-top10/k02-authorization/"&gt;K02 Authorization&lt;/a&gt; · &lt;a
href="https://k8s-km.metacog.co.kr/docs/security/ctf-top10/k03-secrets/"&gt;K03 Secrets&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;컨테이너 실행, 권한, 정보 관리 — 기본기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4~6&lt;/td&gt;
&lt;td&gt;&lt;a
href="https://k8s-km.metacog.co.kr/docs/security/ctf-top10/k04-policy-enforcement/"&gt;K04 Policy Enforcement&lt;/a&gt; · &lt;a
href="https://k8s-km.metacog.co.kr/docs/security/ctf-top10/k05-network-segmentation/"&gt;K05 Network Segmentation&lt;/a&gt; · &lt;a
href="https://k8s-km.metacog.co.kr/docs/security/ctf-top10/k06-exposed-components/"&gt;K06 Exposed Components&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;정책, 네트워크, 외부 노출 관리 — 방어선 구축&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7~10&lt;/td&gt;
&lt;td&gt;&lt;a
href="https://k8s-km.metacog.co.kr/docs/security/ctf-top10/k07-cluster-components/"&gt;K07 Cluster Components&lt;/a&gt; · &lt;a
href="https://k8s-km.metacog.co.kr/docs/security/ctf-top10/k08-cluster-to-cloud/"&gt;K08 Cluster to Cloud&lt;/a&gt; · &lt;a
href="https://k8s-km.metacog.co.kr/docs/security/ctf-top10/k09-authentication/"&gt;K09 Authentication&lt;/a&gt; · &lt;a
href="https://k8s-km.metacog.co.kr/docs/security/ctf-top10/k10-logging-monitoring/"&gt;K10 Logging &amp;amp; Monitoring&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;컴포넌트 보안, 클라우드 연동, 인증, 기록 — 운영 보안&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;기획하면서 의도적으로 지킨 원칙이 하나 있습니다. &lt;strong&gt;앞 단계가 뚫리는 이유를 뒷 단계가 이어받도록 설계한 것&lt;/strong&gt;입니다. K01에서 컨테이너 내부 권한을 최소화해도, K02에서 RBAC이 와일드카드면 결국 API 서버 자체가 뚫립니다. K02를 좁혀도 K03처럼 비밀번호가 평문으로 널려 있으면 그 좁은 권한만으로도 충분히 털립니다. 하나의 레이어만 완벽한 보안은 존재하지 않는다는 걸, 순서대로 밟아보면 몸으로 느끼게 됩니다.&lt;/p&gt;
&lt;h2&gt;가장 기억에 남는 세 장면&lt;span class="hx:absolute hx:-mt-20" id="가장-기억에-남는-세-장면"&gt;&lt;/span&gt;
&lt;a href="#%ea%b0%80%ec%9e%a5-%ea%b8%b0%ec%96%b5%ec%97%90-%eb%82%a8%eb%8a%94-%ec%84%b8-%ec%9e%a5%eb%a9%b4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;K01 — &lt;code&gt;Read-only file system&lt;/code&gt;이 뜨는 순간.&lt;/strong&gt; &lt;code&gt;securityContext&lt;/code&gt; 없이 띄운 Pod에 접속해 &lt;code&gt;whoami&lt;/code&gt;를 치면 &lt;code&gt;root&lt;/code&gt;가 나오고, &lt;code&gt;touch /test.txt&lt;/code&gt;도 그냥 됩니다. &lt;code&gt;runAsNonRoot&lt;/code&gt;와 &lt;code&gt;readOnlyRootFilesystem&lt;/code&gt;만 추가했을 뿐인데, 똑같은 명령이 &lt;code&gt;Read-only file system&lt;/code&gt; 에러로 막힙니다. 설정 네 줄의 무게가 이렇게 크다는 걸 코드로 보여줄 때가 가장 설득력이 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;K03 — &lt;code&gt;describe pod&lt;/code&gt; 한 번으로 끝나는 게임.&lt;/strong&gt; &lt;code&gt;env.value&lt;/code&gt;에 비밀번호를 박아두면 &lt;code&gt;kubectl describe pod&lt;/code&gt;만으로 그대로 노출됩니다. &lt;code&gt;Secret&lt;/code&gt; 참조로 바꾸면 화면에는 &lt;code&gt;&amp;lt;set to the key 'password' in secret 'db-secret'&amp;gt;&lt;/code&gt;라는 레퍼런스만 남고, 실제 값을 보려면 별도의 RBAC 권한이 또 필요해집니다. K02(권한)와 K03(비밀 관리)이 서로를 지탱하는 구조라는 걸 가장 잘 보여주는 미션입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;K08 — &lt;code&gt;169.254.169.254&lt;/code&gt;라는 숫자.&lt;/strong&gt; 클라우드 위에서 도는 Pod는 기본적으로 메타데이터 서비스에 접근할 수 있고, 여기엔 노드의 IAM 임시 토큰이 그대로 담겨 있습니다. Pod 하나만 뚫려도 클러스터를 넘어 &lt;strong&gt;클라우드 계정 자체&lt;/strong&gt;가 위험해질 수 있다는 걸 &lt;code&gt;curl&lt;/code&gt; 한 줄로 확인하는 순간, &amp;ldquo;쿠버네티스 보안&amp;quot;의 경계가 클러스터 안에서 끝나지 않는다는 걸 체감했습니다.&lt;/p&gt;
&lt;h2&gt;Docs와의 관계&lt;span class="hx:absolute hx:-mt-20" id="docs와의-관계"&gt;&lt;/span&gt;
&lt;a href="#docs%ec%99%80%ec%9d%98-%ea%b4%80%ea%b3%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;a
href="https://k8s-km.metacog.co.kr/blog/welcome/"&gt;블로그를 운영하는 방식&lt;/a&gt;에서 밝힌 원칙대로, 이 글은 1차 기록이고 정제된 지식은 &lt;a
href="https://k8s-km.metacog.co.kr/docs/security/ctf-top10/"&gt;Docs의 CTF 미션 페이지&lt;/a&gt;에 남겼습니다. 각 미션 페이지에는 &lt;code&gt;vulnerable.yaml&lt;/code&gt;/&lt;code&gt;fixed.yaml&lt;/code&gt; 비교, 실제 침투·방어 명령어, 체크리스트까지 실려 있어서 실습 환경만 있으면 바로 손으로 따라 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;이론으로 &amp;ldquo;RBAC은 최소 권한이어야 한다&amp;quot;를 아는 것과, &lt;code&gt;verbs: [&amp;quot;*&amp;quot;]&lt;/code&gt;로 뚫린 뒤 &lt;code&gt;verbs: [&amp;quot;get&amp;quot;, &amp;quot;list&amp;quot;]&lt;/code&gt;로 막아본 것은 다른 종류의 지식입니다. 결국 보안은 책보다, 한 번 뚫어보고 막아보는 경험에서 더 오래 남습니다.&lt;/p&gt;</description></item><item><title>실무 능력이 있는 사람의 4가지 패턴 — 쿠버네티스 장애 대응 한 장면으로 보기</title><link>https://k8s-km.metacog.co.kr/blog/4-patterns-of-real-skill-in-k8s-incidents/</link><pubDate>Wed, 17 Jun 2026 00:00:00 +0000</pubDate><guid>https://k8s-km.metacog.co.kr/blog/4-patterns-of-real-skill-in-k8s-incidents/</guid><description>
&lt;p&gt;새벽 2시, 알람이 울립니다. &amp;ldquo;결제 서비스 Pod가 CrashLoopBackOff 상태입니다.&amp;rdquo; 같은 장애, 같은 정보를 받았는데도 사람마다 다음 30분을 완전히 다르게 씁니다. 그 차이가 바로 &amp;ldquo;말만 앞서는 사람&amp;quot;과 &amp;ldquo;실제로 해결하는 사람&amp;quot;을 가른다고 생각합니다.&lt;/p&gt;
&lt;p&gt;이력서의 화려한 스펙이나 면접 답변으로는 이 차이가 잘 안 보입니다. 대신 &lt;strong&gt;문제를 다루는 행동 패턴&lt;/strong&gt;에서 선명하게 드러납니다. 쿠버네티스 장애 대응이라는 구체적인 장면을 빌려 네 가지 패턴을 풀어보겠습니다.&lt;/p&gt;
&lt;h2&gt;① &amp;lsquo;현상&amp;rsquo;과 &amp;lsquo;원인&amp;rsquo;을 분리하여 질문한다&lt;span class="hx:absolute hx:-mt-20" id="-현상과-원인을-분리하여-질문한다"&gt;&lt;/span&gt;
&lt;a href="#-%ed%98%84%ec%83%81%ea%b3%bc-%ec%9b%90%ec%9d%b8%ec%9d%84-%eb%b6%84%eb%a6%ac%ed%95%98%ec%97%ac-%ec%a7%88%eb%ac%b8%ed%95%9c%eb%8b%a4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;같은 알람을 받은 두 사람을 비교해 보겠습니다.&lt;/p&gt;
&lt;div class="hextra-scrollbar hx:overflow-x-auto hx:overflow-y-hidden hx:overscroll-x-contain"&gt;
&lt;div class="hx:mt-4 hx:flex hx:w-max hx:min-w-full hx:border-b hx:border-gray-200 hx:pb-px hx:dark:border-neutral-800" role="tablist"&gt;&lt;button class="hextra-tabs-toggle hx:cursor-pointer hx:data-[state=selected]:border-primary-500 hx:data-[state=selected]:text-primary-600 hx:data-[state=selected]:dark:border-primary-500 hx:data-[state=selected]:dark:text-primary-600 hx:mr-2 hx:rounded-t hx:p-2 hx:font-medium hx:leading-5 hx:transition-colors hx:-mb-0.5 hx:select-none hx:border-b-2 hx:border-transparent hx:text-gray-600 hx:hover:border-gray-200 hx:hover:text-black hx:dark:text-gray-200 hx:dark:hover:border-neutral-800 hx:dark:hover:text-white hx:hextra-focus-visible-inset" id="tabs-tab-tabs-00-0" role="tab" type="button" aria-controls="tabs-panel-tabs-00-0" aria-selected="true" tabindex="0" data-state="selected"&gt;&lt;span class="hx:inline-flex hx:items-center hx:gap-1.5"&gt;&lt;span&gt;일반 직원&lt;/span&gt;&lt;/span&gt;&lt;/button&gt;&lt;button class="hextra-tabs-toggle hx:cursor-pointer hx:data-[state=selected]:border-primary-500 hx:data-[state=selected]:text-primary-600 hx:data-[state=selected]:dark:border-primary-500 hx:data-[state=selected]:dark:text-primary-600 hx:mr-2 hx:rounded-t hx:p-2 hx:font-medium hx:leading-5 hx:transition-colors hx:-mb-0.5 hx:select-none hx:border-b-2 hx:border-transparent hx:text-gray-600 hx:hover:border-gray-200 hx:hover:text-black hx:dark:text-gray-200 hx:dark:hover:border-neutral-800 hx:dark:hover:text-white hx:hextra-focus-visible-inset" id="tabs-tab-tabs-00-1" role="tab" type="button" aria-controls="tabs-panel-tabs-00-1" aria-selected="false" tabindex="-1"&gt;&lt;span class="hx:inline-flex hx:items-center hx:gap-1.5"&gt;&lt;span&gt;실무 능력자&lt;/span&gt;&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;div class="hextra-tabs-panel hx:rounded-sm hx:pt-6 hx:hidden hx:data-[state=selected]:block" id="tabs-panel-tabs-00-0" role="tabpanel" aria-labelledby="tabs-tab-tabs-00-0" aria-hidden="false" tabindex="0" data-state="selected"&gt;&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Pod가 CrashLoopBackOff예요. 일단 재시작하겠습니다.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl delete pod payment-service-7d8f9c-x2k4j&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;새 Pod가 뜨고, 5분 후 또 같은 상태가 됩니다. 같은 조치를 반복하다가 결국 누군가에게 에스컬레이션합니다.&lt;/p&gt;
&lt;/div&gt;&lt;div class="hextra-tabs-panel hx:rounded-sm hx:pt-6 hx:hidden hx:data-[state=selected]:block" id="tabs-panel-tabs-00-1" role="tabpanel" aria-labelledby="tabs-tab-tabs-00-1" aria-hidden="true"&gt;&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;재시작 전에 죽은 이유부터 보죠. 방금 죽기 직전 로그를 먼저 확인하겠습니다.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 재시작 전 컨테이너가 왜 죽었는지부터 본다&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl logs payment-service-7d8f9c-x2k4j --previous
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl describe pod payment-service-7d8f9c-x2k4j &lt;span class="p"&gt;|&lt;/span&gt; grep -A5 &lt;span class="s2"&gt;&amp;#34;Last State&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Last State: Terminated
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Reason: OOMKilled
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Exit Code: 137&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;OOMKilled네요. 메모리 limit이 모자른 게 아니라, 최근 배포된 캐싱 모듈에서 메모리 누수가 의심됩니다. 배포 직전 트래픽 그래프 좀 같이 봐주실 수 있나요?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;재시작은 &amp;ldquo;현상&amp;quot;을 잠깐 가릴 뿐 &amp;ldquo;원인&amp;quot;을 묻지 않습니다. &lt;code&gt;CrashLoopBackOff&lt;/code&gt;는 결과일 뿐이고, &lt;code&gt;OOMKilled&lt;/code&gt;인지 &lt;code&gt;Error&lt;/code&gt;인지 &lt;code&gt;Liveness probe 실패&lt;/code&gt;인지에 따라 손을 대야 할 곳이 완전히 다릅니다. 실무 능력자는 &lt;strong&gt;해결책을 던지기 전에 질문의 정확도&lt;/strong&gt;부터 다릅니다. &amp;ldquo;재시작할까요?&amp;ldquo;가 아니라 &amp;ldquo;직전 상태가 뭐였죠?&amp;ldquo;를 먼저 묻습니다.&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:rounded-lg hx:border hx:py-2 hx:ltr:pr-4 hx:rtl:pl-4 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200"&gt;
&lt;div class="hx:ltr:pl-3 hx:ltr:pr-2 hx:rtl:pr-3 hx:rtl:pl-2"&gt;&lt;svg height=1.2em class="hx:inline-block hx:align-middle" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;실전 팁: &lt;code&gt;kubectl logs --previous&lt;/code&gt;와 &lt;code&gt;kubectl describe pod&lt;/code&gt;의 &lt;code&gt;Last State&lt;/code&gt; 섹션은 죽기 직전 컨테이너의 마지막 진술서입니다. 재시작 명령보다 먼저 눌러야 할 버튼입니다.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;② 복잡한 개념을 초등학생도 이해하게 설명한다&lt;span class="hx:absolute hx:-mt-20" id="-복잡한-개념을-초등학생도-이해하게-설명한다"&gt;&lt;/span&gt;
&lt;a href="#-%eb%b3%b5%ec%9e%a1%ed%95%9c-%ea%b0%9c%eb%85%90%ec%9d%84-%ec%b4%88%eb%93%b1%ed%95%99%ec%83%9d%eb%8f%84-%ec%9d%b4%ed%95%b4%ed%95%98%ea%b2%8c-%ec%84%a4%eb%aa%85%ed%95%9c%eb%8b%a4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;비개발자인 팀장이 묻습니다. &amp;ldquo;그래서 Pod가 재시작되면 왜 주소(IP)가 바뀌어요? 그게 왜 문제예요?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;전문 용어로 답하면 이렇게 됩니다: &lt;em&gt;&amp;ldquo;Pod는 휘발성 리소스라 재스케줄링 시 새로운 IP를 할당받기 때문에, 클라이언트가 고정 IP로 직접 접근하면 Connection Refused가 발생합니다.&amp;rdquo;&lt;/em&gt; — 틀린 말은 아니지만 팀장은 알아듣지 못합니다.&lt;/p&gt;
&lt;p&gt;메타인지가 높은 실무자는 비유로 풉니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Pod 하나하나는 호텔 객실 같은 거예요. 청소나 보수 때문에 손님이 옆방으로 옮겨질 수 있잖아요. 그러면 그 객실 번호로 전화를 걸던 사람은 연결이 안 되겠죠. 그래서 우리는 &amp;lsquo;Service&amp;rsquo;라는 &lt;strong&gt;호텔 안내 데스크&lt;/strong&gt;를 하나 둡니다. 외부에서는 항상 안내 데스크 번호로만 전화하고, 안내 데스크가 그 손님이 지금 몇 호실에 있는지 추적해서 연결해 줍니다. Pod 번지수(IP)가 바뀌어도 안내 데스크(Service) 번호는 절대 안 바뀌니까 문제가 없는 거예요.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div role="img" aria-label="다이어그램"&gt;
&lt;pre class="mermaid hx:mt-6"&gt;
flowchart LR
Client[&amp;#34;외부 요청&amp;#34;] --&amp;gt; SVC[&amp;#34;Service&amp;lt;br/&amp;gt;(안내 데스크, 고정 주소)&amp;#34;]
SVC --&amp;gt; P1[&amp;#34;Pod A&amp;lt;br/&amp;gt;(101호)&amp;#34;]
SVC -.새 Pod로 연결.-&amp;gt; P2[&amp;#34;Pod B&amp;lt;br/&amp;gt;(205호, 재배치 후)&amp;#34;]
&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;전문 용어를 줄줄 읊는다고 깊이가 있는 게 아닙니다. 오히려 자기가 정확히 뭘 알고 있는지 아는 사람만이, 상대의 눈높이에 맞춰 비유를 정확하게 골라낼 수 있습니다.&lt;/p&gt;
&lt;h2&gt;③ 예외 상황(Edge Case)과 리스크를 먼저 계산한다&lt;span class="hx:absolute hx:-mt-20" id="-예외-상황edge-case과-리스크를-먼저-계산한다"&gt;&lt;/span&gt;
&lt;a href="#-%ec%98%88%ec%99%b8-%ec%83%81%ed%99%a9edge-case%ea%b3%bc-%eb%a6%ac%ec%8a%a4%ed%81%ac%eb%a5%bc-%eb%a8%bc%ec%a0%80-%ea%b3%84%ec%82%b0%ed%95%9c%eb%8b%a4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;신규 버전 배포를 앞두고 두 사람의 머릿속이 다릅니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;해피패스만 보는 사람&lt;/strong&gt;: &amp;ldquo;이미지 빌드됐고, 매니페스트 적용하면 끝이죠.&amp;rdquo; → &lt;code&gt;kubectl apply -f deployment.yaml&lt;/code&gt; 실행 후 자리를 뜹니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;리스크를 먼저 계산하는 사람&lt;/strong&gt;: 배포 적용 &lt;em&gt;전에&lt;/em&gt; 이미 답을 준비해 둡니다.
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;새 버전이 실패하면 되돌릴 방법은?&amp;rdquo; → &lt;code&gt;kubectl rollout undo deployment/payment-service&lt;/code&gt;가 즉시 먹히도록 &lt;code&gt;revisionHistoryLimit&lt;/code&gt;을 확인해 둔다.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;배포 도중 트래픽이 한쪽으로만 몰리면?&amp;rdquo; → &lt;code&gt;readinessProbe&lt;/code&gt;로 준비 안 된 Pod에 트래픽이 가지 않게 막아둔다.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;노드 점검 중에 가용 Pod가 0개가 되면?&amp;rdquo; → &lt;code&gt;PodDisruptionBudget&lt;/code&gt;으로 최소 가용 개수를 보장해 둔다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 배포 전에 이미 깔아둔 안전망&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;policy/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;PodDisruptionBudget&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;payment-service-pdb&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;minAvailable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matchLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;payment-service&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;해피패스만 그리는 사람은 계획이 &amp;ldquo;성공했을 때&amp;quot;의 그림만 그립니다. 리스크를 계산하는 사람은 &amp;ldquo;실패했을 때 누르는 비상벨&amp;quot;을 계획 단계에서 이미 설치해 둡니다. 사고가 터졌을 때 비로소 &lt;code&gt;rollout undo&lt;/code&gt;를 검색하는 사람과, 사고가 나기 전에 이미 되돌릴 수 있는 상태를 만들어 둔 사람의 차이입니다.&lt;/p&gt;
&lt;h2&gt;④ 지식의 &amp;lsquo;족보(Context)&amp;lsquo;를 알고 있다&lt;span class="hx:absolute hx:-mt-20" id="-지식의-족보context를-알고-있다"&gt;&lt;/span&gt;
&lt;a href="#-%ec%a7%80%ec%8b%9d%ec%9d%98-%ec%a1%b1%eb%b3%b4context%eb%a5%bc-%ec%95%8c%ea%b3%a0-%ec%9e%88%eb%8b%a4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&amp;ldquo;이 클러스터는 왜 &lt;code&gt;PodSecurityPolicy&lt;/code&gt; 대신 &lt;code&gt;Pod Security Admission&lt;/code&gt;을 쓰나요?&amp;ldquo;라는 질문에 두 가지 답이 가능합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;예전 가이드에 있던 대로 그냥 복사해서 썼습니다.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;PSP는 쿠버네티스 1.25에서 완전히 제거됐습니다. 복잡한 어드미션 규칙 때문에 유지보수가 어려웠던 게 폐기 이유였고, 후속으로 훨씬 단순한 &lt;code&gt;baseline&lt;/code&gt;/&lt;code&gt;restricted&lt;/code&gt; 레벨을 네임스페이스 라벨로 지정하는 PSA가 들어왔습니다. 더 세밀한 정책이 필요하면 그 위에 Kyverno나 OPA Gatekeeper를 얹는 게 지금 권장되는 조합입니다.&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;전자는 &amp;ldquo;구글링해서 복사·붙여넣기&amp;quot;한 결과물이고, 후자는 그 기술이 &lt;strong&gt;왜 지금의 모습으로 존재하는지 족보&lt;/strong&gt;를 꿰고 있는 답입니다. 이런 사람은 새로운 버전이 나왔을 때 &amp;ldquo;이번에 또 뭐가 deprecated됐지?&amp;ldquo;를 스스로 점검하고, 오픈소스를 도입할 때 &amp;ldquo;이 프로젝트가 과거 어떤 CVE를 겪었고 지금 메인테이너 활동이 활발한지&amp;quot;까지 확인합니다. 맥락을 알아야 다음에 같은 실수를 피할 수 있기 때문입니다.&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:rounded-lg hx:border hx:py-2 hx:ltr:pr-4 hx:rtl:pl-4 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-amber-200 hx:bg-amber-100 hx:text-amber-900 hx:dark:border-amber-200/30 hx:dark:bg-amber-900/30 hx:dark:text-amber-200"&gt;
&lt;div class="hx:ltr:pl-3 hx:ltr:pr-2 hx:rtl:pr-3 hx:rtl:pl-2"&gt;&lt;svg height=1.2em class="hx:inline-block hx:align-middle" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;족보를 모르고 가이드만 따라 하면, 가이드를 만든 시점의 가정(예: &amp;ldquo;PSP가 아직 존재하던 시절&amp;rdquo;)이 깨졌을 때 왜 안 되는지조차 모릅니다.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;이 사이트가 이 패턴을 어떻게 반영했는가&lt;span class="hx:absolute hx:-mt-20" id="이-사이트가-이-패턴을-어떻게-반영했는가"&gt;&lt;/span&gt;
&lt;a href="#%ec%9d%b4-%ec%82%ac%ec%9d%b4%ed%8a%b8%ea%b0%80-%ec%9d%b4-%ed%8c%a8%ed%84%b4%ec%9d%84-%ec%96%b4%eb%96%bb%ea%b2%8c-%eb%b0%98%ec%98%81%ed%96%88%eb%8a%94%ea%b0%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;사실 이 네 가지 패턴은 이 지식베이스의 글쓰기 규칙과 그대로 맞물립니다. &lt;a
href="https://k8s-km.metacog.co.kr/docs/"&gt;Docs&lt;/a&gt;의 모든 Hands-on 문서는 의도적으로 &amp;ldquo;현상 → 의심되는 원인(가설) → 확인할 로그/명령어 → 조치&amp;rdquo; 순서로 적혀 있습니다(①). 개념 설명에는 가능한 한 비유와 다이어그램을 곁들였습니다(②). 트러블슈팅 시나리오에는 Happy path만이 아니라 실패 상황을 함께 다뤘습니다(③). 그리고 보안·구성관리처럼 카테고리를 가를 때도 &amp;ldquo;왜 이 경계로 나눴는가&amp;quot;라는 근거를 남겨두려 했습니다(④).&lt;/p&gt;
&lt;p&gt;결국 실무 능력은 화려한 답이 아니라, &lt;strong&gt;무엇을 묻고 무엇을 먼저 확인하고 무엇을 미리 준비해 두는가&lt;/strong&gt;에서 드러납니다.&lt;/p&gt;</description></item><item><title>애플리케이션의 Stateless함을 보장하기 위해, 시스템 자체는 매우 Stateful하게 동작하는 플랫폼</title><link>https://k8s-km.metacog.co.kr/blog/stateless-app-stateful-platform/</link><pubDate>Wed, 17 Jun 2026 00:00:00 +0000</pubDate><guid>https://k8s-km.metacog.co.kr/blog/stateless-app-stateful-platform/</guid><description>
&lt;p&gt;&amp;ldquo;쿠버네티스는 Stateless한 플랫폼이다&amp;quot;라는 말을 들어본 적 있을 겁니다. 그런데 막상 들여다보면 쿠버네티스는 클러스터의 모든 상태를 한순간도 놓치지 않고 데이터베이스에 기록하고, 끊임없이 감시하고, 끊임없이 비교하는 시스템입니다. 모순처럼 들리지 않나요?&lt;/p&gt;
&lt;p&gt;결론부터 말하면 모순이 아닙니다. &lt;strong&gt;&amp;ldquo;애플리케이션&amp;rdquo; 관점과 &amp;ldquo;쿠버네티스 시스템&amp;rdquo; 관점을 구분해서 봐야&lt;/strong&gt; 풀리는 질문입니다.&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:rounded-lg hx:border hx:py-2 hx:ltr:pr-4 hx:rtl:pl-4 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200"&gt;
&lt;div class="hx:ltr:pl-3 hx:ltr:pr-2 hx:rtl:pr-3 hx:rtl:pl-2"&gt;&lt;svg height=1.2em class="hx:inline-block hx:align-middle" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;&lt;strong&gt;애플리케이션(파드)은 Stateless하게&lt;/strong&gt;, &lt;strong&gt;쿠버네티스 제어 평면(Control Plane)은 극도로 Stateful하게&lt;/strong&gt; — 이 두 문장은 서로 다른 층(layer)을 가리키고 있어서 동시에 참입니다.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;비유로 먼저 감을 잡아보기: 콜센터&lt;span class="hx:absolute hx:-mt-20" id="비유로-먼저-감을-잡아보기-콜센터"&gt;&lt;/span&gt;
&lt;a href="#%eb%b9%84%ec%9c%a0%eb%a1%9c-%eb%a8%bc%ec%a0%80-%ea%b0%90%ec%9d%84-%ec%9e%a1%ec%95%84%eb%b3%b4%ea%b8%b0-%ec%bd%9c%ec%84%bc%ed%84%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;고객센터에 전화를 건다고 생각해 봅시다. 받는 상담원은 매번 다른 사람일 수 있습니다. 어제 통화한 상담원이 오늘 휴가일 수도, 아예 퇴사했을 수도 있죠. 그런데도 고객은 &amp;ldquo;저번에 말씀드린 그 문제요&amp;quot;라고만 해도 대화가 이어집니다. 왜일까요?&lt;/p&gt;
&lt;p&gt;상담원 개인이 고객의 사연을 기억하고 있는 게 아니라, &lt;strong&gt;CRM 시스템에 고객의 이력이 전부 저장&lt;/strong&gt;되어 있고 상담원은 그걸 조회만 하기 때문입니다. 상담원(사람)은 언제든 교체 가능한 &amp;ldquo;Stateless&amp;quot;한 존재지만, CRM(시스템)은 모든 이력을 한 글자도 잃지 않고 보관하는 &amp;ldquo;Stateful&amp;quot;한 존재입니다.&lt;/p&gt;
&lt;p&gt;쿠버네티스의 파드와 control plane의 관계가 정확히 이 구조입니다.&lt;/p&gt;
&lt;h2&gt;1. 애플리케이션은 Stateless — 왜 그래야 하는가&lt;span class="hx:absolute hx:-mt-20" id="1-애플리케이션은-stateless--왜-그래야-하는가"&gt;&lt;/span&gt;
&lt;a href="#1-%ec%95%a0%ed%94%8c%eb%a6%ac%ec%bc%80%ec%9d%b4%ec%85%98%ec%9d%80-stateless--%ec%99%9c-%ea%b7%b8%eb%9e%98%ec%95%bc-%ed%95%98%eb%8a%94%ea%b0%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;쿠버네티스 위에서 도는 컨테이너(파드)는 &lt;strong&gt;언제 죽고 언제 다시 떠도 서비스에 지장이 없도록&lt;/strong&gt; 설계하는 것이 원칙입니다. 파드는 다음과 같은 이유로 수시로 사라지고 다시 만들어집니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;배포 중 새 버전으로 교체될 때 (롤링 업데이트)&lt;/li&gt;
&lt;li&gt;노드 장애로 다른 노드로 옮겨갈 때&lt;/li&gt;
&lt;li&gt;오토스케일러가 트래픽에 맞춰 개수를 늘리거나 줄일 때&lt;/li&gt;
&lt;li&gt;단순히 메모리를 너무 많이 써서 OOMKilled로 재시작될 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이때 파드 &lt;strong&gt;안&lt;/strong&gt;에 중요한 데이터를 들고 있었다면 그 데이터는 파드가 사라지는 순간 함께 증발합니다.&lt;/p&gt;
&lt;div class="hextra-scrollbar hx:overflow-x-auto hx:overflow-y-hidden hx:overscroll-x-contain"&gt;
&lt;div class="hx:mt-4 hx:flex hx:w-max hx:min-w-full hx:border-b hx:border-gray-200 hx:pb-px hx:dark:border-neutral-800" role="tablist"&gt;&lt;button class="hextra-tabs-toggle hx:cursor-pointer hx:data-[state=selected]:border-primary-500 hx:data-[state=selected]:text-primary-600 hx:data-[state=selected]:dark:border-primary-500 hx:data-[state=selected]:dark:text-primary-600 hx:mr-2 hx:rounded-t hx:p-2 hx:font-medium hx:leading-5 hx:transition-colors hx:-mb-0.5 hx:select-none hx:border-b-2 hx:border-transparent hx:text-gray-600 hx:hover:border-gray-200 hx:hover:text-black hx:dark:text-gray-200 hx:dark:hover:border-neutral-800 hx:dark:hover:text-white hx:hextra-focus-visible-inset" id="tabs-tab-tabs-01-0" role="tab" type="button" aria-controls="tabs-panel-tabs-01-0" aria-selected="true" tabindex="0" data-state="selected"&gt;&lt;span class="hx:inline-flex hx:items-center hx:gap-1.5"&gt;&lt;span&gt;안티패턴&lt;/span&gt;&lt;/span&gt;&lt;/button&gt;&lt;button class="hextra-tabs-toggle hx:cursor-pointer hx:data-[state=selected]:border-primary-500 hx:data-[state=selected]:text-primary-600 hx:data-[state=selected]:dark:border-primary-500 hx:data-[state=selected]:dark:text-primary-600 hx:mr-2 hx:rounded-t hx:p-2 hx:font-medium hx:leading-5 hx:transition-colors hx:-mb-0.5 hx:select-none hx:border-b-2 hx:border-transparent hx:text-gray-600 hx:hover:border-gray-200 hx:hover:text-black hx:dark:text-gray-200 hx:dark:hover:border-neutral-800 hx:dark:hover:text-white hx:hextra-focus-visible-inset" id="tabs-tab-tabs-01-1" role="tab" type="button" aria-controls="tabs-panel-tabs-01-1" aria-selected="false" tabindex="-1"&gt;&lt;span class="hx:inline-flex hx:items-center hx:gap-1.5"&gt;&lt;span&gt;권장 패턴&lt;/span&gt;&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;div class="hextra-tabs-panel hx:rounded-sm hx:pt-6 hx:hidden hx:data-[state=selected]:block" id="tabs-panel-tabs-01-0" role="tabpanel" aria-labelledby="tabs-tab-tabs-01-0" aria-hidden="false" tabindex="0" data-state="selected"&gt;&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 파드 로컬 디스크에 업로드 파일을 저장 — 파드가 재시작되면 파일이 사라진다&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;upload-service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;app&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;my-upload-app:1.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumeMounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;local-storage&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mountPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/data/uploads&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;local-storage&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;emptyDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{}&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 파드와 생명주기를 같이 하는 임시 저장소&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;이 구성에서 사용자가 업로드한 파일은 &lt;code&gt;emptyDir&lt;/code&gt;에 저장됩니다. &lt;code&gt;emptyDir&lt;/code&gt;은 파드가 삭제되는 순간 함께 삭제되는 휘발성 볼륨이라, 배포 한 번만 해도 사용자 파일이 전부 사라집니다.&lt;/p&gt;&lt;/div&gt;&lt;div class="hextra-tabs-panel hx:rounded-sm hx:pt-6 hx:hidden hx:data-[state=selected]:block" id="tabs-panel-tabs-01-1" role="tabpanel" aria-labelledby="tabs-tab-tabs-01-1" aria-hidden="true"&gt;&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 상태는 파드 바깥(외부 스토리지/DB)으로 분리한다&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;apps/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deployment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;upload-service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;app&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;my-upload-app:1.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;STORAGE_BACKEND&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;s3://my-bucket/uploads&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 상태는 S3(또는 PVC)로&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;SESSION_STORE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;redis://redis-svc:6379&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 세션도 외부 Redis로&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;파드가 어떤 이유로든 사라지고 다시 뜨더라도, 실제 데이터는 S3나 Redis처럼 파드 생명주기와 무관한 곳에 남아 있습니다. 그래서 파드 3개 중 1개가 갑자기 죽어도 사용자는 아무것도 느끼지 못합니다.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;이 원칙을 지키면 얻는 것은 명확합니다: 파드를 &lt;strong&gt;아무 때나, 아무 노드에서나, 아무 개수로나&lt;/strong&gt; 다룰 수 있는 자유입니다. 이게 바로 우리가 &amp;ldquo;쿠버네티스 애플리케이션은 Stateless해야 한다&amp;quot;고 말할 때의 의미입니다.&lt;/p&gt;
&lt;p&gt;(완전히 상태를 가질 수밖에 없는 데이터베이스 같은 워크로드는 StatefulSet으로 별도 취급합니다. 이건 &lt;a
href="https://k8s-km.metacog.co.kr/docs/workloads-scheduling/"&gt;워크로드 &amp;amp; 스케줄링&lt;/a&gt; 카테고리에서 다룹니다.)&lt;/p&gt;
&lt;h2&gt;2. 쿠버네티스 시스템은 극도로 Stateful — Continuous State&lt;span class="hx:absolute hx:-mt-20" id="2-쿠버네티스-시스템은-극도로-stateful--continuous-state"&gt;&lt;/span&gt;
&lt;a href="#2-%ec%bf%a0%eb%b2%84%eb%84%a4%ed%8b%b0%ec%8a%a4-%ec%8b%9c%ec%8a%a4%ed%85%9c%ec%9d%80-%ea%b7%b9%eb%8f%84%eb%a1%9c-stateful--continuous-state" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;이제 시선을 애플리케이션에서 &lt;strong&gt;쿠버네티스 자신&lt;/strong&gt;으로 옮겨봅시다. 누가 &amp;ldquo;지금 클러스터에 파드가 몇 개 떠 있어야 하는지&amp;rdquo;, &amp;ldquo;어떤 노드에 뭐가 배치되어 있는지&amp;rdquo;, &amp;ldquo;어떤 Service가 어떤 파드로 트래픽을 흘려야 하는지&amp;quot;를 기억하고 있을까요?&lt;/p&gt;
&lt;p&gt;쿠버네티스 자신입니다. 그리고 이 기억은 한순간도 끊기면 안 됩니다.&lt;/p&gt;
&lt;h3&gt;자동 온도조절기처럼 동작한다&lt;span class="hx:absolute hx:-mt-20" id="자동-온도조절기처럼-동작한다"&gt;&lt;/span&gt;
&lt;a href="#%ec%9e%90%eb%8f%99-%ec%98%a8%eb%8f%84%ec%a1%b0%ec%a0%88%ea%b8%b0%ec%b2%98%eb%9f%bc-%eb%8f%99%ec%9e%91%ed%95%9c%eb%8b%a4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;집에 있는 자동 온도조절기(thermostat)를 떠올려 보세요. 목표 온도를 24도로 맞춰두면, 온도조절기는:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;지금 실내 온도를 계속 측정한다 (Actual State 관찰)&lt;/li&gt;
&lt;li&gt;목표 온도 24도와 비교한다 (Desired State와 대조)&lt;/li&gt;
&lt;li&gt;차이가 있으면 난방/냉방을 켜거나 끈다 (조정, Reconcile)&lt;/li&gt;
&lt;li&gt;1번으로 돌아가 무한히 반복한다&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;쿠버네티스의 컨트롤러도 똑같습니다. &amp;ldquo;Deployment에 replicas: 3이라고 써놨다&amp;quot;는 게 desired state이고, &amp;ldquo;지금 실제로 떠 있는 파드 개수&amp;quot;가 actual state입니다. 컨트롤러는 이 둘을 끊임없이 비교하면서 차이가 생기면 즉시 메꿉니다.&lt;/p&gt;
&lt;div role="img" aria-label="다이어그램"&gt;
&lt;pre class="mermaid hx:mt-6"&gt;
flowchart LR
A[&amp;#34;사용자가 선언&amp;lt;br/&amp;gt;(replicas: 3)&amp;#34;] --&amp;gt; B[API Server]
B --&amp;gt; C[(&amp;#34;etcd&amp;lt;br/&amp;gt;Desired State 저장&amp;#34;)]
D[Controller] -- Watch --&amp;gt; B
D -- &amp;#34;현재 상태 확인&amp;#34; --&amp;gt; E[&amp;#34;클러스터 실제 상태&amp;lt;br/&amp;gt;(지금 파드 2개)&amp;#34;]
D -- &amp;#34;차이 발견 → 조정&amp;#34; --&amp;gt; F[&amp;#34;파드 1개 추가 생성&amp;#34;]
F --&amp;gt; E
&lt;/pre&gt;
&lt;/div&gt;&lt;h3&gt;etcd: 클러스터 전체의 기억을 담는 단 하나의 장부&lt;span class="hx:absolute hx:-mt-20" id="etcd-클러스터-전체의-기억을-담는-단-하나의-장부"&gt;&lt;/span&gt;
&lt;a href="#etcd-%ed%81%b4%eb%9f%ac%ec%8a%a4%ed%84%b0-%ec%a0%84%ec%b2%b4%ec%9d%98-%ea%b8%b0%ec%96%b5%ec%9d%84-%eb%8b%b4%eb%8a%94-%eb%8b%a8-%ed%95%98%eb%82%98%ec%9d%98-%ec%9e%a5%eb%b6%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;이 모든 desired/actual state는 결국 &lt;strong&gt;etcd&lt;/strong&gt;라는 분산 키-값 저장소에 기록됩니다. 클러스터에 노드가 몇 대 있는지, 파드가 어디 떠 있는지, Secret과 ConfigMap 내용이 무엇인지, RBAC 권한이 어떻게 설정돼 있는지 — 이 모든 게 etcd 안에 있습니다.&lt;/p&gt;
&lt;p&gt;만약 노드 하나가 갑자기 꺼진다면, 쿠버네티스는 어떻게 &amp;ldquo;그 노드 위에 떠 있던 파드 5개를 다른 노드에서 다시 살려야 한다&amp;quot;는 걸 알 수 있을까요? etcd에 &amp;ldquo;그 5개 파드가 desired state로 존재해야 한다&amp;quot;는 기록이 남아있기 때문입니다. 시스템이 끊임없이 그 기록을 들고 비교했기 때문에 장애가 나도 복구할 수 있는 것입니다.&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:rounded-lg hx:border hx:py-2 hx:ltr:pr-4 hx:rtl:pl-4 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-amber-200 hx:bg-amber-100 hx:text-amber-900 hx:dark:border-amber-200/30 hx:dark:bg-amber-900/30 hx:dark:text-amber-200"&gt;
&lt;div class="hx:ltr:pl-3 hx:ltr:pr-2 hx:rtl:pr-3 hx:rtl:pl-2"&gt;&lt;svg height=1.2em class="hx:inline-block hx:align-middle" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;그래서 실무에서는 &lt;strong&gt;etcd 백업이 곧 클러스터 전체의 생명줄&lt;/strong&gt;입니다. etcd 데이터를 잃으면 &amp;ldquo;지금 무엇이 떠 있어야 하는가&amp;quot;에 대한 기억 자체가 사라지므로, 클러스터를 다시 살려도 무엇을 복구해야 할지 알 수 없습니다.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;두 층을 표로 정리하면&lt;span class="hx:absolute hx:-mt-20" id="두-층을-표로-정리하면"&gt;&lt;/span&gt;
&lt;a href="#%eb%91%90-%ec%b8%b5%ec%9d%84-%ed%91%9c%eb%a1%9c-%ec%a0%95%eb%a6%ac%ed%95%98%eb%a9%b4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;애플리케이션 (파드)&lt;/th&gt;
&lt;th&gt;쿠버네티스 제어 평면&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;권장되는 성질&lt;/td&gt;
&lt;td&gt;Stateless&lt;/td&gt;
&lt;td&gt;(의도적으로) 매우 Stateful&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;상태를 어디 두는가&lt;/td&gt;
&lt;td&gt;파드 바깥 (S3, RDS, Redis, PV 등)&lt;/td&gt;
&lt;td&gt;etcd (분산 저장소)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;죽었을 때&lt;/td&gt;
&lt;td&gt;다시 띄우면 그만 — 데이터 손실 없음&lt;/td&gt;
&lt;td&gt;백업이 없으면 클러스터 전체가 &amp;ldquo;기억상실&amp;rdquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비유&lt;/td&gt;
&lt;td&gt;콜센터 상담원&lt;/td&gt;
&lt;td&gt;CRM 시스템 / 온도조절기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;목적&lt;/td&gt;
&lt;td&gt;자유롭게 늘리고 줄이고 교체하기 위해&lt;/td&gt;
&lt;td&gt;장애가 나도 원래 상태로 스스로 복구하기 위해&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;그래서 이걸 알면 뭐가 달라지는가&lt;span class="hx:absolute hx:-mt-20" id="그래서-이걸-알면-뭐가-달라지는가"&gt;&lt;/span&gt;
&lt;a href="#%ea%b7%b8%eb%9e%98%ec%84%9c-%ec%9d%b4%ea%b1%b8-%ec%95%8c%eb%a9%b4-%eb%ad%90%ea%b0%80-%eb%8b%ac%eb%9d%bc%ec%a7%80%eb%8a%94%ea%b0%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;이 둘을 구분하지 못하면 실무에서 흔히 이런 실수를 합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;파드 안에 캐시나 업로드 파일을 저장해놓고, 배포할 때마다 데이터가 날아가는 이유를 못 찾는다&lt;/li&gt;
&lt;li&gt;etcd 백업 전략 없이 운영하다가, control plane 노드 장애 한 번에 클러스터 전체 상태를 잃는다&lt;/li&gt;
&lt;li&gt;&amp;ldquo;쿠버네티스는 알아서 복구해주는 시스템&amp;quot;이라고만 믿고, 그 복구가 사실은 &lt;strong&gt;끊임없이 desired/actual state를 비교하는 reconciliation loop&lt;/strong&gt; 덕분이라는 걸 모른 채 트러블슈팅에서 길을 잃는다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;정리하면: &lt;strong&gt;애플리케이션을 Stateless하게 짤 수 있는 자유는, 그 밑을 받치고 있는 쿠버네티스 시스템이 클러스터의 모든 상태를 한순간도 놓치지 않고 Stateful하게 기억하고 있기 때문에 가능한 것&lt;/strong&gt;입니다. 이 reconciliation loop의 구조를 더 깊이 보고 싶다면 &lt;a
href="https://k8s-km.metacog.co.kr/docs/foundation/"&gt;Foundation 카테고리&lt;/a&gt;에서 control plane/data plane 구성과 선언적 API 모델을 다룹니다.&lt;/p&gt;</description></item><item><title>이 블로그를 운영하는 방식</title><link>https://k8s-km.metacog.co.kr/blog/welcome/</link><pubDate>Wed, 17 Jun 2026 00:00:00 +0000</pubDate><guid>https://k8s-km.metacog.co.kr/blog/welcome/</guid><description>
&lt;p&gt;이 블로그는 &lt;a
href="https://k8s-km.metacog.co.kr/docs/"&gt;Docs&lt;/a&gt;의 보완재입니다. Docs는 카테고리별로 정리된 &lt;strong&gt;개념과 절차&lt;/strong&gt;(MECE 구조)를 다루고, 이 블로그는 &lt;strong&gt;시점이 있는 기록&lt;/strong&gt;을 다룹니다.&lt;/p&gt;
&lt;h2&gt;어떤 글을 올리는가&lt;span class="hx:absolute hx:-mt-20" id="어떤-글을-올리는가"&gt;&lt;/span&gt;
&lt;a href="#%ec%96%b4%eb%96%a4-%ea%b8%80%ec%9d%84-%ec%98%ac%eb%a6%ac%eb%8a%94%ea%b0%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;포스트모템&lt;/strong&gt;: 실제 장애의 현상 → 원인 가설 → 검증 → 조치 → 재발 방지 기록&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;버전 업그레이드 노트&lt;/strong&gt;: 특정 버전에서 바뀐 동작, 마이그레이션 시 주의할 점&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;의사결정 기록(ADR류)&lt;/strong&gt;: 왜 A 대신 B를 선택했는지, 트레이드오프와 당시의 제약&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Docs와의 관계&lt;span class="hx:absolute hx:-mt-20" id="docs와의-관계"&gt;&lt;/span&gt;
&lt;a href="#docs%ec%99%80%ec%9d%98-%ea%b4%80%ea%b3%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;같은 사례가 일반화될 수 있다고 판단되면, 블로그 글의 결론을 해당 카테고리의 &lt;code&gt;concept.md&lt;/code&gt; 또는 &lt;code&gt;hands-on.md&lt;/code&gt;에 반영해 Docs 쪽에도 흡수시킵니다. 블로그는 1차 기록, Docs는 정제된 지식이라는 역할 분리입니다.&lt;/p&gt;</description></item><item><title>쿠버네티스의 Reconciliation Loop와 AI 에이전트의 '루프 엔지니어링'은 왜 닮았을까</title><link>https://k8s-km.metacog.co.kr/blog/reconciliation-loop-vs-ai-agent-loop/</link><pubDate>Wed, 17 Jun 2026 00:00:00 +0000</pubDate><guid>https://k8s-km.metacog.co.kr/blog/reconciliation-loop-vs-ai-agent-loop/</guid><description>
&lt;p&gt;내비게이션 앱을 켜고 운전하다가 갈림길에서 엉뚱한 차선으로 빠졌다고 해봅시다. 내비게이션은 화내지 않습니다. 그냥 1초 만에 &amp;ldquo;다시 계산 중&amp;hellip;&amp;ldquo;이라는 문구를 띄우고 새 경로를 보여줍니다. 목적지(목표)는 그대로인데, 현재 위치(상태)가 바뀌었으니 거기에 맞춰 다음 행동만 다시 정하는 거죠. 이 단순한 동작 하나가, 쿠버네티스 컨트롤러와 요즘 화제인 AI 에이전트 루프를 관통하는 똑같은 원리입니다.&lt;/p&gt;
&lt;h2&gt;같은 춤을 추는 두 시스템&lt;span class="hx:absolute hx:-mt-20" id="같은-춤을-추는-두-시스템"&gt;&lt;/span&gt;
&lt;a href="#%ea%b0%99%ec%9d%80-%ec%b6%a4%ec%9d%84-%ec%b6%94%eb%8a%94-%eb%91%90-%ec%8b%9c%ec%8a%a4%ed%85%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;내비게이션이 하는 일을 3단계로 쪼개 보면 이렇습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;지금 어디 있지?&lt;/strong&gt; (현재 위치를 GPS로 관측)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;목적지랑 비교하면 경로에서 벗어났나?&lt;/strong&gt; (차이 계산)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;벗어났으면 새 경로를 안내한다&lt;/strong&gt; (행동)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;그리고 이 셋을 도착할 때까지 영원히 반복합니다. 이게 바로 **제어 이론(Control Theory)**의 핵심 구조이고, 쿠버네티스와 AI 에이전트는 이 구조를 각각 다른 영역에 그대로 적용한 것입니다.&lt;/p&gt;
&lt;div role="img" aria-label="다이어그램"&gt;
&lt;pre class="mermaid hx:mt-6"&gt;
flowchart LR
subgraph K[&amp;#34;쿠버네티스 컨트롤러&amp;#34;]
direction LR
K1[&amp;#34;Actual State 관측&amp;lt;br/&amp;gt;(지금 Pod 2개)&amp;#34;] --&amp;gt; K2[&amp;#34;Desired State와 비교&amp;lt;br/&amp;gt;(3개여야 함)&amp;#34;] --&amp;gt; K3[&amp;#34;API 호출&amp;lt;br/&amp;gt;(Pod 1개 추가)&amp;#34;] --&amp;gt; K1
end
subgraph A[&amp;#34;AI 에이전트 루프 (ReAct)&amp;#34;]
direction LR
A1[&amp;#34;Observation&amp;lt;br/&amp;gt;(지금 상황 확인)&amp;#34;] --&amp;gt; A2[&amp;#34;Reasoning&amp;lt;br/&amp;gt;(목표와 비교, 다음 수 고민)&amp;#34;] --&amp;gt; A3[&amp;#34;Action&amp;lt;br/&amp;gt;(도구 호출/행동)&amp;#34;] --&amp;gt; A1
end
&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;내비게이션의 &amp;ldquo;관측 → 비교 → 재계산&amp;rdquo; 루프를, 쿠버네티스는 &lt;strong&gt;인프라 상태&lt;/strong&gt;에 적용했고, AI 에이전트는 **불확실한 과업(Task)**에 적용했습니다. 풀어야 할 문제가 다를 뿐, 루프의 골격은 동일합니다.&lt;/p&gt;
&lt;h2&gt;쿠버네티스가 먼저 풀어낸 두 가지 어려운 문제&lt;span class="hx:absolute hx:-mt-20" id="쿠버네티스가-먼저-풀어낸-두-가지-어려운-문제"&gt;&lt;/span&gt;
&lt;a href="#%ec%bf%a0%eb%b2%84%eb%84%a4%ed%8b%b0%ec%8a%a4%ea%b0%80-%eb%a8%bc%ec%a0%80-%ed%92%80%ec%96%b4%eb%82%b8-%eb%91%90-%ea%b0%80%ec%a7%80-%ec%96%b4%eb%a0%a4%ec%9a%b4-%eb%ac%b8%ec%a0%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;쿠버네티스는 10년 가까이 이 루프를 인프라에서 검증해 왔습니다. 그 과정에서 풀어낸 두 가지 문제는 AI 에이전트가 지금 똑같이 마주하고 있는 숙제입니다.&lt;/p&gt;
&lt;h3&gt;1. 멱등성(Idempotency) — &amp;ldquo;같은 명령을 100번 내려도 결과가 같아야 한다&amp;rdquo;&lt;span class="hx:absolute hx:-mt-20" id="1-멱등성idempotency--같은-명령을-100번-내려도-결과가-같아야-한다"&gt;&lt;/span&gt;
&lt;a href="#1-%eb%a9%b1%eb%93%b1%ec%84%b1idempotency--%ea%b0%99%ec%9d%80-%eb%aa%85%eb%a0%b9%ec%9d%84-100%eb%b2%88-%eb%82%b4%eb%a0%a4%eb%8f%84-%ea%b2%b0%ea%b3%bc%ea%b0%80-%ea%b0%99%ec%95%84%ec%95%bc-%ed%95%9c%eb%8b%a4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;내비게이션을 같은 위치에서 &amp;ldquo;다시 계산하기&amp;quot;를 100번 눌러도 매번 같은 경로가 나와야 신뢰할 수 있습니다. 만약 누를 때마다 다른 경로가 튀어나온다면 그 내비게이션은 쓸 수 없겠죠.&lt;/p&gt;
&lt;div class="hextra-scrollbar hx:overflow-x-auto hx:overflow-y-hidden hx:overscroll-x-contain"&gt;
&lt;div class="hx:mt-4 hx:flex hx:w-max hx:min-w-full hx:border-b hx:border-gray-200 hx:pb-px hx:dark:border-neutral-800" role="tablist"&gt;&lt;button class="hextra-tabs-toggle hx:cursor-pointer hx:data-[state=selected]:border-primary-500 hx:data-[state=selected]:text-primary-600 hx:data-[state=selected]:dark:border-primary-500 hx:data-[state=selected]:dark:text-primary-600 hx:mr-2 hx:rounded-t hx:p-2 hx:font-medium hx:leading-5 hx:transition-colors hx:-mb-0.5 hx:select-none hx:border-b-2 hx:border-transparent hx:text-gray-600 hx:hover:border-gray-200 hx:hover:text-black hx:dark:text-gray-200 hx:dark:hover:border-neutral-800 hx:dark:hover:text-white hx:hextra-focus-visible-inset" id="tabs-tab-tabs-00-0" role="tab" type="button" aria-controls="tabs-panel-tabs-00-0" aria-selected="true" tabindex="0" data-state="selected"&gt;&lt;span class="hx:inline-flex hx:items-center hx:gap-1.5"&gt;&lt;span&gt;Kubernetes&lt;/span&gt;&lt;/span&gt;&lt;/button&gt;&lt;button class="hextra-tabs-toggle hx:cursor-pointer hx:data-[state=selected]:border-primary-500 hx:data-[state=selected]:text-primary-600 hx:data-[state=selected]:dark:border-primary-500 hx:data-[state=selected]:dark:text-primary-600 hx:mr-2 hx:rounded-t hx:p-2 hx:font-medium hx:leading-5 hx:transition-colors hx:-mb-0.5 hx:select-none hx:border-b-2 hx:border-transparent hx:text-gray-600 hx:hover:border-gray-200 hx:hover:text-black hx:dark:text-gray-200 hx:dark:hover:border-neutral-800 hx:dark:hover:text-white hx:hextra-focus-visible-inset" id="tabs-tab-tabs-00-1" role="tab" type="button" aria-controls="tabs-panel-tabs-00-1" aria-selected="false" tabindex="-1"&gt;&lt;span class="hx:inline-flex hx:items-center hx:gap-1.5"&gt;&lt;span&gt;AI 에이전트&lt;/span&gt;&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;div class="hextra-tabs-panel hx:rounded-sm hx:pt-6 hx:hidden hx:data-[state=selected]:block" id="tabs-panel-tabs-00-0" role="tabpanel" aria-labelledby="tabs-tab-tabs-00-0" aria-hidden="false" tabindex="0" data-state="selected"&gt;&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 같은 매니페스트를 몇 번을 적용해도 결과는 동일하다&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -f deployment.yaml &lt;span class="c1"&gt;# replicas: 3, 지금 3개 → 아무 일도 안 함&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -f deployment.yaml &lt;span class="c1"&gt;# 또 적용 → 여전히 아무 일도 안 함&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -f deployment.yaml &lt;span class="c1"&gt;# 100번째 적용 → 여전히 Pod는 3개&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;컨트롤러는 &amp;ldquo;지금 상태가 이미 desired state와 같다&amp;quot;는 걸 확인하면 그냥 아무것도 하지 않습니다. 같은 동작을 반복해도 시스템이 오염되지 않는 것 — 이게 멱등성입니다.&lt;/p&gt;&lt;/div&gt;&lt;div class="hextra-tabs-panel hx:rounded-sm hx:pt-6 hx:hidden hx:data-[state=selected]:block" id="tabs-panel-tabs-00-1" role="tabpanel" aria-labelledby="tabs-tab-tabs-00-1" aria-hidden="true"&gt;&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;에이전트: &amp;#34;결제 API를 호출해서 주문을 완료시켜라&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1차 호출 → 타임아웃 (성공했는지 실패했는지 불명확)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;에이전트 판단: &amp;#34;응답을 못 받았으니 재시도하자&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2차 호출 → 만약 1차가 사실 성공했었다면, 중복 결제가 발생한다!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;AI 에이전트 루프에서 재시도(Retry)는 필수입니다. 그런데 재시도했을 때 시스템이 오염되지 않으려면(중복 결제, 중복 메일 발송 방지) &lt;strong&gt;Idempotency Key&lt;/strong&gt; 같은 장치로 &amp;ldquo;같은 요청은 결과가 같아야 한다&amp;quot;는 쿠버네티스식 원칙을 그대로 가져와야 합니다.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3&gt;2. 상태 추상화 — &amp;ldquo;거대한 현실을 다루기 쉬운 단위로 쪼갠다&amp;rdquo;&lt;span class="hx:absolute hx:-mt-20" id="2-상태-추상화--거대한-현실을-다루기-쉬운-단위로-쪼갠다"&gt;&lt;/span&gt;
&lt;a href="#2-%ec%83%81%ed%83%9c-%ec%b6%94%ec%83%81%ed%99%94--%ea%b1%b0%eb%8c%80%ed%95%9c-%ed%98%84%ec%8b%a4%ec%9d%84-%eb%8b%a4%eb%a3%a8%ea%b8%b0-%ec%89%ac%ec%9a%b4-%eb%8b%a8%ec%9c%84%eb%a1%9c-%ec%aa%bc%ea%b0%a0%eb%8b%a4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;쿠버네티스는 클러스터의 수많은 노드, 파드, 네트워크 정보를 통째로 들고 비교하지 않습니다. &lt;code&gt;etcd&lt;/code&gt;에 정리된 객체 단위(Pod, Deployment, Service&amp;hellip;)로 추상화해서 루프가 &amp;ldquo;비교하기 쉬운 모양&amp;quot;으로 만들어 둡니다.&lt;/p&gt;
&lt;p&gt;AI 에이전트도 똑같은 고민을 합니다. LLM에게 작업 전체 history를 매번 통째로 던지면 비싸고 느리고 헷갈립니다. 그래서 에이전트 프레임워크들은 지금까지의 진행 상황을 **체크포인트(Checkpoint)**나 압축된 상태 단위로 정리해서, 매 루프마다 &amp;ldquo;지금까지 뭘 했고 다음에 뭘 해야 하는지&amp;quot;를 가볍게 비교할 수 있게 만듭니다. 내비게이션이 &amp;ldquo;지금 위치&amp;quot;만 알면 되지 출발부터 지금까지의 모든 GPS 좌표를 다 들고 있을 필요가 없는 것과 같습니다.&lt;/p&gt;
&lt;h2&gt;왜 지금에서야 &amp;ldquo;루프 엔지니어링&amp;quot;이 핫해졌나&lt;span class="hx:absolute hx:-mt-20" id="왜-지금에서야-루프-엔지니어링이-핫해졌나"&gt;&lt;/span&gt;
&lt;a href="#%ec%99%9c-%ec%a7%80%ea%b8%88%ec%97%90%ec%84%9c%ec%95%bc-%eb%a3%a8%ed%94%84-%ec%97%94%ec%a7%80%eb%8b%88%ec%96%b4%eb%a7%81%ec%9d%b4-%ed%95%ab%ed%95%b4%ec%a1%8c%eb%82%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;쿠버네티스가 다루는 목표는 아주 구체적입니다. &lt;em&gt;&amp;ldquo;Pod가 정확히 3개여야 한다&amp;rdquo;&lt;/em&gt; — 숫자로 딱 떨어지는 정량적 목표입니다. 반면 AI 에이전트가 받는 목표는 &lt;em&gt;&amp;ldquo;이 고객 문의를 해결하라&amp;rdquo;&lt;/em&gt;, *&amp;ldquo;이 버그를 고쳐라&amp;rdquo;*처럼 모호하고 추상적입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;친구에게 &amp;ldquo;삼겹살집 가서 2번 테이블로 가 있어&amp;quot;라고 부탁하는 것과, &amp;ldquo;아무데나 맛있는 데서 저녁 해결하고 와&amp;quot;라고 부탁하는 것의 차이를 생각해 보세요. 전자는 목적지가 명확해서 길만 안내하면 끝입니다(쿠버네티스). 후자는 친구가 스스로 &amp;ldquo;맛있다&amp;quot;의 기준을 판단하고, 가게를 고르고, 줄이 길면 다른 곳으로 바꾸는 &lt;strong&gt;추론&lt;/strong&gt;까지 해야 합니다(AI 에이전트).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;그래서 AI 에이전트의 루프 엔지니어링은 결국 이런 질문으로 좁혀집니다: &lt;em&gt;&amp;ldquo;LLM의 모호하고 가끔 헛소리(환각)를 하는 추론 결과를, 쿠버네티스 컨트롤러처럼 안정적이고 예측 가능하게(Deterministic) 만들 수 있을까?&amp;rdquo;&lt;/em&gt; 인프라의 자동화를 위해 검증된 루프 철학을, 훨씬 불확실한 영역인 지능의 자동화에 이식하려는 시도인 셈입니다.&lt;/p&gt;
&lt;div class="hx:overflow-x-auto hx:mt-6 hx:flex hx:rounded-lg hx:border hx:py-2 hx:ltr:pr-4 hx:rtl:pl-4 hx:contrast-more:border-current hx:contrast-more:dark:border-current hx:border-blue-200 hx:bg-blue-100 hx:text-blue-900 hx:dark:border-blue-200/30 hx:dark:bg-blue-900/30 hx:dark:text-blue-200"&gt;
&lt;div class="hx:ltr:pl-3 hx:ltr:pr-2 hx:rtl:pr-3 hx:rtl:pl-2"&gt;&lt;svg height=1.2em class="hx:inline-block hx:align-middle" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;div class="hx:w-full hx:min-w-0 hx:leading-7"&gt;
&lt;div class="hx:mt-6 hx:leading-7 hx:first:mt-0"&gt;같은 이유로 AI 에이전트 프레임워크의 코드를 들여다보면 쿠버네티스 컨트롤러의 그림자가 자주 보입니다. 둘 다 &amp;ldquo;관측 → 비교 → 행동 → 반복&amp;quot;이라는 같은 문법을 쓰고 있기 때문입니다.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;의사코드로 나란히 놓고 보면&lt;span class="hx:absolute hx:-mt-20" id="의사코드로-나란히-놓고-보면"&gt;&lt;/span&gt;
&lt;a href="#%ec%9d%98%ec%82%ac%ec%bd%94%eb%93%9c%eb%a1%9c-%eb%82%98%eb%9e%80%ed%9e%88-%eb%86%93%ea%b3%a0-%eb%b3%b4%eb%a9%b4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 쿠버네티스 컨트롤러의 reconcile loop (단순화)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;desired&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_desired_state&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# 매니페스트에 선언된 목표&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;observe_actual_state&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# 클러스터의 현재 상태&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;desired&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;apply_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# API 호출로 차이를 메운다&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# AI 에이전트의 ReAct 루프 (단순화)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;goal_achieved&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;observation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;observe_environment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# 지금 상황 확인&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;thought&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;goal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 목표와 비교해 다음 수 고민&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;thought&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requires_action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;action_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;act&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thought&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 도구 호출/실제 행동&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;두 코드의 골격이 거의 겹칩니다. 차이는 &lt;code&gt;compare()&lt;/code&gt;가 정확한 숫자 비교냐, LLM의 추론이냐일 뿐입니다.&lt;/p&gt;
&lt;h2&gt;정리&lt;span class="hx:absolute hx:-mt-20" id="정리"&gt;&lt;/span&gt;
&lt;a href="#%ec%a0%95%eb%a6%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;AI 에이전트가 쿠버네티스를 &amp;ldquo;베껴 썼다&amp;quot;기보다는, &lt;strong&gt;신뢰할 수 없는 환경에서 신뢰할 수 있는 결과를 얻는 유일하게 검증된 방법이 &amp;lsquo;루프 + 상태 조정&amp;rsquo;이라는 것을, 소프트웨어 공학이 다시 한번 같은 결론으로 수렴&lt;/strong&gt;한 것이라고 보는 게 더 정확합니다.&lt;/p&gt;
&lt;p&gt;이미 &lt;a
href="https://k8s-km.metacog.co.kr/docs/foundation/"&gt;기반 모델(Foundation)&lt;/a&gt;에서 다룬 선언적 API와 reconciliation 패러다임이, 이제는 인프라를 넘어 AI 에이전트의 뇌를 관리하는 핵심 문법으로 확장되고 있습니다. 쿠버네티스의 컨트롤러 패턴을 직접 코드로 다뤄보고 싶다면 &lt;a
href="https://k8s-km.metacog.co.kr/docs/extensibility-automation/"&gt;확장성 &amp;amp; 자동화&lt;/a&gt; 카테고리의 Operator 패턴 실습이 좋은 다음 걸음이 될 것입니다.&lt;/p&gt;</description></item></channel></rss>