쿠버네티스 마스터 노트 시리즈 2편. Pod이 K8s의 최소 배포 단위인 이유, Pod = 1+ 컨테이너 + 공유 네트워크/볼륨의 의미, Pause Container의 숨은 역할, Pod 생명주기 5단계(Pending/Running/Succeeded/Failed/Unknown), Init Container와 Sidecar 패턴, Pod이 일회용(Ephemeral)인 이유와 그것이 K8s 설계 철학에 미치는 영향까지.
이 글은 쿠버네티스 마스터 노트 시리즈의 두 번째 편입니다. 1편(아키텍처)에서 클러스터 구조를 다졌다면, 이번엔 K8s의 가장 작은 단위 — Pod.
Pod을 이해 못 하면 K8s 전체가 막연합니다. 컨테이너와 다른 점, 왜 1+ 컨테이너 묶음인지, 왜 일회용인지가 핵심.
처음 Pod이 어렵게 느껴지는 이유
처음 이 단원이 어렵게 느껴지는 이유는 두 가지예요. 첫째, "Pod = 컨테이너 아닌가" 헷갈립니다. 둘째, "왜 K8s는 컨테이너를 직접 안 다루나" 막연합니다.
해결법은 한 가지예요. "Pod = 컨테이너의 봉투" 비유. 같은 봉투에 들어간 컨테이너끼리는 같은 사무실(네트워크·볼륨) 공유. 다른 봉투끼리는 별도 사무실. 이 그림이 잡히면 모든 동작이 따라옵니다.
Pod — K8s 최소 배포 단위
Pod = 1개 이상 컨테이너 + 공유 자원
├── 공유 네트워크 (같은 IP)
├── 공유 볼륨
└── 공유 IPC·UTS namespace
같은 Pod 안 컨테이너끼리:
- localhost로 통신 가능 (같은 IP)
- 볼륨 공유
다른 Pod끼리:
- 별도 IP, 클러스터 내부 네트워크로 통신
여기서 정말 중요한 시험 함정 — K8s는 컨테이너 직접 X, Pod 단위로 다룸. 모든 자원·스케줄링·생명주기가 Pod 단위. 컨테이너 1개 = Pod 1개라도 OK.
가장 단순한 Pod
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
kubectl apply -f pod.yaml
kubectl get pods
kubectl describe pod nginx-pod
kubectl logs nginx-pod
kubectl delete pod nginx-pod
Pause Container — 숨은 영웅
각 Pod엔 사용자가 안 보지만 Pause 컨테이너가 자동:
Pod
├── pause container (숨겨진)
├── nginx
└── (다른 컨테이너)
역할:
- Pod의 네트워크·IPC namespace 소유
- 다른 컨테이너들이 join
- 컨테이너 다운돼도 Pod 자체 살아 있음
docker ps # 노드에서 실행
# pause 컨테이너가 모든 Pod마다 보임
여기서 시험 함정이 하나 있어요. Pause는 K8s 내부 메커니즘. 사용자가 직접 다룰 일 X. 다만 알아두면 디버깅 시 도움.
Pod 생명주기 — 5 Phase
Pending → Running → Succeeded
→ Failed
→ Unknown
| Phase | 의미 |
|---|---|
| Pending | API Server에 등록, 노드 배정 또는 이미지 pull 중 |
| Running | 노드에 배정 + 모든 컨테이너 시작됨 |
| Succeeded | 모든 컨테이너 정상 종료 (exit 0) |
| Failed | 1개 이상 컨테이너 실패 (exit ≠ 0) |
| Unknown | API Server가 노드 상태 못 받음 |
컨테이너 상태
각 컨테이너 상태 별도:
status:
containerStatuses:
- name: nginx
state:
running: # waiting / running / terminated
startedAt: ...
ready: true
restartCount: 0
여기서 정말 중요한 시험 함정 — Pod Phase ≠ 컨테이너 Ready. Pod Running이라도 컨테이너 readiness probe 실패 시 not ready. Service에서 트래픽 안 받음. 7편에서 자세히.
멀티 컨테이너 Pod — 두 패턴
1. Sidecar 패턴
주 컨테이너 + 보조 컨테이너 함께.
spec:
containers:
- name: app
image: my-app
volumeMounts:
- name: logs
mountPath: /var/log
- name: log-shipper # Sidecar
image: fluentd
volumeMounts:
- name: logs
mountPath: /var/log
volumes:
- name: logs
emptyDir: {}
용도:
- 로그 수집 (Fluentd·Filebeat)
- 프록시 (Envoy·Istio sidecar)
- 모니터링 (Prometheus exporter)
- TLS terminator
2. Adapter / Ambassador 패턴
특수 변환 또는 외부 서비스 추상화.
여기서 시험 함정이 하나 있어요. 하나의 Pod에 너무 많은 컨테이너 X. 권장 = 1~3개. 강하게 결합된 것만 같은 Pod (라이프사이클 함께).
Init Container — 시작 전 준비
spec:
initContainers:
- name: wait-for-db
image: busybox
command: ['sh', '-c', 'until nc -z db 5432; do sleep 1; done']
containers:
- name: app
image: my-app
흐름:
- Init 컨테이너 순차 실행 (모두 성공해야)
- 메인 컨테이너 시작
용도:
- DB 준비 대기
- 설정 파일 다운로드
- 마이그레이션 실행
- 권한 설정
여기서 정말 중요한 시험 함정 — Init 컨테이너는 순차 실행. 모두 성공해야 메인 시작. 하나라도 실패 시 무한 재시도 (또는 backoff). 디버깅 = kubectl logs <pod> -c <init-container>.
restartPolicy — 종료 시 정책
spec:
restartPolicy: Always # Always (기본) / OnFailure / Never
| 정책 | 의미 |
|---|---|
| Always | 항상 재시작 (기본, Deployment에 자동) |
| OnFailure | 실패(exit ≠ 0) 시만 |
| Never | 재시작 X (Job에 흔히) |
여기서 시험 함정이 하나 있어요. Pod 직접 만들면 restartPolicy 적용. Deployment·StatefulSet은 Always 강제.
Pod이 일회용인 이유
K8s 철학 — Pod = Ephemeral (휘발).
Pod 다운 → K8s가 자동으로 새 Pod 생성
새 Pod = 새 IP, 새 식별자
이전 Pod의 상태·로컬 데이터는 사라짐
이래서 K8s 설계:
- Stateless 우선 — 영속 상태는 외부 (DB·Volume)
- Service로 IP 변경 추상화
- Deployment로 Pod 수 유지
- PV/PVC로 데이터 영속화
여기서 정말 중요한 시험 함정 — Pod 이름·IP 고정 의존 X. 항상 추상화 (Service·DNS) 사용. Pod 다운 가정 설계.
자원 요청·제한
spec:
containers:
- name: app
image: my-app
resources:
requests: # 보장 자원 (스케줄링)
cpu: 100m
memory: 128Mi
limits: # 최대 자원 (강제)
cpu: 500m
memory: 512Mi
- requests — 노드 자원에 따라 스케줄링 결정
- limits — 초과 시 throttle(CPU) 또는 OOMKill(Memory)
여기서 정말 중요한 시험 함정 — Memory limit 초과 = OOMKilled. CPU는 throttle만, Memory는 강제 종료. limits 너무 낮으면 자주 죽음.
m = millicore (1000m = 1 CPU core). Mi = Mebibyte.
Pod 라벨·셀렉터
metadata:
labels:
app: nginx
tier: frontend
version: v1
라벨로 그룹화 → Service·Deployment·NetworkPolicy가 셀렉터로 매칭.
kubectl get pods -l app=nginx
kubectl get pods -l tier=frontend,version=v1
Annotations — 메타 정보
metadata:
annotations:
description: "Frontend nginx server"
deployedBy: "team-a"
git-sha: "abc123"
라벨과 다름:
- 라벨 = 셀렉터 매칭용 (필수 정보)
- Annotation = 메타 정보 (도구·운영용)
Pod 디버깅
# 상세 정보
kubectl describe pod <name>
# 로그
kubectl logs <name>
kubectl logs <name> -c <container> # 멀티 컨테이너
kubectl logs <name> -f # follow
kubectl logs <name> --previous # 이전 인스턴스
# 컨테이너 진입
kubectl exec -it <name> -- bash
kubectl exec -it <name> -c <container> -- bash
# 임시 디버깅 컨테이너 (1.25+)
kubectl debug <name> -it --image=busybox
# 포트 포워딩
kubectl port-forward <name> 8080:80
시험 직전 한 번 더 — 자주 헷갈리는 함정 모음
여기까지가 2편의 핵심입니다. 시험 직전 또는 실무에서 헷갈릴 때 다시 펼쳐 볼 수 있게 압축 노트로 마무리할게요.
- Pod = K8s 최소 배포 단위
- 1+ 컨테이너 + 공유 네트워크/볼륨/IPC
- 같은 Pod 안 = localhost 통신
- K8s는 컨테이너 X, Pod 단위로 다룸
- Pause Container = 숨겨진 namespace 소유자
- 생명주기 5 — Pending / Running / Succeeded / Failed / Unknown
- Pod Phase ≠ 컨테이너 Ready
- 멀티 컨테이너 — Sidecar (로그·프록시·모니터링) / Adapter / Ambassador
- 하나의 Pod에 1~3개 권장
- Init Container — 메인 전 순차 실행
- 모두 성공해야 메인 시작
- restartPolicy — Always (기본) / OnFailure / Never
- Deployment는 Always 강제
- Pod = Ephemeral (휘발)
- IP·이름 고정 의존 X
- Service·Deployment·PV로 추상화
- resources — requests (보장·스케줄링) / limits (강제)
- Memory limit 초과 = OOMKilled
- CPU throttle / Memory 강제 종료
- m = millicore (1000m=1core), Mi = Mebibyte
- 라벨 = 셀렉터용 / Annotation = 메타 정보
- 디버깅 — describe·logs (-c, --previous)·exec·debug·port-forward
시리즈 다른 편
- 1편 — 아키텍처·Control Plane
- 2편 — Pod (현재 글)
- 3편 — Workloads
- 4편 — Services·Networking
- 5편 — ConfigMap·Secret
- 6편 — Storage
- 7편 — Scaling·Scheduling
- 8편 — Security
- 9편 — Helm
- 10편 — 실전 (매니지드·GitOps·Observability)
공식 문서: Kubernetes Pods 에서 더 깊이.
다음 글(3편)에서는 Workloads — ReplicaSet·Deployment·StatefulSet·DaemonSet·Job 5종 추상화까지 풀어 갑니다.