목차
32 장

docker-compose에서 k8s로

부록 A — Docker / docker-compose까지 와 본 독자가 K8s로 옮겨갈 때 막히는 일곱 가지 차이를 한 곳에 정리합니다. docker-compose.yml의 각 키가 K8s의 어떤 리소스에 대응되는지 매핑 표로 정리하고, 작은 web + db의 docker-compose.yml을 K8s 매니페스트로 옮기는 마이그레이션 한 사이클을 풀어내고, kompose 자동 변환 도구의 한계와 그 다음 단계를 짚습니다. 책의 마지막 챕터이지만, Docker까지 와 본 독자에게는 출발점이 됩니다.

부록 A입니다. 본 책을 1~31장 따라온 독자에게는 부록이지만, Docker / docker-compose까지 와 보고 본 책을 처음 펼친 독자에게는 출발점입니다. 1장 쿠버네티스란 무엇인가의 끝에서 “본문이 진행되면서 자주 다시 펼치게 될 것"으로 안내한 그 매핑 표가 이 부록의 중심입니다.

이 부록의 목표는 두 가지입니다.

  • docker-compose.yml의 각 키가 K8s의 어떤 리소스에 대응되는가 — 한 표로 큰 그림.
  • 그 매핑이 단순 1 : 1이 아닌 일곱 가지 결 — 같은 의도의 다른 모양들.

이 둘이 손에 들어오면 K8s 본문 1~6장이 자연스럽게 익숙해집니다.

멘탈 모델의 차이 — 한 호스트, 한 네트워크 vs 다중 노드, 다중 컨트롤러 #

docker-compose up 한 명령에서 일어나는 일을 한 줄로 정리하면 다음과 같습니다.

docker-compose의 멘탈 모델
한 호스트 위에:
- 한 네트워크가 만들어지고 (compose-default)
- 그 안에 컨테이너 N 개가 떠 있고
- 같은 네트워크의 서비스끼리는 service name 으로 호출 가능
- 호스트의 디스크에 데이터를 마운트
- restart: always 가 다운 시 자동 재시작

K8s는 이 모델을 여러 노드, 여러 컨트롤러, 여러 네트워크 계층으로 분해한 결입니다. 같은 한 줄을 K8s로 옮기면 다음과 같습니다.

K8s의 멘탈 모델
여러 노드 위에:
- 여러 네임스페이스 (격리 단위)
- 그 안의 Pod 들이 여러 노드에 흩어져 있음
- Service 가 Pod 의 가상 IP + DNS 이름을 제공
- PersistentVolume 이 노드와 분리된 영속 디스크
- Deployment 컨트롤러가 다운 시 자동 재시작 + 롤링 업데이트
- 컨트롤 플레인 (API Server, scheduler, controller-manager, etcd) 이 위 모든 것을 조율

한 호스트의 단순함 vs 여러 노드의 분산의 차이가 멘탈 모델의 핵심입니다. compose에서는 모든 컨테이너가 같은 호스트의 stdout으로 로그를 떨어뜨리고 같은 네트워크에 있지만, K8s에서는 Pod가 어느 노드에 떠 있는지가 매니페스트 결정에 따라 다르고, 로그도 노드별로 분산됩니다.

이 차이가 모든 일곱 가지 결의 출발점입니다.

리소스 매핑 — 큰 그림 #

docker-compose.yml의 자주 쓰는 키들이 K8s의 어느 리소스로 옮겨지는지를 한 표로 정리합니다.

