1. 같은 에러, 다른 원인
IRSA는 한 번 잘 세팅해 두면 거의 신경 쓸 일이 없다. 문제는 "한 번 잘 세팅해 두면"이라는 전제다. 클러스터를 새로 만들거나 ServiceAccount를 옮기거나 IAM Role을 재생성하는 순간 IRSA는 조용히 깨진다. 겉으로 Pod는 정상 기동하고 readiness probe도 통과한다. 그런데 AWS API 호출만 실패한다. 로그에는 다음과 같은 메시지가 반복된다.
An error occurred (AccessDenied) when calling the AssumeRoleWithWebIdentity
operation: Not authorized to perform sts:AssumeRoleWithWebIdentity
에러 메시지만 보면 모든 사례가 같아 보인다. 하지만 실제 원인은 IRSA의 5단계 동작 과정 중 어느 단계에서 깨졌느냐에 따라 6가지로 갈라진다. 원인에 따라 진단 명령과 조치가 달라지므로 "어느 단계에서 깨졌는지"를 먼저 식별해야 한다.
1.1 대표적인 장애 트리거
AWS re:Post IRSA 트러블슈팅 KB 시리즈와 주요 오픈소스 프로젝트(AWS Load Balancer Controller, Karpenter, Grafana Loki 등)의 공개 이슈 아카이브를 교차 분석해 보면 IRSA가 깨지는 상황은 대체로 아래와 같이 정리된다.
- ServiceAccount를 다른 namespace로 이동
- SA 이름 변경(Helm chart 업그레이드로 인한 기본값 변화 포함)
- 클러스터 재구축 후 OIDC Provider 재등록 누락
- 애플리케이션 이미지에 포함된 AWS SDK 버전이 IRSA 미지원
- Terraform 리팩토링 과정의 Trust Policy 변수 변경
- OIDC Provider 생성 시 ClientID 지정 누락
공통점은 "원래 되던 것을 건드렸다"는 점이다. 설정을 새로 만들 때보다 고칠 때 더 자주 깨진다.
1.2 에러 메시지로 받는 첫 신호
장애를 가장 먼저 감지하는 곳은 애플리케이션 로그다. Pod 로그에서 전형적으로 관측되는 메시지는 다음과 같다.
kubectl logs -n kube-system external-dns-xxxxx
time="2026-04-19T10:12:34Z" level=error msg="records retrieval failed:
An error occurred (AccessDenied) when calling the AssumeRoleWithWebIdentity
operation: Not authorized to perform sts:AssumeRoleWithWebIdentity"
메시지 앞부분이 동일하다 보니 원인도 하나일 거라 착각하기 쉽지만, Not authorized, Incorrect token audience, No OpenIDConnect provider found, Unable to locate credentials 같은 세부 메시지는 각각 IRSA의 다른 단계에서 나오는 신호다. 본문 2장에서 IRSA 5단계를 정리하고, 3장에서 각 에러를 그 단계 위에 매핑한다. 에러 메시지만 보고도 어느 단계가 무너졌는지 짚을 수 있게 되는 것이 이 가이드의 목표다.
2. IRSA 5단계 다시 보기
4주차 글에서 IRSA 정상 동작 흐름을 다뤘다. 장애 대응 관점에서는 이 흐름을 "깨질 수 있는 단계"로 다시 볼 필요가 있다.
2.1 5단계 흐름 정리
| 단계 | 주체 | 역할 | 깨졌을 때 증상 |
|---|---|---|---|
| ① JWT 주입 | MutatingWebhook | Pod Spec에 env 2개와 projected volume 추가 | Pod에 AWS_WEB_IDENTITY_TOKEN_FILE이 없음 |
| ② SDK 자격증명 | 애플리케이션 SDK | env 두 개를 읽고 STS 호출 준비 | SDK가 web identity 경로를 모름 |
| ③ STS 호출 | AWS SDK → AWS STS | AssumeRoleWithWebIdentity API 호출 |
STS 네트워크 도달 실패 또는 요청 구조 오류 |
| ④ OIDC 검증 | AWS STS + IAM | JWKS로 토큰 서명 검증 | OIDC Provider가 없거나 thumbprint 불일치 |
| ⑤ Trust Policy 매칭 | AWS IAM | 토큰 claim과 Trust Policy 조건 비교 | sub·aud 조건 불일치로 AccessDenied |
2.2 단계별로 무엇이 검증되는가
단계 ①에서는 Pod가 생성되는 순간 클러스터 내부에서 pod-identity-webhook이 MutatingAdmissionWebhook으로 동작한다. IRSA가 설정된 SA를 쓰는 Pod에 한해 Pod Spec을 변형하고, 주입되는 내용은 env 두 개와 projected volume 하나다.
env:
- name: AWS_ROLE_ARN
value: arn:aws:iam::123456789012:role/my-app-role
- name: AWS_WEB_IDENTITY_TOKEN_FILE
value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
volumeMounts:
- mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount
name: aws-iam-token
readOnly: true
volumes:
- name: aws-iam-token
projected:
sources:
- serviceAccountToken:
audience: sts.amazonaws.com
expirationSeconds: 86400
path: token
env 두 개가 주입되지 않으면 뒤의 모든 단계가 의미 없어진다. 그래서 디버깅은 Pod Spec 확인에서 시작하는 것이 효율적이다.
단계 ②는 SDK의 몫이다. AWS SDK는 Credential Provider Chain을 순회하면서 어떤 자격증명 방식을 쓸지 결정한다. IRSA가 쓰는 방식은 "Web Identity Token" 프로바이더다. 이 프로바이더를 지원하지 않는 구버전 SDK에서는 IRSA env가 존재해도 무시된다.
단계 ③은 SDK가 STS에 API 호출을 하는 지점이다. 이 단계의 실패는 대부분 네트워크 문제(Proxy, VPC Endpoint 설정 누락)나 STS 리전 설정 오류다. IRSA 자체 설정과는 층이 다르다.
단계 ④에서는 STS가 IAM에 "이 토큰이 진짜인지"를 묻는다. IAM은 클러스터의 OIDC Discovery endpoint에서 JWKS를 가져와 토큰 서명을 검증한다. OIDC Provider가 등록되어 있지 않으면 이 단계에서 바로 막힌다.
단계 ⑤는 서명 검증을 통과한 토큰의 claim과 IAM Role의 Trust Policy 조건을 비교하는 단계다. sub, aud 같은 필드가 Trust Policy와 한 글자라도 다르면 AccessDenied가 반환된다. 공식 KB와 이슈 아카이브에서 가장 자주 인용되는 단계다.
3. 깨질 수 있는 6개 지점
6개 패턴을 5단계에 매핑하면 아래와 같다. 빈도 열은 AWS re:Post KB의 언급 순서와 참조한 GitHub 이슈 아카이브 건수를 토대로 매긴 상대 지표다.
| 패턴 | 깨진 단계 | 참고 자료 빈도 |
|---|---|---|
| SA annotation 누락·오타 | ① | 자주 |
| MutatingWebhook 설정 손상 | ① | 드묾 |
| AWS SDK 버전 미달 | ② | 보통 |
| OIDC Provider 미등록 | ④ | 보통 |
| Trust Policy sub 불일치 | ⑤ | 매우 자주 |
| Trust Policy aud 누락 | ⑤ | 보통 |
각 패턴을 증상·진단·조치 순서로 정리한다.
3.1 SA annotation 누락
IRSA가 작동하려면 ServiceAccount에 IAM Role ARN을 annotation으로 알려줘야 한다. 이 annotation이 바로 MutatingWebhook이 참조하는 값이다. Helm chart를 새로 붙이거나 values에서 annotation 필드를 비운 채 배포하는 경우 가장 자주 발생하는 실수 유형이다.
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
namespace: kube-system
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/external-dns-irsa
증상
Pod에 AWS_ROLE_ARN env 자체가 들어가지 않는다. SDK는 web identity 방식을 시도조차 하지 않고 다른 자격증명 프로바이더(IMDS 등)로 fallback한다. 결과적으로 노드 IAM Role을 쓰게 되거나 NoCredentialsError가 발생한다.
애플리케이션 로그에서 자주 보는 메시지는 아래와 같다.
botocore.exceptions.NoCredentialsError: Unable to locate credentials
또는 AWS Load Balancer Controller 같은 도구에서는 다음과 같이 나온다.
failed to retrieve credentials: NoCredentialProviders: no valid providers in chain
진단
먼저 ServiceAccount에 annotation이 붙어 있는지 확인한다.
kubectl describe sa external-dns -n kube-system
출력에 다음 줄이 없으면 annotation이 빠진 것이다.
Annotations: eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/external-dns-irsa
다음으로 Pod Spec에 env가 주입되었는지 본다.
kubectl get pod external-dns-xxxxx -n kube-system -o yaml | grep -A2 AWS_ROLE_ARN
annotation은 있는데 Pod env에 반영되지 않는다면 Pod를 다시 만들어야 한다. MutatingWebhook은 Pod 생성 시점에만 동작하므로 기존 Pod는 annotation 변경의 영향을 받지 않는다.
조치
annotation을 추가하고 Pod를 재생성한다.
kubectl annotate sa external-dns -n kube-system \
eks.amazonaws.com/role-arn=arn:aws:iam::123456789012:role/external-dns-irsa \
--overwrite
kubectl rollout restart deployment external-dns -n kube-system
Deployment가 아닌 수동 Pod라면 삭제하고 다시 만들어야 한다.
조치 후 확인은 Pod env로 한다.
kubectl exec -n kube-system external-dns-xxxxx -- env | grep AWS
다음 두 줄이 보이면 정상이다.
AWS_ROLE_ARN=arn:aws:iam::123456789012:role/external-dns-irsa
AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token
3.2 MutatingWebhook 설정 손상
3.1이 "특정 SA의 annotation이 빠진 경우"라면 이번 패턴은 "클러스터 전체에서 Webhook이 동작하지 않는 경우"다. 발생 빈도는 낮지만 터지면 모든 IRSA Pod가 동시에 깨진다. pod-identity-webhook의 MutatingWebhookConfiguration이 삭제되거나 failurePolicy가 Ignore로 변경된 상태에서 컨트롤 플레인이 불안정해지면 JWT·env 주입이 스킵된다.
증상
annotation이 제대로 붙은 SA인데도 Pod env에 AWS_ROLE_ARN이 주입되지 않는다. 이 상태에서 Pod를 다시 만들어도 상황이 바뀌지 않는다. 클러스터 관리자가 실수로 MutatingWebhookConfiguration을 삭제했거나 EKS 컨트롤 플레인 장애가 있었던 경우다.
진단
Webhook 설정이 남아 있는지 먼저 확인한다.
kubectl get mutatingwebhookconfiguration pod-identity-webhook -o yaml
출력에 rules와 clientConfig가 정상적으로 있어야 한다. 특히 failurePolicy가 Ignore로 바뀌어 있다면 이전 복구 시도에서 누군가 Webhook 오류를 피하려고 바꿨을 가능성이 있다.
조치
pod-identity-webhook은 EKS 컨트롤 플레인이 자동 배포·관리하는 구성요소다. vpc-cni나 coredns 같은 EKS Managed Add-on과 달리 애드온 관리 API(aws eks describe-addon 등)로는 제어할 수 없다. 설정이 손상된 경우 가장 안전한 조치는 EKS 컨트롤 플레인 업데이트를 재시도하거나 AWS 지원에 문의하는 것이다. 임의로 재생성하면 Webhook 인증서가 꼬일 수 있다.
3.3 AWS SDK 버전 미달
IRSA는 SDK의 Web Identity Token Credential Provider가 지원되는 버전에서만 작동한다. 오래된 SDK는 AssumeRoleWithWebIdentity API 자체를 모른다. 베이스 이미지가 오래된 배치 잡이나 레거시 언어 런타임을 쓰는 워크로드에서 특히 자주 걸린다.
증상
Pod Spec에 env가 잘 주입되어 있고 annotation도 정상인데 SDK가 자격증명을 못 찾는다. 구버전 SDK는 Web Identity Token 프로바이더를 Credential Provider Chain에 포함하지 않기 때문에, env를 무시하고 IMDS(EC2 메타데이터) 경로로 fallback한다. 결과적으로 노드 IAM Role 권한으로 API를 호출하게 되어, 노드 Role에 해당 권한이 없으면 아래와 같은 에러가 찍힌다.
NoCredentialProviders: no valid providers in chain
또는 파이썬 boto3 같은 SDK에서는 AssumeRoleWithWebIdentity 경로를 건너뛴 채 다음과 같이 나온다.
botocore.exceptions.NoCredentialsError: Unable to locate credentials
주의할 점은 이 두 메시지는 3.1(SA annotation 누락)과 겹친다는 것이다. 이 경우 Pod env는 정상 주입되어 있다는 점이 구분 포인트다.
진단
컨테이너 이미지에 포함된 SDK 버전을 확인한다. Distroless나 slim 이미지에는 pip 자체가 없을 수 있으므로, 언어 런타임으로 직접 버전을 출력하는 방식이 안전하다.
kubectl exec -n app my-app-xxxxx -- python -c "import boto3; print(boto3.__version__)"
Go 애플리케이션이라면 go.mod를, Java라면 패키지 메타데이터(mvn dependency:tree 등)를 확인한다.
최소 지원 버전 표
AWS 공식 문서에 명시된 최소 버전은 다음과 같다.
| 언어 | 최소 버전 |
|---|---|
| Java v1 | 1.12.782 |
| Java v2 | 2.10.11 |
| Go v1 | 1.23.13 |
| Go v2 | 전체 지원 |
| Python (boto3) | 1.9.220 |
| Python (botocore) | 1.12.200 |
| .NET v3 | 3.3.659.1 |
| JavaScript v2 (Node) | 2.525.0 |
| JavaScript v3 (Node) | 3.27.0 |
| Ruby | 3.58.0 |
| PHP v3 | 3.110.7 |
| C++ | 1.7.174 |
| AWS CLI | 1.16.232 |
조치
SDK를 위 표 이상으로 업그레이드한다. 기본적으로 최신 LTS 버전을 쓰는 게 안전하다. 불가피하게 구버전을 써야 한다면 IRSA 대신 Pod Identity나 Secret에 정적 키를 주입하는 우회 방법을 써야 한다. Pod Identity 내용은 4주차 글에서 다뤘다.
SDK 업그레이드가 어려운 상황이라면 STS_REGIONAL_ENDPOINTS=regional 환경변수를 추가로 설정해 글로벌 엔드포인트 이슈를 피할 수 있다.
env:
- name: AWS_STS_REGIONAL_ENDPOINTS
value: regional
3.4 OIDC Provider 미등록
IRSA는 클러스터의 OIDC Discovery endpoint가 AWS IAM에 "OIDC Provider"로 등록되어 있어야 작동한다. 클러스터를 새로 만들고 이 과정을 잊으면 모든 IRSA 요청이 실패한다. 프로덕션 클러스터를 재구축할 일은 드물어서 자주 보이지는 않지만, 테스트 환경을 반복 생성하거나 클러스터 구축과 IRSA 설정 주체가 다른 상황에서 자주 걸린다.
증상
OIDC Provider가 등록되지 않아 IAM이 토큰 서명을 검증할 공개키를 가져올 경로를 모르는 상태다. SDK 로그에는 다음과 같은 메시지가 찍힌다.
InvalidIdentityTokenException: No OpenIDConnect provider found in your account
for https://oidc.eks.ap-northeast-2.amazonaws.com/id/XXXX
진단
계정에 등록된 OIDC Provider 목록을 확인한다.
aws iam list-open-id-connect-providers
클러스터의 OIDC issuer URL이 이 목록에 없다면 등록이 빠진 것이다.
CLUSTER_NAME=myeks
aws eks describe-cluster --name $CLUSTER_NAME \
--query cluster.identity.oidc.issuer --output text
조치
eksctl을 쓰면 한 줄로 끝난다.
eksctl utils associate-iam-oidc-provider \
--cluster $CLUSTER_NAME --approve
Terraform을 쓰는 환경이라면 aws_iam_openid_connect_provider 리소스를 모듈에 포함시키는 게 안전하다. 이 부분은 6장에서 다시 다룬다.
3.5 Trust Policy sub 불일치
6가지 원인 중 공식 KB와 이슈 아카이브에서 가장 자주 인용되는 패턴이다. AWS re:Post IRSA 트러블슈팅 KB의 첫 번째 체크 항목이자, kubernetes-sigs/aws-load-balancer-controller#1935, aws/karpenter#1666, grafana/loki#20429 같은 주요 오픈소스 이슈 아카이브에서도 반복적으로 등장한다. IAM Role의 Trust Policy 안에 있는 sub 조건이 Pod가 제시하는 JWT의 sub claim과 한 글자라도 다르면 IAM은 즉시 AccessDenied를 반환한다.
Trust Policy는 다음과 같은 구조를 가진다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/ABCD1234"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/ABCD1234:sub": "system:serviceaccount:kube-system:external-dns",
"oidc.eks.ap-northeast-2.amazonaws.com/id/ABCD1234:aud": "sts.amazonaws.com"
}
}
}
]
}
여기서 sub 값의 형식은 system:serviceaccount:<namespace>:<serviceaccount-name>이다. 세 부분 중 어느 하나라도 실제 Pod의 SA와 다르면 일치 실패다.
증상
An error occurred (AccessDenied) when calling the AssumeRoleWithWebIdentity operation:
Not authorized to perform sts:AssumeRoleWithWebIdentity
이전까지 동작하던 Pod에서 갑자기 발생하는 경우가 많으며, 직전의 설정 변경 이력을 먼저 확인하는 것이 효과적이다.
세부 패턴
- namespace 변경: SA를
kube-system에서kube-addons로 옮겼는데 Trust Policy는kube-system그대로 - SA 이름 rename:
external-dns→externaldns로 바꿨는데 Trust Policy 미반영 - Helm chart 업그레이드 후
serviceAccount.name기본값이 바뀐 경우 - Terraform 모듈 리팩토링 도중 변수 값이 한 글자 변경된 경우
- 형식 오타:
system:serviceaccount를system:serviceaccounts로 적은 경우, 하이픈 위치가 어긋난 경우(external-dnsvsexternal_dns)
진단 1: JWT claim 직접 확인
Pod 안에서 실제로 발급된 토큰의 sub 값을 본다. JWT payload는 base64url 인코딩 방식이고 padding이 생략되어 있어서, 일부 환경의 base64 -d가 바로 실패할 수 있다. 아래처럼 padding을 보정하는 방식이 안전하다.
kubectl exec -n kube-system external-dns-xxxxx -- \
cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token | \
awk -F. '{
p=$2
while (length(p) % 4 != 0) p=p"="
print p
}' | base64 -d | jq .
또는 jwt CLI가 설치되어 있다면 더 간단하게 확인할 수 있다.
kubectl exec -n kube-system external-dns-xxxxx -- \
cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token | jwt decode -
출력에서 sub 필드를 찾는다.
{
"aud": ["sts.amazonaws.com"],
"sub": "system:serviceaccount:kube-system:external-dns",
"iss": "https://oidc.eks.ap-northeast-2.amazonaws.com/id/ABCD1234"
}
이 값과 Trust Policy의 sub 조건이 정확히 같아야 한다.
진단 2: Trust Policy 확인
ROLE_NAME=external-dns-irsa
aws iam get-role --role-name $ROLE_NAME \
--query 'Role.AssumeRolePolicyDocument'
AWS CLI v2에서는 위 명령이 JSON 객체를 바로 돌려준다. v1에서는 URL-encoded 문자열로 반환되므로 별도 디코딩이 필요하다.
aws iam get-role --role-name $ROLE_NAME \
--query 'Role.AssumeRolePolicyDocument' --output text | \
python3 -c "import sys, urllib.parse, json; print(json.dumps(json.loads(urllib.parse.unquote(sys.stdin.read())), indent=2))"
진단 3: CloudTrail에서 실제 요청 값 보기
Trust Policy와 JWT를 비교해도 미묘한 차이(공백, 유사 문자)를 놓칠 수 있다. CloudTrail에는 STS가 실제로 받은 요청 값이 남는다.
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity \
--max-results 5 | jq '.Events[].CloudTrailEvent | fromjson | .requestParameters'
출력에서 roleSessionName과 roleArn이 기대한 값인지 확인한다. 만약 Pod가 STS에 도달하지도 못하는 상태라면 CloudTrail에 이벤트 자체가 기록되지 않는다. 그 경우 장애는 ①~③ 단계에서 발생한 것이다.
CloudTrail 이벤트는 발생 시점부터 조회 가능해지기까지 최대 15분 정도 지연이 있다. 장애 직후 쿼리했는데 결과가 비어 있다면 몇 분 뒤 다시 시도해 본다.
진단 4: CloudTrail errorMessage 상세 확인
CloudTrail 이벤트의 errorMessage 또는 responseElements 필드에는 IAM이 거부 판정을 내린 구체적 사유가 남아 있는 경우가 있다. 예를 들어 sub 불일치 시 "Not authorized to perform sts:AssumeRoleWithWebIdentity"와 함께 제공된 sub 값이 로그에 찍혀 Trust Policy 조건과 육안 비교가 가능해진다.
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity \
--max-results 5 | jq '.Events[].CloudTrailEvent | fromjson | {errorCode, errorMessage, requestParameters}'
일부 IAM 일반 거부 응답에는 EncodedAuthorizationMessage 필드가 포함되어 aws sts decode-authorization-message로 복호화할 수 있지만, AssumeRoleWithWebIdentity의 거부 응답에는 포함되지 않는 경우가 많다. CloudTrail 이벤트 원본을 먼저 확인하는 순서가 실효성이 더 높다.
조치
Trust Policy를 실제 SA 값에 맞춰 수정한다. AWS CLI로 바로 업데이트할 수 있다.
cat > trust-policy.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/ABCD1234"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/ABCD1234:sub": "system:serviceaccount:kube-addons:external-dns",
"oidc.eks.ap-northeast-2.amazonaws.com/id/ABCD1234:aud": "sts.amazonaws.com"
}
}
}
]
}
EOF
aws iam update-assume-role-policy \
--role-name external-dns-irsa \
--policy-document file://trust-policy.json
수정은 즉시 반영된다. Pod 재시작 없이 STS가 다음 호출부터 새 Trust Policy로 판단한다. 다만 SDK가 자격증명을 캐싱하고 있을 수 있으므로 수 분 정도 기다린 뒤 확인하는 게 편하다.
3.6 Trust Policy aud 누락
aud는 토큰의 수신자를 지정한다. AWS STS가 받아들이는 값은 sts.amazonaws.com 하나뿐이다. 이 조건이 Trust Policy에 없거나 다른 값으로 설정되면 IAM은 토큰을 거부한다. 오래된 정책 템플릿을 복사해 쓸 때 aud 라인이 빠진 채 전파되는 경우가 대표적이다.
증상
An error occurred (AccessDenied) when calling the AssumeRoleWithWebIdentity operation:
Incorrect token audience
또는 Trust Policy에 aud 조건이 아예 없으면 sub만으로 매칭을 시도하다 실패해 일반 Not authorized 메시지가 뜨기도 한다.
진단
OIDC Provider에 등록된 Client ID List를 확인한다. 한 계정에 EKS 클러스터가 여러 개라면 첫 번째 Provider가 엉뚱한 클러스터의 것일 수 있으므로, 대상 클러스터의 OIDC ID로 필터링해야 한다.
CLUSTER_NAME=myeks
OIDC_ID=$(aws eks describe-cluster --name $CLUSTER_NAME \
--query "cluster.identity.oidc.issuer" --output text | awk -F/ '{print $NF}')
PROVIDER_ARN=$(aws iam list-open-id-connect-providers | \
jq -r ".OpenIDConnectProviderList[] | select(.Arn | contains(\"$OIDC_ID\")) | .Arn")
aws iam get-open-id-connect-provider \
--open-id-connect-provider-arn $PROVIDER_ARN \
--query 'ClientIDList'
출력에 sts.amazonaws.com이 포함되어야 한다. 없다면 OIDC Provider 등록 시 ClientID를 잘못 지정한 것이다.
조치
OIDC Provider를 재생성하거나 Trust Policy의 aud 조건을 다음과 같이 추가한다.
"Condition": {
"StringEquals": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/ABCD1234:aud": "sts.amazonaws.com"
}
}
Trust Policy에 sub와 aud 두 조건을 모두 두는 게 표준 권장안이다. sub만 두면 같은 OIDC Provider를 쓰는 다른 AWS 서비스의 토큰도 통과할 수 있어 권한 격리가 약해진다.
4. 어느 순서로 확인할 것인가
6가지 패턴을 머릿속에 나열식으로 가지고 있으면 막상 장애가 터졌을 때 우왕좌왕하기 쉽다. "어느 쪽부터 볼지"에 대한 순서를 플로우차트로 정리해 두면 디버깅 시간을 크게 줄일 수 있다.
Pod 로그에 AccessDenied 또는 NoCredentials 에러
│
▼
[Q1] 클러스터 전체가 영향받나, 특정 Pod만 영향받나?
│
├── 전체 영향 ──► 3.2 MutatingWebhook 확인
│ kubectl get mutatingwebhookconfiguration pod-identity-webhook
│ 또는 3.4 OIDC Provider 미등록
│ aws iam list-open-id-connect-providers
│
└── 특정 Pod 영향
│
▼
[Q2] Pod env에 AWS_ROLE_ARN이 있나?
│
├── 없음 ──► 3.1 SA annotation 확인
│ kubectl describe sa <name>
│
└── 있음
│
▼
[Q3] 에러 메시지가 "No OpenIDConnect provider found"인가?
│
├── Yes ──► 3.4 OIDC Provider 등록 확인
│
└── No
│
▼
[Q4] 에러 메시지가 "Incorrect token audience"인가?
│
├── Yes ──► 3.6 Trust Policy aud 확인
│
└── No
│
▼
[Q5] SDK 버전이 최소 지원 버전 이상인가?
│
├── 미달 ──► 3.3 SDK 업그레이드
│
└── 충족
│
▼
3.5 Trust Policy sub vs JWT sub 비교
(가장 자주 걸리는 지점)
플로우차트의 번호는 본문 3장의 절 번호(3.1~3.6)다. IRSA 5단계 번호(①~⑤)와는 별개의 축이므로 혼동하지 않아야 한다. 단계 번호는 2장 표에서, 원인 번호는 3장 소제목에서 확인할 수 있다.
이 플로우를 따르면 대부분의 IRSA 장애는 빠르게 범위가 좁혀진다.
3.5 sub 불일치는 "한 글자 오타"처럼 보이지만 범위 좁히기가 선행되지 않으면 다른 곳을 확인하느라 시간이 길어지기 쉽다. Q1~Q5를 순서대로 거른 뒤 sub 검증으로 내려오는 흐름을 권장한다.
5. 에러 메시지와 깨진 단계 매트릭스
로그 메시지를 보고 단계를 역추적할 수 있도록 치트시트 형태로 정리한다.
| 에러 메시지 | 주로 깨진 단계 | 확인할 곳 | 대표 조치 |
|---|---|---|---|
Not authorized to perform sts:AssumeRoleWithWebIdentity |
⑤ | Trust Policy sub/aud |
Trust Policy 수정 |
Incorrect token audience |
⑤ | Trust Policy aud |
aud 조건 추가 |
No OpenIDConnect provider found in your account |
④ | OIDC Provider 등록 | eksctl utils associate-iam-oidc-provider |
InvalidIdentityTokenException |
④ | OIDC Provider 등록 상태 | OIDC Provider 재등록 |
WebIdentityErr: failed to retrieve credentials |
② ~ ③ | SDK 버전 / STS endpoint | SDK 업그레이드 |
NoCredentialProviders: no valid providers in chain |
① | SA annotation / Webhook | annotation 추가, Pod 재생성 |
Unable to locate credentials |
① | Pod env / 토큰 파일 경로 | Pod Spec 주입 여부 확인 |
이 표를 북마크하거나 디버깅 노트에 복사해 두면 로그 메시지만 보고도 의심할 단계를 한 번에 좁힐 수 있다.
6. 팀 수준에서 재발 막기
IRSA 장애는 대부분 "사람이 한 글자 틀렸다"로 환원된다. 개인의 주의력에 의존하는 방식으로는 같은 유형의 장애가 반복되기 쉽다. 파이프라인 차원의 문제로 바라보면 재발을 구조적으로 줄일 수 있다. 아래는 이번 학습 과정에서 조사한 자료와 공개 이슈 사례들을 바탕으로, 실무 적용 시 고려할 수 있는 패턴들을 정리한 것이다.
6.1 Terraform 모듈화
SA, IAM Role, Trust Policy를 세 개의 다른 파일에서 관리하면 변경이 어긋날 확률이 높다. 한 모듈로 묶어 버리는 게 안전하다.
module "irsa_external_dns" {
source = "./modules/irsa-role"
cluster_name = "myeks"
oidc_provider_arn = module.eks.oidc_provider_arn
oidc_issuer = module.eks.cluster_oidc_issuer_url
service_account = "external-dns"
namespace = "kube-system"
managed_policy_arns = [aws_iam_policy.external_dns.arn]
}
모듈 내부에서는 sub 값을 자동으로 계산한다. 사용자가 namespace와 SA 이름을 바꾸면 Trust Policy도 같이 바뀐다.
resource "aws_iam_role" "this" {
name = "${var.service_account}-irsa"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Federated = var.oidc_provider_arn
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"${replace(var.oidc_issuer, "https://", "")}:sub" = "system:serviceaccount:${var.namespace}:${var.service_account}"
"${replace(var.oidc_issuer, "https://", "")}:aud" = "sts.amazonaws.com"
}
}
}
]
})
}
이렇게 하면 sub 값을 사람이 직접 적어 넣는 데서 오는 오타를 크게 줄일 수 있다. terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks 같은 공개 모듈을 쓰면 더 편하다.
6.2 Helm chart annotation 표준화
External DNS, AWS Load Balancer Controller, cert-manager 같은 도구를 Helm chart로 배포한다면 values.yaml에서 annotation을 표준화해 둔다.
serviceAccount:
create: true
name: external-dns
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam::123456789012:role/external-dns-irsa"
배포 자동화 시스템(ArgoCD 등)에서 이 values를 git으로 관리하면 annotation 누락이 PR 리뷰 단계에서 드러나기 쉽다.
'AWS > EKS' 카테고리의 다른 글
| AWS EKS Upgrade 워크샵(1.30 → 1.31) (0) | 2026.05.04 |
|---|---|
| EKS Multi-tenant SaaS GitOps 워크샵(Flux v2 + Argo Workflows) (0) | 2026.04.27 |
| EKS AuthN/AuthZ - AEWS4 4주차 (0) | 2026.04.13 |
| EKS 컴퓨팅과 오토스케일링 - AEWS4 3주차 (0) | 2026.04.03 |
| [ AEWS4 ] EKS 네트워킹 살펴보기 (0) | 2026.03.25 |