쿠버네티스 — 컨테이너 오케스트레이션의 사실상 표준
컨테이너 기술은 로컬 개발 환경에 그야말로 거대한 혁신을 가져왔습니다. “내 컴퓨터에서는 잘 도는데 서버에서는 왜 안 돌지?”라는 오랜 고질병이 사라졌고, 개발자들은 누구나 자신의 PC에서 복잡한 애플리케이션 스택을 손쉽게 띄우고 테스트할 수 있게 되었습니다.
하지만 로컬에서 잘 돌아가는 컨테이너를 실제 고객에게 서비스(Production)하는 순간, 차원이 다른 새로운 과제들 이 쏟아져 나옵니다.
단 하나의 서버에서 컨테이너를 돌리다 서버가 죽으면 서비스 전체가 중단됩니다. 따라서 실제 운영 환경에서는 컨테이너를 여러 대의 서버에 적절히 분산해서 돌려야 하고, 언제든 장애가 발생해도 서비스가 유지되는 고가용성(High Availability)을 확보해야 하며, 트래픽 변화 속에서도 안정적으로 무중단 구동 될 수 있어야 합니다.
- “새로 배포할 컨테이너 10개는 여러 서버 중 어느 머신에 띄워야 자원이 가장 균형 있게 쓰일까?”
- “새벽에 특정 서버 하드웨어가 고장 나서 꺼지면, 거기서 돌던 컨테이너들을 누가 다른 정상 서버로 즉시 옮겨 살려낼까?”
- “사용자 트래픽이 몰릴 때 컨테이너를 동적으로 늘리고, 들어오는 요청을 여러 서버의 컨테이너들로 어떻게 고르게 분산(Load Balancing)해 줄까?”
- “새 버전을 배포할 때 사용자 요청이 끊기지 않도록 구버전과 신버전을 어떻게 무중단으로 교체할 수 있을까?”
개발자가 수동으로 각 서버에 SSH로 접속해 도커 명령어를 치거나 셸 스크립트를 짜는 방식으로는 수십, 수백 대의 서버를 결코 감당할 수 없습니다.
이처럼 여러 대의 서버에 걸쳐 수많은 컨테이너의 분산 배치, 가용성 보장, 헬스체크, 로드밸런싱, 롤링 업데이트를 자동으로 조율(지휘)해 주는 시스템 이 필수적이 되었습니다. 이것이 바로 컨테이너 오케스트레이션(Container Orchestration) 의 탄생 배경이며, 오늘날 그 세계를 완벽하게 평정한 사실상의 표준이 바로 쿠버네티스(Kubernetes, K8s) 입니다.
1. 명령형(Imperative)에서 선언형(Declarative)으로의 패러다임 전환
쿠버네티스를 처음 접할 때 가장 낯설면서도 강력한 개념이 바로 선언적(Declarative) 모델 입니다.
기존의 전통적인 인프라 자동화(Shell 스크립트, Ansible 등)는 대부분 명령형(Imperative) 이었습니다:
“1번 서버에 접속해라. 도커 컨테이너를 3개 띄워라. 만약 실패하면 다시 시도해라.”
명령형 방식은 절차 중 하나라도 꼬이면(예: 네트워크 단절, 타임아웃) 현재 시스템이 어떤 상태에 놓여 있는지 추적하기가 극도로 어렵습니다.
쿠버네티스는 발상을 180도 바꿨습니다. “어떻게(How)“를 명령하지 않고, “원하는 최종 상태(What / Desired State)“만 선언 합니다:
# nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3 # 내가 원하는 상태: 3개의 nginx가 항상 살아있어야 함
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
우리는 그저 kubectl apply -f nginx-deployment.yaml로 선언문(매니페스트)을 던져둘 뿐입니다. 이후의 모든 것은 쿠버네티스의 몫입니다.
2. 자가 치유의 핵심 엔진: 지속적인 Reconcile Loop
쿠버네티스 내부의 모든 컨트롤러는 쉬지 않고 하나의 루프를 돕니다. 이를 조화 루프(Reconciliation Loop) 또는 제어 루프(Control Loop)라고 부릅니다.
조화 루프는 단순한 3단계로 끝없이 회전합니다:
- 관찰 (Observe): 현재 클러스터의 실제 상태(Current State)를 지속적으로 감시합니다.
- 비교 (Analyze): 엔지니어가 선언한 원하는 목표 상태(Desired State)와 실제 상태의 차이(Diff)를 비교합니다.
- 조치 (Act): 차이가 발견되면 이를 일치시키기 위한 생성/삭제/수정 작업을 실행합니다.
만약 3개의 파드(Pod) 중 하나가 돌아가던 노드가 물리적으로 고장 나서 꺼지면:
- 관찰: “어? 현재 살아있는 파드가 2개뿐이네?”
- 비교: “선언된 목표치는 3개인데 실제는 2개네. (차이: -1)”
- 조치: “가장 여유 있는 정상 노드를 골라 즉시 파드 1개를 새로 생성하자.”
이 단순하면서도 강력한 피드백 루프 덕분에 쿠버네티스는 시스템 관리자가 개입하지 않아도 스스로 상태를 복구하는 자가 치유(Self-healing) 능력을 갖추게 됩니다.
flowchart TD
OBS["<b>1. 관찰 (Observe)</b><br/>클러스터의 현재 실제 상태(Current State) 수집"]
DIFF["<b>2. 비교 (Analyze & Diff)</b><br/>선언된 목표 상태(Desired)와 실제 상태의 차이 계산"]
ACT["<b>3. 조치 (Act)</b><br/>차이를 해소하기 위한 Pod 생성·교체·스케일링 실행"]
OBS --> DIFF --> ACT -->|"지속적인 피드백 루프 반복"| OBS
3. 쿠버네티스 아키텍처 해부
쿠버네티스 클러스터는 크게 두 영역으로 나뉩니다: 전체를 지휘하는 컨트롤 플레인(Control Plane) 과 실제 컨테이너가 실행되는 워커 노드(Worker Node) 입니다.
flowchart TD
subgraph CP["<b>Control Plane (마스터 노드)</b>"]
ETCD[("<b>etcd</b><br/>상태 분산 DB")]
API["<b>kube-apiserver</b><br/>중앙 API 관문"]
SCHED["<b>kube-scheduler</b><br/>노드 스케줄러"]
CM["<b>controller-manager</b><br/>자가 치유 제어기"]
ETCD <-->|"상태 저장·조회"| API
API <-->|"배정 대기 파드"| SCHED
API <-->|"상태 감시·조정"| CM
end
subgraph WN["<b>Worker Node (작업 노드)</b>"]
KUBELET["<b>kubelet</b><br/>노드 관리 에이전트"]
CRI["<b>containerd</b><br/>컨테이너 런타임"]
PROXY["<b>kube-proxy</b><br/>네트워크 프록시"]
POD["<b>Pod (컨테이너 그룹)</b><br/>실제 비즈니스 워크로드"]
KUBELET -->|"CRI 명령"| CRI -->|"runc 구동"| POD
KUBELET -.-> PROXY -.->|"가상 IP 라우팅"| POD
end
CP ==>|"보안 mTLS 통신 (지시 및 상태 보고)"| WN
A. 컨트롤 플레인 (Control Plane)
- kube-apiserver: 클러스터의 모든 요청이 통과하는 유일한 관문입니다. 인증, 인가, 스키마 검증을 거쳐 모든 상태 조회를 처리하며, 오직 apiserver만이 etcd와 직접 대화합니다.
- etcd: Raft 합의 알고리즘 기반의 고가용성 분산 Key-Value 저장소입니다. 클러스터의 모든 상태와 매니페스트가 저장되는 ‘단 하나의 진실의 원천(Single Source of Truth)‘입니다.
- kube-scheduler: 아직 노드가 정해지지 않은 새로운 파드가 생기면, 각 노드의 자원 잔여량, 하드웨어 라벨, 어피니티(Affinity), 톨러레이션(Toleration) 등을 종합 평가하여 최적의 노드를 점찍어 배정합니다.
- kube-controller-manager: Node Controller, Deployment Controller 등 수많은 Reconcile Loop 컨트롤러들을 단일 프로세스로 묶어 실행하는 관리자입니다.
B. 워커 노드 (Worker Node)
- kubelet: 각 노드에 상주하는 핵심 에이전트입니다. apiserver를 주기적으로 감시하다가 “너한테 배정된 파드가 있다”는 지시가 내려오면, CRI(Container Runtime Interface)를 통해
containerd에게 컨테이너를 띄우라고 지시하고 헬스체크 결과를 보고합니다. - kube-proxy: 각 노드에서 실행되는 네트워크 프록시로, 가상 IP인
Service로 들어오는 트래픽을 실제 Pod의 IP로 전달하는 라우팅 규칙(iptables 또는 IPVS)을 호스트 커널에 작성합니다. - 컨테이너 런타임 (containerd 등): OCI(Open Container Initiative) 표준에 따라 실제 컨테이너를 호스트 커널 격리 공간(cgroups, namespaces)에 생성하고 구동하는 저수준 엔진입니다.
4. 핵심 추상화 객체: Pod, ReplicaSet, Deployment, Service
도커는 컨테이너 하나를 직접 다루지만, 쿠버네티스는 컨테이너를 직접 제어하지 않고 여러 단계의 추상화 계층으로 감싸서 관리합니다.
① 파드 (Pod) — 배포의 최소 단위
쿠버네티스에서 가장 작은 배포 단위는 컨테이너가 아니라 파드(Pod) 입니다.
- 파드는 하나 이상의 컨테이너가 네트워크 네임스페이스(IP, 포트)와 스토리지 볼륨을 공유하는 논리적 묶음입니다.
- 하나의 파드 안에 있는 컨테이너들은
localhost로 서로 즉각 통신할 수 있습니다. - 보통 주 애플리케이션 컨테이너 1개와 이를 보조하는 사이드카(Sidecar, 예: 로그 수집기, Envoy 프록시) 컨테이너를 한 파드로 묶는 데 널리 활용됩니다.
② 디플로이먼트 (Deployment) — 무중단 롤링 배포의 핵심
파드는 언제든 죽을 수 있는 임시 자원(Ephemeral)입니다. 파드가 죽으면 스스로 살아나지 못합니다.
- 이를 감시하고 항상 지정된 개수(
replicas)를 유지해 주는 것이ReplicaSet입니다. - 그리고 버전을 v1에서 v2로 업데이트할 때, 구버전 ReplicaSet의 파드를 하나 줄이고 신버전 ReplicaSet의 파드를 하나 늘리는 방식으로 무중단 롤링 업데이트를 오케스트레이션해 주는 고수준 객체가 바로 Deployment 입니다.
③ 서비스 (Service) — 동적 IP의 파편화를 극복하는 가상 IP
파드는 새로 뜰 때마다 IP가 계속 바뀝니다. 클라이언트는 어떤 IP로 요청을 보내야 할까요?
Service는 파드들의 집합에 고정된 단일 가상 IP(ClusterIP) 와 DNS 이름을 부여합니다.- 파드가 죽고 새 IP로 살아나도, 서비스의 라벨 셀렉터(
selector: app=nginx)가 알아서 새 파드를 엔드포인트에 등록하므로 클라이언트는 IP 변화를 알 필요가 없습니다.
flowchart TD
CLIENT["<b>외부 클라이언트 요청</b>"]
SVC["<b>Service (ClusterIP)</b><br/>고정 가상 IP & 로드밸런서 (kube-proxy)"]
DEP["<b>Deployment</b><br/>무중단 롤링 업데이트 & 버전 관리"]
RS["<b>ReplicaSet</b><br/>선언된 파드 개수(replicas) 상시 보장"]
POD["<b>Pod (컨테이너 그룹)</b><br/>실제 애플리케이션 실행 (동적 IP)"]
CLIENT --> SVC
SVC -.->|"라벨 셀렉터로 트래픽 분산"| POD
DEP -->|"신규 버전 롤아웃"| RS
RS -->|"Desired 상태 복제본 유지"| POD
5. 왜 쿠버네티스는 이토록 복잡하게 설계되었는가?
쿠버네티스를 배우는 엔지니어들이 가장 많이 토로하는 불만은 “러닝 커브가 너무 높고 설정할 야믈(YAML)이 너무 많다”는 것입니다. 단순한 웹 앱 하나를 띄우려 해도 Pod, Deployment, Service, Ingress, ConfigMap, Secret 등 수많은 객체를 알아야 합니다.
하지만 이 복잡성은 쓸모없는 과설계가 아닙니다. 분산 시스템에서 발생할 수 있는 거의 모든 예외 상황(네트워크 단절, 부분 장애, 자원 고갈, 무중단 배포, 멀티 테넌시)을 사람이 개입하지 않고 시스템이 스스로 극복할 수 있도록 설계했기 때문 입니다.
또한 쿠버네티스는 고정된 완성품이 아니라 하나의 ‘플랫폼을 만들기 위한 플랫폼(Platform for Platforms)‘ 입니다. CRD(Custom Resource Definition) 와 Operator 패턴 을 통해, 사용자는 쿠버네티스 API를 확장하여 카프카(Kafka) 클러스터, 데이터베이스 복제 구성, 머신러닝 파이프라인(Kubeflow)까지 쿠버네티스의 선언적 루프 안으로 끌어들일 수 있습니다.
6. 정리
- 쿠버네티스는 컨테이너를 직접 띄우는 도구가 아니라, 수천 대의 서버에 흩어진 수만 개의 컨테이너를 지휘하는 분산 오케스트레이터 입니다.
- 명령 대신 ‘의도한 최종 상태(Desired State)‘ 를 선언하고, 시스템이 끈질기게 Reconcile Loop 를 돌며 현실을 그 의도에 맞춥니다.
- Pod, ReplicaSet, Deployment, Service라는 계층화된 추상화를 통해 무중단 배포와 고가용성을 보장합니다.
쿠버네티스의 복잡성은 분산 환경의 가혹한 현실을 자가 치유와 선언적 자동화로 해결하기 위한 필연적인 엔지니어링 결과물입니다. 이 철학을 이해하면 단순한 컨테이너 실행을 넘어 대규모 클라우드 네이티브 아키텍처를 안정적으로 설계할 수 있습니다.