- Published on
Azure AKS ImagePullBackOff - ACR 권한·DNS 점검
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스나 매니지드 쿠버네티스라고 해도 ImagePullBackOff는 여전히 자주 만나는 장애입니다. 특히 Azure AKS에서는 이미지가 Azure Container Registry(ACR)에 있는데도 갑자기 풀에 실패하거나, 노드 재이미징/스케일아웃 이후에만 실패하는 식으로 나타나 원인 파악이 까다롭습니다.
이 글은 AKS에서 ImagePullBackOff가 떴을 때 ACR 권한 문제인지, 아니면 DNS/네트워크 문제인지 빠르게 분리해서 진단하는 흐름을 제공합니다. (ECR의 401/429처럼 레지스트리 인증/제한 이슈가 공통적으로 보이는 패턴도 있으니, 레지스트리 일반론은 K8s ImagePullBackOff 401·429 - ECR·레지스트리 제한 해결도 함께 참고하면 좋습니다.)
증상에서 시작: 에러 메시지로 1차 분류
먼저 파드 이벤트를 확인합니다.
kubectl describe pod -n <NAMESPACE> <POD_NAME>
kubectl get events -n <NAMESPACE> --sort-by=.metadata.creationTimestamp
Events에서 자주 보이는 키워드별로 1차 분류가 됩니다.
unauthorized: authentication required,401계열: ACR 인증/권한/자격 증명 문제 가능성이 큼dial tcp: lookup ... no such host,i/o timeout,context deadline exceeded: DNS 또는 네트워크 경로 문제 가능성이 큼x509: certificate signed by unknown authority: 프록시/SSL 검사, 커스텀 CA, egress 장비 개입 가능성
이제 ACR 권한과 DNS/네트워크를 각각 확실히 검증해봅니다.
1) ACR 권한 문제: AKS가 어떤 정체성으로 풀하는지부터 확인
AKS에서 ACR을 풀 때는 보통 아래 중 하나로 인증합니다.
- AKS 클러스터(또는 kubelet)가 가진 Managed Identity로 ACR에 접근
imagePullSecrets로 Docker 레지스트리 크리덴셜을 전달- (권장하지 않음) ACR의 admin user를 켜고 ID/PW로 접근
운영에서 가장 흔한 실패는 1번(Managed Identity) 구성에서 역할 할당이 빠졌거나, attach가 누락되었거나, 노드풀/클러스터 정체성이 바뀌었는데 권한이 안 따라온 경우입니다.
A. 현재 클러스터가 ACR에 attach 되어 있는지 확인
Azure CLI로 확인합니다.
az aks show -g <AKS_RG> -n <AKS_NAME> --query "addonProfiles" -o json
az aks show -g <AKS_RG> -n <AKS_NAME> --query "identity" -o json
가장 쉬운 해결은 아래처럼 attach를 다시 거는 것입니다.
az aks update -g <AKS_RG> -n <AKS_NAME> --attach-acr <ACR_NAME>
이 작업은 내부적으로 AKS가 사용하는 정체성에 AcrPull 역할을 부여합니다.
attach가 먹히지 않는 케이스
- ACR이 다른 구독/테넌트에 있음
- 권한을 부여할 수 있는 권한(예: Owner 또는 User Access Administrator)이 현재 계정에 없음
이 경우는 수동 역할 할당으로 넘어갑니다.
B. ACR에 AcrPull 역할이 있는지 수동 점검
ACR 리소스 ID를 구합니다.
ACR_ID=$(az acr show -n <ACR_NAME> -g <ACR_RG> --query id -o tsv)
echo $ACR_ID
이제 AKS가 실제로 이미지 풀에 사용하는 정체성을 확인해야 합니다. AKS는 구성에 따라 kubelet identity가 따로 존재할 수 있습니다.
az aks show -g <AKS_RG> -n <AKS_NAME> --query "identityProfile.kubeletidentity" -o json
출력에서 objectId 또는 clientId를 확보한 뒤 역할 할당을 확인합니다.
az role assignment list --scope $ACR_ID --assignee <KUBELET_OBJECT_ID_OR_CLIENT_ID> -o table
없다면 AcrPull을 부여합니다.
az role assignment create \
--assignee <KUBELET_OBJECT_ID_OR_CLIENT_ID> \
--role AcrPull \
--scope $ACR_ID
역할 할당 후에도 바로 안 풀리면, 노드에 캐시된 실패 상태 때문에 재시도 타이밍이 늦을 수 있어 파드를 재시작하거나 디플로이를 롤아웃 재시작합니다.
kubectl rollout restart deploy -n <NAMESPACE> <DEPLOYMENT_NAME>
C. imagePullSecrets를 쓰는 경우: 시크릿 타입과 서버 주소 확인
사설 레지스트리 시크릿은 보통 아래 형태입니다.
kubectl create secret docker-registry acr-pull \
--docker-server=<ACR_NAME>.azurecr.io \
--docker-username=<USERNAME> \
--docker-password=<PASSWORD> \
-n <NAMESPACE>
매니페스트에는 다음이 필요합니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
template:
spec:
imagePullSecrets:
- name: acr-pull
containers:
- name: app
image: <ACR_NAME>.azurecr.io/myapp:2026-02-24
여기서 흔한 실수:
--docker-server에https://를 붙이거나, 오타로 다른 호스트를 넣음- 시크릿을
default네임스페이스에 만들고 실제 파드는 다른 네임스페이스에 있음 - ACR admin user를 꺼놨는데 admin 계정 기반으로 시크릿을 만들어 둠
시크릿이 올바른지 확인합니다.
kubectl get secret -n <NAMESPACE> acr-pull -o jsonpath='{.type}'
# 기대값: kubernetes.io/dockerconfigjson
2) DNS/네트워크 문제: 레지스트리 도메인 해석과 443 연결을 분리 검증
권한이 맞는데도 i/o timeout이나 no such host가 보이면, 대개는 DNS 해석 실패 또는 egress 차단입니다. AKS는 노드에서 레지스트리로 나가는 경로가 막히면 이미지 풀 자체가 실패합니다.
A. 노드/파드에서 DNS가 ACR FQDN을 해석하는지 확인
가장 간단한 방법은 임시 디버그 파드를 띄워서 nslookup을 해보는 것입니다.
kubectl run -n <NAMESPACE> dns-debug \
--image=busybox:1.36 \
--restart=Never \
--command -- sh -c "sleep 3600"
kubectl exec -n <NAMESPACE> -it dns-debug -- nslookup <ACR_NAME>.azurecr.io
- 여기서
SERVFAIL,NXDOMAIN이 나오면 CoreDNS 또는 VNet DNS 설정을 의심합니다. - 응답이 매우 느리거나 간헐적으로 실패하면, 사용자 정의 DNS 서버/포워더의 성능 또는 방화벽 정책을 의심합니다.
CoreDNS 상태도 확인합니다.
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=200
B. DNS는 되는데 연결이 안 된다면: 443 egress 차단 여부 확인
DNS가 정상이어도 443이 막혀 있으면 풀은 실패합니다. busybox는 TLS 테스트가 제한적이니 curl 가능한 이미지로 확인합니다.
kubectl run -n <NAMESPACE> net-debug \
--image=curlimages/curl:8.6.0 \
--restart=Never \
--command -- sh -c "sleep 3600"
kubectl exec -n <NAMESPACE> -it net-debug -- \
sh -c "curl -I https://<ACR_NAME>.azurecr.io/v2/"
정상이라면 대개 401 Unauthorized가 떠야 합니다. 이 401은 오히려 좋은 신호입니다. 즉, 네트워크는 살아 있고 이제 인증만 남았다는 뜻입니다.
반대로 timeout, could not resolve host, SSL 관련 오류가 나오면 네트워크/보안 장비를 봐야 합니다.
C. AKS가 프라이빗 클러스터/제한 egress인 경우 체크리스트
다음 구성에서는 이미지 풀이 특히 자주 깨집니다.
- 프라이빗 AKS + 강한 egress 제어(방화벽, UDR)
- ACR에 Private Endpoint를 붙였는데, DNS 존 링크가 누락됨
- 사용자 정의 DNS 서버를 VNet에 지정했는데, Azure Private DNS를 포워딩하지 않음
ACR을 Private Endpoint로 쓴다면, ACR FQDN이 퍼블릭 IP가 아니라 프라이빗 IP로 해석돼야 합니다. nslookup 결과 IP 대역이 기대와 다르면 DNS 존 구성을 다시 봐야 합니다.
3) 실제 현장에서 많이 만나는 원인 6가지
1. --attach-acr는 했는데 노드풀 추가 후에만 실패
노드풀이 추가되면서 kubelet identity가 바뀌거나, 권한 전파가 안 된 상태에서 새 노드가 뜨면 실패가 발생할 수 있습니다. 해결은 kubeletidentity에 AcrPull이 실제로 있는지 확인하는 것입니다.
2. ACR 이름은 맞는데 이미지 경로가 틀림
예를 들어 리포지토리명이 my-app인데 myapp으로 참조하거나, 태그가 존재하지 않으면 ErrImagePull로 시작해 ImagePullBackOff로 전환됩니다.
# 이미지 존재 여부는 로컬에서 ACR 로그인 후 확인 가능
az acr login -n <ACR_NAME>
# 또는
az acr repository show-tags -n <ACR_NAME> --repository myapp -o table
3. imagePullSecrets를 만들었지만 ServiceAccount에 연결 안 함
여러 워크로드에서 공통으로 쓰려면 ServiceAccount에 지정하는 방식이 안전합니다.
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: <NAMESPACE>
imagePullSecrets:
- name: acr-pull
그리고 Deployment에서 serviceAccountName을 지정합니다.
spec:
template:
spec:
serviceAccountName: app-sa
4. CoreDNS는 정상인데 특정 노드에서만 실패
노드 단위로 egress가 막혔거나, 노드가 붙은 서브넷 NSG/UDR이 다를 수 있습니다. 이 경우 실패하는 파드가 올라간 노드를 확인합니다.
kubectl get pod -n <NAMESPACE> <POD_NAME> -o wide
5. ACR 방화벽(네트워크 규칙)에서 AKS 출구 IP를 허용하지 않음
ACR에 네트워크 제한을 걸어두면, AKS의 NAT 게이트웨이/아웃바운드 IP가 허용 목록에 없을 때 풀에 실패합니다. 이 경우는 권한이 맞아도 연결 자체가 안 됩니다.
6. 프록시 환경에서 인증서 검사로 TLS 실패
기업망에서 SSL inspection이 있으면 x509 오류가 날 수 있습니다. 노드 OS에 신뢰할 CA를 배포하거나, 네트워크 정책을 조정해야 합니다.
4) 빠른 해결 루틴(현장용)
아래 순서대로 하면 보통 10분 내에 범위를 좁힐 수 있습니다.
kubectl describe pod이벤트에서401인지timeout인지 확인- 디버그 파드로
nslookup <ACR_NAME>.azurecr.io실행 - 디버그 파드로
curl -I https://<ACR_NAME>.azurecr.io/v2/실행 - 네트워크가 정상이면 AKS 정체성 확인 후
AcrPull역할 점검 - 필요 시
az aks update --attach-acr로 재연결 - 파드 롤아웃 재시작으로 재풀 트리거
5) 운영 안정화 팁: 재발을 줄이는 설정
- Managed Identity 기반으로 통일하고,
imagePullSecrets는 예외적으로만 사용 - 노드풀 추가/교체 자동화 시, ACR 권한 점검을 파이프라인에 포함
- 프라이빗 엔드포인트를 쓴다면, Private DNS 존 링크/포워딩 구조를 문서화
- 장애 시나리오를 위해
dns-debug,net-debug같은 진단 커맨드를 런북으로 준비
쿠버네티스 네트워크 문제는 결국 “DNS 해석”과 “TCP 연결”로 분해하면 빨리 풀립니다. 특히 gRPC나 L7 트래픽 튜닝을 하다 보면 네트워크 계층 이슈와 애플리케이션 이슈가 섞여 보이기도 하는데, 그런 상황에서의 분리 진단 관점은 Kubernetes gRPC UNAVAILABLE·RST_STREAM 원인과 Envoy·NGINX 대응도 도움이 됩니다.
부록: 명령어 모음
# 1) 파드 이벤트
kubectl describe pod -n <NAMESPACE> <POD_NAME>
# 2) CoreDNS 상태
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=200
# 3) DNS 테스트
kubectl run -n <NAMESPACE> dns-debug --image=busybox:1.36 --restart=Never --command -- sh -c "sleep 3600"
kubectl exec -n <NAMESPACE> -it dns-debug -- nslookup <ACR_NAME>.azurecr.io
# 4) HTTPS 연결 테스트
kubectl run -n <NAMESPACE> net-debug --image=curlimages/curl:8.6.0 --restart=Never --command -- sh -c "sleep 3600"
kubectl exec -n <NAMESPACE> -it net-debug -- sh -c "curl -I https://<ACR_NAME>.azurecr.io/v2/"
# 5) ACR attach
az aks update -g <AKS_RG> -n <AKS_NAME> --attach-acr <ACR_NAME>
# 6) kubelet identity 확인
az aks show -g <AKS_RG> -n <AKS_NAME> --query "identityProfile.kubeletidentity" -o json
# 7) 역할 할당 확인/추가
ACR_ID=$(az acr show -n <ACR_NAME> -g <ACR_RG> --query id -o tsv)
az role assignment list --scope $ACR_ID --assignee <KUBELET_OBJECT_ID_OR_CLIENT_ID> -o table
az role assignment create --assignee <KUBELET_OBJECT_ID_OR_CLIENT_ID> --role AcrPull --scope $ACR_ID