쿠버네티스는 무엇으로 이루어져 있는가
쿠버네티스 아키텍처 그림을 처음 보면 상자가 너무 많아서 겁부터 납니다. 하지만 실제로 알아야 할 것은 많지 않습니다. 역할로 나누면 딱 두 그룹뿐입니다 — 컨트롤 플레인(결정을 내리는 쪽)과 노드(실제로 컨테이너를 돌리는 쪽). 이 장에서는 kubectl apply 명령 하나가 입력되는 순간부터 실제 컨테이너가 뜨기까지, 그 사이에 어떤 컴포넌트가 무슨 일을 하는지만 따라갑니다.
컨트롤 플레인 — 결정을 내리고 기록만 할 뿐, 컨테이너는 안 돌린다
컨트롤 플레인은 네 개의 컴포넌트로 이루어져 있는데, 이들이 하는 일은 사실 "누가 무엇을 어디에 둘지 결정하고 그 결정을 기록해두는 것"이 전부입니다. 실제 컨테이너는 여기서 단 하나도 돌지 않습니다.
- API 서버(kube-apiserver) — 클러스터로 들어오는 모든 요청의 유일한 입구입니다.
kubectl이든, 대시보드든, 다른 컴포넌트든 클러스터 상태를 읽거나 바꾸려면 반드시 이 문을 거칩니다. - etcd — 클러스터의 모든 상태가 저장되는 key-value 저장소입니다. "이 상태를 유지해줘"라고 선언한 그 선언 자체가 여기 저장됩니다. etcd가 날아가면 클러스터가 어떤 상태였는지 아무도 모르게 됩니다 — 이게 왜 프로덕션 클러스터에서 etcd 백업을 그렇게 강조하는지의 이유입니다.
- 스케줄러(kube-scheduler) — 새로 만들어야 할 Pod이 생기면 "이 Pod을 어느 노드에 둘지" 결정합니다. 각 노드의 남은 자원, Pod이 요구하는 조건(뒤에서 다룰 리소스 요청량 등)을 비교해서 고릅니다.
- 컨트롤러 매니저(kube-controller-manager) — "현재 상태가 선언된 상태와 다르면 맞춘다"는 반복 루프를 실제로 돌리는 주체입니다. Deployment가 3개의 복제본을 요구하는데 2개만 떠 있다면, 이 컴포넌트가 그 차이를 감지하고 1개를 더 만들라고 지시합니다.
노드 — 실제로 컨테이너가 도는 곳
노드는 컨트롤 플레인의 지시를 받아 실제로 컨테이너를 실행하는 서버입니다. 각 노드에는 세 가지가 돌아갑니다.
- kubelet — 각 노드에 상주하면서 "이 노드에 어떤 Pod이 떠 있어야 하는가"를 API 서버로부터 받아, 실제로 컨테이너 런타임에게 컨테이너를 띄우거나 내리라고 지시합니다.
- 컨테이너 런타임 — 실제로 컨테이너를 실행하는 소프트웨어입니다. 예전에는 Docker 엔진이 직접 쓰였지만, 지금은 대부분 containerd나 CRI-O처럼 더 가벼운 런타임을 씁니다. Docker로 만든 이미지는 OCI 표준을 따르기 때문에 이 런타임들에서도 그대로 돌아갑니다 — Docker 자체가 사라져도 여러분이 만든 이미지는 멀쩡합니다.
- kube-proxy — 노드의 네트워크 규칙을 관리해서, Service로 들어온 트래픽이 올바른 Pod으로 전달되게 합니다. 이 이야기는 6장에서 더 다룹니다.
이 컴포넌트들이 실제로 클러스터에 몇 개씩 떠 있는지는 직접 확인할 수 있습니다. 클러스터가 이미 있다면(없다면 다음 장에서 만듭니다) 이렇게 물어보면 됩니다.
kubectl get pods -n kube-system
# etcd, kube-apiserver, kube-scheduler, kube-controller-manager 가
# 대부분 이 네임스페이스에 Pod 형태로 떠 있는 걸 볼 수 있다컨트롤 플레인 자체도 쿠버네티스 위에서 Pod으로 돌아간다는 사실이 처음엔 이상하게 느껴질 수 있습니다. 관리형 서비스(EKS, GKE 등)를 쓰면 이 부분은 클라우드 제공자가 숨겨서 보이지 않는 경우가 많습니다.
kubectl apply 한 번의 여정
이 컴포넌트들이 실제로 어떻게 맞물리는지는 그림으로 보는 게 빠릅니다. Deployment 매니페스트 하나를 적용했을 때 벌어지는 일입니다.
sequenceDiagram participant U as "kubectl" participant A as "API 서버" participant E as "etcd" participant C as "컨트롤러 매니저" participant S as "스케줄러" participant K as "kubelet (노드)" U->>A: "Deployment 3개 복제본 요청" A->>E: "선언된 상태 저장" A->>C: "상태 변경 알림" C->>A: "Pod 3개 생성 요청 (아직 노드 미배정)" A->>S: "배정 안 된 Pod 알림" S->>A: "각 Pod을 노드에 배정" A->>K: "이 노드에 뜰 Pod 목록 전달" K->>K: "컨테이너 런타임에 컨테이너 실행 지시"
여기서 중요한 건 각 컴포넌트가 서로를 직접 호출하지 않는다는 점입니다. 모두가 API 서버를 거쳐 etcd의 상태를 보고, 자기 담당 영역에서 "선언된 상태"와 "실제 상태"의 차이를 감시하다가 차이가 생기면 채워 넣습니다. 이 패턴을 **컨트롤 루프(control loop)**라고 부르는데, 쿠버네티스의 거의 모든 동작 — Deployment 복제본 유지, 노드 장애 감지, 심지어 여러분이 나중에 만들 커스텀 컨트롤러까지 — 이 전부 이 패턴 하나의 변주입니다. 지금 이 그림을 완벽히 외울 필요는 없습니다. 다음 장부터 실제로 클러스터를 띄우고 명령을 넣어보면서, 이 그림이 몸에 붙을 겁니다.