- Published on
GKE PullBackOff - Artifact Registry 403 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버를 GKE로 옮기거나 레지스트리를 Container Registry에서 Artifact Registry로 전환한 뒤, 어느 날 갑자기 Pod가 뜨지 않고 ImagePullBackOff로 멈추는 경우가 있습니다. 이벤트를 보면 failed to pull and unpack image 같은 메시지와 함께 Artifact Registry에서 403 Forbidden이 찍히곤 합니다.
이 문제는 대부분 “이미지 자체”가 아니라 이미지를 가져오는 주체(노드, 혹은 kubelet)가 Artifact Registry에 접근할 권한이 없어서 발생합니다. 특히 Workload Identity를 쓰는 환경에서는 “Pod의 서비스 계정 권한이면 되겠지”라고 생각하기 쉬운데, 이미지 pull은 기본적으로 노드의 kubelet이 수행한다는 점이 핵심 함정입니다.
아래에서는 증상 확인부터 원인 분리, 가장 흔한 403 케이스별 해결책, 그리고 운영에서 재발하지 않게 만드는 체크리스트까지 순서대로 정리합니다.
관련해서 쿠버네티스 운영 장애를 함께 다룬 글로는 K8s OOMKilled 반복? 메모리 리밋·GC 진단법도 참고하면 트러블슈팅 흐름을 잡는 데 도움이 됩니다.
1) 증상: ImagePullBackOff와 403 확인
먼저 실제로 403이 “이미지 pull 단계”에서 발생하는지 확인합니다.
이벤트 확인
kubectl describe pod -n <namespace> <pod-name>
출력의 Events 섹션에 보통 아래 같은 형태가 보입니다. 부등호는 MDX에서 JSX로 오인될 수 있어 예시는 인라인 코드로 표시합니다.
Failed to pull image "REGION-docker.pkg.dev/PROJECT/REPO/IMAGE:TAG": rpc error: code = Unknown desc = failed to pull and unpack image ... 403 ForbiddenBack-off pulling image ...
이미지 레퍼런스 확인
Pod 스펙에 적힌 이미지가 Artifact Registry 포맷인지 확인합니다.
- Artifact Registry Docker:
REGION-docker.pkg.dev/PROJECT/REPO/IMAGE:TAG
gcr.io 또는 asia.gcr.io 등 Container Registry를 쓰던 시절 레퍼런스가 섞여 있으면, 다른 원인(레지스트리 도메인, 리다이렉트, 권한 정책)이 섞여 진단이 어려워집니다.
2) 원인 분리: “누가” 이미지를 pull 하는가
쿠버네티스에서 이미지 pull은 대개 다음 주체가 관여합니다.
- 노드의 kubelet: 컨테이너 런타임에 이미지를 내려받도록 지시
- 노드가 사용하는 Google Cloud 서비스 계정(Node SA): GKE 노드가 GCP API에 접근할 때 쓰는 정체성
- (선택) imagePullSecrets: 프라이빗 레지스트리 인증을 위해 Docker config를 제공
- (혼동 포인트) Workload Identity의 KSA/GSA: 주로 “애플리케이션이 GCP API 호출”할 때 쓰이며, 이미지 pull에는 기본적으로 직접 영향이 없습니다
정리하면, 403이 Artifact Registry에서 발생한다면 첫 번째로 의심할 것은 노드가 Artifact Registry에 접근할 IAM 권한이 있는지입니다.
3) 가장 흔한 케이스: 노드 서비스 계정에 권한이 없음
3-1) 현재 노드 서비스 계정 확인
클러스터가 어떤 노드 풀을 쓰는지에 따라 노드 SA가 다를 수 있습니다.
gcloud container node-pools list \
--cluster <cluster-name> \
--region <region>
노드 풀의 설정에서 서비스 계정을 확인합니다.
gcloud container node-pools describe <node-pool-name> \
--cluster <cluster-name> \
--region <region> \
--format="value(config.serviceAccount)"
자주 보이는 값은 다음 중 하나입니다.
- 기본 Compute Engine SA:
PROJECT_NUMBER-compute@developer.gserviceaccount.com - 커스텀 SA:
something@PROJECT_ID.iam.gserviceaccount.com
3-2) Artifact Registry 읽기 권한 부여
Artifact Registry에서 이미지를 pull 하려면 최소한 roles/artifactregistry.reader가 필요합니다.
권한을 “프로젝트 단위”로 줄지, “리포지토리 단위”로 줄지 결정해야 합니다.
(권장) 리포지토리 단위로 최소 권한 부여
gcloud artifacts repositories add-iam-policy-binding <repo> \
--location=<region> \
--member="serviceAccount:<node-sa-email>" \
--role="roles/artifactregistry.reader"
프로젝트 단위로 부여(간편하지만 권한이 넓음)
gcloud projects add-iam-policy-binding <project-id> \
--member="serviceAccount:<node-sa-email>" \
--role="roles/artifactregistry.reader"
권한을 준 뒤에도 이미 ImagePullBackOff 상태인 Pod는 즉시 회복되지 않을 수 있습니다. 가장 확실한 방법은 Pod를 재생성하는 것입니다.
kubectl delete pod -n <namespace> <pod-name>
Deployment라면 자동으로 새 Pod가 뜨면서 다시 pull을 시도합니다.
4) 케이스: 다른 프로젝트의 Artifact Registry를 pull함
조직에서는 흔히 “빌드 프로젝트”와 “런타임 프로젝트”가 분리됩니다.
- Artifact Registry가
project-a에 있음 - GKE 클러스터는
project-b에 있음 - 노드 SA는
project-b소속
이 경우 project-a의 리포지토리에 project-b의 노드 SA를 reader로 추가해야 합니다.
gcloud artifacts repositories add-iam-policy-binding <repo> \
--project=<artifact-project-id> \
--location=<region> \
--member="serviceAccount:<node-sa-email>" \
--role="roles/artifactregistry.reader"
여기서 흔한 실수는 --project를 GKE 프로젝트로 두고 실행해 “권한을 준 줄 알았는데 사실 다른 프로젝트에 준 것”처럼 착각하는 것입니다.
5) 케이스: 노드 풀을 새로 만들었는데 SA가 달라짐
운영 중 노드 풀을 교체하거나, 오토스케일링으로 새 노드 풀이 추가되는 과정에서 새 노드 풀이 다른 서비스 계정을 쓰는 경우가 있습니다.
- 기존 노드 풀은 커스텀 SA라서 권한이 있었음
- 새 노드 풀은 기본 Compute SA로 생성되어 권한이 없음
- 특정 시점부터 새 노드에 스케줄된 Pod만
403으로 터짐
이럴 때는 “클러스터 전체가 장애”가 아니라 “일부 Pod만 장애”처럼 보여 진단이 늦어집니다.
해결은 간단합니다.
- 새 노드 풀의 SA를 확인
- 그 SA에
roles/artifactregistry.reader부여 - 장기적으로는 노드 풀 생성 템플릿과 IaC에서 SA를 고정
IaC로 운영한다면 충돌/드리프트로 설정이 바뀌는 문제도 자주 겪습니다. 비슷한 운영 맥락의 글로 Terraform apply 409 충돌 - state 잠금·drift 해결도 함께 참고할 만합니다.
6) 케이스: Workload Identity를 쓰는데 Pod 권한만 줌
Workload Identity를 켜면 보안 모델이 깔끔해져서, 많은 팀이 다음처럼 구성합니다.
- KSA
app를 GSAapp-gsa에 매핑 app-gsa에 필요한 IAM 부여
그리고 “이미지 pull도 이 권한으로 되겠지”라고 기대합니다. 하지만 기본적으로 이미지 pull은 노드가 수행하므로, app-gsa에 roles/artifactregistry.reader를 줘도 403은 그대로일 수 있습니다.
이 케이스의 해결은 다음 중 하나입니다.
- 정석: 노드 SA에 reader 권한 부여
- 대안: 프라이빗 레지스트리 인증을 위한
imagePullSecrets를 사용(다만 GCP에서는 보통 노드 권한으로 해결하는 편이 운영이 단순)
AWS에서 IRSA로 비슷한 혼동을 자주 겪는 것처럼, “Pod의 정체성과 노드의 정체성이 다르다”는 점이 핵심입니다. 같은 결의 트러블슈팅 글로 EKS IRSA로 Pod AWS 권한 오류 5분 해결도 참고하면 개념이 더 선명해집니다.
7) 케이스: 레지스트리 위치(region) 및 엔드포인트 오타
Artifact Registry는 리포지토리가 리전에 종속되고, 도메인도 리전별로 다릅니다.
- 올바른 예:
asia-northeast3-docker.pkg.dev/...
다음과 같은 실수는 403 또는 404처럼 보일 수 있습니다.
- 존재하지 않는 리전 문자열
- 실제 리포지토리 리전과 이미지 레퍼런스의 리전 불일치
- 리포지토리 이름 오타
가장 빠른 확인은 “리포지토리가 실제로 존재하는지”를 CLI로 보는 것입니다.
gcloud artifacts repositories describe <repo> \
--location=<region> \
--project=<project-id>
그리고 이미지가 푸시되어 있는지도 확인합니다.
gcloud artifacts docker images list \
<region>-docker.pkg.dev/<project-id>/<repo> \
--include-tags
8) 케이스: 조직 정책(Policy) 또는 VPC-SC로 차단
권한을 줬는데도 403이 지속되면, IAM 문제가 아니라 조직 정책 또는 경계 보안(VPC Service Controls) 가능성을 봐야 합니다.
단서:
- 같은 SA로 다른 API는 되는데 Artifact Registry만 403
- 특정 네트워크/프로젝트에서만 실패
- 감사 로그에 “정책으로 거부” 류의 메시지
이 경우는 클러스터/노드 레벨보다 상위 계층에서 막고 있는지 확인해야 하므로, Artifact Registry 관련 Audit Log를 확인하는 것이 우선입니다.
gcloud logging read \
'resource.type="audited_resource" AND protoPayload.serviceName="artifactregistry.googleapis.com"' \
--project=<project-id> \
--limit=50 \
--format=json
로그에서 status.code와 status.message를 보면 “권한 부족”인지 “정책 거부”인지 갈라집니다.
9) 재발 방지 체크리스트
운영에서 PullBackOff는 배포 실패로 직결되므로, 다음을 표준으로 만들어두면 좋습니다.
9-1) 노드 SA를 명시적으로 고정
- 노드 풀 생성 시 항상 동일한 커스텀 SA 사용
- IaC에서 node pool의 SA 필드를 필수로 두기
9-2) 리포지토리 단위 최소 권한
roles/artifactregistry.reader를 리포지토리 IAM에 부여- 프로젝트 단위 부여는 임시 대응으로만 사용
9-3) 멀티 프로젝트면 “런타임 노드 SA”를 표준화
- 빌드 프로젝트에 있는 Artifact Registry에
- 런타임 프로젝트의 노드 SA를 reader로 추가
9-4) 배포 파이프라인에 사전 검증 추가
배포 직전에 “노드 SA가 pull 가능한지”를 자동 점검하는 단계가 있으면 장애를 줄일 수 있습니다. 예를 들어, 클러스터 노드 SA를 조회하고 해당 SA가 리포지토리 IAM에 존재하는지 검사하는 식입니다.
10) 빠른 해결 요약(현장용)
kubectl describe pod로 이벤트에서 403 확인- 이미지가
REGION-docker.pkg.dev/...형식인지 확인 - 노드 풀의 서비스 계정 확인
- 해당 SA에
roles/artifactregistry.reader를 리포지토리(또는 프로젝트)에 부여 - Pod 재생성으로 재시도
위 흐름대로 하면 대부분의 GKE ImagePullBackOff: Artifact Registry 403은 10분 내로 해결됩니다. 문제의 본질은 “Pod 권한”이 아니라 “노드가 이미지를 당겨올 권한”이라는 점만 기억해도, 불필요한 우회(이미지 재빌드, 태그 변경, 클러스터 재생성)를 크게 줄일 수 있습니다.