<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Infra_Student</title>
        <link>https://velog.io/</link>
        <description>Don’t get mad at the computer.</description>
        <lastBuildDate>Tue, 17 Mar 2026 07:16:24 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Infra_Student</title>
            <url>https://velog.velcdn.com/images/pizza_loves_me/profile/30f05e36-24f3-44ba-a2c4-305c77314597/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Infra_Student. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/pizza_loves_me" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Project] Multi-Tenant K8s Cluster on ARM64 - (15) Retrospect (Sprint 1)]]></title>
            <link>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-%ED%9A%8C%EA%B3%A01</link>
            <guid>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-%ED%9A%8C%EA%B3%A01</guid>
            <pubDate>Tue, 17 Mar 2026 07:16:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://velog.io/@pizza_loves_me/series/Homelab">Multi-Tenant K8s Cluster on ARM64</a> 의 첫 스프린트(?)였던 <code>프로젝트 구축 및 교내 개발 동아리(GREEDY) 프로젝트 지원</code>이 지난주 토요일에 종료되었습니다. 첫 스프린트 기간을 회고해보겠습니다.</p>
</blockquote>
<h2 id="프로젝트는-왜-시작했는가">프로젝트는 왜 시작했는가?</h2>
<p>작년 여름부터 인프라 분야에 많은 관심을 갖게 되었습니다. 이론 공부를 할수록 직접 만들어보고 싶다는 생각이 강해졌던 것 같습니다. 특히 CSP 쪽 직군에 관심이 많이 생겼고, &quot;나도 클라우드 서비스를 직접 구축하고 운영해보고 싶다&quot; 라는 생각이 많이 들었습니다. <del>미친듯이 치솟는 RAM 가격과 가난한 대학생이었기에</del> 여름부터 12월까지 알바를 하며 돈을 모았습니다.(홈랩 구축 비용, Kubestronaut(졸업 전 버킷리스트) 번들 비용, 맥북 교체 비용...)</p>
<p><a href="https://velog.io/@pizza_loves_me/series/Project-Build-Kubernetes-Cluster-Homelab-on-Bare-metal-Raspberry-Pi-3">📌 홈랩 구축기 - 블로그</a>
.
.
.</p>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/1fd4ba81-3e90-46f0-9739-2dd29a1925d9/image.png" alt=""></p>
<p>매우 많은 시행착오들을 겪으며 살아숨쉬는 제 <code>반려 홈랩</code> 이 탄생했습니다. 🥹</p>
<p>홈랩에서 쿠버네티스 설치 및 클러스터 구축을 완료하고 <em><strong>&quot;다음은 뭘 할까?&quot;</strong></em> 라는 고민을 했습니다. CSP 와 같이 사용자에게 서버를 제공하고 관리하는 역할을 해보고 싶었기에 주위에 서버가 필요한 개발자를 찾아보았습니다. </p>
<p>때마침 운영진(메인테이너)으로 활동 중인 교내 개발 동아리 그리디(GREEDY)에서 3기 멤버들이 곧 프로젝트 시작을 할 때였습니다. 저 또한 그리디에서 BE, FE 를 배웠고, 프로젝트 과정 중 느꼈던 아쉬웠던 점이 있었는지 생각해보았습니다. 가장 생각이 많이 났던 부분은 <code>학생이라 비용 부담 때문에 프리티어 계정만 사용이 가능하고, 이마저도 단일 서버로만 운영을 해야한다</code> 는 점이었습니다. </p>
<p>단일 서버로 프로젝트를 진행했기 때문에 미션 요구사항 중 <code>무중단 배포</code> 와 <code>모니터링 대시보드 구축</code> 모두 아쉬움이 많이 남았던 기억이 있습니다.(ec2  t2.micro 를 반으로 쪼개어 블루-그린 배포를 진행하고, 하나의 인스턴스에 모니터링 서버와 백엔드 서버를 함께 띄우는 구조적 모순까지,,,ㅜㅜ)</p>
<p>멤버들에게 평생 서버를 제공해줄 수는 없겠지만,, 적어도 동아리 프로젝트 기간(약 2달) 동안은 멤버들이 다중 서버 운영도 해보고, &#39;진짜&#39; 무중단 배포도 경험해보고, 아키텍처 고민도 해보는 시간이 되었으면 했습니다.</p>
<p>최종 데모데이 요구사항(무중단 배포 적용, 모니터링 대시보드 구축 등) 공개일이 많이 남지 않아서 빠르게 멀티-테넌트 프로젝트를 진행했습니다.</p>
<p><a href="https://velog.io/@pizza_loves_me/series/Homelab">📌 멀티 테넌트 제공 프로젝트 구축기 - 블로그</a>
<del>마감기간 직전에는 밤을 샌 날이 참 많았던 것 같습니다..허허</del>
다행히 (가벼운)테스트까지 끝마치고 멤버들에게 테넌트 서버 제공이 가능했고, 두 팀에게 각각 <code>테넌트 서버 5대씩 총 10대</code> 를 제공해주었습니다.</p>
<h2 id="멀티-테넌트-프로젝트-운영-과정">멀티-테넌트 프로젝트 &#39;운영&#39; 과정</h2>
<h3 id="1-테넌트-서버-1차-안내">1. 테넌트 서버 1차 안내</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/fc87511c-466d-428e-a30a-23b6d5216f7a/image.png" alt=""></p>
<blockquote>
<ul>
<li>동아리의 주된 소통 채널이 <code>디스코드</code> 이기 때문에 테넌트 서버 안내사항을 전달했습니다.</li>
</ul>
</blockquote>
<ul>
<li>서버 리소스 확보를 위해 Alpine 버전의 운영체제를 사용했는데, 많은 멤버들이 <code>apk</code> 사용이 처음일 것 같아서 추가 안내 또한 진행했습니다.</li>
</ul>
<h3 id="2-사용자-피드백-정리-및-대응-관리">2. 사용자 피드백 정리 및 대응 관리</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/4782ee5f-2b1c-4943-a960-f225ff7bb340/image.png" alt=""></p>
<blockquote>
<ul>
<li>제공한 테넌트 서버는 총 10대, 이를 사용하는 백엔드 멤버 총 6명.</li>
</ul>
</blockquote>
<ul>
<li>많은 인원이 적지 않은 수의 테넌트 서버를 사용하기 때문에 기술적 문제에 대한 피드백 또한 많을 것 같았습니다. 이를 관리하고 전체 사용자들에게 업데이트 안내를 하고자 정리하고 관리했습니다.</li>
</ul>
<h3 id="3-피드백---문제-파악-및-해결---업데이트-안내-및-패치-노트-작성">3. 피드백 -&gt; 문제 파악 및 해결 -&gt; 업데이트 안내 및 패치 노트 작성</h3>
<blockquote>
<h3 id="case-1">Case 1)</h3>
<p>⚠️ 사용자 피드백 중 테넌트 서버 재시작 시 기존 파일이 저장되지 않는다는 피드백이 있었습니다.</p>
</blockquote>
<h3 id="1️⃣-문제-파악-및-해결">1️⃣ 문제 파악 및 해결</h3>
<p><a href="https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-10-Refactor-Building-a-Truly-Stateful-Storage">📌 &#39;파드 재시작 시 데이터 휘발&#39; 에 대한 정리 - 블로그</a></p>
<blockquote>
</blockquote>
<h3 id="2️⃣-업데이트-안내">2️⃣ 업데이트 안내</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/cc5503a5-7881-4e9d-a57d-cbfaa887be1c/image.png" alt=""></p>
<blockquote>
</blockquote>
<h3 id="3️⃣-패치-노트-작성">3️⃣ 패치 노트 작성</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/017a3545-7c82-433b-a4b0-99379c74964a/image.png" alt=""></p>
<blockquote>
</blockquote>
<p><a href="https://github.com/developowl/multi-tenant-server/blob/main/Patch-Note/26.03.05_%EB%8D%B0%EC%9D%B4%ED%84%B0_%EC%98%81%EC%86%8D%EC%84%B1.md">📌 패치 노트 - Github</a></p>
<blockquote>
<h3 id="case-2">Case 2)</h3>
<p>⚠️ 또 다른 피드백 중 사전에 제공해준 포트를 사용하여 웹 접속이 안 된다는 피드백이 있었습니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/cdec60cb-e481-4526-88ac-fb0184d33ac3/image.png" alt=""></p>
<blockquote>
</blockquote>
<h3 id="1️⃣-문제-파악-및-해결-1">1️⃣ 문제 파악 및 해결</h3>
<p><a href="https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-13-Refactor-Add-web-external-port">📌 &#39;외부에서 브라우저 접속 시 응답 없음&#39; 에 대한 정리 - 블로그</a></p>
<blockquote>
</blockquote>
<h3 id="2️⃣-업데이트-안내-1">2️⃣ 업데이트 안내</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/3ab3ba1e-f162-4829-9a01-238710f22b25/image.png" alt=""></p>
<blockquote>
</blockquote>
<h3 id="3️⃣-패치-노트-작성-1">3️⃣ 패치 노트 작성</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/dd32dd53-0298-488c-96ed-91efebf04ce3/image.png" alt=""></p>
<blockquote>
</blockquote>
<p><a href="https://github.com/developowl/multi-tenant-server/blob/main/Patch-Note/26.03.10_%EC%9B%B9_%EC%A0%84%EC%9A%A9_%ED%8F%AC%ED%8A%B8_%EC%B6%94%EA%B0%80.md">📌 패치 노트 - Github</a></p>
<h2 id="🗣️-멀티-테넌트-서버-첫-사용자들의-피드백-구글폼">🗣️ 멀티 테넌트 서버 &#39;첫&#39; 사용자들의 피드백 (구글폼)</h2>
<blockquote>
<p>멀티 테넌트 서버를 제공한 당일부터 예상하지 못했던 문제점과 피드백이 많은 도움이 되었습니다. 또, 이번 첫 사용자들의 피드백을 참고하여 이후 <code>고도화 작업</code> 및 <code>오프라인 인프라 핸즈온 세미나</code> 를 진행하고자 했습니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/fdbeaffc-42ce-4ec8-9c68-1a27cf5867c9/image.png" alt=""><img src="https://velog.velcdn.com/images/pizza_loves_me/post/c333c11f-e839-47ed-828a-38d14586d2f1/image.png" alt=""></p>
<blockquote>
<p>감사하게도 여러 부원들이 피드백을 남겨주었고, 다음 고도화 방향과 세미나 주제를 선정하는데에 큰 도움이 되었습니다. 🙏🏻</p>
</blockquote>
<h2 id="🪧-next-step">🪧 Next Step!</h2>
<blockquote>
<p>일단 해보고 싶은 작업이 너무 많습니다ㅎㅎ. 그중에서 사용자 피드백을 우선 반영하고 고도화를 진행해볼까 합니다. </p>
</blockquote>
<h3 id="📝-list">📝 List</h3>
<ul>
<li>테넌트 생성 및 접속 시 <code>Public Key</code> 만을 사용하여 접속하도록 리팩토링 (비밀번호 입력이 번거롭다는 피드백 &amp; AWS - ec2의 경우 인스턴스 생성 시 퍼블릭 키 제공 참고)</li>
<li><code>스토리지 전용 테넌트 모델</code> 생성 (현재 제공 중인 tenant-basic, standard, pro 모두 서비스 특화 모델)</li>
<li>테넌트 서버 생성 및 관리 방식 고도화 및 자동화</li>
<li>⭐️ <code>모니터링 시스템 고도화</code> (현재는 <code>Node-exporter + Prometheus + Grafana</code> 를 사용하고 있는데, 파드 레벨의 매트릭 도입 및 다양한 패턴 적용해보기)</li>
<li><code>GitOps</code> 도입 (GitHub + Helm + ArgoCD)</li>
<li>다른 컨테이너/가상화 솔루션 도입(<code>Kata Container, KubeVirt</code> 등)</li>
<li>로드밸런서 구축 및 도입 (남는 라즈베리파이 사용)</li>
<li>실제 입주민 모집 (동아리 및 주변 지인들의 프로젝트를 홈랩에 입주)</li>
</ul>
<h3 id="마무리">마무리</h3>
<blockquote>
<p>처음 홈랩 클러스터 부품들(<del>약 80만원,,,,,😭</del>)을 주문할 때가 12월 중순이었습니다. 베어메탈 단계부터 직접 구축하고, 테넌트 서버 프로젝트 구축 &amp; 운영을 하다보니 정말 많은 것들을 배웠고, 값진 경험을 한 것 같습니다. 실제 클라우드 기업이 구현하고 운영하는 것과는 많은 차이가 있겠지만...^^ 비슷하게나마 따라서 만들고 운영 해보길 잘한 것 같습니다. </p>
</blockquote>
<p>현재는 1차 스프린트였던 그리디 3기 프로젝트가 종료되었지만, 바로 2차 스프린트 계획을 잡을까 합니다. 그리디 부원들이 만든 꽤나 성숙한 프로젝트들이 많기에 각 프로젝트팀에게 입주 제안을 하려고 합니다. 1차 스프린트 때는 <code>미션(요구사항)</code> 이라는 강제성(?)이 있었지만, 이번에는 <code>사용자(프로젝트 팀) - 클라우드 기업(나)</code> 의 관계로 견고한 제품을 팔아본다는 마음을 가져볼까 합니다.ㅎㅎ</p>
<blockquote>
</blockquote>
<p>감사합니다. :D</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Project] Multi-Tenant K8s Cluster on ARM64 - (14) Network Flow]]></title>
            <link>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-14-Network-Flow</link>
            <guid>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-14-Network-Flow</guid>
            <pubDate>Fri, 13 Mar 2026 17:51:10 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>현재까지 구축한 멀티-테넌트 프로젝트의 네트워크 흐름에 대해 정리해보고 도식화 해보았습니다.</p>
</blockquote>
<h1 id="아키텍처">아키텍처</h1>
<blockquote>
<p>현재 구축한 멀티-테넌트 프로젝트의 아키텍처 입니다. Users(클라이언트)가 테넌트 서버로 네트워크 통신을 보냈을 때 네트워크의 흐름을 패킷의 목적지 정보와 함께 정리해보겠습니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/2a1a2b09-922f-4065-936f-9c873a400120/image.png" alt=""></p>
<h1 id="전체-네트워크-흐름도">전체 네트워크 흐름도</h1>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/bc8d48ef-e785-465b-8374-48a620438748/image.png" alt=""></p>
<h2 id="순차적-설명">순차적 설명</h2>
<ol>
<li><code>&lt;Public IP&gt;:&lt;외부 포트&gt;</code> 주소를 가지고 가정용 모뎀에 접근</li>
<li>가정용 모뎀은 이를 <code>&lt;홈랩 내부 IP&gt;:&lt;로컬포트&gt;</code> 로 포트포워딩</li>
<li>직후 패킷이 들고 있는 목적지 정보는 <code>&lt;홈랩 내부 IP&gt;:&lt;로컬 포트&gt;</code></li>
<li>홈랩(마스터 노드) 진입 즉시 iptables 규칙에 따라 <code>&lt;홈랩 내부 IP&gt;:&lt;nodePort&gt;</code> 에서 <code>&lt;파드 고유 IP&gt;:&lt;targetPort&gt;</code> 로 변환</li>
<li>직후 패킷이 들고 있는 목적지 정보는 <code>&lt;파드 고유 IP&gt;:&lt;targetPort&gt;</code></li>
<li>정해진 길(iptables 명시)을 따라 워커노드에 도착<strong>+</strong>) <strong>CNI 라우팅(라우팅 테이블/터널/encap) + conntrack + iptables 체인</strong>이 협업</li>
<li>워커 노드 진입 이후 패킷이 들고 있는 목적지 정보는 <code>&lt;파드 고유 IP&gt;:&lt;targetPort&gt;</code></li>
<li>워커 노드는 추가적인 서비스 선택/재-DNAT 없이 커널 경로(CNI/conntrack)를 거쳐 Pod로 직접 전달</li>
</ol>
<h3 id="2번-항목-가정용-모뎀의-포트포워딩">2번 항목: 가정용 모뎀의 포트포워딩</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/27bd2cce-7aa2-4d29-aa42-7d993b316f90/image.png" alt=""></p>
<ul>
<li>포트포워딩 설정을 하기 위해 접속한 <code>네트워크 관리자 페이지</code> 내의 <code>포트포워딩 항목</code>입니다.</li>
</ul>
<h3 id="마무리">마무리</h3>
<p>지난번 포스팅에서는 아키텍처만 보았을 때 네트워크 흐름이 간단할 줄 알았습니다만,, 실제로 패킷이 어떠한 목적지 정보를 가지고 테넌트 서버(파드)로 도달하는지를 고민해보니 헷갈리는 점이 너무나 많았던 것 같습니다. 이론으로 네트워크의 흐름을 알게 되었으니, 조만간 아래의 명령어로 iptables 를 분석해보는 시간을 갖겠습니다.</p>
<pre><code class="language-bash">sudo iptables -t nat -L -n | &lt;포트 번호&gt;</code></pre>
<ul>
<li>핵심 정보<ul>
<li><code>DNAT (Destination NAT) 기록</code><ul>
<li>패킷이 노드에 도착했을 때 원래 목적지(Node IP: 30100)를 진짜 목적지(Pod IP: 40100)로 바꾸는 명령줄을 확인</li>
</ul>
</li>
<li><code>KUBE-SERVICES &amp; KUBE-NODEPORTS 체인</code><ul>
<li>쿠버네티스가 관리하는 특별한 규칙들<ul>
<li><code>KUBE-SERVICES</code>: Cluster IP 로 들어오는 내부 트래픽을 처리하는 규칙들</li>
<li><code>KUBE-NODEPORTS</code>: 외부에서 <code>nodePort</code> 로 들어오는 트래픽을 낚아채는 규칙들</li>
</ul>
</li>
</ul>
</li>
<li><code>Load Balancing 로직</code><ul>
<li>만약 파드가 여러 개라면, <code>iptables</code> 는 패킷을 어느 파드로 보낼지 결정함</li>
<li><code>statistic mode random probability 0.50000</code> 같은 문구가 있다면, “패킷을 50% 확률로 이 파드에, 나머지 50%는 저 파드에 보내라” 라는 설정을 의미함</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Project] Multi-Tenant K8s Cluster on ARM64 - (13) [Refactor] Add web external port]]></title>
            <link>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-13-Refactor-Add-web-external-port</link>
            <guid>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-13-Refactor-Add-web-external-port</guid>
            <pubDate>Wed, 11 Mar 2026 02:59:09 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>현재 테넌트 서버에 접속 가능한 포트는 SSH 접속을 위한 포트뿐입니다. 테넌트 서버에 백엔드 서비스 혹은 모니터링 대시보드 등을 띄웠을 경우 외부 접속 포트가 없고, SSH 접속을 위한 포트로 접속 시 브라우저(HTTP)가 기대하는 응답 웹 프로토콜과 실제 응답으로 나가는 SSH 프로토콜이 충돌하는 상황 발생.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/22905f68-c7a2-4b3e-b1f9-08cd0f4ef56d/image.png" alt=""></p>
<hr>
<h2 id="문제-상황">문제 상황</h2>
<ul>
<li>웹 서비스용 외부 개방 포트 부재</li>
<li>기존에 테넌트 서버당 하나씩 개방한 포트는 SSH 전용 포트이기에 요청-응답 프로토콜 간 충돌 발생</li>
</ul>
<h3 id="현재-테넌트-서버의-nodeport-관련-설정">현재 테넌트 서버의 NodePort 관련 설정</h3>
<pre><code class="language-yaml">  ports:
  - port: 2222           # 서비스 자체의 포트
    targetPort: 2222     # 파드 내부 컨테이너 포트
    nodePort: $NODEPORT  # 외부에서 노드로 들어오는 포트</code></pre>
<blockquote>
</blockquote>
<h2 id="📌-port-targetport-nodeport-란">📌 port, targetPort, nodePort 란?</h2>
<blockquote>
</blockquote>
<h3 id="port---서비스-자체의-포트">port - 서비스 자체의 포트</h3>
<blockquote>
</blockquote>
<ul>
<li>역할: 쿠버네티스 클러스터 내부에서 서비스 객체가 노출하는 포트</li>
<li>설명: 클러스터 안의 다른 파드들이 이 서비스를 호출할 때 사용하는 포트. 즉, 서비스의 DNS 이름이나 ClusterIP를 통해 통신할 때 기준이 됨<blockquote>
</blockquote>
<h3 id="targetport---파드-내부-컨테이너-포트">targetPort - 파드 내부 컨테이너 포트</h3>
<blockquote>
</blockquote>
</li>
<li>역할: 트래픽이 최종적으로 도달하는 파드(애플리케이션) 내부의 포트</li>
<li>설명: 실제 애플리케이션 코드가 수신 대기(Listen)하고 있는 포트 번호와 일치해야 함. <code>selector</code> 를 통해 연결된 파드로 트래픽을 전달하는 최종 목적지<blockquote>
</blockquote>
<h3 id="nodeport---외부에서-노드로-들어오는-포트">nodePort - 외부에서 노드로 들어오는 포트</h3>
<blockquote>
</blockquote>
</li>
<li>역할: 클러스터 외부에서 각 워커 노드의 IP를 통해 접근할 때 사용하는 포트</li>
<li>설명: 클러스터 외부의 사용자가 <code>노드IP:nodePort</code> 로 접속하면 트래픽이 쿠버네티스 내부로 유입</li>
<li>범위: 기본적으로 <code>30000~32767</code> 사이의 포트번호 사용 (지정하지 않으면 범위 내에서 랜덤하게 할당됨)</li>
</ul>
<h2 id="해결-방안">해결 방안</h2>
<ul>
<li>기존에 미리 선점(포트포워딩)해두었던 여분의 포트를 사용하여 각 테넌트 서버의 웹 서비스용 포트를 추가 지급</li>
<li>환경변수를 사용하여 <code>SSH_NODEPORT</code> 와 <code>WEB_EXTERNAL_PORT</code> 를 관리</li>
</ul>
<pre><code class="language-yaml">spec:
  type: NodePort
  selector:
    app: &lt;app 이름&gt;
  ports:
  # SSH 접속 관련
  - name: ssh-access
    port: 2222
    targetPort: 2222
    nodePort: $SSH_NODEPORT # SSH 접속용 NodePort
    # 웹 접속 관련
  - name: web
    port: $WEB_EXTERNAL_PORT
    targetPort: $WEB_EXTERNAL_PORT
    nodePort: $WEB_NODEPORT</code></pre>
<h3 id="테스트">테스트</h3>
<blockquote>
<p>같은 스펙을 가진 테스트용 테넌트 서버에 간단한 nc 명령어로 접속 테스트</p>
</blockquote>
<pre><code class="language-bash">echo -e &quot;Welcome to Test Tenant Server!&quot; | nc -l -p &lt;테넌트 서버의 웹 서비스용 포트&gt;</code></pre>
<ul>
<li><code>http://&lt;Public IP&gt;:&lt;웹 서비스용 포트&gt;</code> 로 접속</li>
<li>결과 1 <img src="https://velog.velcdn.com/images/pizza_loves_me/post/87609180-aeef-443b-bf9e-4b4c6a965b96/image.png" alt="">
(웹 서비스 전용 포트로 접속이 가능!)</li>
</ul>
<ul>
<li>결과 2 (실제 테넌트 사용자)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/0464169a-8c51-4b7f-bf3b-93361dd9729e/image.png" alt=""></p>
<ul>
<li>문제 상황을 제시해준 테넌트 사용자의 초기 목적대로 해당 팀의 Grafana 대시보드가 성공적으로 띄워졌음을 확인할 수 있습니다.</li>
</ul>
<h3 id="마무리">마무리</h3>
<p>하나의 테넌트 서버에 용도가 다른 두 포트를 개방하며, 외부에서 트래픽이 들어왔을 때 내부에서 그 흐름을 파악할 수 있게 된 리팩토링이었던 것 같습니다. 네트워크 흐름에 대한 정리는 다음 포스팅에서 간단한 아키텍처와 함께 정리해보겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Project] Multi-Tenant K8s Cluster on ARM64 - (12) Using Master Node Resource]]></title>
            <link>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-12-Using-Master-Node-Resource</link>
            <guid>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-12-Using-Master-Node-Resource</guid>
            <pubDate>Mon, 09 Mar 2026 08:58:20 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>마스터 노드의 리소스 구성은 <code>CPU - 4.0 Core</code> / <code>RAM - 8GB</code> / <code>Storage - 256GB</code> 입니다. 여기서 실제 마스터 노드 운영에 필요한 리소스를 제외하면 놀고 있는 리소스가 상당할 것으로 예상이 됩니다. 그래서 더 많은 리소스를 멀티 테넌트에 사용하고자 합니다.</p>
</blockquote>
<h2 id="1-마스터-노드-작동에-필요한-리소스-견적">1. 마스터 노드 작동에 필요한 리소스 견적</h2>
<ul>
<li><code>CPU: 1.0 Core</code><ul>
<li>etcd, kube-apiserver 는 테넌트 파드가 많아질수록 데이터 통신량과 연산량이 급증.</li>
<li>물리적인 1코어를 시스템 전용으로 박제해둠으로써, 테넌트 파드가 CPU를 100% 점유하려는 폭주 상황에서도 제어 신호가 밀리지 않고 클러스터 업무 처리가 가능</li>
</ul>
</li>
<li><code>Memory: 2.0Gi ~ 2.5Gi</code><ul>
<li>etcd 메모리 캐싱과 클러스터 상태 감시 프로세스가 차지하는 비중</li>
<li>메모리 부족 시 지연이나 노드 이탈 현상 발생</li>
</ul>
</li>
<li><code>Ephemeral Storage: 5Gi 이상</code><ul>
<li>로그와 컨테이너 이미지 레이어들이 쌓이는 공간</li>
<li>여기가 꽉 차면 마스터 노드 자체가 DiskPressure 상태에 빠져 멈춤</li>
</ul>
</li>
</ul>
<blockquote>
<p>일반적으로 마스터 노드는 최소 사양 2코어 이상을 권장하지만,, 리소스 제약(홈랩)을 고려하여 <strong>1.0 Core</strong> 를 물리적으로 <strong>예약(Reserved)</strong> 하는 방식을 채택했습니다!</p>
</blockquote>
<h2 id="2-마스터-노드에-걸린-제한taint-해제하기">2. 마스터 노드에 걸린 제한(Taint) 해제하기</h2>
<ul>
<li>현재 마스터 노드에는 <code>Taint</code>가 붙어 있기 때문에 일반 파드가 배포되지 않는다.</li>
</ul>
<pre><code class="language-bash"># 마스터 노드의 NoSchedule 설정 제거 (명령어 끝의 &#39;-&#39;가 제거를 의미합니다)
kubectl taint nodes master node-role.kubernetes.io/control-plane:NoSchedule-</code></pre>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/f043cee4-bad1-464e-aa5a-2fdd23d60fbb/image.png" alt=""></p>
<blockquote>
<p>⚠️ 주의사항: <code>NoSchedule-</code> 을 사용하여 마스터 노드를 개방했습니다. 나중에 테넌트 파드가 배포될 때, 테넌트 파드가 CPU 스케줄링을 두고 경쟁을 하게 되고 이때 마스터 노드의 작업에 영향을 끼칠 수도 있습니다.
이에 대한 명시적 조치로 <code>PriorityClass</code> 와 <code>NodeAffinity</code> 라는 설정이 있습니다.</p>
</blockquote>
<blockquote>
</blockquote>
<h2 id="📌-priorityclass--nodeaffinity">📌 PriorityClass &amp; NodeAffinity</h2>
<blockquote>
</blockquote>
<h3 id="priorityclass--누구를-먼저-내보낼-것인가"><code>PriorityClass</code> == “누구를 먼저 내보낼 것인가?”</h3>
<blockquote>
</blockquote>
<p>쿠버네티스 노드에 자원이 부족해지면 커널은 어떤 파드를 죽여서 노드를 살릴 것인지 결정을 합니다. 이때 <code>PriorityClass</code> 는 파드에게 붙여주는 계급장 역할을 합니다.</p>
<blockquote>
</blockquote>
<ul>
<li>파드마다 숫자(value)를 부여하고, 숫자가 높은 파드일수록 제거 후순위가 됩니다.</li>
<li>마스터 노드에서의 활용<ul>
<li>시스템 파드 (apiserver, etcd 등): 매우 높은 점수 (기본값으로 이미 높음)</li>
<li>테넌트 파드: 낮은 점수</li>
</ul>
</li>
<li>기대 효과<ul>
<li>마스터 노드의 메모리가 꽉 차면, 쿠버네티스는 점수가 낮은 테넌트 파드를 먼저 kill 시켜서 마스터 노드의 생존을 최우선으로 보장한다<blockquote>
</blockquote>
<h3 id="nodeaffinity--어디에-위치하게-할-것인가"><code>NodeAffinity</code> == “어디에 위치하게 할 것인가?”</h3>
<blockquote>
</blockquote>
</li>
</ul>
</li>
<li>파드가 배포될 때 라벨에 따라 선호/비선호를 지정하는 기능</li>
<li>마스터 노드에서의 활용<ul>
<li>테넌트 파드들에게 약한 거부감(Preferred Scheduling)을 부여</li>
<li>워커 노드에 공간이 있다면 워커 노드로 먼저 가고, 워커 노드가 꽉 찼을 때만 마스터 노드의 남는 자리를 쓰도록 유도</li>
</ul>
</li>
</ul>
<h3 id="priorityclass-리스트-확인하기">PriorityClass 리스트 확인하기</h3>
<pre><code class="language-bash">kubectl get priorityclasses

or

kubectl get pc</code></pre>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/5e6fcd83-c413-4c65-876f-f810d2294ed2/image.png" alt=""></p>
<ul>
<li>현재 쿠버네티스 시스템의 <code>priorityclass</code> 만 값이 20억점대로 지정이 되어 있음을 확인할 수 있습니다.</li>
<li>테넌트 파드들의 <code>priorityclass</code> 를 따로 지정하지 않았기 때문에 0점(Default)으로 작동 중입니다.</li>
<li>→ 마스터 노드의 생존은 어느 정도 보장이 된 상태!</li>
</ul>
<h3 id="각-파드들의-pc-값을-한-번에-조회하기">각 파드들의 PC 값을 한 번에 조회하기</h3>
<pre><code class="language-bash">kubectl get pods -A -o custom-columns=&quot;NAMESPACE:.metadata.namespace,NAME:.metadata.name,PRIORITY-CLASS:.spec.priorityClassName,VALUE:.spec.priority&quot;</code></pre>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/f23038fb-591b-4347-b8a7-18684a080fcb/image.png" alt=""></p>
<ul>
<li>시스템 설정 관련 파드들은 모두 20억대의 값(value)을 갖고 있고, 일반 파드들은 기본값인 0을 부여받았음을 확인할 수 있습니다.</li>
</ul>
<h2 id="3-리소스-예약-설정마스터-노드-보호-설정">3. 리소스 예약 설정(마스터 노드 보호 설정)</h2>
<ul>
<li>테넌트 파드가 마스터 노드의 자원을 다 사용하지 못하도록 <code>Kubelet</code> 설정 파일을 수정하여 관리자 전용 자원을 강제로 묶음</li>
</ul>
<h3 id="3-1-설정-파일-열기">3-1. 설정 파일 열기</h3>
<pre><code class="language-bash">sudo nano /var/lib/kubelet/config.yaml</code></pre>
<h3 id="3-2-리소스-예약-값-추가">3-2. 리소스 예약 값 추가</h3>
<ul>
<li><code>kubeReserved</code> (쿠버네티스 관리용) / <code>systemReserved</code> (OS 구동용)</li>
<li><code>kubelet/config.yaml</code> 하단에 설정 추가</li>
</ul>
<pre><code class="language-bash"># 마스터 노드 보호를 위한 리소스 예약 설정
systemReserved:
  cpu: &quot;300m&quot;
  memory: &quot;500Mi&quot;
kubeReserved:
  cpu: &quot;700m&quot;      # 시스템과 합쳐서 총 1.0 Core 예약
  memory: &quot;2000Mi&quot;  # 시스템과 합쳐서 총 2.5Gi 예약
enforceNodeAllocatable: [&quot;pods&quot;]
evictionHard:
  memory.available: &quot;500Mi&quot; # 여유 메모리 500Mi 미만 시 테넌트 파드 자동 퇴거
  nodefs.available: &quot;10%&quot;   # 디스크 여유 10% 미만 시 경고 및 제한</code></pre>
<ul>
<li><code>systemReserved</code><ul>
<li>리눅스 운영체제 자체가 동작할 공간을 미리 떼어두는 설정</li>
</ul>
</li>
<li><code>kubeReserved</code><ul>
<li>쿠버네티스 관리 객체(kubelet, container runtime 등)를 위한 전용 자원 설정</li>
</ul>
</li>
<li><code>enforceNodeAllocatable</code><ul>
<li>노드에서 파드에게 실제로 나누어 줄 수 있는 자원(Allocatable)을 계산할 때, 위에서 예약한 자원들을 강제로 제외하겠다는 선언</li>
<li>보통 <code>[”pods”]</code> 로 설정하며, 스케줄러는 이 계산된 값을 보고 파드를 배치함</li>
</ul>
</li>
<li><code>evictionHard</code><ul>
<li>노드의 자원이 한계치에 도달했을 때, 파드를 강제로 즉시 종료 시켜 노드를 살리는 임계값</li>
</ul>
</li>
</ul>
<h2 id="4-마무리-및-설정-반영">4. 마무리 및 설정 반영</h2>
<h3 id="4-1-kubelet-재시작-필수">4-1. Kubelet 재시작 (필수)</h3>
<pre><code class="language-bash">sudo systemctl restart kubelet</code></pre>
<h3 id="4-2-마스터-노드-상태-및-자원-할당량-확인">4-2. 마스터 노드 상태 및 자원 할당량 확인</h3>
<pre><code class="language-bash">kubectl describe node master | grep -A 5 &quot;Allocatable:&quot;</code></pre>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/9d5baaec-524f-41be-8c9b-61c5e7f22db3/image.png" alt=""></p>
<blockquote>
<p>설정이 완료된 후 <code>Allocatable</code> 수치를 확인해 보면, 마스터 노드 예약 자원을 제외한 <code>“실제 가용 범위&quot;</code> 를 확인할 수 있습니다.</p>
</blockquote>
<ul>
<li><code>cpu: 3</code><ul>
<li>4코어 중 시스템용으로 1코어를 예약했기 때문에 <code>cpu - 3</code> 가 출력</li>
</ul>
</li>
<li><code>ephemeral-storage: 22536xxxxx</code><ul>
<li>256GB 스토리지 중 시스템 영역을 제외하고 파드들이 사용 가능한 저장 공간</li>
</ul>
</li>
<li><code>memory: 581xxxxxKi</code><ul>
<li>전체 RAM 8GB 중, systemReserved + kubeReserved 로 설정한 2.5Gi 와 evictionHard 로 설정한 500Mi 등으로 제외한 수치</li>
</ul>
</li>
<li><code>pods: 110</code><ul>
<li>해당 노드에 최대로 올릴 수 있는 파드의 개수 (쿠버네티스 기본값 == 110)</li>
</ul>
</li>
</ul>
<p>→ 마스터 노드 예약 리소스 설정 완료!😋</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Project] Multi-Tenant K8s Cluster on ARM64 - (11)  Monitoring Dashboard]]></title>
            <link>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-11-Monitoring-Dashboard</link>
            <guid>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-11-Monitoring-Dashboard</guid>
            <pubDate>Mon, 09 Mar 2026 03:38:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이번에는 홈랩의 모니터링 서버를 구축해보려고 합니다. 고도화 해볼 요소가 많은 부분인데, 우선 간단하게 <code>Node-Exporter/kube-state-metrics + Prometheus + Grafana</code> 를 사용하겠습니다.</p>
