Published on

AKS ImagePullBackOff - ACR 권한·토큰 만료 진단

Authors

서론

AKS(Azure Kubernetes Service)에서 파드가 ImagePullBackOff / ErrImagePull로 멈추는 순간은 대개 “레지스트리 인증이 깨졌다”는 신호입니다. 특히 ACR(Azure Container Registry)을 쓰는 환경에서는 권한(Role Assignment) 문제토큰 만료/갱신 실패가 가장 흔합니다. 증상이 비슷해서 로그만 보고는 원인을 착각하기 쉽고, 그 사이 배포 파이프라인은 계속 실패하거나, 노드 재부팅 후 갑자기 전체 서비스가 내려가기도 합니다.

이 글은 다음을 목표로 합니다.

  • 이벤트 메시지로 권한 문제 vs 토큰 문제를 빠르게 구분
  • AKS가 ACR에 접근하는 대표 방식(관리형 ID/서비스 프린시플/imagePullSecret)별로 복구 절차 제시
  • “일단 되게 만들기”가 아니라 재발 방지 체크리스트까지 정리

빌드 단계에서 이미지 태그/캐시가 꼬여 엉뚱한 이미지를 당기는 상황도 종종 함께 발생합니다. 그런 경우는 Docker 빌드 캐시가 무효화되는 원인 7가지도 같이 보면 진단이 빨라집니다.

1) ImagePullBackOff의 본질: kubelet이 ACR에서 pull 못함

Kubernetes에서 이미지 pull은 대략 이렇게 진행됩니다.

  1. 스케줄러가 파드를 노드에 배치
  2. 노드의 kubelet이 image: <registry>/<repo>:<tag>를 보고 레지스트리에 인증 시도
  3. 인증 성공 → manifest 다운로드 → layer pull
  4. 실패 → ErrImagePull → 재시도 → ImagePullBackOff

즉, ImagePullBackOff는 애플리케이션 문제가 아니라 노드가 레지스트리에 접근/인증을 못한 상태입니다.

2) 3분 안에 원인 분류: kubectl describe 이벤트 읽기

가장 먼저 해야 할 일은 파드 이벤트를 보는 것입니다.

kubectl -n <ns> describe pod <pod-name>

여기서 아래 패턴을 찾습니다.

2.1 권한(Authorization) 계열

  • denied: requested access to the resource is denied
  • unauthorized: authentication required
  • 401 Unauthorized
  • 403 Forbidden

→ 대체로 ACR에 대한 권한이 없거나, 올바르지 않은 자격 증명(imagePullSecret)이 들어간 경우입니다.

2.2 토큰 만료/갱신 실패(Managed Identity/OIDC) 계열

  • failed to fetch oauth token
  • error getting credentials
  • AADSTS700082: The refresh token has expired
  • The token has expired / invalid_token

→ kubelet(또는 노드의 credential provider)이 토큰을 갱신하지 못한 상태일 가능성이 큽니다. 이 메시지는 OAuth/OIDC에서 흔히 보이는 형태라서, 토큰 문제의 감이 안 잡히면 Spring Security OAuth2 로그인 401·invalid_token 해결처럼 “토큰이 왜 만료/무효가 되는지” 관점으로 읽어보면 원인 파악에 도움이 됩니다.

2.3 레지스트리/네트워크 계열(부가)

  • i/o timeout, dial tcp ...:443: i/o timeout
  • x509: certificate signed by unknown authority

→ 이 글의 주제(권한·토큰)와는 다르지만, Private ACR + DNS/방화벽에서 자주 나옵니다. 권한을 아무리 고쳐도 안 되니 이벤트에서 먼저 배제해야 합니다.

3) AKS에서 ACR 인증 방식 3가지와 실패 포인트

AKS가 ACR에서 이미지를 pull하는 방식은 크게 3가지입니다.

  1. AKS 클러스터(또는 kubelet)의 Managed Identity로 ACR 접근
  2. 서비스 프린시플(Service Principal) 기반(구형/일부 환경)
  3. imagePullSecret(docker-registry secret) 로 직접 인증

각 방식마다 “무엇이 만료되는지”, “어디서 권한을 주는지”가 다릅니다.

3.1 Managed Identity 방식(권장)

  • 장점: 비밀번호/시크릿을 쿠버네티스에 저장하지 않음
  • 실패 포인트:
    • ACR에 AcrPull 역할이 없음
    • 클러스터/노드풀 ID가 바뀌었는데 권한을 옮기지 않음
    • 토큰 갱신 경로가 막힘(희귀하지만 프록시/네트워크/노드 상태에 따라 발생)

