[쿠버네티스 패턴] 15장 Init Container 패턴

bocopile·2025년 11월 14일

쿠버네티스 패턴

목록 보기
13/28

쿠버네티스에서 애플리케이션을 올리다 보면 이런 상황, 한 번쯤 겪습니다.

  • “DB가 먼저 떠 있어야 서비스가 제대로 뜨는데…”
  • “공유 볼륨에 초기 데이터가 채워지기 전에 트래픽이 들어오면 안 되는데…”
  • “Config Server에서 설정을 못 가져오면 그냥 죽어야 할까, 재시도해야 할까?”

이런 “시작 전에 꼭 해야 하는 준비 작업”을 애플리케이션 코드에 섞어두면 점점 코드가 지저분해지고, 실패·재시작·롤백 정책이 꼬이기 시작합니다.

이 글에서는 이런 문제를 Pod 수준에서 깔끔하게 해결해주는 Init Container 패턴을 정리하고,

추가로 Kubernetes v1.28 ~ v1.34 기준 최신 기능(Native Sidecar, 컨테이너별 restartPolicy 등)까지 한 번에 묶어서 살펴봅니다.


1. Init Container란? (정의와 배경)

1-1. 한 줄 정의

Init Container 패턴은 "애플리케이션 컨테이너가 시작되기 전에 반드시 수행해야 하는 준비 작업을
별도의 초기화 컨테이너로 분리해 실행 순서를 보장하는 패턴”입니다.

Pod spec의 initContainers 섹션에 준비 작업을 담당하는 컨테이너들을 정의해두고,

이 컨테이너들이 모두 성공해야 비로소 containers 섹션의 메인 애플리케이션 컨테이너가 실행됩니다.

즉,

  • 준비 작업 → Init Container
  • 실제 서비스 트래픽 처리 → 메인 컨테이너

로 역할을 분리하는 패턴입니다.

1-2. 왜 필요한가? (배경)

클라우드 네이티브 애플리케이션은 흔히 “stateless”라고 부르지만,
실제로는 시작 시점에 여러 전제 조건이 필요합니다.

예를 들어

  • 외부 서비스(DB, 메시지 큐, 캐시 등)가 살아 있어야 하고
  • 설정 파일이나 시크릿을 외부 시스템에서 가져와야 하고
  • 초기 데이터/캐시가 미리 채워져 있어야 하고
  • 스키마 마이그레이션, 권한/계정 생성 같은 작업이 선행되어야 하는 경우

이런 것들을 애플리케이션 코드 안에서 처리하면

  • 부트 로직 곳곳에 리트라이·타임아웃·에러 처리 코드가 퍼지고
  • “언제까지 기다릴까?”, “실패하면 프로세스를 죽일까?” 같은 정책이 코드에 박혀버리며
  • Pod 단위의 헬스체크/재시작 정책과 충돌하기 쉽습니다.

Init Container 패턴은 이런 책임을 Pod 레벨로 끌어올려서 정리해 주는 도구입니다.

2. Init Container는 어떻게 동작하나?

2-1. 실행 순서와 재시작

기본 동작은 매우 단순합니다.

  • spec.initContainers 배열에 1개 이상 정의
  • 선언된 순서대로 하나씩 실행
  • 각 Init 컨테이너가 Exit Code 0으로 끝나야 다음 단계로 진행
  • 중간에 하나라도 실패(Exit Code ≠ 0)하면
    • Pod는 restartPolicy에 따라 재시작
    • 재시작 시 Init 시퀀스를 처음부터 다시 실행

즉, Init 컨테이너는 “이 Pod가 Ready가 되기 전에 반드시 통과해야 하는 관문” 역할을 합니다.

따라서 Init Container에 들어가는 로직은 여러 번 실행돼도 안전한(idempotent) 형태로 만드는 것이 중요합니다.

2-2. 리소스 스펙

Init 컨테이너도 일반 컨테이너와 거의 동일하게 설정할 수 있습니다.

  • 이미지
  • 커맨드/엔트리포인트
  • 환경 변수
  • Volume mount
  • 리소스 요청/제한

등을 그대로 사용합니다.

보통 실행 시간이 짧기 때문에, 상황에 따라 리소스 요청을 작게 가져가거나,
반대로 “부트 단계에서만 세게 쓰는” 식으로 설계하는 것도 가능합니다.