</blockquote>
<h2 id="아키텍처-후보">아키텍처 후보</h2>
<blockquote>
<p>우선 공통적으로 모니터링 서버를 클러스터와 격리시키면 좋겠다고 생각했습니다. 외부 클라우드 서비스에 모니터링 서버를 띄우는게 괜찮은지는 모르겠다만,, <code>‘클러스터와 모니터링 서버의 격리</code>’에 초점을 두기로 했습니다.</p>
</blockquote>
<h3 id="1안---각-노드에-모니터링용-포트를-개방하여-외부-모니터링-서버와-연결하기-포트포워딩">1안) - 각 노드에 모니터링용 포트를 개방하여 외부 모니터링 서버와 연결하기 (포트포워딩)</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/22f9a92d-ff84-4626-bfa7-a7cedc372bfc/image.png" alt=""></p>
<ul>
<li><code>장점</code><ul>
<li>구축이 비교적 간단하다. (모든 노드에 대한 포트를 열고 연결하면 되므로)</li>
</ul>
</li>
<li><code>단점</code><ul>
<li>현재 클러스터에서는 Bastion 방식으로 워커 노드를 숨기고 있는데 이 구조가 깨지게 됨</li>
<li>매트릭이 오고 가는 통로가 외부에 노출되므로 보안상 위험할 수 있음</li>
</ul>
</li>
</ul>
<h3 id="2안---마스터-노드와-모니터링-서버만-연결하고-이때-연결-방식은-vpn-방식을-사용하기-vpn--bastion-구조">2안) - 마스터 노드와 모니터링 서버만 연결하고, 이때 연결 방식은 VPN 방식을 사용하기 (VPN + Bastion 구조)</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/e8c8fc30-a6da-4607-9c99-9c9187c87b92/image.png" alt=""></p>
<ul>
<li><code>장점</code><ul>
<li>기존 클러스터 구축 방식과 동일하게 Bastion 구조로 워커 노드를 외부로 노출시키지 않음</li>
<li>마스터 노드와 모니터링 서버만 연결을 하고, 이를 VPN 방식으로 연결했기 때문에 매트릭이 지나는 통로를 숨길 수 있음.<ul>
<li>공인 IP 노출 없이 NAT 환경(가정용 공유기)을 통과하여 양방향 통신을 함. - <code>Hole Punching</code></li>
</ul>
</li>
</ul>
</li>
<li><code>단점</code><ul>
<li>마스터 노드를 <code>Metric Gateway</code> 로도 사용하게 됨.(트래픽 집중 현상이 발생하지 않을까..? 하는 생각이 듭니다.)</li>
<li>구축이 비교적 복잡하다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>클러스터 아키텍처상 Bastion 방식을 채택하고 있으므로 이를 유지하고자 <code>2안</code> 의 방식으로 구축을 진행하겠습니다.</p>
</blockquote>
<hr>
<h2 id="전체-아키텍처">전체 아키텍처</h2>
<ul>
<li>라즈베리파이 클러스터 (데이터 생성)<ul>
<li><code>Node Exporter</code> &amp; <code>Kube-state-metrics</code> 가 각각 하드웨어 지표와 쿠버네티스 상태 정보를 생성</li>
</ul>
</li>
<li>OCI 인스턴스 (수집 및 시각화)<ul>
<li><code>Prometheus</code> 가 Tailscale을 통해 라즈베리파이의 데이터를 Scraping 하여 저장하고, <code>Grafana</code> 가 이를 시각화</li>
</ul>
</li>
<li>보안 게이트웨이 설계<ul>
<li>보안을 위해 워커 노드를 외부에 노출하지 않고, 마스터 노드를 통해서만 지표가 나가도록 설계</li>
</ul>
</li>
</ul>
<h2 id="1단계-oci-인스턴스-보안-설정-ingress">1단계: OCI 인스턴스 보안 설정 (Ingress)</h2>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/6c9bbdb1-cf37-4667-9e58-3d03f523a59f/image.png" alt=""></p>
<ul>
<li>Security List 규칙 추가<ul>
<li>3000 (Grafana 웹 대시보드 접속용)</li>
<li>9090 (Prometheus 타겟 상태 확인 및 디버깅 확인용)</li>
</ul>
</li>
<li>OS 방화벽(iptables/firewalld) 설정<ul>
<li>OCI 보안 리스트뿐만 아니라 인스턴스 내부 방화벽에서도 해당 포트를 <code>ACCEPT</code> 하도록 설정</li>
</ul>
</li>
</ul>
<h2 id="2단계-네트워크-연결-tailscale-vpn">2단계: 네트워크 연결 (Tailscale VPN)</h2>
<ul>
<li>공인 IP 노출 없이 안전하게 데이터를 전송하기 위한 연결</li>
<li>Tailscale 설치<ul>
<li>OCI 인스턴스와 라즈베리파이 마스터 노드에 각각 설치하여 사설 IP로 통신하도록 설정</li>
</ul>
</li>
<li>MTU 최적화<ul>
<li>대량의 메트릭 데이터(특히 <code>kube-state-metrics</code>) 전송 시 패킷 유실을 방지하기 위해 Tailscale 인터페이스의 <code>MTU</code>를 <code>1280</code>으로 조정<ul>
<li>⚠️ 현재 구축 환경에서는 kube-state-metrics 가 수집한 매트릭이 유실되거나, OOM이 발생하여 인스턴스가 다운되는 상황이 자주 발생.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="3단계-라즈베리파이-에이전트-배치">3단계: 라즈베리파이 에이전트 배치</h2>
<ul>
<li><code>Node Exporter</code><ul>
<li>각 노드의 CPU, 메모리, 온도 등의 지표 수집</li>
</ul>
</li>
<li><code>kube-state-metrics</code><ul>
<li>쿠버네티스 API 를 통해 파드, 노드, 배포 상태 등을 수집</li>
<li>NodePort 설정: OCI에서 접근할 수 있도록 30080 포트 개방</li>
<li>리소스 최적화: 타임아웃 방지를 위해 CPU/Memory Request 를 명시적으로 할당</li>
</ul>
</li>
</ul>
<h2 id="4단계-oci-prometheus--grafana-구축">4단계: OCI Prometheus &amp; Grafana 구축</h2>
<ul>
<li><code>Docker Compose 구성</code><ul>
<li>두 서비스를 컨테이너로 띄워 관리 효율성 높임</li>
</ul>
</li>
<li><code>Prometheus 네트워크 모드 (network_mode: host)</code><ul>
<li>컨테이너 내부가 아닌 호스트의 Tailscale 네트워크를 직접 사용하도록 설정하여 통신 장애를 해결</li>
</ul>
</li>
<li><code>prometheus.yml 설정</code><ul>
<li>scrape_interval 과 scrape_timeout 을 조정하여 지연 시간 극복</li>
<li>대시보드 필터링을 위해 <code>cluster: ‘raspberry-pi-cluster’</code> 라벨을 강제로 부여</li>
</ul>
</li>
</ul>
<blockquote>
</blockquote>
<h2 id="⚠️-문제-발생">⚠️ 문제 발생</h2>
<blockquote>
</blockquote>
<ul>
<li>현재 모니터링 서버는 OCI(Oracle Cloud Architecture) 프리티어 계정으로 받은 인스턴스에 띄워져 있습니다.</li>
<li>Node-Exporter가 수집하는 <code>매트릭 + Refresh 1m</code> 는 문제가 없는데, <code>kube-state-metrics + Refresh 1m</code> 조합은 인스턴스가 터지는 상황이 계속 발생했습니다.</li>
<li>너무 많은 양의 매트릭 + 전송량 + Refresh 주기 등의 문제로 OOM이 발생하여 인스턴스가 다운되는 것 같음.<blockquote>
</blockquote>
→ 우선, 모니터링 대시보드 구축이 첫 목표였기에 Node-Exporter 의 매트릭을 사용하여 모니터링 대시보드를 구축하겠습니다.<blockquote>
</blockquote>
→ Relabeling 기능 사용, 인스턴스 사양 업그레이드, gRPC 도입 등으로 추후 해결해보겠습니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/e8f1523e-9be6-4412-b200-8e2b91c8f3bb/image.png" alt=""></p>
<ul>
<li><code>Prometheus</code> 는 정상적으로 연결이 되어 있는 상태이므로,,, 추후 모니터링 고도화 작업을 진행하며 <code>kube-state-metrics</code> 또한 살려보겠습니다..!</li>
</ul>
<h2 id="5단계-grafana-시각화">5단계: Grafana 시각화</h2>
<ul>
<li>데이터 소스 연동<ul>
<li>특정 URL로 Prometheus 연결</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/debda536-6cad-49bb-9a5f-8c9ea77f90af/image.png" alt=""></p>
<blockquote>
</blockquote>
<h2 id="📌-모니터링-방법론-use-red-fore-golden-signal">📌 모니터링 방법론 (USE, RED, Fore golden signal)</h2>
<blockquote>
</blockquote>
<ul>
<li>이번 포스팅에서 자세히 다룰 것은 아니지만, 다양한 방법론이 있기에 이들을 학습해보고 적절히 도입을 해보면 좋을 것 같습니다.<blockquote>
</blockquote>
<h3 id="use-method---인프라-시스템-중심--node-exporter">USE Method - 인프라 시스템 중심 / Node-Exporter</h3>
<blockquote>
</blockquote>
하드웨어 자원의 상태를 확인하기 위한 방법론<blockquote>
</blockquote>
</li>
<li><code>Utilization (사용률)</code><ul>
<li>자원이 얼마나 사용되었는가?<ul>
<li>ex: node_cpu_seconds_total<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><code>Saturation (포화도)</code><ul>
<li>자원이 얼마나 줄을 서서 기다리고 있는가?<ul>
<li>ex: node_load1<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><code>Errors (에러)</code><ul>
<li>하드웨어/드라이버 레벨의 에러 발생 횟수<ul>
<li>ex: node_net_errs_total<blockquote>
</blockquote>
<h3 id="red-method---서비스-중심--app-메트릭-kube-state-metrics">RED Method - 서비스 중심 / App 메트릭, kube-state-metrics</h3>
<blockquote>
</blockquote>
사용자가 느끼는 서비스의 품질을 측정하기 위한 방법론<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><code>Rate (TPS 등)</code><ul>
<li>초당 요청 수<ul>
<li>ex: http_request_total<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><code>Errors (에러)</code><ul>
<li>실패한 요청 수<ul>
<li>ex: HTTP 500 에러 비율<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><code>Durations (응답 시간)</code><ul>
<li>요청을 처리하는 데 걸린 시간<ul>
<li>ex: http_request_duration_seconds_bucket<blockquote>
</blockquote>
<h3 id="four-golden-signal--구글-sre-팀의-핵심-지표">Four golden signal / 구글 SRE 팀의 핵심 지표</h3>
<blockquote>
</blockquote>
구글에서 정의한 가장 유의미한 4가지 지표. RED 메서드의 확장판..?<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><code>Latency (지연 시간)</code><ul>
<li>서비스 응답에 걸리는 시간<blockquote>
</blockquote>
</li>
</ul>
</li>
<li><code>Traffic (트래픽)</code><ul>
<li>서비스에 대한 수요<blockquote>
</blockquote>
</li>
</ul>
</li>
<li><code>Errors (에러)</code><ul>
<li>명시적에러(500), 암시적 에러(성공했지만 응답 내용이 잘못됨) 등<blockquote>
</blockquote>
</li>
</ul>
</li>
<li><code>Saturation (포화도)</code><ul>
<li>서비스가 얼마나 꽉 찼는지.</li>
<li>CPU 뿐만 아니라 큐의 길이나 메모리 잔량 포함<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
</blockquote>
<p>USE - 인프라(Hardware)</p>
<blockquote>
</blockquote>
<p>RED - 서비스(Software)</p>
<blockquote>
</blockquote>
<p>Golden Signals - 사용자 경험(SRE)</p>
<h3 id="마무리">마무리</h3>
<p>모니터링 시스템 고도화와 로깅 시스템 고도화에 재밌는 작업들이 많을 것으로 예상됩니다. 현재는 <code>Node-Exporter + Prometheus + Granfana</code> 조합만을 사용하고 있지만, 고도화 과정에서</p>
<ul>
<li>kube-state-metrics 사용</li>
<li>Loki 도입</li>
<li>⭐️ Opentelemetry + gRPC + Grafana 구축</li>
</ul>
<p>등을 해보면 좋을 것 같습니다. (gRPC로 로그나 매트릭을 보내게 된다면 통신 부담이 조금은 덜 하지 않을까 싶습니다..)</p>
<p>그리고 매번 모니터링 시스템을 ‘구축’ 만 해보았는데, 이번 기회에 다양한 방법론도 도입해보고 ‘어떻게 유의미한 데이터를 만들어낼 수 있을지’ 에 대해서도 고민해보면 좋을 것 같습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Project] Multi-Tenant K8s Cluster on ARM64 - (10) [Refactor] Building a Truly Stateful (Storage)]]></title>
            <link>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-10-Refactor-Building-a-Truly-Stateful-Storage</link>
            <guid>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-10-Refactor-Building-a-Truly-Stateful-Storage</guid>
            <pubDate>Mon, 09 Mar 2026 01:39:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>현재 입주해 있는 테넌트 중 한 분이 “인스턴스가 재시작 되면 일부 코드가 날라가는 것 같아요” 라는 피드백을 주셨습니다. <a href="https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-3-Storage-Quota-Isolation">이전에 진행한 스토리지 설정 과정</a> 에서 스토리지 자원 관리(Quota)와 데이터 격리(Isolation)만 설정을 했고, 영속성에 대한 생각을 못했던 것이 문제가 되었습니다.
치명적인 문제가 발생했기에,, 빠르게 작업을 해주었습니다..</p>
</blockquote>
<blockquote>
</blockquote>
<h2 id="들어가기-전-개념-정리-pv--pvc">들어가기 전 개념 정리 (PV &amp; PVC)</h2>
<blockquote>
</blockquote>
<ul>
<li>쿠버네티스에서 스토리지는 크게 두 단계로 관리됩니다.<blockquote>
</blockquote>
<h3 id="📌-pv-persistentvolume">📌 PV (PersistentVolume)</h3>
<blockquote>
</blockquote>
</li>
<li>실제 물리적 스토리지(SSD)의 일부분을 쿠버네티스가 인식할 수 있는 자원 형태로 등록해둔 것 → <code>관리자 영역</code><blockquote>
</blockquote>
<h3 id="📌-pvc-persistentvolumeclaim">📌 PVC (PersistentVolumeClaim)</h3>
<blockquote>
</blockquote>
</li>
<li>사용자가 특정 용량과 접근 권한을 명시하여 스토리지를 할당받으려고 제출하는 요청 → <code>테넌트 영역</code><blockquote>
</blockquote>
테넌트가 PVC를 생성하면, 쿠버네티스는 테넌트의 요구사항(용량, 읽기/쓰기 모드)에 맞는 PV를 찾아서 서로 연결(<code>Binding</code>) 해줍니다.</li>
</ul>
<hr>
<h2 id="문제-상황">문제 상황</h2>
<ul>
<li><code>/config</code> 내에 생성한 데이터, 파일들은 영속성이 보장되지만, 그 밖에 생성한 코드의 경우 영속성 유지가 안 됨.</li>
<li>이는 파드가 죽을 경우 해당 디렉토리 밖에 있는 폴더나 설정들이 모두 사라지게 됨</li>
</ul>
<h2 id="해결-방안-영속성-범위를-홈-디렉토리까지-확장">해결 방안: 영속성 범위를 홈 디렉토리까지 확장</h2>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/440e2929-eec1-4c13-acb7-032ca346474f/image.png" alt=""></p>
<ul>
<li><p>현재 <code>v1-basic-setup.sh</code> 내에 있는 volumeMounts의 경로는 /config</p>
</li>
<li><p>→ 이를 유지하며 <code>subPath</code> 기능을 사용해 영역 추가하기</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/63715f89-2875-4377-82c5-8736600040a1/image.png" alt=""></p>
<ul>
<li><code>name: tenant-storage</code><ul>
<li>역할: 연결할 PVC의 이름을 지정</li>
<li>기능: 하단 volumes 섹션에 정의된 <code>tenant-storage</code> 라는 이름의 PVC를 찾아서 가져옴. 이전에 설정한 8Gi 짜리 디스크 공간을 가리킴</li>
</ul>
</li>
<li><code>mountPath</code><ul>
<li>역할: 파드 내부에서 데이터가 보일 가상 경로</li>
<li>기능:<ul>
<li><code>/config</code> : SSH 서버가 부팅될 때 필요한 호스트 키, 설정 파일들을 읽어가는 장소</li>
<li><code>/home/tenant-user</code> : 테넌트가 접속하자마자 마주하는 홈 디렉토리</li>
</ul>
</li>
<li>효과: 이 경로에 파일을 저장하면 파드 내부의 휘발성 메모리가 아닌, 실제 외부 디스크(PVC)에 기록됨</li>
</ul>
</li>
<li><code>subPath</code><ul>
<li>역할: 디스크 내 격리된 저장 구역 생성</li>
<li>기능: 하나의 PVC 내부에 <code>system-config</code>, <code>tenant-home-data</code> 라는 별도의 디렉토리를 자동 생성하여 관리<ul>
<li><code>system-config</code> : PVC 내부에 <code>system-config</code> 라는 폴더를 만들어 SSH 시스템 파일만 따로 모음</li>
<li><code>tenant-home-data</code> : PVC 내부에 <code>tenant-home-data</code> 폴더를 만들어 유저가 생성한 폴더나 파일들만 따로 모음</li>
</ul>
</li>
<li>도입 이유<ul>
<li><code>subPath</code> 없이 마운트하면 볼륨의 루트(Root)가 해당 경로를 완전히 덮어씌우는 상황 발생.</li>
<li><code>subPath</code> 기능을 통해 시스템 설정 파일과 사용자 데이터를 물리적으로 분리 &amp; 관리포인트(PVC)는 하나로 유지</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="현재-홈랩-물리-디스크의-구조">현재 홈랩 물리 디스크의 구조</h3>
<ul>
<li><code>tenant-storage</code><ul>
<li>8Gi 용량의 실제 물리 디스크 (PVC)</li>
</ul>
</li>
<li><code>system-config</code><ul>
<li>SSH 구동을 위한 필수 설정 저장소 (/config)</li>
</ul>
</li>
<li><code>tenant-home-data</code><ul>
<li>유저의 실제 데이터 저장소 (/home/tenant-user)</li>
</ul>
</li>
</ul>
<h3 id="before--after">Before &amp; After</h3>
<ul>
<li><code>Before</code><ul>
<li>파드가 재시작 되면 SSH 설정은 남지만, 테넌트가 작성한 코드 등의 데이터는 사라짐(휘발성)</li>
</ul>
</li>
<li><code>After</code><ul>
<li>파드가 재시작 되어도 설정과 테넌트가 저장한 데이터 모두 남아 있음(영속성)</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Project] Multi-Tenant K8s Cluster on ARM64 - (9) Network Trouble Shooting Report]]></title>
            <link>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-9-%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-%ED%86%B5%ED%95%A9-%EB%A6%AC%ED%8F%AC%ED%8A%B8</link>
            <guid>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-9-%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-%ED%86%B5%ED%95%A9-%EB%A6%AC%ED%8F%AC%ED%8A%B8</guid>
            <pubDate>Mon, 09 Mar 2026 01:09:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>홈랩 구축 및 멀티 테넌트 프로젝트를 진행하며 압도적으로 고생을 많이 했던 네트워크 설정 과정에서 경험한 트러블 슈팅 내용을 정리해보았습니다. 가정용 네트워크 환경에서 구축한 홈랩이다보니 문제 원인을 찾는 것도 오래 걸리고,, 파악하는 것도 정말 오래 걸렸던 것 같습니다..(<del>덕분에 네트워크 공부가 아주 많이 되었네용</del>)</p>
