Skip to content
Published on

쿠버네티스 아키텍처 시각화 — 컨트롤 플레인부터 파드까지

Authors

들어가며

쿠버네티스(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를 자극하고, 어떤 조정을 유발하는가"를 추적하는 습관을 들이면 클러스터의 동작이 한결 명료해집니다. 세부 동작과 기능은 버전과 배포판에 따라 달라질 수 있으니, 실제 운영에서는 공식 문서를 함께 참조하시길 권합니다.


참고 자료