2-3. 네트워크 / Volume 공유

Pod 안의 컨테이너들은 모두 동일한 네트워크 네임스페이스를 공유하고
정의된 Volume을 함께 사용할 수 있기 때문에, Init 컨테이너에서는 다음과 같은 패턴이 자연스럽습니다.

  • 메인 컨테이너가 접속할 DB, 외부 API 등 의존 서비스 상태를 실제와 동일한 네트워크 환경에서 체크
  • emptyDir / PVC / ConfigMap / Secret 같은 Volume에
    • 설정 파일 생성
    • 인증서/토큰 저장
    • 초기 데이터/캐시 작성

→ 메인 컨테이너는 “이미 파일/데이터가 준비된 상태”를 전제로 단순히 읽기만 하면 됩니다.

3. 언제 Init Container를 쓰면 좋은가? (대표 활용 시나리오)

이 섹션에서는 실제 현업에서 자주 등장하는 케이스를 중심으로 Init 패턴을 정리합니다.

3-1. 의존 서비스 준비 상태 체크

예시:

  • DB, Redis, 메시지 브로커 포트가 열려 있는지 확인
  • Config API/Endpoint에서 필수 설정값을 가져올 수 있는지 확인

Init 컨테이너에서

  • 일정 간격으로 서비스 상태를 체크하고
  • 준비 완료 시 0으로 종료 → 메인 컨테이너 시작
  • 준비되지 않으면 비정상 종료 → Pod 재시작 → 다시 Init부터 재실행

메인 컨테이너 입장에서는 “이미 의존 서비스가 살아 있다”는 가정을 하고 단순하게 부팅할 수 있습니다.

3-2. 설정 파일 / 시크릿 생성

예시

  • Config Server / Secrets Manager에서 설정을 가져와 로컬 설정 파일로 저장
  • 템플릿 엔진을 이용해 최종 설정 파일 렌더링
  • TLS 인증서, 토큰, API 키 등을 외부 시스템에서 가져와 Volume에 저장

이렇게 하면 메인 컨테이너는 /config/app.yaml, /etc/ssl/certs/app.pem 같은 파일만 읽으면 되고 보안/자격증명 사용 로직은 Init 컨테이너에만 존재하게 되어 책임과 보안을 분리할 수 있습니다.

3-3. 데이터 / 캐시 프리로딩

예시:

  • 코드 테이블, 환율, Feature Flag 등 참조 데이터를 외부에서 가져와 로컬 캐시에 저장
  • 정적 자산(이미지, JS 번들, 번역 리소스 등)을 외부 스토리지에서 로컬로 복사

장점:

  • Init이 끝나기 전까지는 Pod가 Ready가 아니므로 트래픽이 들어오지 않음
  • 초기 로딩이 모두 끝난 뒤에만 서비스가 트래픽을 받게 되어, cold start 구간의 에러를 줄일 수 있습니다.

3-4. 스키마 마이그레이션 자동화

Init 컨테이너에서 DB 마이그레이션 스크립트를 실행하고 현재 스키마 버전을 체크하여, 필요할 때만 마이그레이션 수행 합니다.

다만, 여기에는 몇 가지 주의 사항이 있습니다.

  1. 여러 Pod가 동시에 같은 마이그레이션을 실행하면 경쟁 상태가 발생할 수 있음
    보통은 리더 Pod에서만 수행하거나 별도 Job/파이프라인으로 분리하고 Init에서는 “스키마 버전 검사” 정도만 수행하는 식으로 설계 합니다.

3-5. 보안·권한 작업 분리

예시:

  • 파일 권한/소유자 변경
  • 사용자/그룹 생성
  • 특정 OS 튜닝 작업
  • 고권한 자격증명을 이용한 외부 시스템 초기화

이런 고권한 작업을 Init 컨테이너에서만 수행하고:

  • 메인 컨테이너는 낮은 권한 + 최소 SecurityContext로만 실행하면,

장점:

  • 메인 애플리케이션 이미지가 가벼워지고
  • 공격 표면이 줄어들며
  • 보안 담당자 관점에서도 역할과 책임이 명확해집니다.

4. 다른 메커니즘과 비교해 보기

Init Container는 혼자 있는 기능이 아니라, Sidecar / Lifecycle Hook / Job 등과 함께 비교하면서 보는 게 이해가 쉽습니다.

