Published on

EKS Pod에서 AWS SDK 자격증명 못찾음 해결 가이드

Authors

서버리스처럼 보이는 쿠버네티스 워크로드도 결국 AWS API를 호출하려면 AWS SDK 자격증명 체인(credential provider chain) 을 만족해야 합니다. 그런데 EKS Pod에서 S3, SQS, STS 등을 호출할 때 아래와 같은 에러를 자주 만납니다.

Unable to locate credentials
NoCredentialProviders: no valid providers in chain
botocore.exceptions.NoCredentialsError: Unable to locate credentials
The security token included in the request is invalid

이 글은 “왜 Pod가 자격증명을 못 찾는지”를 원인별로 분해하고, 운영 환경에서 재현/검증 가능한 방식으로 해결하는 체크리스트를 제공합니다. (IRSA를 쓰는데도 AccessDenied가 뜨는 케이스는 별도 글인 EKS IRSA인데 AccessDenied? OIDC·TrustPolicy·SA 점검도 함께 참고하세요.)

1) AWS SDK 자격증명 체인: Pod에서 실제로 무엇을 찾나

대부분의 AWS SDK는 아래 순서로 자격증명을 탐색합니다(언어별로 약간 차이 있음).

  1. 환경변수: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
  2. 공유 크리덴셜 파일: ~/.aws/credentials, ~/.aws/config
  3. 웹 아이덴티티(IRSA): AWS_WEB_IDENTITY_TOKEN_FILE + AWS_ROLE_ARN
  4. EC2/ECS 메타데이터: IMDS(노드 IAM Role), ECS task role 등

EKS에서 “Pod별 권한”을 제대로 하려면 3번(IRSA)이 정석이고, 1번(정적 키)은 지양합니다. 그런데 현장에서는 다음 두 가지가 자주 섞이며 문제가 생깁니다.

  • IRSA를 설정했는데 Pod에 필요한 env/volume이 안 들어옴
  • IRSA를 안 쓰고 노드 IAM Role(IMDS)에 기대려다 IMDS 접근이 막힘

2) 가장 흔한 원인 Top 6

원인 A. ServiceAccount에 role-arn 어노테이션이 없다/오타

IRSA는 Pod가 사용하는 ServiceAccount에 아래 어노테이션이 있어야 합니다.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-sa
  namespace: default
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-irsa-role

그리고 Deployment/Pod가 해당 SA를 실제로 사용해야 합니다.

spec:
  serviceAccountName: app-sa

검증

kubectl -n default get sa app-sa -o yaml | yq '.metadata.annotations'
kubectl -n default get deploy my-app -o yaml | yq '.spec.template.spec.serviceAccountName'

원인 B. Pod에 AWS_WEB_IDENTITY_TOKEN_FILE/AWS_ROLE_ARN가 주입되지 않음

EKS는 IRSA가 정상일 때 Pod에 다음이 생깁니다.

  • env: AWS_ROLE_ARN
  • env: AWS_WEB_IDENTITY_TOKEN_FILE
  • volume: projected serviceaccount token

검증(컨테이너 내부)

kubectl -n default exec -it deploy/my-app -- sh -lc '
  env | egrep "AWS_(ROLE_ARN|WEB_IDENTITY_TOKEN_FILE|REGION|DEFAULT_REGION)" || true
  ls -l $AWS_WEB_IDENTITY_TOKEN_FILE 2>/dev/null || true
  cat $AWS_WEB_IDENTITY_TOKEN_FILE 2>/dev/null | head -c 20; echo
'
  • 파일이 없거나 env가 비어 있으면 IRSA가 주입되지 않은 상태입니다.
  • 보통은 ServiceAccount 미지정, 어노테이션 오타, Pod가 다른 SA 사용, 혹은 mutating webhook/정책이 토큰 projected volume을 막는 경우가 원인입니다.

원인 C. SDK가 IRSA를 지원하지 않는 버전/설정

언어별로 IRSA는 “웹 아이덴티티”를 지원해야 합니다. 너무 오래된 SDK를 쓰면 IRSA를 못 읽고 “Unable to locate credentials”가 납니다.

  • Python(boto3/botocore): botocore가 웹 아이덴티티를 지원하는지 확인(대체로 최신이면 OK)
  • Java(AWS SDK v1/v2): v2 권장, v1도 가능하지만 provider 설정 확인
  • Node.js: v3 권장(@aws-sdk/credential-providers)

