Docker에서 Kubernetes로 — 무엇이 달라지는가
docker-compose.yml 한 파일로 충분했던 세계에서, 왜 Deployment·Service·PVC·ConfigMap이 필요해지는가. 개념 매핑부터 마이그레이션 트레이드오프까지 추적한다.
- 01 컨테이너는 어떻게 격리되면서도 빠른가
- 02 Docker 이미지 크기가 10배 차이 나는 이유
- 03 Docker 네트워크는 어떻게 패킷을 옮기는가
- 04 Docker 스토리지는 어디서 끝나고 어디서 시작되는가
- 05 Docker Compose에서 Swarm까지, 어떻게 설계가 이어지는가
- 06 Docker 보안은 왜 여러 계층이 필요한가
- 07 Docker 컨테이너 리소스 관리의 철학
- 08 Docker는 어떻게 컨테이너를 만드는가
- 09 컨테이너 패턴의 통일된 철학은 무엇인가
- 10 코드 한 줄이 프로덕션에 닿기까지
- 11 컨테이너 디버깅은 왜 이렇게 어려운가
- 12 Docker로 Full-stack 서비스를 운영한다는 것은 무엇인가
- 13 Docker에서 Kubernetes로 — 무엇이 달라지는가
docker-compose up -d 한 줄로 전체 스택이 올라오는 세계에서, Kubernetes는 같은 일을 하기 위해 Deployment·Service·PVC·ConfigMap·Secret을 따로 정의하도록 요구한다. 왜 이렇게 복잡해졌는가? 그리고 그 복잡함은 무엇을 얻기 위한 대가인가?
단일 호스트에서 클러스터로
Docker는 한 대의 서버 위에서 컨테이너를 실행한다. 스케일링이 필요하면 더 큰 서버로 올리거나, 수동으로 여러 서버를 구성해야 한다. 장애가 나면 사람이 재시작한다. 로드 밸런싱은 별도 구성이다.
Kubernetes는 이 세 가지를 클러스터 수준의 자동화로 해결한다.
Docker (단일 호스트): Kubernetes (클러스터):
┌────────────────────┐ ┌───────────────────────────────┐
│ Container1 │ │ Control Plane │
│ Container2 │ │ ┌─────────────────────────┐ │
│ Container3 │ │ │ API Server / Scheduler │ │
│ Docker Engine │ │ └────────────┬────────────┘ │
└────────────────────┘ │ ↓ │
│ Worker1 Worker2 Worker3 │
│ [Pod][Pod] [Pod][Pod] [Pod] │
└───────────────────────────────┘
Pod가 죽으면 Kubernetes가 다시 만든다. 트래픽이 늘면 HPA가 Pod를 추가한다. 노드가 죽으면 Scheduler가 다른 노드에 Pod를 재배치한다. Docker에서는 이 모두를 운영자가 직접 했다.
개념 매핑 — Docker의 어휘가 K8s에서 어떻게 쪼개지는가
Docker Compose의 service 하나는 Kubernetes에서 최소 두 개의 리소스로 분리된다.
docker-compose service → Deployment (실행 방법)
+ Service (네트워크 접근)
Volume → PersistentVolumeClaim
Environment Variables → ConfigMap (공개) + Secret (민감)
Networks → Service (ClusterIP / LoadBalancer)
healthcheck → livenessProbe + readinessProbe
이 분리는 단순한 명명 규칙 변화가 아니다. Docker에서 ports: "8080:80"은 “이 컨테이너의 80 포트를 호스트 8080에 바인딩하라”는 명령이다. Kubernetes의 Service는 “이 레이블을 가진 Pod들에게 트래픽을 분산하라”는 선언이다. 컨테이너가 어느 노드에 있든 상관없이.
# docker-compose.yml — 명령적
services:
web:
image: nginx
ports:
- "8080:80"
# kubernetes — 선언적
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
type: LoadBalancer
ports:
- port: 80
targetPort: 80
Pod — Kubernetes의 최소 단위가 컨테이너가 아닌 이유
Docker에서 배포 단위는 컨테이너다. Kubernetes에서 배포 단위는 Pod다. Pod는 하나 이상의 컨테이너를 묶어 같은 네트워크 네임스페이스와 볼륨을 공유하게 한다.
이 설계는 Sidecar 패턴을 자연스럽게 만든다. 애플리케이션 컨테이너 옆에 로그 수집기나 메트릭 익스포터를 붙이면, 두 컨테이너는 localhost로 통신하고 같은 파일 시스템을 본다.
spec:
volumes:
- name: shared-logs
emptyDir: {}
containers:
- name: web
image: nginx
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
- name: log-collector
image: busybox
command: ["tail", "-f", "/logs/access.log"]
volumeMounts:
- name: shared-logs
mountPath: /logs
Init Container는 이 패턴의 변형이다. Main 컨테이너 전에 순차적으로 실행되고 완료 후 종료된다. “DB가 준비될 때까지 대기”, “마이그레이션 실행”, “설정 파일 다운로드” 같은 초기화 작업에 쓰인다.
Deployment vs StatefulSet — 상태가 있는가 없는가
Pod를 직접 생성하지 않는다. Deployment나 StatefulSet이 Pod를 관리한다.
Deployment는 Stateless 워크로드를 위한 컨트롤러다. 세 개의 Pod는 서로 교체 가능하다. 어느 Pod가 죽어도 새 Pod가 같은 역할을 이어받는다. 이름은 랜덤 해시로 붙는다.
StatefulSet은 Stateful 워크로드를 위한 컨트롤러다. 각 Pod는 고유한 ID(postgres-0, postgres-1, postgres-2)를 가지고, 각자 독립적인 PVC를 할당받는다. 스케일 업은 순서대로(0 → 1 → 2), 스케일 다운은 역순으로(2 → 1)일어난다.
Deployment: StatefulSet:
[Pod-abc] [Pod-def] [Pod-ghi] [postgres-0] [postgres-1] [postgres-2]
↓ ↓ ↓ ↓
공유 PVC 또는 없음 PVC-0 PVC-1 PVC-2
데이터베이스에 StatefulSet을 쓰는 것이 옳지만, 실무에서는 RDS·Cloud SQL 같은 매니지드 서비스에 위임하고 K8s에서는 Stateless 워크로드만 운영하는 패턴이 더 흔하다.
Kubernetes는 복잡도를 자동화로 교환한다. docker-compose up 한 줄이 kubectl apply -f k8s/ 에 해당하지만, 그 뒤에는 자동 복구·롤링 업데이트·HPA가 붙어온다. 반대로 소규모 팀·단순 앱·낮은 트래픽 환경에서 K8s의 운영 오버헤드는 이득보다 크다. “클러스터가 필요한 문제가 있는가”가 먼저다.
마이그레이션 — 변환이 아니라 아키텍처 전환
docker-compose.yml을 K8s YAML로 바꾸는 것은 기계적 작업이다. Kompose 같은 도구가 자동으로 변환해주기도 한다. 그러나 진짜 마이그레이션은 다음 세 가지를 요구한다.
첫째, 환경변수 분리. docker-compose의 environment 블록을 그대로 Deployment에 옮기는 것은 작동하지만 잘못된 패턴이다. 공개 설정은 ConfigMap으로, 패스워드·API 키는 Secret으로 분리한다.
둘째, Health Probe 추가. Docker의 healthcheck는 단일 상태를 확인한다. Kubernetes는 세 가지를 구분한다 — startupProbe(시작 완료 여부), livenessProbe(재시작 필요 여부), readinessProbe(트래픽 수신 가능 여부). 셋 다 설정해야 롤링 업데이트가 안전하게 동작한다.
셋째, 전환 전략 선택. 전면 전환(Big Bang)은 빠르지만 위험하다. 서비스 단위로 하나씩 옮기는 점진적 전환(Strangler Fig)은 느리지만 롤백이 쉽다. Canary 배포는 새 버전에 10%의 트래픽만 먼저 보내며 검증한다.
정리
- Docker의
service하나는 K8s에서 Deployment + Service로 분리된다. 분리는 복잡함이 아니라 클러스터 수준 자동화를 위한 구조다. - Pod는 컨테이너의 묶음이다. Sidecar·Init Container 패턴은 이 구조에서 자연스럽게 나온다.
- Stateless → Deployment, Stateful → StatefulSet. 데이터베이스는 가능하면 매니지드 서비스에 위임하라.
- 마이그레이션은 YAML 변환이 아니다. ConfigMap/Secret 분리, Health Probe 추가, 전환 전략 선택이 실질적인 작업이다.
다음 글에서는 K8s 네트워킹의 핵심 — Service·Ingress·DNS가 클러스터 안팎의 트래픽을 어떻게 라우팅하는지 추적한다.