Nishant Sarawagi가 기고한 글을 공유드립니다.
Kubernetes가 “복잡하다”는 느낌은 대부분 요청이 Control Plane을 어떻게 통과해 워커 노드에서 실행되는지가 머릿속에 그려지지 않을 때 커집니다. 이 글에서는 kubectl 요청이 인증/인가 → Admission → etcd 저장 → 스케줄링 → kubelet 실행으로 이어지는 전체 흐름을 한 번에 정리합니다.
1) 큰 그림: Kubernetes는 “원하는 상태(Desired State)”를 계속 맞추는 시스템입니다
Kubernetes의 핵심은 사용자가 “이런 상태가 되었으면 좋겠다(Desired State)”라고 선언하면, 시스템이 그 상태를 맞추기 위해 계속 조정한다는 점입니다.
이 조정이 가능한 이유는 아래 두 축 덕분입니다.
- Control Plane: 요청을 받아 저장하고(상태), 배치하고(스케줄), 원하는 상태를 유지하도록 조정합니다(컨트롤러).
- Worker Node: 실제 컨테이너를 실행해 Pod를 “현실”로 만듭니다.
이제 kubectl apply -f deployment.yaml 같은 요청이 어떤 길을 지나가는지 순서대로 보겠습니다.
2) 요청 흐름(1): kubectl → API Server (클러스터의 관문)
사용자의 모든 Kubernetes 요청은 기본적으로 kube-apiserver(API Server) 로 들어갑니다. API Server는 단순한 HTTP 엔드포인트가 아니라, 아래 책임을 함께 집니다.
Authentication / Authorization (인증/인가)
- Authentication: “누구인지” 확인합니다. (예: client cert, OIDC, service account token 등)
- Authorization: “무엇을 할 수 있는지” 확인합니다. (예: RBAC)
이 단계가 운영 안정성에 직접 영향을 주는 이유는, 잘못된 권한이 곧 “잘못된 리소스 생성/변경”으로 이어지기 때문입니다.
Admission Controller (검증/변형) — 종종 빠뜨리기 쉬운 핵심
요약 자료의 “댓글 보완 관점”처럼, API Server는 etcd에 저장하기 전에 Admission 단계를 거칠 수 있습니다.
- Validating Admission: 생성/수정 요청을 허용/거절합니다.
- Mutating Admission: 스펙을 자동으로 변형합니다. (예: label 주입, sidecar 주입)
또한 Admission은 Webhook으로 확장 가능합니다(ValidatingWebhookConfiguration / MutatingWebhookConfiguration).
실무에서 “왜 이 리소스가 생성이 안 되지?”의 많은 경우가 여기서 막힙니다.
3) 요청 흐름(2): etcd에 상태 저장 (클러스터의 단일 진실 소스)
Admission까지 통과한 오브젝트는 etcd 에 저장됩니다. etcd는 Kubernetes의 “DB”에 가깝고, 다음이 중요합니다.
- “현재 클러스터 상태”와 “Desired State”가 저장되는 곳입니다.
- 컨트롤러들은 etcd의 상태 변화를 감시(Watch)하면서 동작합니다.
즉, kubectl 요청은 “컨테이너를 바로 실행”하는 게 아니라 상태를 기록하고, 그 상태를 기반으로 시스템이 실행을 만들어냅니다.
4) 요청 흐름(3): Scheduler가 Pod를 어느 노드에 둘지 결정합니다
Pod가 생성되면 처음에는 보통 어느 노드에도 배치되지 않은 상태입니다(nodeName이 비어 있음).
kube-scheduler가 다음 정보를 기반으로 “이 Pod를 어느 워커 노드에 둘지” 결정합니다.
- 리소스 여유(CPU/Memory)
- affinity/anti-affinity
- taint/toleration
- topology spread constraints 등
결정이 나면 Scheduler는 Pod 스펙에 “이 노드로 가라”는 식으로 반영하고(바인딩), 그 결과가 다시 etcd에 저장됩니다.
5) 요청 흐름(4): Kubelet + Container Runtime이 “실제 실행”을 담당합니다
이제부터는 워커 노드의 영역입니다.
- kubelet: 해당 노드에서 “내게 할당된 Pod”를 감시하고, 실행 상태를 계속 맞춥니다.
- container runtime(예: containerd, CRI-O): 실제 컨테이너 생성/실행을 담당합니다.
- (부가적으로) 이미지 pull, 볼륨 마운트, 프로브 실행(health check), 상태 보고 등이 kubelet을 통해 이뤄집니다.
Pod가 실행 중 크래시 나거나 프로브가 실패하면, kubelet은 재시작 같은 조치를 수행하며 “원하는 상태”로 되돌리려 합니다.
6) 요청 흐름(5): Controller Manager가 “자기치유(Self-healing)”를 완성합니다
kube-controller-manager는 다양한 컨트롤러 묶음입니다. 대표적으로:
- Deployment → ReplicaSet → Pod 수를 원하는 만큼 유지
- Node Controller → 노드 상태를 감시하고 문제가 생기면 조치
- Endpoint/Service 관련 컨트롤러 등
여기서 중요한 포인트는, Kubernetes의 복구는 “한 번 실행하고 끝”이 아니라 계속 감시하고 계속 수정하는 루프라는 점입니다.
7) 네트워킹: Kube Proxy는 서비스 디스커버리/트래픽 라우팅을 담당합니다
Pod가 뜬 뒤 “어떻게 트래픽이 그 Pod로 들어가나?”의 영역에서 kube-proxy가 중요한 역할을 합니다.
- Service(ClusterIP 등)를 기반으로 트래픽을 적절한 Pod로 라우팅
- iptables/ipvs 규칙 등을 통해 로드밸런싱 경로 구성(환경에 따라 다름)
정리하면, “Pod 실행”과 “Pod로의 트래픽 유입”은 다른 문제이고, kube-proxy는 후자(네트워크 경로)의 핵심 구성 요소입니다.
8) 운영 인사이트: “엄격한 Admission”보다 “런타임 장애”가 더 무섭다면?
대화에서 나온 포인트처럼, 운영에서 더 두려운 축이 배포 차단(정책으로 막힘) 이 아니라 런타임 장애라면 우선순위가 달라질 수 있습니다.
- Admission/RBAC을 극단적으로 엄격히 하면 “나쁜 배포”는 줄지만 개발자 마찰이 커질 수 있습니다.
- 반대로 런타임 장애를 줄이려면 “실행 중 흔들려도 버티는 설계”가 더 직접적인 효과를 내는 경우가 많습니다.
특히 “노드가 늦게 Ready가 된다” 같은 이슈는 단순히 API Server 단계의 문제가 아니라, 보통 다음 체인 중 병목에서 발생합니다.
- 노드 부팅/초기화(cloud-init 등)
- CNI 준비 지연, kubelet 등록 지연
- DaemonSet 준비(네트워크/스토리지 에이전트 등)
- 이미지 pull 지연 등
그래서 제가 현장에서 도움이 됐던 접근은 이렇습니다.
- 요청 흐름을 머릿속에 그린 뒤, 지연이 “어느 구간(Control Plane vs Node runtime)”인지 먼저 나눕니다.
- “늦게”를 수치로 정의합니다(예: 생성→Ready 평균/최악 몇 분).
- 이벤트/로그로 병목을 특정하고, 동시에 “늦어도 서비스가 버티는 장치”(여유 용량, 분산, PDB 등)를 같이 챙깁니다.
kubectl 요청은 API Server에서 인증/인가와 Admission을 거쳐 etcd에 저장되고, Scheduler가 배치한 뒤 kubelet과 container runtime이 실행을 현실로 만들며, Controller Manager가 원하는 상태를 계속 유지합니다. 이 흐름이 한 번 머릿속에 그려지면 Kubernetes 트러블슈팅도 “어디를 봐야 할지”가 훨씬 빨라집니다.