Python 예시(권장: 환경 자동 탐지)

import boto3

# IRSA가 정상이라면 별도 키 없이도 동작
s3 = boto3.client("s3")
print(s3.list_buckets())

만약 코드에서 실수로 “로컬 프로파일”을 강제하면 Pod에서는 자격증명을 못 찾습니다.

import boto3

# 안티패턴: 컨테이너에 ~/.aws/credentials가 없으면 실패
session = boto3.Session(profile_name="default")

원인 D. AWS_REGION/AWS_DEFAULT_REGION 미설정으로 다른 에러처럼 보임

자격증명 문제로 착각하지만 실제로는 리전 미설정인 케이스도 많습니다.

  • boto3: You must specify a region.
  • 일부 SDK: STS 글로벌 엔드포인트/서명 문제로 혼동

권장: Pod에 명시

env:
  - name: AWS_REGION
    value: ap-northeast-2
  - name: AWS_DEFAULT_REGION
    value: ap-northeast-2

원인 E. 노드 IAM Role(IMDS)에 기대는데 IMDS 접근이 막힘

IRSA를 쓰지 않거나, SDK가 웹 아이덴티티를 못 읽으면 마지막으로 IMDS(노드 메타데이터)로 떨어질 수 있습니다. 그런데 EKS 보안 설정/네트워크 정책/프록시로 169.254.169.254 접근이 막히면 자격증명 탐색이 실패합니다.

검증

kubectl -n default exec -it deploy/my-app -- sh -lc '
  apk add --no-cache curl >/dev/null 2>&1 || true
  curl -sS --max-time 1 http://169.254.169.254/latest/meta-data/iam/security-credentials/ || echo "IMDS blocked"
'
  • IRSA를 쓰는 게 목적이라면 IMDS 의존을 제거하는 것이 정답입니다.
  • 반대로 노드 Role을 쓰기로 했다면, IMDSv2 요구/홉 제한(HTTP PUT token) 등도 함께 점검해야 합니다.

원인 F. ECR/ImagePull 문제와 혼동

간혹 “컨테이너가 뜨기 전” 단계에서 ECR pull이 실패하는데, 이를 앱의 SDK 자격증명 문제로 착각합니다.

  • ImagePullBackOff, 401노드/이미지 풀 권한 문제일 가능성이 큽니다.

관련해서는 Kubernetes ImagePullBackOff 401 - ECR·IRSA·imagePullSecrets를 함께 보시면 진단 속도가 빨라집니다.

3) IRSA로 정석 구성: 최소 구성 예시

여기서는 “S3 읽기”만 가능한 역할을 예로 듭니다.

3.1 OIDC 공급자 연결(클러스터 1회)

이미 연결되어 있지 않다면 OIDC provider를 생성해야 합니다(eksctl 예시).

eksctl utils associate-iam-oidc-provider \
  --cluster my-eks \
  --region ap-northeast-2 \
  --approve

3.2 IAM Role + Trust Policy(핵심)

Trust policy는 해당 클러스터 OIDC에서 특정 namespace/serviceaccount만 AssumeRoleWithWebIdentity 할 수 있게 묶어야 합니다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:sub": "system:serviceaccount:default:app-sa",
          "oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}

권한 정책(예: S3 List/Get만)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": ["arn:aws:s3:::my-bucket"]
    },
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject"],
      "Resource": ["arn:aws:s3:::my-bucket/*"]
    }
  ]
}

3.3 Kubernetes ServiceAccount/Deployment

apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-sa
  namespace: default
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-irsa-role
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      serviceAccountName: app-sa
      containers:
        - name: app
          image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/my-app:latest
          env:
            - name: AWS_REGION
              value: ap-northeast-2
            - name: AWS_DEFAULT_REGION
              value: ap-northeast-2

4) “Unable to locate credentials”를 빠르게 좁히는 실전 진단 명령

4.1 Pod에서 STS 호출로 자격증명 유무 확인

앱 로직(S3 등)보다 먼저 STS로 확인하면 원인이 단순해집니다.

