- Published on
AKS ImagePullBackOff - ACR 권한·토큰 만료 진단
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
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은 대략 이렇게 진행됩니다.
- 스케줄러가 파드를 노드에 배치
- 노드의 kubelet이
image: <registry>/<repo>:<tag>를 보고 레지스트리에 인증 시도 - 인증 성공 → manifest 다운로드 → layer pull
- 실패 →
ErrImagePull→ 재시도 →ImagePullBackOff
즉, ImagePullBackOff는 애플리케이션 문제가 아니라 노드가 레지스트리에 접근/인증을 못한 상태입니다.
2) 3분 안에 원인 분류: kubectl describe 이벤트 읽기
가장 먼저 해야 할 일은 파드 이벤트를 보는 것입니다.
kubectl -n <ns> describe pod <pod-name>
여기서 아래 패턴을 찾습니다.
2.1 권한(Authorization) 계열
denied: requested access to the resource is deniedunauthorized: authentication required401 Unauthorized403 Forbidden
→ 대체로 ACR에 대한 권한이 없거나, 올바르지 않은 자격 증명(imagePullSecret)이 들어간 경우입니다.
2.2 토큰 만료/갱신 실패(Managed Identity/OIDC) 계열
failed to fetch oauth tokenerror getting credentialsAADSTS700082: The refresh token has expiredThe 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 timeoutx509: certificate signed by unknown authority
→ 이 글의 주제(권한·토큰)와는 다르지만, Private ACR + DNS/방화벽에서 자주 나옵니다. 권한을 아무리 고쳐도 안 되니 이벤트에서 먼저 배제해야 합니다.
3) AKS에서 ACR 인증 방식 3가지와 실패 포인트
AKS가 ACR에서 이미지를 pull하는 방식은 크게 3가지입니다.
- AKS 클러스터(또는 kubelet)의 Managed Identity로 ACR 접근
- 서비스 프린시플(Service Principal) 기반(구형/일부 환경)
- imagePullSecret(docker-registry secret) 로 직접 인증
각 방식마다 “무엇이 만료되는지”, “어디서 권한을 주는지”가 다릅니다.
3.1 Managed Identity 방식(권장)
- 장점: 비밀번호/시크릿을 쿠버네티스에 저장하지 않음
- 실패 포인트:
- ACR에
AcrPull역할이 없음 - 클러스터/노드풀 ID가 바뀌었는데 권한을 옮기지 않음
- 토큰 갱신 경로가 막힘(희귀하지만 프록시/네트워크/노드 상태에 따라 발생)
- ACR에
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 토큰 엔드포인트 접근을 간헐적으로 실패
- 노드풀 업그레이드/스케일 이벤트 이후 일부 노드만 실패
실전 복구 팁(영향도 순):
- 문제 노드에서 파드 재스케줄
kubectl cordon <node>
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data
- 노드풀 스케일 인/아웃(문제 노드 교체)
az aks nodepool scale -g $AKS_RG --cluster-name $AKS_NAME -n <nodepool> --node-count <N>
- 클러스터/노드풀 업그레이드로 이미지 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) 현장용 빠른 진단 런북(요약)
kubectl describe pod이벤트에서 401/403인지, token fetch 실패인지, timeout인지 분류- 권한 의심이면
- AKS/kubelet identity 확인 → ACR scope에
AcrPull부여 - 또는
az aks update --attach-acr
- AKS/kubelet identity 확인 → ACR scope에
- 토큰/갱신 의심이면
- 문제 노드 drain → 노드 교체(스케일)로 즉시 복구
- SP 기반이면 secret 만료 여부 확인 및 갱신
- imagePullSecret 사용 시
- secret이 파드/SA에 연결됐는지
- secret의 서버 주소/자격 증명이 최신인지
결론
AKS의 ImagePullBackOff는 “쿠버네티스 문제”가 아니라 대부분 ACR 인증 경로의 문제입니다. 특히 ACR 권한(AcrPull) 누락과 토큰 만료/갱신 실패는 증상이 비슷하지만, 이벤트 메시지와 AKS 인증 방식을 기준으로 접근하면 10분 내로 원인을 좁힐 수 있습니다. 단기적으로는 노드 교체/드레인으로 서비스 복구를 우선하고, 장기적으로는 Managed Identity + 올바른 Role Assignment + 태그/배포 규칙 정리로 재발을 막는 것이 정석입니다.