- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며
- 1. 전체 아키텍처 한눈에 보기
- 2. 컨트롤 플레인 구성요소
- 3. 핵심 원리 — 컨트롤러 루프와 desired state
- 4. 노드 구성요소
- 5. 파드 생성 시퀀스 다이어그램
- 6. 네트워킹
- 7. 스토리지 (CSI)
- 8. 확장 — CRD와 오퍼레이터
- 9. 운영에서 자주 만나는 함정
- 10. 헬스 프로브와 파드 수명주기
- 마치며
- 참고 자료
들어가며
쿠버네티스(Kubernetes)는 컨테이너화된 애플리케이션의 배포·확장·운영을 자동화하는 오케스트레이션 플랫폼입니다. 처음 접하면 수많은 구성요소와 오브젝트에 압도되기 쉽지만, 핵심을 관통하는 하나의 원리가 있습니다. 바로 선언적 desired state와 이를 끊임없이 맞춰가는 컨트롤러 루프입니다.
이 글은 "무엇을 명령하는가"보다 "클러스터 내부에서 그 명령이 어떻게 흐르고, 컨트롤 플레인과 노드가 어떻게 협력해 desired state를 실현하는가"를 그림으로 풀어냅니다. 구체적인 동작은 버전과 배포판에 따라 달라질 수 있으므로, 정확한 동작은 공식 문서(kubernetes.io)를 함께 확인하시길 권합니다.
1. 전체 아키텍처 한눈에 보기
클러스터는 크게 **컨트롤 플레인(두뇌)**과 **워커 노드(일꾼)**로 나뉩니다.
┌──────────────────────── 컨트롤 플레인 ────────────────────────┐
│ │
│ ┌─────────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ kube-api │◀──▶│ etcd │ │ controller- │ │
│ │ server │ │ (상태저장) │ │ manager │ │
│ └──────┬──────┘ └──────────┘ └──────────────────┘ │
│ │ ┌──────────────┐ │
│ │ │ scheduler │ │
│ │ └──────────────┘ │
└──────────┼────────────────────────────────────────────────────┘
│ (모든 통신은 api-server 경유)
┌───────┴───────────────────┬───────────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 워커 노드 1 │ │ 워커 노드 2 │ │ 워커 노드 N │
│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │ kubelet │ │ │ │ kubelet │ │ │ │ kubelet │ │
│ ├─────────────┤ │ │ ├─────────────┤ │ │ ├─────────────┤ │
│ │ kube-proxy │ │ │ │ kube-proxy │ │ │ │ kube-proxy │ │
│ ├─────────────┤ │ │ ├─────────────┤ │ │ ├─────────────┤ │
│ │ 컨테이너런타임 │ │ │ │ 컨테이너런타임 │ │ │ │ 컨테이너런타임 │ │
│ │ (CRI) │ │ │ │ (CRI) │ │ │ │ (CRI) │ │
│ ├─────────────┤ │ │ ├─────────────┤ │ │ ├─────────────┤ │
│ │ Pod Pod Pod │ │ │ │ Pod Pod │ │ │ │ Pod │ │
│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
가장 중요한 규칙은 모든 통신이 api-server를 거친다는 점입니다. 컴포넌트끼리 직접 대화하지 않고, api-server를 중앙 허브로 삼아 상태를 읽고 씁니다.
2. 컨트롤 플레인 구성요소
kube-apiserver — 클러스터의 정문
api-server는 모든 요청의 입구입니다. 인증·인가·검증을 거친 뒤 상태를 etcd에 저장하고, 다른 컴포넌트에 변경을 알립니다.
kubectl / 컨트롤러 / kubelet
│ REST 요청
▼
┌──────────────────────────────────┐
│ kube-apiserver │
│ 1) 인증 (누구인가?) │
│ 2) 인가 (권한이 있는가? RBAC) │
│ 3) Admission (정책/변형/검증) │
│ 4) etcd 에 저장 │
└──────────────────────────────────┘
│
▼ (저장된 상태를 watch 로 구독)
etcd — 단일 진실의 원천
etcd는 분산 키-값 저장소로, 클러스터의 모든 상태(오브젝트 명세, 현재 상태)를 보관합니다. 클러스터의 기억이라 할 수 있으며, etcd가 없으면 클러스터는 자신이 무엇을 원했는지 알 수 없습니다.
kube-scheduler — 파드를 노드에 배치
스케줄러는 "아직 노드가 정해지지 않은 파드"를 찾아 적절한 노드를 고릅니다.
대기 중 파드 발견
│
▼
┌─────────────────────────────────────┐
│ 1단계: 필터링(Filtering) │
│ - 리소스 충분한가? (CPU/메모리) │
│ - nodeSelector/affinity 만족? │
│ - taint/toleration 통과? │
│ ──▶ 후보 노드 집합으로 좁힘 │
├─────────────────────────────────────┤
│ 2단계: 점수화(Scoring) │
│ - 후보들에 점수 부여(분산, 친화도 등)│
│ ──▶ 최고점 노드 선택 │
└─────────────────────────────────────┘
│
▼
파드에 노드 바인딩(api-server에 기록)
kube-controller-manager — 컨트롤러들의 집
여러 컨트롤러를 하나의 프로세스로 묶어 실행합니다. 각 컨트롤러는 특정 리소스의 desired state와 현재 상태를 비교해 차이를 메웁니다(예: Deployment, ReplicaSet, Node, Job 컨트롤러).
3. 핵심 원리 — 컨트롤러 루프와 desired state
쿠버네티스를 이해하는 단 하나의 열쇠는 **조정 루프(reconciliation loop)**입니다. 사용자는 "원하는 상태"를 선언하고, 컨트롤러는 그 상태가 될 때까지 현실을 끊임없이 조정합니다.
┌──────────────────────────────────────────┐
│ 조정 루프 (무한 반복) │
│ │
│ 1) desired state 읽기 (예: replicas=3) │
│ │ │
│ ▼ │
│ 2) current state 관찰 (실제 파드 2개) │
│ │ │
│ ▼ │
│ 3) 차이 계산 (3 - 2 = 1 부족) │
│ │ │
│ ▼ │
│ 4) 액션 (파드 1개 추가 생성) │
│ │ │
│ └────────▶ (다시 1로) │
└──────────────────────────────────────────┘
이 루프 덕분에 쿠버네티스는 자가 치유(self-healing) 합니다. 파드가 죽으면 current state가 desired state보다 부족해지고, 컨트롤러가 즉시 새 파드를 만들어 채웁니다.
오브젝트 계층
Deployment
│ 관리
▼
ReplicaSet (replicas=3)
│ 관리
▼
Pod Pod Pod ◀── 실제 실행 단위(컨테이너 묶음)
Deployment는 롤링 업데이트와 롤백을 담당하고, ReplicaSet은 파드 개수 유지를 담당하며, Pod는 실제로 컨테이너를 담는 최소 배포 단위입니다.
4. 노드 구성요소
kubelet — 노드의 대리인
kubelet은 각 노드에서 동작하며, api-server로부터 "이 노드에 어떤 파드를 띄워야 하는지"를 받아 컨테이너 런타임에 실제 생성을 지시합니다. 그리고 파드 상태를 주기적으로 api-server에 보고합니다.
api-server ──"이 파드를 띄워라"──▶ kubelet
│
▼
컨테이너 런타임(CRI)에 생성 요청
│
▼
컨테이너 실행 + 헬스체크
│
상태 보고 ──▶ api-server
kube-proxy — 서비스 네트워킹
kube-proxy는 각 노드에서 Service 추상화를 실제 네트워크 규칙으로 구현합니다. 클라이언트가 Service의 가상 IP로 보낸 트래픽을 실제 파드들로 분배합니다.
컨테이너 런타임 (CRI)
kubelet은 표준 인터페이스인 **CRI(Container Runtime Interface)**를 통해 런타임과 대화합니다. 덕분에 특정 런타임에 묶이지 않고 다양한 구현을 갈아끼울 수 있습니다.
kubelet ──CRI(표준 인터페이스)──▶ 컨테이너 런타임
│
이미지 풀, 컨테이너 생성/시작/정지
5. 파드 생성 시퀀스 다이어그램
kubectl apply로 Deployment를 만들면 어떤 일이 벌어지는지 단계별로 따라가 봅니다.
사용자 api-server etcd controller scheduler kubelet
│ │ │ │ │ │
│ apply │ │ │ │ │
├─────────────▶│ │ │ │ │
│ │ 인증/인가/검증 │ │ │
│ ├───────────▶│ 저장 │ │ │
│ │ │ │ │ │
│ │ watch: 새 Deployment 감지 │ │
│ │◀───────────────────────┤ │ │
│ │ ReplicaSet 생성 │ │ │
│ │◀───────────────────────┤ │ │
│ │ Pod(노드 미지정) 생성 │ │
│ │◀───────────────────────┤ │ │
│ │ │ │ │
│ │ watch: 미배치 파드 감지 │ │
│ │◀───────────────────────────────────┤ │
│ │ 노드 바인딩 기록 │ │ │
│ │◀───────────────────────────────────┤ │
│ │ │ │ │
│ │ watch: 내 노드의 파드 감지 │
│ │◀───────────────────────────────────────────────┤
│ │ 컨테이너 런타임에 생성 지시 → 실행 │
│ │ 상태 보고(Running) │ │ │
│ │◀───────────────────────────────────────────────┤
▼ ▼ ▼ ▼ ▼ ▼
핵심은 어떤 컴포넌트도 서로를 직접 호출하지 않는다는 점입니다. 모두 api-server의 상태를 watch하며, 자신과 관련된 변화가 보이면 행동합니다. 이 느슨한 결합이 쿠버네티스의 확장성과 견고함의 비결입니다.
6. 네트워킹
쿠버네티스 네트워킹은 몇 가지 계층으로 이해하면 명료합니다.
┌──────────────────────────────────────────────────────────┐
│ 1) 파드 네트워크 (CNI) │
│ - 모든 파드는 고유 IP를 가지며 NAT 없이 서로 통신 │
│ - CNI 플러그인이 이 규약을 구현 │
├──────────────────────────────────────────────────────────┤
│ 2) Service (안정적 가상 IP + 로드밸런싱) │
│ - 파드는 사라지고 재생성되며 IP가 바뀜 │
│ - Service는 변하지 않는 이름/IP로 파드 집합을 추상화 │
├──────────────────────────────────────────────────────────┤
│ 3) Ingress / Gateway API (외부 → 내부 라우팅) │
│ - HTTP 경로/호스트 기반으로 외부 트래픽을 Service로 분배 │
└──────────────────────────────────────────────────────────┘
Service가 트래픽을 보내는 방식
클라이언트 ──▶ Service(가상 IP, 변하지 않음)
│ 엔드포인트 집합 참조
┌─────────────┼─────────────┐
▼ ▼ ▼
Pod A Pod B Pod C
(IP 바뀌어도 Service 이름은 그대로)
Gateway API
전통적인 Ingress의 한계를 보완하기 위해 Gateway API가 표준으로 자리잡아 가고 있습니다. 역할을 분리(인프라 담당자 vs 앱 개발자)하고, 더 표현력 있는 라우팅을 제공합니다.
GatewayClass (인프라 종류 정의)
│
▼
Gateway (리스너: 포트/프로토콜)
│
▼
HTTPRoute (경로/호스트 → Service 매핑)
│
▼
Service ──▶ Pod
7. 스토리지 (CSI)
컨테이너는 본질적으로 휘발성입니다. 상태를 보존하려면 외부 볼륨이 필요하고, 쿠버네티스는 **CSI(Container Storage Interface)**라는 표준으로 다양한 스토리지를 연결합니다.
PersistentVolumeClaim (PVC) ◀── 앱이 "이만큼의 저장소를 원해" 라고 요청
│ 바인딩
▼
PersistentVolume (PV) ◀── 실제 저장 자원(동적/정적 프로비저닝)
│ CSI 드라이버 경유
▼
외부 스토리지 (블록/파일/오브젝트)
StorageClass (프로비저닝 정책 정의)
│ PVC가 참조
▼
동적 프로비저닝: PVC 생성 시 PV를 자동으로 만들어 바인딩
이 추상화 덕분에 앱은 "저장소가 필요하다"고만 선언하고, 실제 어떤 스토리지인지는 운영자가 StorageClass로 결정합니다.
8. 확장 — CRD와 오퍼레이터
쿠버네티스의 진짜 힘은 확장 가능성입니다. CRD(Custom Resource Definition)로 새로운 오브젝트 종류를 정의하고, 오퍼레이터로 그 오브젝트를 조정하는 컨트롤러를 직접 작성할 수 있습니다.
CRD 정의 ──▶ 새 리소스 종류 등록 (예: Database)
│
▼
사용자가 Database 오브젝트 생성 (desired state 선언)
│
▼
오퍼레이터(커스텀 컨트롤러)가 watch
│ 조정 루프 실행
▼
실제 DB 파드/스토리지/백업을 desired state에 맞춰 생성·운영
오퍼레이터 패턴은 "쿠버네티스의 조정 루프 원리를 임의의 도메인에 적용"하는 것입니다. 데이터베이스, 메시지 큐, ML 워크로드 등 복잡한 운영 지식을 코드로 담아 자동화합니다.
9. 운영에서 자주 만나는 함정
[ ] 리소스 requests/limits 미설정 ─▶ 노드 과부하, 예측 불가한 축출(eviction)
[ ] 라이브니스/레디니스 프로브 부재 ─▶ 죽은 파드로 트래픽 유입
[ ] etcd 백업 미수행 ─▶ 컨트롤 플레인 장애 시 복구 불가
[ ] 단일 노드/단일 컨트롤플레인 ─▶ 가용성 취약(다중화 권장)
[ ] 무분별한 권한(RBAC) ─▶ 보안 위험, 최소 권한 원칙 적용
[ ] 이미지 태그 latest 남용 ─▶ 재현 불가, 명시적 태그/다이제스트 권장
특히 requests/limits 설정은 운영 안정성의 기초입니다. requests는 스케줄러가 배치 결정을 내리는 근거가 되고, limits는 한 파드가 노드를 잠식하지 못하게 막습니다.
10. 헬스 프로브와 파드 수명주기
자가 치유가 실제로 작동하려면, 클러스터가 "파드가 살아있는가, 트래픽을 받을 준비가 되었는가"를 알아야 합니다. 이를 판단하는 것이 세 종류의 프로브입니다.
┌────────────────────────────────────────────────────────────┐
│ Liveness Probe (살아있는가?) │
│ 실패 ─▶ kubelet 이 컨테이너를 재시작 │
│ 용도: 데드락/멈춤 감지 │
├────────────────────────────────────────────────────────────┤
│ Readiness Probe (받을 준비가 되었는가?) │
│ 실패 ─▶ Service 엔드포인트에서 제외(트래픽 차단) │
│ 용도: 워밍업/일시적 과부하 동안 트래픽 보호 │
├────────────────────────────────────────────────────────────┤
│ Startup Probe (다 떴는가?) │
│ 성공 전까지 liveness/readiness 보류 │
│ 용도: 기동이 느린 레거시 앱 보호 │
└────────────────────────────────────────────────────────────┘
파드의 상태도 정해진 수명주기를 따릅니다. 각 단계의 의미를 알면 kubectl get pods 출력이 훨씬 잘 읽힙니다.
Pending ──▶ 스케줄/이미지 풀 대기 중
│
▼
Running ──▶ 컨테이너가 노드에서 실행 중
│
├──▶ Succeeded ──▶ (Job 처럼) 정상 종료
└──▶ Failed ──▶ 비정상 종료
(Unknown) ──▶ 노드와 통신 불가
종료 흐름과 graceful shutdown
파드를 삭제하면 즉시 죽지 않고, 정해진 순서로 정리됩니다. 이 흐름을 이해하면 무중단 배포의 비결이 보입니다.
삭제 요청
│
▼
1) Service 엔드포인트에서 제외 (새 트래픽 차단)
│
▼
2) preStop 훅 실행 (있으면) + SIGTERM 전송
│
▼
3) grace period 동안 진행 중 요청 마무리 대기
│
▼
4) 기한 초과 시 SIGKILL 강제 종료
readiness 프로브로 트래픽을 먼저 끊고, graceful 종료로 진행 중 요청을 마무리하는 조합이 롤링 업데이트 중에도 사용자 경험을 매끄럽게 유지하는 핵심입니다.
마치며
쿠버네티스의 복잡해 보이는 구성요소들은 사실 하나의 단순한 원리로 수렴합니다. 사용자는 원하는 상태를 선언하고, 모든 컴포넌트는 api-server를 통해 상태를 watch하며, 컨트롤러들은 현실을 그 상태에 맞추도록 조정합니다. 이 선언적 모델과 조정 루프가 자가 치유, 확장성, 그리고 CRD/오퍼레이터를 통한 무한한 확장성의 토대입니다.
컴포넌트의 이름을 외우기보다, "이 변화가 누구의 watch를 자극하고, 어떤 조정을 유발하는가"를 추적하는 습관을 들이면 클러스터의 동작이 한결 명료해집니다. 세부 동작과 기능은 버전과 배포판에 따라 달라질 수 있으니, 실제 운영에서는 공식 문서를 함께 참조하시길 권합니다.