4-1. Init Container vs Sidecar 컨테이너

공통점

  • 둘 다 Pod 안에 존재하는 또 다른 컨테이너
  • 메인 컨테이너와 네트워크·Volume 공유 가능

차이점

  • Init 컨테이너
    • Pod 시작 전에 한 번 실행되고 종료
    • 모든 Init 컨테이너가 성공해야 메인 컨테이너 실행
    • 준비 작업, 초기화용 작업에 적합
  • Sidecar 컨테이너
    • 메인 컨테이너와 동시에 실행·종료
    • 로그 수집, 프록시, 보안 토큰 갱신, 메트릭 수집 등 상시 보조 작업에 적합

정리하면:

  • “앱이 뜨기 전에 한 번만 하면 되는 작업” → Init
  • “앱이 실행되는 동안 내내 필요한 작업” → Sidecar

4-2. Init Container vs Lifecycle Hooks (postStart, preStop)

  • Lifecycle Hook
    • 컨테이너 프로세스 내부에서 실행되는 작은 훅
    • 단순한 스크립트/명령어를 실행하는 용도에 적합
  • Init Container
    • 완전히 분리된 컨테이너 환경
    • 별도 이미지, 다양한 패키지, 복잡한 로직까지 가능

즉, 복잡도와 책임이 커질수록 Lifecycle Hook 대신 Init 컨테이너로 옮기는 것이 자연스럽습니다.

4-3. Init Container vs Job / CronJob

  • Job / CronJob
    • 클러스터 전역에서 동작하는 “배치 작업” 느낌
    • 여러 Pod/서비스가 공유하는 마이그레이션, 정리 작업 등에 적합
  • Init Container
    • 특정 Pod의 생명주기에 밀접하게 묶여 있음
    • “이 Pod가 시작되기 위해 꼭 필요한 작업”에 초점을 맞춤

둘은 대체제가 아니라 레벨이 다른 도구입니다.

  • 클러스터 수준의 한번짜리/주기적 작업 → Job/CronJob
  • Pod 단위의 부트스트랩 → Init Container

5. 설계 시 고려사항 & 모범 사례

5-1. Idempotency (재실행 안전성)

Pod 재시작 → Init 전체 시퀀스 재실행

따라서 같은 작업을 여러 번 실행해도 문제가 없도록 설계해야 합니다.

  • “존재하지 않을 때만 생성”
  • “이미 있으면 스킵”
  • 마이그레이션 시 버전 체크/락 사용 등

5-2. 종료 조건과 타임아웃

  • 무한 루프 형태의 wait는 피하는 것이 좋습니다.
  • 최대 재시도 횟수나 타임아웃을 명시적으로 두는 것이 안전합니다.
  • 너무 긴 대기 시간은 롤링 업데이트/배포 속도를 심각하게 늦출 수 있으므로 운영 정책과 균형이 필요합니다.

5-3. 로깅과 관찰 가능성(Observability)

Init 컨테이너는 짧게 실행되고 끝나버리기 때문에:

  • 실패 원인을 파악하기 위해선 로그가 거의 유일한 단서인 경우가 많습니다.
  • 의미 있는 로그 메시지를 남기고,
  • 필요하다면 Init 실행 시간/실패율 등을 메트릭으로 수집해 모니터링하는 것도 좋습니다.

5-4. 이미지 크기와 재사용 전략

  • 준비 작업에 필요한 도구만 들어 있는 작고 전용화된 Init 이미지를 사용하는 것이 이상적입니다.
  • 여러 애플리케이션에서 공통으로 사용하는 Init 로직(예: Config Server에서 설정 가져오기)을
    하나의 공용 Init 이미지로 관리하면:
    - 중복 감소
    - 운영/업데이트 일관성 확보

5-5. 보안 컨텍스트 분리

  • Init 컨테이너는 필요하면 높은 권한(SYS_ADMIN, root 등)을 사용할 수 있지만,
  • 메인 컨테이너는 가능한 한 최소 권한으로 실행하는 것이 좋습니다.

Pod spec에서 컨테이너별 securityContext를 분리해서 설정하면:

  • 고권한 작업은 Init에만 국한되고
  • 메인 서비스 컨테이너는 최소 권한 원칙(Least Privilege)을 지킬 수 있습니다.

