Pod 심화 — 컨테이너의 최소 실행 단위가 노드에서 동작하는 방식
컨테이너 하나를 실행하는 건
docker run으로도 되는데, Kubernetes는 왜 굳이 "Pod"라는 단위를 만들었을까요?
Pod은 Kubernetes에서 배포할 수 있는 가장 작은 단위입니다. 하나 이상의 컨테이너를 묶어서 같은 네트워크 네임스페이스와 스토리지를 공유하게 만드는 구조인데, 단순히 컨테이너를 감싸는 래퍼가 아니라 라이프사이클 관리, 헬스체크, 초기화 로직까지 담당하는 핵심 오브젝트입니다.
Pod 라이프사이클
Pod은 생성부터 종료까지 다음 단계를 거칩니다.
| 상태 | 설명 |
|---|---|
| Pending | 스케줄러가 노드를 배정하거나 이미지를 풀링하는 중 |
| Running | 최소 하나의 컨테이너가 실행 중 |
| Succeeded | 모든 컨테이너가 정상 종료(exit 0) |
| Failed | 하나 이상의 컨테이너가 비정상 종료 |
| Unknown | 노드와 통신이 끊겨 상태를 알 수 없음 |
# Pod 상태 확인
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: app
image: nginx:1.25
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo 'Pod이 시작되었습니다' > /tmp/started"]
preStop:
exec:
command: ["/bin/sh", "-c", "nginx -s quit"]
postStart는 컨테이너 시작 직후, preStop은 종료 직전에 실행됩니다. 특히 preStop은 Graceful Shutdown을 구현할 때 자주 사용합니다.
Init 컨테이너
Init 컨테이너는 메인 컨테이너가 시작되기 전에 순차적으로 실행되는 특수한 컨테이너입니다. DB 마이그레이션, 설정 파일 생성, 외부 서비스 대기 같은 초기화 작업에 활용합니다.
apiVersion: v1
kind: Pod
metadata:
name: init-demo
spec:
initContainers:
# 1단계: DB가 준비될 때까지 대기
- name: wait-for-db
image: busybox:1.36
command: ['sh', '-c', 'until nslookup mysql-service; do echo "DB 대기 중..."; sleep 2; done']
# 2단계: 설정 파일 생성
- name: config-init
image: busybox:1.36
command: ['sh', '-c', 'echo "db.host=mysql-service" > /config/app.properties']
volumeMounts:
- name: config-volume
mountPath: /config
containers:
- name: app
image: my-app:1.0
volumeMounts:
- name: config-volume
mountPath: /config
volumes:
- name: config-volume
emptyDir: {}
Init 컨테이너의 핵심 특징을 정리하면 다음과 같습니다.
- 순차 실행: 여러 개가 있으면 정의된 순서대로 하나씩 실행됩니다
- 완료 필수: 이전 Init 컨테이너가 성공해야 다음으로 넘어갑니다
- 실패 시 재시도: 실패하면 Pod의
restartPolicy에 따라 재시도합니다 - 메인과 분리: Init 컨테이너의 리소스 제한은 메인 컨테이너와 독립적입니다
Sidecar 컨테이너
Sidecar는 메인 컨테이너와 함께 실행되면서 보조 역할을 하는 컨테이너입니다. 로그 수집, 프록시, 모니터링 에이전트가 대표적인 용도입니다.
Kubernetes 1.28부터는 initContainers에 restartPolicy: Always를 지정해서 네이티브 사이드카를 정의할 수 있게 되었습니다.
apiVersion: v1
kind: Pod
metadata:
name: sidecar-demo
spec:
initContainers:
# 네이티브 사이드카 (1.28+)
- name: log-collector
image: fluent-bit:2.1
restartPolicy: Always # 이 옵션이 사이드카로 만들어줌
volumeMounts:
- name: log-volume
mountPath: /var/log/app
containers:
- name: app
image: my-app:1.0
volumeMounts:
- name: log-volume
mountPath: /var/log/app
volumes:
- name: log-volume
emptyDir: {}
네이티브 사이드카의 장점은 Pod 종료 시 메인 컨테이너가 먼저 종료된 후에 사이드카가 종료되는 순서가 보장된다는 것입니다. 기존 방식에서는 사이드카가 먼저 죽어서 로그가 유실되는 문제가 있었습니다.
Probe — 헬스체크의 세 가지 종류
Kubernetes는 세 가지 Probe로 컨테이너의 상태를 확인합니다.
livenessProbe — "살아있는가?"
애플리케이션이 데드락에 빠지거나 응답 불가 상태가 되었을 때 감지합니다. 실패하면 컨테이너를 재시작합니다.
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15 # 처음 체크까지 대기 시간
periodSeconds: 10 # 체크 주기
failureThreshold: 3 # 연속 실패 허용 횟수
readinessProbe — "트래픽을 받을 수 있는가?"
애플리케이션이 요청을 처리할 준비가 되었는지 확인합니다. 실패하면 Service의 Endpoints에서 해당 Pod을 제거합니다.
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 2 # 연속 성공해야 Ready로 전환
startupProbe — "초기화가 끝났는가?"
시작이 느린 애플리케이션을 위한 Probe입니다. startupProbe가 성공할 때까지 liveness/readiness Probe가 비활성화됩니다.
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30 # 30 × 10초 = 최대 5분 대기
periodSeconds: 10
Java 애플리케이션처럼 초기 부팅이 오래 걸리는 경우, startupProbe 없이 livenessProbe만 쓰면 아직 초기화 중인데 재시작이 반복되는 문제가 생길 수 있습니다.
Probe 방식 비교
Probe는 세 가지 방식으로 동작합니다.
| 방식 | 설명 | 사용 예 |
|---|---|---|
httpGet | HTTP 요청을 보내 200-399 응답 확인 | 웹 서버, REST API |
tcpSocket | TCP 연결 가능 여부 확인 | 데이터베이스, gRPC |
exec | 컨테이너 내 명령 실행 후 exit code 확인 | 파일 존재 확인, 스크립트 |
# TCP 소켓 방식 예제
livenessProbe:
tcpSocket:
port: 3306
initialDelaySeconds: 10
periodSeconds: 5
# exec 방식 예제
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 10
Pod이 노드에서 실행되는 과정
Pod이 실제로 노드에 배치되어 실행되기까지의 과정을 단계별로 보면 다음과 같습니다.
- API Server:
kubectl apply명령으로 Pod 스펙이 etcd에 저장됩니다 - Scheduler: 조건에 맞는 노드를 선택하여 Pod을 할당합니다
- kubelet: 해당 노드의 kubelet이 Pod 스펙을 받아 컨테이너 런타임에 전달합니다
- Container Runtime: containerd 등이 실제 컨테이너를 생성합니다
- CNI Plugin: 네트워크 인터페이스를 설정합니다
- Init Containers: 정의된 Init 컨테이너를 순차 실행합니다
- Main Containers: Init이 모두 성공하면 메인 컨테이너를 시작합니다
- Probes: kubelet이 Probe를 통해 상태를 지속적으로 모니터링합니다
# Pod의 상세 이벤트 확인 — 위 과정을 실제로 볼 수 있습니다
kubectl describe pod lifecycle-demo
# Pod 상태 확인
kubectl get pod lifecycle-demo -o jsonpath='{.status.phase}'
Pod 종료 과정 (Graceful Shutdown)
Pod 삭제 시에도 정해진 순서가 있습니다.
- Pod 상태가
Terminating으로 변경됩니다 - Service Endpoints에서 제거됩니다 (새 트래픽 차단)
preStop훅이 실행됩니다- SIGTERM 신호가 전송됩니다
terminationGracePeriodSeconds(기본 30초) 동안 대기합니다- 시간 초과 시 SIGKILL로 강제 종료합니다
spec:
terminationGracePeriodSeconds: 60 # 종료 대기 시간을 60초로 연장
containers:
- name: app
image: my-app:1.0
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"] # 연결 드레인 대기
실무에서 자주 하는 실수
Pod을 설정할 때 자주 놓치는 부분들을 정리합니다.
- Probe 설정 누락: readinessProbe 없이 배포하면 초기화 중인 Pod에 트래픽이 들어갑니다
- initialDelaySeconds 부족: 애플리케이션 시작 시간보다 짧으면 불필요한 재시작이 반복됩니다
- preStop 미설정: Graceful Shutdown이 안 되면 진행 중인 요청이 끊어집니다
- 리소스 제한 미설정: requests/limits 없으면 노드 리소스를 무한정 사용할 수 있습니다
정리
Pod은 단순한 컨테이너 래퍼가 아닙니다. Init 컨테이너로 초기화를 분리하고, Sidecar로 부가 기능을 추가하며, Probe로 상태를 관리하는 Kubernetes의 핵심 실행 단위입니다. Pod의 라이프사이클과 종료 과정을 이해하면 "왜 배포 중에 5XX 에러가 나는지", "왜 Pod이 계속 재시작되는지" 같은 문제를 정확하게 진단할 수 있습니다.