docker-composeKubernetes본 책 챕터
servicesDeployment + Service4장, 5장
imagePod spec의 containers[].image3장
command / entrypointPod spec의 command / args3장
ports: "8080:8080"Service (ClusterIP / NodePort / LoadBalancer) + Ingress5장, 10장
volumes (named)PersistentVolumeClaim + StorageClass9장
volumes (bind mount)hostPath (개발용) 또는 ConfigMap (설정 파일)9장, 6장
environment / env_fileConfigMap + Secret + envFrom6장
restart: alwaysDeployment의 기본 (ReplicaSet)4장
depends_oninitContainer / Job / Helm hook (정확한 1 : 1 없음)8장, 23장
healthcheckreadinessProbe + livenessProbe + startupProbe12장
networksNetworkPolicy + Service DNS + CNI5장, 14장, 15장
deploy.replicasDeployment의 replicas4장
deploy.resourcesPod spec의 resources.requests/limits11장
deploy.update_configDeployment의 strategy.rollingUpdate4장
deploy.placement.constraintsnodeSelector / affinity / taints22장
secrets: (Compose)Secret + (sealed-secrets / external-secrets)6장, 29장
configs: (Compose)ConfigMap (volumeMount 또는 envFrom)6장

표만 봐도 이미 보이는 결이 있습니다. services 한 줄이 K8s에서는 두 객체 (Deployment + Service)로 갈라집니다. 워크로드 자체 (Deployment)와 그 워크로드의 진입점 (Service)이 분리된 책임이라는 게 K8s의 결입니다. compose의 services가 두 일을 한 개념으로 묶었던 데 비해, K8s는 두 객체로 나누어 각각의 라이프사이클을 독립적으로 운영합니다.

또 한 가지 — depends_on의 정확한 K8s 대응은 없습니다. 가장 가까운 패턴이 initContainer (Pod 시작 전 사전 작업) 이지만, “db가 다른 서비스로 떠올라 있어야 web이 시작” 같은 의미는 K8s의 비동기 모델과 결이 다릅니다. K8s는 두 워크로드의 시작 순서를 강제하지 않고, readiness와 retry로 그 결을 해결합니다 — 자세한 결은 §“흔히 막히는 일곱 가지"의 다섯 번째에서 다룹니다.

흔히 막히는 일곱 가지 차이 #

1. 네트워킹 — service name DNS vs ClusterIP + Service #

compose의 경험:

docker-compose.yml — 한 네트워크의 자동 발견
services:
  web:
    image: nginx
  db:
    image: postgres

web 컨테이너 안에서 psql -h db ...가 그냥 됩니다. compose가 만든 기본 네트워크 안에서 service name이 DNS로 자동 해결됩니다.

K8s의 결:

K8s — Service 객체 필요
apiVersion: v1
kind: Service
metadata:
  name: db
  namespace: default
spec:
  selector:
    app: db
  ports:
    - port: 5432

K8s에서는 Service 객체가 명시적으로 만들어져야 web Pod 안에서 db (또는 db.default.svc.cluster.local)로 호출할 수 있습니다. Pod 끼리 직접 호출이 아니라 Service를 거치는 것이 표준입니다.

이유는 5장 Service의 결입니다 — Pod는 죽고 다시 뜨면서 IP가 바뀌지만, Service는 안정적인 가상 IP와 DNS 이름을 보장합니다. compose의 service name 자동 해결은 사실 Docker의 embedded DNS가 한 호스트 안에서 한 네트워크의 컨테이너를 추적해 주는 결이고, K8s는 여러 노드를 가로지르는 안정적인 진입점을 Service라는 별도 객체로 분리해 두는 결입니다.

2. 서비스 디스커버리 — 같은 네트워크 자동 발견 vs Service + endpoints #

위와 결이 같지만 보는 각도가 다릅니다. compose에서는 암묵적으로 자동 발견됩니다. K8s에서는 명시적인 Service 정의가 있어야 발견됩니다.

K8s — Service의 endpoints 확인
kubectl get endpoints db

Endpoints가 Service의 selector와 일치하는 Pod의 IP 목록입니다. 이 둘이 비어 있거나 어긋나면 호출이 안 됩니다 — 27장 kubectl 디버깅 패턴 §“Service / Ingress가 안 닿을 때"의 selector → endpoints → port의 3 단 체인 그대로입니다.