6. 간단 예제: Init Container로 DB 준비 상태 확인

아래는 가장 단순한 형태의 Init 패턴 예시입니다.

외부 DB가 열려 있을 때까지 기다린 후에 앱을 기동하는 Pod 입니다.

apiVersion: v1
kind: Pod
metadata:
  name: demo-init-container
spec:
  initContainers:
    - name: wait-for-db
      image: busybox:1.36
      command:
        - sh
        - -c
        - |
          echo "Checking DB availability..."
          until nc -z mysql.example.com 3306; do
            echo "DB not ready, sleeping..."
            sleep 5
          done
          echo "DB is ready!"

  containers:
    - name: app
      image: myorg/myapp:1.0.0
      ports:
        - containerPort: 8080

이 예제에서:

  • wait-for-db Init 컨테이너가 mysql.example.com:3306에 연결될 수 있을 때까지 반복 체크하고,
  • 성공하면 Exit Code 0으로 종료 → 메인 컨테이너 app 시작
  • 실패로 종료하면 Pod 재시작 → Init부터 전체 시퀀스 다시 실행됩니다.

7. Kubernetes v1.28~v1.34 기준 확장 포인트 (책에는 없는 최신 내용)

이 섹션은 Kubernetes Patterns 책의 Init Container 장에서 잘 다루지 않는,

v1.28~v1.34 사이에 추가된 기능과 운영 관점의 보강 설명입니다.

7-1. 컨테이너별 재시작 정책 & “Try-Once Init Containers” (v1.34, Alpha)

기존에는 Pod 단위의 .spec.restartPolicy만 존재해서:

  • 메인 컨테이너와 Init 컨테이너가 모두 동일한 재시작 정책을 따라야 했습니다.

v1.34부터 컨테이너 단위 재시작 정책(알파)이 도입되면서:

  • 어떤 Init 컨테이너는 “실패해도 다시 재시도하지 않는(try-once)” 형태로 정의할 수 있고
  • 메인 컨테이너는 Always, Init 컨테이너는 Never 등 서로 다른 정책을 갖도록 설정할 수 있습니다.

이렇게 되면:

  • 스키마 마이그레이션, 데이터 초기화처럼 실패 시 무한 재시도하면 곤란한 작업을 보다 안전하게 정의할 수 있습니다.
    • 예: “Init가 한 번 실패하면 Pod는 그 상태로 두고, 사람이/자동화가 보고 원인을 처리한 뒤 새 버전을 롤아웃”

즉, v1.34 이후 Init 설계에서는 “컨테이너별 restartPolicy”까지 고려하는 것이 베스트 프랙티스에 가깝습니다.

7-2. Native Sidecar Containers & Restartable Init (v1.28~v1.33)

1.28에서 알파로 도입된 SidecarContainers 기능은,

1.33 기준으로 안정화된 Native Sidecar 개념으로 정리되었습니다.

핵심 아이디어:

  • initContainers 배열에 있으면서도 restartPolicy: Always를 가진 컨테이너를 네이티브 Sidecar로 취급
  • “초기에 한 번 작업을 수행한 뒤, Pod 라이프사이클 내내 상주해야 하는 보조 컨테이너”를 자연스럽게 표현

조합 예시:

  • 순수 Init 컨테이너
    • restartPolicy: OnFailure(기본)
    • 초기 데이터 로딩, 디렉터리/권한 설정 등
  • Native Sidecar (Init + Always)
    • 로그 수집, 프록시, 보안 토큰 갱신, 오브저버빌리티 에이전트 등
  • 메인 컨테이너
    • 실제 비즈니스 로직 처리

책에서는 Init 컨테이너와 Sidecar 패턴을 완전히 분리된 개념으로 설명하지만,

실제 최신 쿠버네티스에서는 스펙 차원에서 Init과 Sidecar가 연결되어 있다는 점이 중요합니다.

7-3. Init → 파일 → 메인 컨테이너 Env/Config 주입 패턴 (v1.34, Alpha)

책에서는 보통:

  • Init 컨테이너가 Volume에 설정 파일/시크릿을 써주고
  • 메인 컨테이너가 그 파일을 읽어 사용하는 패턴

정도만 소개됩니다.

