컨테이너 이미지 이해하기
컨테이너 이미지 이해하기
파드를 만들기 전에 정작 많이 헷갈리는 게 이미지죠. 컨테이너 이미지는 애플리케이션과 그 실행에 필요한 모든 소프트웨어 의존성을 묶어 놓은 바이너리 데이터예요. 쿠버네티스에서 파드는 바로 이 이미지를 받아서 컨테이너를 띄우니까, 이미지가 어떻게 이름 붙고 언제 받아지는지만 알아도 디버깅이 훨씬 쉬워져요. 이 페이지에서는 이미지 이름·태그·다이제스트부터 받아오는 정책, 프라이빗 레지스트리 인증까지 한 번에 정리해 볼게요.
이미지 이름, 태그, 다이제스트
컨테이너 이미지는 보통 pause, example/mycontainer, kube-apiserver처럼 이름을 갖고 있어요. 레지스트리 호스트명을 앞에 붙이면 fictional.registry.example/imagename 같은 형태가 되고, 포트 번호까지 붙이면 fictional.registry.example:10443/imagename처럼 되죠.
이미지 태그는 대소문자 영문자·숫자·밑줄(_)·마침표(.)·하이픈(-)만 쓸 수 있어요. 최대 길이는 128자이고, [a-zA-Z0-9_][a-zA-Z0-9._-]{0,127} 정규식에 맞아야 해요.
이미지를 지정하는 방법을 몇 가지로 나눠 볼게요. busybox처럼 이름만 쓰면 Docker 공개 레지스트리와 latest 태그를 쓰는 거라 docker.io/library/busybox:latest와 같아요. busybox:1.32.0처럼 태그를 주면 그 버전을 받고요. registry.k8s.io/pause:latest처럼 레지스트리와 태그를 함께 줄 수도 있어요. 여기에 다이제스트를 더한 형태도 있어요:
registry.k8s.io/pause@sha256:1ff6c18fbef2045af6b9c16bf034cc421a29027b800e4f9b68ae9b1cb3e9ae07— 다이제스트만 지정registry.k8s.io/pause:3.5@sha256:1ff6c18fbef2045af6b9c16bf034cc421a29027b800e4f9b68ae9b1cb3e9ae07— 태그와 다이제스트 둘 다 지정. 이 경우 받아오기엔 다이제스트만 사용돼요.
이미지 업데이트와 pull 정책
Deployment·StatefulSet·Pod같이 PodTemplate을 포함하는 오브젝트를 처음 만들 때 pull 정책을 따로 지정하지 않으면, 기본값으로 모든 컨테이너에 IfNotPresent가 설정돼요. 이 정책은 이미지가 로컬에 이미 있으면 kubelet이 pull을 건너뛰게 해 주죠.
imagePullPolicy가 가질 수 있는 값은 세 가지예요.
IfNotPresent— 이미지가 로컬에 없을 때만 pull. (기본값)Always— kubelet이 컨테이너를 띄울 때마다 항상 컨테이너 런타임에 pull을 요청. 런타임이 레지스트리에 접속해 이미지를 확인해요.Never— 이미지를 pull하지 않고 로컬에 있는 이미지만 사용.
프로덕션에서는 :latest 태그를 피하는 게 좋아요. 지금 어떤 버전이 돌고 있는지 추적하기 어렵고, 롤백도 힘들거든요. 대신 v1.42.0 같은 의미 있는 태그나 다이제스트를 쓰는 걸 권해요. admission controller가 Pod가 생성될 때 태그 대신 다이제스트 기반으로 워크로드를 정의하도록 변형해 주는 서드파티 도구도 있어요 — 레지스트리에서 태그가 바뀌어도 워크로드 전체가 같은 코드로 돌도록 보장하고 싶을 때 유용하죠.
기본 pull 정책이 결정되는 조건
API 서버에 새 파드를 제출할 때 다음 조건이 맞으면 클러스터가 imagePullPolicy 필드를 설정해 줘요.
imagePullPolicy를 명시하지 않았고 태그가:latest라면 →AlwaysimagePullPolicy를 명시하지 않았고 태그가 없으면 →IfNotPresentimagePullPolicy를 명시하지 않았고 태그가:latest가 아니라면 →IfNotPresent
ImagePullBackOff
kubelet이 컨테이너 런타임으로 파드 컨테이너를 만들 때 이미지를 못 받아서 컨테이너가 Waiting 상태에 머무는 경우가 있어요. 그 상태가 바로 ImagePullBackOff죠. 잘못된 이미지 이름, imagePullSecret 없이 프라이빗 레지스트리에서 pull 시도 같은 이유로 컨테이너가 시작되지 못한 상황이에요. BackOff는 kubelet이 기하급수적으로 백오프를 늘려가며 pull을 재시도한다는 뜻이에요.
RuntimeClass별 이미지 pull
RuntimeClassInImageCriApi 기능 게이트를 켜면(v1.29부터 alpha), kubelet이 이미지를 이름이나 다이제스트가 아니라 이미지 이름과 런타임 핸들러의 튜플로 참조해요. Windows Hyper-V 컨테이너 같은 VM 기반 컨테이너에 유용해요.
직렬·병렬 이미지 pull
kubelet은 하나의 파드를 위해 여러 이미지를 병렬로 받지 않아요. 예를 들어 init 컨테이너와 애플리케이션 컨테이너가 같은 파드에 있으면 두 컨테이너의 pull은 병렬화되지 않아요. 다만 서로 다른 이미지를 쓰는 파드가 두 개 있고 병렬 pull 기능이 켜져 있다면, 두 파드 각각에 대해 이미지를 병렬로 받을 수 있어요. maxParallelImagePulls를 n으로 설정하면 동시에 최대 n개까지만 pull되고, 그 이상은 진행 중인 pull이 끝날 때까지 기다려요.
멀티 아키텍처 이미지와 이미지 인덱스
바이너리 이미지뿐 아니라 레지스트리는 컨테이너 이미지 인덱스도 제공할 수 있어요. 이미지 인덱스는 아키텍처별 버전을 가리키는 여러 이미지 매니페스트를 가리켜요. 즉 pause라는 이름 하나로 지원되는 모든 아키텍처의 pause-amd64 같은 변형을 묶어둘 수 있는 거예요. pause-amd64 같은 이름은 구형 설정이나 YAML에 하드코딩된 이미지 이름을 위한 하위 호환 버전이에요.
프라이빗 레지스트리 사용하기
프라이빗 레지스트리의 이미지를 쓰려면 인증이 필요해요. imagePullSecrets를 파드에 지정하면 되는데, 주의할 점이 몇 가지 있어요. 모든 imagePullSecrets는 파드와 같은 네임스페이스에 존재해야 하고, 타입이 kubernetes.io/dockercfg 또는 kubernetes.io/dockerconfigjson인 Secret이어야 해요.
config.json의 auths에 여러 경로 패턴을 넣으면:
{
"auths": {
"my-registry.example/images": { "auth": "…" },
"*.my-registry.example/images": { "auth": "…" }
}
}
my-registry.example/images, my-registry.example/images/my-image, sub.my-registry.example/images/my-image 같은 이름은 패턴과 일치해서 인증이 가능하지만, a.sub.my-registry.example/images/my-image는 *가 한 세크먼트만 매칭하므로 일치하지 않아요. kubelet은 찾아낸 자격 증명마다 이미지 pull을 순차적으로 수행해요. auths에 경로별로 여러 개체를 나열해도 되고, 컨테이너가 지정한 이미지를 pull할 때 한 인증 소스가 실패하면 두 인증 소스를 모두 시도해요.
imagePullPolicy가 IfNotPresent 또는 Never면 로컬에 미리 받아 둔(pre-pulled) 이미지가 (각각 우선적으로, 또는 배타적으로) 사용돼요. 다만 이 방식은 노드 구성을 직접 통제할 수 있을 때만 어울려요. 클라우드 제공자가 노드를 자동 교체하는 환경에선 신뢰할 수 없어요. 클러스터의 모든 노드에 같은 프리-pull 이미지가 준비돼 있어야 하고요.
imagePullSecrets 예시
파드 정의에 imagePullSecrets 섹션을 추가하면 만들 수 있어요. 배열의 각 항목은 같은 네임스페이스의 Secret 하나만 참조할 수 있어요:
cat <<EOF > pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: foo
namespace: awesomeapps
spec:
containers:
- name: foo
image: janedoe/awesomeapp:v1
imagePullSecrets:
- name: myregistrykey
EOF
cat <<EOF >> ./kustomization.yaml
resources:
- pod.yaml
EOF
프라이빗 레지스트리를 쓰는 파드마다 이 작업을 반복해야 하는데, 이걸 ServiceAccount 리소스에 imagePullSecrets 섹션을 지정해서 자동화할 수 있어요. 노드별 .docker/config.json과 함께 사용하면 자격 증명이 병합되고요.
언제 어떤 방식이 잘 맞나
- 공개 이미지만 쓰는 클러스터 → 공개 레지스트리 사용. 별도 설정 불필요. 일부 클라우드는 공개 이미지를 캐시·미러링해 pull 시간을 줄여 줘요.
- 회사 밖에 숨겨야 할 독점 이미지가 있고 모든 클러스터 사용자에겐 보여줘야 한다면 → 호스팅 프라이빗 레지스트리, 방화벽 뒤의 내부 레지스트리(읽기 공개), 또는
imagePullSecrets를 선택해요. - 일부 이미지만 엄격한 접근 제어가 필요하다면 → AlwaysPullImages admission controller를 켜고, 민감 데이터는 이미지에 담지 말고 Secret 리소스로 옮겨요.
- 테넌트마다 자기 프라이빗 레지스트리가 필요한 멀티 테넌트 → 테넌트별로 자격 증명을 만들어 Secret에 저장하고 각 네임스페이스에 Secret을 퍼뜨린 뒤, 각 테넌트가 그 Secret을
imagePullSecrets에 추가하게 해요.
여러 레지스트리에 접근해야 하면 레지스트리당 Secret 하나씩 만들면 돼요.
레거시 kubelet 자격 증명 제공자
예전 쿠버네티스에는 kubelet이 클라우드 제공자 자격 증명에 직접 통합돼 있어서, 레지스트리용 자격 증명을 동적으로 가져올 수 있었어요. ACR(Azure)·ECR(Elastic)·GCR(Google) 세 가지 내장 구현이 있었는데, 쿠버네티스 1.26부터 이 레거시 메커니즘은 제거됐어요. 이제는 노드마다 kubelet 이미지 자격 증명 제공자를 구성하거나, imagePullSecrets와 Secret 하나 이상으로 pull 자격 증명을 지정해야 해요.
더 알아보기 (Learn more)
- OCI Image Manifest Specification 읽어보기
- 컨테이너 이미지 가비지 컬렉션 알아보기
- 프라이빗 레지스트리에서 이미지 받기 실습하기