</blockquote>
<hr>
<h2 id="1-배경---csp-모델-구현을-위한-프로젝트">1. 배경 - CSP 모델 구현을 위한 프로젝트</h2>
<ul>
<li>목표: 라즈베리파이로 구축한 홈랩 클러스터를 활용해 외부 테넌트에게 <code>공인 IP + SSH</code> 기반의 독립 인스턴스 환경을 제공하기</li>
<li>핵심 도구: Calico(CNI), MetalLB(L2 Mode), SSH</li>
</ul>
<h2 id="2-phase-1-calico와-커널의-충돌-초기-장애">2. Phase 1: Calico와 커널의 충돌 (초기 장애)</h2>
<blockquote>
<p>가장 먼저 마주한 충돌 요소는 채택한 <code>CNI 방식</code>(Flannel → Calico)과 라즈베리파이 <code>커널의 충돌</code></p>
</blockquote>
<ul>
<li><p>현상: 노드는 <code>Ready</code> 지만 파드 간 통신 두절, 패킷 유실 발생</p>
</li>
<li><p>원인 분석:</p>
<ul>
<li>원인 1) <code>Tailscale 간섭</code><ul>
<li>초기 구축 과정 중(포트포워딩 전) 집 밖(외부)에서도 작업을 하기 위해 VPN(Tailscale)을 사용</li>
<li>VPN 인터페이스와 VXLAN 경로가 충돌하여 패킷이 길을 잃음<ul>
<li>→ <a href="%5Bhttps://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-6-CNI-%EC%84%A4%EC%A0%95-Calico-%EC%82%AC%EC%9A%A9%5D(https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-6-CNI-%EC%84%A4%EC%A0%95-Calico-%EC%82%AC%EC%9A%A9)">CNI 설정</a> 에서 가정용 네트워크 환경과 <code>설정 편의성</code> 을 고려하여 VXLAN 모드를 채택했습니다.</li>
</ul>
</li>
</ul>
</li>
<li>원인 2) <code>체크섬 오프로드 오류</code><ul>
<li>라즈베리파이 랜카드 하드웨어 가속 기능이 패킷을 변조된 것으로 오인하여 드랍하는 상황 발생</li>
</ul>
</li>
<li>원인 3) <code>커널 설정 미비</code><ul>
<li><code>ip_forward</code> 비활성화 및 <code>iptables</code> 포워딩 정책 차단</li>
</ul>
</li>
</ul>
</li>
<li><p>💡 <strong>해결 !</strong></p>
<ul>
<li>인터페이스 정리, <code>ethtool</code> 로 체크섬 오프로드 비활성화, 커널 포워딩 활성화</li>
</ul>
</li>
</ul>
<pre><code class="language-bash">ethtool -K eth0 tx off rx off</code></pre>
<h2 id="3-phase-2-metallb-l2-모드-문제">3. Phase 2: MetalLB L2 모드 문제</h2>
<blockquote>
<p>커널 문제를 해결했으나 L2 계층(ARP)의 한계가 드러남</p>
</blockquote>
<ul>
<li><p>현상: <code>type: LoadBalancer</code> 로 IP(192.168.x.x)는 할당이 되지만, 외부 접속 시 <code>TIMEOUT</code> 발생</p>
</li>
<li><p><code>디버깅 (tcpdump/log)</code> : MetalLB Speaker 로그에 메시지가 뜨지 않음</p>
</li>
<li><p>원인 분석:</p>
<ul>
<li><code>(가정용) 공유기 환경의 한계</code><ul>
<li>가정용 공유기가 MetalLB의 ARP 광고(Announce)를 ARP 스푸핑으로 간주해 차단하거나 무시하는 것으로 추정됨..🤬</li>
<li>+) 가정용 공유기의 경우 보안을 위해 ARP 스푸핑 방지 기능이 기본 탑재된 경우가 많다고 합니다. 저희 집 네트워크 관리자 페이지에서는 해당 기능을 설정할 수 있는 항목이 없었기에,,, 선택의 여지가 없었네요..</li>
</ul>
</li>
<li><code>CNI 충돌</code><ul>
<li>Calico 와 MetalLB 가 동일한 인터페이스를 제어하려다 발생하는 논리적 충돌도 가능성이 높았음</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>해결 방안..!</strong></p>
<ul>
<li>불안정한 Calico를 버리고, eBPF 기반의 <code>Cilium</code> 으로 마이그레이션 결정<ul>
<li>초기 선택지였던 <code>Flannel, Calico, Cilium</code> 중 남은 CNI 툴은 Cilium 뿐이었음..</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="4-phase-3-cilium-도입과-nodeport-우회-최종-해결-방법">4. Phase 3: Cilium 도입과 NodePort 우회 (최종 해결 방법)</h2>
<blockquote>
<p>하나 밖에 남지 않은 선택지 &amp; 추후 도입(고도화) 하려고 했던 <code>Cilium</code> 을 도입하고, 불확실한 L2 광고(Announce) 대신 확실한 경로를 선택하도록 변경</p>
</blockquote>
<ul>
<li><p>전략: MetalLB의 L2 ARP 방식이 (가정용) 공유기 환경에서 신뢰도가 낮음을 확인. NodePort 기반의 직결 경로로 방식 변경</p>
</li>
<li><p>최종 아키텍처</p>
<ol>
<li><code>Service</code> : LoadBalancer → NodePort 로 전환</li>
<li><code>공유기</code> : 공인 IP:2001 → 노드IP:Port 방식으로 포트포워딩</li>
<li><code>흐름</code> : 외부 요청이 실존하는 노드 IP를 타고 파드로 즉시 전달됨</li>
</ol>
</li>
<li><p>결과: 네트워크 추상화 레이어를 한 단계 낮춤으로써 안정적인 SSH 접속 성공!!</p>
</li>
</ul>
<h2 id="포트-관리-체계최최최종">포트 관리 체계(최최최종)</h2>
<table>
<thead>
<tr>
<th>계층</th>
<th>구분</th>
<th>예시</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>1단계 (외부)</td>
<td>공용 포트 (Public)</td>
<td>50111</td>
<td>사용자가 외부 인터넷에서 실제 접속 시 사용하는 번호</td>
</tr>
<tr>
<td>2단계 (공유기)</td>
<td>노드 포트 (NodePort)</td>
<td>30111</td>
<td>공유기 포트포워딩의 목적지이자, K8s 노드가 열고 있는 포트</td>
</tr>
<tr>
<td>3단계 (내부)</td>
<td>타겟 포트 (Target)</td>
<td>2222</td>
<td>실제 파드 내부에서 SSH 데몬이 떠 있는 포트</td>
</tr>
</tbody></table>
<blockquote>
<p>라즈베리파이 본체의 SSH 접속 포트(22)와 파드의 SSH 접속 포트 간의 충돌을 방지하고자 파드 내부의 SSH 포트를 2222 로 설정했습니다.</p>
</blockquote>
<h2 id="📌-네트워크-포트-범위">📌 네트워크 포트 범위</h2>
<table>
<thead>
<tr>
<th>포트 범위</th>
<th>명칭</th>
<th>특징 및 활용 예시</th>
</tr>
</thead>
<tbody><tr>
<td>0 ~ 1023</td>
<td>Well-Known Ports (특권 포트)</td>
<td>시스템의 공식 창구(SSH-22, HTTP-80 등). 일반 사용자는 함부로 열 수 없고 <code>root</code> 권한이 필요</td>
</tr>
<tr>
<td>1024 ~ 49151</td>
<td>Registered Ports (등록 포트)</td>
<td>특정 애플리케이션이나 서비스가 등록해서 사용하는 구간. 30000 ~ 32767 구간이 쿠버네티스의 NodePort 기본 범위.</td>
</tr>
<tr>
<td>49152 ~ 65535</td>
<td>Dynamic/Private Ports (사설 포트)</td>
<td>누구나 자유롭게 사용하고, 임시로 쓰고 버리는 구간. 현재: 외부로 노출시키는 공용 포트(Public)는 5만번대를 사용</td>
</tr>
</tbody></table>
<blockquote>
</blockquote>
<h2 id="❗️-우리집-가정용-공유기에서-calico는-안-되었고-cilium-은-되었던-이유">❗️ (우리집) 가정용 공유기에서 Calico는 안 되었고, Cilium 은 되었던 이유</h2>
<blockquote>
</blockquote>
<h3 id="1-calico의-실패---vxlan--iptables">1. Calico의 실패 - (VXLAN + Iptables)</h3>
<blockquote>
</blockquote>
<ul>
<li><code>공유기 입장</code><ul>
<li>패킷 안에 또 패킷이 들어 있으니 구조가 복잡함(VXLAN)</li>
<li>라즈베리파이의 랜카드(NIC)가 이 복잡한 패킷의 체크섬(오류검사)을 계산하다가 실수를 하면, 공유기는 이를 변조된 패킷 혹은 ARP 스푸핑 공격으로 오해해서 버려버림</li>
</ul>
</li>
<li><code>자원 소모</code><ul>
<li>Calico 는 iptables 라는 규칙 수 천개를 일일이 검사함</li>
<li>저사양 공유기와 라즈베리파이 입장에서 이 과정에서 발생하는 Latency 가 통신 두절로 이어짐<blockquote>
</blockquote>
<h3 id="2-cilium의-성공---ebpf-direct-routing">2. Cilium의 성공 - (eBPF Direct Routing)</h3>
<blockquote>
</blockquote>
</li>
</ul>
</li>
<li>Cilium은 ‘지능형 고속도로’와 같음</li>
<li><code>커널 직접 처리</code><ul>
<li>Cilium 은 패킷이 커널에 들어오자마자 eBPF 프로그램을 통해 목적지 지정을 즉시 판별함. 복잡한 iptables를 거치지 않으니 패킷이 깨끗하고 가벼운 장점이 있음.</li>
</ul>
</li>
<li><code>Native Routing</code><ul>
<li>Cilium 은 설정을 통해 VXLAN 같은 무거운 포장 없이 물리망(L2/L3)에 직접 패킷을 태울 수 있음.</li>
<li>공유기 입장에서는 복잡한 캡슐화가 없는 정상적이고 가벼운 패킷으로 보이기 때문에 ARP 스푸핑 방지 로직에 걸리지 않고 통과됨</li>
</ul>
</li>
</ul>
<h2 id="📌-정리">📌 정리</h2>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/6282576f-9ac0-4c6c-bc5b-dada20ed7ae1/image.jpeg" alt=""></p>
<h3 id="calico">Calico</h3>
<ul>
<li>L3 초입에 위치하는 소프트웨어이며, iptables(2계층 이후 3~4계층에 걸쳐 위치) 까지 도착한 패킷을 검사함.</li>
<li>수천개의 iptable의 규칙을 조회하므로 많은 통신이 발생.</li>
<li>추가적으로 VXLAN, BGP 등의 통신 방법(주머니로 감쌈)을 사용하기 때문에 이 포장지를 벗기는 데에도 연산이 필요.</li>
</ul>
<h3 id="cilium">Cilium</h3>
<ul>
<li>L2 초입(XDP, TC)에 eBPF 프로그램으로 작성한 샌드박스 내에 위치한 소프트웨어.</li>
<li>eBPF 내에서 패킷을 가로채서 분석하고, 이때 3계층에서 쓰이는 ip와 4계층에서 쓰이는 port를 먼저 들여다보고 정상적인 패킷이라면 이후 계층에서 복잡한 검사를 하지 않고 바로 통과시킴(지능형 고속도로 역할).<ul>
<li>→ <code>Socket Redirection</code></li>
</ul>
</li>
</ul>
<h3 id="metallb">MetalLB</h3>
<ul>
<li>L2 레벨에서 ARP 수행.</li>
<li>CSP에서 제공하는 로드밸런서 기능을 L2 단계에서 제공함으로써 쿠버네티스 내에서 로드밸런서 기능을 지원하게 해줌.</li>
</ul>
<h3 id="트러블-슈팅의-원인과-해결">트러블 슈팅의 원인과 해결</h3>
<ul>
<li><p><code>원인 1)</code> Calico의 VXLAN 해석과 Iptables의  내의 수천개의 규칙 조회에 시간도 오래 걸리고 라즈베리파이 NIC에서 자체적으로 진행하는 체크섬 과정 중 오류가 발생하여 변조된 패킷 혹은 스푸핑 공격으로 오해함. 이때 가정용 공유기는 해당 통신을 사전에 정의된 L2 레벨에 적용되는 ARP 스푸핑 로직으로 걸러버림.</p>
</li>
<li><p><code>원인 2)</code> MetalLB의 ARP 광고는 L2 레벨에서 광고를 하는데, 이때 무분별한 광고를 공유기는 ARP 스푸핑으로 간주하기도 함. 그래서 세부 조정이 안되는 가정용 공유기는 MetalLB의 L2 단계에서의 로드밸런싱을 스푸핑으로 인식하고 패킷을 drop 함.</p>
</li>
<li><p><code>해결 1)</code> VXLAN, BGP 사용하지 않음.</p>
</li>
<li><p><code>해결 2)</code> Cilium은 L2 단계의 최전방(NIC 을 통과한 직후 XDP/TC)에 위치하며, 이곳에서는 흔한 개발 언어로는 프로그래밍을 할 수 없지만, 커널단에서 샌드박스 형식으로 프로그래밍을 할 수 있는 eBPF 기술을 사용하여 그 내부에 구축된 소프트웨어.</p>
</li>
<li><p>초입에서 eBPF로 패킷을 가로채서 확인을 하는데 이때 미리 패킷을 열어보아서 3계층(IP), 4계층(Port) 정보를 미리 확인, 판별 및 조회하고 정상적인 패킷이라면, 리눅스 커널의 <code>Socket Redirection</code> 기능을 사용</p>
<ul>
<li><code>Socket Redirection</code> : 표준네트워크 스택 연산 과정을 생략(Bypass)하고 파드의 소켓(Socket)으로 데이터를 직접 전달</li>
</ul>
</li>
</ul>
<h3 id="최종결론">최종결론</h3>
<ul>
<li>복잡하고 많은 연산을 하는 <code>Calico</code>와 가정용 공유기가 통과시키지 않는 L2 단계에서의 ARP 광고를 하는 <code>MetalLB</code>를 사용했을 때는 ‘연산시간 오래 걸림’, ‘타임아웃’으로 인해 변조된 패킷 혹은 ARP 스푸핑으로 간주 &amp; MetalLB의 작업을 ARP 스푸핑으로 간주하여 통신이 안 되었음.<ul>
<li>→ 쿠버네티스에서는 Egress까지 가는 모든 설정과 연결을 완료했지만 Ingress가 오는 통로에서 수많은 장애물과 역경이 있는 상황.</li>
</ul>
</li>
<li>Calico와 MetalLB를 버리고 <code>Cilium</code>을 사용하였는데 이는 2계층 초입에서 eBPF 기술을 사용하여 패킷을 가로채어 확인하고 지능형 고속도로로 바로 쏴버림.<ul>
<li>→ 오랜 연산시간이 필요하지 않고 가정용 공유기가 혼동하게끔 L2 광고를 하지 않기 때문에 Ingress에 필요한 모든 작업이 정상 수행됨.</li>
</ul>
</li>
</ul>
<h3 id="마무리며칠-밤을-새며-얻은-교훈">마무리(며칠 밤을 새며 얻은 교훈….)</h3>
<ul>
<li><code>가정용 네트워크 환경은 실제 현업 환경에 비해 매우 열악하다.</code><ul>
<li>BGP, L2 ARP, VXLAN 같은 고급 기술은 기업용 스위치가 없는 가정용 공유기 환경에서 언제든 깨질 수 있고, 원인 파악이 쉽지 않다…🥲</li>
</ul>
</li>
<li><code>가시성이 참말로 중요하다.</code><ul>
<li><code>tcpdump</code> 가 없었다면 ARP 단계에서 신호가 끊긴다는 사실을 파악할 수 없었을 것 같다.(<del>땡큐 제미나이..!!</del>)</li>
</ul>
</li>
<li><code>주어진 환경에서 가능한(실용적) 방법을 최대한으로 사용하기.</code><ul>
<li>LoadBalancer 설정을 해보고 싶었지만,, 관리자(나)가 구조를 완벽하게 파악할 수 있는 <code>공유기 포트포워딩 + NodePort</code> 조합이 좋은 것 같다. (<del>집에다가 고가 장비를 구축하기엔,,,</del>)</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Project] Multi-Tenant K8s Cluster on ARM64 - (8) External Access Point]]></title>
            <link>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-8-External-Access-Point</link>
            <guid>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-8-External-Access-Point</guid>
            <pubDate>Sun, 08 Mar 2026 04:58:25 GMT</pubDate>
            <description><![CDATA[<hr>
<blockquote>
<p>쿠버네티스 내부의 파드는 기본적으로 외부와 격리되어 있습니다. 관리자(나)는 각 테넌트 네임스페이스에 <code>MetalLB</code> 를 이용한 <code>LoadBalancer IP</code> 를 할당하고, 이를 통해 사용자가 할당받은 IP로 즉시 <code>SSH</code> 접속을 할 수 있는 환경을 구축해보고자 합니다. 이렇게 한다면 각 테넌트 입장에서 고유한 가상 서버를 제공받고, 사용하는 경험을 하게 됩니다. (초기 프로젝트 목표였던, 외부 테넌트에게 CSP처럼 인스턴스를 제공하는 부분의 핵심입니다!)</p>
</blockquote>
<h3 id="핵심-목표">핵심 목표</h3>
<ul>
<li><code>네트워크 독립성 확보</code> : 테넌트별로 고유한 내부망 IP(LoadBalancer IP) 를 할당하여 자원 간 간섭을 최소화</li>
<li><code>접속 편의성 극대화</code> : 22번 포트(SSH 전용)를 외부로 노출하여 접속 가능하도록 환경 구성</li>
<li><code>인프라 추상화</code> : 테넌트가 쿠버넽네티스 노드나 파드의 개념을 몰라도, 부여받은 IP 하나로 서버(인스턴스)처럼 활용할 수 있게 함</li>
</ul>
<hr>
<h1 id="1-기능-설명-테넌트별-전용-고정-ip-할당">1. [기능 설명] 테넌트별 전용 고정 IP 할당</h1>
<blockquote>
<p>사용자가 파드를 ‘하나의 독립된 가상 서버’처럼 느끼게 하기 위해 네트워크 접점을 제공</p>
</blockquote>
<h2 id="1-1-주요-설계-목표">1-1. 주요 설계 목표</h2>
<ul>
<li><code>1인 1 IP 제공</code><ul>
<li>MetalLB 의 IP 풀에서 테넌트당 하나의 외부 IP를 고정적으로 할당</li>
</ul>
</li>
<li><code>SSH 통로 개방</code><ul>
<li>80/443 포트가 아닌, SSH용 22번 포트를 외부로 노출하여 사용자가 터미널</li>
</ul>
</li>
<li><code>추상화 제거</code><ul>
<li>사용자는 본인의ㅏ 파드가 쿠버네티스 위에 있는지 몰라도 되며, 오직 부여받은 IP로만 소통<ul>
<li>독립된 서버 경험 제공</li>
<li>고정성(Persistence) - 파드가 재시작되거나 노드가 바뀌어도 사용자가 할당 받은 IP와 접속 포트는 변하지 않는 고정성 제공</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="1-2-필요한-기술적-장치">1-2. 필요한 기술적 장치</h2>
<ul>
<li><code>MetalLB (L2 Mode)</code><ul>
<li>별도의 라우터 설정 없이 ARP 응답 대행을 통해 가상 IP를 물리 네트워크에 바인딩 하는 엔진</li>
<li>클라우드의 LoadBalancer 서비스를 집 안의 홈랩 클러스터로 가져오는 핵심!!</li>
</ul>
</li>
<li><code>Service (Type: LoadBalancer)</code><ul>
<li>파드의 22번 포트를 <code>MetalLB</code>가 선점한 <code>사설 IP의 22번 포트</code>와 1:1로 매핑</li>
</ul>
</li>
<li><code>공유기 포트포워딩(Port Forwarding)</code><ul>
<li>외부 공인 IP의 특정 포트로 들어온 요청을 MetalLB가 할당한 사설 IP의 22번 포트로 전달하는 이정표</li>
</ul>
</li>
</ul>
<blockquote>
</blockquote>
<h2 id="📌-metallb-란">📌 MetalLB 란?</h2>
<blockquote>
</blockquote>
<ul>
<li>퍼블릭 클라우드에만 존재하는 LoadBalancer 기능을 일반 베어메탈 환경에서도 사용할 수 있게 해주는 네트워크 구현체<blockquote>
</blockquote>
<h3 id="✅-왜-사용하는가">✅ 왜 사용하는가??</h3>
<blockquote>
</blockquote>
</li>
<li>쿠버네티스에서 서비스의 <code>type: LoadBalancer</code> 는 원래 클라우드 환경을 위해 설계되었다고 합니다.<ul>
<li><code>클라우드</code>: LoadBalancer 를 생성하면 CSP가 자동으로 외부 IP를 할당하고 로드밸러서를 대신 띄워줌</li>
<li><code>홈랩</code> : 기본적으로 쿠버네티스는 외부 IP를 줄 수 있는 능력이 없다. 그래서 아무리 설정헤도 외부 IP가 <code>&lt;pending&gt;</code> 상태에 머물게 됨..</li>
<li>→ <code>해결책</code> : MetalLB 가 클러스터 내에서 가상의 로드밸런서 역할을 수행하며 외부 IP를 직접 할당하고 관리함<blockquote>
</blockquote>
<h3 id="✅-주요-작동-모드-l2-vs-bgp">✅ 주요 작동 모드: L2 vs BGP</h3>
<blockquote>
</blockquote>
(현재 사용 중인 모드는 L2)<blockquote>
</blockquote>
<code>L2 모드 (Layer 2)</code><blockquote>
</blockquote>
</li>
</ul>
</li>
<li>MetalLB가 클러스터 내에서 리더 노드(마스터 노드가 아닌, 특정 외부 IP를 책임지는 통신 대표!)를 선출하고, 해당 노드가 네트워크의 ARP 요청에 대신 응답하여 트래픽을 독점적으로 수신한 뒤 실제 파드로 전달하는 방식</li>
<li>장점: 특별한 네트워크 장비(관리형 스위치 등)가 필요 없다. 일반 가정 공유기에서도 잘 작동한다.(<del>홈랩의 한계 == 가정용 공유기/네트워크,,,ㅜ</del>)</li>
<li>단점: 특정 시점에 트래픽이 한 대의 노드로만 몰릴 수 있기 때문에 대역폭의 한계가 있을 수 있음<blockquote>
</blockquote>
<code>BGP 모드 (Layer 3)</code><blockquote>
</blockquote>
(데이터센터 급(?)의 설정을 할 때 사용한다고 합니다.)<blockquote>
</blockquote>
</li>
<li>원리: 라우터와 클러스터가 BGP 프로토콜로 직접 통신하며 경로를 설정함</li>
<li>장점: 여러 노드로 트래픽을 분산시킬 수 있음</li>
<li>단점: BGP를 지원하는 고가의 라우터 장비가 필요함<blockquote>
</blockquote>
<h3 id="✅-핵심-구성-요소">✅ 핵심 구성 요소</h3>
<blockquote>
</blockquote>
</li>
<li><code>Controller</code> : IP 할당을 담당. 서비스가 생성되면 기존에 설정한 <code>IPAddressPool</code> 에서 남는 IP를 찾아 서비스에 매칭시킴</li>
<li><code>Speaker</code> : 네트워크 통신을 담당. L2 모드에서는 각 노드에서 실행되며 <code>광고(Announce)</code>를 수행함.<blockquote>
</blockquote>
<h3 id="⚠️-주의사항">⚠️ 주의사항</h3>
<blockquote>
</blockquote>
</li>
<li>할당하려는 IP 대역은 반드시 공유기의 DHCP 할당 범위와 겹치지 않아야 함!!! (IP 충돌 방지)</li>
</ul>
<h1 id="2-기능-개발-metallb-설치-및-전용-ip-부여">2. [기능 개발] MetalLB 설치 및 전용 IP 부여</h1>
<h2 id="2-1-metallb-설치-컨트롤러-배포">2-1. MetalLB 설치 (컨트롤러 배포)</h2>
<ul>
<li>IP를 관리하고 ARP 응답을 대신해 줄 MetalLB 시스템을 클러스터에 올림</li>
</ul>
<pre><code class="language-bash"># MetalLB 네이티브 매니페스트 적용
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-native.yaml

# 설치 확인
kubectl get pods -n metallb-system</code></pre>
<h2 id="2-2-ip-주소-범위-지정-ipaddresspool">2-2. IP 주소 범위 지정 (IPAddressPool)</h2>
<ul>
<li><code>metallb-config.yaml</code></li>
</ul>
<pre><code class="language-yaml">apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: tenant-ip-pool
  namespace: metallb-system
spec:
  # 공유기 대역 내에서 안전하게 비어있는 구간 50개 설정
  addresses:
  - 192.168.55.150-192.168.55.199
  # 자동으로 꺼내 쓸 수 있도록 설정
  autoAssign: true
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: l2-adv
  namespace: metallb-system
spec:
  # 위에서 만든 pool을 실제로 네트워크에 광고(Announce)함
  ipAddressPools:
  - tenant-ip-pool</code></pre>
<h2 id="2-3-테넌트용-ssh-서비스-생성-external-access">2-3. 테넌트용 SSH 서비스 생성 (External Access)</h2>
<ul>
<li>테넌트 파드를 외부 IP와 연결</li>
<li>테넌트가 생성될 때마다 하나씩 만들어 줌</li>
<li><code>tenant-alpha-ssh-service.yaml</code></li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: external-access
  namespace: tenant-alpha
spec:
  selector:
    role: server
  ports:
  - protocol: TCP
    port: 22
    targetPort: 22
  type: LoadBalancer</code></pre>
<h2 id="2-4-포트포워딩">2-4. 포트포워딩</h2>
<blockquote>
<p>네트워크 관리자 페이지에서 테넌트 서버의 IP로 사용할 주소를 사전에 포트포워딩 해줌</p>
</blockquote>
<h1 id="3-검증-테스트-자동-할당-및-ssh-접속-확인">3. [검증 테스트] 자동 할당 및 SSH 접속 확인</h1>
<h2 id="3-1-ip-할당-확인">3-1. IP 할당 확인</h2>
<pre><code class="language-bash">kubectl get svc -n tenant-alpha</code></pre>
<ul>
<li>→ <code>EXTERNAL -IP</code> 항목이 <code>&lt;pending&gt;</code> 에서 <code>192.168.55.150</code> (첫 번째 IP) 으로 바뀌면 성공</li>
</ul>
<h3 id="flow-정리">Flow 정리</h3>
<blockquote>
<p>트래픽 경로)
<code>외부 클라이언트</code> → <code>공유기(포트포워딩)</code> → <code>MetalLB 할당 IP</code> → <code>Service(LB)</code> → <code>Pod</code></p>
</blockquote>
<table>
<thead>
<tr>
<th>구분</th>
<th>명칭</th>
<th>예시</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>접속 IP</td>
<td>공인 IP (Public)</td>
<td>218.x.x.x</td>
<td>외부 인터넷에서 우리 집을 찾는 주소</td>
</tr>
<tr>
<td>접속 포트</td>
<td>외부 포트 (Outer)</td>
<td>50111</td>
<td>공유기 대문에서 테넌트를 구분하는 포트 번호</td>
</tr>
<tr>
<td>내부 IP</td>
<td>MetalLB 할당 IP</td>
<td>192.168.x.x</td>
<td>클러스터가 외부 네트워크와 소통하는 사설 주소</td>
</tr>
<tr>
<td>내부 포트</td>
<td>서비스 포트 (Port)</td>
<td>22</td>
<td>MetalLB가 외부에 노출한 포트</td>
</tr>
<tr>
<td>타겟 포트</td>
<td>파드 포트 (Target)</td>
<td>22</td>
<td>실제 파드 내부의 SSH 서비스 번호</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Project] Multi-Tenant K8s Cluster on ARM64 - (7) Zero-Trust Network Policy - 2]]></title>
            <link>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-7-Zero-Trust-Network-Policy-2</link>
            <guid>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-7-Zero-Trust-Network-Policy-2</guid>
            <pubDate>Sun, 08 Mar 2026 03:42:18 GMT</pubDate>
            <description><![CDATA[<hr>
<h1 id="1-기능-설명-egress-제어와-dns-화이트리스팅">1. [기능 설명] Egress 제어와 DNS 화이트리스팅</h1>
<h2 id="1-1-주요-설계-목표">1-1. 주요 설계 목표</h2>
<ul>
<li><code>Egress All Deny</code> - 나가는 문(Egress)을 모두 잠그기<ul>
<li>쿠버네티스의 기본 설정은 모든 파드가 외부와 자유롭게 통신할 수 있는 ‘Open’ 상태임/</li>
<li>테넌트 환경에서는 보안 사고 방지를 위해 <code>화이트리스트</code> 전략이 필수임</li>
</ul>
</li>
<li><code>DNS 허용 (Essential)</code> - 이름(DNS)을 찾는 통로만 개방<ul>
<li>인터넷을 막더라도 클러스터 내부의 서비스끼리 통신하거나, 외부 주소의 IP를 알아내기 위해서는 <code>CoreDNS</code> 와의 통신이 반드시 필요함<ul>
<li><strong>[왜 DNS만 허용을 하는가?]</strong><ul>
<li>모든 트래픽을 막으면 파드 내부에서 apt-get 이나 curl 명령을 입력했을 때 도메인 해석부터 실패하여 서비스 운영이 불가능해짐</li>
</ul>
</li>
<li><strong>[최소 권한의 원칙]</strong><ul>
<li>무조건적인 통신이 아니라, 특정 포트<code>[UDP/TCP 53]</code> 만 개방하는 것이 핵심</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><code>특정 외부망 허용</code> - 신뢰할 수 있는 통로만 선별<ul>
<li>모든 인터넷을 차단하더라도, 특정 업무를 위해 반드시 필요한 외부 API나 업데이트 서버는 열어주어야 함</li>
</ul>
</li>
</ul>
<blockquote>
</blockquote>
<h2 id="📌-ingress--egress-란">📌 Ingress &amp; Egress 란?</h2>
<blockquote>
</blockquote>
<h3 id="ingress-안으로-들어오는-길">Ingress: 안으로 들어오는 길</h3>
<blockquote>
</blockquote>
<ul>
<li>클러스터 외부에서 내부의 서비스(파드)로 <code>들어오는 트래픽</code>을 의미</li>
<li><code>[Ingress Policy]</code><ul>
<li>어떤 외부 IP가 우리 서버에 접속할 수 있는지 제어</li>
<li>특정 포트만 열어두고 나머지는 닫는 설정을 함</li>
</ul>
</li>
<li>설정 예시<ul>
<li>NodePort, LoadBalancer, Ingress<blockquote>
</blockquote>
<h3 id="egress-밖으로-나가는-길">Egress: 밖으로 나가는 길</h3>
<blockquote>
</blockquote>
</li>
</ul>
</li>
<li>클러스터 내부의 파드에서 외부(다른 노드, 다른 네임스페이스, 혹은 인터넷)로 <code>나가는 트래픽</code>을 의미</li>
<li><code>[Egress Policy]</code><ul>
<li>Zero-Trust 의 핵심: 허락 없이 외부로 데이터를 보내는 것을 막음</li>
<li>기본적으로 모든 출구를 막고(All Deny), 꼭 필요한 통로만 열어줌</li>
</ul>
</li>
<li>설정 예시<ul>
<li>NetworkPolicy (Egress rule)<blockquote>
</blockquote>
<h3 id="egress-의-중요-포인트">Egress 의 중요 포인트</h3>
<blockquote>
</blockquote>
</li>
</ul>
</li>
<li>공격자의 경로 차단</li>
<li>내부 전파 방지(Lateral Movement)</li>
<li>네트워크 비용 및 리소스 낭비 방지</li>
</ul>
<h1 id="2-기능-개발-egress-격리-정책-작성">2. [기능 개발] Egress 격리 정책 작성</h1>
<ul>
<li><code>tenant-alpha</code> 내의 모든 파드가 오직 <code>DNS</code> 만 가능하고 나머지는 모두 차단되도록 설정</li>
</ul>
<h2 id="2-1-egress-격리-정의-tenant-egress-policyyaml">2-1. Egress 격리 정의 (tenant-egress-policy.yaml)</h2>
<pre><code class="language-yaml">apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-and-deny-egress
  namespace: tenant-alpha
spec:
  podSelector: {} # 테넌트 내 모든 파드에 적용
  policyTypes:
  - Egress
  egress:
  # 1. 클러스터 내부 DNS(CoreDNS) 통신 허용
  - to:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: kube-system
      podSelector:
        matchLabels:
          k8s-app: kube-dns
    ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53</code></pre>
<h1 id="3-검증-테스트-차단-및-허용-확인">3. [검증 테스트] 차단 및 허용 확인</h1>
<h2 id="3-1-정책-적용">3-1. 정책 적용</h2>
<pre><code class="language-bash">kubectl apply -f tenant-egress-policy.yaml</code></pre>
<h2 id="3-2-외부-인터넷-차단-확인-egress-deny-테스트">3-2. 외부 인터넷 차단 확인 (Egress Deny 테스트)</h2>
<p>시나리오: <code>tenant-alpha</code> 내부 파드에서 8.8.8.8 로 직접 통신을 시도</p>
<pre><code class="language-bash">kubectl exec local-tester -n tenant-alpha -- nc -zv 8.8.8.8 53 -w 2</code></pre>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/d63e9fa9-5d1a-4b0d-a473-874b6ebb0123/image.png" alt=""></p>
<ul>
<li>결과 → <code>nc: 8.8.8.8 (8.8.8.8:53): Connection timed out</code> 출력</li>
</ul>
<h2 id="3-3-내부-dns-해석-확인-화이트리스트-테스트">3-3. 내부 DNS 해석 확인 (화이트리스트 테스트)</h2>
<p>시나리오: 외부 서버의 IP는 몰라도 DNS lookup은 성공해야 함</p>
<pre><code class="language-bash">kubectl exec local-tester -n tenant-alpha -- nslookup google.com</code></pre>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/dce5c831-2c8c-40cd-adea-2b7604970ede/image.png" alt=""></p>
<ul>
<li>결과 → 구글의 IP 주소들이 정상적으로 출력</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Project] Multi-Tenant K8s Cluster on ARM64 - (6) CNI 설정 - Calico 사용
]]></title>
            <link>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-6-CNI-%EC%84%A4%EC%A0%95-Calico-%EC%82%AC%EC%9A%A9</link>
            <guid>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-6-CNI-%EC%84%A4%EC%A0%95-Calico-%EC%82%AC%EC%9A%A9</guid>
            <pubDate>Fri, 06 Mar 2026 09:29:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이전 포스팅에서 Network Policy 적용을 위해 Calico를 설치하는 과정 중, 기존에 사용하던 Flannel 과 충돌하여 파드 생성에 실패하는 오류가 발생했습니다.
이번 포스팅에서는 CNI의 개념을 다시 한 번 짚어보고, 구체적인 문제 원인 파악과 함께 Calico 로의 마이그레이션 과정을 다시 한 번 정리해보겠습니다..(ㅜ…)</p>
</blockquote>
<hr>
<h1 id="📌-쿠버네티스-네트워크의-심장-cnicontainer-network-interface">📌 쿠버네티스 네트워크의 심장, CNI(Container Network Interface)</h1>
<h2 id="cni란"><code>CNI란?</code></h2>
<ul>
<li>쿠버네티스는 자체적으로 파드 간의 네트워크를 구축하는 기능을 가지고 있지 않음</li>
<li>대신 <code>CNI</code> 라는 표준 규격을 만들어 놓고, 네트워크 구현은 플러그인에게 맡김</li>
<li><code>핵심 역할</code> : 파드가 생성될 때 가상 네트워크 인터페이스를 할당하고, 고유한 IP를 부여하며, 파드 간 통신 경로(Router)를 생성</li>
</ul>
<h1 id="⚠️-flannel-에서-에러가-발생한-이유">⚠️ Flannel 에서 에러가 발생한 이유</h1>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/f22fb191-564f-4356-a0bf-87b5b8d68f2a/image.png" alt=""></p>
<pre><code class="language-bash">Warning  FailedCreatePodSandBox  49m                    kubelet            Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox &quot;7a46363082ddde997807d803e54195e8a5b6d3fc96eab219318c78e6bb6aa93c&quot;: plugin type=&quot;flannel&quot; failed (add): failed to load flannel &#39;subnet.env&#39; file: open /run/flannel/subnet.env: no such file or directory. Check the flannel pod log for this node.</code></pre>
<ul>
<li><code>/run/flannel/subnet.env</code> 파일이 없다는 에러 발생…!</li>
</ul>
<h3 id="상태-불일치"><code>상태 불일치</code></h3>
<ul>
<li>Flannel 설정 파일은 노드에 남아있는데, 정작 네트워크 일꾼 역할을 하는 Flannel Pod는 클러스터에 배포되지 않았거나 삭제된 상태</li>
<li>Kubelet 관점) CNI 설정 파일은 존재하기 때문에 Flannel을 호출하지만, 정작 일을 해야 할 Flannel 프로세스가 없어 발생하는 MissMatch 상황..!</li>
</ul>
<h3 id="sandbox-생성-실패"><code>Sandbox 생성 실패</code></h3>
<ul>
<li>쿠버네티스는 파드를 만들기 전 Sandbox를 먼저 만드는데, CNI가 응답하지 않으니 파드 생성 자체가 멈춘 상황</li>
</ul>
<h3 id="격리-기능의-한계"><code>격리 기능의 한계</code></h3>
<ul>
<li>Flannel은 단순 통신에는 강하지만, 현재 작업 중인 <code>Network Policy(특정 네임스페이스 차단)</code> 기능을 지원하지 않음.<ul>
<li>설계 자체가 통로만 뚫어주는 구조이기 때문</li>
</ul>
</li>
</ul>
<h1 id="cni-도구-비교-flannel-vs-calico-vs-cilium">CNI 도구 비교: Flannel vs Calico vs Cilium</h1>
<table>
<thead>
<tr>
<th></th>
<th>Flannel</th>
<th>Calico</th>
<th>Cilium</th>
</tr>
</thead>
<tbody><tr>
<td>핵심 기술</td>
<td>VXLAN (터널링)</td>
<td>IPTables / BGP</td>
<td>eBPF</td>
</tr>
<tr>
<td>보안 정책</td>
<td>불가능 (차단 안 됨)</td>
<td>강력함 (L3/L4 격리)</td>
<td>매우 강력함 (L7/API 격리 가능)</td>
</tr>
<tr>
<td>복잡도</td>
<td>매우 낮음</td>
<td>중간</td>
<td>높음</td>
</tr>
<tr>
<td>적절한 상황</td>
<td>단순 학습용</td>
<td>멀티 테넌시/엔터프라이즈</td>
<td>고성능/대규모/관제 중심</td>
</tr>
</tbody></table>
<blockquote>
<p>현재 프로젝트에서는 <code>Calico</code>를 사용해보겠습니다.</p>
</blockquote>
<h1 id="calico-cni-설치-및-전환">Calico CNI 설치 및 전환</h1>
<ul>
<li>기존의 꼬인 설정(Flannel 관련)을 삭제하고 새롭게 네트워크를 구축하는 과정</li>
</ul>
<h2 id="1-구형-flannel-설정-제거">1. 구형 Flannel 설정 제거</h2>
<pre><code class="language-bash"># 모든 노드(Master, Worker)에서 실행
sudo rm /etc/cni/net.d/10-flannel.conflist</code></pre>
<h2 id="2-calico-operator">2. Calico Operator</h2>
<ul>
<li><code>Operator</code>는 Calico의 컴포넌트들을 자동으로 관리해주는 네트워크 관리자 파드</li>
</ul>
<pre><code class="language-bash">kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/tigera-operator.yaml</code></pre>
<h2 id="3-클러스터-맞춤형-자원cr-배포">3. 클러스터 맞춤형 자원(CR) 배포</h2>
<ul>
<li>클러스터의 <code>Pod CIDR(주소 대역)</code> 에 맞춰 설정을 주입해야 함.</li>
<li>현재 라즈베리파이 홈랩에서는 <code>10.244.0.0/16</code> 을 사용</li>
</ul>
<pre><code class="language-bash">cat &lt;&lt;EOF | kubectl apply -f -
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
  name: default
spec:
  calicoNetwork:
    ipPools:
    - blockSize: 26
      cidr: 10.244.0.0/16
      encapsulation: VXLANCrossSubnet
      natOutgoing: Enabled
      nodeSelector: all()
EOF</code></pre>
<blockquote>
</blockquote>
<h2 id="📌-calico-인데-vxlan을-사용하는-이유">📌 Calico 인데 VXLAN을 사용하는 이유?</h2>
<blockquote>
</blockquote>
<p>Calico 는 기본적으로 BGP 라우팅을 권장하지만, 네트워크 환경에 따라 VXLAN 터널링 모드도 완벽하게 지원한다고 합니다.</p>
<blockquote>
</blockquote>
<p>설정의 편의성과 범용성을 고려하여 VXLAN 모드를 채택했습니다. VXLAN 방식을 사용하더라도, Calico를 사용하는 가장 큰 목적인 ‘L3/L4 Network Policy’ 기능은 그대로 활용할 수 있기에 적용해보았습니다.</p>
<blockquote>
</blockquote>
<p>(VXLAN, BGP, eBPF 에 대한 자세한 내용은 학습 후 정리해봐야겠습니다…. <del>넘 어렵습니다…ㅜ</del>)</p>
<h1 id="zero-trust-네트워크-격리-테스트">Zero-Trust 네트워크 격리 테스트</h1>
<ul>
<li>허가받은 내부 통신은 허용하고, 허가받지 않은 외부 접근은 차단하는지 검증</li>
</ul>
<h2 id="1-테스트-환경-준비">1. 테스트 환경 준비</h2>
<ul>
<li>네트워크 교체(Calico) 과정에서 파드 재시작이 되었을 수 있으니 현재 타겟 파드의 IP를 변수에 저장</li>
</ul>
<pre><code class="language-bash">TARGET_IP=$(kubectl get pod limit-test-pod -n tenant-alpha -o jsonpath=&#39;{.status.podIP}&#39;)

# 확인
echo &quot;Target Pod IP: $TARGET_IP&quot;</code></pre>
<h2 id="2-case-1-내부-통신-테스트-intra-namespace">2. [Case 1] 내부 통신 테스트 (Intra-Namespace)</h2>
<ul>
<li>시나리오: 같은 네임스페이스(현재 <code>tenant-alpha</code>)에 속한 파드끼리는 통신이 가능해야 함</li>
</ul>
<pre><code class="language-bash">kubectl exec local-tester -n tenant-alpha -- wget -qO- --timeout=2 $TARGET_IP</code></pre>
<ul>
<li>결과</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/3a0c91b9-ea35-44c7-a9cb-a8fc162a7c00/image.png" alt=""></p>
<ul>
<li>이전에 작성해둔 Nginx 의 Welcome HTML 소스코드가 출력됨 (→ 정상 작동)</li>
<li><code>NetworkPolicy</code> 의 <code>from.podSelector: {}</code> 규칙이 동일 네임스페이스 내의 트래픽을 <code>화이트리스트</code>로 승인</li>
</ul>
<h2 id="3-case-2-외부-침입-테스트-inter-namespace">3. [Case 2] 외부 침입 테스트 (Inter-Namespace)</h2>
<ul>
<li>시나리오: 다른 네임스페이스(<code>default</code>)에서 <code>tenant-alpha</code> 로의 접근은 차단되어야 함</li>
</ul>
<pre><code class="language-bash">kubectl run intruder -n default --image=busybox --restart=Never -it --rm -- wget -qO- --timeout=5 $TARGET_IP</code></pre>
<ul>
<li>결과</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/854174de-69c9-4be6-9b86-cee54e8fdfc8/image.png" alt=""></p>
<ul>
<li><code>wget: download timed out</code> (→ 정상 작동)</li>
<li>정책에 정의되지 않은 외부 네임스페이스의 패킷을 Calico가 생성한 iptables 규칙에 의해 패킷이 커널 레벨에서 Drop 됨</li>
</ul>
<p>.</p>
<p>.</p>
<p>.</p>
<p>→ Calico 설치 완료! 😋</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Project] Multi-Tenant K8s Cluster on ARM64 - (5) Zero-Trust Network Policy - 1]]></title>
            <link>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-5-Zero-Trust-Network-Policy-1</link>
            <guid>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-5-Zero-Trust-Network-Policy-1</guid>
            <pubDate>Fri, 06 Mar 2026 08:12:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li>현재 클러스터는 자원은 분리되어 있지만, 네트워크는 여전히 모두가 모두와 대화할 수 있는 상태</li>
</ul>
</blockquote>
<ul>
<li>이를 해결하기 위해 <code>Zero-Trust 기반 Network Policy 설정</code>을 진행</li>
</ul>
<hr>
<h1 id="1-기능-설명-network-isolation">1. [기능 설명] Network Isolation</h1>
<ul>
<li>쿠버네티스의 기본 네트워크 구조는 <code>Flat Network</code><ul>
<li>네임스페이스가 달라도 서로의 IP만 알면 통신이 가능한 구조</li>
</ul>
</li>
<li><code>Network Policy</code> 는 파드로 드나드는 트래픽을 제어하는 L3/L4 방화벽 역할을 수행</li>
</ul>
<h2 id="1-1-주요-설계-목표">1-1. 주요 설계 목표</h2>
<ul>
<li><code>테넌트 간 통신 차단</code> : <code>tenant-alpha</code> 네임스페이스에 속한 파드들은 다른 테넌트 네임스페이스의 파드와 통신할 수 없어야 함</li>
<li><code>내부 통신만 허용 (Intra-Namespace)</code> : 같은 네임스페이스 안에 있는 파드들끼리는 자유롭게 데이터를 주고받을 수 있게 함</li>
</ul>
<blockquote>
<p>구조가 헷갈릴 수 있으나 정리를 하자면,
외부 트래픽은 막고(테넌트 간 통신 차단) - 내부 파드끼리는 통신 가능한(내부 통신만 허용) 구조입니다.</p>
</blockquote>
<h2 id="1-2-필요한-쿠버네티스-기술-객체">1-2. 필요한 쿠버네티스 기술 객체</h2>
<h3 id="networkpolicy"><code>NetworkPolicy</code></h3>
<ul>
<li>어떤 트래픽을 허용(<code>Allow</code>) 할지 정의하는 규칙<ul>
<li>쿠버네티스 네트워크 정책은 기본적으로 <code>허용되지 않은 모든 것은 거부(Deny)</code> 하는 방식으로 작동함 → <code>Default Deny 모델</code></li>
</ul>
</li>
</ul>
<blockquote>
</blockquote>
<h3 id="📌-cnicoontainer-network-interface-호환성">📌 CNI(Coontainer Network Interface) 호환성</h3>
<blockquote>
</blockquote>
<ul>
<li>설정이 실제로 동작하려면 클러스터에 Network Policy 를 지원하는 CNI 가 설치되어 있어야 함</li>
<li><code>확인 사항</code> : 라즈베리파이에서 주로 사용하는 <code>Flannel</code>(현재 사용 중)은 기본적으로 Network Policy를 지원하지 않음..<ul>
<li><code>Calico</code>나 <code>Cilium</code>, 또는 Flannel 위에 <code>Canal</code>을 얹어서 사용 중이라면 정책이 즉시 반영됨.</li>
</ul>
</li>
<li><code>동작 원리</code> : 정책을 적용하는 순간, 해당 파드와 연결된 가상 인터페이스에 <code>iptables</code> 혹은 <code>eBPF</code> 규칙이 생성되어 패킷을 필터링 함</li>
<li>-&gt; 우선 <code>Flannel</code> 에서 <code>Calico</code> 로 CNI 설정을 변경하도록 하겠습니다.</li>
</ul>
<h1 id="2-기능-개발-테넌트-간-격리-정책-작성">2. [기능 개발] 테넌트 간 격리 정책 작성</h1>
<ul>
<li><code>tenant-alpha</code> 에 동일 네임스페이스가 아니면 다 차단하는 정책을 적용</li>
</ul>
<h2 id="2-1-네임스페이스-레이블링">2-1. 네임스페이스 레이블링</h2>
<ul>
<li>네트워크 정책에서 네임스페이스를 구분하려면 각 네임스페이스에 <code>Label</code>(이름표 역할)이 붙어 있어야 함</li>
</ul>
<pre><code class="language-bash"># tenant-alpha 네임스페이스에 label 부착
kubectl label namespace tenant-alpha ns-name=tenant-alpha</code></pre>
<h2 id="2-2-네트워크-격리-정의-tenant-network-policyyaml">2-2. 네트워크 격리 정의 (tenant-network-policy.yaml)</h2>
<pre><code class="language-yaml">apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-other-namespaces
  namespace: tenant-alpha
spec:
  podSelector: {} # {}는 이 네임스페이스 내의 모든 파드를 의미함
  policyTypes:
  - Ingress # 들어오는 트래픽 제어
  ingress:
  - from:
    - podSelector: {} # 동일 네임스페이스 내의 모든 파드로부터의 접속 허용
    - namespaceSelector:
        matchLabels:
          ns-name: tenant-alpha # 본인의 네임스페이스에서 오는 것만 허용</code></pre>
<ul>
<li><code>podSelector: {}</code> 만 단독으로 있으면 ‘해당 네임스페이스 내의 모든 파드’를 의미</li>
</ul>
<h1 id="3-검증-테스트-차단-확인-절차">3. [검증 테스트] 차단 확인 절차</h1>
<h2 id="3-1-정책-적용">3-1. 정책 적용</h2>
<pre><code class="language-bash">kubectl apply -f tenant-network-policy.yaml</code></pre>
<h2 id="3-2-테넌트-내-통신-intra-namespace">3-2. 테넌트 내 통신 (Intra-Namespace)</h2>
<ul>
<li>시나리오: <code>tenant-alpha</code> 네임스페이스 내부의 <code>local-tester</code> 파드에서 웹 서버(<code>limit-test-pod</code>)로 접속을 시도</li>
</ul>
<pre><code class="language-bash"># 1. 대상 파드의 최신 IP 확인
TARGET_IP=$(kubectl get pod limit-test-pod -n tenant-alpha -o jsonpath=&#39;{.status.podIP}&#39;)

# 2. 내부 통신 시도 (성공 시 HTML 코드 출력)
kubectl exec local-tester -n tenant-alpha -- wget -qO- --timeout=2 $TARGET_IP</code></pre>
<p>→ Nginx의 Welcome 메시지가 담긴 HTML 소스코드가 출력</p>
<h2 id="3-3-테넌트-간-통신-inter-namespace--침입-테스트">3-3. 테넌트 간 통신 (Inter-Namespace / 침입 테스트)</h2>
<ul>
<li>시나리오: <code>default</code> 네임스페이스에 임시 파드를 생성하여 <code>tenant-alpha</code> 내부의 웹 서버로 무단 접속을 시도</li>
</ul>
<pre><code class="language-bash"># 외부(default NS)에서 침입 시도 (차단 시 Timeout 발생)
kubectl run intruder -n default --image=busybox --restart=Never -it --rm -- wget -qO- --timeout=5 $TARGET_IP</code></pre>
<p>→ <code>wget: download timed out</code> 메시지 출력 (격리 성공!)</p>
<blockquote>
<p><code>Connection Refused</code> 가 아닌 <code>Timeout</code> 이 발생하는 이유는, Network Policy 가 패킷이 들어오는 단계에서 조용히 드롭시키기 때문이다..</p>
</blockquote>
<blockquote>
</blockquote>
<h2 id="📌-connection-refused-vs-timeout">📌 Connection Refused vs. Timeout</h2>
<blockquote>
</blockquote>
<ul>
<li><code>Connection Refused</code><ul>
<li>서버까지 패킷은 도달했으나, 포트가 닫혀 있어 운영체제가 거절 응답(RST)을 보낸 경우</li>
</ul>
</li>
<li><code>Timeout</code><ul>
<li>패킷이 방화벽(Network Policy)에 의해 입구에서 소멸(Drop)되어 서버가 패킷의 존재조차 모르는 상태.</li>
<li>Zero-Trust 관점에서는 내부 정보를 일절 노출하지 않는 이 방식이 더 안전하다..</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Project] Multi-Tenant K8s Cluster on ARM64 - (4) Dynamic Resource Quota - CPU/Memory
]]></title>
            <link>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-4-Dynamic-Resource-Quota-CPUMemory</link>
            <guid>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-4-Dynamic-Resource-Quota-CPUMemory</guid>
            <pubDate>Fri, 06 Mar 2026 03:34:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>지난번에 스토리지에 대한 제한 및 격리를 진행했습니다. 이번에는 CPU와 RAM에 대한 제한과 격리 작업을 진행해보겠습니다.</p>