kubectl -n default exec -it deploy/my-app -- sh -lc '
  python - <<"PY"
import boto3
sts=boto3.client("sts")
print(sts.get_caller_identity())
PY
'
  • 여기서도 NoCredentialsErrorSDK가 어떤 provider도 못 찾는 상태
  • AccessDenied자격증명은 잡혔지만 권한/Trust 문제(IRSA 트러스트/정책 점검)

4.2 실제로 어떤 자격증명을 잡았는지(가능한 범위에서) 로그

Python(boto3)은 내부 디버그 로그로 provider 선택 과정을 볼 수 있습니다.

kubectl -n default exec -it deploy/my-app -- sh -lc '
  python - <<"PY"
import boto3, logging
logging.basicConfig(level=logging.DEBUG)
logging.getLogger("botocore").setLevel(logging.DEBUG)

session=boto3.Session()
creds=session.get_credentials()
print("creds:", type(creds), "frozen:", creds.get_frozen_credentials() if creds else None)
PY
'

운영에서는 DEBUG가 과하니, 재현용 파드/임시 잡에서만 사용하세요.

5) 자주 하는 실수 패턴과 교정

5.1 (실수) Helm/Kustomize에서 serviceAccountName 누락

차트 values에서 serviceAccount.create=true만 해놓고 실제 Deployment에 serviceAccountName이 반영되지 않는 경우가 있습니다.

교정 포인트

  • 렌더링 결과를 꼭 확인: helm template / kustomize build
helm template my-app ./chart | yq 'select(.kind=="Deployment") | .spec.template.spec.serviceAccountName'

5.2 (실수) 동일 네임스페이스가 아닌 SA를 참조

default에 SA 만들고 prod에 Deployment 띄우면 당연히 주입이 안 됩니다.

kubectl get pod -n prod <pod> -o jsonpath='{.spec.serviceAccountName}{"\n"}'

5.3 (실수) 앱 컨테이너에서 환경변수로 정적 키를 덮어씀

IRSA가 잘 붙어도, 아래처럼 잘못된 정적 키가 env로 들어가면 SDK는 그걸 우선 사용합니다.

env:
  - name: AWS_ACCESS_KEY_ID
    valueFrom:
      secretKeyRef:
        name: aws-static-creds
        key: access_key

이 경우는 보통 InvalidClientTokenId, The security token included in the request is invalid 같은 에러로 나타납니다. IRSA를 쓸 거면 정적 키 env는 제거하세요.

6) 체크리스트(운영자용 요약)

아래 순서로 보면 대부분 10분 안에 결론이 납니다.

  1. Pod가 어떤 SA를 쓰는가? (spec.serviceAccountName)
  2. SA에 eks.amazonaws.com/role-arn이 정확한가?
  3. Pod 내부에 AWS_WEB_IDENTITY_TOKEN_FILE/AWS_ROLE_ARN이 존재하는가?
  4. Pod에서 sts:GetCallerIdentity가 되는가?
  5. SDK 버전/코드가 profile_name 강제 등으로 체인을 깨지 않는가?
  6. (IRSA 미사용 시) IMDS 접근이 가능한가?

IRSA를 쓴다면 “자격증명 못찾음”은 대개 2~3번에서 걸리고, “AccessDenied”는 트러스트/정책에서 걸립니다. 후자의 경우는 위에서 언급한 EKS IRSA인데 AccessDenied? OIDC·TrustPolicy·SA 점검 체크리스트를 그대로 따라가면 빠르게 해결됩니다.

7) 마무리: 결론은 IRSA + 검증 가능한 STS 테스트

EKS Pod에서 AWS SDK 자격증명 문제를 안정적으로 끝내는 방법은 결국 IRSA로 Pod 단위 권한을 명확히 하고, 배포 후에는 STS GetCallerIdentity로 자격증명 확보 여부를 자동 점검하는 것입니다.

  • “Unable to locate credentials”는 주입 자체가 안 된 상태일 확률이 높고
  • “AccessDenied”는 주입은 됐지만 권한/트러스트가 틀린 상태일 확률이 높습니다.

두 에러를 구분해 진단하면 불필요한 시행착오(노드 롤 수정, 시크릿 키 주입, 권한 과다 부여)를 크게 줄일 수 있습니다.