Published on

AKS ImagePullBackOff 401 - ACR 연결 진단

Authors

서버가 멀쩡하고 이미지 태그도 존재하는데, AKS 파드가 ImagePullBackOff 로 멈추고 이벤트에는 401 Unauthorized 가 찍히는 상황은 생각보다 흔합니다. 문제는 대부분 “이미지 자체”가 아니라 AKS 노드가 ACR에 인증하는 경로(kubelet의 pull 자격증명, Managed Identity 권한, ACR 방화벽/프라이빗 엔드포인트, DNS/라우팅) 어딘가가 끊긴 것입니다.

이 글은 401 이라는 결과만 보고 무작정 Secret을 다시 만들기보다, 어느 레이어에서 인증이 실패했는지를 빠르게 좁히는 진단 순서와 명령어를 제공합니다.

관련해서 일반적인 원인 범주는 아래 글도 함께 참고하면 좋습니다.

증상 정리: “401” 이 의미하는 것

401 Unauthorized 는 대개 아래 중 하나입니다.

  1. ACR 자격증명 자체가 없음/틀림: imagePullSecrets 누락, Secret 값 오타, 만료된 토큰 등
  2. AKS가 ACR에 붙는 ID 경로가 바뀜: 클러스터 업그레이드/노드풀 교체로 kubelet identity가 달라졌는데 권한이 그대로
  3. ACR 방화벽/프라이빗 링크 정책으로 인해 인증 플로우가 깨짐: 네트워크 차단이 401 로 보이는 케이스가 있음(특히 프록시/프라이빗 DNS 꼬임)
  4. 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.io DNS 존 연결 누락

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.io DNS 존과 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분 내에 원인을 좁힐 수 있습니다.