Notes
K8s 07. Container Orchestration
Containerization과 container orchestration의 차이를 구분하고, scheduling, deployment, scaling, self-healing, service discovery, rolling update를 중심으로 Kubernetes 운영 모델을 정리한다.
- Published
- Updated
- Area
- Cloud Infrastructure
- Type
- concept
- Series
- Kubernetes Essentials
- Category
- Notes
Container image를 만들 수 있다는 것과 production 환경에서 수많은 container를 안정적으로 운영할 수 있다는 것은 다른 문제다. 개발 환경에서는 container 하나를 직접 실행하는 것만으로 충분할 수 있다.
docker run -d --name api api:1.0.0
하지만 production 환경에서는 container가 여러 machine에 분산되고, traffic 증가, 장애, 배포, 네트워크, storage, configuration, secret, observability 문제가 함께 발생한다.
container를 어느 서버에 띄울 것인가?
container가 죽으면 누가 다시 살릴 것인가?
traffic이 늘어나면 몇 개까지 늘릴 것인가?
여러 container 사이의 네트워크 연결은 어떻게 할 것인가?
새 버전을 배포할 때 기존 요청은 어떻게 유지할 것인가?
서버 하나가 죽으면 그 서버 위의 container는 어떻게 복구할 것인가?
설정값과 secret은 어떻게 주입할 것인가?
로그와 metric은 어디서 볼 것인가?
Container orchestration은 이러한 문제를 해결하기 위해 여러 container를 여러 machine에 걸쳐 배포, 스케줄링, 확장, 복구, 네트워킹, 업데이트하는 운영 자동화 계층이다.
핵심 요약
Containerization과 container orchestration은 서로 다르다.
| 구분 | 핵심 질문 | 대표 기술 |
|---|---|---|
| Containerization | 애플리케이션을 어떻게 실행 단위로 포장할 것인가? | Docker, Podman, Buildah, OCI image |
| Container runtime | image를 어떻게 container process로 실행할 것인가? | containerd, CRI-O |
| Container orchestration | 여러 container를 cluster에서 어떻게 운영할 것인가? | Kubernetes, OpenShift, Docker Swarm 등 |
| Platform layer | 개발자 경험과 운영 정책까지 어떻게 통합할 것인가? | OpenShift, managed Kubernetes, platform engineering stack |
핵심 구분은 다음이다.
Containerization
→ 애플리케이션을 image로 packaging하는 기술
Container orchestration
→ 여러 containerized workload를 cluster에서 운영하는 기술
즉 containerization은 포장 방식이고, orchestration은 운영 방식이다.
Containerization과 orchestration은 다르다
Containerization
Containerization은 애플리케이션과 실행 의존성을 하나의 image로 묶는 것이다.
Application code
+
Runtime
+
Libraries
+
Dependencies
↓
Container image
예를 들어 Node.js API를 container image로 만들 수 있다.
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
CMD ["node", "server.js"]
이 image를 실행하면 container가 된다.
docker run -d --name api -p 3000:3000 api:1.0.0
여기까지가 containerization의 영역이다.
Container orchestration
Container orchestration은 그 다음 단계다. Container가 많아졌을 때, 이 container들을 전체 cluster 관점에서 관리하는 것이다.
Container image
↓
여러 container instance
↓
여러 server에 배치
↓
traffic 분산
↓
장애 복구
↓
scaling
↓
rolling update
Container 하나를 실행하는 것과 여러 container를 안정적으로 운영하는 것은 다르다. Orchestration은 containerized application을 production에서 운영 가능한 형태로 끌어올리는 계층이다.
왜 orchestration이 필요한가?
Container 하나는 수동으로 관리할 수 있다
개발 환경에서는 다음 정도로 충분할 수 있다.
docker run -d --name frontend frontend:1.0.0
docker run -d --name api api:1.0.0
docker run -d --name worker worker:1.0.0
또는 docker compose로 여러 container를 묶을 수도 있다.
services:
frontend:
image: frontend:1.0.0
ports:
- "80:80"
api:
image: api:1.0.0
ports:
- "3000:3000"
worker:
image: worker:1.0.0
작은 개발 환경이나 단일 서버에서는 이 방식이 편하다.
Production에서는 container가 빠르게 늘어난다
예를 들어 작은 쇼핑몰 애플리케이션을 생각해보자.
frontend
user-service
product-service
order-service
payment-service
notification-service
search-service
admin-service
각 service를 고가용성으로 운영하려면 replica가 여러 개 필요하다.
frontend × 3
user-service × 3
product-service × 3
order-service × 3
payment-service × 2
notification-service × 2
search-service × 3
admin-service × 2
벌써 container가 20개 이상이다. 여기에 staging, dev, batch worker, monitoring agent, sidecar, cron job까지 붙으면 수십 개에서 수백 개로 늘어날 수 있다.
이제 단순히 docker run을 반복하는 방식은 한계에 부딪힌다.
어느 서버에 어떤 container가 떠 있는지 추적해야 한다.
서버별 CPU와 memory를 고려해야 한다.
container가 죽으면 다시 띄워야 한다.
버전 업데이트 순서를 제어해야 한다.
traffic을 healthy instance로만 보내야 한다.
서버 장애 시 다른 서버로 옮겨야 한다.
Container orchestration은 이 복잡성을 자동화한다.
수동 운영의 한계
직접 배치해야 한다
서버가 세 대 있다고 하자.
node-1
node-2
node-3
container를 수동으로 배포하면 사람이 직접 판단해야 한다.
frontend는 node-1에 2개, node-2에 1개
api는 node-1에 1개, node-2에 2개
worker는 node-3에 2개
처음에는 가능하다. 하지만 서비스가 늘어나면 어려워진다.
node-1 CPU 사용률 80%
node-2 memory 부족
node-3 disk pressure
node-4 신규 추가
node-2 장애
api replica 5개로 증가
worker는 queue 길이에 따라 2개에서 20개로 증가
이런 조건을 사람이 계속 계산하는 것은 비현실적이다. Orchestrator는 scheduler를 통해 workload를 적절한 node에 배치한다.
장애를 사람이 감지해야 한다
수동 운영에서는 container가 죽었을 때 사람이 알아야 한다.
docker ps
docker logs api
docker restart api
작은 환경에서는 가능하지만, production에서는 장애가 자주 발생한다.
container process crash
OOMKilled
node reboot
network partition
disk full
image pull failure
readiness probe 실패
dependency 장애
Orchestration system은 이런 상태를 감지하고, 가능한 범위에서 자동 복구한다.
Scaling을 사람이 직접 해야 한다
traffic이 늘어나면 container를 늘려야 한다.
수동 방식:
docker run -d --name api-1 api:1.0.0
docker run -d --name api-2 api:1.0.0
docker run -d --name api-3 api:1.0.0
하지만 이 작업은 container를 더 띄우는 것만으로 끝나지 않는다.
새 container를 load balancer에 등록해야 한다.
health check를 붙여야 한다.
container가 준비되기 전에 traffic이 가면 안 된다.
traffic이 줄면 container를 줄여야 한다.
Kubernetes에서는 replica 수를 선언한다.
spec:
replicas: 5
또는 명령으로 조정한다.
kubectl scale deployment api --replicas=5
Kubernetes는 desired state를 기준으로 실제 Pod 수를 맞춘다.
Orchestration이 하는 핵심 작업
Container orchestration이 다루는 작업은 크게 다음으로 정리할 수 있다.
Scheduling
Deployment
Scaling
Service discovery
Load balancing
Self-healing
Rolling update
Rollback
Configuration management
Secret management
Storage orchestration
Observability integration
이 기능들은 각각 독립적인 듯 보이지만, production 운영에서는 서로 연결되어 있다.
예를 들어 rolling update가 안전하게 동작하려면 다음이 함께 맞아야 한다.
new Pod scheduling
image pull
readinessProbe 통과
Service endpoint update
old Pod graceful shutdown
traffic routing
rollout status 확인
Orchestration은 이런 과정을 사람이 매번 수동으로 처리하지 않도록 표준화한다.
Scheduling: 어디에 띄울 것인가
Scheduling의 기본 문제
Cluster에는 여러 node가 있다.
node-1: CPU 8 core, memory 32Gi
node-2: CPU 8 core, memory 32Gi
node-3: CPU 16 core, memory 64Gi
새로운 workload가 들어온다.
api Pod
requests:
cpu: 500m
memory: 512Mi
Scheduler는 이 workload를 어느 node에 둘지 결정한다.
Pending Pod
↓
Scheduler
↓
적절한 Node 선택
↓
kubelet이 해당 Node에서 container 실행
Scheduler가 고려하는 요소
Kubernetes scheduler는 단순히 아무 node나 고르지 않는다. 다음 조건들이 관여할 수 있다.
CPU request
memory request
node resource availability
nodeSelector
node affinity
pod affinity / anti-affinity
taints / tolerations
volume binding
topology spread constraints
priority
예를 들어 GPU node에만 배치하고 싶다면 label과 nodeSelector를 사용할 수 있다.
spec:
nodeSelector:
accelerator: nvidia
특정 node에 일반 Pod가 올라오지 못하게 하려면 taint/toleration을 쓸 수 있다.
kubectl taint nodes gpu-node accelerator=nvidia:NoSchedule
해당 workload에 toleration을 준다.
tolerations:
- key: "accelerator"
operator: "Equal"
value: "nvidia"
effect: "NoSchedule"
이런 scheduling 정책은 단순 container 실행 도구가 아니라 orchestrator가 필요한 이유를 보여준다.
Deployment: 어떻게 배포할 것인가
단순 실행과 배포는 다르다
Container를 실행하는 것과 application을 배포하는 것은 다르다.
단순 실행:
docker run api:1.0.0
운영 배포:
새 버전 image 적용
replica 수 유지
old Pod와 new Pod 교체
readiness 확인
실패 시 rollout 중단
필요 시 rollback
Kubernetes에서는 Deployment가 stateless workload 배포의 대표 object다. 사용자는 desired state를 선언하고, Deployment Controller가 actual state를 desired state에 맞춘다.
Deployment 예시
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: registry.example.com/api:1.0.0
ports:
- containerPort: 3000
이 YAML의 의미는 다음이다.
api라는 애플리케이션을
항상 3개 replica로 유지하고,
각 Pod는 registry.example.com/api:1.0.0 image를 사용한다.
Orchestrator는 이 선언을 보고 실제 상태를 맞춘다.
desired replicas = 3
actual replicas = 2
action = create 1 Pod
Scaling: 몇 개를 유지할 것인가
수동 scaling
Kubernetes에서는 replica 수를 바꾸면 된다.
kubectl scale deployment api --replicas=10
또는 manifest를 수정한다.
spec:
replicas: 10
그러면 Deployment Controller가 Pod 수를 맞춘다.
desired replicas = 10
actual replicas = 3
action = create 7 Pods
자동 scaling
Production에서는 traffic이나 CPU 사용량에 따라 자동 scaling을 원할 수 있다.
CPU usage 증가
↓
Horizontal Pod Autoscaler
↓
replicas 증가
예시:
kubectl autoscale deployment api --cpu-percent=70 --min=3 --max=20
다만 autoscaling은 단순히 “Pod를 늘리면 해결”이라는 뜻이 아니다.
주의할 점:
application이 stateless하게 scale 가능한가?
DB connection pool이 버틸 수 있는가?
message queue consumer 수가 늘어도 순서 문제가 없는가?
external API rate limit에 걸리지 않는가?
resource requests가 적절히 설정되어 있는가?
Orchestrator는 Pod 수를 늘릴 수 있지만, application architecture가 horizontal scaling에 맞지 않으면 오히려 장애가 커질 수 있다.
Self-healing: 죽으면 어떻게 복구할 것인가
Container failure
Container process가 죽으면 kubelet이 restart policy에 따라 재시작할 수 있다.
container crash
↓
kubelet 감지
↓
container restart
하지만 같은 오류가 계속 발생하면 CrashLoopBackOff가 된다.
NAME READY STATUS RESTARTS
api-7f9d8c9c8-abcde 0/1 CrashLoopBackOff 5
이 경우 Kubernetes는 재시작을 시도하지만, application bug를 고쳐주지는 않는다.
확인 명령:
kubectl logs api-7f9d8c9c8-abcde
kubectl describe pod api-7f9d8c9c8-abcde
Pod failure
Deployment가 관리하는 Pod가 사라지면 replacement Pod가 만들어진다.
kubectl delete pod api-7f9d8c9c8-abcde
그러면 ReplicaSet이 desired replica 수를 맞추기 위해 새 Pod를 만든다.
desired replicas = 3
actual replicas = 2
action = create 1 replacement Pod
Node failure
Node 자체가 죽으면 그 node 위의 Pod들도 실패한다.
node-1 down
↓
node-1 위의 Pods unavailable
↓
control plane이 node failure 감지
↓
다른 node에 replacement Pods 생성
다만 주의할 점이 있다.
stateless Pod는 비교적 쉽게 다른 node에 재배치 가능
stateful workload는 volume attach/detach, data consistency, failover 정책 필요
application-level HA는 Kubernetes만으로 완성되지 않음
Self-healing은 container restart와 replacement Pod 생성을 자동화하지만, 모든 장애를 자동으로 해결하는 것은 아니다.
Service discovery와 load balancing
Container가 많아지면 서로를 어떻게 찾을지가 중요하다.
Pod IP에 직접 의존하면 안 된다
Pod는 ephemeral하다.
api-pod-1: 10.244.1.10
api-pod-2: 10.244.2.11
api-pod-3: 10.244.3.12
하나가 죽고 새로 생기면 IP가 바뀔 수 있다.
api-pod-2 deleted
api-pod-4: 10.244.4.21
Frontend가 Pod IP를 직접 알고 있다면 장애가 난다.
Service가 안정적인 endpoint를 제공한다
Kubernetes에서는 Service가 Pod 집합 앞에 stable endpoint를 제공한다.
apiVersion: v1
kind: Service
metadata:
name: api
spec:
selector:
app: api
ports:
- port: 80
targetPort: 3000
Frontend는 다음처럼 호출하면 된다.
http://api
Service는 backend Pod 집합 앞에서 stable endpoint 역할을 한다. Pod가 준비되지 않았거나 실패하면 endpoint에서 제외되어 traffic을 받지 않게 할 수 있다.
Orchestration에서 네트워크는 핵심이다
Container orchestration은 단순히 container를 띄우는 것이 아니다. 여러 container가 서로 통신할 수 있도록 네트워크 abstraction을 제공해야 한다.
frontend → api
api → user-service
api → payment-service
worker → queue
운영에서는 다음도 고려해야 한다.
Service DNS
ClusterIP
Ingress
LoadBalancer
NetworkPolicy
mTLS
Service Mesh
CNI plugin
Rolling update: 새 버전을 어떻게 배포할 것인가
Production에서 새 버전을 배포할 때 가장 피하고 싶은 것은 전체 중단이다.
나쁜 방식:
old version 전체 종료
↓
new version 전체 시작
↓
기동 중 downtime 발생
Orchestrator는 rolling update를 통해 old Pod와 new Pod를 점진적으로 교체할 수 있다.
api:1.0.0 × 3
api:1.1.0 × 0
rolling update 진행
api:1.0.0 × 2
api:1.1.0 × 1
api:1.0.0 × 1
api:1.1.0 × 2
api:1.0.0 × 0
api:1.1.0 × 3
Kubernetes에서는 Deployment image를 바꾸면 rollout이 시작된다.
kubectl set image deployment/api api=registry.example.com/api:1.1.0
상태 확인:
kubectl rollout status deployment/api
문제 발생 시 rollback:
kubectl rollout undo deployment/api
하지만 rolling update가 무조건 안전한 것은 아니다.
주의할 점:
readinessProbe가 정확해야 한다.
Pod가 graceful shutdown을 처리해야 한다.
새 버전과 구버전이 동시에 떠도 호환되어야 한다.
DB migration이 backward compatible해야 한다.
session이나 cache가 특정 Pod에 묶이면 문제가 될 수 있다.
Orchestration system은 rollout 메커니즘을 제공하지만, 안전한 배포 전략은 application 설계와 함께 맞아야 한다.
Desired state와 reconciliation loop
Container orchestration의 가장 중요한 mental model은 desired state다.
사용자는 명령형으로 “이 container를 지금 실행해라”라고 말하는 대신, 선언형으로 “이 애플리케이션이 이런 상태를 유지해야 한다”고 선언한다.
spec:
replicas: 3
이 선언은 다음 의미다.
이 application은 항상 3개 replica가 있어야 한다.
Kubernetes는 actual state를 관찰하고 desired state와 비교한다.
Observe current state
↓
Compare with desired state
↓
Take action
↓
Repeat
예시:
desired state: api Pod 3개
actual state: api Pod 2개
action: Pod 1개 생성
또 다른 예시:
desired state: api image 1.1.0
actual state: api image 1.0.0
action: rolling update 수행
이 구조 때문에 Kubernetes는 단순 실행기가 아니라 distributed control system에 가깝다.
Cluster resource 효율과 bin packing
Container orchestration은 resource 효율과도 연결된다.
여러 node에 workload를 효율적으로 배치해야 한다.
node-1: CPU 8, memory 32Gi
node-2: CPU 8, memory 32Gi
node-3: CPU 16, memory 64Gi
workload마다 resource request가 있다.
frontend: cpu 200m, memory 256Mi
api: cpu 500m, memory 512Mi
worker: cpu 1, memory 1Gi
Scheduler는 node의 여유 resource와 Pod의 request를 보고 배치한다. 이 과정은 cluster resource를 더 효율적으로 쓰는 데 중요하다.
Resource requests와 limits
Kubernetes에서는 다음처럼 resource를 선언한다.
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
| 항목 | 의미 |
|---|---|
requests.cpu | scheduling 시 필요한 최소 CPU로 간주 |
requests.memory | scheduling 시 필요한 최소 memory로 간주 |
limits.cpu | container가 사용할 수 있는 CPU 상한 |
limits.memory | 초과 시 OOM kill될 수 있는 memory 상한 |
자칫 실수하기 쉬운 부분은 다음이다.
requests가 없으면 scheduler가 필요한 resource를 제대로 판단하기 어렵다.
limits.memory가 너무 낮으면 OOMKilled가 발생한다.
limits.cpu가 너무 낮으면 throttling이 심해질 수 있다.
requests가 너무 높으면 cluster utilization이 낮아진다.
Orchestration은 resource를 자동으로 관리하는 것처럼 보이지만, 좋은 scheduling을 위해서는 workload resource profile을 정확히 이해해야 한다.
Configuration과 Secret 관리
Orchestration이 필요한 또 다른 이유는 configuration 관리다.
Container image 안에 환경별 설정을 넣으면 안 된다.
나쁜 예:
ENV DB_HOST=prod-db.example.com
ENV DB_PASSWORD=my-real-password
이렇게 하면 image가 특정 환경에 종속되고, secret이 image에 들어가는 위험이 생긴다.
Kubernetes에서는 ConfigMap과 Secret을 사용한다.
apiVersion: v1
kind: ConfigMap
metadata:
name: api-config
data:
LOG_LEVEL: "info"
apiVersion: v1
kind: Secret
metadata:
name: api-secret
type: Opaque
stringData:
DB_PASSWORD: "[REDACTED]"
Deployment에서 주입한다.
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: api-config
key: LOG_LEVEL
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: api-secret
key: DB_PASSWORD
이 구조를 사용하면 image를 환경별로 다시 build하지 않고도 configuration과 secret을 주입할 수 있다.
Storage orchestration
Stateless workload는 비교적 단순하다.
Pod 죽음
↓
새 Pod 생성
↓
다시 traffic 처리
하지만 stateful workload는 다르다.
database
message broker
object storage
file upload service
persistent cache
이런 workload는 data를 유지해야 한다.
Kubernetes에서는 PersistentVolume, PersistentVolumeClaim, StorageClass 등을 사용한다.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
Pod에서 mount한다.
volumes:
- name: data
persistentVolumeClaim:
claimName: data
Self-healing과 storage가 결합되면 더 복잡해진다. Kubernetes는 Pod를 재시작하거나 다른 node에 배치할 수 있지만, storage failure나 stateful application의 data consistency는 별도 설계가 필요하다.
주의할 점:
Pod 재시작과 data 복구는 다른 문제다.
PVC가 있다고 DB HA가 자동 완성되는 것은 아니다.
volume attach/detach 지연이 있을 수 있다.
ReadWriteOnce volume은 동시에 여러 node에서 mount되지 않을 수 있다.
backup/restore 전략이 별도로 필요하다.
Health check: 살아 있음과 준비됨은 다르다
Orchestration에서 health check는 매우 중요하다.
Container process가 떠 있다고 해서 traffic을 받아도 되는 상태는 아니다.
container running
≠
application ready
예를 들어 application process는 떴지만 DB 연결이 아직 안 되었을 수 있다.
Kubernetes에서는 probe를 사용한다.
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 30
periodSeconds: 20
| Probe | 목적 |
|---|---|
readinessProbe | traffic을 받을 준비가 되었는지 판단 |
livenessProbe | application이 살아 있는지 판단, 실패하면 재시작 가능 |
startupProbe | 느리게 시작하는 application의 초기 기동 보호 |
주의할 점:
readinessProbe가 없으면 준비되지 않은 Pod로 traffic이 갈 수 있다.
livenessProbe가 너무 공격적이면 정상 회복 가능한 Pod를 계속 죽일 수 있다.
외부 dependency 장애를 liveness에 넣으면 cascading restart가 발생할 수 있다.
Orchestration system은 health check를 기반으로 traffic routing과 restart를 수행하므로, probe 설계가 운영 안정성에 직접 영향을 준다.
Load balancing
Orchestrator는 여러 replica에 traffic을 분산해야 한다.
Client
↓
Service
├─ api-pod-1
├─ api-pod-2
└─ api-pod-3
Kubernetes Service는 backend Pod 집합 앞에서 stable endpoint 역할을 한다. Pod가 준비되지 않았거나 실패하면 endpoints에서 제외되어 traffic을 받지 않게 할 수 있다.
다만 Kubernetes Service의 load balancing은 기본적인 L4 수준에 가깝다. HTTP path 기반 routing, header 기반 routing, canary traffic split, retry, circuit breaking 같은 기능은 Ingress Controller나 service mesh 영역으로 넘어간다.
Service
→ 기본 service discovery와 L4 load balancing
Ingress
→ HTTP/HTTPS host/path routing
Service Mesh
→ service-to-service traffic policy, mTLS, retry, circuit breaking, telemetry
Orchestration은 network 계층의 여러 abstraction과 함께 동작한다.
Orchestration과 microservices
Container orchestration은 microservices와 밀접하게 연결된다.
Microservices architecture에서는 기능별 service가 독립적으로 배포되고 확장된다.
frontend
auth-service
user-service
order-service
payment-service
notification-service
각 service는 서로 다른 언어와 runtime을 사용할 수 있다.
frontend: Node.js
user-service: Java
payment-service: Go
notification-service: Python
Containerization은 이 service들을 각각 image로 포장할 수 있게 해준다.
frontend: frontend:1.0.0
user-service: user:1.0.0
payment-service: payment:1.0.0
notification-service: notification:1.0.0
Orchestration은 이 image들을 cluster에서 운영한다.
frontend × 3
user-service × 5
payment-service × 3
notification-service × 2
여기서 중요한 점은 microservice마다 scaling 기준이 다르다는 것이다. payment-service는 외부 결제 API rate limit 때문에 무작정 늘릴 수 없고, notification-service는 queue 길이에 따라 늘어날 수 있다. Orchestration은 각 service의 replica와 rollout을 독립적으로 관리할 수 있게 해준다.
Orchestration과 CI/CD
Container orchestration은 CI/CD와도 연결된다.
일반적인 흐름은 다음과 같다.
1. Source code commit
2. CI test
3. Container image build
4. Registry push
5. Kubernetes manifest 또는 Helm values update
6. Deployment rollout
7. Health check
8. Monitoring
9. Rollback if needed
예시:
docker build -t registry.example.com/api:${GIT_SHA} .
docker push registry.example.com/api:${GIT_SHA}
kubectl set image deployment/api api=registry.example.com/api:${GIT_SHA}
kubectl rollout status deployment/api
Helm을 쓴다면 다음처럼 할 수 있다.
helm upgrade --install api ./chart \
-f values-prod.yaml \
--set image.tag=${GIT_SHA}
Orchestrator는 CI/CD가 만든 artifact를 실제 runtime으로 배포하는 계층이다.
Git commit
↓
Container image
↓
Registry
↓
Orchestrator rollout
↓
Running Pods
여기서 중요한 것은 image tag와 deployment revision을 추적할 수 있어야 한다는 점이다.
Orchestration이 해결하지 않는 문제
Container orchestration은 강력하지만 만능은 아니다.
Application bug는 고쳐주지 않는다
Kubernetes가 container를 재시작해도 code bug가 있으면 계속 죽는다.
buggy app
↓
container crash
↓
Kubernetes restart
↓
container crash again
↓
CrashLoopBackOff
확인해야 할 것은 log와 application code다.
kubectl logs <pod-name>
kubectl describe pod <pod-name>
잘못된 architecture를 자동으로 고치지 않는다
Monolith를 container에 넣는다고 자동으로 cloud-native가 되지는 않는다.
monolith.jar
↓
container image
↓
Kubernetes deployment
이렇게 할 수는 있지만, 독립 scaling, 장애 격리, 배포 유연성이 자동으로 생기는 것은 아니다.
Database HA는 별도 설계가 필요하다
Kubernetes가 Pod를 재시작할 수 있다고 해서 database HA가 자동 완성되는 것은 아니다.
DB replication
backup/restore
failover
data consistency
storage latency
volume recovery
이런 문제는 별도 설계가 필요하다.
Observability stack은 별도로 구성해야 한다
Kubernetes는 상태 정보를 제공하지만, production observability는 별도 stack이 필요하다.
metrics: Prometheus, Grafana
logs: Loki, Elasticsearch, Fluent Bit
tracing: Jaeger, Tempo, OpenTelemetry
alerting: Alertmanager
Security policy도 설계해야 한다
Kubernetes에는 RBAC, NetworkPolicy, Secret, admission controller 같은 기능이 있지만, 정책을 어떻게 설계할지는 운영자의 몫이다.
누가 어떤 namespace에 접근할 수 있는가?
Pod가 root로 실행될 수 있는가?
image registry는 신뢰 가능한가?
network egress는 어디까지 허용할 것인가?
secret은 어떻게 암호화하고 회전할 것인가?
Container orchestration 도입 시 운영자가 봐야 할 것
Container orchestration을 도입할 때는 “Kubernetes 설치”만 보면 안 된다.
체크해야 할 영역은 다음과 같다.
Cluster architecture
Node sizing
Container runtime
CNI
CSI
Ingress / Gateway
Registry
CI/CD
Secret management
Monitoring
Logging
Backup
RBAC
NetworkPolicy
Resource quota
Autoscaling
Upgrade strategy
Disaster recovery
Kubernetes 자체는 orchestration engine이지만, production platform은 여러 구성요소가 함께 필요하다.
예를 들어 최소 production platform을 생각하면 다음처럼 된다.
Kubernetes cluster
├─ CNI
├─ CSI / StorageClass
├─ Ingress Controller
├─ Metrics Server
├─ Prometheus / Grafana
├─ Logging pipeline
├─ cert-manager
├─ ExternalDNS
├─ Container registry
├─ GitOps controller
└─ Backup tool
이런 관점에서 container orchestration은 단순히 “container를 자동으로 띄워주는 것”이 아니라, cloud-native platform의 중심부에 해당한다.
예제로 보는 orchestration 전후 비교
Orchestration 없이 운영
서버 세 대에 직접 container를 띄운다.
node-1
├─ frontend-1
├─ api-1
└─ worker-1
node-2
├─ frontend-2
└─ api-2
node-3
├─ api-3
└─ worker-2
문제가 생긴다.
node-2 장애
↓
frontend-2, api-2 중단
↓
운영자가 감지
↓
node-1 또는 node-3에 수동 재배포
↓
load balancer 설정 수정
↓
health check 확인
배포도 수동이다.
api:1.0.0 중단
api:1.1.0 실행
load balancer 업데이트
장애 시 수동 rollback
Orchestration 사용
Kubernetes에 desired state를 선언한다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: registry.example.com/api:1.0.0
Kubernetes가 Pod를 여러 node에 배치한다.
node-1 → api Pod
node-2 → api Pod
node-3 → api Pod
node-2가 죽으면:
node-2 failure
↓
api Pod 1개 unavailable
↓
Deployment desired replicas = 3
↓
replacement Pod scheduled on healthy node
새 버전 배포:
kubectl set image deployment/api api=registry.example.com/api:1.1.0
Kubernetes가 rolling update를 수행한다.
old Pod 일부 유지
new Pod 생성
readiness 확인
old Pod 제거
반복
Kubernetes object로 보는 orchestration
Container orchestration이 Kubernetes에서는 여러 object로 표현된다.
| Orchestration 기능 | Kubernetes object / component |
|---|---|
| workload 실행 | Pod |
| replica 유지 | Deployment, ReplicaSet |
| stateful workload | StatefulSet |
| node마다 하나씩 실행 | DaemonSet |
| batch 작업 | Job, CronJob |
| service discovery | Service |
| 외부 HTTP routing | Ingress |
| 설정 관리 | ConfigMap |
| 민감정보 관리 | Secret |
| storage 연결 | PersistentVolume, PersistentVolumeClaim, StorageClass |
| scheduling | kube-scheduler |
| 상태 조정 | controllers |
| node agent | kubelet |
| cluster state 저장 | etcd |
이 표를 보면 Kubernetes는 단일 기능 도구가 아니라, container 운영 문제를 여러 API object와 controller로 나누어 해결하는 system이라는 점이 보인다.
자주 생기는 오해
오해 1: Orchestration은 단순히 container 여러 개 실행하는 것이다
아니다. 여러 개 실행하는 것은 시작일 뿐이다.
Orchestration은 다음을 포함한다.
배치
복구
확장
업데이트
네트워킹
스토리지
설정
secret
health check
rollout
rollback
오해 2: Kubernetes가 있으면 Docker가 필요 없다
정확히는 역할이 다르다.
Docker / Podman / Buildah
→ image build와 local container workflow
containerd / CRI-O
→ runtime에서 container 실행
Kubernetes
→ cluster에서 containerized workload orchestration
오해 3: Self-healing은 모든 장애를 자동 해결한다
Kubernetes는 container를 재시작하고 replacement Pod를 만들 수 있다. 하지만 application bug, DB 장애, 잘못된 config, 외부 dependency 장애를 자동으로 해결하지는 않는다.
오해 4: Pod를 직접 관리하면 된다
Kubernetes에서 Pod는 disposable object에 가깝다. 실무에서는 보통 Deployment, StatefulSet, DaemonSet, Job 같은 상위 object를 사용한다.
오해 5: Orchestration을 쓰면 운영이 단순해진다
정확히는 반복 운영은 자동화되지만, platform 복잡도는 생긴다.
자동화되는 것:
replica 유지
scheduling
restart
rollout
service discovery
새로 생기는 운영 과제:
cluster upgrade
CNI/CSI 관리
RBAC
observability
security policy
cost optimization
Orchestration의 진짜 가치
Container orchestration의 진짜 가치는 단순히 container를 많이 띄우는 것이 아니다.
핵심 가치는 다음이다.
운영 의도를 선언형으로 표현한다.
반복적인 운영 작업을 controller가 수행한다.
application instance 수를 자동으로 유지한다.
장애 발생 시 기본 복구 경로를 제공한다.
traffic routing과 service discovery를 추상화한다.
rolling update와 rollback을 표준화한다.
cluster resource를 효율적으로 사용한다.
CI/CD와 연결해 배포를 자동화한다.
즉 orchestration은 containerized application을 production에서 운영 가능한 형태로 끌어올리는 계층이다.
전체 mental model
Container orchestration을 이해하기 위한 흐름은 다음과 같다.
1. Containerization은 application을 image로 packaging하는 기술이다.
2. 하지만 production에서는 container가 많아지고 여러 server에 분산된다.
3. 수동으로 container를 배치, 재시작, scale, 업데이트하는 것은 복잡하다.
4. Container orchestration은 이 운영 문제를 자동화한다.
5. Orchestrator는 scheduling, deployment, scaling, networking, self-healing,
rolling update, configuration management를 담당한다.
6. Kubernetes는 대표적인 container orchestration engine이다.
7. Kubernetes는 desired state를 선언하면 actual state를 계속 맞추는 방식으로 동작한다.
8. Self-healing은 container restart, replacement Pod 생성, node failure 대응 등을 포함한다.
9. Service discovery와 load balancing은 Pod IP 변화와 replica 증가를 application이 직접 신경 쓰지 않게 해준다.
10. Orchestration은 cloud-native application 운영의 기반이지만,
application bug, database HA, observability, security policy는 별도 설계가 필요하다.
요약
Container image를 만들 수 있다는 것과 production에서 수많은 container를 안정적으로 운영할 수 있다는 것은 다른 문제다. Container가 많아지면 배치, scaling, service discovery, load balancing, health check, self-healing, rolling update, rollback, configuration, secret, storage, monitoring이 필요하다.
Kubernetes는 이런 container orchestration 문제를 해결하기 위한 대표적인 platform이다. Kubernetes는 사용자가 선언한 desired state를 기준으로 cluster의 actual state를 계속 조정한다.
한 문장으로 정리하면 다음과 같다.
Container orchestration은 containerized application을 여러 machine 위에서 안정적으로 배포, 확장, 복구, 연결, 업데이트하기 위한 운영 자동화 계층이다.