v1.34 즈음에는 이것이 발전해서:

  • Init 컨테이너가 특정 파일에 값을 기록하고
  • 메인 컨테이너가 그 파일을 읽어 환경 변수/설정으로 사용하는 패턴이
  • 공식 Task 문서와 알파 기능으로 좀 더 명확히 소개되고 있습니다.

운영 관점에서 이 패턴의 의미는:

  • 외부 Secrets Manager/Config Server에서 값을 가져와 Init에서 가공 후 파일로 저장
  • 메인 컨테이너는 단순히 해당 파일만 읽어 env/config로 사용
  • “민감 값은 애플리케이션 이미지 안에 절대 넣지 않는다” 같은 보안 요구사항을 지키면서도 앱 코드를 단순하게 유지 가능

즉, 책에서 아이디어 수준으로 언급되던 “Init → Volume → 메인” 패턴이 공식 구현/운영 가이드까지 포함하는 형태로 진화했습니다.

7-4. Init Container 디버깅 및 운영 가이드 강화

공식 문서에서는 Init 컨테이너 실패/디버깅을 위한 별도 가이드를 제공합니다.

대표적인 플로우는 다음과 같습니다.

  1. Pod 상태 및 이벤트 확인
kubectl describe pod <pod-name>
  1. 각 Init 컨테이너 로그 확인
kubectl logs <pod-name> -c <init-container-name>
  1. 동일 명령을 별도 테스트 Pod에서 재현
kubectl run debug-init --rm -it --image=busybox:1.36 -- sh
  1. 필요하면 kubectl debug로 동일 환경에 디버그 컨테이너 붙이기
kubectl debug -it <pod-name> --image=busybox:1.36 --target=<init-container-name>

실제 운영 환경에서는 Init 실패가 배포 실패/롤아웃 지연의 주요 원인이 되는 경우가 많기 때문에,

Init 설계뿐 아니라 디버깅 루틴을 팀 차원에서 표준화해 두는 것이 중요합니다.

7-5. 버전 / Feature Gate 관점에서의 설계 포인트

v1.28~v1.34 사이의 Init/Sidecar 관련 기능들은 대체로 다음과 같은 흐름을 가집니다.

  • 처음에는 Alpha 기능으로 들어와 기능 게이트 활성 + 실험적 사용이 필요
  • Beta / GA(Stable) 단계로 가면서 기본 활성/비활성 상태와 동작 방식이 바뀔 수 있음

따라서 클러스터를 운영할 때는

  • 어떤 버전에서
    • 어떤 Init/Sidecar 관련 기능을
    • 어떤 Feature Gate 설정으로 사용하고 있는지
  • 버전/기능 매트릭스로 관리하는 것이 좋습니다.

특히, 클러스터 업그레이드 시에는:

  • 이전에 사용하던 Alpha 기능이 제거되거나 동작 방식이 바뀔 수 있으므로
  • Init/Sidecar 관련 기능 사용 여부를 업그레이드 체크리스트에 포함하는 것을 추천합니다.

8. 실습 예제 모음

8-1. 기본 Init Container로 DB 준비 상태 확인하기

  • mysql Deployment 코드 작성
    # 00-mysql.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: mysql
    spec:
      selector:
        app: mysql
      ports:
        - port: 3306
          targetPort: 3306
    
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: mysql
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mysql
      template:
        metadata:
          labels:
            app: mysql
        spec:
          containers:
            - name: mysql
              image: mysql:8.0
              env:
                - name: MYSQL_ROOT_PASSWORD
                  value: "rootpassword"   # 실습용 임시 값
              ports:
                - containerPort: 3306
              volumeMounts:
                - name: data
                  mountPath: /var/lib/mysql
          volumes:
            - name: data
              emptyDir: {}                 # 실습용이니 영구 저장소는 생략
    
  • mysql 배포
    kubectl apply -f 00-mysql.yaml
  • mysql 배포 확인
    kubectl get po,svc;
    kubectl logs -f pod/mysql-6b9fdfcd4d-lpk9h
  • pod 배포 진행
    # 01-init-wait-for-db.yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: demo-init-wait-db
    spec:
      restartPolicy: Always
      initContainers:
        - name: wait-for-db
          image: busybox:1.36
          command:
            - sh
            - -c
            - |
              DB_HOST=${DB_HOST:-mysql}
              DB_PORT=${DB_PORT:-3306}
    
              echo "[init] waiting for DB at ${DB_HOST}:${DB_PORT}..."
              until nc -z "${DB_HOST}" "${DB_PORT}"; do
                echo "[init] DB not ready, sleep 5s"
                sleep 5
              done
              echo "[init] DB is ready!"
          env:
            - name: DB_HOST
              value: "mysql"   # 여기! Service 이름
            - name: DB_PORT
              value: "3306"
      containers:
        - name: app
          image: nginx:1.27
          ports:
            - containerPort: 80
    
  • 배포
    kubectl apply -f 01-init-wait-for-db.yaml
  • 배포 확인
    kubectl get pod demo-init-wait-db -w
    kubectl logs demo-init-wait-db -c wait-for-db
    kubectl logs demo-init-wait-db -c app
    
    kubectl describe po demo-init-wait-db

