Notice
Recent Posts
Recent Comments
Link
«   2025/12   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Archives
Today
Total
관리 메뉴

근묵자흑

Kubernetes Pattern: Init Conatiner 본문

k8s/kubernetes-pattern

Kubernetes Pattern: Init Conatiner

Luuuuu 2025. 11. 15. 20:08

 

애플리케이션을 배포할 때 이런 고민을 해본 적 있으신가요?

  • "데이터베이스가 준비되기 전에 애플리케이션이 시작되면 어떡하지?"
  • "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 클론 성공!

동작 원리

  1. Init Container (download) 시작
    • Git 이미지 사용
    • GitHub에서 HTML 사이트 클론
    • /var/lib/data (emptyDir 볼륨)에 저장
    • 완료 후 종료
  2. 메인 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!

핵심 포인트

  1. 순차 실행: wait-for-postgres가 완료되어야 wait-for-redis가 시작
  2. 실패 시 재시작: Init Container가 실패하면 Pod의 restartPolicy에 따라 재시작
  3. 멱등성: 재시작을 고려하여 멱등한 로직 작성 필요

예제 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의 문제점:

  1. 시작 순서 보장 불가: Application Container와 Sidecar의 시작 순서를 보장할 수 없음
  2. Job 완료 차단: Job이 완료되어도 Sidecar가 계속 실행되어 Job이 종료되지 않음
  3. 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