Skip to content

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

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며

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

참고 자료

- [Kubernetes 공식 문서](https://kubernetes.io/docs/home/)

- [Kubernetes — 컴포넌트 개요](https://kubernetes.io/docs/concepts/overview/components/)

- [Kubernetes — kube-scheduler](https://kubernetes.io/docs/concepts/scheduling-eviction/kube-scheduler/)

- [Kubernetes — Controllers](https://kubernetes.io/docs/concepts/architecture/controller/)

- [Kubernetes — Service](https://kubernetes.io/docs/concepts/services-networking/service/)

- [Kubernetes — Gateway API](https://kubernetes.io/docs/concepts/services-networking/gateway/)

- [Kubernetes — Persistent Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/)

- [Kubernetes — Custom Resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/)

현재 단락 (1/241)

쿠버네티스(Kubernetes)는 컨테이너화된 애플리케이션의 배포·확장·운영을 자동화하는 오케스트레이션 플랫폼입니다. 처음 접하면 수많은 구성요소와 오브젝트에 압도되기 쉽지만, 핵...

작성 글자: 0원문 글자: 7,967작성 단락: 0/241