8-2. Init 컨테이너로 설정 파일 생성 후 앱에서 사용하기

Init이 Config API에서 설정을 가져와 파일로 저장하고,

앱 컨테이너는 해당 파일만 읽어 사용하는 패턴입니다.

  • 코드 작성
    # 02-init-generate-config.yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: demo-init-generate-config
    spec:
      restartPolicy: Never
      volumes:
        - name: config-volume
          emptyDir: {}
      initContainers:
        - name: generate-config
          image: busybox:1.36
          volumeMounts:
            - name: config-volume
              mountPath: /config
          command:
            - sh
            - -c
            - |
              echo "[init] generating config..."
              cat <<EOF > /config/app.conf
              APP_NAME=demo-app
              LOG_LEVEL=INFO
              BACKEND_URL=http://backend.default.svc.cluster.local
              EOF
              echo "[init] done."
      containers:
        - name: app
          image: busybox:1.36
          volumeMounts:
            - name: config-volume
              mountPath: /config
          command:
            - sh
            - -c
            - |
              echo "[app] loaded config:"
              cat /config/app.conf
              echo "[app] sleeping..."
              sleep 3600
  • 배포
    kubectl apply -f 02-init-generate-config.yaml
  • 배포 확인
    kubectl get pod demo-init-generate-config -w
    kubectl describe pod  demo-init-generate-config 
    kubectl logs demo-init-generate-config -c generate-config
    kubectl logs demo-init-generate-config -c app

8-3. Init + Native Sidecar 조합 맛보기 (v1.33+)

Kubernetes v1.33+에서 Native Sidecar 기능이 활성화된 클러스터라면,

initContainers 안에 restartPolicy: Always를 가진 컨테이너를 정의해 네이티브 사이드카로 동작시킬 수 있습니다.

  • 코드 작성
    # 03-init-with-sidecar.yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: demo-init-with-sidecar
    spec:
      restartPolicy: Always
      volumes:
        - name: shared-logs
          emptyDir: {}
      initContainers:
        # 1) 한 번만 실행되는 전통 Init
        - name: prepare-dir
          image: busybox:1.36
          volumeMounts:
            - name: shared-logs
              mountPath: /logs
          command:
            - sh
            - -c
            - |
              echo "[init] preparing /logs dir..."
              mkdir -p /logs
              chmod 777 /logs
        # 2) restartPolicy=Always 를 갖는 네이티브 사이드카
        - name: log-shipper
          image: busybox:1.36
          restartPolicy: Always
          volumeMounts:
            - name: shared-logs
              mountPath: /logs
          command:
            - sh
            - -c
            - |
              echo "[sidecar] tail /logs/app.log"
              touch /logs/app.log
              tail -n+1 -F /logs/app.log
      containers:
        - name: app
          image: busybox:1.36
          volumeMounts:
            - name: shared-logs
              mountPath: /logs
          command:
            - sh
            - -c
            - |
              i=0
              while true; do
                echo "[app] log line $i" | tee -a /logs/app.log
                i=$((i+1))
                sleep 5
              done
    
  • 배포
    kubectl apply -f 03-init-with-sidecar.yaml
  • 배포 확인
    kubectl get pod demo-init-with-sidecar -w
    kubectl logs demo-init-with-sidecar -c prepare-dir
    kubectl logs demo-init-with-sidecar -c log-shipper
    kubectl logs demo-init-with-sidecar -c app
  • 주의사항

    restartPolicy를 Init 컨테이너에 직접 주는 기능은

    클러스터 버전 및 Feature Gate 상태에 따라 동작이 달라질 수 있습니다.

    v1.33+ / 기본 설정 클러스터에서 테스트해 보는 것을 권장합니다.