compose의 자동 발견은 단순하지만 한 호스트 안에서만 동작합니다. K8s의 명시적 모델은 손이 더 가지만 클러스터 전체에서 일관되게 동작 한다는 결의 차이입니다.

3. 영속 볼륨 — 호스트 경로 자동 마운트 vs PVC 요청 + StorageClass #

compose의 경험:

docker-compose.yml — 볼륨
services:
  db:
    image: postgres
    volumes:
      - db-data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql

volumes:
  db-data:

named volume은 자동 생성되고, bind mount는 호스트의 파일 경로를 그대로 마운트합니다. 한 호스트의 디스크가 그대로 보이는 단순한 모델입니다.

K8s의 결:

K8s — PVC 요청 + StorageClass
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: db-data
spec:
  storageClassName: gp3
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 10Gi
---
# Pod 의 volumeMount + volumes
spec:
  containers:
    - name: db
      image: postgres
      volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: db-data

9장 PV / PVC / StorageClass의 모델입니다. PVC가 요청, PV가 실제 디스크, StorageClass가 자동 프로비저닝의 정책의 세 객체로 갈라집니다.

이유는 K8s의 결 — Pod가 어느 노드에 떠오를지를 미리 알 수 없으므로, 노드의 호스트 디스크에 의존하면 안 됩니다. EBS / EFS / GCP PD 같은 노드와 독립된 영속 디스크를 PVC로 요청하고, StorageClass가 자동으로 프로비저닝해 Pod에 마운트합니다.

compose의 bind mount에 대응되는 패턴은 본 책의 표준 경로에는 없습니다 — 운영 환경의 호스트 경로 마운트는 보안과 이식성 측면에서 권장되지 않습니다. 개발 환경의 hostPath는 가능하지만 본문에서는 다루지 않습니다.

4. 시크릿 — env_file vs Secret + 운영 라이프사이클 #

compose의 경험:

docker-compose.yml — env_file
services:
  api:
    image: myapi
    env_file:
      - .env

.env 한 파일에 비밀번호와 API 키를 적어 두고, .gitignore에 추가하면 끝입니다. 단순합니다.

K8s의 결:

K8s — Secret
apiVersion: v1
kind: Secret
metadata:
  name: api-secrets
type: Opaque
stringData:
  DATABASE_PASSWORD: "..."
---
# Pod 의 envFrom
spec:
  containers:
    - name: api
      envFrom:
        - secretRef:
            name: api-secrets

6장 ConfigMap과 Secret의 모델입니다. 매니페스트의 data는 base64 인코딩일 뿐 암호화가 아닙니다 — 그래서 본 매니페스트를 그대로 git에 커밋하면 비밀이 노출됩니다.

이 결을 푸는 본격적인 도구가 29장 시크릿 운영의 세 옵션 (sealed-secrets / external-secrets / SOPS)입니다. compose의 env_file + .gitignore 모델은 K8s에서는 더 정교한 운영 라이프사이클로 갈라집니다 — 저장 · 회전 · 주입 · 감사의 네 축이 본격적인 결이고, 단순함의 대가입니다.

5. 헬스체크 — 한 단계 vs 세 단계 #

compose의 경험:

docker-compose.yml — healthcheck
services:
  api:
    image: myapi
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s

한 healthcheck로 컨테이너의 “건강함"을 판단합니다. unhealthy가 일정 횟수 누적되면 (restart: on-failure와 결합하면) 재시작입니다.

K8s의 결:

K8s — 세 가지 probe
spec:
  containers:
    - name: api
      readinessProbe:
        httpGet: { path: /health/ready, port: http }
        initialDelaySeconds: 5
        periodSeconds: 5
      livenessProbe:
        httpGet: { path: /health/live, port: http }
        initialDelaySeconds: 30
        periodSeconds: 10
      startupProbe:
        httpGet: { path: /health/live, port: http }
        failureThreshold: 30
        periodSeconds: 10