3.2 서비스 프린시플 방식

  • 장점: 구형 클러스터에서 흔함
  • 실패 포인트:
    • SP 비밀키(client secret) 만료
    • SP 권한(ACR scope)에 AcrPull 없음

3.3 imagePullSecret 방식

  • 장점: 특정 네임스페이스/서비스 계정 단위로 제어 쉬움
  • 실패 포인트:
    • secret에 들어있는 사용자/비밀번호(또는 토큰) 만료
    • secret이 파드/SA에 연결되지 않음
    • 잘못된 레지스트리 서버 주소(예: myacr.azurecr.io 오타)

4) ACR 권한 문제 해결: AcrPull Role Assignment 확인/부여

권한 문제의 핵심은 AKS가 사용하는 주체(Identity)가 ACR에 AcrPull 권한을 갖고 있느냐입니다.

4.1 ACR 리소스 ID 확인

ACR_NAME=<myacr>
az acr show -n $ACR_NAME --query id -o tsv

4.2 AKS가 어떤 ID로 pull 하는지 확인(대표 케이스)

클러스터가 Managed Identity라면 다음을 확인합니다.

AKS_RG=<aks-resource-group>
AKS_NAME=<aks-name>

az aks show -g $AKS_RG -n $AKS_NAME --query identity -o json
az aks show -g $AKS_RG -n $AKS_NAME --query identityProfile -o json
  • identity.principalId 또는
  • identityProfile.kubeletidentity.objectId (kubelet identity)

환경에 따라 ACR pull은 kubelet identity가 담당하는 경우가 많습니다.

4.3 AcrPull 역할 부여

<PRINCIPAL_ID><ACR_ID>를 위에서 얻었다고 가정합니다.

PRINCIPAL_ID=<principal-or-object-id>
ACR_ID=$(az acr show -n $ACR_NAME --query id -o tsv)

az role assignment create \
  --assignee-object-id $PRINCIPAL_ID \
  --assignee-principal-type ServicePrincipal \
  --role AcrPull \
  --scope $ACR_ID

권한이 제대로 들어갔는지 확인:

az role assignment list --assignee $PRINCIPAL_ID --scope $ACR_ID -o table

4.4 “aks update --attach-acr”로 연결하는 방법

AKS에서 공식적으로 ACR을 붙이는 명령도 있습니다.

az aks update -g $AKS_RG -n $AKS_NAME --attach-acr $ACR_NAME

이 명령은 내부적으로 필요한 Role Assignment를 구성해 주는 편이라, 수동으로 principal을 찾는 과정이 번거로울 때 유용합니다.

5) 토큰 만료/갱신 실패: 왜 갑자기 터지고, 어떻게 복구하나

권한이 맞는데도 failed to fetch oauth token류가 나온다면, “자격 증명 자체는 맞지만 토큰을 발급/갱신하는 흐름이 깨진 상태”일 수 있습니다.

5.1 서비스 프린시플 secret 만료

구형 AKS/특정 구성에서 SP의 client secret이 만료되면, 이미지 pull이 연쇄적으로 실패합니다.

  • 증상: 특정 시점 이후 모든 새 파드가 ImagePullBackOff
  • 이벤트: unauthorized + AAD 관련 에러가 섞여 나오는 경우

대응은 SP secret을 갱신하고 AKS 자격 증명을 업데이트하는 것입니다(환경별로 명령이 다를 수 있어, 현재 클러스터가 SP 기반인지 먼저 확인 필요).

5.2 Managed Identity 토큰 갱신 문제

Managed Identity는 일반적으로 “만료되면 자동 갱신”이지만, 아래 상황에서 문제가 커질 수 있습니다.

  • 노드가 오래 살아있고, credential provider가 비정상 상태
  • 노드 네트워크가 AAD/ACR 토큰 엔드포인트 접근을 간헐적으로 실패
  • 노드풀 업그레이드/스케일 이벤트 이후 일부 노드만 실패

실전 복구 팁(영향도 순):

  1. 문제 노드에서 파드 재스케줄
kubectl cordon <node>
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data
  1. 노드풀 스케일 인/아웃(문제 노드 교체)
az aks nodepool scale -g $AKS_RG --cluster-name $AKS_NAME -n <nodepool> --node-count <N>
  1. 클러스터/노드풀 업그레이드로 이미지 credential provider 갱신