8-4. Init Container 디버깅 기본 흐름 (실습 관점)

Init:CrashLoopBackOff 같은 상태가 떴다고 가정하면,

가장 기본적인 디버깅 패턴은 다음과 같습니다.

# 1) 상태 및 이벤트 확인
kubectl describe pod <pod-name>

# 2) 특정 Init 컨테이너 로그 확인
kubectl logs <pod-name> -c <init-container-name>

# 3) 동일 명령을 별도 테스트 Pod에서 재현
kubectl run debug-init --rm -it --image=busybox:1.36 -- sh

# 4) 필요하면 kubectl debug 로 같은 Pod에 디버그 컨테이너 붙이기
kubectl debug -it <pod-name> --image=busybox:1.36 --target=<init-container-name>

이 흐름을 팀 내부 위키나 운영 매뉴얼로 정리해 두면, Init 관련 장애가 발생했을 때 공통 언어로 대응할 수 있습니다.

  • 예제 코드 작성
    apiVersion: v1
    kind: Pod
    metadata:
      name: demo-init-crashloop
    spec:
      restartPolicy: Always   
      containers:
        - name: app
          image: busybox:1.36
          command:
            - sh
            - -c
            - |
              echo "[app] running..."
              sleep 3600
      initContainers:
        - name: init-fail
          image: busybox:1.36
          command:
            - sh
            - -c
            - |
              echo "[init-fail] starting..."
              echo "[init-fail] I will fail now"
              exit 1   
  • 배포
    kubectl apply -f 04-demo-init-crashloop.yaml
  • 상태 및 이벤트 확인
    kubectl get pod demo-init-crashloop -w
    kubectl describe pod demo-init-crashloop
  • debug 컨테이너 붙여서 에러 확인하기
    kubectl debug -it demo-init-crashloop --image=busybox:1.36 --target=init-fail
  • pod 삭제
     kubectl delete po demo-init-crashloop 

9. 마무리

Init Container 패턴은 처음에는 “DB 기다리는 용도” 정도로만 보일 수 있지만, 실제로는 다음을 모두 포괄하는 강력한 도구입니다.

  • 시작 순서 의존성 해결
  • 보안/권한 작업 분리
  • 설정/시크릿 주입 패턴 정리
  • cold start 구간 최적화
  • 스키마/데이터 마이그레이션 자동화

여기에 v1.28~v1.34 사이에 추가된 Native Sidecar, 컨테이너별 restartPolicy, Init 기반 Env/Config 주입 공식 패턴까지 함께 이해하면, 실제 프로덕션 환경에서 Init Container를 훨씬 더 안정적이고 유연하게 활용할 수 있습니다.

결국 핵심 메시지는 하나입니다.

“앱이 뜨기 전에 꼭 해야 하는 일을 앱 코드 밖으로 꺼내어 Pod 레벨에서 관리하자.”

복잡한 부트스트랩 로직이 애플리케이션 코드 안에 섞여 있다면,

한 번쯤 Init Container로 분리해 보는 리팩터링을 고려해볼 만합니다.


10. 참고 자료

profile
DevOps Engineer

5개의 댓글

comment-user-thumbnail
2025년 11월 16일

안녕하세요~ 오늘 블로그 글도 잘 봤습니다. 질문이 몇가지 있는데요~
1. Init Container가 CrashLoopBackOff 상태인데 로그도 짧고 금방 사라져서 디버깅이 어려운경우가 있었는데요. 보통 어떻게 디버깅 하시는지 궁금합니다.
2. 블로그에서 "Init Container로 권한 분리"를 언급했는데 구체적인 예시가 궁금합니다.

1개의 답글
comment-user-thumbnail
2025년 11월 16일

안녕하세요 작성해주신 글 잘 읽었습니다. Native Sidecar에 대해 질문이 있는데, 아래와 같은 경우에서는 C가 정상적으로 시작이 되나요?

initContainers:

  • name: A
  • name: B # Native Sidecar (restartPolicy: Always)
  • name: C
2개의 답글