</blockquote>
<hr>
<h1 id="1-기능-설명-cpuram-격리-및-할당">1. [기능 설명] CPU/RAM 격리 및 할당</h1>
<ul>
<li>테넌트가 생성하는 파드들이 사용하는 물리적인 연산 자원(CPU/RAM)을 제한하는 기능</li>
</ul>
<h2 id="1-1-주요-설계-목표">1-1. 주요 설계 목표</h2>
<ul>
<li><code>안전한 멀티 테넌시</code> : 특정 테넌트의 코드가 무한 루프에 빠져 CPU를 점유하거나, 메모리 누수가 발생해도 다른 테넌트와 마스터 노드의 동작에 영향을 주지 않아야 함</li>
<li><code>사양 정의</code> : t2.micro 인스턴트처럼 1 vCPU, 1GiB RAM 수준의 리소스 프로파일을 테넌트 네임스페이스에 강제함</li>
</ul>
<h2 id="1-2-필요한-쿠버네티스-기술-객체">1-2. 필요한 쿠버네티스 기술 객체</h2>
<h3 id="resourcequota-총량"><code>ResourceQuota (총량)</code></h3>
<ul>
<li>네임스페이스 전체에서 돌아가는 모든 파드들의 자원 합계를 제한함<ul>
<li>→ 비유: 지갑(네임스페이스)의 총액(네임스페이스가 갖는 리소스) 제한</li>
</ul>
</li>
</ul>
<h3 id="limitrange-개별"><code>LimitRange (개별)</code></h3>
<ul>
<li>파드 하나가 가질 수 있는 최소/최대 자원 크기를 결정</li>
<li>테넌트가 자원 설정을 누락하고 파드를 띄울 때 자동으로 적용될 기본값을 부여<ul>
<li>→ 비유: 지갑(네임스페이스) 안에서 꺼내 쓰는 지폐 한 장(파드가 갖는 리소스)의 단위 제한</li>
</ul>
</li>
</ul>
<blockquote>
</blockquote>
<h3 id="📌-자원-제한의-기준-requests-와-limits">📌 자원 제한의 기준 Requests 와 Limits</h3>
<blockquote>
<p>💡 라즈베리파이는 저전력 장치이기에,, 특정 테넌트에서 OOM이 발생하게 될 경우 노드 전체가 멈추는 Freeze 상황에 놓이기 쉽습니다. 때문에 <code>limits.memory</code> 설정이 필수적입니다!</p>
</blockquote>
<ul>
<li><code>Requests (보장량)</code><ul>
<li>파드가 실행되기 위해 최소한으로 필요한 자원</li>
<li>K8s 스케줄러는 노드에 보장량 만큼의 여유 공간이 있을 때만 파드를 배치함<blockquote>
</blockquote>
</li>
</ul>
</li>
<li><code>Limits (상한선)</code><ul>
<li>파드가 최대로 사용할 수 있는 자원</li>
<li><code>CPU</code>: Limit을 넘으면 스로틀링(Throttling)이 발생하여 속도가 느려지지만 파드가 죽지는 않음</li>
<li><code>Memory</code>: Limit 을  넘으면 커널이 파드를 즉시 종료시킴.(OOM Kill)<ul>
<li>→ Memory 는 압축할 수 없는 자원이기 때문</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="2-기능-개발-연산-자원-쿼터-및-제한-매니페스트-작성">2. [기능 개발] 연산 자원 쿼터 및 제한 매니페스트 작성</h1>
<h2 id="2-1-연산-자원-총량-정의-tenant-compute-quotayaml">2-1. 연산 자원 총량 정의 (tenant-compute-quota.yaml)</h2>
<ul>
<li>네임스페이스 전체의 장부 역할</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-quota
  namespace: tenant-alpha
spec:
  hard:
    requests.cpu: &quot;500m&quot;      # 0.5 코어 보장
    requests.memory: &quot;512Mi&quot;  # 512MiB 보장
    limits.cpu: &quot;1000m&quot;       # 최대 1 코어 (1000m = 1 vCPU)
    limits.memory: &quot;1Gi&quot;      # 최대 1GiB (t2.micro 급)</code></pre>
<h2 id="2-2-기본-규격-강제-정의-tenant-compute-limityaml">2-2. 기본 규격 강제 정의 (tenant-compute-limit.yaml)</h2>
<ul>
<li>테넌트가 자원 설정을 누락하더라도 기본값을 넣어주는 설정</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: LimitRange
metadata:
  name: compute-limit-range
  namespace: tenant-alpha
spec:
  limits:
  - type: Container
    default:                 # Limit 미설정 시 자동 부여
      cpu: &quot;500m&quot;
      memory: &quot;512Mi&quot;
    defaultRequest:          # Request 미설정 시 자동 부여
      cpu: &quot;200m&quot;
      memory: &quot;256Mi&quot;
    max:                     # 개별 컨테이너가 가질 수 있는 최대치
      cpu: &quot;800m&quot;
      memory: &quot;800Mi&quot;</code></pre>
<h1 id="3-검증-테스트-쿼터-및-제한-적용-후-검증">3. [검증 테스트] 쿼터 및 제한 적용 후 검증</h1>
<ul>
<li>매니페스트를 적용한 후, 테넌트가 자원을 지정하지 않고 파드를 생성했을 때 관리자가 정한 기본값이 정상적으로 주입되는지 확인</li>
</ul>
<h2 id="3-1-매니페스트-적용">3-1. 매니페스트 적용</h2>
<pre><code class="language-yaml">kubectl apply -f tenant-compute-quota.yaml
kubectl apply -f tenant-compute-limit.yaml</code></pre>
<h2 id="3-2-테스트용-파드-생성-test-podyaml">3-2. 테스트용 파드 생성 (test-pod.yaml)</h2>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: limit-test-pod
  namespace: tenant-alpha
spec:
  containers:
  - name: nginx
    image: nginx</code></pre>
<ul>
<li>실행</li>
</ul>
<pre><code class="language-yaml">kubectl apply -f test-pod.yaml</code></pre>
<h2 id="3-3-주입된-값-검증">3-3. 주입된 값 검증</h2>
<pre><code class="language-yaml">kubectl get pod limit-test-pod -n tenant-alpha -o yaml</code></pre>
<h3 id="마무리">마무리</h3>
<p>이제 테넌트가 CPU와 RAM을 마음껏 사용해도(제한된 범위 내에서) 다른 테넌트에 영향을 주지 않게 되었습니다. 리소스에 대한 제한과 격리는 잘 되었습니다만, 다른 테넌트에 네트워크로 접속해서 데이터를 훔쳐보는(??) 상황은 아직 처리를 안 한 것 같습니다. 다음에는 Zero-Trust Network Policy 를 통해 네트워크 통신까지 제한하는 작업을 해보겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Project] Multi-Tenant K8s Cluster on ARM64 - (3) Storage Quota & Isolation
]]></title>
            <link>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-3-Storage-Quota-Isolation</link>
            <guid>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-3-Storage-Quota-Isolation</guid>
            <pubDate>Fri, 06 Mar 2026 02:19:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>RBAC 설정을 통해 <code>‘누가(admin/tenant)’</code> 들어올 수 있는지를 정의했습니다. 이제 각 테넌트가 사용할 수 있는 <code>물리적 자원의 크기</code>를 결정할 차례입니다.
그 중에서 라즈베리파이의 SD 카드나 외장 SSD 를 보호하기 위한 Storage Quota &amp; Isolation 을 먼저 진행해보겠습니다.</p>
</blockquote>
<hr>
<h1 id="1-기능-설명-스토리지-격리-및-용량-제한">1. [기능 설명] 스토리지 격리 및 용량 제한</h1>
<ul>
<li>특정 테넌트가 무분별하게 대용량 데이터를 저장하여 클러스터 전체의 디스크 공간(Storage)을 고갈시키는 것을 방지하는 작업</li>
</ul>
<h2 id="1-1-주요-설계-목표">1-1. 주요 설계 목표</h2>
<ul>
<li><code>총량 제한</code> : 해당 네임스페이스(<code>tenant-alpha</code>) 내에서 생성되는 모든 <code>PVC(PersistentVolumeClaim)</code> 용량의 합계를 제한</li>
<li><code>개수 제한</code> : 테넌트가 생성할 수 있는 스토리지 요청(PVC) 객체의 개수 자체를 제한하여 관리 오버헤드를 방지</li>
</ul>
<h2 id="1-2-필요한-쿠버네티스-기술-객체">1-2. 필요한 쿠버네티스 기술 객체</h2>
<h3 id="resourcequota"><code>ResourceQuota</code></h3>
<ul>
<li>CPU/RAM 뿐만 아니라 스토리지의 총합(<code>requests.storage</code>) 을 제한하는 용도로 사용</li>
</ul>
<h3 id="persistentvolumeclaim-pvc"><code>PersistentVolumeClaim (PVC)</code></h3>
<ul>
<li>사용자가 스토리지를 사용하겠다고 요청하는 예약권</li>
</ul>
<blockquote>
</blockquote>
<h2 id="📌-pv-와-pvc">📌 PV 와 PVC</h2>
<blockquote>
</blockquote>
<ul>
<li>쿠버네티스에서 스토리지는 크게 두 단계로 관리됩니다.<blockquote>
</blockquote>
<h3 id="pv-persistentvolume">PV (PersistentVolume)</h3>
<blockquote>
</blockquote>
</li>
<li>실제 물리적 스토리지(SSD)의 일부분을 쿠버네티스가 인식할 수 있는 자원 형태로 등록해둔 것 → <code>관리자 영역</code><blockquote>
</blockquote>
<h3 id="pvc-persistentvolumeclaim">PVC (PersistentVolumeClaim)</h3>
<blockquote>
</blockquote>
</li>
<li>사용자가 특정 용량과 접근 권한을 명시하여 스토리지를 할당받으려고 제출하는 요청 → <code>테넌트 영역</code><blockquote>
</blockquote>
테넌트가 PVC를 생성하면, 쿠버네티스는 테넌트의 요구사항(용량, 읽기/쓰기 모드)에 맞는 PV를 찾아서 서로 연결(<code>Binding</code>) 해줍니다.</li>
</ul>
<h3 id="storageclass"><code>StorageClass</code></h3>
<ul>
<li>어떤 종류의 저장 장치(SSD, HDD 등)를 사용할지 정의하며, 쿼터 계산의 기준이 됨</li>
</ul>
<h1 id="2-기능-개발-yaml-매니페스트-및-테스트">2. [기능 개발] YAML 매니페스트 및 테스트</h1>
<ul>
<li><code>tenant-alpha</code> 네임스페이스에 총 <code>5Gi 의 스토리지 사용량</code>을 제한하는 설정을 적용</li>
</ul>
<h2 id="2-1-스토리지-쿼터-정의-tenant-storage-quotayaml">2-1. 스토리지 쿼터 정의 (<code>tenant-storage-quota.yaml</code>)</h2>
<pre><code class="language-yaml">apiVersion: v1
kind: ResourceQuota
metadata:
  name: storage-quota
  namespace: tenant-alpha # 테넌트 알파 공간에 적용
spec:
  hard:
    requests.storage: &quot;5Gi&quot;    # 모든 PVC 용량의 합계를 5Gi로 제한
    persistentvolumeclaims: &quot;3&quot; # 생성 가능한 PVC 개수를 3개로 제한</code></pre>
<h2 id="2-2-환경-적용">2-2. 환경 적용</h2>
<pre><code class="language-yaml"># 마스터 노드에서 실행
kubectl apply -f tenant-storage-quota.yaml</code></pre>
<h2 id="2-3-검증-테스트-quota-초과-상황-재현">2-3. 검증 테스트 (Quota 초과 상황 재현)</h2>
<ul>
<li>테넌트가 할당량을 넘어서는 요청을 할 때 시스템이 어떻게 차단하는지 확인</li>
<li><code>정상 요청 (2Gi)</code><ul>
<li><code>tenant-alpha</code> 내에서 2Gi 크기의 PVC를 생성하면 정상적으로 승인</li>
</ul>
</li>
<li><code>초과 요청 (4Gi 추가)</code><ul>
<li>이미 2Gi 를 사용 중인 상태에서 4Gi를 추가 요청하면, 총합이 6Gi가 되어 설정된 5Gi를 초과하므로 API 서버에서 생성을 거부</li>
</ul>
</li>
</ul>
<pre><code class="language-bash"># 쿼터 상태 확인
kubectl describe resourcequota storage-quota -n tenant-alpha</code></pre>
<ul>
<li><code>USED</code> 열은 현재 테넌트가 사용하는 양을, <code>HARD</code> 열은 설정한 상한선을 보여줌</li>
</ul>
<h3 id="마무리">마무리</h3>
<p>이제 단순히 용량만 제한하는 것이 아니라, 네임스페이스별로 PVC를 분리함으로써 테넌트 A가 테넌트 B의 데이터를 물리적/논리적으로 들여다볼 수 없는 구조가 완성되었습니다. 다음 포스트에서는 CPU/RAM 에 대한 격리를 설정해보겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Project] Multi-Tenant K8s Cluster on ARM64 - (2) RBAC - admin/tenant]]></title>
            <link>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-2-RBAC-admintenant</link>
            <guid>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-2-RBAC-admintenant</guid>
            <pubDate>Thu, 05 Mar 2026 03:17:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>우선 제 홈랩 영역에 외부 사용자(테넌트)가 들어올 수 있기 때문에 권한 격리를 했습니다.</p>
</blockquote>
<h1 id="1-기능-설명-테넌트-인증-및-권한-격리-rbac">1. [기능 설명] 테넌트 인증 및 권한 격리 (RBAC)</h1>
<blockquote>
<p>클러스터 내에서 각 테넌트가 자신의 자원만 제어할 수 있도록 논리적 격리를 구현하는 것.(CSP 서비스에서 사용자가 본인의 인스턴스만 제어하고 타인의 정보는 볼 수 없는 환경을 쿠버네티스에서 재현하고자 함.)</p>
</blockquote>
<h2 id="1-1-주요-설계-목표">1-1. 주요 설계 목표</h2>
<ul>
<li><code>논리적 공간 분리</code> : 각 테넌트에게 전용 <strong>Namespace</strong> 를 할당하여 자원이 섞이지 않게 함</li>
<li><code>전용 계정 생성</code> : 클러스터 전체 관리자 권한이 아닌, 특정 테넌트 전용 <strong>ServiceAccount</strong> 를 발급함</li>
<li><code>권한 범위 제한</code> : <strong>RoleBinding</strong> 을 통해 해당 계정이 지정된 <strong>Namespace 내부의 리소스</strong>(Pod, Service 등)<code>만</code> 관리할 수 있도록 제한함</li>
</ul>
<h2 id="1-2-필요한-쿠버네티스-기술-객체">1-2. 필요한 쿠버네티스 기술 객체</h2>
<h3 id="namespace"><code>Namespace</code></h3>
<ul>
<li>클러스터 내의 가상 클러스터, 리소스 격리의 기본 단위</li>
</ul>
<h3 id="serviceaccount"><code>ServiceAccount</code></h3>
<ul>
<li>파드(Pod) 또는 외부 사용자(테넌트)가 쿠버네티스 API를 호출할 때 사용하는 인증 주체<ul>
<li>→ 일반 User 는 인증서 발급 등 외부 인프라 관리가 필요하지만, ServiceAccount 는 쿠버네티스 내부 리소스로서 <strong>API Token(인증 토큰)</strong> 기반으로 관리가 용이하기 떄문에 테넌트에게 즉시 권한을 부여하는 모델에 적합함.</li>
</ul>
</li>
</ul>
<h3 id="clusterrole-admin"><code>ClusterRole (admin)</code></h3>
<ul>
<li>쿠버네티스에 미리 정의된 역할로, 리소스에 대한 생성/수정/삭제 권한을 가짐</li>
</ul>
<h3 id="rolebinding"><code>RoleBinding</code></h3>
<ul>
<li>특정 ServiceAccount 를 특정 Namespace 내의 ClusterRole 과 연결하여 권한을 발효시킴</li>
</ul>
<hr>
<h1 id="2-기능-개발-yaml-매니페스트-및-테스트">2. [기능 개발] YAML 매니페스트 및 테스트</h1>
<h2 id="2-1-테넌트-격리-환경-정의">2-1. 테넌트 격리 환경 정의</h2>
<ul>
<li>tenant-setup.yaml</li>
</ul>
<pre><code class="language-yaml"># 1. 테넌트 전용 네임스페이스 생성
apiVersion: v1
kind: Namespace
metadata:
  name: tenant-alpha
---
# 2. 테넌트 전용 서비스 어카운트 생성
apiVersion: v1
kind: ServiceAccount
metadata:
  name: alpha-user
  namespace: tenant-alpha
---
# 3. 권한 연결 (RoleBinding)
# ClusterRole인 &#39;admin&#39;을 &#39;tenant-alpha&#39; 네임스페이스 내의 &#39;alpha-user&#39;에게만 매핑합니다.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: alpha-user-admin-binding
  namespace: tenant-alpha
subjects:
- kind: ServiceAccount
  name: alpha-user
  namespace: tenant-alpha
roleRef:
  kind: ClusterRole
  name: admin
  apiGroup: rbac.authorization.k8s.io</code></pre>
<blockquote>
</blockquote>
<h3 id="📌-rolebinding">📌 RoleBinding</h3>
<blockquote>
</blockquote>
<ul>
<li><code>누가(Subject)</code>, <code>무엇을(RoleRef)</code>, <code>어디에서(Namespace)</code> 할 수 있는지를 연결<ul>
<li><code>metadata.namespace</code><ul>
<li>권한이 효력을 발휘할 범위(Scope)를 결정</li>
<li><code>tenant-alpha</code> 를 적었기 때문에, 이 권한은 다른 네임스페이스에는 아무런 영향을 주지 않음</li>
</ul>
</li>
<li><code>subjects</code> (권한을 받는 대상)<ul>
<li><code>kind: ServiceAccount</code> : 권한을 부여할 대상의 유형</li>
<li><code>name: alpha-user</code> : 위에서 만든 서비스 어카운트의 이름을 정확히 지칭</li>
</ul>
</li>
<li><code>roleRef</code> (부여할 권한 내용)<ul>
<li><code>kind: ClusterRole</code> : 권한의 정의가 클러스터 전체 수준에서 공용으로 만들어진 것</li>
<li><code>name: admin</code> : 쿠버네티스가 기본적으로 제공하는 ‘관리자용 권한 세트’.<ul>
<li>파드 생성, 수정, 삭제, 서비스 생성 등 네임스페이스 내의 거의 모든 작업이 포함되어 있음</li>
</ul>
</li>
<li><code>apiGroup: rbac.authorization.k8s.io</code> : RBAC 기능을 담당하는 쿠버네티스 API 그룹</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="2-2-환경-적용-및-접속-토큰-발행">2-2. 환경 적용 및 접속 토큰 발행</h2>
<ul>
<li>작성한 매니페스트를 클러스터에 반영하고, 테넌트가 외부에서 API 서버에 인증할 때 사용할 Access Token을 발행</li>
</ul>
<pre><code class="language-yaml"># 매니페스트 적용
kubectl apply -f tenant-setup.yaml

