컨테이너 오케스트레이션과 실무
Kubernetes의 Pod·Deployment·Service·HPA가 컨테이너 배포·복구·확장을 자동화하는 구조를 이해합니다.
컨테이너 하나를 실행하는 것은 간단합니다.
그러나 수백, 수천 개의 컨테이너를 여러 서버에 걸쳐 배포하고, 장애 시 자동 복구하고, 트래픽에 따라 확장하려면 오케스트레이션(Orchestration) 도구가 필요합니다.
Kubernetes는 이 문제를 해결하는 사실상의 표준입니다.
Kubernetes 아키텍처
Kubernetes(K8s)는 구글의 Borg 운영 경험을 바탕으로 개발하고 오픈소스로 공개한 컨테이너 오케스트레이션 시스템입니다.
| 구성 요소 | 역할 |
|---|---|
| API Server | 클러스터 제어 API의 진입점, kubectl과 통신 |
| etcd | 클러스터 상태를 저장하는 분산 키-값 저장소 |
| Scheduler | 새 Pod를 어떤 Node에 배치할지 결정 |
| Controller Manager | 원하는 상태와 현재 상태를 맞추는 컨트롤 루프 |
| kubelet | 각 Node에서 Pod를 실제로 실행·관리 |
| kube-proxy | Service의 가상 IP 전달 규칙을 구현 |
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의 선언적 관리를 담당합니다.
이 템플릿의 Pod 3개를 유지하라고 선언하면 컨트롤러가 현재 상태를 원하는 상태로 맞추도록 반복해서 동작합니다.
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 # 롤아웃으로 허용하는 unavailable Pod 수: 0
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.25-alpine
resources:
requests:
cpu: "250m"
ports:
- containerPort: 80컨테이너 실패는 kubelet이 재시작 정책에 따라 같은 Pod 안에서 복구할 수 있습니다. Deployment가 관리하는 Pod가 사라지면 ReplicaSet 컨트롤러가 새 UID의 대체 Pod를 만듭니다. 기존 Pod 자체가 다른 노드로 이동하는 것은 아닙니다.
이미지를 변경하면 롤링 업데이트로 점진적으로 교체합니다. 이 예산이 외부 장애 중 가용 Pod 3개를 보장하지는 않으며, 종료 중인 Pod까지 세면 일시적으로 4개를 넘을 수도 있습니다.
이전 리비전과 이미지가 남아 있으면 롤백을 요청할 수 있지만 복구 완료는 별도로 확인해야 합니다.
# 배포
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-deployment애플리케이션 시작, 트래픽 수신, 재시작의 조건을 나눕니다.
| 검사 | 실패했을 때 | 다른 검사와의 관계 |
|---|---|---|
| 시작 검사 | 설정된 실패 임계값에 이르면 컨테이너 종료·재시작 정책 적용 | startupProbe가 성공하기 전에는 readiness와 liveness 검사를 시작하지 않음 |
| 준비 검사 | Pod의 Ready 상태가 false가 되어 일반 Service 트래픽 대상에서 제외 | 검사 실패 자체로 컨테이너를 재시작하지 않음 |
| 생존 검사 | 실패 임계값에 이르면 컨테이너 종료·재시작 정책 적용 | 일시적 부하를 실패로 오판하지 않도록 임계값 설계 |
- 시작 검사
- 실패했을 때: 설정된 실패 임계값에 이르면 컨테이너 종료·재시작 정책 적용다른 검사와의 관계: startupProbe가 성공하기 전에는 readiness와 liveness 검사를 시작하지 않음
- 준비 검사
- 실패했을 때: Pod의 Ready 상태가 false가 되어 일반 Service 트래픽 대상에서 제외다른 검사와의 관계: 검사 실패 자체로 컨테이너를 재시작하지 않음
- 생존 검사
- 실패했을 때: 실패 임계값에 이르면 컨테이너 종료·재시작 정책 적용다른 검사와의 관계: 일시적 부하를 실패로 오판하지 않도록 임계값 설계
앞의 YAML에는 probe가 없습니다. 애플리케이션 응답 상태를 검사하려면 대상에 맞는 probe를 별도로 정의해야 합니다.
Service
Pod는 생성·소멸을 반복하므로 IP가 바뀝니다.
Service는 안정적인 네트워크 엔드포인트를 제공합니다.
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
type: ClusterIP
selector:
app: web
ports:
- port: 80
targetPort: 80- ClusterIP: 클러스터 내부에서 접근할 Service 주소를 제공합니다.
- NodePort: 노드 주소의 포트로 접근할 수 있게 합니다. 실제 접근은 방화벽·라우팅 정책에도 달려 있습니다.
- LoadBalancer: 구현 환경의 로드 밸런서를 요청합니다. 내부 또는 외부 노출 여부는 제공자 설정에 달려 있습니다.
- Ingress: HTTP/HTTPS 라우팅 규칙을 별도 리소스로 정의합니다.
Ingress Controller가 라우팅 규칙을 구현합니다. 또한 위 Service는 app: web 레이블을 선택하므로 같은 네임스페이스에 앞의 단독 web-pod도 만들었다면 그 Pod까지 대상이 될 수 있습니다.
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: 70여기서 70%는 Pod의 CPU request 대비 사용 비율을 평균한 목표입니다. Deployment에 CPU request를 설정했으며, 별도로 리소스 메트릭 API가 필요합니다. HPA는 누락 지표·허용 오차·안정화 정책도 반영하므로 임계값을 넘는 즉시 늘리거나 줄이는 단순 스위치는 아닙니다.
HPA가 관리하는 replicas와 수동 scale 또는 YAML 재적용으로 지정한 replicas는 충돌할 수 있으므로 확장 제어의 주체를 정해야 합니다.
컨테이너 네트워킹
Docker 네트워크
리눅스 Docker Engine의 기본 네트워크는 브리지 네트워크입니다.
각 컨테이너는 veth pair로 브리지에 연결됩니다.
기본 브리지의 외부 통신은 NAT 규칙을 이용합니다. 방화벽 백엔드는 구성에 따라 달라집니다.
Kubernetes 네트워크 모델
Kubernetes는 Pod 네트워크에 대해 세 가지 기본 원칙을 전제합니다.
- 모든 Pod는 NAT 없이 다른 Pod와 직접 통신할 수 있다
- 모든 Node는 NAT 없이 모든 Pod와 통신할 수 있다
- Pod가 보는 자기 IP = 다른 Pod가 보는 그 Pod의 IP
CNI(Container Network Interface) 플러그인 등이 Pod 네트워크를 구성합니다. 실제 통신은 NetworkPolicy 등으로 제한할 수 있으며, 일부 구현은 kube-proxy의 기능도 대체합니다.
오버레이(VXLAN 등), 노드 간 라우팅(BGP 등), eBPF 데이터 평면처럼 구현 방식이 다릅니다. Flannel·Calico·Cilium 등이 예이며, Weave도 메시 오버레이 사례로 사용되어 왔습니다. 제품 이름만으로 성능이나 적합한 클러스터 규모를 단정하지 않고 지원 상태와 구성 기능을 확인합니다.
서버리스와 마이크로 VM
서버리스(Serverless)는 개발자가 서버를 관리하지 않고 함수 단위로 코드를 배포하는 모델입니다.
AWS Lambda, Google Cloud Functions, Azure Functions가 대표적입니다.
서버리스라고 서버가 없는 것은 아닙니다.
내부적으로는 컨테이너나 마이크로 VM이 함수 실행 환경을 제공합니다.
AWS Lambda는 Firecracker라는 경량 VM을 사용합니다.
Firecracker는 KVM 기반 마이크로 VM으로 게스트 커널을 별도로 실행하면서 가상 장치 구성을 줄입니다. virtio-net·virtio-block 같은 제한된 장치를 제공하며, 시작 시간과 메모리 비용은 게스트 이미지·호스트·측정 구간에 따라 달라집니다.
서버리스의 가장 큰 도전 과제는 콜드 스타트(Cold Start)입니다.
요청이 없던 함수가 호출되면, 실행 환경을 새로 준비하는 데 수백 ms ~ 수 초가 걸립니다.
인프라 선택 기준
성능 서열 대신 필요한 제어와 운영 책임을 비교합니다.
| 환경 | 직접 확인할 범위 | 선택 질문 |
|---|---|---|
| 베어메탈 | 물리 장비·OS·애플리케이션 | GPU·HPC의 장치 접근과 하드웨어 제어가 필요한가? |
| 가상 머신 | 게스트 OS·가상 자원·애플리케이션 | 게스트 커널 경계와 운영체제 제어가 필요한가? |
| 컨테이너 | 이미지·실행 정책·오케스트레이션 | 공유 커널 전제에서 배포 단위를 표준화할 수 있는가? |
| 서버리스 | 함수 코드·권한·서비스 제약 | 이벤트 모델과 실행·동시성 제한에 작업이 맞는가? |
- 베어메탈
- 직접 확인할 범위: 물리 장비·OS·애플리케이션선택 질문: GPU·HPC의 장치 접근과 하드웨어 제어가 필요한가?
- 가상 머신
- 직접 확인할 범위: 게스트 OS·가상 자원·애플리케이션선택 질문: 게스트 커널 경계와 운영체제 제어가 필요한가?
- 컨테이너
- 직접 확인할 범위: 이미지·실행 정책·오케스트레이션선택 질문: 공유 커널 전제에서 배포 단위를 표준화할 수 있는가?
- 서버리스
- 직접 확인할 범위: 함수 코드·권한·서비스 제약선택 질문: 이벤트 모델과 실행·동시성 제한에 작업이 맞는가?
관리형 서비스 사용 여부에 따라 운영 책임이 달라집니다. 한 시스템 안에서도 서로 다른 환경을 함께 사용할 수 있습니다.
데이터베이스는 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 (범용)"위 함수는 입력 키가 갖춰졌다고 가정한 의사결정 예제입니다. 조건 검사 순서가 추천을 결정하며, 5분은 여기서 정한 기준이지 특정 서버리스 서비스의 실행 시간 한도가 아닙니다.
CI/CD와 컨테이너
컨테이너는 CI/CD(지속적 통합/배포) 파이프라인과 자연스럽게 결합됩니다.
# 1. 푸시할 이름으로 이미지 빌드
docker build -t registry.example.com/myapp:${GIT_SHA} .
# 2. 보안 스캐닝
trivy image registry.example.com/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 커밋 해시를 사용하면, 어떤 코드 버전이 배포되었는지 추적할 수 있습니다.
태그는 덮어쓸 수 있으므로 내용 고정이 필요하면 이미지 digest를 사용합니다. 파이프라인은 레지스트리 인증, 기존 myapp Deployment, 사용할 도구를 전제합니다. 롤백은 이전 이미지의 보존과 데이터 호환성까지 확인해야 합니다.
다음 장에서는 OS 지식이 실제로 쓰이는 순간들 — 리눅스 필수 명령어, 시스템 모니터링, 면접 주제를 정리하겠습니다.