컨테이너 오케스트레이션과 실무
컨테이너 하나를 실행하는 것은 간단합니다. 그러나 수백, 수천 개의 컨테이너를 여러 서버에 걸쳐 배포하고, 장애 시 자동 복구하고, 트래픽에 따라 확장하려면 오케스트레이션(Orchestration) 도구가 필요합니다. Kubernetes는 이 문제를 해결하는 사실상의 표준입니다.
Kubernetes 아키텍처
Kubernetes(K8s)는 구글이 내부에서 사용하던 Borg 시스템을 기반으로 만들고 오픈소스로 공개한 컨테이너 오케스트레이션 시스템입니다.
┌─────────────────── Control Plane ───────────────────┐
│ ┌──────────┐ ┌──────────┐ ┌─────────────────┐ │
│ │ API │ │ etcd │ │ Scheduler │ │
│ │ Server │ │ (저장소) │ │ │ │
│ └─────┬────┘ └──────────┘ └─────────────────┘ │
│ │ ┌──────────────────────┐ │
│ │ │ Controller Manager │ │
│ │ └──────────────────────┘ │
└────────┼────────────────────────────────────────────┘
│
┌────┴────────────── Worker Nodes ─────────────────┐
│ ┌──────────────────────────────────────────┐ │
│ │ Node 1 │ │
│ │ ┌────────┐ ┌────────┐ ┌────────────┐ │ │
│ │ │kubelet │ │kube- │ │Container │ │ │
│ │ │ │ │proxy │ │Runtime │ │ │
│ │ └────────┘ └────────┘ └────────────┘ │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ │ │
│ │ │Pod A │ │Pod B │ │Pod C │ │ │
│ │ └──────┘ └──────┘ └──────┘ │ │
│ └──────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘| 구성 요소 | 역할 |
|---|---|
| API Server | 모든 요청의 진입점, kubectl과 통신 |
| etcd | 클러스터 상태를 저장하는 분산 키-값 저장소 |
| Scheduler | 새 Pod를 어떤 Node에 배치할지 결정 |
| Controller Manager | 원하는 상태와 현재 상태를 맞추는 컨트롤 루프 |
| kubelet | 각 Node에서 Pod를 실제로 실행·관리 |
| kube-proxy | 서비스의 네트워크 트래픽을 Pod로 라우팅 |
Kubernetes 핵심 개념
Pod
Kubernetes의 최소 배포 단위입니다. 하나 이상의 컨테이너를 묶은 그룹입니다.
apiVersion: v1
kind: Pod
metadata:
name: web-pod
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.25-alpine
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"같은 Pod 안의 컨테이너는 네트워크(localhost)와 볼륨을 공유합니다. 애플리케이션 컨테이너 옆에 로그 수집기나 프록시를 함께 배치하는 사이드카(Sidecar) 패턴이 대표적입니다.
Deployment
Pod의 선언적 관리를 담당합니다. 이 이미지의 컨테이너 3개를 항상 유지하라고 선언하면, Kubernetes가 자동으로 그 상태를 맞춥니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
spec:
replicas: 3
selector:
matchLabels:
app: web
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 업데이트 중 추가 Pod 수
maxUnavailable: 0 # 항상 3개 유지
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.25-alpine
ports:
- containerPort: 80Pod가 충돌하면 자동으로 새 Pod를 생성합니다. 이미지를 변경하면 롤링 업데이트로 하나씩 교체합니다. 문제가 발생하면 즉시 이전 버전으로 롤백할 수 있습니다.
# 배포
kubectl apply -f deployment.yaml
# 상태 확인
kubectl get pods -o wide
kubectl describe pod web-deployment-xxx
# 스케일링
kubectl scale deployment web-deployment --replicas=5
# 롤링 업데이트
kubectl set image deployment/web-deployment nginx=nginx:1.26-alpine
# 롤백
kubectl rollout undo deployment/web-deployment
kubectl rollout history deployment/web-deploymentService
Pod는 생성·소멸을 반복하므로 IP가 바뀝니다. Service는 안정적인 네트워크 엔드포인트를 제공합니다.
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
type: ClusterIP
selector:
app: web
ports:
- port: 80
targetPort: 80| Service 타입 | 접근 범위 | 용도 |
|---|---|---|
| ClusterIP | 클러스터 내부 | 내부 서비스 간 통신 |
| NodePort | 외부 (Node IP:Port) | 개발·테스트 |
| LoadBalancer | 외부 (클라우드 LB) | 프로덕션 외부 노출 |
| Ingress | 외부 (L7 라우팅) | 도메인 기반 라우팅 |
HPA (Horizontal Pod Autoscaler)
트래픽에 따라 자동으로 Pod 수를 조절합니다.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-deployment
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70CPU 사용률이 70%를 넘으면 Pod를 늘리고, 낮아지면 줄입니다. 개발자가 인프라를 직접 관리하는 대신, 원하는 상태를 선언하면 시스템이 유지합니다.
컨테이너 네트워킹
Docker 네트워크
Docker는 기본적으로 브리지 네트워크를 생성합니다.
┌──────── 호스트 ──────────────────────────────────┐
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │Container │ │Container │ │
│ │ eth0 │ │ eth0 │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ veth │ veth │
│ ┌────┴────────────────┴────┐ │
│ │ docker0 (브리지) │ │
│ └───────────┬──────────────┘ │
│ │ NAT (iptables) │
│ ┌────┴────┐ │
│ │ eth0 │ ← 호스트 NIC │
│ └─────────┘ │
└──────────────────────────────────────────────────┘각 컨테이너는 veth pair로 브리지에 연결됩니다. 외부 통신은 NAT(iptables)를 거칩니다.
Kubernetes 네트워크 모델
Kubernetes는 세 가지 네트워크 원칙을 따릅니다.
- 모든 Pod는 NAT 없이 다른 Pod와 직접 통신할 수 있다
- 모든 Node는 NAT 없이 모든 Pod와 통신할 수 있다
- Pod가 보는 자기 IP = 다른 Pod가 보는 그 Pod의 IP
CNI(Container Network Interface) 플러그인이 이를 구현합니다.
| CNI 플러그인 | 방식 | 특징 |
|---|---|---|
| Flannel | VXLAN 오버레이 | 간단, 소규모 적합 |
| Calico | BGP / VXLAN | 네트워크 정책 지원, 대규모 적합 |
| Cilium | eBPF 기반 | 고성능, 관찰 가능성 |
| Weave | 메시 오버레이 | 설정 간단 |
서버리스와 마이크로 VM
서버리스(Serverless)는 개발자가 서버를 관리하지 않고 함수 단위로 코드를 배포하는 모델입니다. AWS Lambda, Google Cloud Functions, Azure Functions가 대표적입니다.
서버리스라고 서버가 없는 것은 아닙니다. 내부적으로는 컨테이너나 마이크로 VM이 함수 실행 환경을 제공합니다. AWS Lambda는 Firecracker라는 경량 VM을 사용합니다.
| 특성 | 일반 VM | Firecracker |
|---|---|---|
| 부팅 시간 | 수십 초 | ~125ms |
| 메모리 오버헤드 | 수백 MB | ~5MB |
| 보안 격리 | 하이퍼바이저 | KVM 기반 |
| 디바이스 | 전체 에뮬레이션 | 최소 (virtio-net, virtio-block) |
서버리스의 가장 큰 도전 과제는 콜드 스타트(Cold Start)입니다. 요청이 없던 함수가 호출되면, 실행 환경을 새로 준비하는 데 수백 ms ~ 수 초가 걸립니다.
인프라 선택 기준
| 인프라 | 성능 | 격리 | 배포 속도 | 관리 부담 | 적합한 경우 |
|---|---|---|---|---|---|
| 베어메탈 | 최고 | 물리적 | 느림 | 높음 | GPU 학습, HPC |
| VM | 높음 | 강함 | 분 단위 | 중간 | 멀티테넌트 |
| 컨테이너 | 높음 | 중간 | 초 단위 | 낮음 | 마이크로서비스 |
| 서버리스 | 가변 | 강함 | 즉시 | 최소 | 이벤트 처리 |
실무에서는 이들을 혼합합니다. 데이터베이스는 VM이나 매니지드 서비스로, 애플리케이션 서버는 컨테이너(Kubernetes)로, 이벤트 처리는 서버리스로 구성하는 것이 흔한 패턴입니다.
def recommend_infra(workload):
"""워크로드 특성에 따른 인프라 추천"""
if workload["gpu_required"]:
return "베어메탈 또는 GPU VM"
if workload["multi_tenant"]:
return "VM (강한 격리 필요)"
if workload["traffic_pattern"] == "spiky":
if workload["avg_duration_ms"] < 300000: # 5분 미만
return "서버리스 (Lambda/Cloud Functions)"
else:
return "컨테이너 + HPA"
if workload["microservices"]:
return "Kubernetes 컨테이너"
return "VM (범용)"CI/CD와 컨테이너
컨테이너는 CI/CD(지속적 통합/배포) 파이프라인과 자연스럽게 결합됩니다.
코드 Push → 빌드 → 테스트 → 이미지 빌드 → 레지스트리 Push → K8s 배포
(Git) (CI) (CI) (Docker) (ECR/GCR) (CD)# 1. 이미지 빌드 (멀티 스테이지로 크기 최소화)
docker build -t myapp:${GIT_SHA} .
# 2. 보안 스캐닝
trivy image myapp:${GIT_SHA}
# 3. 레지스트리에 푸시
docker push registry.example.com/myapp:${GIT_SHA}
# 4. Kubernetes에 배포
kubectl set image deployment/myapp \
myapp=registry.example.com/myapp:${GIT_SHA}
# 5. 배포 상태 확인
kubectl rollout status deployment/myapp이미지 태그로 Git 커밋 해시를 사용하면, 어떤 코드 버전이 배포되었는지 추적할 수 있습니다. 문제 발생 시 이전 커밋의 이미지로 즉시 롤백이 가능합니다.
다음 장에서는 OS 지식이 실제로 쓰이는 순간들 — 리눅스 필수 명령어, 시스템 모니터링, 면접 주제를 정리하겠습니다.