멀티 컨테이너 패턴의 필요성
Kubernetes Pod는 하나 이상의 컨테이너를 포함할 수 있습니다. 단일 컨테이너로 모든 책임을 처리하면 이미지가 비대해지고, 관심사 분리가 불가능합니다. Init Container는 메인 컨테이너 시작 전 초기화 작업을, Sidecar Container는 메인 컨테이너와 함께 실행되며 보조 기능을 담당합니다. 이 두 패턴을 조합하면 마이크로서비스의 횡단 관심사를 깔끔하게 분리할 수 있습니다.
Init Container 기본 개념
Init Container는 Pod의 spec.initContainers에 정의하며, 메인 컨테이너보다 먼저 순차적으로 실행됩니다. 모든 Init Container가 성공해야 메인 컨테이너가 시작됩니다.
apiVersion: v1
kind: Pod
metadata:
name: app-with-init
spec:
initContainers:
- name: wait-for-db
image: busybox:1.36
command:
- sh
- -c
- |
until nc -z postgres-svc 5432; do
echo "DB 대기 중..."
sleep 2
done
echo "DB 연결 확인 완료"
- name: run-migrations
image: myapp:latest
command: ["npx", "typeorm", "migration:run"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-credentials
key: url
containers:
- name: app
image: myapp:latest
ports:
- containerPort: 3000
이 예시에서 두 Init Container는 순서대로 실행됩니다:
- wait-for-db: PostgreSQL이 준비될 때까지 대기
- run-migrations: DB 마이그레이션 실행
둘 다 성공한 후에야 메인 app 컨테이너가 시작됩니다.
Init Container 실전 패턴
패턴 1: 설정 파일 다운로드
Vault, S3, Git 등에서 설정 파일을 가져와 emptyDir 볼륨에 저장합니다:
initContainers:
- name: fetch-config
image: vault:1.15
command:
- sh
- -c
- |
vault kv get -format=json secret/myapp/config
| jq -r '.data.data' > /config/app-config.json
env:
- name: VAULT_ADDR
value: "http://vault.infra:8200"
- name: VAULT_TOKEN
valueFrom:
secretKeyRef:
name: vault-token
key: token
volumeMounts:
- name: config-volume
mountPath: /config
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: config-volume
mountPath: /app/config
readOnly: true
volumes:
- name: config-volume
emptyDir: {}
패턴 2: 스키마 검증
DB 스키마가 앱 버전과 호환되는지 사전 검증합니다:
initContainers:
- name: schema-check
image: myapp:latest
command:
- sh
- -c
- |
PENDING=$(npx typeorm migration:show | grep -c "\[ \]")
if [ "$PENDING" -gt 0 ]; then
echo "미적용 마이그레이션 ${PENDING}개 발견"
exit 1 # Pod 시작 차단
fi
echo "스키마 정합성 확인 완료"
패턴 3: 의존 서비스 헬스체크
Redis, Kafka 등 의존 서비스가 모두 준비될 때까지 대기합니다:
initContainers:
- name: wait-for-dependencies
image: busybox:1.36
command:
- sh
- -c
- |
services="redis-svc:6379 kafka-svc:9092 elasticsearch-svc:9200"
for svc in $services; do
host=$(echo $svc | cut -d: -f1)
port=$(echo $svc | cut -d: -f2)
until nc -z $host $port; do
echo "$host:$port 대기 중..."
sleep 3
done
echo "$host:$port 준비 완료"
done
K8s 1.28+ 네이티브 Sidecar
Kubernetes 1.28부터 네이티브 사이드카가 정식 지원됩니다. 기존에는 사이드카를 일반 컨테이너로 정의했지만, 이제 initContainers에 restartPolicy: Always를 지정하면 Init 단계에서 시작되어 Pod 전체 수명 동안 실행됩니다:
apiVersion: v1
kind: Pod
metadata:
name: app-with-native-sidecar
spec:
initContainers:
# 네이티브 사이드카 — Pod 수명 동안 계속 실행
- name: log-collector
image: fluent/fluent-bit:2.2
restartPolicy: Always # 핵심! 이것이 네이티브 사이드카
volumeMounts:
- name: log-volume
mountPath: /var/log/app
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mi
# 일반 Init Container — 완료 후 종료
- name: run-migrations
image: myapp:latest
command: ["npx", "typeorm", "migration:run"]
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: log-volume
mountPath: /var/log/app
volumes:
- name: log-volume
emptyDir: {}
네이티브 사이드카의 장점:
| 기존 방식 | 네이티브 사이드카 (1.28+) |
|---|---|
| 메인 컨테이너와 동시 시작 | Init 단계에서 먼저 시작 — 의존성 보장 |
| Job 완료 후에도 사이드카 남아있음 | 메인 컨테이너 종료 시 자동 정리 |
| 종료 순서 제어 불가 | 메인 → 사이드카 순서로 graceful shutdown |
| preStop 훅 워크어라운드 필요 | 라이프사이클 자동 관리 |
Sidecar 실전 패턴
패턴 1: Envoy 프록시 사이드카
서비스 메시 없이도 Envoy를 사이드카로 배치하여 mTLS, 리트라이, 서킷 브레이커를 구현합니다:
initContainers:
- name: envoy-proxy
image: envoyproxy/envoy:v1.29
restartPolicy: Always
ports:
- containerPort: 9901 # admin
volumeMounts:
- name: envoy-config
mountPath: /etc/envoy
resources:
requests:
cpu: 100m
memory: 128Mi
containers:
- name: app
image: myapp:latest
env:
- name: HTTP_PROXY
value: "http://localhost:10000" # Envoy를 통해 아웃바운드
volumes:
- name: envoy-config
configMap:
name: envoy-sidecar-config
패턴 2: CloudSQL Auth Proxy
GCP Cloud SQL에 접근할 때 가장 흔히 사용하는 사이드카 패턴입니다:
initContainers:
- name: cloud-sql-proxy
image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.8
restartPolicy: Always
args:
- "--structured-logs"
- "--port=5432"
- "myproject:us-central1:mydb"
securityContext:
runAsNonRoot: true
resources:
requests:
cpu: 50m
memory: 64Mi
containers:
- name: app
image: myapp:latest
env:
- name: DATABASE_HOST
value: "localhost" # 사이드카가 로컬에서 프록시
- name: DATABASE_PORT
value: "5432"
패턴 3: 로그 수집 + 메트릭 내보내기
앱이 파일로 로그를 쓰면, Fluent Bit 사이드카가 수집하여 구조화된 JSON 로그를 Elasticsearch로 전송합니다:
initContainers:
- name: fluent-bit
image: fluent/fluent-bit:2.2
restartPolicy: Always
volumeMounts:
- name: log-volume
mountPath: /var/log/app
- name: fluent-config
mountPath: /fluent-bit/etc
env:
- name: ES_HOST
value: "elasticsearch.logging:9200"
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: log-volume
mountPath: /var/log/app
volumes:
- name: log-volume
emptyDir: {}
- name: fluent-config
configMap:
name: fluent-bit-sidecar-config
리소스 관리 전략
사이드카는 메인 컨테이너의 리소스를 잠식할 수 있으므로, ResourceQuota/LimitRange와 함께 세밀하게 관리해야 합니다:
# Pod 전체 리소스 = 메인 + 모든 사이드카 합산
# HPA는 Pod 단위로 스케일하므로 사이드카 리소스도 고려
# 리소스 최적화 예시
initContainers:
- name: envoy-proxy
restartPolicy: Always
resources:
requests:
cpu: 50m # 사이드카는 최소한으로
memory: 64Mi
limits:
cpu: 200m # 버스트 허용
memory: 128Mi
containers:
- name: app
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
주의사항:
- HPA 계산: Pod의 CPU/메모리 사용량에 사이드카 리소스가 포함됨. targetAverageUtilization 설정 시 이를 고려
- QoS 클래스: 모든 컨테이너(사이드카 포함)에 requests=limits를 설정해야 Guaranteed QoS 확보
- Init Container 리소스: Init Container의 리소스는 메인 컨테이너와 별도로 계산됨. 둘 중 더 큰 값이 Pod의 effective request
보안 강화: SecurityContext
사이드카도 최소 권한 원칙을 적용해야 합니다:
initContainers:
- name: log-collector
restartPolicy: Always
securityContext:
runAsNonRoot: true
runAsUser: 65534 # nobody
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
volumeMounts:
- name: log-volume
mountPath: /var/log/app
readOnly: true # 읽기 전용으로 마운트
- name: tmp
mountPath: /tmp # 쓰기 필요 시 별도 emptyDir
트러블슈팅 가이드
| 증상 | 원인 | 해결 |
|---|---|---|
| Pod가 Init:CrashLoopBackOff | Init Container 스크립트 오류 | kubectl logs pod -c init-container-name |
| Pod가 Init:0/2 에서 멈춤 | 첫 번째 Init이 완료되지 않음 | 의존 서비스 상태 확인, timeout 추가 |
| Job이 완료되지 않음 | 기존 방식 사이드카가 종료 안 됨 | 네이티브 사이드카(restartPolicy: Always) 전환 |
| 사이드카 OOMKilled | 메모리 limit 부족 | 리소스 모니터링 후 limit 조정 |
| emptyDir 공간 부족 | 로그/임시파일 누적 | sizeLimit 설정 및 로테이션 추가 |
# 디버깅 명령어
kubectl describe pod app-with-init # Init 상태 확인
kubectl logs app-with-init -c wait-for-db # Init Container 로그
kubectl logs app-with-init -c log-collector # 사이드카 로그
kubectl get pod app-with-init -o jsonpath='{.status.initContainerStatuses}'
정리
Init Container와 Sidecar는 단일 책임 원칙을 Pod 레벨로 확장하는 패턴입니다. Init으로 사전 조건을 보장하고, Sidecar로 횡단 관심사를 분리하면 메인 애플리케이션은 비즈니스 로직에만 집중할 수 있습니다. K8s 1.28의 네이티브 사이드카는 Job 환경에서의 종료 문제를 해결하고, 시작 순서를 보장하여 프로덕션 안정성을 크게 높입니다.