Table of contents
- 클러스터를 열어보는 이유
- 전체 구조 다시 보기
- Control Plane: 클러스터의 두뇌
- Worker Node: 실제로 일하는 서버
- 한 번의 배포가 일으키는 파장
- 고가용성 구성의 감
클러스터를 열어보는 이유
1편에서 Kubernetes가 “어떤 상태여야 하는지” 선언하면 알아서 맞춰준다고 정리했다. 그런데 실제로 누가, 어떻게 맞춰주는 걸까? 이 질문에 답하려면 클러스터 내부를 뜯어봐야 한다.
클러스터 내부 구조를 이해하면 세 가지 이득이 있다. 첫째, 장애가 났을 때 어느 컴포넌트를 의심할지 빠르게 판단할 수 있다. 둘째, 보안을 설계할 때 어디를 막아야 하는지 보인다. 셋째, 고급 기능(오퍼레이터, 커스텀 컨트롤러)을 배울 때 기초가 잡혀 있다.
대부분의 책은 여기서 그림 한 장을 던지고 넘어간다. 이번 편은 조금 더 진득하게, 각 컴포넌트가 왜 존재하고 어떤 순간에 움직이는지 그려본다.
전체 구조 다시 보기
공식 문서에서는 클러스터 구조를 이렇게 정리해 그려둔다. 아래 그림과 우리가 앞으로 그릴 Mermaid 다이어그램을 겹쳐 보면서 읽으면 좋다.
출처: Kubernetes 공식 문서 — CC BY 4.0
먼저 1편에서 봤던 그림을 더 촘촘히 그려보자.
flowchart TB
subgraph CP["Control Plane (Master Node)"]
API[API Server]
ETCD[(etcd)]
SCH[Scheduler]
CM[Controller Manager]
CCM[Cloud Controller Manager]
end
subgraph WN["Worker Node"]
KUB[kubelet]
KP[kube-proxy]
CR[Container Runtime<br/>containerd / CRI-O]
POD1[Pod A]
POD2[Pod B]
CR --> POD1
CR --> POD2
KUB --> CR
end
USER[kubectl / CI / Operator] -->|REST API| API
API <-->|watch / write| ETCD
SCH -->|bind pod to node| API
CM -->|reconcile| API
CCM -->|cloud API| API
API <-->|watch / report| KUB
API <-->|watch service| KP
KP -.->|iptables/IPVS rules| POD1
KP -.->|iptables/IPVS rules| POD2
이 그림을 머릿속에 두고 하나씩 살펴보자.
Control Plane: 클러스터의 두뇌
Control Plane은 “클러스터가 어떤 상태여야 하는가”를 결정하고 관리하는 컴포넌트들의 집합이다. 보통 마스터 노드(master node)라고도 불린다. 작은 클러스터에서는 한 대, 운영 환경에서는 가용성을 위해 세 대 이상으로 구성한다.
API Server — 모든 소통의 창구
API Server는 클러스터의 정문이다. kubectl, CI 파이프라인, 오퍼레이터, 다른 내부 컴포넌트까지 전부 API Server를 통해 소통한다. 만약 API Server가 죽으면 클러스터는 “읽기 전용” 상태가 된다. 이미 떠 있는 파드는 돌아가지만, 새 배포나 변경은 불가능하다.
API Server의 역할을 세 가지로 요약할 수 있다.
- 인증/인가: 이 요청이 누가 보낸 것이고, 그 사람이 이 일을 할 권한이 있는지 확인한다
- 검증(Admission): 요청이 스키마에 맞는지, 정책에 위배되지 않는지 검사한다
- 저장과 배포: 유효한 요청을 etcd에 저장하고, 관심 있는 컴포넌트가 변화를 알 수 있도록 한다
“다른 컴포넌트가 알 수 있도록 한다”가 중요하다. Kubernetes는 API Server를 중심으로 watch 메커니즘이 돌아간다. Scheduler, Controller Manager, kubelet 같은 컴포넌트들은 “내가 관심 있는 리소스가 바뀌면 알려줘”라고 구독해두고, 변화가 생기면 반응한다.
etcd — 클러스터의 기억
etcd는 분산 키-값 저장소다. 파드 스펙, 서비스 설정, 시크릿, 모든 게 여기 들어간다. API Server만이 etcd에 직접 쓴다. 다른 컴포넌트는 반드시 API Server를 거쳐야 한다.
왜 이런 구조일까? 단일 창구 원칙을 지키기 위해서다. 만약 여러 컴포넌트가 etcd에 직접 쓴다면, 권한 검사, 검증, 감사 로그 같은 걸 각자 구현해야 한다. API Server를 거치게 하면 이 모든 걸 한 곳에서 일관되게 처리할 수 있다.
etcd는 Raft 합의 알고리즘(Raft — 여러 노드가 리더를 뽑고 다수결로 데이터 일관성을 맞추는 분산 합의 프로토콜)을 쓴다. 운영 환경에서 3대 또는 5대로 홀수 구성을 권장하는 이유다. 절반이 넘는 노드가 살아 있어야 쓰기가 가능하기 때문에, 2대로 구성하면 하나만 죽어도 클러스터가 멈춘다. 백업도 중요하다. etcd를 잃으면 클러스터의 기억을 잃는 셈이다.
Scheduler — 파드를 어디에 태울지 결정
새 파드가 만들어졌을 때 “어느 Worker Node에서 돌릴지”를 정하는 게 Scheduler다. 사용자는 YAML에 “이 파드를 이 노드에 올려라”라고 직접 지정하지 않는다. 그냥 “이런 파드를 띄워라”라고만 선언한다. 실제 배치는 Scheduler가 한다.
Scheduler는 두 단계로 결정을 내린다.
- 필터링(Filtering): 이 파드를 띄울 수 없는 노드를 걸러낸다. 리소스가 부족하거나, 파드가 요구하는 taint/tolerations이 안 맞거나, nodeSelector 조건에 맞지 않는 노드는 후보에서 제외된다
- 스코어링(Scoring): 남은 후보 중 어느 노드가 최선인지 점수를 매긴다. 리소스가 균등하게 분산되도록, 같은 서비스의 파드가 한 노드에 몰리지 않도록 같은 규칙을 고려한다
결정이 나면 Scheduler는 API Server에 “이 파드는 이 노드에 배정한다”라고 업데이트한다. 그러면 해당 노드의 kubelet이 watch를 통해 이 변화를 감지하고, 실제 컨테이너를 실행한다. Scheduler 자체는 컨테이너를 띄우지 않는다. 배정만 한다.
Controller Manager — 원하는 상태로 수렴시키는 엔진
Controller Manager 안에는 수십 개의 컨트롤러가 돌아간다. 각 컨트롤러는 자기가 관심 있는 리소스에 대해 단순한 루프를 돈다.
while true {
desired := API Server에서 "이래야 한다"를 읽어옴
actual := 클러스터의 실제 상태를 확인
if desired != actual {
맞추기 위한 작업을 수행 (API Server에 변경 요청)
}
}
이걸 Reconciliation Loop(조정 루프)라고 부른다. Kubernetes의 심장과도 같은 패턴이다.
예를 들어 Deployment 컨트롤러는 “이 Deployment는 ReplicaSet 하나를 가져야 한다”를 보장하고, ReplicaSet 컨트롤러는 “이 ReplicaSet은 파드 N개를 가져야 한다”를 보장한다. 파드가 하나 죽으면, ReplicaSet 컨트롤러가 그걸 감지해서 “현재 3개인데 원래 5개여야 하네”라며 API Server에 “파드 2개를 더 만들어줘”라고 요청한다.
이 원리 덕분에 자가 치유가 동작한다. 사용자는 “5개 있어야 한다”고만 적어두면 되고, 실제로 맞추는 일은 컨트롤러가 한다.
Cloud Controller Manager — 클라우드 벤더 연동
AWS, GCP, Azure 같은 클라우드에서 Kubernetes를 돌릴 때 필요한 통합 지점이다. 예를 들어 Service 타입을 LoadBalancer로 만들면, Cloud Controller Manager가 AWS의 ELB를 실제로 프로비저닝해준다. 온프레미스 환경에서는 이 컴포넌트가 없거나 MetalLB 같은 대체제를 쓴다.
Worker Node: 실제로 일하는 서버
Control Plane이 지휘자라면, Worker Node는 연주자다. 진짜 애플리케이션 컨테이너는 여기서 돌아간다.
kubelet — 노드의 대리인
kubelet은 모든 Worker Node에 한 개씩 돌아가는 에이전트다. API Server와 소통하며 “내 노드에 배정된 파드가 뭐가 있는지” 감시하고, 그 파드들이 정말로 동작하고 있는지 관리한다.
kubelet이 하는 일을 정리하면 이렇다.
- API Server에 “나 살아 있다”라고 주기적으로 보고한다(노드 하트비트)
- 자신에게 배정된 파드를 컨테이너 런타임에 실행 요청한다
- 컨테이너가 죽으면 재시작한다
- 헬스 체크(liveness/readiness probe)를 수행하고 결과를 보고한다
- 로그, 메트릭, 이벤트를 수집해서 API Server에 올린다
중요한 건 kubelet은 도커를 직접 다루지 않는다는 점이다. CRI(Container Runtime Interface)라는 표준 인터페이스로 컨테이너 런타임과 이야기한다.
컨테이너 런타임 — 실제 컨테이너 실행자
컨테이너 이미지를 가져와서 실행하는 게 컨테이너 런타임의 일이다. 과거에는 Docker가 표준이었지만, Kubernetes 1.24부터는 Docker 지원이 제거되었고 지금은 대부분 containerd나 CRI-O를 쓴다.
containerd는 사실 Docker 내부에서도 쓰이던 컴포넌트다. Docker라는 거대한 도구에서 컨테이너 실행 엔진만 분리해 표준화한 것이다. kubelet이 CRI로 containerd에 “이 이미지를 가져와서 컨테이너를 띄워라”라고 요청하면, containerd가 이미지를 pull하고 runc로 실제 프로세스를 띄운다.
이 계층 구조를 그림으로 보면 이렇다.
flowchart TB
KUB[kubelet] -->|CRI| CTR[containerd / CRI-O]
CTR -->|OCI| RUNC[runc]
RUNC -->|시스템 콜| LINUX[Linux 커널<br/>cgroups / namespaces]
이해하면 장애 디버깅이 쉬워진다. 컨테이너가 안 뜨면 kubelet 로그, containerd 로그, runc 로그를 순서대로 확인하면 된다.
kube-proxy — 서비스 네트워크의 일꾼
5편에서 본격적으로 다루겠지만 간단히 언급하면, kube-proxy는 노드에서 Service 추상화를 구현하는 컴포넌트다. 사용자가 Service를 만들면 kube-proxy가 노드의 iptables 규칙(또는 IPVS)을 조정해서, 그 서비스 IP로 들어온 트래픽을 실제 파드 IP로 전달되도록 한다.
이 덕분에 파드가 죽고 새로 뜨면서 IP가 바뀌어도, Service IP는 안정적으로 유지된다. 파드를 직접 가리키지 않고 Service로 접근하는 이유가 여기 있다.
한 번의 배포가 일으키는 파장
이 모든 컴포넌트가 어떻게 맞물리는지, 배포 한 번을 따라가보자.
sequenceDiagram
participant U as 사용자
participant API as API Server
participant E as etcd
participant DC as Deployment Controller
participant RC as ReplicaSet Controller
participant S as Scheduler
participant K as kubelet
participant CR as containerd
U->>API: kubectl apply deployment.yaml
API->>E: Deployment 저장
API-->>U: 201 Created
DC->>API: Deployment watch
DC->>API: ReplicaSet 생성 요청
API->>E: ReplicaSet 저장
RC->>API: ReplicaSet watch
RC->>API: Pod N개 생성 요청
API->>E: Pod 저장 (nodeName 미지정)
S->>API: 미배정 Pod watch
S->>API: Pod에 nodeName 바인딩
K->>API: 자기 노드의 Pod watch
K->>CR: 컨테이너 실행 요청
CR-->>K: 실행 완료
K->>API: Pod 상태 Running 보고
API->>E: 상태 업데이트
이 흐름의 핵심은 누구도 남에게 직접 명령하지 않는다는 점이다. 모두가 API Server에만 이야기하고, 관심 있는 리소스의 변화를 watch한다. 이 분산된 협력 덕분에 한 컴포넌트가 잠깐 죽어도 전체 시스템이 멈추지 않는다.
고가용성 구성의 감
실제 운영 환경에서는 Control Plane 컴포넌트를 여러 대로 구성한다. etcd는 3대 또는 5대, API Server는 로드밸런서 뒤에 여러 인스턴스를 두는 식이다. Scheduler와 Controller Manager는 리더 선출(leader election)을 통해 한 인스턴스만 활성화된 상태로 돌아간다.
Worker Node도 보통 최소 세 대 이상으로 구성한다. 파드를 여러 노드에 분산시키면, 한 노드가 죽어도 다른 노드에 복제본이 있어서 서비스가 이어진다.
처음부터 이런 복잡한 구성을 이해할 필요는 없다. 다만 “단일 장애 지점을 없애려면 각 컴포넌트를 어떻게 분산시켜야 하는가”라는 관점이 있다는 것만 기억해두자.
다음 편에서는 파드 자체를 깊게 파본다. 왜 컨테이너가 아니라 파드가 배포 단위인지, 한 파드에 컨테이너 여러 개를 넣는 사이드카 패턴이 무엇인지 실습하며 익혀본다.
→ 3편: Pod




Loading comments...