근본적으로는 노드 이미지/에이전트 버전이 오래되어 ACR 인증 플로우에서 문제가 나는 케이스도 있어, 장기적으로는 업그레이드가 안전합니다.

6) imagePullSecret로 고정 인증할 때의 정석(그리고 만료 방지)

조직 정책상 Managed Identity를 못 쓰거나, 외부 레지스트리/특정 네임스페이스만 별도 인증이 필요하면 imagePullSecret을 씁니다.

6.1 Secret 생성

ACR Admin User를 켜서 쓰는 건 보안상 권장되지 않습니다. 대신 토큰/전용 계정을 만들거나(조직 정책에 맞게), 최소한 읽기 전용 권한을 보장해야 합니다.

kubectl -n <ns> create secret docker-registry acr-pull \
  --docker-server=<myacr>.azurecr.io \
  --docker-username=<username> \
  --docker-password='<password-or-token>'

6.2 Pod/Deployment에 연결

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      imagePullSecrets:
        - name: acr-pull
      containers:
        - name: api
          image: myacr.azurecr.io/api:2026-02-23
          imagePullPolicy: IfNotPresent

6.3 ServiceAccount에 붙여 “기본값”으로 만들기

네임스페이스에서 여러 워크로드가 같은 secret을 쓰면, 매번 Deployment에 쓰는 대신 SA에 붙이는 게 덜 실수합니다.

kubectl -n <ns> patch serviceaccount default \
  -p '{"imagePullSecrets": [{"name": "acr-pull"}]}'

6.4 만료 방지

  • 토큰/패스워드 만료 주기를 캘린더링
  • GitOps/CI에서 secret을 자동 갱신(단, 보안 감사 필요)
  • 가능하면 Managed Identity로 전환

7) 자주 하는 실수 체크리스트(재발 방지)

7.1 이미지 경로/태그 실수

  • myacr.azurecr.io/team/app:tag에서 repo 경로가 실제와 다름
  • latest 태그를 쓰다가 예상치 못한 이미지가 pull됨

이미지 태그 전략과 캐시가 꼬이면 “인증 문제처럼 보이는” 혼선이 생깁니다. 빌드/배포 단계에서 태그를 어떻게 만들고 있는지 점검은 필수입니다. 관련해서는 Docker 빌드 캐시가 무효화되는 원인 7가지가 실무적으로 도움이 됩니다.

7.2 ACR이 Private Endpoint인데 DNS/라우팅이 다른 노드풀

  • 노드풀이 여러 개고, 일부만 VNet/DNS 설정이 다름
  • 특정 노드에서만 i/o timeout

이 경우는 권한을 아무리 줘도 해결되지 않습니다. 먼저 이벤트에서 네트워크 에러를 분리하세요.

7.3 클러스터 재생성/노드풀 교체 후 principal이 바뀜

  • IaC로 클러스터를 새로 만들었는데 ACR Role Assignment를 옛 principal에 줌
  • 결과: 기존엔 되다가 신규 노드에서만 실패

해결: “현재 AKS가 쓰는 kubelet identity”를 기준으로 Role Assignment를 다시 잡습니다.

8) 현장용 빠른 진단 런북(요약)

  1. kubectl describe pod 이벤트에서 401/403인지, token fetch 실패인지, timeout인지 분류
  2. 권한 의심이면
    • AKS/kubelet identity 확인 → ACR scope에 AcrPull 부여
    • 또는 az aks update --attach-acr
  3. 토큰/갱신 의심이면
    • 문제 노드 drain → 노드 교체(스케일)로 즉시 복구
    • SP 기반이면 secret 만료 여부 확인 및 갱신
  4. imagePullSecret 사용 시
    • secret이 파드/SA에 연결됐는지
    • secret의 서버 주소/자격 증명이 최신인지

결론

AKS의 ImagePullBackOff는 “쿠버네티스 문제”가 아니라 대부분 ACR 인증 경로의 문제입니다. 특히 ACR 권한(AcrPull) 누락과 토큰 만료/갱신 실패는 증상이 비슷하지만, 이벤트 메시지와 AKS 인증 방식을 기준으로 접근하면 10분 내로 원인을 좁힐 수 있습니다. 단기적으로는 노드 교체/드레인으로 서비스 복구를 우선하고, 장기적으로는 Managed Identity + 올바른 Role Assignment + 태그/배포 규칙 정리로 재발을 막는 것이 정석입니다.