Pod를 배포했는데 ImagePullBackOff에서 멈췄다면 kubectl logs부터 열어도 애플리케이션 로그가 없을 수 있습니다. 이미지를 받는 단계에서 실패해 프로그램이 아직 시작되지 않았기 때문이에요. 이때는 Pod의 Events에서 마지막 실패 메시지를 확인하는 것이 출발점입니다.
아래 예시는 접근 권한이 있는 Kubernetes 클러스터를 전제로 합니다. demo는 네임스페이스, web은 Deployment와 컨테이너 이름, POD_NAME은 실제 Pod 이름으로 바꿉니다. 사설 레지스트리 주소 registry.example.com도 설명용 예시이며 실제로 접속할 서버가 아닙니다.
이 글의 목차
Pod가 어느 노드에서 무엇을 받으려는지 확인합니다
kubectl get pods -n demo -o wide
kubectl describe pod POD_NAME -n demo
kubectl get events -n demo \
--field-selector involvedObject.name=POD_NAME \
--sort-by=.metadata.creationTimestamp
ImagePullBackOff는 이미지를 가져오지 못해 재시도 간격을 늘리고 있다는 상태입니다. 처음에는 ErrImagePull이 보였다가 바뀔 수 있습니다. 상태 이름을 반복해서 보기보다 Events의 Failed 메시지에서 주소·인증·통신 중 어느 지점이 실패했는지 읽어 보세요.
확인할 대상은 Pod가 배치된 노드입니다. 내 노트북에서 이미지를 받을 수 있어도 그 노드에서는 DNS·프록시·인증서 조건이 다를 수 있어요. 이벤트는 보존 기간에 따라 사라질 수 있으므로 장애가 발생했을 때 Pod 이름, UID, 노드, 실패 시각을 함께 기록하는 편이 좋습니다.
| 이벤트의 단서 | 먼저 확인할 곳 |
|---|---|
not found, manifest unknown | 레지스트리·프로젝트 경로·이미지 태그 존재 여부 |
unauthorized, pull access denied | 이미지 경로와 계정 권한·인증 정보. 비공개 저장소의 경로 오류도 함께 확인 |
FailedToRetrieveImagePullSecret | Secret 이름과 Pod 네임스페이스 |
no such host, i/o timeout | 해당 노드의 DNS·프록시·방화벽·레지스트리 상태 |
x509: certificate signed by unknown authority | 노드의 컨테이너 런타임이 신뢰하는 CA |
no matching manifest, HTTP 429 | 노드 아키텍처 또는 레지스트리 요청 제한 |
이미지 이름과 태그는 실행 중인 Pod에서 읽습니다
kubectl get pod POD_NAME -n demo \
-o jsonpath='{range .spec.initContainers[*]}init/{.name}{": "}{.image}{"\n"}{end}{range .spec.containers[*]}{.name}{": "}{.image}{"\n"}{end}'
로컬 YAML 파일에 적힌 이름과 실제 Pod의 이름이 같다고 가정하지 않습니다. Helm 값, Kustomize, GitOps 저장소, 배포 파이프라인에서 다른 태그를 넣었을 수 있습니다. init container의 이미지가 실패해도 본문 컨테이너는 시작되지 않으므로 함께 확인합니다.
이미지를 바꿀 때는 Pod를 직접 편집하기보다 소유한 Deployment 등 관리 리소스의 템플릿을 수정합니다. 아래는 Deployment web의 컨테이너 web에 승인된 이미지가 실제로 존재한다는 전제의 예시입니다. 운영 환경에서는 검증한 태그나 digest를 사용합니다.
kubectl set image deployment/web \
web=registry.example.com/team/web:1.2.3 \
-n demo
Argo CD 같은 GitOps 도구가 관리한다면 선언의 원본 저장소에서 이미지를 수정합니다. 임시로 실행한 kubectl set image가 다시 되돌려질 수 있기 때문입니다. 단순 오타를 고치려고 여러 Pod를 반복 삭제할 필요는 없습니다.
인증 Secret은 Pod와 같은 네임스페이스에 있어야 합니다
kubectl get pod POD_NAME -n demo \
-o jsonpath='{.spec.imagePullSecrets[*].name}{"\n"}'
kubectl get secret regcred -n demo \
-o jsonpath='{.type}{"\n"}'
imagePullSecrets에 등록한 이름이 맞고 Secret이 같은 네임스페이스에 있는지 확인합니다. regcred는 예시 이름입니다. Secret이 존재해도 계정 권한이 없거나 토큰이 만료됐다면 받을 수 없습니다. 로컬의 docker login이 성공했다고 그 인증이 Kubernetes에 자동 전달되는 것도 아닙니다.
새 Secret이 필요한 경우에는 레지스트리용 Docker config 파일을 안전한 경로에 준비하고 아래처럼 만들 수 있습니다. 파일에는 해당 레지스트리의 유효한 인증이 들어 있어야 합니다. 예시 경로는 실제 파일 경로로 바꾸며, Secret 생성 권한이 필요합니다.
kubectl create secret generic regcred \
--namespace demo \
--type=kubernetes.io/dockerconfigjson \
--from-file=.dockerconfigjson=/secure/registry/config.json
로컬 Docker config가 외부 credential helper만 가리키면 인증 값이 파일 안에 없을 수 있습니다. 그런 파일을 복사하는 것만으로는 충분하지 않습니다. 실제 배포용 인증을 준비하고, 원본 파일·Secret YAML·토큰을 Git 저장소나 공개 로그에 넣지 않습니다. 이미 같은 이름의 Secret이 있다면 이 생성 명령은 실패하므로 조직의 갱신 절차로 처리합니다.
Deployment의 Pod 템플릿에는 아래 항목을 병합합니다. 전체 Deployment 문서가 아니며 이미지나 볼륨 등의 기존 설정을 지우고 대체하지 않습니다. ServiceAccount를 통해 연결한 구성이라면 실제 Pod에 반영된 항목까지 확인하세요.
spec:
template:
spec:
imagePullSecrets:
- name: regcred
통신 문제는 이미지를 받는 노드에서 확인합니다
이미지 다운로드는 노드의 kubelet과 컨테이너 런타임이 수행합니다. 실행 중인 다른 Pod에서 레지스트리에 접속된다고 해서 노드의 이미지 다운로드 경로도 정상이라고 판단하지 않습니다. Pod 내부 DNS와 노드가 사용하는 DNS 조건이 다를 수 있어요.
관리 권한과 운영 절차가 허용하는 경우, 문제가 난 노드에서 아래처럼 확인합니다. 주소는 실제 레지스트리로 바꿉니다. curl이 사용한 CA와 컨테이너 런타임의 신뢰 저장소가 다를 수 있으므로 참고 결과로 읽습니다.
getent hosts registry.example.com
curl --connect-timeout 5 --max-time 10 \
-I https://registry.example.com/v2/
sudo journalctl -u kubelet --since '30 minutes ago' --no-pager
# containerd를 사용하는 노드인 경우
sudo journalctl -u containerd --since '30 minutes ago' --no-pager
인증이 필요한 레지스트리의 /v2/에서 HTTP 401이 오는 것은 통신이 도달했다는 단서일 수 있습니다. 그 응답만으로 이미지 읽기 권한까지 확인한 것은 아닙니다. 이미지 레이어를 다른 도메인에서 내려받는 서비스도 있으므로 이벤트와 런타임 로그에 나온 실제 목적지도 확인합니다.
사설 CA 오류는 해당 런타임에서 신뢰하는 인증서 설정을 고칩니다. TLS 검증을 끄는 설정을 기본 해결책으로 사용하지 않습니다. 노드 런타임 재시작이 필요한 변경은 다른 Pod에도 영향을 줄 수 있으므로 배수·정비 절차와 함께 계획합니다.
imagePullPolicy를 바꾸기 전에 배포 조건을 확인합니다
Always는 인증이나 네트워크 오류를 해결하지 않습니다. IfNotPresent는 노드에 필요한 이미지가 있을 때 다운로드를 건너뛸 수 있지만 새 노드에는 여전히 이미지가 필요합니다. Never는 이미지가 없는 노드에서 다른 실패를 만들 수 있어요.
폐쇄망이라면 모든 대상 노드에 이미지가 준비되는 방식이나 내부 레지스트리 경로를 먼저 정합니다. 개발 PC의 Docker에 이미지를 내려받은 것과 Kubernetes 노드 런타임에 준비한 것은 별개입니다. 다운로드 정책을 바꾸는 일로 누락된 이미지와 인증 문제를 가리지 않도록 합니다.
수정 후에는 새 Pod의 이벤트와 서비스 응답을 봅니다
kubectl rollout status deployment/web -n demo --timeout=120s
kubectl get pods -n demo -o wide
kubectl get events -n demo --sort-by=.metadata.creationTimestamp
정상 이미지를 받았는지 확인한 뒤 컨테이너가 시작됐는지, Ready 상태인지, 실제 요청에 응답하는지 차례로 봅니다. 이미지를 받은 뒤 CrashLoopBackOff로 바뀌면 이제 애플리케이션 시작 단계의 로그와 종료 원인을 조사합니다. 상태가 바뀌었다는 것만으로 전체 복구라고 기록하지 않습니다.
이 글은 Kubernetes 공식 문서에 근거한 진단 절차입니다. 실제 클러스터에서 사설 레지스트리·인증서 장애를 재현한 결과는 아닙니다. Pod 실행 이후의 진단은 Pod 장애 진단과 노드 drain에서 이어서 볼 수 있습니다.

![[쿠버네티스 학습 01] Windows 11·VMware와 Rocky Linux 실습 환경](https://blog.maruops.com/wp-content/uploads/2026/10/maruops-photo-107-768x502.jpg)
![[쿠버네티스 학습 02] Rocky Linux 고정 IP·시간·커널 설정](https://blog.maruops.com/wp-content/uploads/2026/10/maruops-photo-088-768x966.jpg)
![[쿠버네티스 학습 03] containerd 2.x와 kubeadm 설치](https://blog.maruops.com/wp-content/uploads/2026/10/maruops-photo-000-768x510.jpg)