12장 헬스 체크에서 다룬 모델 그대로입니다 — readiness / liveness / startup의 세 결로 갈라집니다.

  • readiness — 트래픽을 받을 준비가 되었는가. fail 이면 Service endpoints에서 제외.
  • liveness — 컨테이너가 살아 있는가. fail이 누적되면 kubelet이 컨테이너 재시작.
  • startup — 초기화 중인가. 초기화 시간이 긴 워크로드의 유예.

compose의 한 healthcheck가 K8s에서 세 결로 갈라진 이유 — “트래픽을 받을 수 있다"와 “살아 있다"와 “초기화 중이다"가 다른 의도라는 결입니다. 같은 endpoint를 fail 했어도 의미가 다릅니다. 한 단계로 묶으면 초기화 중에 liveness가 fail 하면서 컨테이너가 영원히 재시작되는 사고가 발생합니다.

depends_on의 대응 결도 여기서 풀립니다. compose의 “db가 healthy가 된 후에 api 시작"은 K8s에서는 api의 readinessProbe가 db의 응답을 확인하는 패턴으로 다룹니다 — 시작 순서를 강제하지 않고, “db가 응답 안 하면 readiness가 false → Service endpoints에 안 올림 → 트래픽이 안 옴"의 결로 자연스럽게 해결됩니다. 더 명시적이 필요하면 initContainer로 db 응답을 기다리는 패턴입니다.

6. 스케일링 — --scale의 한 호스트 한계 vs HPA + Cluster Autoscaler #

compose의 경험:

docker-compose의 scale
docker-compose up --scale api=3

한 호스트의 한도까지는 늘릴 수 있지만, 그 호스트의 CPU / 메모리 한계에 도달하면 끝입니다.

K8s의 결:

K8s — HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api
spec:
  scaleTargetRef:
    kind: Deployment
    name: api
  minReplicas: 2
  maxReplicas: 50
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60

13장 오토스케일링의 HPA와 Cluster Autoscaler / Karpenter의 두 단계 결합이 K8s의 답입니다. Pod가 늘어나면 노드가 자동으로 추가 되는 모양이 컨테이너 오케스트레이션의 본질 중 하나이고, compose가 가지지 못한 결입니다.

7. 로그 — 한 호스트 stdout vs 노드별 분산 + 에이전트 #

compose의 경험:

docker-compose logs
docker-compose logs -f api

한 호스트의 모든 컨테이너 stdout이 한 곳에 모입니다. 단순합니다.

K8s의 결:

K8s — Pod 단위 logs
kubectl logs <pod-name>
kubectl logs -l app=api --tail=100   # 라벨 단위

Pod가 여러 노드에 흩어져 있어 각 노드의 kubelet이 그 노드의 컨테이너 로그를 들고 있습니다. kubectl logs가 API Server를 거쳐 각 노드의 로그를 가져옵니다.

