- Published on
AKS ImagePullBackOff 401 - ACR 연결 진단
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 멀쩡하고 이미지 태그도 존재하는데, AKS 파드가 ImagePullBackOff 로 멈추고 이벤트에는 401 Unauthorized 가 찍히는 상황은 생각보다 흔합니다. 문제는 대부분 “이미지 자체”가 아니라 AKS 노드가 ACR에 인증하는 경로(kubelet의 pull 자격증명, Managed Identity 권한, ACR 방화벽/프라이빗 엔드포인트, DNS/라우팅) 어딘가가 끊긴 것입니다.
이 글은 401 이라는 결과만 보고 무작정 Secret을 다시 만들기보다, 어느 레이어에서 인증이 실패했는지를 빠르게 좁히는 진단 순서와 명령어를 제공합니다.
관련해서 일반적인 원인 범주는 아래 글도 함께 참고하면 좋습니다.
- K8s ImagePullBackOff·ErrImagePull 원인 9가지
- (운영 관점의 원인 추적법이 필요하면) systemd 서비스가 계속 재시작될 때 원인 추적법
증상 정리: “401” 이 의미하는 것
401 Unauthorized 는 대개 아래 중 하나입니다.
- ACR 자격증명 자체가 없음/틀림:
imagePullSecrets누락, Secret 값 오타, 만료된 토큰 등 - AKS가 ACR에 붙는 ID 경로가 바뀜: 클러스터 업그레이드/노드풀 교체로 kubelet identity가 달라졌는데 권한이 그대로
- ACR 방화벽/프라이빗 링크 정책으로 인해 인증 플로우가 깨짐: 네트워크 차단이
401로 보이는 케이스가 있음(특히 프록시/프라이빗 DNS 꼬임) - ACR 권한 부족:
AcrPull역할이 없는 경우
핵심은 “어떤 주체(Principal)가 ACR에 접근했는지”를 확인하는 것입니다.
1단계: Pod 이벤트에서 정확한 에러 문구 확보
가장 먼저 파드 이벤트를 보고, kubelet이 어떤 레지스트리 URL로 접근했는지와 에러 지점을 확인합니다.
kubectl describe pod -n <namespace> <pod-name>
MDX 빌드 에러 방지를 위해 위의 <namespace> 같은 표기는 반드시 인라인 코드로 감쌌습니다.
이때 아래를 체크하세요.
- 이미지 경로가
myregistry.azurecr.io/myapp:tag형태로 정확한지 - 에러가
failed to authorize인지,unauthorized: authentication required인지 x509같은 인증서 에러가 섞여 있는지(이 경우는 401이 아닌 TLS 문제일 수 있음)
추가로 해당 파드의 실제 이미지/Secret 설정을 바로 확인합니다.
kubectl get pod -n <namespace> <pod-name> -o yaml
확인 포인트:
spec.imagePullSecrets존재 여부serviceAccountName이 지정되어 있다면 해당 SA에imagePullSecrets가 붙어 있는지
2단계: ACR이 “사설 레지스트리”인지, AKS-ACR Attach 모델인지 구분
AKS에서 ACR을 쓰는 방식은 크게 2가지입니다.
- AKS와 ACR을 attach 해서, AKS의 관리 ID(또는 kubelet identity)에
AcrPull을 부여하는 방식 - Docker registry Secret 을 만들어
imagePullSecrets로 당겨오는 방식
두 방식이 섞이면(예: attach가 되어 있다고 믿고 Secret을 제거) 바로 401 로 이어집니다.
현재 클러스터가 ACR attach가 되어 있는지 확인합니다.
az aks show -g <rg> -n <aks-name> --query "addonProfiles" -o json
attach 여부를 직접적으로 보여주지 않는 경우가 많아서, 보통은 역으로 권한을 확인하는 방식이 더 확실합니다.
3단계: AKS가 ACR에 접근하는 “주체” 확인 (kubelet identity)
최근 AKS는 노드풀/kubelet이 사용하는 ID가 별도로 존재할 수 있습니다. 여기서 가장 흔한 함정은:
- 클러스터 생성 당시엔 하나의 ID였는데
- 노드풀 추가/교체, 업그레이드 후 kubelet identity가 바뀌었고
- ACR에는 옛 ID만
AcrPull이 남아있는 상황
kubelet identity 정보를 확인합니다.
az aks show -g <rg> -n <aks-name> --query "identityProfile.kubeletidentity" -o json
출력에서 clientId 또는 objectId 를 확보합니다.
4단계: ACR에 AcrPull 역할이 실제로 부여되어 있는지 확인
ACR 리소스 ID를 구합니다.
ACR_ID=$(az acr show -g <acr-rg> -n <acr-name> --query id -o tsv)
echo $ACR_ID
이제 kubelet identity(또는 클러스터 identity)에 AcrPull 이 있는지 봅니다.
az role assignment list --scope $ACR_ID --assignee <principal-id> -o table
여기서 <principal-id> 는 위에서 얻은 objectId 또는 clientId 를 사용합니다(환경에 따라 다르게 먹힐 수 있어 둘 다 시도).
AcrPull 이 없다면 부여합니다.
az role assignment create --assignee <principal-id> --role AcrPull --scope $ACR_ID
권한 부여 후에도 즉시 반영되지 않는 경우가 있어 수 분 정도의 전파 시간을 고려합니다.
5단계: ACR Admin user 기반 Secret을 쓴다면(권장 X) 값 검증
조직에 따라 빠른 임시조치로 ACR Admin user를 켜고 Secret을 쓰는 경우가 있습니다. 이 경우 401은 대개 Secret 불일치입니다.
Admin user가 켜져 있는지 확인:
az acr show -n <acr-name> --query "adminUserEnabled" -o tsv
Secret이 올바른지 검증하려면, Secret 내용을 직접 복호화해서 dockerconfigjson 의 서버 주소가 정확한지 확인합니다.
kubectl get secret -n <namespace> <secret-name> -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d
확인 포인트:
auths키 아래 레지스트리 주소가myregistry.azurecr.io로 정확한지- 사용자/비밀번호가 실제 ACR 자격증명과 일치하는지
Secret을 새로 만들 땐 아래처럼 합니다.
kubectl create secret docker-registry acr-pull \
-n <namespace> \
--docker-server=<acr-name>.azurecr.io \
--docker-username=<username> \
--docker-password=<password>
그리고 Deployment 또는 ServiceAccount에 연결합니다.
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: default
imagePullSecrets:
- name: acr-pull
6단계: ACR 방화벽/Private Endpoint 사용 시 네트워크 진단
ACR에 방화벽이 걸려 있거나 Private Endpoint를 쓰면, “인증이 틀려서 401” 이 아니라 “경로가 달라져서 401처럼 보이는” 케이스가 생깁니다. 특히 다음 조합에서 자주 터집니다.
- ACR은 Private Endpoint
- AKS는 별도 VNet 또는 노드 서브넷 DNS가 공용으로 향함
privatelink.azurecr.ioDNS 존 연결 누락
ACR 네트워크 설정을 확인합니다.
az acr show -n <acr-name> --query "networkRuleSet" -o json
AKS 노드에서 DNS가 어디로 해석되는지 보는 게 핵심입니다. 가장 간단한 방법은 디버그 파드를 띄워서 확인하는 것입니다.
kubectl run net-debug -n <namespace> --rm -it --image=busybox:1.36 --restart=Never -- sh
파드 내부에서:
nslookup <acr-name>.azurecr.io
wget -S -O - https://<acr-name>.azurecr.io/v2/ 2>&1 | head
여기서도 <acr-name> 같은 표기는 인라인 코드로 감싸야 안전합니다.
정상이라면 /v2/ 는 보통 401 을 반환합니다. 중요한 건 “응답이 온다”는 사실입니다.
nslookup결과가 공인 IP로 가는데 ACR이 Private Endpoint만 허용이면 당연히 실패wget이 timeout이면 네트워크/NSG/UDR 문제- 인증 흐름 이전에 연결 자체가 안 되면, AKS 서브넷에서 Private Endpoint로 라우팅/DNS를 먼저 고쳐야 합니다
7단계: 이미지 참조/태그/레포 존재 여부 재확인 (의외로 흔함)
401처럼 보여도 실제로는 이미지 경로가 틀린 경우가 있습니다(특히 멀티 ACR 환경에서 레지스트리명을 바꿔치기).
ACR에 리포지토리와 태그가 있는지 확인합니다.
az acr repository list -n <acr-name> -o table
az acr repository show-tags -n <acr-name> --repository <repo> -o table
GitOps나 Helm values에서 레지스트리 prefix가 환경별로 바뀌는 경우, dev 는 되는데 prod 만 401이 나는 패턴이 자주 나옵니다.
8단계: 가장 흔한 “원인-해결” 매핑
운영에서 반복되는 패턴을 빠르게 매핑하면 다음과 같습니다.
노드풀 교체 후 401
- 원인: kubelet identity 변경, 기존 ID에만
AcrPull존재 - 해결: 새 kubelet identity에
AcrPull부여
ACR 방화벽 켠 뒤 401/timeout
- 원인: AKS 노드 서브넷이 허용 IP/서브넷에 없음, Private Endpoint DNS 미구성
- 해결: 네트워크 룰 업데이트 또는 Private DNS 존 연결, 노드 서브넷 라우팅 점검
특정 네임스페이스만 401
- 원인: 해당 네임스페이스의 ServiceAccount에
imagePullSecrets누락 - 해결: SA에 Secret 연결 또는 Deployment에
imagePullSecrets명시
로컬에서는 pull 되는데 AKS만 401
- 원인: 로컬은
az acr login으로 토큰이 있지만, 클러스터는 권한/Secret 없음 - 해결: attach 모델이면
AcrPull확인, Secret 모델이면 Secret 재생성
9단계: 재발 방지 체크리스트
- ACR 접근은 가능하면 AKS-ACR attach + Managed Identity +
AcrPull로 표준화 - 노드풀 추가/교체 시
identityProfile.kubeletidentity변경 여부를 운영 체크리스트에 포함 - Private Endpoint 사용 시
privatelink.azurecr.ioDNS 존과 VNet 링크를 IaC로 고정 - 배포 파이프라인에서 이미지 경로를 조립할 때
registry/repository:tag를 로그로 남겨, 오타를 빠르게 발견
부록: 문제 재현/확인에 유용한 최소 YAML
아래는 imagePullSecrets 를 명시한 Deployment 예시입니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: hello
template:
metadata:
labels:
app: hello
spec:
imagePullSecrets:
- name: acr-pull
containers:
- name: hello
image: myregistry.azurecr.io/hello:1.0.0
ports:
- containerPort: 8080
이 YAML로도 401이 난다면, Secret 문제라기보다 ACR 네트워크 정책 또는 클러스터가 사용하는 주체의 권한(AcrPull) 문제일 확률이 높습니다.
정리하면, AKS의 ImagePullBackOff 401 은 “비밀번호 틀림” 한 줄로 끝나는 이슈가 아니라, 어떤 ID가 어떤 경로로 ACR에 접근하는지를 분해해 보는 것이 핵심입니다. 위 순서대로 Pod 이벤트 → SA/Secret → kubelet identity → AcrPull 역할 → ACR 네트워크/DNS 를 확인하면 대부분 30분 내에 원인을 좁힐 수 있습니다.