- Published on
EKS Pod에서 AWS SDK 자격증명 못찾음 해결 가이드
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스처럼 보이는 쿠버네티스 워크로드도 결국 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는 아래 순서로 자격증명을 탐색합니다(언어별로 약간 차이 있음).
- 환경변수:
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN - 공유 크리덴셜 파일:
~/.aws/credentials,~/.aws/config - 웹 아이덴티티(IRSA):
AWS_WEB_IDENTITY_TOKEN_FILE+AWS_ROLE_ARN - 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
'
- 여기서도
NoCredentialsError면 SDK가 어떤 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분 안에 결론이 납니다.
- Pod가 어떤 SA를 쓰는가? (
spec.serviceAccountName) - SA에
eks.amazonaws.com/role-arn이 정확한가? - Pod 내부에
AWS_WEB_IDENTITY_TOKEN_FILE/AWS_ROLE_ARN이 존재하는가? - Pod에서
sts:GetCallerIdentity가 되는가? - SDK 버전/코드가
profile_name강제 등으로 체인을 깨지 않는가? - (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”는 주입은 됐지만 권한/트러스트가 틀린 상태일 확률이 높습니다.
두 에러를 구분해 진단하면 불필요한 시행착오(노드 롤 수정, 시크릿 키 주입, 권한 과다 부여)를 크게 줄일 수 있습니다.