운영 환경에서는 노드별 로그 에이전트 (19장 옵저버빌리티 §“Loki — 가벼운 로그 스택"의 Promtail, 또는 25장 모니터링 · 알람의 Fluent Bit)가 모든 로그를 중앙 저장소 (Loki / CloudWatch)로 모으는 결이 추가됩니다. compose의 한 줄 명령이 K8s에서는 수집 에이전트 + 중앙 저장소 + 검색 인터페이스의 세 컴포넌트로 이어집니다.

마이그레이션 한 사이클 — 작은 web + db의 예 #

가장 흔한 docker-compose 한 묶음을 K8s 매니페스트로 옮겨 봅니다.

docker-compose.yml — 출발점
version: "3"

services:
  web:
    image: nginx:1.27-alpine
    ports:
      - "80:80"
    depends_on:
      - api
    environment:
      API_URL: http://api:8000
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/"]
      interval: 30s

  api:
    image: myapi:1.0
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://app:secret@db:5432/myapp
    depends_on:
      - db

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: myapp
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:

이 한 파일이 K8s의 약 200 줄의 매니페스트로 이어집니다. 핵심 객체들만 짚어 갑니다.

1. Namespace #

apiVersion: v1
kind: Namespace
metadata:
  name: myapp

compose에는 없는 결입니다. K8s의 격리 단위 — 7장 Namespace와 라벨의 첫 객체입니다.

2. Secret (DB 비밀번호) #

apiVersion: v1
kind: Secret
metadata:
  name: db
  namespace: myapp
type: Opaque
stringData:
  POSTGRES_PASSWORD: secret
  POSTGRES_USER: app
  POSTGRES_DB: myapp

compose의 environment 평문이 K8s의 Secret으로 분리됩니다. 운영에서는 29장의 세 옵션 중 하나로 봉인하지만, 학습용 매니페스트에서는 일단 평문 stringData로 시작합니다.

3. PVC (DB 데이터) #

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: db-data
  namespace: myapp
spec:
  storageClassName: gp3   # 또는 minikube 의 standard
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 10Gi

compose의 named volume이 PVC + StorageClass로 풀려납니다.

4. DB — Deployment + Service #

apiVersion: apps/v1
kind: Deployment
metadata:
  name: db
  namespace: myapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: db
  template:
    metadata:
      labels:
        app: db
    spec:
      containers:
        - name: postgres
          image: postgres:16
          envFrom:
            - secretRef:
                name: db
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
          readinessProbe:
            exec:
              command: ["pg_isready", "-U", "app"]
            periodSeconds: 5
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: db-data
---
apiVersion: v1
kind: Service
metadata:
  name: db
  namespace: myapp
spec:
  selector:
    app: db
  ports:
    - port: 5432
      targetPort: 5432

운영 PostgreSQL은 보통 8장 StatefulSet · DaemonSet · Job의 StatefulSet으로 가지만, 단일 인스턴스 학습 시나리오에서는 Deployment + 단일 PVC로 충분합니다. 운영의 표준은 23장 DB 연동의 매니지드 RDS입니다 — K8s 안에 DB를 직접 띄우지 않습니다.

5. API — Deployment + Service #

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  namespace: myapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: myapi:1.0
          env:
            - name: DATABASE_URL
              value: "postgresql://app:$(POSTGRES_PASSWORD)@db.myapp.svc.cluster.local:5432/myapp"
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db
                  key: POSTGRES_PASSWORD
          readinessProbe:
            httpGet: { path: /health, port: 8000 }
            initialDelaySeconds: 5
            periodSeconds: 5
          livenessProbe:
            httpGet: { path: /health, port: 8000 }
            initialDelaySeconds: 30
            periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
  name: api
  namespace: myapp
spec:
  selector:
    app: api
  ports:
    - port: 8000
      targetPort: 8000

compose의 depends_on: [db]는 K8s에서는 readinessProbe의 결로 풀립니다 — api의 readiness가 false 인 동안 Service endpoints에서 제외되어 web이 api를 못 봅니다. db가 떠올라 api가 db에 연결할 수 있게 되면 readiness가 true가 되고 트래픽이 흐릅니다.

6. Web — Deployment + Service + Ingress #

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: myapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:1.27-alpine
          env:
            - name: API_URL
              value: "http://api.myapp.svc.cluster.local:8000"
          readinessProbe:
            httpGet: { path: /, port: 80 }
---
apiVersion: v1
kind: Service
metadata:
  name: web
  namespace: myapp
spec:
  selector:
    app: web
  ports:
    - port: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  namespace: myapp
spec:
  rules:
    - host: myapp.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

compose의 ports: "80:80" 한 줄이 K8s에서는 Service (클러스터 내부 가상 IP) + Ingress (외부 진입점)로 갈라집니다. 외부 노출의 결정은 Service가 아니라 Ingress 에서5장 Service + 10장 Ingress의 분리 모델입니다.

적용 #

kubectl apply -f namespace.yaml
kubectl apply -f secret.yaml
kubectl apply -f pvc.yaml
kubectl apply -f db.yaml
kubectl apply -f api.yaml
kubectl apply -f web.yaml

이 6단계가 끝나면 compose의 한 명령 (docker-compose up)과 같은 시스템이 K8s 위에 떠 있습니다. 차이는 각 객체가 명시적이고 라이프사이클이 분리되어 있다는 결입니다. 한 객체만 업데이트할 수 있고, 한 객체만 디버깅할 수 있고, 한 객체만 권한을 분리할 수 있습니다.

매니페스트의 총 줄 수는 약 200 줄 — compose의 30 줄에 비하면 6배 이상입니다. 이 차이가 K8s의 학습 곡선의 출발점이지만, 그만큼의 표현력과 운영 결의 본격적인 도구를 받는 결입니다.

kompose 도구의 한계 #

komposedocker-compose.yml을 K8s 매니페스트로 자동 변환해 주는 도구입니다.

kompose convert
kompose convert -f docker-compose.yml -o k8s-manifests/

이 명령이 위 6단계의 K8s 매니페스트를 한 번에 만들어 줍니다 — 시작점으로 유용합니다. 그러나 자동 변환에는 한계가 있습니다.

kompose가 못 풀어 주는 결
1. depends_on -> initContainer 매핑은 단순하지만, 의도된 readiness 패턴이 아님
2. healthcheck -> probe 변환은 한 단계 (liveness) 만 — 세 결의 분리는 사람 손
3. bind mount -> hostPath 로 변환 — 운영에서는 부적합
4. networks 의 격리 -> NetworkPolicy 로 변환 안 됨
5. secrets / configs 의 봉인 옵션 -> 매니페스트 평문 Secret 로만 변환
6. Ingress 매니페스트 -> kompose 가 만들어 주지 않음. 항상 사람이 추가
7. PersistentVolume 의 StorageClass 결정 -> 클러스터마다 다르므로 사람이 선택

kompose는 시작점이지 목표가 아닙니다. 자동 변환된 매니페스트를 그대로 운영에 올리면 본 책의 일곱 가지 차이를 거의 다 무시한 결과가 됩니다. 운영 매니페스트로 가려면 본 책의 1~14장의 결을 적용해 손으로 다듬어야 합니다.

대안으로 Helm 차트로 처음부터 작성 하는 흐름이 더 자연스럽습니다. compose의 한 묶음을 helm create myapp으로 시작해 본 책의 패턴을 한 매니페스트씩 적용하는 게 운영 K8s의 표준 진입 경로입니다.

부록 A의 다음 단계 — 어디로 가야 하는가 #

본 부록을 마친 시점에서, Docker / docker-compose까지 와 있던 독자에게 권장하는 본 책의 진입 경로는 다음과 같습니다.

진입 권장 경로
[부록 A 마침]
   |
   v
[1장 쿠버네티스란 무엇인가] -- 본 부록을 다 본 시점에서는 큰 그림이 이미 잡혀 있음
[2장 로컬 환경] -- kind 또는 minikube 띄우기
[3장 kubectl 과 첫 Pod] -- 위 마이그레이션 한 사이클 의 첫 매니페스트
[4장 Deployment] -- Deployment 모델 본격
[5장 Service] -- ClusterIP / NodePort / LoadBalancer + DNS
[6장 ConfigMap · Secret] -- envFrom 의 본격 결
[7장 Namespace 와 라벨] -- 격리와 분류
   |
   v
   1부 완료 -- compose 의 모든 결이 K8s 한 묶음에 들어와 있는 상태

1장 쿠버네티스란 무엇인가가 본격 진입의 첫 챕터이고, 본 부록의 매핑 표를 곁에 두고 본문을 따라가시면 한 매 챕터마다 “이게 compose의 어느 키였지"가 자연스럽게 연결됩니다.

본 책의 1부 (1~7장)까지 따라가면 본 부록의 마이그레이션 매니페스트가 손에 익은 결로 보입니다. 그 시점부터 2부 (8~14장)로 넘어가 StatefulSet · PV · Ingress · 자원 관리 · 헬스체크 · 오토스케일링 · RBAC까지 다루고 나면, 작은 클러스터에서 다양한 워크로드를 운영할 수 있는 시야가 잡힙니다. 3부 (15~20장)의 깊이, 4부 (21~26장)의 EKS 실전, 5부 (27~30장)의 운영 · 디버깅 · 비용, 6부 (31장)의 캡스톤까지가 본 책 전체의 큰 그림입니다.

연습문제 #

  1. 본인이 운영하거나 학습 중인 작은 docker-compose.yml 한 파일을 골라, 본 부록 §“마이그레이션 한 사이클"의 6단계를 따라 K8s 매니페스트로 옮겨 봅니다. 변환 전 / 후 매니페스트의 줄 수를 비교하고, 늘어난 줄들이 어떤 결의 추가인지 (격리 · 라이프사이클 분리 · 명시적 객체)를 한 단락으로 정리합니다.
  2. 같은 docker-compose.ymlkompose convert로 자동 변환한 결과와 본인이 손으로 옮긴 결과를 비교합니다. 두 결과의 차이를 §“kompose의 한계"의 7 가지 항목 중 어디에 해당하는지로 분류하고, 자동 변환이 채우지 못한 결을 본문의 어느 챕터로 보완해야 하는지를 매핑합니다.
  3. 본 부록의 §“흔히 막히는 일곱 가지 차이” 중 본인 경험에서 가장 와 닿는 차이 하나를 골라, 본인 워크로드의 docker-compose 모델이 K8s에서 어떤 결로 변할지를 한 페이지로 미리 그려 봅니다. 이 메모를 본 책의 본문을 읽으시는 동안 곁에 두고, 해당 챕터에 도달했을 때 그 결이 실제로 어떻게 풀리는지 검증합니다.

한 줄 요약: docker-compose의 한 호스트 / 한 네트워크 / 단순 service name DNS 모델이 K8s에서는 여러 노드 / 여러 네임스페이스 / Service + endpoints의 명시적 모델로 풀려난다. services 한 줄이 Deployment + Service 두 객체로, volumes가 PVC + StorageClass로, env_file이 Secret + 운영 라이프사이클로, healthcheck 한 단계가 readiness + liveness + startup의 세 단계로, --scale의 한 호스트 한계가 HPA + Cluster Autoscaler의 두 단계 자동 반응으로 갈라진다. depends_on의 정확한 대응은 없고 readiness probe의 자연스러운 결로 풀린다. kompose가 시작점은 자동화하지만 목표는 아니다 — 본 책의 1~14장의 결을 손으로 적용해야 운영 매니페스트가 된다. 본 부록을 마친 시점에서 1장으로 진입하시면 매핑 표가 챕터마다 자연스럽게 풀려납니다.

책의 끝 #

본 부록으로 본 책의 32장 (1~31장 + 부록 A)이 모두 손에 들어왔습니다. 본 책의 본문 마지막 챕터인 31장 풀스택 앱 EKS 배포의 사후 회고 표가 본 책의 한 줄 요약이고, 본 부록의 매핑 표가 본 책의 출발점입니다. 출발점과 목표가 한 책 안에 묶인 모양이 본 책의 구조입니다.

1장 쿠버네티스란 무엇인가부터 본격 본문을 시작하시고, 부록 A가 본문 진행 중 매핑이 흐릿해질 때 다시 돌아오는 reference가 되어 주기를 기대합니다.

X