2.1 전체 구조: control plane과 노드
Kubernetes 클러스터는 두 부류의 머신으로 나뉜다. 클러스터 전체의 상태를 관리하고 결정을 내리는 control plane, 그리고 그 결정에 따라 컨테이너를 실제로 실행하는 노드다. control plane이 “이 Pod는 저 노드에서 실행되어야 한다"고 정하면, 노드에 상주하는 에이전트가 그 결정을 읽어 컨테이너를 기동한다. 결정과 실행이 분리되어 있으므로 노드 몇 대가 장애를 겪어도 결정 기능은 유지되고, 반대로 control plane이 잠시 응답하지 못해도 이미 실행 중이던 컨테이너는 계속 동작한다.
구성 요소 지도
control plane에는 kube-apiserver, etcd, kube-scheduler, kube-controller-manager 네 개의 상주 프로세스가 있고, 각 노드에는 kubelet, kube-proxy, 컨테이너 런타임 세 개가 있다. 클라우드 환경이라면 control plane에 cloud-controller-manager가 하나 더 추가된다.
flowchart TD
subgraph CP["control plane"]
API["kube-apiserver"]
ETCD[("etcd")]
SCHED["kube-scheduler"]
KCM["kube-controller-manager"]
end
subgraph NODE["노드 (여러 대)"]
KL["kubelet"]
KP["kube-proxy"]
CRT["컨테이너 런타임"]
end
SCHED -->|"watch·기록"| API
KCM -->|"watch·기록"| API
API -->|"읽기·쓰기"| ETCD
KL -->|"watch·보고"| API
KP -->|"watch"| API
KL -->|"CRI"| CRT
| 컴포넌트 | 위치 | 역할 |
|---|---|---|
| kube-apiserver | control plane | 모든 API 요청의 관문. 인증·인가·검증 후 etcd에 저장 |
| etcd | control plane | 클러스터 상태 전체를 저장하는 key-value 저장소 |
| kube-scheduler | control plane | 노드가 정해지지 않은 Pod에 노드를 배정 |
| kube-controller-manager | control plane | 원하는 상태와 현재 상태를 맞추는 컨트롤러 모음 |
| cloud-controller-manager | control plane | 클라우드 제공자 API 연동 (로드밸런서, 노드 수명주기) |
| kubelet | 노드 | 자기 노드에 배정된 Pod를 컨테이너로 실행하고 상태 보고 |
| kube-proxy | 노드 | Service 트래픽을 실제 Pod로 전달하는 네트워크 규칙 관리 |
| 컨테이너 런타임 | 노드 | 컨테이너의 실제 생성과 실행 (containerd, CRI-O 등) |
각 컴포넌트는 이후 챕터에서 하나씩 자세히 다루므로, 여기서는 도식의 화살표 방향에 집중한다. etcd로 향하는 화살표는 apiserver에서 나오는 하나뿐이고, 나머지 컴포넌트의 화살표는 전부 apiserver를 향한다.
컴포넌트는 서로 대화하지 않는다
Kubernetes 컴포넌트 사이에는 직접 호출이 없다. 스케줄러가 kubelet에게 “이 Pod를 실행하라"고 지시하는 RPC는 존재하지 않는다. 스케줄러는 배정 결과를 apiserver에 기록할 뿐이고, kubelet은 apiserver를 감시하다가 자기 노드에 배정된 Pod를 발견해 실행한다. 모든 컴포넌트가 apiserver라는 허브에 바큇살처럼 연결된 hub-and-spoke 구조다.
kubectl run nginx --image=nginx를 실행했을 때의 흐름을 따라가면 이 구조가 드러난다.
- kubectl이 apiserver에 Pod 생성 요청을 보내고, apiserver가 검증을 거쳐 etcd에 저장한다.
- 스케줄러가 watch로 노드가 정해지지 않은 새 Pod를 감지하고, 배정할 노드를 선택해 결과를 apiserver에 기록한다.
- 해당 노드의 kubelet이 watch로 자기 몫의 배정을 감지하고, 컨테이너 런타임을 통해 컨테이너를 시작한다.
- kubelet이 실행 결과를 Pod의 상태로 apiserver에 보고한다.
네 단계 어디에도 컴포넌트 사이의 직접 통신이 없다. 모든 정보가 apiserver에 저장된 객체를 매개로 오간다.
이 구조에서 얻는 것이 셋 있다. 첫째, 인증과 인가와 감사 로그를 apiserver 한 지점에서 처리한다. 컴포넌트마다 통신 상대를 따로 검증할 필요가 없다. 둘째, 컴포넌트가 일시적으로 죽어도 지시가 유실되지 않는다. 지시가 이벤트로 전달되는 것이 아니라 상태로 저장되어 있으므로, 재시작한 컴포넌트가 apiserver를 다시 읽으면 그동안 쌓인 차이를 마저 처리한다. 셋째, 확장이 쉽다. 커스텀 컨트롤러를 추가할 때 기존 컴포넌트를 고칠 필요 없이 같은 apiserver를 감시하면 된다.
HA control plane 토폴로지
control plane을 머신 한 대에 두면 그 머신이 곧 단일 장애점이다. 프로덕션에서는 보통 control plane 노드를 3대 이상 두고, 그 앞의 로드밸런서가 apiserver 요청을 분산한다. apiserver는 상태를 갖지 않으므로 수를 늘리는 데 제약이 없다. 상태를 가진 쪽은 etcd이고, etcd를 어디에 두는가에 따라 토폴로지가 둘로 갈린다.
stacked etcd는 etcd 멤버를 각 control plane 노드 안에 함께 배치하는 방식이다. kubeadm의 기본 구성이며, 필요한 머신이 적고 관리가 단순하다. 대신 control plane 노드 하나를 잃으면 apiserver와 etcd 멤버를 동시에 잃는다. 3대 구성이라면 한 대 상실까지는 etcd 과반(2/3)이 유지되므로 클러스터는 계속 동작한다.
external etcd는 etcd를 별도 머신의 독립 클러스터로 분리하는 방식이다. control plane 노드 장애와 etcd 멤버 장애가 서로 결합되지 않는 대신, 최소 머신 수가 두 배 가까이 늘어난다(control plane 3대 + etcd 3대).
| 항목 | stacked etcd | external etcd |
|---|---|---|
| etcd 위치 | control plane 노드 내부 | 별도 전용 머신 |
| 최소 머신 수 (HA) | 3 | 6 |
| 노드 장애의 영향 | apiserver·etcd 동시 상실 | 서로 분리 |
대부분의 환경에서 출발점은 stacked 구성이다. 과반이 유지되는 한 멤버 하나의 상실은 클러스터 동작에 영향을 주지 않기 때문이다. etcd 과반 규칙과 홀수 멤버 구성의 이유는 2.3에서 자세히 다룬다. 한편 EKS, GKE 같은 관리형 서비스에서는 control plane 전체가 제공자의 관리 영역이라 이 토폴로지 선택 자체가 사용자에게 드러나지 않는다.
정리
- 클러스터는 결정하는 control plane(apiserver, etcd, 스케줄러, 컨트롤러 매니저)과 실행하는 노드(kubelet, kube-proxy, 런타임)로 나뉜다.
- 컴포넌트 사이에 직접 호출은 없다. 모두 apiserver에 저장된 상태를 매개로 협력하고, etcd에 접근하는 것도 apiserver 하나뿐이다.
- HA 구성은 etcd 배치에 따라 stacked와 external로 갈리며, 어느 쪽이든 apiserver는 로드밸런서 뒤에 여러 대를 둔다.