- Published on
K8s ImagePullBackOff - 프라이빗 레지스트리 인증 오류 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버는 멀쩡한데 Pod만 ImagePullBackOff로 멈추는 상황은 운영에서 꽤 자주 만납니다. 특히 프라이빗 레지스트리(사내 Harbor, ECR, GCR, ACR, GitHub Container Registry 등)를 쓰는 클러스터라면 원인의 상당수가 “인증 정보가 Pod까지 제대로 전달되지 않음”으로 귀결됩니다.
이 글은 ImagePullBackOff가 떴을 때 이벤트에서 인증 실패를 확인하는 방법, 정석적인 imagePullSecrets 구성, ServiceAccount로 기본 적용, 노드 런타임(containerd)·프록시·TLS 이슈까지 한 번에 정리합니다.
1) 먼저 증상 구분: ImagePullBackOff는 결과, 원인은 이벤트에 있다
ImagePullBackOff는 말 그대로 “이미지 pull이 반복 실패해서 backoff 중”이라는 상태일 뿐입니다. 원인(인증 실패, 네트워크, DNS, TLS, 이미지명 오타 등)은 kubectl describe 이벤트에 있습니다.
kubectl get pod -n myns
kubectl describe pod mypod -n myns
이벤트에서 아래 패턴이 보이면 인증/권한 문제일 확률이 큽니다.
Failed to pull image+unauthorized: authentication requiredpull access denied/repository does not exist or may require authorizationdenied: requested access to the resource is denied
반대로 네트워크 계열은 다음처럼 보입니다.
dial tcp ... i/o timeoutx509: certificate signed by unknown authorityno such host
네트워크가 의심되면 먼저 클러스터 환경을 점검하세요. 특히 “DNS는 되는데 외부 HTTPS만 실패” 같은 케이스가 섞이면 원인 추적이 꼬입니다. 관련해서는 EKS에서 Pod DNS는 되는데 외부 HTTPS만 실패할 때도 함께 참고하면 좋습니다.
2) 가장 흔한 원인 5가지 체크리스트
인증 오류로 보일 때, 아래를 순서대로 체크하면 대부분 해결됩니다.
- 이미지 레퍼런스 오타: 레지스트리 호스트/프로젝트/리포/태그
- 레지스트리 자격증명(
dockerconfigjson)이 올바른지 - Secret이 Pod가 있는 네임스페이스에 존재하는지
- Pod spec에
imagePullSecrets가 붙었는지(또는 ServiceAccount에 기본 설정) - 토큰/패스워드 만료 및 권한 범위(scope) 문제
3) 정석: docker-registry Secret 생성과 검증
Kubernetes에서 프라이빗 레지스트리 인증은 보통 kubernetes.io/dockerconfigjson 타입 Secret으로 처리합니다.
3-1) Secret 생성
kubectl create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=myuser \
--docker-password='mypassword-or-token' \
--docker-email=devnull@example.com \
-n myns
--docker-server는 이미지에 쓰는 레지스트리 호스트와 정확히 일치해야 합니다.- 예: 이미지가
registry.example.com/team/app:1.0.0이면 server도registry.example.com
- 예: 이미지가
- GitHub Container Registry는 보통 server가
ghcr.io입니다. - 토큰 기반 레지스트리(ECR/GHCR 등)는 “비밀번호”에 토큰이 들어갑니다.
3-2) Secret 타입/데이터 확인
kubectl get secret regcred -n myns -o yaml
다음이 보이면 정상입니다.
type: kubernetes.io/dockerconfigjsondata:아래.dockerconfigjson:키
실제 내용 확인이 필요하면 디코드해서 auths가 기대대로 들어있는지 봅니다.
kubectl get secret regcred -n myns \
-o jsonpath='{.data..dockerconfigjson}' | base64 -d
여기서도 주의할 점은 레지스트리 키가 이미지의 호스트와 일치해야 한다는 것입니다. 예를 들어 https://registry.example.com처럼 스킴이 붙어 있거나, 포트가 빠져 있거나(registry.example.com:5000), 반대로 포트가 붙어 있으면 일치하지 않아 실패할 수 있습니다.
4) Pod에 imagePullSecrets 적용 (가장 빠른 해결)
Deployment/Pod spec에 imagePullSecrets를 직접 붙입니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: myns
spec:
replicas: 1
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
imagePullSecrets:
- name: regcred
containers:
- name: app
image: registry.example.com/team/app:1.0.0
imagePullPolicy: IfNotPresent
적용 후에는 새 Pod가 뜨면서 pull을 다시 시도합니다.
kubectl rollout restart deploy/app -n myns
kubectl get pod -n myns -w
5) 운영 친화적 방법: ServiceAccount에 기본 imagePullSecrets 설정
매번 Deployment마다 imagePullSecrets를 붙이는 건 실수 유발 포인트입니다. 네임스페이스에서 기본으로 사용하는 ServiceAccount에 imagePullSecrets를 설정해 두면, 해당 SA를 쓰는 Pod는 자동으로 Secret을 상속받습니다.
5-1) default ServiceAccount에 패치
kubectl patch serviceaccount default -n myns \
-p '{"imagePullSecrets": [{"name": "regcred"}]}'
5-2) 전용 ServiceAccount 생성
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: myns
imagePullSecrets:
- name: regcred
그리고 Deployment에서 SA를 지정합니다.
spec:
template:
spec:
serviceAccountName: app-sa
containers:
- name: app
image: registry.example.com/team/app:1.0.0
이 방식은 “새로 만든 워크로드가 pull secret을 빼먹는” 사고를 크게 줄여줍니다.
6) 레지스트리별 자주 터지는 인증 포인트
6-1) AWS ECR
ECR은 토큰이 주기적으로 바뀝니다. 일반적으로는 docker-registry Secret에 고정 비밀번호를 넣는 방식이 아니라, 외부 컨트롤러(예: External Secrets, ECR credential helper, IRSA 기반 솔루션 등)로 주기 갱신을 구성합니다.
ECR에서 권한이 부족하면 이벤트에 denied 또는 unauthorized가 나오는데, 이때는 IAM 정책/AssumeRole 구성이 원인인 경우도 많습니다. IAM 권한 진단은 AWS IAM AssumeRole AccessDenied 원인 10가지 체크리스트가 도움이 됩니다.
6-2) GitHub Container Registry (GHCR)
read:packages권한이 없는 토큰을 쓰면 pull이 실패합니다.- private 패키지면 org 정책에 따라 토큰 스코프가 더 필요할 수 있습니다.
6-3) Harbor / 사내 레지스트리
- 프로젝트가 private인데 계정이
pull권한이 없는 경우 - 레지스트리 앞단에 프록시/Nginx가 있고, 인증 헤더가 누락되는 경우
- 자체 CA 인증서를 쓰는데 노드가 신뢰하지 못하는 경우(TLS)
7) TLS/인증서 문제: x509 에러도 pull 실패로 이어진다
이벤트에 다음이 보이면 인증 정보 문제가 아니라 TLS 신뢰 체인 문제입니다.
x509: certificate signed by unknown authority
해결은 두 갈래입니다.
- 레지스트리 인증서를 공인 CA로 교체
- 노드 런타임(containerd/docker)에 사내 CA를 신뢰하도록 추가
containerd를 쓰는 환경이 많으므로, 일반적인 방향은 노드에 CA를 배포하고 containerd를 재시작하는 것입니다. 배포 방식은 OS/관리도구(AMIs, MDM, Ansible 등)에 따라 다르지만, 핵심은 “Kubelet이 아니라 런타임이 TLS 검증을 한다”는 점입니다.
8) containerd 설정 함정: registry mirror, auth, hosts.toml
최근 Kubernetes는 containerd 조합이 표준에 가깝습니다. 이때 레지스트리 설정이 hosts.toml로 들어가 있는 경우가 있고, 여기서 인증/미러 설정이 꼬이면 Secret이 있어도 pull이 실패할 수 있습니다.
확인 포인트:
- 노드의
/etc/containerd/config.toml - (환경에 따라)
/etc/containerd/certs.d/registry.example.com/hosts.toml
특히 레지스트리 호스트가 registry.example.com:5000처럼 포트가 포함되면 디렉터리명도 동일해야 합니다. 호스트 불일치가 나면 “자격증명을 줘도 다른 호스트로 인식”해서 실패합니다.
9) 실제 트러블슈팅 절차(현장에서 빠르게)
아래 순서로 하면 불필요한 삽질을 줄일 수 있습니다.
9-1) 이벤트에서 에러 문자열 확보
kubectl describe pod mypod -n myns | sed -n '/Events:/,$p'
9-2) 이미지명/레지스트리 호스트 일치 확인
registry.example.com/team/app:tag- Secret의
auths키가registry.example.com인지
9-3) Secret이 같은 네임스페이스에 있는지
kubectl get secret -n myns | grep regcred
9-4) Pod에 secret이 연결됐는지
kubectl get pod mypod -n myns -o jsonpath='{.spec.imagePullSecrets}'
또는 SA 상속 여부 확인:
kubectl get pod mypod -n myns -o jsonpath='{.spec.serviceAccountName}'
kubectl get sa -n myns $(kubectl get pod mypod -n myns -o jsonpath='{.spec.serviceAccountName}') -o yaml
9-5) 토큰 만료/권한 확인
- 레지스트리에서 동일 계정으로 로컬
docker login후docker pull이 되는지 - 조직/프로젝트 권한에서 pull 권한이 있는지
10) 자주 묻는 질문
Q1. Secret 만들었는데도 계속 ImagePullBackOff가 떠요
대부분 아래 중 하나입니다.
- Secret이 다른 네임스페이스에 있음
- Pod/Deployment에
imagePullSecrets가 적용되지 않음(또는 SA 상속 안 됨) - Secret의
auths키가 이미지 호스트와 불일치 - 레지스트리 토큰 만료
Q2. 같은 Secret인데 어떤 노드에서는 되고 어떤 노드에서는 실패해요
인증 정보 문제라기보다 노드 단위 문제일 가능성이 큽니다.
- 노드의 egress/NAT/방화벽 차이
- 프록시 환경변수 차이
- TLS 신뢰 CA 누락
- containerd 설정 차이
네트워크가 섞여 의심될 때는 EKS에서 Pod DNS는 되는데 외부 HTTPS만 실패할 때 같은 관점으로 “DNS, 라우팅, TLS”를 분리해 확인하는 게 효과적입니다.
11) 마무리: 재발 방지 체크
- 네임스페이스별로 전용 ServiceAccount를 만들고
imagePullSecrets를 기본 탑재 - 토큰 만료형 레지스트리(ECR/GHCR 등)는 자동 갱신 체계를 도입
- 레지스트리 호스트 표기(포트 포함 여부)를 조직 표준으로 고정
- 사내 CA 사용 시 노드 런타임의 신뢰 체인 배포를 IaC로 관리
ImagePullBackOff는 흔하지만, 원인만 정확히 분리하면 해결은 단순합니다. 이벤트에서 에러를 확정하고(unauthorized인지 x509인지), Secret-네임스페이스-SA 연결을 점검한 뒤, 런타임/네트워크로 확장해 나가면 빠르게 정상화할 수 있습니다.