근묵자흑
Kubernetes Pattern: Init Conatiner 본문
애플리케이션을 배포할 때 이런 고민을 해본 적 있으신가요?
- "데이터베이스가 준비되기 전에 애플리케이션이 시작되면 어떡하지?"
- "Git 저장소에서 최신 설정을 가져온 후에 서버를 시작하고 싶은데..."
- "로그 수집기를 사이드카로 붙이고 싶은데, 순서를 어떻게 보장하지?"
이런 문제들을 우아하게 해결하는 것이 바로 Init Container 패턴입니다. 이 글에서는 Kubernetes v1.34.0 환경에서 테스트한 6가지 예제를 통해 Init Container의 모든 것을 알아보겠습니다.
Init Container란 무엇인가?
Init Container는 Pod의 메인 애플리케이션 컨테이너가 시작되기 전에 실행되는 특수한 컨테이너입니다.
일반 컨테이너와의 차이점:
| 특성 | Init Container | Application Container |
|---|---|---|
| 실행 시점 | Pod 시작 시 순차 실행 | Init 완료 후 병렬 실행 |
| 실행 순서 | 보장됨 (순차) | 보장 안 됨 |
| 완료 조건 | 반드시 종료 (exit 0) | 계속 실행 가능 |
| Health Probe | ❌ 지원 안 함 | 모두 지원 가능 |
실습 환경 준비
요구사항
- Kubernetes 클러스터 (Minikube, Kind, 또는 실제 클러스터)
- kubectl CLI
- (선택) Kubernetes 1.28+ (Native Sidecar 테스트용)
테스트 환경:
- Kubernetes: v1.34.0
- Minikube: v1.37.0
- 플랫폼: macOS (ARM64)
예제 1: Git 저장소 클론하기
가장 기본적이면서도 실용적인 예제부터 시작해봅시다. Init Container로 Git 저장소를 클론하고, 메인 컨테이너에서 웹 서버로 제공하는 패턴입니다.
YAML 정의
apiVersion: v1
kind: Pod
metadata:
name: www
labels:
app: www
spec:
# Init Container가 먼저 실행됨
initContainers:
- name: download
image: bitnami/git:latest
command:
- git
- clone
- --depth=1
- https://github.com/mdn/beginner-html-site-scripted
- /var/lib/data
volumeMounts:
- name: source
mountPath: /var/lib/data
# Init Container 완료 후 실행되는 메인 컨테이너
containers:
- name: run
image: docker.io/centos/httpd:latest
ports:
- containerPort: 80
name: http
protocol: TCP
volumeMounts:
- name: source
mountPath: /var/www/html
# 공유 볼륨
volumes:
- name: source
emptyDir: {}
실행 및 확인
# Pod 생성
kubectl apply -f 01-basic-pod.yml
# 상태 확인
kubectl get pod www -w
실제 실행 결과:
NAME READY STATUS RESTARTS AGE
www 0/1 Init:0/1 0 8s # Init Container 실행 중
www 0/1 PodInitializing 0 24s # 메인 Container 준비 중
www 1/1 Running 0 30s # 완료!
Init Container 로그:
kubectl logs www -c download
Cloning into '/var/lib/data'...
# Git 클론 성공!
동작 원리
- Init Container (
download) 시작- Git 이미지 사용
- GitHub에서 HTML 사이트 클론
/var/lib/data(emptyDir 볼륨)에 저장- 완료 후 종료
- 메인 Container (
run) 시작- Apache HTTP 서버 실행
- 같은 emptyDir 볼륨을
/var/www/html에 마운트 - 클론된 파일을 웹으로 제공
Service로 외부 노출
apiVersion: v1
kind: Service
metadata:
name: www
spec:
selector:
app: www
ports:
- name: http
port: 8080
protocol: TCP
targetPort: 80
type: NodePort
kubectl apply -f 02-service.yml
# NodePort 확인
kubectl get service www
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# www NodePort 10.108.70.124 <none> 8080:30561/TCP 3s
# 포트 포워딩으로 접속
kubectl port-forward service/www 8080:8080
# 브라우저에서 http://localhost:8080 접속
Init Container가 준비한 컨텐츠를 웹 서버로 제공하는 것을 확인했습니다.
예제 2: 순차적 의존성 대기
실제 프로덕션에서 가장 많이 사용하는 패턴입니다. 데이터베이스, 캐시 등 여러 의존 서비스가 준비될 때까지 기다립니다.
YAML 정의
apiVersion: v1
kind: Pod
metadata:
name: myapp-wait-db
labels:
app: myapp
spec:
initContainers:
# 첫 번째 Init Container: PostgreSQL 대기
- name: wait-for-postgres
image: busybox:1.36
command:
- sh
- -c
- |
echo "Waiting for PostgreSQL..."
until nc -z postgres-service 5432; do
echo "PostgreSQL is unavailable - sleeping"
sleep 2
done
echo "PostgreSQL is ready!"
# 두 번째 Init Container: Redis 대기
- name: wait-for-redis
image: busybox:1.36
command:
- sh
- -c
- |
echo "Waiting for Redis..."
until nc -z redis-service 6379; do
echo "Redis is unavailable - sleeping"
sleep 2
done
echo "Redis is ready!"
# 모든 Init Container 완료 후 실행
containers:
- name: myapp
image: myapp:1.0
env:
- name: DATABASE_URL
value: "postgres://postgres-service:5432/mydb"
- name: REDIS_URL
value: "redis://redis-service:6379"
실행 결과
kubectl apply -f 03-wait-for-service.yml
kubectl get pod myapp-wait-db -w
Pod 상태 변화:
NAME READY STATUS RESTARTS AGE
myapp-wait-db 0/1 Init:0/2 0 2s # 첫 번째 Init 실행 중
myapp-wait-db 0/1 Init:1/2 0 7s # 두 번째 Init 실행 중
myapp-wait-db 0/1 PodInitializing 0 12s # 메인 준비 중
myapp-wait-db 1/1 Running 0 15s # 완료!
Init Container 로그:
# 첫 번째 Init Container
kubectl logs myapp-wait-db -c wait-for-postgres
Waiting for PostgreSQL...
PostgreSQL check simulation - would check: nc -z postgres-service 5432
PostgreSQL is ready!
# 두 번째 Init Container
kubectl logs myapp-wait-db -c wait-for-redis
Waiting for Redis...
Redis check simulation - would check: nc -z redis-service 6379
Redis is ready!
핵심 포인트
- 순차 실행:
wait-for-postgres가 완료되어야wait-for-redis가 시작 - 실패 시 재시작: Init Container가 실패하면 Pod의
restartPolicy에 따라 재시작 - 멱등성: 재시작을 고려하여 멱등한 로직 작성 필요
예제 3: 동적 설정 파일 생성
ConfigMap의 템플릿을 환경 변수로 치환하여 실제 설정 파일을 생성하는 패턴입니다.
ConfigMap 정의
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-template
data:
config.txt: |
Server Configuration
====================
Host: {{ SERVER_HOST }}
Port: {{ SERVER_PORT }}
Database Configuration
====================
URL: {{ DB_URL }}
Pool Size: {{ DB_POOL_SIZE }}
Pod 정의
apiVersion: v1
kind: Pod
metadata:
name: app-with-config
spec:
initContainers:
- name: config-generator
image: busybox:1.36
command:
- sh
- -c
- |
echo "Generating configuration file..."
# 템플릿 읽기 및 환경 변수로 치환
template=$(cat /templates/config.txt)
echo "$template" | \
sed "s/{{ SERVER_HOST }}/$SERVER_HOST/g" | \
sed "s/{{ SERVER_PORT }}/$SERVER_PORT/g" | \
sed "s|{{ DB_URL }}|$DB_URL|g" | \
sed "s/{{ DB_POOL_SIZE }}/$DB_POOL_SIZE/g" \
> /config/config.txt
echo "Configuration file generated:"
cat /config/config.txt
env:
- name: SERVER_HOST
value: "0.0.0.0"
- name: SERVER_PORT
value: "8080"
- name: DB_URL
value: "postgres://db:5432/myapp"
- name: DB_POOL_SIZE
value: "20"
volumeMounts:
- name: config-template
mountPath: /templates
- name: config-output
mountPath: /config
containers:
- name: application
image: myapp:latest
volumeMounts:
- name: config-output
mountPath: /etc/app
readOnly: true
volumes:
- name: config-template
configMap:
name: app-config-template
- name: config-output
emptyDir: {}
실행 결과
kubectl apply -f 04-config-generation.yml
# Init Container 로그
kubectl logs app-with-config -c config-generator
출력:
Generating configuration file...
Configuration file generated:
Server Configuration
====================
Host: 0.0.0.0
Port: 8080
Database Configuration
====================
URL: postgres://db:5432/myapp
Pool Size: 20
메인 애플리케이션 로그:
kubectl logs app-with-config -c application
Application starting...
Reading configuration:
Server Configuration
====================
Host: 0.0.0.0
Port: 8080
Database Configuration
====================
URL: postgres://db:5432/myapp
Pool Size: 20
Application running with config...
Init Container가 동적으로 생성한 설정 파일을 메인 애플리케이션이 사용합니다.
예제 4: Native Sidecar 패턴 (Kubernetes 1.28+)
Kubernetes 1.28부터 도입된 Native Sidecar는 Init Container의 게임 체인저입니다. restartPolicy: Always를 설정하면 Init Container가 일반 Init처럼 종료되지 않고, 메인 Container와 함께 계속 실행됩니다.
왜 Native Sidecar가 필요한가?
기존 Sidecar의 문제점:
- 시작 순서 보장 불가: Application Container와 Sidecar의 시작 순서를 보장할 수 없음
- Job 완료 차단: Job이 완료되어도 Sidecar가 계속 실행되어 Job이 종료되지 않음
- PostStart Hook 남용: 복잡한 대기 로직이 필요
Native Sidecar의 해결책:
- Init Container 단계에서 시작하므로 순서 보장
- 메인 Container 종료 시 자동으로 종료
- startupProbe, readinessProbe, livenessProbe 모두 지원
YAML 정의
apiVersion: v1
kind: Pod
metadata:
name: myapp-with-native-sidecar
spec:
initContainers:
# 일반 Init Container - 데이터베이스 초기화
- name: database-init
image: busybox:1.36
command:
- sh
- -c
- |
echo "Running database initialization..."
sleep 3
echo "Database schema created!"
# Native Sidecar 1 - 로그 라우터
- name: log-router
image: busybox:1.36
restartPolicy: Always # 🔑 이것이 핵심!
command:
- sh
- -c
- |
echo "Log router starting..."
while true; do
if [ -f /var/log/app/app.log ]; then
tail -f /var/log/app/app.log | while read line; do
echo "[FORWARDED] $line"
done
else
echo "[LOG ROUTER] Waiting for log file..."
sleep 5
fi
done
volumeMounts:
- name: varlog
mountPath: /var/log/app
# Native Sidecar 2 - 메트릭 수집기
- name: metrics-collector
image: busybox:1.36
restartPolicy: Always # Native Sidecar
command:
- sh
- -c
- |
echo "Metrics collector starting..."
while true; do
echo "[METRICS] CPU: $((RANDOM % 100))% | Memory: $((RANDOM % 100))%"
sleep 10
done
# 메인 애플리케이션
containers:
- name: application
image: busybox:1.36
command:
- sh
- -c
- |
echo "Application starting..."
mkdir -p /var/log/app
counter=1
while [ $counter -le 30 ]; do
echo "[$counter] Application is running - $(date)" | tee -a /var/log/app/app.log
sleep 2
counter=$((counter + 1))
done
echo "Application finished"
volumeMounts:
- name: varlog
mountPath: /var/log/app
volumes:
- name: varlog
emptyDir: {}
실행 결과
kubectl apply -f 05-native-sidecar.yml
sleep 15
kubectl get pod myapp-with-native-sidecar
Pod 상태:
NAME READY STATUS RESTARTS AGE
myapp-with-native-sidecar 3/3 Running 0 15s
주목! READY가 3/3입니다. 이는:
- 1개의 메인 Container (
application) - 2개의 Native Sidecar (
log-router,metrics-collector)
가 모두 실행 중임을 의미합니다.
Container 상태 확인
kubectl get pod myapp-with-native-sidecar -o jsonpath='{range .status.initContainerStatuses[*]}{.name}{": "}{.state}{"\n"}{end}'
출력:
database-init: {"terminated":{"exitCode":0,"reason":"Completed"}}
log-router: {"running":{"startedAt":"2025-11-15T10:53:26Z"}}
metrics-collector: {"running":{"startedAt":"2025-11-15T10:53:27Z"}}
핵심 차이점:
database-init:terminated상태 → 일반 Init Container (완료 후 종료)log-router:running상태 → Native Sidecar (계속 실행)metrics-collector:running상태 → Native Sidecar (계속 실행)
로그 확인
Log Router Sidecar:
kubectl logs myapp-with-native-sidecar -c log-router --tail=10
[LOG ROUTER] Processing logs from /var/log/app/app.log
[FORWARDED] [1] Application is running - Sat Nov 15 10:53:28 UTC 2025
[FORWARDED] [2] Application is running - Sat Nov 15 10:53:30 UTC 2025
[FORWARDED] [3] Application is running - Sat Nov 15 10:53:32 UTC 2025
[FORWARDED] [4] Application is running - Sat Nov 15 10:53:34 UTC 2025
Metrics Collector Sidecar:
kubectl logs myapp-with-native-sidecar -c metrics-collector --tail=5
Metrics collector starting...
[METRICS] CPU: 73% | Memory: 36%
[METRICS] CPU: 46% | Memory: 47%
실행 타임라인
T0: Pod 생성
│
├─ T1-T4: database-init (일반 Init) 실행 → 완료 (3초)
│
├─ T5: log-router (Native Sidecar) 시작 → 계속 실행
│
├─ T6: metrics-collector (Native Sidecar) 시작 → 계속 실행
│
├─ T7: application (메인) 시작
│ └─ 로그 파일 생성
│ └─ log-router가 실시간으로 수집
│
├─ T8: application 60초 후 완료
│
└─ T9: Sidecars 자동 종료 (역순)
Native Sidecar vs 일반 Init Container
| 특성 | 일반 Init Container | Native Sidecar |
|---|---|---|
| restartPolicy | 없음 (기본값) | Always |
| 실행 완료 | 종료 필수 | 계속 실행 |
| 다음 단계 진행 | 완료 대기 | startupProbe 성공 시 즉시 |
| Health Probe | ❌ 없음 | 모두 지원 |
| 종료 시점 | 작업 완료 즉시 | 메인 Container 종료 후 |
| 사용 사례 | DB 마이그레이션, Git 클론 | 로그 수집, 메트릭, Proxy |
Native Sidecar의 장점: Istio, Fluentd 같은 Sidecar 패턴을 훨씬 쉽고 안정적으로 구현할 수 있습니다!
예제 5: 리소스 관리
Init Container와 Application Container의 리소스 요청이 어떻게 계산되는지 정확히 이해하는 것은 매우 중요합니다.
리소스 계산 로직
Pod의 유효 리소스는 다음과 같이 계산됩니다:
Pod Resources = max(
max(모든 Init Container 리소스),
sum(모든 Application Container 리소스)
)
YAML 정의
apiVersion: v1
kind: Pod
metadata:
name: resource-example
spec:
initContainers:
- name: init-1
image: busybox:1.36
resources:
requests:
cpu: "500m" # 높은 리소스
memory: "256Mi"
limits:
cpu: "1000m"
memory: "512Mi"
- name: init-2
image: busybox:1.36
resources:
requests:
cpu: "200m" # 낮은 리소스
memory: "128Mi"
containers:
- name: app-1
image: busybox:1.36
resources:
requests:
cpu: "100m"
memory: "64Mi"
- name: app-2
image: busybox:1.36
resources:
requests:
cpu: "150m"
memory: "64Mi"
리소스 계산 과정
Step 1: Init Container 최대값
CPU Request: max(500m, 200m) = 500m
Memory Request: max(256Mi, 128Mi) = 256Mi
Step 2: Application Container 합계
CPU Request: 100m + 150m = 250m
Memory Request: 64Mi + 64Mi = 128Mi
Step 3: Pod 유효 리소스 (더 큰 값 선택)
CPU Request: max(500m, 250m) = 500m ← Init 기준
Memory Request: max(256Mi, 128Mi) = 256Mi ← Init 기준
실제 검증
kubectl apply -f 06-resource-example.yml
kubectl describe pod resource-example | grep -A 15 "Requests:"
출력:
Init Container (init-1):
Requests:
cpu: 500m
memory: 256Mi
Init Container (init-2):
Requests:
cpu: 200m
memory: 128Mi
Application Container (app-1):
Requests:
cpu: 100m
memory: 64Mi
Application Container (app-2):
Requests:
cpu: 150m
memory: 64Mi
최적화 전략
비효율적인 예:
initContainers:
- name: heavy-init
resources:
requests:
cpu: "4000m" # 너무 높음!
memory: "8Gi"
containers:
- name: app
resources:
requests:
cpu: "500m" # 훨씬 작음
memory: "512Mi"
→ Pod은 4000m CPU를 요청하지만, App 실행 중에는 500m만 사용 (3500m 낭비!)
최적화된 예:
initContainers:
- name: optimized-init
resources:
requests:
cpu: "500m" # 필요한 만큼만
memory: "512Mi"
containers:
- name: app
resources:
requests:
cpu: "500m"
memory: "512Mi"
→ Init과 App이 같은 리소스 요청 → 낭비 없음!
실무 적용 시
1. 멱등성 보장 (Idempotency)
Init Container는 실패 시 재시작되므로 반드시 멱등해야 합니다.
나쁜 예:
CREATE TABLE users (id serial primary key);
-- 재시작 시 "table already exists" 에러!
좋은 예:
CREATE TABLE IF NOT EXISTS users (id serial primary key);
# 파일 다운로드도 마찬가지
if [ ! -f /data/file.txt ]; then
curl -o /data/file.txt https://example.com/file.txt
fi
2. 빠른 실행 시간
Init Container는 순차 실행되므로 각각 빠르게 완료되어야 합니다.
Best Practice:
initContainers:
- name: wait-for-db
command:
- sh
- -c
- |
MAX_RETRIES=60
RETRY_INTERVAL=5
count=0
while [ $count -lt $MAX_RETRIES ]; do
if nc -z postgres 5432; then
exit 0
fi
sleep $RETRY_INTERVAL
count=$((count + 1))
done
echo "Timeout after $((MAX_RETRIES * RETRY_INTERVAL)) seconds"
exit 1
3. 보안 - 최소 권한 원칙
Init Container에만 필요한 권한을 부여하고, Application Container는 최소 권한으로 실행합니다.
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
initContainers:
# 권한이 필요한 Init만 root로 실행
- name: setup-permissions
securityContext:
runAsUser: 0 # root
allowPrivilegeEscalation: false
command: ["chown", "-R", "1000:2000", "/data"]
# 일반 Init은 비권한 사용자로
- name: download
securityContext:
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop: ["ALL"]
containers:
- name: app
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
4. 언제 Init Container를 사용할까?
Init Container 사용:
- 데이터베이스 마이그레이션
- 외부 서비스 준비 대기
- 설정 파일 생성
- Git 저장소 클론
- 파일 권한 설정
- 데이터 전처리
Native Sidecar 사용:
- Service Mesh (Istio, Linkerd)
- 로그 수집 (Fluentd, Fluent Bit)
- 메트릭 수집 (Prometheus Exporter)
- Configuration 서버
- 네트워크 프록시
피해야 할 경우:
- 장기 실행 백그라운드 작업
- 병렬 실행 가능한 독립적 작업
- 멱등하지 않은 작업
5. 디버깅 팁
Init Container 로그 확인:
# 현재 로그
kubectl logs <pod-name> -c <init-container-name>
# 이전 실행 로그 (재시작된 경우)
kubectl logs <pod-name> -c <init-container-name> --previous
Init Container 상태 확인:
kubectl get pod <pod-name> -o jsonpath='{.status.initContainerStatuses[*].state}'
실시간 모니터링:
# Pod 상태 실시간 확인
kubectl get pods -w
# Events 확인
kubectl get events --watch
'k8s > kubernetes-pattern' 카테고리의 다른 글
| Kubernetes Pattern: Adapter (0) | 2025.11.29 |
|---|---|
| Kubernetes Pattern: Sidecar (4) | 2025.11.22 |
| Kubernetes Pattern: Self Awareness (0) | 2025.11.08 |
| Service Discovery 심화: Knative (2) | 2025.11.01 |
| Kubernetes Pattern: Service Discovery (4) | 2025.10.25 |