LabHub

CKAD — 쿠버네티스 애플리케이션 개발자 · 애플리케이션 설계와 빌드 · 이론

파드는 왜 컨테이너 하나가 아닌가

LabHub 에서 이어서 보기

한 줄 요약

파드는 "컨테이너를 담는 상자"가 아니라 같은 노드에서, 같은 네트워크 네임스페이스와 볼륨을 공유하며, 같이 뜨고 같이 죽는 프로세스 묶음의 최소 배포 단위다. 이 정의를 받아들이면 init 컨테이너와 사이드카가 왜 그렇게 생겼는지가 한 번에 설명된다.

왜 이게 필요했나

컨테이너 하나 = 프로세스 하나라는 원칙을 지키다 보면 곧 벽에 부딪힌다. 웹 서버는 액세스 로그를 파일로 쓰는데 로그 수집기는 별도 프로세스다. 앱은 설정 파일이 있어야 뜨는데 그 파일은 시작 직전에 내려받아야 한다. 이걸 한 이미지에 다 밀어 넣으면 이미지가 비대해지고, 로그 수집기를 바꿀 때마다 앱 이미지를 다시 빌드해야 한다.

그렇다고 완전히 다른 파드로 떼어 놓으면 두 가지가 깨진다. 같은 파일시스템을 볼 수 없고, 스케줄이 어긋난다. 로그 수집기가 다른 노드에 뜨면 읽을 파일이 없다.

그래서 쿠버네티스는 중간 단계를 만들었다. 스케줄 단위와 컨테이너 단위를 분리한 것이다. 스케줄되는 것은 파드, 실행되는 것은 컨테이너다. 파드 안의 컨테이너들은 같은 노드에 배치되고, localhost 로 서로를 부르며, emptyDir 볼륨으로 같은 디렉터리를 본다.

어떻게 동작하나

파드 스펙에는 컨테이너를 넣는 자리가 두 개 있다.

| 자리 | 실행 시점 | 실패하면 |
| --- | --- | --- |
| spec.initContainers | 순서대로 하나씩, 끝날 때까지 | 다음 init 도 메인도 시작 안 함 |
| spec.containers | init 전부 성공 후 동시에 | restartPolicy 에 따라 재시작 |

init 컨테이너는 "완료"가 목적이다. 설정 내려받기, DB 마이그레이션, 선행 서비스 대기 같은 일을 한다. 세 개를 나열하면 1번이 끝나야 2번이 시작한다. 하나라도 끝나지 않으면 파드는 Init:1/3 같은 상태에 영원히 머무르고, 메인 컨테이너는 시작조차 하지 않는다.

여기서 오래된 문제가 하나 있었다. 사이드카(로그 수집기, 프록시)는 "계속 떠 있어야" 하므로 init 자리에 넣을 수 없었다. 그런데 containers 에 넣으면 메인과 동시에 시작하므로, 메인이 이미 로그를 쓰기 시작한 뒤에야 수집기가 뜨는 경쟁 상태가 생겼다. Job 파드에서는 더 심각해서, 메인이 끝나도 사이드카가 계속 살아 있어 Job 이 영원히 완료되지 않았다.

네이티브 사이드카가 이 문제를 정리했다. initContainers 목록에 넣되 그 컨테이너에만 restartPolicy: Always 를 준다.

spec:  initContainers:    - name: logger      image: busybox:1.36      restartPolicy: Always     # ← 이 한 줄이 네이티브 사이드카로 만든다      command: ["sh", "-c", "tail -F /var/log/nginx/access.log"]  containers:    - name: web      image: nginx:1.27

이렇게 하면 세 가지가 동시에 성립한다. 메인보다 먼저 시작하고, 계속 살아 있고, Job 에서 메인이 끝나면 같이 종료된다.

패턴 이름도 정리해 두면 좋다. 사이드카는 메인의 기능을 보조(로그·메트릭)하고, 앰배서더는 메인이 바깥으로 나갈 때 대신 나가 주는 프록시(localhost:6379 로 붙으면 실제 클러스터로 라우팅)이며, 어댑터는 메인의 출력을 바깥이 원하는 형식으로 바꾼다(앱 로그 → 프로메테우스 메트릭).

현장에서 만나는 모습

홈랩 7노드 클러스터에 NVIDIA GPU Operator 를 올릴 때 겪은 일이다. 설치 직후 GPU 관련 파드가 전부 이런 상태였다.

gpu-feature-discovery-fpw5l            0/1   Init:0/1nvidia-dcgm-exporter-gmlvl             0/1   Init:0/1nvidia-device-plugin-daemonset-k4ggb   0/1   Init:0/1nvidia-operator-validator-krj69        0/1   Init:0/4

0/1, 0/4 는 init 컨테이너를 하나도 통과하지 못했다는 뜻이다. 메인 컨테이너는 아예 언급조차 없다. 이벤트를 보니 원인은 컨테이너 안이 아니라 그 바깥이었다.

Warning FailedCreatePodSandBox  desc = failed to get sandbox runtime:        no runtime for "nvidia" is configured

파드를 담을 샌드박스를 만드는 단계에서 막혔다. 컨테이너가 실패한 게 아니라, 컨테이너들이 공유할 네트워크·IPC 네임스페이스를 만드는 단계가 실패한 것이다. 파드가 "묶음"이라는 정의가 여기서 그대로 드러난다. 묶음을 만들 자리가 없으면 그 안의 어떤 컨테이너도 시작하지 않는다.

또 하나. 같은 클러스터에 KubeVirt 를 올렸을 때 컴포넌트 상태는 전부 AllComponentsReady 였는데 VM 은 뜨지 않았다. virt-launcher 파드 명세를 뜯어 보니 init 컨테이너가 실행할 바이너리를 담은 볼륨 마운트가 누락돼 있었다. init 컨테이너가 끝나지 못하니 메인이 영원히 대기했다. "상태가 Ready"와 "실제로 동작한다"는 다른 명제라는 걸 이 클러스터에서만 세 번째로 확인한 사건이었다.

다음 실습에서 할 것

ckad-design 네임스페이스에서 파드·라벨·애너테이션·명령 오버라이드·환경변수·restartPolicy 를 만들고, Job 의 completions/parallelism/backoffLimit 과 CronJob 의 schedule/concurrencyPolicy/startingDeadlineSeconds 를 직접 채운다. 이어지는 실습에서는 ckad-multi 네임스페이스에 init 컨테이너 순서, 네이티브 사이드카, 앰배서더·어댑터 패턴, 공유 emptyDir, terminationGracePeriodSeconds 를 손으로 만든다.