# 테넌트용 토큰 발행 (보안을 고려하여 24시간 유효 기간 설정)
kubectl create token alpha-user -n tenant-alpha --duration=24h</code></pre>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/38f3bb3c-7517-4aae-a77f-1c9723fc5164/image.png" alt=""></p>
<h2 id="2-3-권한-검증-impersonation-테스트">2-3. 권한 검증 (Impersonation 테스트)</h2>
<ul>
<li><p>관리자 권한을 가진 상태에서 특정 계정을 사칭(<code>--as</code>)하여, 설정한 RBAC 정책이 의도대로 동작하는지 확인</p>
</li>
<li><p>정상 동작 확인: <code>tenant-alpha</code> 네임스페이스 내의 자원 조회</p>
</li>
</ul>
<pre><code class="language-bash">kubectl get pods -n tenant-alpha --as=system:serviceaccount:tenant-alpha:alpha-user</code></pre>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/2c3f1431-8c26-4116-a92e-bcdc4909ea10/image.png" alt=""></p>
<blockquote>
<p>-&gt; <code>No resources found</code> (성공 → 권한이 있어 조회가 가능하지만 생성된 파드가 없음)</p>
</blockquote>
<ul>
<li>격리 동작 확인: 관리 영역인 <code>kube-system</code> 네임스페이스 자원 조회</li>
</ul>
<pre><code class="language-bash">kubectl get pods -n kube-system --as=system:serviceaccount:tenant-alpha:alpha-user</code></pre>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/1eeb1a05-3fb2-4db1-b9f1-0273cf2e1fc9/image.png" alt=""></p>
<blockquote>
<p>-&gt; <code>Error from server (Forbidden)</code>  (성공 - 권한이 없어 접근이 차단됨)</p>
</blockquote>
<h3 id="마무리">마무리</h3>
<p>이번 작업에서는 <code>RBAC</code> 을 기반으로 관리자(나)와 테넌트(사용자)의 권한 격리를 진행해보았습니다. 다음 포스트에서는 스토리지 격리를 설정해보겠습니다.</p>
<hr>
<p>p.s) 처음 보는 쿠버네티스 개념이 너무 많은 것 같습니다. CKA 준비도 병행하고 있는데 운동 많<del>이 됩니다</del>^^</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Project] Multi-Tenant K8s Cluster on ARM64 - (1) Intro]]></title>
            <link>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-1-Intro</link>
            <guid>https://velog.io/@pizza_loves_me/Project-Multi-Tenant-K8s-Cluster-on-ARM64-1-Intro</guid>
            <pubDate>Thu, 05 Mar 2026 02:37:38 GMT</pubDate>
            <description><![CDATA[<p>지난 <a href="https://velog.io/@pizza_loves_me/%ED%99%88%EB%9E%A9-%EA%B5%AC%EC%B6%95%EA%B8%B0-4-Kubernetes-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EA%B5%AC%EC%B6%95">홈랩 구축기</a> 를 마무리 하며 드디어 온전한 홈랩을 갖게 되었습니다. 여기서 할 수 있는 것들이 너무나 많지만 가장 해보고 싶었던 것을 생각해보니 Google, AWS, MS 처럼 <code>CSP의 시스템을 구축</code>해보고 <code>그 역할을 수행</code>해보면 참 재밌겠다는 생각이 들었습니다. 또, 애플리케이션을 올리기 위해 만든 홈랩보다 <code>운영</code>의 관점에서 다양한 경험을 해보고 싶었기에 홈랩 프로젝트를 좀 더 고도화 하기로 결정했습니다.</p>
<h2 id="뭐-먼저-할까">뭐 먼저 할까?</h2>
<blockquote>
<p>사용자에게는 가상 인스턴스(SSH Server) 환경을 제공하고, 내부적으로는 Namespace 기반의 멀티 테넌시로 관리</p>
</blockquote>
<p>인프라는 모르는 것 투성이기에 제 지식이 클라우드 서비스 전체를 커버할 수 없겠다는 생각이 들었습니다.(<del>너무나 당연한 얘기..</del>) 초기 구축 이후에 사용자들의 피드백을 지속적으로 받고, 개선해나가며 방향성을 잡기로 했습니다. 일단 지금 놀고 있는 리소스가 넘쳐나기에,,(<code>RAM 24GB</code> + <code>CPU 12-Core</code> + <code>768GB SSD</code> - <code>K8s 구축 리소스</code> = <code>놀고 있는 리소스</code>) 이 리소스들을 사용자에게 CSP의 <code>인스턴스</code>(AWS-ec2, OCI-Instance 등)와 같이 제공을 해보면 좋겠다고 생각했습니다.</p>
<p>또, 현재 운영진으로 속해 있는 교내 개발 동아리 &#39;그리디(GREEDY)&#39; 에서 3기 멤버들이 프로젝트를 진행하고 있습니다. 4차 데모데이 요구사항 중 <code>무중단 배포</code> 와 <code>모니터링 서버</code> 가 예정되어 있는데, 이때 제 테넌트 서버가 멤버들에게 좋은 경험을 줄 수 있다고 생각했고, 4차 데모데이가 다가오기 전에 프로젝트를 1차 런칭하기로 했습니다.</p>
<h2 id="기술-스택">기술 스택</h2>
<blockquote>
<p>우선 테넌트 서버가 동작하는 홈랩의 스펙을 고려하여 라이트하게 사용할 기술 스택을 선정해봤습니다.</p>
</blockquote>
<ul>
<li><code>Infrastructure</code>: Raspberry Pi 5 (8GB) * 3, NVMe SSD</li>
<li><code>Orchestration</code>: Kubernetes v1.31 (kubeadm)</li>
<li><code>Runtime</code>: Containerd v1.6.20</li>
<li><code>CNI (Network)</code>: Cilium v1.18.5</li>
<li><code>Storage</code>: Local Path Provisioner</li>
</ul>
<h2 id="프로젝트-명세서-project-specs">프로젝트 명세서 (Project Specs)</h2>
<h3 id="1핵심-가치-core-values">1.핵심 가치 (Core Values)</h3>
<ul>
<li><code>격리성 (Isolation)</code> : 테넌트 간의 간섭 최소화 (리소스, 권한, 네트워크, 스토리지 등)</li>
<li><code>자율성 (Autonomy)</code> : 할당된 범위 내에서 테넌트가 자유롭게 리소스 관리</li>
<li><code>가시성 (Visibility)</code> : 관리자와 테넌트 모두 자원 사용 현황 확인 가능</li>
</ul>
<h3 id="2-상세-기능-명세-functional-specifications">2. 상세 기능 명세 (Functional Specifications)</h3>
<table>
<thead>
<tr>
<th></th>
<th><strong>기능명</strong></th>
<th><strong>상세 설명</strong></th>
</tr>
</thead>
<tbody><tr>
<td>권한 관리</td>
<td>RBAC</td>
<td><code>관리자(나)</code> : 클러스터 전체 권한(cluster-admin)</td>
</tr>
<tr>
<td></td>
<td></td>
<td><code>테넌트</code> : 특정Namespace 내에서만 리소스 생성/삭제 가능. 타 네임스페이스 및 노드 정보 조회 불가</td>
</tr>
<tr>
<td>자원 관리</td>
<td>Dynamic Resource Quota</td>
<td>Namespace별 CPU/RAM 상한선 설정</td>
</tr>
<tr>
<td></td>
<td></td>
<td><code>확장 기능</code> : 자원 부족 시 관리자 승인을 통항 쿼터 증설 프로세스 마련</td>
</tr>
<tr>
<td>네트워크</td>
<td>Zero-Trust Network Policy</td>
<td><code>테넌트 간 격리</code> : A 네임스페이스의 Pod 는 B 네임스페이스의 Pod 와 직접 통신 불가</td>
</tr>
<tr>
<td></td>
<td></td>
<td>외부(인터넷)을 거쳐 들어오는 통신은 허용 가능하도록 설계(Ingress / LoadBalancer 활용)</td>
</tr>
<tr>
<td>외부 접속</td>
<td>External Access Point</td>
<td><code>관리자/테넌트</code> : 포트포워딩을 통해 SSH 접속 지원</td>
</tr>
<tr>
<td></td>
<td></td>
<td><code>서비스</code> : 각 테넌트 앱에 고유 도메인 또는 포트 할당</td>
</tr>
<tr>
<td>모니터링</td>
<td>Tenant Dashboard</td>
<td>Prometheus &amp; Grafana 를 활용하여 테넌트별 실시간 자원 사용량 시각화</td>
</tr>
</tbody></table>
<h3 id="마무리">마무리</h3>
<p>우선 멀티 테넌트 제공 프로젝트의 초기 계획을 세워봤습니다. 본격적인 개발 및 구축 과정을 이어서 기록해보겠습니다.</p>
<p>p.s) 글을 쓰는 현재 시점에서는 테넌트 서버 제공 서비스가 런칭을 하였습니다. 구축 및 개발 과정에서 너무나 많은 시행착오가 있었기에 개발과 함께 블로그 포스팅을 못하였지만, 노션 등에 정리한 내용과 시행착오들을 잘 블로그에 잘 정리해보겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부트캠프 - 39일차] 2/13.금 - Kubernetes]]></title>
            <link>https://velog.io/@pizza_loves_me/%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-39%EC%9D%BC%EC%B0%A8-213.%EA%B8%88-Kubernetes</link>
            <guid>https://velog.io/@pizza_loves_me/%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-39%EC%9D%BC%EC%B0%A8-213.%EA%B8%88-Kubernetes</guid>
            <pubDate>Sun, 15 Feb 2026 05:28:04 GMT</pubDate>
            <description><![CDATA[<hr>
<h1 id="pod">Pod</h1>
<h2 id="pod-1">Pod</h2>
<h3 id="컨테이너-접속과-명령어-실행">컨테이너 접속과 명령어 실행</h3>
<ul>
<li>생성된 컨테이너에 접속할 때는 <code>kubectl exec</code> 명령어를 사용하는데 가상 터니멀을 생성(<code>-t</code>)하고 표준 입력을 패스 스루(<code>-i</code>) 하면서 <code>/bin/sh</code> 를 실행하면 컨테이너에 SSH로 로그인 한 상태가 됨</li>
<li>정상 수행 중 인지 명령어 실행</li>
</ul>
<pre><code class="language-bash">kubectl exec -it sample-pod -- /bin/bash

# 정상 수행 중인지 명령어 실행
apt update &amp;&amp; apt -y install iproute2 procps

ip a | grep &quot;inet&quot;

ss -napt | grep LISTEN
</code></pre>
<ul>
<li>외부에서 명령어 실행</li>
</ul>
<pre><code class="language-bash"># 컨테이너가 1개인 파드에 명령어 수행
kubectl exec -it sample-pod -- /bin/ls

# 특정 파드를 지정해서 명령어 수행
kubectl exec -it sample-2pod -c nginx-container -- /bin/ls

# 옵션을 활용한 명령어 실행
kubectl exec -it sample-pod -- /bin/ls --all --classify

# 특수문자가 포함된 명령어 실행
kubectl exec -it sample-pod -- /bin/bash -c &quot;ls --all --classify | grep lib&quot;</code></pre>
<h3 id="entrypoint-명령-cmd-명령과-commandargs">ENTRYPOINT 명령 /CMD 명령과 command/args</h3>
<ul>
<li>도커 파일로 이미지를 생성할 때는 <code>ENTRYPOINT</code> 명령과 <code>CMD</code> 명령을 사용하여 컨테이너 실행 시 명령어를 정의하는데 <strong><code>쿠버네티스</code></strong>에서는 도커용 용어와 조금 다르게 <code>ENTRYPOINT</code>를 <code>command</code>로 <code>CMD</code> 를 <code>args</code> 라고 부름</li>
</ul>
<h3 id="파드명-제한">파드명 제한</h3>
<ul>
<li>이용 가능한 문자는 영문 소문자와 숫자</li>
<li>이용 가능한 기호는 <code>-</code> 와 <code>.</code></li>
<li>시작과 끝은 영문 소문자</li>
</ul>
<h3 id="호스트의-네트워크-구성을-사용한-파드-기동">호스트의 네트워크 구성을 사용한 파드 기동</h3>
<ul>
<li>쿠버네티스에 기동하는 파드에 할당된 IP 주소는 쿠버네티스 노드의 호스트 IP 주소와 범위가 달라 외부에서 볼 수 없는 IP 주소가 할당됨</li>
<li>호스트의 네트워크를 사용하는 설정(<code>spec.hostNetwork</code>)을 활성화하면 호스트상에서 프로세스를 기동하는 것과 같은 네트워크 구성(<code>IP 주소, DNS 설정, host 설정 등</code>)으로 파드를 기동시킬 수 있음</li>
<li><code>hostNetwork</code> 를 사용한 파드는 쿠버네티스 <code>노드의 IP 주소를 사용</code>하는 관계로 포트 번호 충돌을 방지하기 위해 기본적으로 사용하지 않고, 사용할 때는 <code>에지(edge) 환경에서의 사용</code>이나 <code>호스트 측의 네트워크를 감시 또는 제어</code>와 같은 특수한 애플리케이션 등에서만 사용</li>
</ul>
<h3 id="파드-dns-설정과-서비스-디스커버리">파드 DNS 설정과 서비스 디스커버리</h3>
<ul>
<li>DNS 서버에 관한 설정(dnsPolicy)은 파드 정의 <code>spec.dnsPolicy</code> 에 설정</li>
<li>dnsPolicy 설정값<ul>
<li><code>ClusterFirst(기본값)</code><ul>
<li>일반적으로 파드는 클러스터 내부 DNS 를 사용하여 이름을 해석</li>
<li>서비스 디스커버리나 클러스터 내부 로드 밸런싱에서 사용</li>
<li>dnsPolicy 가 ClusterFirst 인 경우 클러스터 내부의 DNS 서버ㅔ 질의를 하고 클러스터 내부 DNS에서 해석이 안 되는 도메인에 대해서는 업스트림 DNS 서버에 질의</li>
</ul>
</li>
<li><code>None</code><ul>
<li>특별한 요건에 따라서는 클러스터 외부 DNS 를 참조하는 경우도 있음</li>
<li>DNS 서버를 수동으로 설정하려면 <code>spec.dnsPolicy: None</code> 이라고 설정한 후 dnsConfig에 설정하고 싶은 값을 작성</li>
<li>정적으로 외부 DNS 서버만 설정하면 클러스터 내부 DNS를 사용한 서비스 디스커버리는 사용할 수 없게 되므로 주의</li>
</ul>
</li>
<li><code>Default</code><ul>
<li>쿠버네티스 노드의 DNS 설정을 그대로 상속받는 경우에는 <code>spec.dnsPolicy: Default</code> 로 설정</li>
<li>dnsPolicy 의 기본값은 Default 가 아니므로 주의</li>
<li>쿠버네티스 노드의 DNS 설정을 상속받게 설정하면 클러스터 내부의 DNS를 사용한 서비스 디스커버리를 할 수 없음</li>
</ul>
</li>
<li><code>ClusterFirstWithHostNet</code></li>
<li>hostNetwork 를 사용한 파드에 클러스터 내부의 DNS를 참조하고 싶은 경우에는 <code>spec.dnsPolicy: ClusterFirstWithHostNet</code> 을 설정</li>
<li>hostNetwork 를 사용하는 경우 기본값 ClusterFirst 의 설정값은 무시되고 쿠버네티스 <code>ClusterFirstWithHostNet</code> 을 지정해야 함</li>
</ul>
</li>
</ul>
<h3 id="정적-호스트명-설정">정적 호스트명 설정</h3>
<ul>
<li>리눅스 운영체제에서는 DNS로 호스트명을 해석하기 전에 <code>/etc/hosts</code> 파일로 정적 호스트명을 해석</li>
<li>쿠버네티스에서는 파드 내부 모든 컨테이너의 <code>/etc/hosts</code> 를 변경하는 기능이 준비되어 있으며 <code>spec.hostAliases</code> 로 지정하여 사용</li>
</ul>
<h3 id="작업-디렉터리-설정">작업 디렉터리 설정</h3>
<ul>
<li>컨테이너에서 동작하는 애플리케이션의 작업 디렉러티(Working Directory) 는 도커 파일의 <code>WORKDIR</code> 명령 설정을 따르지만, <code>spec.containers[].workingDir</code> 로 덮어 쓸 수 있음</li>
<li>특정 스크립트 등이 배치된 볼륨을 파드에 마운트할 때 그 스크립트가 배치된 디렉터리로 이동한 후 실행하고 싶은 경우가 있는데 이는 작업 디렉터리를 변경하는 기능을 사용하여 해결할 수 있음</li>
<li>컨테이너에 <code>workingDir</code> 을 설정한 경우 프로세스가 실행되는 디렉터리가 변경된 것을 확인할 수 있음</li>
</ul>
<h2 id="replicaset--replicationcontroller">ReplicaSet / ReplicationController</h2>
<h3 id="개요">개요</h3>
<ul>
<li>Replicaset은 일정한 개수의 동일한 Pod가 항상 실행되도록 관리하는데 이러한 기능이 필요한 이유는 서비스의 지속성 때문</li>
<li>노드의 하드웨어에서 발생하는 장애 등의 이유로 파드룰 사용할 수 없을 때 다른 노드에서 파드를 다시 생성해서 사용자에게 중단 없는 서비스를 제공할 수 있음</li>
<li>원래 파드의 레플리카를 생성하는 리소스의 이름은 레플리케이션 컨트롤러였는데 레플리카셋을 추가하고 일부 기능을 추가</li>
<li>레플리케이션 컨트롤러는 앞으로 사용하지 않는 추세이기 때문에 기본적으로 레플리카셋을 사용하는 것을 권장</li>
</ul>
<h3 id="파드-정지와-자동화된-복구">파드 정지와 자동화된 복구</h3>
<ul>
<li>레플리카셋에서는 노드나 파드에 장애가 발생했을 때 지정한 파드 수를 유지하기 위해 다른 노드에서 파드를 기동시켜 주기 때문에 장애 시에도 많은 영향을 받지 않는데 이것이 쿠버네티스의 중요한 콘셉트 중 하나로 <code>자동화된 복구</code>라는 기능</li>
</ul>
<h3 id="레플리카셋과-레이블">레플리카셋과 레이블</h3>
<ul>
<li>레플리카셋은 쿠버네티스가 파드를 모니터링하여 파드 수를 조정</li>
<li>모니터링은 특정 레이블을 가진 파드 수를 계산하는 형태로 이루어 짐</li>
<li>레플리카 수가 부족한 경우 매니페스트에 기술된 <code>spec.template</code> 로 파드를 생성하고 레플리카 수가 많을 경우 레이블이 일치하는 파드 중 하나를 삭제</li>
</ul>
<h3 id="일치성-기준-조건과-집합성-기준-조건">일치성 기준 조건과 집합성 기준 조건</h3>
<ul>
<li>레플리카 제어 조건은 서비스 중단 예정인 레플리케이션 컨트롤러의 일치성 기준(<code>equality-based</code>) 셀렉터였지만, 레플리카셋에서는 좀 더 강화된 집합성 기준(<code>set-based</code>) 셀렉터를 사용하여 유연한 제어도 가능<ul>
<li>일치성 기준(equality-based): 조건부에 일치 불일치(<code>=</code>, <code>!=</code>) 조건 지정</li>
<li>집합성 기준(set-based): 조건부에 일치 불일치(<code>=</code>, <code>!=</code>) 조건 지정과 집합(<code>in</code>, <code>notin</code>, <code>exists</code>) 조건 지정 가능</li>
</ul>
</li>
<li>쿠버네티스에서 어떤 조전을 지정할 때는 이 두 가지 방법이 있고, 레플리카셋 외에 스케줄링할 때도 이 조건 지정이 사용됨</li>
<li>일치성 기준 조건에서는 조건부에 같은지 혹은 같지 않은지에 대한 조건을 사용할 수 있는데 <code>app=sample-app</code> 과 같이 지정하는 것</li>
<li>집합성 기준 조건에서는 일치성 기준 조건과 함께 집합 조건을 지정할 수 있는데 <code>env ln [development, staging]</code> 과 같이 지정할 수 있으며 <code>ln</code> 에 해당하는 연산자는 스케줄링 조건에서 사용할 때 수치 비교도 가능</li>
</ul>
<h2 id="deployment">Deployment</h2>
<blockquote>
<p>Deployment → ReplicaSet → Pods</p>
</blockquote>
<h3 id="개요-1">개요</h3>
<ul>
<li>여러 레플리카셋을 관리하여 롤링 업데이트나 롤백 등을 구현하는 리소스</li>
<li>디플로이먼트가 레플리카셋을 관리하고 레플리카셋이 파드를 관리하는 관계</li>
<li>디플로이먼트는 레플리카셋의 상위 개념으로서 파드의 개수를 유지할 뿐만 아니라 배포 작업을 좀 더 세분화해 관리할 수 있음</li>
<li>디플로이먼트를 사용하면 신규 레플리카셋에 컨테이너가 기동되었는지와 헬스 체크는 통과했는지를 확인하면서 전환 작업이 진행되며 레플리카셋의 이행 과정에서 파드 수에 대한 상세한 지정도 가능</li>
<li>쿠버네티스에서 가장 권장하는 컨테이너 기동 방법으로 알려져 있음</li>
<li>하나의 파드를 기동만 하더라도 디플로이먼트 사용을 권장하는데 파드로만 배포한 경우에는 파드에 장애가 발생하면 자동으로 파드가 다시 생성되지 않으며 레플리카셋으로만 배포한 경우에는 롤링 업데이트 등의 기능을 사용할 수 없음</li>
</ul>
<h3 id="레플리카셋이-생성되는-조건">레플리카셋이 생성되는 조건</h3>
<ul>
<li>다플로이먼트에서는 변경이 발생하면 레플리카셋이 생성되는데 변경에는 레플리카 수의 변경 등은 포함되어 있지 않으며 생성된 파드의 내용 변경이 필요</li>
<li><code>spec.template</code> 에 변경이 있으면 생성된 파드의 설정이 변경되기 때문에 레플리카셋을 신규로 생성하고 롤링 업데이트를 수행</li>
<li>실제로 매니페스트를 쿠버네티스에 등록한 후 레플리카셋의 정의를 보면, <code>spec.template</code> 아래의 해시값(파드 템플릿 해시 - Pod Template Hash) 을 계산하고 이 값을 사용한 레이블로 관리</li>
<li>수작업으로 이미지 등을 이전 버전으로 재변경하여 해시값이 동일해진 경우에는 레플리카셋을 신규로 생성하지 않고 기존 레플리카셋을 사용</li>
</ul>
<h3 id="디플로에먼트에-속한-파드의-삭제-및-복구-과정">디플로에먼트에 속한 파드의 삭제 및 복구 과정</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/6c6e7b72-49ea-498a-b159-a95e0e7e4484/image.png" alt=""></p>
<h3 id="변경-롤백">변경 롤백</h3>
<ul>
<li>디플로이먼트에는 롤백 기능이 있음</li>
<li>롤백 기능의 실체는 현재 사용 중인 레플리카셋의 전환과 같은 것</li>
<li>디플로이먼트가 생성한 기존 레플리카셋은 레플리카 수가 0인 상태로 남아 있기 때문에 레플리카 수를 변경시켜 다시 사용할 수 있는 상태가 됨</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/55e9c307-db3b-411e-a91a-c6b74e9370bb/image.png" alt=""></p>
<ul>
<li>변경 이력을 확인할 때는 <code>kubectl rollout history</code> 명령어를 사용하는데 <code>CHANGE-CAUSE</code> 부분은 디플로이먼트를 생성할 때 <code>--record</code> 옵션을 사용하여 이력 내용이 있지만 <code>--record</code> 옵션을 사용하지 않았다면  <code>&lt;none&gt;</code></li>
</ul>
<blockquote>
<p>실제 환경에서는 롤백 기능을 사용하는 경우가 많지 않은데, CI/CD 파이프라인에서 롤백을 하는 경우 <code>kubectl rollout</code> 명령어보다 <code>이전 매니페스트를 다시 kubectl apply</code> 명령어로 실행하여 적용하는 것이 <strong>호환성</strong> 면에서 더 좋기 때문이며, <code>spec.template</code> 을 같은 내용으로 되돌렸던 경우에는 <code>pod-template-hash</code> 값이 같기 때문에 <code>kubectl rollout</code> 처럼 기존에 있었던 레플리카셋의 파드가 기동됨</p>
</blockquote>
<h3 id="업데이트-전략">업데이트 전략</h3>
<ul>
<li>Deployment 의 배포 전략은 주로 애플리케이션이 변경될 때 사용</li>
<li>이전 버전의 애플리케이션에서 업데이트가 필요한 경우에 주로 사용되며 방법으로는 <code>RollingUpdate</code>, <code>Recreate</code> 가 있음</li>
<li>기본 전략은 <code>RollingUpdate</code></li>
</ul>
<h3 id="recreate"><strong><code>recreate</code></strong></h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/4f1f106e-79d2-412f-85b6-40860275543c/image.png" alt=""></p>
<ul>
<li>재생성(recreate) 업데이트는 모든 이전 버전(V1)의 파드를 모두 한 번에 종료하고 새 버전(V2)의 파드로 일괄적으로 교체하는 방식</li>
<li>빠르게 교체할 수 있지만 새로운 버전의 파드에 문제가 발생하면 대처가 늦어질 수 있음</li>
</ul>
<h3 id="rollingupdate"><strong><code>RollingUpdate</code></strong></h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/92e743bd-514c-4654-be64-7dec88686ed8/image.png" alt=""></p>
<ul>
<li><p>롤링 업데이트는 새 버전의 애플리케이션을 배포할 때 새 버전의 애플리케이션은 하나씩 늘려가고 기존 버전의 애플리케이션은 하나씩 줄여나가는 방식</p>
</li>
<li><p>쿠버네티스에서 사용하는 표준 배포 방식</p>
</li>
<li><p>새로운 버전(v2)으로 배포된 파드에 문제가 발생하면 다시 이전 버전(v1)의 파드로 서비스를 대체할 수 있어서 상당히 안정적인 배포 방식이지만 업데이트가 느리게 진행된다는 단점이 있음</p>
</li>
<li><p><code>maxSurge</code></p>
<ul>
<li>롤링 업데이트를 위해 최대로 생성할 수 있는 파드 개수</li>
<li>maxSurge 를 높게 설정하면 롤링 배포를 빠르게 적용 가능</li>
<li><code>%</code> 또는 개수로 지정 가능</li>
<li>설정하지 않을 경우 기본 값은 <code>25%</code></li>
</ul>
</li>
<li><p><code>maxUnavailable</code></p>
<ul>
<li>롤링 업데이트 중 최대로 삭제할 파드 개수</li>
<li>maxUnavailable 을 높게 설정하면 롤링 배포를 빠르게 적용 가능</li>
<li>한 번에 많은 파드를 삭제할 경우 트래픽이 남아있는 소수의 파드로 집중될 수 있어 값을 적절히 설정 필요</li>
<li><code>%</code> 또는 개수로 지정 가능</li>
<li>설정하지 않을 경우 기본 값은 <code>25%</code></li>
</ul>
</li>
<li><p><code>maxUnavailable=0 / maxSurge=1</code></p>
<p>  <img src="https://velog.velcdn.com/images/pizza_loves_me/post/a8cd1d53-c342-47bb-9d1a-2cd85d9ce19b/image.png" alt=""></p>
</li>
</ul>
<ul>
<li><p><code>maxUnavailable=1 / maxSurge=0</code></p>
<p>  <img src="https://velog.velcdn.com/images/pizza_loves_me/post/1d01049d-e675-48d2-b271-484b655ac269/image.png" alt=""></p>
</li>
</ul>
<h3 id="블루그린-업데이트">블루/그린 업데이트</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/ab39a63f-8d1b-41c9-885f-a0bc78a6d288/image.png" alt=""></p>
<ul>
<li>블루/그린 업데이트는 애플리케이션의 이전 버전(블루, v1 pod)과 새 버전(그린, v2 pod)을 동시에 운영</li>
<li>서비스 목적으로 접속할 수 있는 것은 새 버전의 파드만 가능하며 이전 버전의 파드는 테스트 목적으로만 접속할 수 있음</li>
<li>새로운 버전의 파드에 문제가 발생했을 때 빠르게 대응할 수 있으며 안정적으로 배포할 수 있음</li>
<li>많은 파드가 필요하므로 그만큼 많은 자원이 필요하다는 단점이 있블로</li>
<li>블루/그린 업데이트를 이용해 애플리케이션을 배포할 때는 version으로 구분해 관리</li>
<li>green 배포용 yaml 파일을 생성하는데 이전 버전의 배포 yaml 파일과 내용이 같지만 사용하는 이미지의 경로와 labels 의 version 만 수정</li>
</ul>
<h3 id="canary-업데이트">Canary 업데이트</h3>
<ul>
<li>Canary 업데이트는 블루/그린과 비슷하지만 더 진보적인 단계적 접근 방식</li>
<li>Canary는 주로 애플리케이션의 몇몇 새로운 기능을 테스트할 때 사용</li>
<li>Canary는 두 개의 버전을 모두 배포하지만 새 버전에는 조금씩 트래픽을 증가시켜(처음에는 5% 정도의 트래픽만 흘려 보내지만 나중에는 50%의 트래픽을 흘려보냄) 새로운 기능들을 테스트</li>
<li>기능 테스트가 끝나고 새 버전에 문제가 없다고 판단하면 이전 버전은 모두 종료시키고 새 버전으로만 서비스</li>
<li>Canary 업데이트용 새로운 배포 파일을 생성하는데 <code>track: canary</code> 를 통해 Canary 업데이트를 만들어 줄 수 있음</li>
<li>Canary 업데이트의 문제점<ul>
<li>한명의 유저가 서비스를 사용할 때 로드밸런서에 의해 자동으로 여러 서버에 접속될 수 있음</li>
<li>유저가 새로고침을 할 때 로드 밸런싱이 일어나서 구버전/신버전으로 바뀌게 되는 문제가 생길 수 있음</li>
<li>스티키 세션을 사용하여 사용자가 한 번 특정한 서버에 접속되어 있으면 그 서버에만 계속 접속되도록 만들 수 있움</li>
</ul>
</li>
</ul>
<h3 id="상세-업데이트-파라미터">상세 업데이트 파라미터</h3>
<ul>
<li><code>minReadySeconds(최소 대기 시간- 초)</code><ul>
<li>파드가 Ready 상태가 된 다음부터 디플로이먼트 리소스에서 파드 기동이 완료되었다고 판단(다음 파드의 교체가 가능하다고 판단하기까지의 최소 시간</li>
</ul>
</li>
<li><code>revisionHistoryLimit(수정 버전 기록 제한)</code><ul>
<li>디플로이먼트가 유지할 레플리카셋 수</li>
<li>롤백이 가능한 이력 수</li>
</ul>
</li>
<li><code>progressDeadlineSeconds(진행 기한 시간(초))</code><ul>
<li>Recreate/Rollingllpdate 처리 타임아웃 시간</li>
<li>타임아웃 시간이 경과하면 자동으로 롤백</li>
</ul>
</li>
</ul>
<h3 id="디플로이먼트-스케일링">디플로이먼트 스케일링</h3>
<ul>
<li>디플로이먼트가 관리하는 레플리카셋의 레플리카 수는 레플리카셋과 같은 방법으로 <code>kubectl apply -f</code> 또는 <code>kubectl scale</code> 을 사용하여 스케일링</li>
</ul>
<h2 id="daemonset">DaemonSet</h2>
<h3 id="개요-2">개요</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/ff4f05b4-0d5a-411d-81ad-b21de96e0932/image.png" alt=""></p>
<ul>
<li>ReplicaSet 의 특수한 형태</li>
<li>데몬셋은 모든 노드에 파드를 생성</li>
<li>ReplicaSet 이 특정 개수의 파드를 유지할 때 사용하는 것이라면 데몬셋은 모든 노드에 파드를 배포할 때 사용</li>
<li>데몬셋은 레플리카 수를 지정할 수 없고 하나의 노드에 누 개의 파드를 배치할 수도 없음</li>
<li>파드를 배치하고 싶지 않은 노드가 있을 경우 <code>nodeselector</code>나 노드 안티어피니티를 사용한 스케줄링으로 예외 처리</li>
<li>쿠버네티스 노드를 늘렸을 때도 데몬셋의 파드는 자동으로 늘어난 노드에서 기동하기 때문에 데몬셋은 각 파드가 출력하는 로그를 호스트 단위로 수집하는 플루언트디나 각 파드 리소스 사용 현황 및 노드 상태를 모니터링 하는 데이터독 등 모든 노드에서 반드시 동작해야 하는 프로세스를 위해 사용하는 것이 유용</li>
</ul>
<h3 id="데몬셋-업데이트-전략-ondelete--rollingupdate">데몬셋 업데이트 전략: OnDelete &amp; RollingUpdate</h3>
<h3 id="ondelete">OnDelete</h3>
<ul>
<li>OnDelete 에서는 데몬셋 매니페스트를 수정하여 이미지 등을 변경했더라도 기존 파드는 업데이트 되지 않음</li>
<li>디플로이먼트와 달리 데몬셋은 모니터링이나 로그 전송과 같은 용도로 많이 사용하기 때문에 업데이트는 다음 번에 다시 생성할 때나 수동으로 임의의 시점에 하게 되어 있으며 type 외에 지정할 수 있는 항목은 없음</li>
<li>OnDelete로 설정하고 아무것도 하지 않을 경우 노드의 유지 보수를 이유로 파드가 정지되거나 예기치 못하게 파드가 정지되는 등 다양한 이유로 파드가 정지되어 다시 생성되기 전까지는 업데이트 되지 않기 때문에 운영상의 이유로 정지하면 안 되는 파드이거나 업데이트가 급하게 필요 없는 경우 OnDelete 설정으로 사용해도 되지만 이전 버전이 계속 장기간 사용된다는 점에 주의</li>
<li>임의의 시점에 파드를 업데이트 하는 경우에는 데몬셋과 연결된 파드를 kubectl delete pod 명령어로 수동으로 정지시키고 자동화된 복구 기능으로 새로운 파드를 생성해야 함</li>
</ul>
<h3 id="rollingupdate-1">RollingUpdate</h3>
<ul>
<li>데몬셋도 디플로이먼트와 마찬가지로 RollingUpdate 를 사용한 업데이트가 가능</li>
<li>데몬셋에서는 하나의 노드에 동일한 파드를 여러 개 생성할 수 없으므로 디플로이먼트와 달리 동시에 생성할 수 있는 최대 파드 수(masSurge)를 설정할 수 없고 동시에 정지 가능한 최대 파드 수(maxUnavailable)만 지정하여 RollingUpdate를 수행</li>
<li>maxUnavailable=2 의 경우 파드를 두 개씩 동시에 업데이트해 나가는 형태</li>
<li>maxUnavailable 의 기본값은 1이며 maxUnavailable 을 0으로 지정할 수는 없음</li>
</ul>
<h2 id="statefullset">StatefullSet</h2>
<ul>
<li>스테이트풀셋도 데몬셋과 마찬가지로 레플리카셋의 특수한 형태라고 할 수 있는 리소스</li>
<li>데이터베이스 등과 같은 스테이트풀한 워크로드에 사용하기 위한 리소스</li>
<li>RepliaSet과 차이점<ul>
<li>생성되는 파드명의 접미사는 숫자 인덱스가 부여된 것</li>
<li>파드명이 바뀌지 변경되어도 내용은 바뀌지 않음</li>
<li>데이터를 영구적으로 저정하기 위한 구조</li>
<li>스테이트풀셋에서는 spec.volumeClaimTemplates를 지정할 수 있는데 이 설정으로 스테이트풀셋으로 생성되는 각 파드에 영구 볼륨 클레임(영구 볼륨 요청)을 설정할 수 있으며 영구 볼륨 클레임을 사용하면 클러스터 외부의 네트워크를 통해 제공되는 영구 볼륨을 파드에 연결할 수 있으므로 파드를 재기동할 때나 다른 노드로 이동할 때 같은 데이터를 보유한 상태로 컨테이너가 다시 생성되고 영구 볼륨은 하나의 파드가 소유할 수도 있고 영구 볼륨 종류에 따라 여러 파드에서 공유할 수도 있음</li>
</ul>
</li>
</ul>
<h3 id="스케일링">스케일링</h3>
<ul>
<li>스케일링 수행<ul>
<li><code>kubectl scale statefulset sample-statefulset --replicas=5</code></li>
</ul>
</li>
<li>스테이트풀셋에서 레플리카 수를 변경하여 파드를 생성하고 삭제하면 레플리카셋이나 데몬셋 등과 달리 기본적으로 파드를 동시에 하나씩만 생성하고 삭제하기 때문에 조금 시간이 걸림</li>
<li>스케일 아웃일 때는 인덱스가 가장 작은 것부터 파드를 하나씩 생성하고 이전에 생성된 파드가 Ready 상태가 되고 나서 다음 파드를 생성하기 시작하는데, 위 예제에서는 <code>sample-statefulset-0, sample-statefulset-1, sample-statefulset-2</code> 순서로 생성</li>
<li>스케일 인일 때는 인덱스가 가장 큰 파드(가장 최근에 생성된 컨테이너)부터 삭제되므로 2 → 1 → 0 순서로 삭제</li>
</ul>
<h3 id="라이프사이클">라이프사이클</h3>
<ul>
<li>스테이트풀셋에서도 <code>spec.podManagementPolicy</code> 를 Parallel 로 설정하여 레플리카셋 등과 마찬가지로 병렬로 동시에 파드를 기동시킬 수 있음</li>
<li><code>spec.podManagementPolicy</code> 기본값은 <code>OrderReady</code> 로 설정되어 있음</li>
</ul>
<h3 id="업데이트-전략-1">업데이트 전략</h3>
<h3 id="ondelete-1">OnDelete</h3>
<ul>
<li>OnDelete 에서는 스테이트풀셋 매니페스트를 수정하여 이미지 등을 변경했더라도 기존 파드는 업데이트 되지 않음</li>
<li>스테이트풀셋에서 OnDelete는 영속성 영역을 가진 데이터베이스나 클러스터 등에서 많이 사용되기 때문에 수동으로 업데이트 하고 싶을 경우 OnDelete 를 사용하여 임의의 시점에서나 다음에 재기동할 때 업데이트를 진행하게 되어 있는데 type 외에 지정할 수 있는 항목은 없음</li>
</ul>
<h3 id="rollingupdate-2">RollingUpdate</h3>
<ul>
<li>디플로이먼트와 마찬가지로 롤링업데이트를 사용한 스테이트풀셋 업데이트가 가능하지만 스테이트풀셋에는 영속성 데이터가 있으므로 디플로이먼트와 다르게 추가 파드를 생성해서 롤링 업데이트를 할 수 없고, 동시에 정지 가능한 최대 파드 수(maxUnavailable)를 지정하여 롤링 업데이트를 할 수도 없으므로 파드마다 Ready 상태인지를 확인하고 업데이트</li>
<li><code>spec.podManagementPolicy</code>가 Parallel로 설정되어 있는 경우에도 병렬로 동시에 처리되지 않고 파드 하나씩 업데이트</li>
<li>스테이트풀셋의 RollingUpdate에서는 partition이라는 특정 값을 설정할 수 있는데 partition을 설정하면 전체 파드 중 어떤 파드까지 업데이트할지를 지정할 수 있고 이 설정을 사용하면 전체에 영향을 주지 않고 부분적으로 업데이트를 적용하고 확인할 수 있어 안전한 업데이트를 할 수 있음</li>
<li>OnDelete와 달리 수동으로 재기동한 경우에도 partition 값보다 작은 인덱스를 가진 파드는 업데이트되지 않음</li>
</ul>
<h3 id="삭제">삭제</h3>
<ul>
<li>스테이트풀셋을 생성하면 파드에 영구 볼륨 클레임(영구 볼륨 요청(을 설정할 수 있어 영구 볼륨도 동시에 확보할 수 있음</li>
<li>영구 볼륨은 스테이프풀셋이 삭제되어도 동시에 해제되지 않음</li>
<li>스테이트풀셋이 영구 볼륨을 해제하기 전에 볼륨에서 데이터를 백업할 수 있도록 시간을 주기 때문</li>
<li>스테이트풀셋을 삭제하고 스테이트풀셋이 영구 볼륨 클레임으로 확보한 영구 볼륨을 해제하지 않고 다시 스테이트풀셋을 생성한 경우 영구 볼륨 데이터는 그대로 파드가 기동되는데 스케일 인하여 레플리카 수를 줄인 경우도 마찬가지</li>
<li>스테이트풀셋을 삭제해도 영구 볼륨이 확보된 상태일 경우 비용이 발생</li>
<li>관리형 서비스의 종량 과금 방식인 경우 볼륨을 그대로 유지하면 비용이 발생하고 온프레미스 환경인 경우에도 디스크 공간이 확보된 상태로 남아 있기 때문에 스테이트풀셋을 삭제한 후에는 확보된 영구 볼륨도 해제해야 함</li>
<li>스테이트풀셋이 확보한 영구 볼륨 해제<ul>
<li><code>kubectl delete persistentvolumeclaims www-sample-statefulset-{0..4}</code></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠버네티스 공식 문서에 기여하는 법(한글화 기여)]]></title>
            <link>https://velog.io/@pizza_loves_me/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EA%B3%B5%EC%8B%9D-%EB%AC%B8%EC%84%9C%EC%97%90-%EA%B8%B0%EC%97%AC%ED%95%98%EB%8A%94-%EB%B2%95%ED%95%9C%EA%B8%80%ED%99%94</link>
            <guid>https://velog.io/@pizza_loves_me/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EA%B3%B5%EC%8B%9D-%EB%AC%B8%EC%84%9C%EC%97%90-%EA%B8%B0%EC%97%AC%ED%95%98%EB%8A%94-%EB%B2%95%ED%95%9C%EA%B8%80%ED%99%94</guid>
            <pubDate>Thu, 12 Feb 2026 13:21:13 GMT</pubDate>
            <description><![CDATA[<hr>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/7b9d2ecb-4a1a-41ca-8896-72b5e513dd51/image.png" alt=""></p>
<p>작년 6월부터 꾸준한 기여 활동을 하며 이번에 <code>Kubernetes SIG-Docs-ko Reviewer</code> 자격을 획득했습니다. 🎉🎉</p>
<p>이번 포스트에서는 어떻게 쿠버네티스에 기여할 수 있는지, 문서 기여는 어떻게 진행되는지 등을 다뤄보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/2102579d-87fa-4674-9904-b9a03416a4d1/image.png" alt=""></p>
<p>우선 다들 <a href="https://kubernetes.io/ko/docs/home/">쿠버네티스 문서</a> 를 접한 적이 있을 것이라 생각합니다. 해당 문서는 쿠버네티스의 SIG-Docs 그룹이 관리를 담당합니다. </p>
<ul>
<li>(여기서 <strong><code>SIG</code></strong> 는 <code>Special Interest Group</code> 의 약자입니다.)</li>
</ul>
<p>위 쿠버네티스 문서의 한글 부분을 담당하는 팀이 Kubernetes SIG-Docs-ko 팀이며 현재 제가 기여를 하고 있는 팀입니다.</p>
<p>쿠버네티스 문서 기여자들은</p>
<ul>
<li><code>기존 콘텐츠를 개선한다</code> | <code>Improve existing content</code></li>
<li><code>새 콘텐츠를 만든다</code> | <code>Create new content</code></li>
<li><code>문서를 번역한다</code> | <code>Translate the documentation</code></li>
<li><code>쿠버네티스 릴리스 주기에 맞추어 문서 부분을 관리하고 발행한다</code> | <code>Manage and publish the documentation parts of the Kubernetes release cycle</code></li>
</ul>
<p>위와 같은 역할을 수행합니다.</p>
<blockquote>
<p>기여를 하기에 앞서 쿠버네티스 문서는 <code>GitHub</code> 와 <code>Markdown</code>, <code>Hugo(정적 사이트 생성기)</code> 를 사용하여 문서 관리를 합니다. (깃허브 사용에 익숙하다면, 기여에 많은 도움이 됩니다!)</p>
</blockquote>
<h2 id="1-cla-서명하기">1. CLA 서명하기</h2>
<p>쿠버네티스 프로젝트에 코드를 한 줄이라도 올리기 위해서는 법적 절차가 필요합니다. 바로 <strong>CNCF CLA(Contributor License Agreement)</strong> 서명입니다.</p>
<ul>
<li><strong>Q1) 왜 하나요?</strong><ul>
<li>→ 내가 기여한 내용이 오픈소스 표준에 따라 사용될 수 있음을 법적으로 동의하는 과정입니다.</li>
</ul>
</li>
<li><strong>Q2) 어떻게 하나요?</strong><ul>
<li>→ CNCF CLA 사이트에 접속해 GitHub 계정으로 로그인한 뒤, 개인 기여자라면 <strong>ICLA</strong>에 서명하면 됩니다. 서명 시 이메일 주소는 반드시 GitHub 계정에 등록된 기본 이메일과 일치해야 커밋이 인증됩니다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>⚠️ CLA 서명은 Pull Request를 생성할 때 자동으로 확인되며, 서명이 되지 않은 경우 해당 PR은 Merge 될 수 없습니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/1bbdfd2c-47ee-4ed7-bf8c-4d478e6213d0/image.png" alt=""></p>
<p>→ 자세한 설명은 <a href="https://github.com/kubernetes/community/blob/master/CLA.md">kubernetes / community - The Contributor License Agreement
</a> 의 내용을 참고하면 됩니다! :D</p>
<h2 id="2-기여할-내용-찾기-issue--pr">2. 기여할 내용 찾기 (Issue &amp; PR)</h2>
<p>무작정 문서를 수정하기보다, 현재 저장소에서 어떤 작업이 필요한지 확인하는 것이 좋습니다.</p>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/ea102b34-b775-4a41-bcce-801dd2db6099/image.png" alt=""></p>
<ul>
<li><strong>Issue 확인:</strong> <a href="https://github.com/kubernetes/website/issues">kubernetes/website 저장소</a>에서 <code>language/ko</code> 라벨이 붙은 이슈를 찾아보면 됩니다.</li>
<li><strong>직접 제안:</strong> 오타나 잘못된 번역을 발견했다면 직접 이슈를 생성하여 기여 의사를 밝힐 수 있습니다. (신규 문서 혹은 outdated 문서를 직접 업데이트 하고 싶은 경우에도 이슈를 생성할 수 있습니다.^^)</li>
</ul>
<h2 id="3-로컬-작업-환경-설정하기">3. 로컬 작업 환경 설정하기</h2>
<p>쿠버네티스 문서는 <strong>Hugo</strong>라는 정적 사이트 생성기를 사용합니다. 내가 수정한 내용이 웹에서 어떻게 보일지 미리 확인해야 합니다.</p>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/ac8542f6-328a-4899-b88c-dfb1c718f3d0/image.png" alt=""></p>
<ol>
<li><strong>Fork &amp; Clone:</strong> <code>kubernetes/website 저장소</code> 를 내 계정으로 포크하고 로컬로 가져옵니다.</li>
<li><strong>Upstream 설정</strong>: 원본 저장소를 <code>upstream</code> 으로 등록하여 최신 변경 사항을 수시로 반영해야 합니다.(<code>git fetch &amp; pull</code>)</li>
<li><strong>브랜치 생성:</strong> 작업할 브랜치를 만듭니다. (예: <code>fix-typo-in-deployment</code>)</li>
<li><strong>Hugo 실행:</strong> 로컬에서 <code>hugo server</code> 명령어를 입력해 브라우저(localhost:1313)에서 수정 사항을 실시간으로 확인합니다.<ul>
<li><strong>Tip</strong>: 로컬 환경에서 쿠버네티스 website를 실행하는 <a href="https://github.com/kubernetes/website?tab=readme-ov-file#the-kubernetes-documentation">방법</a>은 다양하기 때문에 해당 부분을 README 참고하시길 바랍니다.)</li>
</ul>
</li>
</ol>
<h2 id="4-문서-수정-및-규칙-준수">4. 문서 수정 및 규칙 준수</h2>
<p>한글 문서 기여 시 가장 중요한 것은 <strong><code>일관성</code></strong>입니다.</p>
<ul>
<li><strong>한글화 가이드라인:</strong> SIG-Docs-ko팀이 정한 <a href="https://www.google.com/search?q=https://kubernetes.io/ko/docs/contribute/style-guide/">한글화 스타일 가이드</a>를 <code>반드시</code> 참고하세요!! (예: 존댓말 사용, 기술 용어 번역 원칙 등)</li>
<li><strong>Markdown 규칙:</strong> 마크다운 문법뿐만 아니라 Hugo에서 사용하는 단축 코드(shortcodes)가 깨지지 않도록 주의해야 합니다.</li>
</ul>
<h2 id="5-pull-requestpr-생성-및-리뷰">5. Pull Request(PR) 생성 및 리뷰</h2>
<p>작업이 끝났다면 원본 저장소로 PR을 보냅니다.</p>
<ul>
<li>PR 제목: 한국어 문서의 경우 제목 앞에 <code>[ko]</code> 를 붙여야 합니다. (예시: <code>[ko] Update deployment documentation</code>)</li>
<li><strong>리뷰 프로세스:</strong><ol>
<li><strong>Reviewer</strong>가 내용을 검토하고, 문제가 없다면 <code>/lgtm</code> 명령어를 입력합니다.</li>
<li><strong>Approver</strong>가 최종 확인 후 <code>/approve</code>를 입력하면 비로소 메인 브랜치에 병합(Merge)됩니다.</li>
</ol>
</li>
</ul>
<hr>
<h2 id="도움이-필요하다면-쿠버네티스-slack에-참여하기">도움이 필요하다면? (쿠버네티스 Slack에 참여하기)</h2>
<ul>
<li>쿠버네티스 팀의 도움이 필요하거나 질문이 있을 경우 <strong><a href="https://communityinviter.com/apps/kubernetes/community">쿠버네티스 슬랙 채널</a></strong>을 통해 쿠버네티스 멤버들에게 도움을 요청할 수 있습니다.</li>
<li>한글화 팀의 경우 쿠버네티스 슬랙 내에 <strong><a href="https://kubernetes.slack.com/archives/CA1MMR86S">#kubernetes-docs-ko</a></strong> 채널이 존재합니다. 많은 기여자분들이 활발히 활동을 하고 계시니 편하게 방문해주시면 좋을 것 같습니다!</li>
</ul>
<blockquote>
<p>☺️ Kubernetes 한글화 팀은 신규 기여자를 언제나 환영합니다! 쿠버네티스 한글화에 관심이 있거나, 첫 기여에 관심이 있는 분들은 편하게 질문해주시고 도전해주세요!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[NAT & PAT]]></title>
            <link>https://velog.io/@pizza_loves_me/NAT-PAT</link>
            <guid>https://velog.io/@pizza_loves_me/NAT-PAT</guid>
            <pubDate>Thu, 12 Feb 2026 11:53:47 GMT</pubDate>
            <description><![CDATA[<hr>
<h1 id="📌-nat란">📌 NAT란?</h1>
<ul>
<li>Network Address Translation</li>
<li>NAT는 사설 IP 주소를 공인 IP 주소로 변환하거나, 그 반대의 과정을 수행하는 기술이다. (사설 IP 주소 ↔ 공인 IP 주소)</li>
<li>IPv4 주소 부족 문제를 해결하기 위해 고안되었으며, 내부 네트워크의 보안성을 높이는 역할도 한다.</li>
</ul>
<h2 id="주요-목적">주요 목적</h2>
<ul>
<li><code>IPv4 주소 고갈 해결</code></li>
<li><code>보안성 향상</code>: 내부 네트워크의 IP 주소 체계를 외부로부터 숨길 수 있음. (호스트 은닉)</li>
<li><code>유연성</code>: ISP(인터넷 서비스 제공자)를 변경해도 내부 IP 대역을 유지할 수 있음</li>
</ul>
<h2 id="nat의-동작-방식에-따른-분류">NAT의 동작 방식에 따른 분류</h2>
<h3 id="1-static-nat-11-nat">1. Static NAT (1:1 NAT)</h3>
<ul>
<li>공인 IP와 사설 IP를 <strong>1:1로 고정 매핑</strong>하는 방식</li>
<li>특징: 외부에서 내부 서버에 접근해야 할 때 주로 사용</li>
<li>장점: 설정이 간편함</li>
<li>단점: 공인 IP 절약 효과는 없음(IPv4 주소 고갈 문제 해결 x)</li>
</ul>
<h3 id="2-dynamic-nat-nm-nat">2. Dynamic NAT (N:M NAT)</h3>
<ul>
<li>공인 IP 주소 풀(Pool)을 만들어 놓고, 내부 호스트가 외부로 나갈 때 <strong>남는 공인 IP를 동적으로 할당</strong>하는 방식</li>
<li>특징: 호스트가 통신을 마치면 해당 공인 IP는 다시 풀(Pool)로 반환됨</li>
<li>장점: 일시적으로 IP를 공유할 수 있음</li>
<li>단점: 동시 접속 호스트 수가 공인 IP 개수를 초과하면 통신 불가능</li>
</ul>
<h1 id="📌-pat란">📌 PAT란?</h1>
<ul>
<li>Port Address Translation</li>
<li>PAT는 하나의 공인 IP 주소에 여러 개의 사설 IP 주소를 매핑하는 기술</li>
<li><code>N:1 NAT</code> 라고도 함</li>
<li>주소 변환 시 <strong>포트 번호(Port Number)</strong>를 사용하여 각 세션을 구분</li>
</ul>
<h3 id="pat의-동작-원리">PAT의 동작 원리</h3>
<ol>
<li>내부 호스트 A(<code>192.168.0.10:5000</code>) 가 외부로 패킷을 보냄</li>
<li>NAT 라우터는 이를 공인 IP(<code>203.0.113.1</code>) 로 바꾸면서, 임의의 포트 번호(ex: 10001)를 할당함</li>
<li>NAT Table 에 <code>192.168.0.10:5000 &lt;-&gt; 203.0.113.1:10001</code> 정보를 기록함</li>
<li>외부에서 응답이 오면 테이블을 참조해 다시 내부 호스트 A로 전달함</li>
</ol>
<blockquote>
<p>참고: 실무나 가정용 공유기에서 말하는 NAT는 대부분 이 PAT를 의미함</p>
</blockquote>
<h1 id="📌-nat-vs-pat-비교">📌 NAT vs PAT 비교</h1>
<table>
<thead>
<tr>
<th></th>
<th>Static NAT</th>
<th>Dynamic NAT</th>
<th>PAT (N:1 NAT)</th>
</tr>
</thead>
<tbody><tr>
<td>매핑 관계</td>
<td>1:1 (고정)</td>
<td>N:M (유동)</td>
<td>N:1 (포트 구분)</td>
</tr>
<tr>
<td>공인 IP 소모</td>
<td>많음 (사설 IP당 1개)</td>
<td>중간</td>
<td>적음 (1개로 다수 수용)</td>
</tr>
<tr>
<td>포트 번호 사용</td>
<td>사용 안 함</td>
<td>사용 안 함</td>
<td><strong>필수 사용</strong></td>
</tr>
<tr>
<td>주요 용도</td>
<td>내부 서버 공개</td>
<td>보안 및 일반 통신</td>
<td>일반적인 인터넷 접속</td>
</tr>
</tbody></table>
<h1 id="📌-nat의-장단점-및-한계">📌 NAT의 장단점 및 한계</h1>
<h3 id="장점">장점</h3>
<ul>
<li><code>보안</code>: 외부에서 내부 사설 IP로 직접 접근하는 것이 기본적으로 차단됨</li>
<li><code>비용 절감</code>: 소수의 공인 IP만으로 대규모 네트워크 구축이 가능함</li>
</ul>
<h3 id="단점-및-고려사항">단점 및 고려사항</h3>
<ul>
<li><code>성능 부하</code>: 패킷마다 헤더를 수정하고 체크섬(Checksum)을 재계산하므로 라우터에 부하가 발생함</li>
<li><code>End-to-End 추적 불가</code>: IP가 변환되므로 로그 분석이나 추적이 복잡해질 수 있음</li>
<li><code>일부 프로토콜 호환성</code>: IP 주소 정보를 데이터 페이로드 내에 포함하는 프로토콜(FTP, SIP 등)은 별도의 <code>ALG(Application Layer Gateway)</code> 처리가 필요</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부트캠프 - 38일차] 2/12.목 - kubernetes]]></title>
            <link>https://velog.io/@pizza_loves_me/%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-38%EC%9D%BC%EC%B0%A8-212.%EB%AA%A9-kubernetes</link>
            <guid>https://velog.io/@pizza_loves_me/%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-38%EC%9D%BC%EC%B0%A8-212.%EB%AA%A9-kubernetes</guid>
            <pubDate>Thu, 12 Feb 2026 11:32:32 GMT</pubDate>
            <description><![CDATA[<hr>
<h1 id="kubernetes-basic">Kubernetes Basic</h1>
<h1 id="namespace">Namespace</h1>
<h2 id="분리성-확보">분리성 확보</h2>
<h3 id="namespace-1">namespace</h3>
<ul>
<li><p>가상적인 쿠버네티스 클러스터 분리 기능</p>
</li>
<li><p>완전한 분리 개념이 아니기 때문에 용도는 제한되지만, 하나의 쿠버네티스 클러스터를 여러팀에서 사용하거나 서비스 환경/스테이징 환경/개발 환경으로 구분하는 경우 사용 가능</p>
</li>
<li><p>기본 설정에서 제공하는 Namespace</p>
<ul>
<li><code>kube-system</code><ul>
<li>쿠버네티스 클러스터 구성 요소와 애드온이 배포될 네임스페이스</li>
</ul>
</li>
<li><code>kube-public</code><ul>
<li>모든 사용자가 사용할 수 있는 ConfigMap 등을 배치하는 네임스페이스</li>
</ul>
</li>
<li><code>kube-node-lease</code><ul>
<li>노드의 하트비트 정보를 저장하기 위한 Lease 리소스가 저장되는 네임스페이스</li>
</ul>
</li>
<li><code>default</code><ul>
<li>기본 네임스페이스</li>
</ul>
</li>
</ul>
</li>
<li><p>관리형 서비스나 구축 도구로 구축된 경우 대부분의 쿠버네티스 클러스터는 RBAC이 기본값으로 활성화되어 있으며 일부 환경에서는 네트워크 정책을 사용할 수 있음</p>
</li>
<li><p>RBAC은 클러스터 조작에 대한 권한을 네임스페이스별로 구분할 수 있고 네트워크 정책과 함께 사용하여 네임스페이스 간의 통신을 제어할 수 있는 구조</p>
</li>
<li><p>네임스페이스만으로는 높은 분리성을 확보하기 어렵지만 RBAC 이나 네트워크 정책을 사용하면 분리성을 높일 수 있음</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/d0b1158d-1e91-479f-b39a-520721faf88f/image.png" alt=""></p>
<h1 id="kubectl">kubectl</h1>
<h2 id="kubectl-1">kubectl</h2>
<ul>
<li>쿠버네티스에서 클러스터 조작은 모든 쿠버네티스 마스터의 API를 통해 수행</li>
<li>직접 API에 요청을 보내거나 클라이언트 라이브러리를 사용하여 클러스터를 조작할 수도 있지만, 수동으로 조작하는 경우에는 CLI 도구인 kubectl 을 사용하는 것이 일반적</li>
</ul>
<h2 id="인증-정보와-컨텍스트">인증 정보와 컨텍스트</h2>
<h3 id="인증-정보">인증 정보</h3>
<ul>
<li>kubectl 이 쿠버네티스 마스터와 통신할 때는 접속 대상의 서버 정보, 인증 정보 등이 필요</li>
<li>kubectl 은 <code>kubeconfig</code>(기본 위치는 ~/.kube/config)에 쓰여 있는 정보를 사용하여 접속</li>
<li>kubeconfig 도 매니페스트와 동일한 형식으로 작성</li>
</ul>
<h3 id="kubeconfig-파일">kubeconfig 파일</h3>
<pre><code class="language-bash">apiVersion: v1
kind: Config
preferences: {}
clusters:                  # 1. 클러스터 목록 시작
- name: sample-cluster     # 리스트 아이템(-) 뒤에 한 칸 공백
  cluster:                 # name과 같은 레벨
    server: https://localhost:6443 # 부모(cluster)보다 2칸 더 들여쓰기
users:                     # 2. 유저 정보 시작
- name: sample-user
  user:
    client-certificate-data: LS0tLs1CRUd3Ti...
    client-key-data: l_S0tl_S1CRUdJTi...
contexts:                  # 3. 컨텍스트(조합) 시작
- name: sample-context
  context:
    cluster: sample-cluster
    namespace: default
    user: sample-user
current-context: sample-context # 현재 활성화된 컨텍스트 이름</code></pre>
<ul>
<li>kubeconfig 에서 구체적으로 설정이 이루어지는 부분은 <code>cluster/users/contexts</code> 세 가지</li>
<li>세 가지 설정 항목은 모두 배열로 되어 있어 여러 대상을 등록할 수 있음</li>
<li><code>cluster</code> 에는 접속 대상 클러스터 정보를 정의</li>
<li><code>users</code> 에는 인증 정보를 정의<ul>
<li>사용자 인증에는 <code>X.509</code> 클라이언트 <code>인증서/토큰/패스워드/웹훅</code> 등 다양한 방식을 사용할 수 있음</li>
</ul>
</li>
<li><code>contexts</code> 에는 cluster 와 user 그리고 네임스페이스를 지정한 것을 정의</li>
</ul>
<h3 id="kubeconfig-설정-변경">kubeconfig 설정 변경</h3>
<ul>
<li>파일을 직접 수정</li>
<li>명령어 이용</li>
</ul>
<pre><code class="language-bash"># 클러스터(prd-cluster) 정의를 추가, 변경
kubectl config set-cluster prd-cluster --server=https://localhost:6443

# 인증 정보 정의를 추가, 변경
kubectl config set-credentials admin-user --client-certificate=./sample.crt --
client-key=./sample.key --embed-certs=true

# 컨텍스트 정의(클러스터/인증 정보/네임스페이스 정의)를 추가 및 변경
kubectl config set-context prd-admin --cluster=prd-cluster --user=admin-user
--namespace=default

# 현재 컨텍스트 확인
kubectl config current-context

# 명령어를 실행할 때 컨텍스트 지정
kubectl get pods --context</code></pre>
<h2 id="리소스-생성삭제갱신">리소스 생성/삭제/갱신</h2>
<h3 id="리소스-생성---kubectl-run">리소스 생성 - <code>kubectl run</code></h3>
<ul>
<li>가장 간단하게 pod를 만드는 방식 중 하나</li>
<li>run 명령어는 실행 커맨드에 각종 정보를 입력해서 생성</li>
<li>간편하지만 실행 시 입력한 정보가 남지 않기 때문에 재실행하기가 어렵고 문제가 발생했을 때 원인을 찾거나 해결 방법을 공유하는 등이 불편</li>
<li>테스트나 간단한 파드를 생성할 때는 유용하지만 많은 configuration 을 활용한다면 불편</li>
<li>nginx 파드 생성</li>
</ul>
<pre><code class="language-bash">kubectl run nginx --image=nginx

# 확인
kubectl get pods</code></pre>
<ul>
<li>명령어로 만들 때는 종류를 지정해서 생성해야 함</li>
<li><code>kubectl create deployment 디플로이먼트이름 --image=이미지이름</code></li>
</ul>
<pre><code class="language-bash">kubectl create deployment dpy-nginx --image=nginx

# 확인
kubectl get pods</code></pre>
<h3 id="리소스-생성---kubectl-create">리소스 생성 - <code>kubectl create</code></h3>
<ul>
<li>리소스가 존재하는 경우 에러</li>
<li><code>kubectl create -f 리소스파일경로</code></li>
<li>nginx 파드 생성</li>
</ul>
<pre><code class="language-bash"># 리소스 파일 작성(sample-pod.yaml)
apiVersion: v1
kind: Pod
metadata:
 name: sample-pod
spec:
 containers:
 - name: nginx-container
 image: nginx:1.16

 # 리소스 생성
 kubectl create -f sample-pod.yaml     

     # 존재하지 않는 경우
     pod/sample-pod created

     # 존재하는 경우
     Error from server (AlreadyExists): error when creating &quot;sample-pod.yaml&quot;: pods
&quot;sample-pod&quot; already exists</code></pre>
<h3 id="리소스-삭제---kubectl-delete">리소스 삭제 - <code>kubectl delete</code></h3>
<ul>
<li>리소스 파일로 만든 리소스 삭제<ul>
<li><code>kubectl delete -f 리소스파일</code></li>
</ul>
</li>
</ul>
<pre><code class="language-bash">kubectl delete -f sample-pod.yaml

# 리소스가 존재하는 경우
pod &quot;sample-pod&quot; deleted

# 리소스가 존재하지 않는 경우
Error from server (NotFound): error when deleting &quot;sample-pod.yaml&quot;: pods
&quot;sample-pod&quot; not found</code></pre>
<ul>
<li><p>리소스 종류와 이름을 이용하여 삭제</p>
<ul>
<li><code>kubectl delete 리소스종류 [리소스이름]</code><ul>
<li>리소스 이름 대신에 <code>--all</code> 을 사용하면 모든 리소스 삭제</li>
</ul>
</li>
</ul>
</li>
<li><p>kubectl 명령어 실행은 바로 완료되지만 쿠버네티스에 의한 실제 리소스 처리는 비동기로 실행되어 처리가 바로 완료되지 않음</p>
</li>
<li><p><code>--wait</code> 옵션을 사용하면 리소스의 삭제 완료를 기다렸다가 명령어 실행을 종료할 수 있는데 모든 <code>Finalizer</code>(삭제 표시된 리소스를 완전히 삭제하기 전에 특정 조건이 충족될 때까지 기다리도록 쿠버네티스에 지시하는 네임스페이스 키) 실행이 완료될 때까지 대기</p>
</li>
<li><p>리소스를 강제로 즉시 삭제하려면 정지까지 유예 기간을 0으로 하는 <code>--grace-period -0</code> 옵션과 강제로 삭제하는 <code>--force</code> 옵션을 사용</p>
<ul>
<li>쿠버네티스 1.8 이후부터는 <code>--force</code> 옵션 지정만으로 <code>--grace-period 0</code> 옵션도 자동으로 부여</li>
</ul>
</li>
<li><p>비슷한 옵션으로 <code>--now</code> 가 있지만 이 옵션은 <code>--grace-period 1</code> 과 동일하여 바로 삭제가 안 되는 경우가 있으니 주의</p>
</li>
<li><p>리소스 삭제: 삭제 완료 대기</p>
<ul>
<li><code>kubectl delete -f sample-pod.yaml --wait</code></li>
</ul>
</li>
<li><p>리소스 삭제: 즉시 강제 삭제</p>
<ul>
<li><code>kubectl delete -f sample-pod.yaml --grace-period 0 --force</code></li>
</ul>
</li>
</ul>
<h3 id="리소스--업데이트---kubectl-apply">리소스  업데이트 - <code>kubectl apply</code></h3>
<ul>
<li>변경 부분이 있으면 적용하고 없으면 적용하지 않으며 리소스가 없을 때는 kubectl create 명령어와 동일하게 신규로 리소스를 생성</li>
<li>쿠버네티스는 생성한 리소스 상태를 내부에 기록하는데 한 번 기록된 리소스의 필드(내부 상태의 설정 항목) 대부분은 리소스 업데이트에 따라 변경할 수 있지만 리소스에 따라서는 생성 후에 변경할 수 없는 필드도 존재</li>
</ul>
<pre><code class="language-bash"># 없으면 생성
kubectl apply -f sample-pod.yaml
    # &gt;&gt; pod/sample-pod created

# 변경 내용이 없으면 적용하지 않음
kubectl apply -f sample-pod.yaml
    # &gt;&gt; pod/sample-pod unchanged</code></pre>
<ul>
<li>가동 중인 파드 이미지는 <code>kubectl get pods</code> 명령어에 옵션을 사용하여 확인할 수 있음<ul>
<li><code>kubectl get pods -ㅐ jsonpath=&#39;{.items[*].spec.containers[*].image}&#39;</code></li>
</ul>
</li>
</ul>
<h3 id="리소스-생성에도-kubectl-apply-를-사용해야-하는-이유">리소스 생성에도 kubectl apply 를 사용해야 하는 이유</h3>
<ul>
<li>생성에는 kubectl create 를 사용하고 업데이트 때는 kubectl apply 를 사용해야 한다는 구분이 불필요하기 때문인데 스크립트나 CI/CD 등에 포함될 것을 생각하면 조건 분기는 하지 않는 것이 좋음</li>
<li>kubectl create 와 kubectl apply 를 섞어서 사용하면 kubectl apply 를 실행할 때 변경 사항을 검출하지 못할 경우가 있기 때문<ul>
<li><code>kubectl apply</code> 로 적용된 변경 사항은 <code>이전에 적용한 매니페스트</code>, <code>현재 클러스터에 등록된 리소스 상태</code>, <code>이번에 적용할 매니페스트</code>, 이렇게 세 종류에서 산출</li>
<li><strong>이번에 추가되거나 변경될 필드</strong>는 <code>현재 리소스 상태</code>와 <code>이번에 적용할 매니페스트</code>를 비교하여 산출</li>
<li>삭제된 필드는 <code>이전에 적용한 매니페스트</code>와 <code>이번에 적용할 매니페스트</code>를 기준으로 산출</li>
<li><code>--save-config</code> 옵션 없이 kubectl create를 사용하여 리소스를 생성한 경우에는 이전 상태가 저장되지 않기 때문에 이번에 적용할 매니페스트에서 특정 필드를 삭제하고 싶은 경우에 변경 사항을 산출하지 못하고 의도한 대로 반영되지 않는 필드가 생성될 수 있음</li>
</ul>
</li>
<li>이전에 적용한 매니페스트가 저장되지 않는 경우는 kubectl apply 를 실행할 때 다음과 같은 경고가 출력됨</li>
</ul>
<pre><code class="language-bash">Warning: kubectl apply should be used on resource created by either
kubectl create --save-config or kubectl apply pod/sample-pod configured</code></pre>
<h2 id="server-side-apply">Server-side Apply</h2>
<h3 id="리소스-필드-수정">리소스 필드 수정</h3>
<ul>
<li>쿠버네티스에서는 사용자가 사용하는 kubectl 외에 다양한 시스템 구성 요소가 리소스 필드를 자동으로 수정할 수 있음</li>
<li>현재 변경 사항의 산출은 클라이언트 측(kubectl)에서 계산하여 패치 요청을 보내기 때문에(<code>Client-side apply</code>) 여러 사용자나 구성 요소가 동시에 같은 필드를 변경하는 경우 경합 현상이 발생할 수 있는데 이 문제를 해결하기 위해 필드를 변경한 구성 요소(kubectl 이나 구성 요소의 이름)를 기록하는 기능과 서버 측에서 변경 사항을 계산하는 기능이 도입됨</li>
<li><code>kubectl set image</code> 명령어에서는 매니페스트 파일을 사용하지 않고 서버 측 정보를 직접 수정하여 컨테이너 이미지를 변경할 수 있는데 이 명령어는 매니페스트를 수정하지 않기 때문에 kubectl apply 명령어로 매니페스트를 다시 적용하면 컨테이너 이미지가 원래대로 돌아가버리는 예상치 못한 변경이 발생</li>
<li>그 외에 사용자가 존재하는 필드를 수정하여 매니페스트를 재적용하는 순간에 지금까지 운영 중인 시스템 구성 요소로 수행되던 다른 필드에 대한 변경을 예기치 않게 덮어 씌워 매니페스트 내용으로 되돌려 버리는 경우 등이 있을 수 있음</li>
</ul>
<h3 id="서버-사이드의-정보를-이용해서-적용">서버 사이드의 정보를 이용해서 적용</h3>
<ul>
<li>Server-side apply 를 사용하면 이러한 충돌을 감지할 수 있음</li>
<li>Server-side apply 는 충돌 감지만 하기 때문에 실제 충돌이 발생할 때는 별도로 충돌 내용을 해결해야 함</li>
<li>서버 측은 쿠버네티스 1.18 이후 기본값으로 활성화 되어 있지만, 클라이언트 측은 기본값으로 활성화 되어 있지 않음</li>
<li>클라이언트 측에서 Server-side apply 를 활성화 하려면 <code>--server-side</code> 옵션을 사용</li>
</ul>
<pre><code class="language-bash"># sample-pod.yaml 파일 수정
apiVersion: v1
kind: Pod
metadata:
 name: sample-pod
spec:
 containers:
 - name: nginx-container
 image: nginx:1.16

# 리소스 배포
kubectl apply -f sample-pod.yaml
    # &gt;&gt; pod/sample-pod created

# 이미지 확인
kubectl get pods -o jsonpath=&#39;{.items[*].spec.containers[*].image}&#39;
    # &gt;&gt; nginx:1.16     </code></pre>
<pre><code class="language-bash"># 이미지 수정
kubectl set image pod sample-pod nginx-container=nginx:1.17
    # &gt;&gt; pod/sample-pod image updated

# 이미지 확인
kubectl get pods -o jsonpath=&#39;{.items[*].spec.containers[*].image}&#39;
    # &gt;&gt; nginx:1.17

# 리소스 배포
kubectl apply -f sample-pod.yaml
    # pod/sample-pod created

# 이미지 확인
kubectl get pods -o jsonpath=&#39;{.items[*].spec.containers[*].image}&#39;
    # &gt;&gt; nginx:1.16 </code></pre>
<blockquote>
<h2 id="📌-명령형imperative-vs-선언적declarative">📌 명령형(Imperative) vs 선언적(Declarative)</h2>
</blockquote>
<h3 id="kubectl-set-image-명령형-방식"><code>kubectl set image</code> (명령형 방식)</h3>
<blockquote>
</blockquote>
<ul>
<li>클러스터의 실행 중인 객체(Live Object)는 즉시 변경되지만, 로컬에 있는 YAML 파일은 수정되지 않음<blockquote>
</blockquote>
<h3 id="kubectl-apply--f-선언적-방식"><code>kubectl apply -f</code> (선언적 방식)</h3>
<blockquote>
</blockquote>
</li>
<li>쿠버네티스는 파일의 내용과 현재 실행 중인 상태(1.17)를 비교하고, 파일에 적힌대로 다시 1.16으로 되돌려버림.</li>
</ul>
<ul>
<li>실습</li>
</ul>
<pre><code class="language-bash"># 모든 파드 삭제
kubectl delete pod --all

# 리소스 배포
kubectl apply -f sample-pod.yaml --server-side

# 이미지 확인
kubectl get pods -o jsonpath=&#39;{.items[*].spec.containers[*].image}&#39;

# 이미지 수정
kubectl set image pod sample-pod nginx-container=nginx:1.17

# 이미지 확인
kubectl get pods -o jsonpath=&#39;{.items[*].spec.containers[*].image}&#39;

# 리소스 배포
kubectl apply -f sample-pod.yaml
    # &gt;&gt; error: Apply failed with 1 conflict: conflict with &quot;kubectl-set&quot; using v1:
    # &gt;&gt; .spec.containers[name=&quot;nginx-container&quot;].image
    # &gt;&gt; Please review the fields above--they currently have other managers. Here
    # &gt;&gt; are the ways you can resolve this warning:

# 충돌을 무시하고 적용
kubectl apply -f sample-pod.yaml --server-side --force-conflicts
    # &gt;&gt; pod/sample-pod serverside-applied
</code></pre>
<h2 id="파드-재시작">파드 재시작</h2>
<h3 id="개요">개요</h3>
<ul>
<li>Deployment 등의 리소스와 연결되어 있는 모든 파드를 재가동<ul>
<li><code>-rollout restart</code></li>
</ul>
</li>
<li>파드 기동 시 처리를 재실행하고 싶을 때나 시크릿 리소스에서 참조되는 환경 변수를 변경하고 싶을 때 사용</li>
<li>리소스와 연결되어 있지 않은 단독 파드에는 사용할 수 없음</li>
</ul>
<pre><code class="language-bash"># sample-deployment.yaml 파일을 생성하고 작성
apiVersion: apps/v1
kind: Deployment
metadata:
    name: sample-deployment
spec:
    replicas: 3
    selector:
        matchLabels:
            app: sample-app
    template:
        metadata:
            labels:
                app: sample-app
        spec:
            containers:
            - name: nginx-container
                image: nginx</code></pre>
<pre><code class="language-bash"># 리소스 생성
kubectl apply -f sample-pod.yaml
    # &gt;&gt; pod/sample-pod created

kubectl apply -f sample-deployment.yaml
    # &gt;&gt; deployment.apps/sample-deployment created

# 파드 재시작
kubectl rollout restart deployment sample-deployment
    # &gt;&gt; deployment.apps/sample-deployment restarted

kubectl rollout restart pod sample-pod 
    # &gt;&gt; error: pods &quot;sample-pod&quot; restarting is not supported</code></pre>
<h2 id="generatename">generateName</h2>
<h3 id="개요-1">개요</h3>
<ul>
<li>기본적으로 kubectl apply 명령어를 사용하는 것이 바람직하지만 kubectl create 를 사용하여 난수가 있는 이름의 리소스를 생성할 수 있음</li>
<li><code>metadata.name</code> 대신 <code>metadata.generateName</code> 을 지정하고 리소스를 생성하면 그 이름에 접두사(<code>prefix</code>)를 붙여 이름이 자동으로 생성</li>
<li>실습</li>
</ul>
<pre><code class="language-bash"># sample-generatename.yaml 파일을 생성하고 작성
apiVersion: v1
kind: Pod
metadata:
 generateName: sample-generatename
spec:
    containers:
     - name: nginx-container
         image: nginx</code></pre>
<pre><code class="language-bash"># 리소스 생성
kubectl create -f sample-generatename.yaml
    # &gt;&gt; pod/sample-generatename-mdlxg created

kubectl create -f sample-generatename.yaml
    # &gt;&gt; pod/sample-generatename-7gtmr created

kubectl create -f sample-generatename.yaml
    # &gt;&gt; pod/sample-generatename-bjwv9 created

# 리소스 확인
kubectl get pods

# 모든 리소스 삭제
kubectl delete all --all
kubectl get all
    # &gt;&gt; No resources found in default namespace.</code></pre>
<h2 id="리소스-상태-체크와-대기">리소스 상태 체크와 대기</h2>
<h3 id="개요-2">개요</h3>
<ul>
<li>kubectl 명령어를 연속적으로 실행하여 리소스를 조작할 때는 다음 명령어를 실행하기 전에 그때까지 작업한 리소스가 의도한 상태가 된 후 다음 명령어를 실행해야 하는 경우가 있음</li>
<li>이때 사용할 수 있는 것이 <code>kubectl wait</code> 명령어인데, 실행하면 <code>--for</code> 옵션에 지정한 상태가 되기까지 kubectl 명령어가 최대 <code>--timeout</code> 옵션에 지정하는 시간(기본값은 30초)까지 종료하지 않고 대기</li>
<li>실습</li>
</ul>
<pre><code class="language-bash"># 파드 3개 생성
kubectl create -f sample-pod.yaml
    # &gt;&gt; pod/sample-pod created

kubectl create -f sample-generatename.yaml
    # &gt;&gt; pod/sample-generatename-h6qpz created

kubectl create -f sample-generatename.yaml
    # &gt;&gt; pod/sample-generatename-kppck created

# sample-pod 가 정상적으로 기동할 때(Ready 상태가 될 때)까지 대기
kubectl wait --for=condition=Ready pod/sample-pod
    # &gt;&gt; pod/sample-pod condition met

# 모든 파드가 삭제될 때까지 파드마다 5초씩 대기하는데 아직 파드를 삭제하지 않았으므로 타임아웃
kubectl wait --for=delete pod --all --timeout=5s
    # &gt;&gt; timed out waiting for the condition on pods/sample-generatename-h6qpz
    # &gt;&gt; client rate limiter Wait returned an error: context deadline exceeded
    # &gt;&gt; client rate limiter Wait returned an error: context deadline exceeded

# 모든 파드를 삭제한 후 곧바로 kubectl wait를 실행
kubectl delete pod --all --wait=false
    # &gt;&gt; pod &quot;sample-generatename-h6qpz&quot; deleted
    # &gt;&gt; pod &quot;sample-generatename-kppck&quot; deleted
    # &gt;&gt; pod &quot;sample-pod&quot; deleted

# 모든 파드가 삭제될 때까지 대기
kubectl wait --for=delete pod --all

# 리소스에 매니패스트 파일 사용 가능
kubectl apply -f sample-pod.yaml
    # &gt;&gt; pod/sample-pod created

kubectl wait --for=condition=Ready -f sample-pod.yaml
    # &gt;&gt; pod/sample-pod condition met</code></pre>
<h2 id="manifest-파일-설계">Manifest 파일 설계</h2>
<h3 id="하나의-매니페스트-파일에-여러-리소스를-정의">하나의 매니페스트 파일에 여러 리소스를 정의</h3>
<ul>
<li>매니패스트 파일은 여러 리소스를 한 개의 매니페스트 파일에 정의할 수 있기 때문에 어떤 서비스에서 사용할 여러 종류의 리소스를 한 개의 매니페스트 파일로 통합 가능</li>
<li>일반적인 사용 사례로는 파드를 기동하는 워크로드 API 카테고리의 리소스와 외부에 공개하는 서비스 API 카테고리의 리소스를 매니페스트에 통합하여 작성하는 방법을 생각해볼 수 있는데 이 경우 그 한 개의 매니페스트 파일을 적용하는 것만으로 서비스를 외부에 공개할 수 있게 됨</li>
<li>실행 순서를 정확하게 지켜야 하거나 리소스 간의 결합도를 높이고 싶다면 한 개의 매니페스트를 사용하는 것이 좋지만 공통으로 사용하는 설정 파일(configmap 리소스)이나 패스워드(시크릿 리소스) 등은 여러 리소스에서 사용되는 경우가 있기 때문에 공통으로 사용되는 리소스는 별도 매니페스트로 분리하는 것이 좋음</li>
<li>실습</li>
</ul>
<pre><code class="language-bash"># 매니페스트 적용
kubectl apply -f sample-multi-resource-manifest.yaml
    # &gt;&gt; deployment.apps/order1-deployment created
    # &gt;&gt; service/order2-service created</code></pre>
<ul>
<li>매니페스트 파일 내의 일부 리소스 정의 부분에 문법 에러 등으로 문제가 발생한 경우 이후에 정의된 리소스는 적용되지 않는다는 점에 주의해야 함</li>
</ul>
<h3 id="여러-매니페스트-파일을-동시에-적용">여러 매니페스트 파일을 동시에 적용</h3>
<ul>
<li>여러 매니페스트 파일을 동시에 적용하려면 디렉터리 안에 적용하고 싶은 여러 매니페스트 파일을 배치해두고 kubectl apply 명령어를 실행할 때 그 디렉터리를 지정</li>
<li>파일명순으로 매니페스트 파일이 적용되기 때문에 순서를 제어하고 싶을 때는 파일명 앞에 연번의 인덱스 번호 등을 지정하여 사용</li>
<li><code>kubectl apply -f ./ -R</code> 과 같이 <code>-R</code> 옵션을 사용하면 재귀적으로 디렉터리 안에 존재하는 매니페스트 파일을 적용할 수도 있음</li>
</ul>
<h3 id="매니페스트-설계-방침">매니페스트 설계 방침</h3>
<ul>
<li>아주 규모가 크지 않을 경우에는 시스템 전체를 구성하기 위한 모든 마이크로서비스의 매니페스트 파일으르 하나의 디렉토리로 통합하여 사용</li>
<li>한 개의 디렉토리에 파일 통합이 어려울 정도로 거대한 시스템인 경우 분리가 가능하다면 서브 시스템이나 부서별로 디렉터리를 나누어서 사용</li>
<li>마이크로서비스마다 디렉터리로 구분하여 리소스 종류별로 파일을 생성할 수도 있는데 가독성이 높아진다는 장점이 있지만, 적용 순서 제어가 어렵다는 단점도 있음</li>
</ul>
<h2 id="어노테이션과-레이블">어노테이션과 레이블</h2>
<ul>
<li>쿠버네티스에는 각 리소스에 대해 어노테이션과 레이블이라는 메타데이터를 부여할 수 있는데, 둘 다 쿠버네티스가 리소스를 관리할 때 사용한다는 점에서 비슷하지만 용도가 다름</li>
<li><code>Label</code> : 리소스를 분류/검색하는 태그</li>
<li><code>Annotation</code> : 추가적인 긴 설명/메타데이터 저장소</li>
</ul>
<table>
<thead>
<tr>
<th>구분</th>
<th>Label</th>
<th>Annotation</th>
</tr>
</thead>
<tbody><tr>
<td>주요 목적</td>
<td>리소스 분류, 선택, 검색에 사용</td>
<td>리소스에 추가 설명/메타데이터</td>
</tr>
<tr>
<td>저장(Storage)</td>
<td>etcd (인덱싱됨)</td>
<td>etcd (비인덱싱됨)</td>
</tr>
<tr>
<td>검색/Selector 지원</td>
<td>지원 (Label Selector로 매칭)</td>
<td>지원 안 함 (Selector 불가)</td>
</tr>
<tr>
<td>Key 형식</td>
<td>prefix/name (DNS prefix 허용)</td>
<td>동일</td>
</tr>
<tr>
<td>Key 길이 제한</td>
<td>253자 이하</td>
<td>253자 이하</td>
</tr>
<tr>
<td>Value 길이 제한</td>
<td>63자 이하</td>
<td>262,144자(256KB) 이하</td>
</tr>
<tr>
<td>Value 용도</td>
<td>간단한 구분자</td>
<td>설명, 설정, 빌드 정보 등</td>
</tr>
</tbody></table>
<h3 id="어노테이션">어노테이션</h3>
<ul>
<li>어노테이션은 <code>metadata.annotations</code> 로 설정할 수 있는 메타데이터</li>
<li>어노테이션은 리소스에 대한 메모 같은 것으로 어노테이션 자체는 단순한 Key-Value 값이기 때문에 어노테이션 값으로 어떤 처리를 하는 시스탬 구성 요소가 없을 경우에는 아무 일도 일어나지 않음</li>
<li>리소스에 의미를 가지지 않는 메모를 하고 싶을 경우에도 사용할 수 있고, 값에 수치를 사용하는 경우는 큰따옴표로 묶어야 함</li>
<li>매니페스트에 작성할 수도 있지만 kubectl 에서 직접 부여하는 것도 가능</li>
<li>실습</li>
</ul>
<pre><code class="language-bash"># sample-annotations.yaml 파일 생성 및 작성
apiVersion: v1
kind: Pod
metadata:
 name: sample-annotations
 annotations:
     annotation1: val1
     annotation2: &quot;200&quot;
spec:
 containers:
 - name: nginx-container
     image: nginx:1.16</code></pre>
<pre><code class="language-bash"># 리소스 생성
kubectl apply -f sample-annotations.yaml
    # &gt;&gt; pod/sample-annotations created

# 리소스 생성 후 어노테이션 부여
kubectl annotate pods sample-annotations annotations3=val3
    # &gt;&gt; pod/sample-annotations annotated    

# 어노테이션 덮어쓰기
kubectl annotate pods sample-annotations annotations3=val3-new --overwrite
    # &gt;&gt; pod/sample-annotations annotated

# 어노테이션 확인
kubectl describe pod sample-annotations

# 어노테이션 삭제
kubectl annotate pods sample-annotations annotations3-</code></pre>
<ul>
<li>용도<ul>
<li>시스템 구성 요소를 위한 데이터 저장<ul>
<li>시스템 구성 요소가 리소스를 업데이트할 때 사용하기 위해서 자동으로 부여</li>
</ul>
</li>
<li>모든 환경에서 사용할 수 없는 설정</li>
<li>정식으로 통합되기 전의 기능을 설정</li>
</ul>
</li>
</ul>
<h3 id="레이블">레이블</h3>
<ul>
<li>metadata.lables 에 설정할 수 있는 메타 데이터로 리소스를 구분하기 위한 정보 같은 것</li>
<li>sample-label.yaml 파일 생성 및 작성</li>
</ul>
<pre><code class="language-bash">apiVersion: v1
kind: Pod
metadata:
 name: sample-label
 labels:
     label1: val1
     label2: val2
spec:
 containers:
 - name: nginx-container
     image: nginx:1.16</code></pre>
<pre><code class="language-bash"># 리소스 생성
kubectl apply -f sample-label.yaml
    # &gt;&gt; pod/sample-label created

# 리소스 생성 후 부여
kubectl label pods sample-label label3=val3
    # &gt;&gt; pod/sample-label labeled

# 어노테이션 덮어쓰기
kubectl label pods sample-label label3=val3-new --overwrite
    # &gt;&gt; pod/sample-label labeled

# 레이블 확인
kubectl describe pod sample-label

# 레이블 삭제
kubectl label pods sample-label label3-
    # &gt;&gt; pod/sample-label labeled</code></pre>
<ul>
<li>개발자가 사용하는 레이블<ul>
<li>개발자가 많은 리소스를 효율적으로 관리하는 데 레이블을 유용하게 사용</li>
<li>리소스에 부여된 레이블을 사용하여 대상 리소스를 필터링함으로써 운영</li>
</ul>
</li>
</ul>
<pre><code class="language-bash"># label1=val1 과 label2 레이블을 가진 파드를 표시
kubectl get pods -l label1=val1,label2 

# 모든 레이블을 표시하고 파드 목록을 출력
kubectl get pods --show-labels</code></pre>
<ul>
<li><p>시스템이 사용하는 레이블</p>
<ul>
<li>파드 수를 유지하는 리소스(ReplicaSet)에서는 레이블에 부여된 파드 수를 계산하여 레플리카 수를 관리</li>
<li>대상 파드 수가 레플리카 수의 설정보다 많은 경우에는 파드 중 하나를 삭제하고 반대로 부족한 경우에는 새로운 파드를 생성하는데 조건에 일치하는 파드를 별도로 생성</li>
<li>외부의 요청을 로드 밸런서로 받은 후에 파드로 전송하는 서비스 리소스(Load Balancer)에서는 이 레이블을 기준으로 목적지 파드를 결정하는데 리소스에 대해 레이블을 여러 개 지정할 수 있기 때문에 유연하게 조건을 설정할 수 있음</li>
</ul>
</li>
<li><p>권장되는 레이블 키 이름: 쿠버네티스의 에코시스템을 구성하는 OSS에서도 사용</p>
<ul>
<li><code>app.kubernetes.io/name</code>: 애플리케이션 이름</li>
<li><code>app.kubernetes.io/version</code>: 애플리케이션 버전</li>
<li><code>app.kubernetes.io/component</code>: 애플리케이션 내 구성 요소</li>
<li><code>app.kubernetes.io/part-of</code>: 애플리케이션이 전체적으로 구성하는 시스템 이름</li>
<li><code>app.kubernetes.io/instance</code>: 애플리케이션이나 시스템을 식별하는 인스턴스명</li>
<li><code>app.kubernetes.io/managed-by</code>: 이 애플리케이션을 관리하는 데 사용되는 도구</li>
</ul>
</li>
</ul>
<h2 id="prune-을-사용한-리소스-삭제">Prune 을 사용한 리소스 삭제</h2>
<h3 id="매니페스트-관리와-자동-적용">매니페스트 관리와 자동 적용</h3>
<ul>
<li>매니페스트를 깃 저장소에서 관리하고 변경이 있을 때만 kubectl apply 명령어를 사용하여 자동으로 매니페스트를 적용시키는 방법 등을 사용</li>
<li>매니페스트에서 삭제된 리소스를 삭제하는데 필요한 kubectl delete 명령어를 자동으로 실행하려면 매니페스트에서 삭제된 리소스를 감지할 수 있는 구조를 만들어야 함</li>
<li>여기서 필요한 것이 kubectl apply 명령어에서 사용 가능한 <code>--prune</code> 옵션</li>
<li>kubectl apply 명령어를 실행할 때 매니페스트에서 삭제된 리소스를 감지하여 자동으로 삭제하는 기능을 구현할 수 있음</li>
<li>CI/CD 에서는 업데이트된 매니페스트에 대해 <code>kubectl apply --prune</code> 명령어를 계속 실행하는 것만으로 매니페스트에서 삭제된 리소스도 자동으로 삭제할 수 있음</li>
</ul>
<pre><code class="language-bash"># 리소스 생성
kubectl apply -f ./prune
    # &gt;&gt; pod/sample-pod1 created
    # &gt;&gt; pod/sample-pod2 created

# sample-pod2.yaml 파일을 삭제
# 리소스 수정
kubectl apply -f ./prune
    # &gt;&gt; pod/sample-pod1 unchanged

# 리소스 확인
kubectl get pods</code></pre>
<h2 id="리소스-일부-정보-업데이트">리소스 일부 정보 업데이트</h2>
<ul>
<li><code>kubectl set 변경할정보 리소스종류 리소스이름 실제값</code></li>
<li>변경 가능한 설정값<ul>
<li>env</li>
<li>image</li>
<li>resources</li>
<li>selector</li>
<li>serviceaccount</li>
<li>subject</li>
</ul>
</li>
</ul>
<pre><code class="language-bash"># 파드 생성
kubectl apply -f sample-pod.yaml
    # &gt;&gt; pod/sample-pod created

# 파드의 이미지 변경
kubectl set image pod sample-pod nginx-container=1.17
    # &gt;&gt; pod/sample-pod image updated</code></pre>
<h2 id="로컬-매니페스트와-쿠버네티스-등록-정보-비교-출력">로컬 매니페스트와 쿠버네티스 등록 정보 비교 출력</h2>
<pre><code class="language-bash"># diff 명령 이용해서 확인
kubectl diff -f sample-pod.yaml</code></pre>
<h2 id="사용-가능한-리소스-종류의-목록-가져오기">사용 가능한 리소스 종류의 목록 가져오기</h2>
<pre><code class="language-bash"># 모든 리소스 종류 표시
kubectl api-resources

# 네임스페이스 수준의 리소스
kubectl api-resources --namespaced=true

# 클러스터 수준의 리소스
kubectl api-resources --namespaced=false</code></pre>
<h2 id="리소스-정보-가져오기">리소스 정보 가져오기</h2>
<h3 id="기본-정보-확인">기본 정보 확인</h3>
<pre><code class="language-bash"># 리소스 전체 가져오기
kubectl get all

# 리소스를 지정해서 목록에 해당하는 리소스 전체 가져오기(여러 개를 가져오고자 하는 경우는 리소스 종류 나열)
kubectl get 리소스종류

# 리소스 중 특정한 이름을 가진 리소스 정보 가져오기
kubectl get 리소스종류 리소스이름

# 특정 레이블을 가진 리소스 가져오기
kubectl get 리소스종류 label이름=레이블값

# 노드 목록 표시
kubectl get nodes</code></pre>
<h3 id="-output-o-옵션을-사용"><code>—-output(-o) 옵션을 사용</code></h3>
<ul>
<li>JSON/YAML/Custom Columns/JSON Path/Go Template 등과
같은 다양한 형식으로 출력</li>
</ul>
<pre><code class="language-bash"># 자세히 표시
kubectl get pods -o wide

# yaml로 표시
kubectl get pods -o yaml
kubectl get pods -o yaml sample-pod

# Custom-columns 사용해서 컬럼 이름 수정
kubectl get pods -o custom-columns=&quot;NAME:{.metadata.name},NodeIP:{.status.hostIP}&quot;

# JSON Path 형식의 출력은 특정 항목을 표시하며 셸 스크립트 등으로 
# 변수에 특정 값을 지정하는 등 특정 값을 조사할 때 자주 사용
kubectl get pods sample-pod -o jsonpath=&quot;{.metadata.name}&quot;</code></pre>
<h3 id="metric-server가-있어야-아래-명령은-수행-가능">Metric Server가 있어야 아래 명령은 수행 가능</h3>
<pre><code class="language-bash"># YAML 적용
kubectl apply -f https://github.com/kubernetes-sigs/metricsserver/releases/latest/download/components.yaml

# Metrics Server Pod 확인
kubectl -n kube-system get pods | grep metrics-server

# 로컬 쿠버네티스인 경우는 매니페스트 수정
kubectl edit deploy metrics-server -n kube-system

    # 아래 내용 추가
    containers:
    - name: metrics-server
     image: k8s.gcr.io/metrics-server/metrics-server:v0.7.1
     args:
        - --kubelet-insecure-tls
        - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname

# 적용
kubectl rollout restart deploy metrics-server -n kube-system

# 노드의 리소스 사용량 확인
kubectl top node

# 파드의 리소스 사용량 확인
kubectl -n kube-system top pod

# 컨테이너의 리소스 사용량 확인
kubectl -n kube-system top pod --containers</code></pre>
<ul>
<li>컨테이너에서 명령어 실행</li>
</ul>
<pre><code class="language-bash">kubectl apply -f sample-pod.yaml
kubectl exec -it sample-pod -- /bin/ls
    # &gt;&gt; bin docker-entrypoint.d home mnt root srv usr
    # &gt;&gt; boot docker-entrypoint.sh lib opt run sys var
    # &gt;&gt; dev etc media proc sbin tmp

kubectl exec -it sample-pod -c nginx-container -- /bin/ls
    # &gt;&gt; bin docker-entrypoint.d home mnt root srv usr
    # &gt;&gt; boot docker-entrypoint.sh lib opt run sys var
    # &gt;&gt; dev etc media proc sbin tmp

# bash 셸에 접속
kubectl exec -it sample-pod -- /bin/bash

# 인수를 전달해서 실행
kubectl exec -it sample-pod -- /bin/bash -c &quot;ls -all --classify | grep lib&quot;
    # &gt;&gt; lrwxrwxrwx 1 root root 7 Dec 2 00:00 lib -&gt; usr/lib/</code></pre>
<h2 id="포트-포워딩">포트 포워딩</h2>
<h3 id="kubectl-port-forward-리소스-외부포트내부포트">kubectl port-forward 리소스 외부포트:내부포트</h3>
<ul>
<li>리소스에 파드를 설정하면 파드에 포워딩</li>
<li>리소스에 레플리카 나 디플로이먼트를 설정하면 파드 중 하나로 포워딩</li>
<li>서비스를 포트포워딩 하면 서비스에 연결된 파드 중 하나로 포트 포워딩</li>
</ul>
<h2 id="로그-확인">로그 확인</h2>
<ul>
<li>파드의 로그 확인<ul>
<li><code>kubectl logs 파드이름</code></li>
</ul>
</li>
<li>파드의 특정 컨테이너 로그 확인<ul>
<li><code>kubectl logs 파드이름 -c 컨테이너이름</code></li>
</ul>
</li>
<li>실시간 로그 출력<ul>
<li><code>-f</code> 옵션</li>
</ul>
</li>
<li>최근 1시간 이내 10건의 로그를 타임 스탬프와 함께 출력<ul>
<li><code>kubectl logs --since=1h --tail=10 --timestamps=true 파드이름</code></li>
</ul>
</li>
<li>특정 레이블을 가진 모든 파드의 로그 출력<ul>
<li><code>kubectl logs --selector 테이블</code></li>
</ul>
</li>
</ul>
<h2 id="컨테이너와-파일-복사">컨테이너와 파일 복사</h2>
<h3 id="kubectl-cp-소스-타겟"><code>kubectl cp 소스 타겟</code></h3>
<pre><code class="language-bash">kubectl apply -f sample-pod.yaml
    # &gt;&gt; pod/sample-pod created

kubectl cp sample-pod:etc/hostname ./hostname
cat hostname
    # &gt;&gt; sample-pod

kubectl cp hostname sample-pod:/tmp/newfile
kubectl exec -it sample-pod -- ls /tmp
    # &gt;&gt; newfile</code></pre>
<hr>
<h1 id="workload-api">Workload API</h1>
<h2 id="workload-api-1">WORKLOAD API</h2>
<ul>
<li>클러스터에 컨테이너를 기동시키기 위해 사용되는 리소스</li>
<li>종류<ul>
<li>Pod</li>
<li>ReplicaController</li>
<li>ReplicaSet</li>
<li>Deployment</li>
<li>DaemonSet</li>
<li>StatefulSet</li>
<li>Job</li>
<li>CronJob</li>
</ul>
</li>
</ul>
<h3 id="리소스-간의-관계">리소스 간의 관계</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/14ba839e-df4e-4e0b-a4af-45d78a289ff5/image.png" alt=""></p>
<h1 id="pod">Pod</h1>
<h2 id="pod-1">Pod</h2>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/1b36f9fd-030b-43aa-91c6-36ca06990932/image.png" alt=""></p>
<h3 id="개요-3">개요</h3>
<ul>
<li>파드는 쿠버네티스의 기본 배포(최소) 단위로 다수의 컨테이너가 모인 집합체</li>
<li>하나의 파드는 한 개 이상의 컨테이너로 구성되며 하나의 파드에 여러 개의 컨테이너를 포함시킬 수 있는데 이러한 구조를 사이드카 패턴이라고 함</li>
<li>쿠버네티스에서는 결합이 강한 컨테이너를 파드로 묶어 일괄 배포</li>
<li>같은 파드에 포함된 컨테이너끼리는 네트워크적으로 격리되어 있지 않고 IP 주소를 공유</li>
<li>컨테이너가 두 개 들어 있는 파드를 생성한 경우 이 두 컨테이너는 같은 IP 주소를 가지며 이로 인해 파드 내부의 컨테이너는 서로 <code>localhost</code> 로 통신할 수 있음</li>
</ul>
<h3 id="파드-디자인-패턴">파드 디자인 패턴</h3>
<ul>
<li>대부분의 경우 하나의 파드에 하나의 컨테이너를 가지지만 메인 컨테이너 이외에 메인 컨테이너를 지원하는 서브 컨테이너도 포함하듯이 하나의 파드에 여러 컨테이너를 가질 수도 있음</li>
<li>서브 컨테이너라고 하면 프록시 역할을 하는 컨테이너, 설정 값을 동적으로 변경시키는 컨테이너, 로컬 캐시용 컨테이너, SSL용 컨테이너 등을 예로 들 수 있음</li>
<li>종류<ul>
<li>Sidecar pattern</li>
<li>Ambassador pattern</li>
<li>Adapter pattern</li>
</ul>
</li>
</ul>
<h3 id="sidecar-pattern">Sidecar Pattern</h3>
<ul>
<li>사이드카 패턴은 메인 컨테이너 외에 보조적인 기능을 추가하는 서브 컨테이너를 포함하는 패턴<ul>
<li>예를 들어 특정 변경 사항을 감지하여 동적으로 설정을 변경하는 컨테이너 그리고 깃 저장소와 로컬 스토리지를 동기화 하는 컨테이너와 애플리케이션의 로그 파일을 오브젝트 스토리지로 전송하는 컨테이너라는 구성이 자주 사용됨</li>
</ul>
</li>
<li>파드는 데이터 영역을 공유하고 가지고 있을 수 있기 때문에 대부분 데이터와 설정에 관련된 패턴이라 할 수 있음</li>
</ul>
<h3 id="ambassador-pattern">Ambassador pattern</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/b6c45e9b-2837-4816-b302-78f56f814ef6/image.png" alt=""></p>
<ul>
<li>앰배서더 패턴은 메인 컨테이너가 외부 시스템과 접속할 때 대리로 중계해주는 서브 컨테이너(앰배서더 컨테이너)를 포함한 패턴</li>
<li>파드에 두 개의 컨테이너가 있어 메인 컨테이너에서 목적지에 localhost 를 지정하여 앰배서더 컨테이너로 접속할 수 있음</li>
<li>앰배서더 컨테이너를 사용하지 않고 메인 컨테이너에서 동작 중인 애플리케이션이 sharding 된 데이터베이스 하나를 선택하여 접속하게 된다면 메인 컨테이너는 데이터베이스와의 결합도가 강해짐</li>
<li>앰배서더 컨테이너를 사용함으로써 메인 컨테이너에서는 항상 localhost 를 지정하여 앰배서더 컨테이너로만 접속하고 앰배서더 컨테이너가 여러 목적지에 중계하여 연결하도록 구성하면 느슨한 결합을 유지할 수 있게 하는데 앰배서더 컨테이너를 경유하면 단일 데이터베이스를 사용히는 개발 환경이나 분산된 데이터베이스를 사용하는 서비스 환경 모두에서 특별한 변경 사항 없이 애플리케이션을 사용할 수 있음</li>
</ul>
<h3 id="adapter-pattern">Adapter pattern</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/c6c7716a-fed0-4b8a-99cc-b9b9cc6aa143/image.png" alt=""></p>
<ul>
<li>어댑터 패턴은 서로 다른 데이터 형식을 변화해주는 컨테이너(어댑터 컨테이너)를 포함하는 패턴</li>
<li>프로메테우스 등의 모니터링 소프트웨어에서는 정의된 형식으로 메트릭을 수집해야 하는데, 대부분의 미들웨어가 제공하는 메트릭 출력 형식은 프로메테우스 메트릭 형식을 지원하지 않기 때문에 어댑터 컨테이너를 사용해서 외부 요청에 맞게 데이터 형식으로 변환하고 데이터를 반환해서 사용</li>
<li>어댑터 패턴의 경우도 메인 컨테이너와 어댑터 컨테이너 간에는 localhost 를 통해 접속</li>
</ul>
<h3 id="파드-생성">파드 생성</h3>
<pre><code class="language-bash"># sample-pod.yaml 파일 생성 및 작성
apiVersion: v1
kind: Pod
metadata:
 name: sample-pod
spec:
 containers:
 - name: nginx-container
     image: nginx

# 리소스 파일 적용
kubectl apply -f sample-pod.yaml
    # &gt;&gt; pod/sample-pod created</code></pre>
<h3 id="컨테이너-실행과-관리">컨테이너 실행과 관리</h3>
<ul>
<li>쿠버네티스가 직접 컨테이너를 실행하지는 않음</li>
<li>컨테이너를 생성할 책임을 해당 노드에 설치된 컨테이너 런타임에 맡기는 형태</li>
<li>컨테이너 런타임은 containerd 나 docker 가 될 수 있음</li>
<li>파드는 쿠버네티스가 관리하는 리소스고 컨테이너는 쿠버네티스 외부에서 관리</li>
</ul>
<h3 id="stateless--stateful">Stateless &amp; Stateful</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/ced249ba-763e-4290-ab82-2787ccb696cf/image.png" alt=""></p>
<ul>
<li>Stateless(상태가 없는) 는 사용자가 애플리케이션을 사용할 때 상태나 세션을 저장해 둘 필요가 없는 애플리케이션에서 사용</li>
<li>Stateful은 사용자가 애플리케이션을 사용할 때 상태나 세션을 별도의 데이터베이스에 저장해야 하는 애플리케이션에서 사용</li>
<li>상태나 세션을 저장해야 하는 애플리케이션으로는 쇼핑몰, 금융 거래 사이트 등이 있음</li>
</ul>
<h3 id="파드-정보-확인">파드 정보 확인</h3>
<pre><code class="language-bash"># 정상적으로 생성되었는지 확인
kubectl get pods

# 추가적인 정보를 얻으려면 -o wide 옵션 사용
kubectl get deployment -o wide</code></pre>
<h3 id="두-개의-컨테이너를-포함한-파드-생성">두 개의 컨테이너를 포함한 파드 생성</h3>
<pre><code class="language-bash"># sample-2pod.yaml 파일을 생성하고 작성
apiVersion: v1
kind: Pod
metadata:
 name: sample-2pod
spec:
 containers:
 - name: nginx-container
     image: nginx:1.16
 - name: redis-container
     image: redis:3.2</code></pre>
<pre><code class="language-bash"># 파드 생성
kubectl apply -f sample-2pod.yaml

# 파드 확인
kubectl get pods</code></pre>
<pre><code class="language-bash"># sample-2pod-fail.yaml 파일을 생성하고 작성
apiVersion: v1
kind: Pod
metadata:
 name: sample-2pod-fail
spec:
 containers:
     - name: nginx-container-112
         image: nginx:1.16
     - name: nginx-container-113
         image: nginx:1.17</code></pre>
<pre><code class="language-bash"># 파드 생성
kubectl apply -f sample-2pod-fail.yaml

# 파드 확인 - 동일한 포트를 사용하므로 첫번째만 컨테이너로 기동되고
# 두번째 컨테이너는 기동 안 됨
kubectl get pods

# 로그 확인
kubectl logs sample-2pod-fail -c nginx-container-113</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[부트캠프 - 37일차] 2/11.수 - Kubernetes]]></title>
            <link>https://velog.io/@pizza_loves_me/37%EC%9D%BC%EC%B0%A8-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-211.%EC%88%98-Kubernetes</link>
            <guid>https://velog.io/@pizza_loves_me/37%EC%9D%BC%EC%B0%A8-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-211.%EC%88%98-Kubernetes</guid>
            <pubDate>Thu, 12 Feb 2026 11:28:30 GMT</pubDate>
            <description><![CDATA[<hr>
<h1 id="kubernetes">Kubernetes</h1>
<h2 id="container-런타임">Container 런타임</h2>
<ul>
<li>일반적인 가상화 환경은 하드웨어 수준에서 가상화되지만 Container는 운영체제 수준에서 가상화르 수행</li>
<li>Container는 운영체제의 커널을 공유하기 때문에 상대적으로 가볍고 유연하게 운영 가능</li>
<li>Container 화된 애플리케이션은 몇초 내로 빠르게 실행되며 가상머신과 비교했을 때 자원을 더 적게 사용해서 하나의 시스템에서 더 많은 애플리케이션을 구동할 수 있음</li>
<li>운영체제를 공유해서 사용하기 때문에 패치, 업데이트 등 유지관리와 관련한 오버헤드가 줄어든다는 장점이 있음</li>
<li>런타임은 프로그래밍 언어가 구동되는 환경을 의미<ul>
<li>자바스크립츠가 브라우저에서 실행되면 런타임은 브라우저</li>
</ul>
</li>
<li>Container를 생성하고 실행할 수 있도록 도와주는 것이 바로 Container Runtime</li>
</ul>
<h3 id="종류">종류</h3>
<ul>
<li><code>Containerd</code><ul>
<li>컨테이너 실행을 관리하는 오픈 소스 런타임</li>
<li>Docker 및 Kubernetes 와 같은 컨테이너 오케스트레이션 시스템에서 컨테이너 실행을 처리하는 데 사용</li>
<li>Container 이미지를 다운로드 받아 압축을 푸는 과정부터 Container 를 실행하고 감독하는 과정까지 Container의 전체 수명 주기를 관리하는 기능을 제공</li>
</ul>
</li>
<li><code>Docker</code><ul>
<li>Containerd 위에 설치되는 데몬</li>
<li>Container 를 생성하고 관리하는 데 필요한 모든 기능을 제공</li>
<li>Docker 는 컨테이너의 전체 라이프사이클(빌드, 배포, 실행)을 관리하는 반면, Containerd는 컨테이너 실행과 이미지 관리를 담당하는 핵심 컴포넌트로 Docker 는 내부적으로 Containerd 를 사용하며 Kubernetes 도 Containerd 를 기본 런타임으로 지원</li>
</ul>
</li>
<li><code>CRI-O</code><ul>
<li>Redhat, Intel, SUSE, Hyper, IBM 등의 관리자 및 커뮤니티를 중심으로 개발된 오픈 소스 런타임</li>
<li>CRI-I 는 Docker를 대체하기 위한 툴로 개발</li>
</ul>
</li>
</ul>
<h2 id="container-orchestration">Container Orchestration</h2>
<h3 id="개요">개요</h3>
<ul>
<li>다수의 컨테이너를 유기적으로 연결 및 실행할 뿐만 아니라 상태를 추적하고 보존하는 등 컨테이너를 안정적으로 사용할 수 있게 만들어주는 솔루션</li>
<li>컨테이너를 효과적으로 관리하도록 도와주는 것이 컨테이너 오케스트레이션</li>
<li>여러 시스템에 컨테이너를 분산해서 배치하거나 문제가 생긴 컨테이너를 교체하는 등 여러 역할을 수행</li>
</ul>
<h3 id="container-orchestration-솔루션">Container Orchestration 솔루션</h3>
<ul>
<li><code>Docker Swarm</code><ul>
<li>간단하게 설치할 수 있고 사용하기도 용이</li>
</ul>
</li>
<li><code>Mesos</code><ul>
<li>Apache의 오픈 소스 프로젝트로 트위터, AirBnB, 애플, Uber 등 다양한 곳에서 이미 검증된 솔루션</li>
<li>2016년 DC/OS(Data Center OS, 대규모 서버 환경에서 자원을 유연하게 공유하며 하나의 자원처럼 관리하는 도구)의 지원으로 매우 간결하지만 기능을 충분히 활용하려면 분산 관리 시스템과 연동해야 하기 때문에 여러가지 솔루션을 유기적으로 구성해야 하는 부담이 있음</li>
</ul>
</li>
<li><code>Nomad</code><ul>
<li>Vagrant를 만든 HashiCorp 사의 Container 오케스트레이션으로 Vagrant처럼 간단한 구성으로 Container 오케스트레이션 환경을 제공</li>
<li>HashiCorp의 Consul(서비스 검색, 구성 및 분할 기능 제공) 과 Vault(암호화 저장소) 와 연동이 원할하므로 이런 도구에 대한 사용 성숙도가 높은 조직이라면 노매드 도입을 고려해볼 수 있음</li>
</ul>
</li>
<li><code>Kubernetes</code></li>
<li>컨테이너화된 애플리케이션을 자동으로 배포, 스케일링 및 운영하는 오픈 소스 오케스트레이션 플랫폼</li>
<li>구글이 내부적으로 사용하던 Borg 시스템에서 발전한 기술로 현재는 Cloud Native Computing Foundation(CNCF)에서 관리</li>
</ul>
<h2 id="kubernetes-개요">Kubernetes 개요</h2>
<ul>
<li>쿠버네티스는 컨테이너 기반의 애플리케이션을 개발하고 배포할 수 있도록 설계된 오픈 소스 플랫폼으로 컨테이너 오케스트레이션 도구의 일종</li>
<li>쿠버네티스는 여러 대의 물리적 서버에 걸쳐 실행되는 것을 전제로 함<ul>
<li>쿠버네티스는 여러 대의 물리적 서버가 존재하는 것을 전제</li>
<li>물리적 서버 한 대 한 대마다 제각기 여러 개의 컨테이너를 실행한다고 가정</li>
<li>여러 대의 서버에서 일일이 컨테이너를 실행하고 관리하기는 쉬운 일이 아닌데 쿠버네티스는 이를 위한 도구</li>
<li>도커만을 이용해서 20개의 컨테이너를 만들려면 docker run 커맨드를 20번 실행해야 함</li>
<li>Docker Compose를 사용한다 해도 물리적 서버가 여러 대라면 반복 작업은 사라지지 않고, 어떻게든 컨테이너를 생성해 실행했다 해도 물리적 서버를 일일이 모니터링 하면서 장애가 일어나면 컨테이너를 다시 실행해야 하고 컨테이너를 업데이트 하는 경우 큰 수고가 따름</li>
<li>→ 쿠버네티스는 번거로운 컨테이너 생성이나 관리의 수고를 덜어주는 도구로 Docker Compose에서 사용되는 Compose 파일과 비슷한 정의 파일만 작성하면 이 정의에 따라 모든 물리적 서버에 컨테이너를 생성하고 생성한 컨테이너를 관리해 줌</li>
</ul>
</li>
</ul>
<h2 id="kubernetes-를-사용하는-이유">Kubernetes 를 사용하는 이유</h2>
<h3 id="자동화된-컨테이너-오케스트레이션">자동화된 컨테이너 오케스트레이션</h3>
<ul>
<li>컨테이너의 배포, 관리, 확장, 복구를 자동으로 수행</li>
<li>여러 개의 컨테이너를 그룹화하여 하나의 애플리케이션처럼 운영</li>
</ul>
<h3 id="무중단-서비스---rolling-update-self-healing">무중단 서비스 - Rolling Update, Self-Healing</h3>
<ul>
<li><code>Rolling Update</code><ul>
<li>새 버전의 Pod를 점진적으로 생성</li>
<li>새 Pod가 준비되면, 구 버전의 Pod를 점진적으로 종료</li>
<li>이 과정을 통해 전체 Pod를 한 번에 교체하는 것이 아니라 일부 인스턴스 별로 순차적으로 교체하여 서비스의 가용성을 유지</li>
</ul>
</li>
<li><code>Self-Healing</code><ul>
<li>애플리케이션 인스턴스(Pod)에 문제가 발생하여 중지될 경우 쿠버네티스가 자동으로 이를 감지하고 새로운 인스턴스를 생성하여 서비스를 유지하는 기능</li>
</ul>
</li>
</ul>
<h3 id="효율적인-자원-사용">효율적인 자원 사용</h3>
<ul>
<li>쿠버네티스는 Pod가 사용할 수 있는 자원(CPU, 메모리 등)을 사전에 지정할 수 있어서 시스템(노드)의 전체 자원을 관리할 수 있기 때문에 자원을 효율적으로 사용할 수 있음</li>
<li><code>Traffic Routing</code> : 트래픽은 주로 Service 와 Ingress 를 통해 원하는 Pod로 전달</li>
</ul>
<h3 id="유연한-확장성">유연한 확장성</h3>
<ul>
<li><code>Auto-Scaling</code> : CPU/메모리 사용량을 모니터링 하여 자동으로 확장 또는 축소하는 것</li>
<li>쿠버네티스의 Pod의 자원 사용률에 따라 Pod의 개수를 늘리거나 줄일 수 있기 때문에 CPU 사용률이 200%로 증가하는 경우 Pod를 1개에서 5개로 늘리고 CPU 사용률이 감소하면 Pod를 다시 1개로 줄일 수 있음</li>
</ul>
<h3 id="항상-바람직한-상태를-유지">항상 바람직한 상태를 유지</h3>
<ul>
<li>쿠버네티스도 컨테이너를 생성하거나 삭제할 수 있지만, 일일이 명령어를 입력하는 방식을 사용하지는 않음</li>
<li><code>선언적 방식(Declarative)</code>: 어떤 상태가 되어야 하는지 기술하는 것.<ul>
<li><code>자가 치유(Self-healing)</code></li>
<li><code>IaC</code></li>
<li><code>복잡성 감소</code></li>
</ul>
</li>
</ul>
<h2 id="kubernetes를-사용했을-떄-편한-이유">Kubernetes를 사용했을 떄 편한 이유</h2>
<h3 id="실제-프로젝트를-할-때-구조적인-문제">실제 프로젝트를 할 때 구조적인 문제</h3>
<ul>
<li>개발과 모니터링 시스템이 서로 엮일 수 밖에 없는 구조</li>
<li>개발에서는 한 번도 써보지 않은 개발 시스템을 위한 모니터링 시스템을 만드는 문제</li>
<li>오픈 시 개발 프로젝트와 서로 다른 범위의 App들을 모니터링 하게 되는 구조</li>
</ul>
<h2 id="kubernetes-리소스">Kubernetes 리소스</h2>
<h3 id="리소스-분류">리소스 분류</h3>
<ul>
<li><p><code>Workload API</code> - 컨테이너 실행에 관련된 리소스</p>
<ul>
<li><code>Pod</code></li>
<li><code>ReplicationController</code></li>
<li><code>ReplicaSet</code></li>
<li><code>Deployment</code></li>
<li><code>DaemonSet</code></li>
<li><code>StatefulSet</code></li>
<li><code>Job</code></li>
<li><code>CronJob</code></li>
</ul>
</li>
<li><p><code>Service API</code> - 컨테이너를 외부에 공개하는 End Point를 제공하는 리소스</p>
<ul>
<li><code>ClusterIP</code></li>
<li><code>ExternalIP</code></li>
<li><code>NodePort</code></li>
<li><code>LoadBalancer</code></li>
<li><code>Headless(None)</code></li>
<li><code>ExternalName</code></li>
<li><code>None-Selector</code></li>
<li><code>Ingress</code></li>
</ul>
</li>
<li><p><code>Config &amp; Storage API</code> - 설정/기밀 정보/영구 볼륨 등에 관련된 리소스</p>
<ul>
<li><code>Secret</code></li>
<li><code>ConfigMap</code></li>
<li><code>PersistentVolumeClaim</code></li>
</ul>
</li>
<li><p><code>Cluster API</code> - 보안이나 쿼터 등에 관련된 리소스</p>
<ul>
<li><code>Node</code></li>
<li><code>Namespace</code></li>
<li><code>PersistentVolume</code></li>
<li><code>ResourceQuota</code></li>
<li><code>ServiceAccount</code></li>
<li><code>Role</code></li>
<li><code>ClusterRole</code></li>
<li><code>RoleBinding</code></li>
<li><code>ClusterRoleBinding</code></li>
<li><code>NetworkPolicy</code></li>
</ul>
</li>
<li><p><code>Meta Data API</code> - 클러스터 내부의 다른 리소스를 관리하기 위한 리소스</p>
<ul>
<li><code>LimitRange</code></li>
<li><code>HorizontalPodAutoScaler</code></li>
<li><code>PodDisruptionBudget</code></li>
<li><code>CustomResourceDefinition</code></li>
</ul>
</li>
</ul>
<h2 id="kubernetes-architecture">Kubernetes Architecture</h2>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/2783703a-3f70-490e-a347-e6f03bdd2f9a/image.png" alt=""></p>
<h3 id="pod">Pod</h3>
<ul>
<li>하나의 애플리케이션을 생성하기 위해서는 하나 이상의 파드가 필요한데 파드는 쿠버네티스에서 생성할 수 있는 가장 작은 배포 단위이면서 단일 혹은 다수의 Container를 포함</li>
<li>Pod 외에 Service, Volume, Namespace 등을 묶어서 Object라 하고 Object는 쿠버네티스에서 상태 관리가 필요한 대상</li>
<li>Pod 와 Container<ul>
<li>Pod 는 쿠버네티스에서 가장 기본적인 배포 단위이면서 하나 이상의 컨테이너를 포함</li>
<li>쿠버네티스의 특징 중 하나는 컨테이너를 개별적으로 하나씩 배포하는 것이 아니라 유사한 역할을 하는 컨테이너를 파드라는 단위로 묶어서 배포</li>
</ul>
</li>
</ul>
<h3 id="cluster">Cluster</h3>
<ul>
<li>여러 리소스를 관리하기 위한 집합체로 <code>Master Node</code> 와 <code>Worker Node</code> 를 이용해 하나의 클러스터를 구성</li>
</ul>
<h3 id="node">Node</h3>
<ul>
<li>쿠버네티스 리소스 중에서 가장 큰 개념은 노드</li>
<li>노드는 쿠버네티스 Cluster 의 관리 대상으로 등록된 Container 가 배치되는 대상</li>
<li>쿠버네티스 클러스터 전체를 관리하는 서버인 마스터(Control Plane)가 하나 이상 있어야 함</li>
<li>쿠버네티스 클러스터는 마스터와 노드의 그룹으로 구성</li>
</ul>
<h2 id="kubernetes-구조">Kubernetes 구조</h2>
<h3 id="master-node">Master Node</h3>
<ul>
<li>쿠버네티스 클러스터 전체를 관리하는 시스템으로 <code>Control Plane</code></li>
<li>Master Node를 설정하는 관리자의 컴퓨터에는 kubectl 를 설치하는데 kubectl을 설치해야 Master Node에 로그인 해 초기 설정을 진행하거나 추후 조정 가능</li>
</ul>
<h3 id="persisten-storage">Persisten Storage</h3>
<ul>
<li>Pod는 휘발성이므로 Worker Node에 떠 있는 Pod가 삭제되면 Pod 안의 모든 데이터도 삭제되기 때문에 중요한 데이터는 Pod 외부에 있는 저장소에 저장해 두어야 하며, <code>CSI(Container Storage Interface)</code> 로 외부 저장소를 Pod에 연결할 수 있음</li>
</ul>
<h3 id="worker-node">Worker Node</h3>
<ul>
<li>Container Rumtime 이 설치된 환경으로 Pod가 실제로 생성되고 유지되는 Node</li>
<li>리눅스 위에 Container Runtime 을 설치하고 그 위에 하나 이상의 Container 가 포함된 Pod 를 생성하면 각 컨테이너에서는 그 목적에 맞게 서비스가 실행됨</li>
</ul>
<h1 id="kubernetes-cluster-구축">Kubernetes Cluster 구축</h1>
<h2 id="local-kubernetes">Local Kubernetes</h2>
<ul>
<li>물리 머신 1대에 구축해서 사용</li>
</ul>
<h3 id="docker-desktop-활용">Docker Desktop 활용</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/3fcf3414-0be8-4917-8afe-5d882640c5d1/image.png" alt=""></p>
<ul>
<li>Docker 실행 후 톱니바퀴를 눌러 Kubernetes 메뉴에서 <code>Enable Kubernetes</code> - <code>Apply &amp; Restart</code> 를 누르면 Kubernetes 설치가 진행됨</li>
</ul>
<h3 id="minikube-활용">Minikube 활용</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/2f4b66b0-3a70-4499-9efb-f3cdc0fafff1/image.png" alt=""></p>
<ul>
<li>물리 머신에 로컬 쿠버네티스를 쉽게 구축하고 실행할 수 있는 도구로 쿠버네티스의 <code>SIG-Cluster-Lifecycle</code> 에서 만듦</li>
<li>미니큐브는 로컬 가상 머신에 쿠버네티스를 설치하기 위해 하이퍼바이저가 필요한데 도커 또는 Virtual Box 나 베어메탈 등</li>
<li>설치: <a href="https://minikube.sigs.k8s.io/docs/start/">https://minikube.sigs.k8s.io/docs/start/</a></li>
</ul>
<h3 id="kind">Kind</h3>
<ul>
<li>Docker 컨테이너 노드를 사용하여 로컬 쿠버네티스 클러스터를 실행하기 위한 도구</li>
<li>주로 쿠버네티스 자체를 테스트하기 위해 설계 되었지만 로컬 개발이나 CI에 사용될 수도 있음</li>
<li>설치:  <a href="https://kind.sigs.k8s.io/docs/user/quick-start">https://kind.sigs.k8s.io/docs/user/quick-start</a></li>
<li>kubectl 설치: <a href="https://kubernetes.io/ko/docs/tasks/tools/install-kubectl-linux/">https://kubernetes.io/ko/docs/tasks/tools/install-kubectl-linux/</a></li>
</ul>
<pre><code class="language-bash"># 명령어

# 클러스터 생성
kind create cluster

# 클러스터 삭제
kind delete cluster

# 설정 파일을 이용한 클러스터 생성
kind create cluster --config 설정파일경로 --name 클러스터이름</code></pre>
<ul>
<li>여러 개의 Worker를 가진 클러스터 생성</li>
</ul>
<pre><code class="language-bash"># 설정 파일을 생성하고 작성: kind.yaml
apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
nodes:
- role: control-plane
- role: worker
- role: worker
- role: worker

# 클러스터 생성
./kind create cluster --config kind.yaml --name kindcluster

# 여러 개의 클러스터 생성한 경우 클러스터 전환
kubectl config use-context kind-클러스터이름</code></pre>
<h3 id="k3s--k0s">K3s &amp; K0s</h3>
<ul>
<li><p>두 프로젝트들은 모두 쿠버네티스 클러스터를 구축하는 데에 필수적인 구성 요소들을 간추려서 단일 파일로 설치 및 실행하기 때문에 리소스 경량화와 클러스터 구축 간소화를 모두 얻을 수 있음</p>
</li>
<li><p>기존 쿠버네티스는 클러스터의 데이터 저장소로 etcd를 사용하는데, K3s &amp; K0s는 경량화를 위해 etcd 보다 더 가벼운 sqlite3 를 기본 데이터 저장소로 사용하며 etcd나 MySQL 같은 다른 저장소도 지원하기 때문에 불필요한 경우라면 사용하는 것도 가능</p>
</li>
<li><p>K3s 와 K0s 의 최소 요구사항</p>
<ul>
<li><code>K3S</code>: 1 CPU / 512MB RAM</li>
<li><code>K0S</code>: 1 CPU / 1GB RAM</li>
</ul>
</li>
<li><p>둘 모두 테스트 및 배포 환경에도 적합하지만 적은 리소스 사용량 덕에 IoT 환경에도 사용하기 적합</p>
</li>
<li><p>두 프로젝트 모두 단일 파일로 클러스터 구성요소를 작동시키는 방식이기 때문에 현재 리눅스 계열 운영체제만 공식 지원</p>
</li>
<li><p>설치</p>
<ul>
<li>K3S: <a href="https://docs.k3s.io/">https://docs.k3s.io/</a></li>
<li>K0S: <a href="https://docs.k0sproject.io/head/install/">https://docs.k0sproject.io/head/install/</a></li>
</ul>
</li>
</ul>
<h2 id="kubernetes-클러스터-구축-도구-이용">Kubernetes 클러스터 구축 도구 이용</h2>
<ul>
<li>Onpremise 나 Cloud 환경에 클러스터를 구축하여 사용</li>
<li>사용하는 시스템에 쿠버네티스 클러스터를 자동으로 구성해주는 솔루션을 사용</li>
<li>주요 솔루션으로는 kubeadm, kops(Kubernetes Operations), KRIB(Kubernetes Rebar Integrated Bootstrap), kubespray 등이 있음</li>
<li>kudeadm이 가장 널리 알려져 있는데 kubeadm은 사용자가 변경하기도 수월하고 온프레미스와 클라우드를 모두 지원하며 배우기도 쉬운 편</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/40044efe-f3ea-47fb-a637-99dc20344d93/image.png" alt=""></p>
<h3 id="virtual-box-를-이용한-우분투-운영체제에-네트워크-연결">Virtual Box 를 이용한 우분투 운영체제에 네트워크 연결</h3>
<pre><code class="language-bash"># Unbuntu 설정

# 호스트 이름 수정
sudo hostnamectl set-hostname master
cat /etc/hostname

# Ubuntu 설치
# IP 수정
# 현재 네트워크 인터페이스 확인
ip a

# netplan 설정 파일 수정
sudo nano /etc/netplan/00-installer-config.yaml
network:
 version: 2
 ethernets:
     enp0s8:
     dhcp4: no
     addresses:
         - 10.0.2.15/24
     routes:
         - to: default
         via: 10.0.2.1
     nameservers:
         addresses:
             - 8.8.8.8
             - 8.8.4.4

# IP 수정
# 소유권을 root로 변경
sudo chown root:root /etc/netplan/00-installerconfig.yaml

# 권한을 600으로 변경
sudo chmod 600 /etc/netplan//etc/netplan/00-installer-config.yaml

# 변경 사항 적용
sudo netplan apply

# IP 주소와 호스트 이름 매핑
sudo nano /etc/hosts
    127.0.0.1 localhost
    127.0.1.1 master
    10.0.2.15 master
    10.0.2.16 worker1
    10.0.2.17 worker2</code></pre>
<h3 id="kubeadm-을-이용한-클러스터-구축">kubeadm 을 이용한 클러스터 구축</h3>
<ul>
<li>Ubuntu 24.04에 쿠버네티스 클러스터 구축</li>
</ul>
<h2 id="관리형-kubernetes">관리형 Kubernetes</h2>
<ul>
<li>Cloud의 관리형 서비스로 제공하는 클러스터를 사용<ul>
<li><a href="https://hostnextra.com/learn/tutorials/how-to-install-kubernetes-k8s-on-ubuntu">https://hostnextra.com/learn/tutorials/how-to-install-kubernetes-k8s-on-ubuntu</a></li>
</ul>
</li>
<li>Master Node<ul>
<li>CPU가 2개 이상<ul>
<li>Core가 1개이면 설치는 성공을 하지만 마스터 노드로 실행할 때 에러가 발생</li>
</ul>
</li>
<li>포트 개방<ul>
<li><code>API Server</code>: 6443</li>
<li><code>etcd Server</code>: 2379, 2380</li>
<li><code>kubelet AP</code>I: 10250</li>
<li><code>kube scheduler</code>: 10251</li>
<li><code>kube controller manager</code>: 10252</li>
<li><code>Flannel CNI 플러그인에 대한 포트</code>: 8285, 8472</li>
</ul>
</li>
</ul>
</li>
<li>Worker Node<ul>
<li>하드웨어 제약 없음</li>
<li>포트 개방<ul>
<li><code>API Server</code>: 6443</li>
<li><code>kube-proxy가 서비스를 Load Balancing하기 위해 사용하는 포트</code>: 26443</li>
<li><code>Flannel CNI Plugin에 대한 포트</code>: 8285, 8472</li>
<li><code>NodePort 로 사용할 포트</code>: 30000-32767</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="language-bash"># 패키지 정보 업데이트
sudo apt update &amp;&amp; sudo apt upgrade -y
sudo apt install -y curl apt-transport-https ca-certificates software-propertiescommon gnupg

# Memory Swap 비활성화: 가상 메모리 사용으로 인한 문제점을 제거
sudo swapoff -a
sudo sed -i &#39;/ swap / s/^\(.*\)$/#\1/g&#39; /etc/fstab

# 확인
sudo free -m
sudo swapon -s

# iptable 설정
cat &lt;&lt;EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

cat &lt;&lt;EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF

sudo sysctl --system</code></pre>
<pre><code class="language-bash"># 컨테이너 런타임 설치
sudo apt install -y containerd

sudo mkdir -p /etc/containerd

containerd config default | sudo tee /etc/containerd/config.toml

# systemd를 cgroup 으로 전환
sudo sed -i &#39;s/SystemdCgroup = false/SystemdCgroup = true/&#39;
/etc/containerd/config.toml

sudo systemctl restart containerd

sudo systemctl enable containerd </code></pre>
<pre><code class="language-bash"># 쿠버네티스 설치
# 필수 도구 설치
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gpg

# 구글 클라우드 GPG 키 다운로드
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

# 쿠버네티스 저장소 추가
echo &#39;deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /&#39; | sudo tee /etc/apt/sources.list.d/kubernetes.list

sudo apt-get update

# 필수 도구들 설치
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl</code></pre>
<ul>
<li>&lt;마스터 노드에서 클러스터 생성&gt;</li>
</ul>
<pre><code class="language-bash"># kubeadm 을 초기화하고 파드의 네트워크 대역을 설정
sudo kubeadm init --pod-network-cidr=10.244.0.0/16

# 쿠버네티스 환경 설정 파일 생성
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# CNI 설치
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

# 방화벽 중지
sudo systemctl stop ufw
sudo systemctl disable ufw

# 노드 확인
kubectl get nodes

# 토큰 확인
kubeadm token list

# 토큰 생성
kubeadm token create

# 해시값 확인
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt

# 워커 노드에서 마스터 노드에 연결
sudo kubeadm join [마스터_노드_IP]:6443 \
  --token [임시_비밀번호] \
  --discovery-token-ca-cert-hash sha256:[서버_인증_해시값]</code></pre>
<h2 id="외부-관리형-서비스-사용">외부 관리형 서비스 사용</h2>
<ul>
<li>기업에서 쿠버네티스를 사용하는 비중이 높아지면서 퍼블릭 클라우드 서비스 제공업체들은 쿠버네티스를 PaaS 형식의 서비스로 출시<ul>
<li>AWS - EKS</li>
<li>MS - AKS</li>
<li>Google - GKE</li>
<li>Kakao - DKOS</li>
<li>NHN - NKS</li>
</ul>
</li>
</ul>
<h1 id="kubernetes-component">Kubernetes Component</h1>
<h2 id="개요-1">개요</h2>
<ul>
<li>Master Node 에는 Cluster 를 유지하고 제어하기 위한 다양한 Component 가 있고 Worker Node 에는 애플리케이션을 실행하기 위한 컴포넌트들이 있음</li>
</ul>
<h2 id="kubectl">kubectl</h2>
<ul>
<li>쿠버네티스 클러스터에 명령을 내리는 역할</li>
<li>다른 구성 요소들과 달리 바로 실행되는 명령 형태인 binary 로 배포되기 때문에 마스터 노드에 있을 필요는 없음</li>
<li>통상적으로 API Server 와 통신하므로 마스터 노드에 구성하는 것이 일반적</li>
</ul>
<h2 id="api-server">API Server</h2>
<ul>
<li>쿠버네티스 클러스터의 API를 사용할 수 있게 해주는 프로세스로 클러스터로 요청이 들어왔을 때 그 요청이 유효한지 검증</li>
<li>검증 과정<ul>
<li>사용자 검증 → 명령어 검증 → [API Server] → 파드 생성 → [워커 노드]</li>
</ul>
</li>
<li>Cluster에 접속할 권한이 있는 사용자인지를 검증</li>
<li>사용자가 보낸 명령어를 검증하는데 명령어기 문법에 맞게 작성되었는지 오타가 있는지 등을 검증</li>
<li>사용자의 요청에 따라 명령을 수행하는데 API Server 가 Worker Node에 Pod 를 생성하도록 요청했지만 아직 생성은 안 된 상태</li>
</ul>
<h2 id="etcd">etcd</h2>
<ul>
<li>클러스터에 필요한 파드와 같은 리소스들의 상태 정보가 담겨 있는 곳</li>
<li>API Server는 파드를 만든다는 사실을 etcd에 알려서 상태를 저장하고 사용자에게 파드가 생성되었음을 알림</li>
<li>정보들은 key-value 형태로 저장되는데 사용자에게 파드가 생성되었음을 알렸지만 내부적으로는 여전히 파드가 생성되지 않았음</li>
<li>etcd를 여러 곳에 저장해두면 장애가 나더라도 시스템의 가용성을 확보할 수 있음 - Multi Master Node</li>
</ul>
<h2 id="scheduler">scheduler</h2>
<ul>
<li>스케줄러는 파드를 어떤 노드에 할당해야 할지를 결정하는데 워커 노드의 리소스 사용량이나 레이블 그리고 노드의 상태 등을 참조해 결정</li>
</ul>
<h3 id="controller-manager">Controller Manager</h3>
<ul>
<li>클러스터의 상태릴 지속적으로 감시하고 선언된(desired) 상태와 실제 상태를 일치시키기 위해 다양한 컨트롤러들을 실행하는 핵심 컴포넌트</li>
<li>역할<ul>
<li>클러스터 내 리소스르르 지속적으로 모니터링</li>
<li>선언된 상태와 실제 상태를 비교하여 필요한 변경 수행</li>
<li>컨트롤러들을 관리하고 실행</li>
<li>장애가 발생하면 자동으로 복구 작업 수행</li>
<li>다양한 컴포넌트의 상태를 지속적으로 모니터링 하는 동시에 실행 상태를 유지하는 역할을 하는데 컨트롤러 매니저가 특정 워커 노드와 통신이 불가능하다고 판단되면 해당 노드에 할당된 파드를 제거하고 다른 워커 노드에서 파드를 생성해 서비스가 계속 되도록 함</li>
</ul>
</li>
</ul>
<h2 id="kubelet">kubelet</h2>
<ul>
<li>API Server 는 파드가 생성될 워커 노드에 있는 kubelet에 파드의 생성 정보를 전달</li>
<li>kubelet은 해당 정보를 이용해 파드를 생성하는데 kubelet은 클러스터의 각 노드에서 실행되는 에이전트로 파드에서 컨테이너의 동작(생성 및 운영)을 관리</li>
<li>파드를 생성했다면 kubelet은 API Server 에 생성된 파드의 정보를 전달하고 API Server는 다시 etcd를 업데이트 하는데 최종적으로 어떤 워커 노드에 어떤 파드가 생성되었는지 etcd에 저장</li>
</ul>
<h2 id="kube-proxy">kube-proxy</h2>
<ul>
<li><p>쿠버네티스 클러스터 내에서 네트워크 프록시 역할을 하는 컴포넌트로 서비스와 파드 간의 트래픽을 라우팅 하는 역할을 수행</p>
</li>
<li><p>역할</p>
<ul>
<li>클러스터 네트워크 트래픽 관리<ul>
<li>쿠버네티스의 서비스 개념을 구현하기 위해 각 노드에서 실행됨</li>
<li>클러스터 내부에서 서비스와 파드 간 트래픽을 적절히 분배</li>
</ul>
</li>
<li>서비스 IP와 포트 포워딩<ul>
<li>쿠버네티스 서버스가 가상 IP(Cluster IP)를 사용해도 실제 파드로 트래픽이 전달 되도록 설정</li>
<li>iptables, ipvs, 또는 userspace 모드를 활용하여 트래픽을 제어</li>
</ul>
</li>
</ul>
</li>
<li><p>실행 방식</p>
<ul>
<li>쿠버네티스 클러스터의 각 노드에서 데몬셋(DaemonSet) 으로 실행됨</li>
<li><code>kubectl get pods -n kube-system</code> 으로 확인 가능</li>
</ul>
</li>
<li><p><code>iptables 모드 (기본값)</code></p>
<ul>
<li>각 노드에서 iptables 규칙을 생성하여 패킷을 올바른 파드로 전달</li>
<li>빠르고 효율적 (커널 레벨에서 처리됨)</li>
<li>단점: 수천 개의 서비스가 있을 경우 iptables 규칙이 많아져 성능 저하 가능</li>
</ul>
</li>
<li><p><code>ipvs 모드 (권장)</code></p>
<ul>
<li>ipvsadm 을 사용하여 커널 공간에서 패킷을 처리</li>
<li>고성능, 대규모 트래픽 처리에 적합</li>
<li>L4 로드 밸런싱을 지원 (RR, Least Connection 등)</li>
<li>ipvs 커널 모듈이 필요</li>
</ul>
</li>
<li><p><code>userspace 모드 (비추천)</code></p>
<ul>
<li>트래픽을 kube-proxy 프로세스를 거쳐 전달 (유저 공간에서 처리)</li>
<li>성능이 낮아서 현재는 잘 사용되지 않음</li>
</ul>
</li>
</ul>
<h2 id="container-runtime">Container Runtime</h2>
<ul>
<li>컨테이너 런타임은 컨테이너 실행을 담당</li>
<li>쿠버네티스는 다양한 종류의 런타임을 지원하는데 Docker, Containerd, CRI-O 등</li>
</ul>
<h2 id="kubernetes-controller">Kubernetes Controller</h2>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/375e5e14-ff6e-4550-a028-1023a8c3a7b7/image.png" alt=""></p>
<ul>
<li><p>Pod를 관리하는 역할</p>
</li>
<li><p><code>DaemonSet</code></p>
<ul>
<li>파드를 생성하고 관리하는 컨트롤러</li>
<li>Deployment 가 실행해야 할 Pod의 개수와 배포 전략을 세분화해 조작할 수 있다면 DaemonSet은 모든 노드에 파드를 배포하고 관리하는 컨트롤러</li>
<li>데몬셋은 대부분 노드마다 배치되어야 하는 성능 수집 및 로그 수집 같은 작업에 사용</li>
<li>taint / toleration<ul>
<li>쿠버네티스 클러스터를 운영하다 보면 특정 워커 노드에는 특정 성격의 파드만 배포하고 싶을 때가 있는데, GPU가 설치된 워커노드에는 GPU가 필요한 파드만 배포하고 싶은 경우에 사용하는 것이 taint 와 toleration</li>
<li>taint 가 설정된 노드에는 일반적으로 사용되는 파드는 배포될 수 없으나 toleration 을 적용하면 배포할 수 있음</li>
</ul>
</li>
<li>Manifest<ul>
<li>apiVersion: apps/v1</li>
<li>kind: DaemonSet</li>
</ul>
</li>
</ul>
</li>
<li><p><code>Deployment</code></p>
<ul>
<li>쿠버네티스에서 stateless 애플리케이션을 배포할 때 사용하는 가장 기본적인 컨트롤러</li>
<li>ReplicaSet 의 상위 개념이면서 Pod를 배포할 때 사용하고 다양한 방법을 지원해서 배포할 때 세밀한 조작이 가능</li>
<li>container ← Pod ← ReplicaSet ← Deployment</li>
<li>Manifest<ul>
<li>apiVersion: apps/v1</li>
<li>kind: Deployment</li>
</ul>
</li>
</ul>
</li>
<li><p><code>ReplicaSet</code></p>
<ul>
<li>몇 개의 Pod를 유지할 지 결정하는 컨트롤러</li>
<li>Manifest<ul>
<li>apiVersion: apps/v1</li>
<li>kind: ReplicaSet</li>
</ul>
</li>
<li>ReplicaSet이 관리하는 동일한 구성의 파드를 <code>Replica</code> 라고 부름</li>
<li>파드의 수를 조정하는 것을 Replica의 수를 조정/결정한다고 표현</li>
</ul>
</li>
<li><p><code>StatefulSet</code></p>
<ul>
<li>애플리케이션의 Stateful 을 관리하는데 사용하는 워크로드 API 오브젝트</li>
<li>파드 집합의 Deployment 와 Scaling 을 관리하며 파드들의 순서 및 고유성을 보장</li>
<li>Deployment 와는 다르게 StatefulSet 은 각 파드의 독자성을 유지하는데 파드들은 동일한 스펙으로 생성되었지만 서로 교체는 불가능</li>
<li>각각은 재 스케줄링 간에도 지속적으로 유지되는 식별자를 가짐</li>
<li>스토리지 볼륨을 사용해서 워크로드에 지속성을 제공하려는 경우 솔루션의 일부로 StatefulSet 을 사용할 수 있음</li>
<li>Manifest<ul>
<li>apiVersion: apps/v1</li>
<li>kind: StatefulSet</li>
</ul>
</li>
</ul>
</li>
<li><p><code>Job</code></p>
<ul>
<li>하나 이상의 파드를 지정하고 지정된 수의 파드가 성공적으로 실행되도록 해주는 컨트롤러</li>
<li>노드의 하드웨어 장애나 재부팅 등으로 파드가 비정상적으로 작동하면(혹은 실행되지 않으면) 다른 노드에서 파드를 시작해 서비스가 지속되도록 함</li>
<li>Manifest<ul>
<li>apiVersion: batch/v1</li>
<li>kind: Job</li>
</ul>
</li>
</ul>
</li>
<li><p><code>CronJob</code></p>
<ul>
<li>Job의 일종으로 특정 시간에 특정 파드를 실행시키는 것과 같이 지정한 일정에 따라서 Job을 실행시킬 때 사용</li>
<li>CronJob은 애플리케이션 프로그램, 데이터베이스 등에서 데이터를 백업하는 데 주로 사용</li>
<li>Manifest<ul>
<li>apiVersion: batch/v1</li>
<li>kind: CronJob</li>
<li>Spec:<ul>
<li>schedule: <em>/1 *</em> * * **</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p><code>ReplicationController</code></p>
</li>
</ul>
<h1 id="kubernetes-communication">Kubernetes Communication</h1>
<h2 id="kubernetes-service">Kubernetes Service</h2>
<h3 id="개요-2">개요</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/11c25e2b-503d-431d-9717-9ce9109fcaa0/image.png" alt=""></p>
<ul>
<li>파드는 쿠버네티스 클러스터 안에서 옮겨 다니는 특성이 있는데 파드가 실행 중인 워커 노드에 문제가 생기면 다른 워커 노드에서 파드가 다시 생성될 수 있고 파드 IP가 변경되기도 함</li>
<li><code>동적으로 변하는 파드에 고정된 방법으로 접근하기 위해서 사용하는 것</code>이 Service</li>
<li>Service를 사용하면 파드가 클러스터 내의 어디에 떠 있든지 고정된 주소를 이용해 접근할 수 있으며 클러스터 외부에서 파드에 접근할 수도 있음</li>
<li>하나의 Service가 관리하는 파드는 모두 동일한 구성을 갖는데 구성이 다른 파드는 별도의 Service로 관리</li>
<li>여러 개의 워커 노드에 걸쳐 실행되더라도 동일한 구성의 파드는 하나의 Service가 관리 가능</li>
<li>서비스의 역할은 로드밸런서로 각 서비스는 자동적으로 고정된 IP 주소(cluster IP) 를 설정해서 이 주소로 들어오는 통신을 처리하는 객체</li>
<li>내부적으로는 여러 개의 파드가 있어도 밖에서는 하나의 IP 주소(cluster IP)만 볼 수 있으며 이 주소로 접근하면 서비스가 통신을 적절히 분배해주는 구조</li>
<li>서비스가 분배하는 통신은 한 워커 노드 안으로 국한되며 여러 워커 노드 간의 분배는 로드밸런서 또는 Ingress(인그레스)가 담당하는데 이들은 마스터 노드에도 워커 노드도 아닌 <code>별도의 노드에서 동작하거나 물리적 전용 하드웨어</code></li>
</ul>
<h3 id="종류-1">종류</h3>
<ul>
<li><p><code>Cluster IP</code></p>
<ul>
<li>쿠버네티스 클러스터 내의 파드들은 기본적으로 외부에서 접근할 수 있는 IP를 할당받지 않지만, 같은 클러스터 내부에서는 파드들이 통신할 방법을 제공해주어야 하는데 그것이 Cluster IP로 Cluster 내의 모든 파드가 해당 Cluster IP 주소로 접근할 수 있음</li>
</ul>
</li>
<li><p><code>Node Port</code></p>
<ul>
<li>서비스를 외부로 노출할 때 사용하는것</li>
<li>노드 포트로 서비스를 노출하기 위해 워커 노드의 IP와 포트를 이용하는데, 워커 노드의 IP가 192.168.2.3 이고 30010 이라는 노드 포트를 사용한다면 외부에서 192.168.2.3:30010 로 접근할 수 있음</li>
</ul>
</li>
<li><p><code>Load Balancer</code></p>
<ul>
<li>퍼블릭 클라우드에 존재하는 Load Balancer 에 연결하고자 할 때 사용되는 것으로 사용자는 Load Balancer의 외부 IP를 통해 접근</li>
</ul>
</li>
<li><p><code>Ingress</code></p>
<ul>
<li>URI, Hostname, Path 등과 같은 웹 개념을 이해하는 프로토콜로 <code>인지형(protocol-aware configuration)</code> 설정 메커니즘을 이용하여 HTTP(혹은 HTTPS) Network 서비스를 사용 가능하게 해주는 것</li>
<li>Ingress 개념은 쿠버네티스 API를 통해 정의한 규칙에 기반하여 트래픽을 다른 Service에 매핑할 수 있게 해줌</li>
</ul>
</li>
<li><p><code>External Name</code></p>
<ul>
<li>외부의 특정 <code>FQDN(Fully Qualified Domain Name - 완전한 도메인 이름)</code> 에 대한 <code>CNAME</code> 매핑을 제공하는 것으로 파드가 CNAME을 이용해 특정 FQDN과 통신하기 위함</li>
</ul>
</li>
<li><p>Manifest</p>
<ul>
<li>apiVersion: v1</li>
<li>kind: Service</li>
<li>spec:<ul>
<li>type: ClusterIP</li>
<li>selector:</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="kubernetes-에서-통신할-때-나타나는-특징">Kubernetes 에서 통신할 때 나타나는 특징</h2>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/a92cf2c2-b8fa-4ee2-a5d8-b5c853e75605/image.png" alt=""></p>
<ul>
<li><code>파드가 사용하는 네트워크</code>와 <code>호스트(노드)가 사용하는 네트워크</code>는 다름<ul>
<li>노드 내의 파드들은 가상의 네트워크를 사용하고 호스트는 물리 네트워크를 사용</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/a88c18eb-2d4a-48f3-89ce-22ca3f692c5b/image.png" alt=""></p>
<ul>
<li>동일한 노드에 떠 있는 파드끼리 통신할 수 있음<ul>
<li>동일한 노드에 떠 이쓴 파드들은 통신이 가능하지만 다른 노드의 파드 또는 외부와의 통신은 불가능</li>
</ul>
</li>
</ul>
<h2 id="같은-파드에-포함된-컨테이너-간-통신">같은 파드에 포함된 컨테이너 간 통신</h2>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/d5a1f3cf-9b51-4c9a-b3f1-98c2f9a60462/image.png" alt=""></p>
<ul>
<li>기본적으로 같은 파드 내에 있는 컨테이너 간의 통신은 직접 통신(로컬 호스트 통신)이 가능한데 하나의 파드에는 하나의 가상 네트워크가 생성되며 그 파드 내에 존재하는 컨테이너들은 같은 가상 네트워크를 사용하기 때문</li>
<li>하나의 파드 내에 존재하는 컨테이너들은 모두 동일한 IP 사용</li>
<li>같은 파드 내에 존재하는 컨테이너들은 모두 동일한 IP를 사용하는데 이를 구별하기 위해서 포트 번호를 이용</li>
<li>IP는 같지만 포트 번호가 다르므로 두 개의 컨테이너를 구분할 수 있음</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/6df6974c-42a5-40f5-8988-5b6fd55c6560/image.png" alt=""></p>
<ul>
<li>사용자가 컨테이너 2의 서비스에 접근한다면 <a href="http://a.com:1002">http://a.com:1002</a> 처럼 URL에 포트 정보를 입력하면 이후 eth0와 veth0 을 거쳐 서비스(container2)에 접근</li>
</ul>
<h2 id="단일-노드에서-파드-간-통신">단일 노드에서 파드 간 통신</h2>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/66fff0c0-d6b6-4618-b56f-e19b56cedb1b/image.png" alt=""></p>
<ul>
<li>veth0 과 eth0 사이에는 docker0 라고 하는 브리지가 존재</li>
<li>단일 노드에 떠 있는 파드들은 같은 대역(veth0: 172.10.0.1, veth0: 172.10.0 2)을 사용하므로 docker0 라는 브리지르르 통해 서로 통신</li>
</ul>
<h2 id="다른-노드의-파드와-통신하려면-cni-plugin-필요">다른 노드의 파드와 통신하려면 CNI Plugin 필요</h2>
<ul>
<li>CNI(Container Network Interface) 는 컨테이너 간의 통신을 위한 네트워크 인터페이스</li>
<li>CNI Plugin은 컨테이너 들의 네트워크를 연결하거나 삭제하며 삭제할 때 할당된 자원을 제거</li>
</ul>
<h2 id="다수-노드에서-파드-간-통신-문제">다수 노드에서 파드 간 통신 문제</h2>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/9a066756-2b68-4786-b368-cf702c66da7b/image.png" alt=""></p>
<ul>
<li>서로 다른 워커 노드에 존재하는 파드의 IP가 동일하게 할당되는 문제가 발생하는데 이 문제를 해결할 수 있는 방법이 Overlay Network</li>
<li>Overlay Network란 노드에서 사용하는 물리적인 Network 위에 가상의 Network 를 구성하는 것</li>
<li>Overlay Network 를 사용하면 쿠버네티스 클러스터로 묶인 노드에 떠 있는 파드 간의 통신이 가능</li>
<li>쿠버네티스는 오버레이 네트워크를 구성하기 위해 클러스터를 구성할 때 CNI 규약을 따르는 플러그인을 함께 설치하는 것을 강제</li>
<li>쿠버네티스는 기본적으로 <code>kubenet</code> 이라는 기본적이고 간단한 Network Plugin 을 제공하지만 다수의 노드에 떠 있는 파드 간의 통신이나 네트워크 정책 설정 같은 고급 기능은 제공하지 않기 때문에 CNI 의 스펙으 준수하는 네트워크 플러그인을 사용해야 함</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/065adddb-5127-4ce5-ad65-5fc21843eece/image.png" alt=""></p>
<h3 id="cnicontainer-network-interface">CNI(Container Network Interface)</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/04714a03-2789-4b92-a28a-4b5f20a29e0f/image.png" alt=""></p>
<ul>
<li>파드 간의 통신은 동일한 호스트 내에서만 가능</li>
<li>파드1과 파드3은 통신할 수 있지만 파드1과 파드2는 통신할 수 없고, 파드1과 파드2가 통신하기 위해 CNI 플러그인을 사용</li>
<li>각 노드에 설치된 CNI Plugin을 통해 파드들이 통신</li>
<li>쿠버네티스 클러스터 구성에 필요한 kubeadm 은 기본적으로 CNI 기반 플러그인을 지원</li>
<li>쿠버네티스에서 기본으로 제공하는 kubenet 이 아닌 CNI Plugin을 별도로 구성해서 사용</li>
</ul>
<h3 id="쿠버네티스에서-사용할-수-있는-cni-플러그인">쿠버네티스에서 사용할 수 있는 CNI 플러그인</h3>
<ul>
<li><code>Flannel</code></li>
</ul>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/fee9c932-47e2-49cd-bc26-fac0b55149e7/image.png" alt=""></p>
<pre><code>- 쿠버네티스와 함께 사용할 수 있는 Overlay Network 플러그인</code></pre><ul>
<li><p><code>Calico</code></p>
<ul>
<li>캡슐화 또는 Overlay 없이 파트 간에 통신이 가능한 네트워크를 제공하는 플러그인</li>
</ul>
</li>
<li><p><code>Cilium</code></p>
<ul>
<li>리눅스 커널 기술을 이용해 데이터 경로의 필터링, 트래픽 모니터링 및 리디렉션을 수행</li>
</ul>
</li>
<li><p><code>Multus</code></p>
<ul>
<li>쿠버네티스에서 다중 네트워크 자원을 위한 멀티 플러그인으로 2개 이상의 플러그인을 사용할 때 유용</li>
<li>Multus 는 CNI 사양을 구현하는 다양한 유형의 플러그인을 지원하고 NFV 기반 응용 프로그램과 함께 SRIOV, DPDK, OVS-DPDK, VPP 도 지원</li>
</ul>
</li>
</ul>
<h2 id="파드와-서비스-간의-통신">파드와 서비스 간의 통신</h2>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/3a1a8202-a37a-40b5-b89e-a62e31ab71b2/image.png" alt=""></p>
<h3 id="service의-특성">Service의 특성</h3>
<ul>
<li>Service 도 파드와 같이 IP를 가짐</li>
<li>파드와 서비스에서 사용하는 IP 대역이 다름<ul>
<li>파드의 IP 대역은 <code>10.244.1.x</code> 지만, 서비스에서 사용하는 IP 대역은 <code>10.101.x.x</code></li>
<li>서비스에서 사용하는 가상 네트워크는 ifconfig 나 Routing Table 에서 확인할 수 없음</li>
</ul>
</li>
<li>Service IP 와 관련된 정보를 라우팅 테이블에서 확인할 수 없지만 외부에서 서비스 IP로 서비스를 요청하면 서비스 IP는 특정 파드에 전달됨</li>
</ul>
<h3 id="netfilter">netfilter</h3>
<p><img src="https://velog.velcdn.com/images/pizza_loves_me/post/a7691a49-4e1e-4345-8c07-f7462c2c775c/image.png" alt=""></p>
<ul>
<li>netfilter는 규칙 기반의 패킷 처리 엔진으로 서비스 IP를 특정 IP로 변경할 수 있는 규칙을 저장하고 변환하는 역할을 하는데 netfilter 와 iptables</li>
<li>규칙 기반의 패킷 처리 엔진으로 서비스 IP를 특정 IP로 변경할 수 있는 규칙을 저장하고 변환하는 역할을 하는데 <code>Proxy</code> 라고도 함</li>
<li>서비스 IP를 만나면 서버 IP로 변환하고 라우터/게이트웨이로 패킷을 전달하는데 동일한 작업이 Worker Node2에서도 진행되고 이후에는 라우터/게이트웨이를 거쳐서 Worker Node2의 Web Server Pod에 접근할 수 있음</li>
</ul>
<h2 id="외부와-통신할-수-있게-하는-서비스-유형">외부와 통신할 수 있게 하는 서비스 유형</h2>
<ul>
<li><code>Node Port</code><ul>
<li>Node IP(eth)에 포트를 붙여서 외부에 노출시키는 것을 의미하는데 노드의 IP가 192.168.10.2 라면 포트를 붙여서 192.168.10.2:30001 같은 형태를 만들어서 외부에 노출</li>
</ul>
</li>
<li><code>Load Balancer</code><ul>
<li>퍼블릭 클라우드에서 제공하는 로드밸런서와 파드를 연결한 후 해당 Load Balancer IP를 이용해 클러스터 외부에서 파드에 접근할 수 있도록 해줌</li>
</ul>
</li>
<li><code>Ingress</code><ul>
<li>클러스터 외부에서 내부로 접근하는 요청들을 어떻게 처리할지에 관한 규칙 모음</li>
<li>인그레스 자체는 클러스터 외부에서 URL로 접근할 수 있도록 Load Balancing, SSL 인증서 처리 등의 규칙을 정의한 것에 불과하며 실제로 동작시키는 것은 Ingress-Controller</li>
</ul>
</li>
</ul>
<hr>
]]></description>
        </item>
    </channel>
</rss>