- Published on
EKS IRSA는 되는데 STS 429 Throttling 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
EKS에서 IRSA(IAM Roles for Service Accounts) 설정은 멀쩡합니다. 서비스어카운트에 어노테이션도 붙었고, 파드 안에서 AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE도 보이며, 권한도 맞습니다. 그런데 운영 트래픽이 올라가거나 파드가 많이 뜨는 타이밍에 갑자기 애플리케이션 로그에 아래 같은 오류가 쏟아집니다.
ThrottlingException: Rate exceeded- HTTP
429from STS AssumeRoleWithWebIdentity호출 실패
이 상황은 “IRSA가 안 된다”가 아니라, IRSA가 너무 잘(?) 동작해서 STS 토큰 교환 요청이 폭주한 결과인 경우가 대부분입니다. 이 글에서는 (1) 왜 STS 429가 나는지, (2) 어떤 지표/로그로 확진하는지, (3) 즉효성 있는 완화책과 (4) 구조적으로 재발을 막는 방법을 실전 관점에서 정리합니다.
관련해서 IRSA 자체 점검이 필요하다면 먼저 아래 글의 10분 진단 체크리스트가 도움이 됩니다.
증상: “IRSA는 되는데” 왜 STS만 429로 터지나
IRSA 흐름을 아주 단순화하면 다음과 같습니다.
- 파드가 서비스어카운트 토큰(JWT)을 파일로 마운트받음 (
AWS_WEB_IDENTITY_TOKEN_FILE) - AWS SDK/클라이언트가 STS
AssumeRoleWithWebIdentity호출 - STS가 임시 자격 증명(AccessKey/SecretKey/SessionToken)을 반환
- SDK는 이 자격 증명을 캐시하고, 만료 전에 갱신
문제는 2번입니다. 다음 조건이 겹치면 STS 호출 수가 기하급수적으로 늘어납니다.
- 파드 수가 많음(수십~수백)
- 각 파드 안에 워커/스레드/프로세스가 많음(예: gunicorn worker 8개, sidecar 포함)
- 애플리케이션이 여러 AWS 클라이언트를 짧은 주기로 생성(클라이언트 생성 시마다 크리덴셜 리졸브)
- 크리덴셜 캐시가 프로세스/컨테이너 단위로 분리되어 공유되지 않음
- 트래픽 스파이크/오토스케일로 파드가 동시에 기동(콜드 스타트)
즉, IRSA는 정상이라도 “토큰 교환 API(STS) 호출량”이 한도를 넘으면 429가 납니다.
확진: STS Throttling인지 빠르게 확인하는 방법
1) 애플리케이션 로그에서 API 이름 확인
대부분 SDK는 어떤 API가 실패했는지 로그/예외 메시지에 남깁니다.
AssumeRoleWithWebIdentity가 보이면 거의 확정GetCallerIdentity만 실패한다면(헬스체크에서 자주 호출) 다른 패턴일 수 있음
2) CloudTrail에서 STS 이벤트 빈도 확인
CloudTrail의 EventName이 AssumeRoleWithWebIdentity로 급증하는지 확인합니다.
- 이벤트 소스:
sts.amazonaws.com - 이벤트 이름:
AssumeRoleWithWebIdentity - UserAgent:
aws-sdk-*등
3) CloudWatch 지표/로그(가능하면)로 429 집계
STS는 서비스별 지표가 제한적일 수 있으나, 애플리케이션 측에서 다음을 반드시 남기면 좋습니다.
- 예외 타입(ThrottlingException)
- HTTP status(429)
- 재시도 횟수
- 호출한 SDK/라이브러리 버전
4) 파드 내부에서 환경변수/토큰 파일 확인(보조)
IRSA 자체가 틀린 건 아닌지 확인하는 최소 체크입니다.
kubectl exec -it deploy/myapp -- sh -c 'env | egrep "AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION"'
kubectl exec -it deploy/myapp -- sh -c 'ls -l $AWS_WEB_IDENTITY_TOKEN_FILE && head -c 50 $AWS_WEB_IDENTITY_TOKEN_FILE'
여기까지 정상인데도 429라면, 거의 항상 “호출량/캐시/재시도” 문제입니다.
원인 패턴 5가지 (현장에서 가장 흔함)
1) 멀티프로세스 서버에서 크리덴셜 캐시가 프로세스마다 따로 돈다
예: gunicorn --workers 8이면, 8개 프로세스가 각자 STS를 치는 구조가 됩니다. 파드 50개면 동시에 400개의 프로세스가 토큰 교환을 시도할 수 있습니다.
2) AWS 클라이언트를 요청마다 새로 만든다
요청 핸들러에서 매번 boto3.client('s3') 같은 코드를 호출하면, 크리덴셜 로딩/갱신이 과도해질 수 있습니다(특히 여러 서비스 클라이언트를 매번 만들 때).
3) 짧은 시간에 파드가 대량 기동(롤링 배포/오토스케일)
모든 파드가 거의 동시에 “첫 STS 호출”을 하면서 콜드 스타트 폭주가 발생합니다.
4) 과도한 GetCallerIdentity/STS 호출을 헬스체크에 넣었다
liveness/readiness에서 STS를 직접 호출하면, 클러스터 전체가 지속적으로 STS를 두드립니다.
5) 재시도 정책이 공격적이거나(혹은 없음) 동시성 제어가 없다
429는 재시도를 해야 하지만, 지수 백오프 없이 즉시 재시도하면 스로틀링을 더 악화시킵니다. 반대로 재시도가 전혀 없으면 순간 스파이크에 바로 장애가 납니다.
해결 전략 개요: “STS 호출을 줄이고, 실패 시 우아하게 버틴다”
해결은 두 축으로 나뉩니다.
- 호출량 감소: 캐싱/클라이언트 재사용/프로세스 수 조절/배포 전략
- 스로틀링 내성: 적절한 재시도(지수 백오프+지터), 타임아웃, 서킷브레이커
아래부터는 우선순위대로 적용하기 쉬운 것부터 정리합니다.
1) 애플리케이션에서 AWS 클라이언트 “요청당 생성”을 없애기
Python(boto3) 예시: 전역/싱글톤으로 재사용
# bad: 요청마다 생성
import boto3
def handler(event, context):
s3 = boto3.client("s3")
return s3.list_buckets()
# good: 프로세스당 1회 생성(재사용)
import boto3
_s3 = boto3.client("s3")
def handler(event, context):
return _s3.list_buckets()
주의: 멀티프로세스(gunicorn workers)에서는 “프로세스당 1개”이므로, 워커 수가 많으면 여전히 STS 호출이 많을 수 있습니다. 그래도 요청당 생성보다는 압도적으로 낫습니다.
Node.js(AWS SDK v3) 예시
// bad
import { S3Client, ListBucketsCommand } from "@aws-sdk/client-s3";
export async function handler() {
const s3 = new S3Client({});
return await s3.send(new ListBucketsCommand({}));
}
// good
import { S3Client, ListBucketsCommand } from "@aws-sdk/client-s3";
const s3 = new S3Client({});
export async function handler() {
return await s3.send(new ListBucketsCommand({}));
}
2) 멀티프로세스/멀티컨테이너 동시성 줄이기 (가장 즉효)
- gunicorn/uwsgi worker 수를 “CPU 기준 최대치”로 무작정 올리면 STS가 먼저 죽습니다.
- sidecar(예: metrics/logging)에서 AWS SDK를 쓰는지 확인하세요. 사이드카도 IRSA를 사용하면 STS 호출 주체가 됩니다.
권장 접근:
- 워커 수를 줄이고(예: 8 → 2~4) HPA로 파드를 늘리는 방식이 STS 관점에서 더 안정적일 때가 많습니다.
- 파드 기동 시점에만 STS가 몰리면,
maxSurge,maxUnavailable조정으로 롤링 배포 동시성을 낮춥니다.
예: Deployment 롤링 업데이트 완화
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 10%
maxUnavailable: 0
3) STS 재시도 정책을 “지수 백오프 + 지터”로 강제
429는 일시적일 때가 많아 재시도로 회복됩니다. 다만 재시도는 반드시 **지수 백오프(Exponential Backoff) + 지터(Jitter)**가 있어야 합니다.
Python(botocore)에서 adaptive retry 활성화
botocore는 retry mode를 지원합니다. 컨테이너 환경변수로 강제하는 게 가장 간단합니다.
env:
- name: AWS_RETRY_MODE
value: adaptive
- name: AWS_MAX_ATTEMPTS
value: "10"
adaptive는 클라이언트 측에서 스로틀링 신호를 반영해 속도를 조절합니다.AWS_MAX_ATTEMPTS는 무작정 크게 하기보다, 서비스 특성/타임아웃과 함께 조정하세요.
Java SDK v2 예시(재시도 정책 명시)
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.core.retry.RetryMode;
import software.amazon.awssdk.services.s3.S3Client;
S3Client s3 = S3Client.builder()
.overrideConfiguration(
ClientOverrideConfiguration.builder()
.retryStrategy(RetryMode.ADAPTIVE)
.build()
)
.build();
재시도 설계 전반(타임아웃, 폴백, 서킷브레이커) 관점은 아래 글의 패턴이 그대로 적용됩니다.
4) STS 호출이 “언제” 폭주하는지 분리: 콜드 스타트 vs 상시 폭주
A. 배포/스케일 시점에만 터진다
- 원인: 동시에 많은 파드가 기동하며 첫 STS 교환을 수행
- 대응:
- 롤링 업데이트 동시성 낮추기
- HPA scale-up 속도 제한(가능하면)
- 애플리케이션 시작 시 AWS 호출(예: SSM 파라미터 로드)을 지연/캐시
B. 평소에도 지속적으로 터진다
- 원인: 요청당 클라이언트 생성, STS를 헬스체크에서 호출, 워커 과다, 외부 라이브러리의 잦은 credential refresh 등
- 대응:
- 클라이언트 재사용
- 헬스체크에서 AWS 호출 제거(단순 HTTP/DB ping 등으로 대체)
- 워커/스레드 모델 재검토
5) IRSA 토큰/자격증명 갱신 주기 이해하기 (불필요한 갱신 방지)
IRSA에서 사용하는 웹 아이덴티티 토큰은 쿠버네티스가 회전(rotating)합니다. SDK는 임시 자격증명의 만료를 감지해 갱신합니다.
여기서 중요한 점:
- 자격증명 캐시가 살아있어야 갱신이 최소화됩니다.
- 컨테이너/프로세스가 자주 재시작되면 매번 “첫 AssumeRoleWithWebIdentity”가 발생합니다.
따라서:
- CrashLoopBackOff가 있다면 먼저 안정화(재시작이 STS 폭주를 유발)
- readiness 실패로 재기동이 반복되는지 확인
노드/파드 상태 점검이 필요하면 아래 글의 체크리스트가 도움이 됩니다.
6) (가능하면) STS 호출 자체를 줄이는 아키텍처 선택지
서비스 특성에 따라 아래 옵션이 “근본 해결”이 될 수 있습니다.
1) 역할(Role) 분리/통합 재검토
마이크로서비스마다 Role을 잘게 쪼개면 보안은 좋아지지만, 파드 수가 많을수록 STS 호출 주체가 늘어납니다.
- 정말로 서비스마다 별도 Role이 필요한지
- 동일 권한을 쓰는 워커/잡이 여러 개라면 Role을 통합할 수 있는지
2) AWS 서비스 호출을 중앙화(예: 내부 API로 프록시)
모든 파드가 S3/SSM/Secrets Manager를 직접 호출하는 대신, 내부에서 제한된 수의 “credential-holding service”로 집약하면 STS 호출 주체가 줄어듭니다.
- 단점: 중앙 컴포넌트 장애 시 영향이 커짐
- 장점: STS/QPS 제어가 쉬움, 캐시를 공유하기 쉬움
3) STS를 헬스체크/부트스트랩에 쓰지 않기
부트 시점에 SSM/Secrets Manager를 읽어 환경을 구성하는 패턴은 흔하지만, 대규모 롤아웃에서 STS/다운스트림 모두에 스파이크를 줍니다.
- 가능한 값은 ConfigMap/Secret로 미리 주입
- 꼭 필요하면 지연 로딩 + 캐시 + 백오프
실전 체크리스트: 30분 내 안정화 플랜
- 로그에서 429가
AssumeRoleWithWebIdentity인지 확인 - 요청당 AWS 클라이언트 생성 제거(가장 먼저)
AWS_RETRY_MODE=adaptive,AWS_MAX_ATTEMPTS적용- gunicorn/worker 수 조정, 사이드카 포함 AWS SDK 사용 여부 점검
- 롤링 업데이트 동시성(
maxSurge) 낮춰 콜드 스타트 폭주 완화 - 헬스체크에서 STS/AWS 호출 제거
- CloudTrail로 STS 이벤트 급증이 줄었는지 확인
트러블슈팅 예시: “IRSA는 되는데 ExternalSecret이 가끔 죽는다”
External Secrets Operator/ExternalSecret도 내부적으로 AWS SDK를 사용하며, 클러스터 규모가 크면 STS 스로틀링의 영향을 받습니다. IRSA/KMS/권한 문제와 스로틀링 문제는 증상이 섞여 보일 수 있으니, 아래 글의 진단 순서를 따라가면 분리하는 데 도움이 됩니다.
결론
EKS에서 “IRSA는 되는데 STS 429 Throttling”은 설정 오류라기보다 STS 토큰 교환 호출이 과도하게 발생하는 운영/구현 문제인 경우가 대부분입니다. 가장 효과적인 처방은 (1) AWS 클라이언트 재사용으로 STS 호출을 줄이고, (2) 적절한 재시도(지수 백오프+지터, adaptive retry)를 적용하며, (3) 배포/스케일 동시성을 낮춰 콜드 스타트 폭주를 막는 것입니다.
위 조치만으로도 대개 “간헐적 인증 장애”가 사라지고, STS 호출량이 안정 구간으로 내려옵니다. 그래도 지속된다면 Role 설계/중앙화 같은 아키텍처 레벨의 감소 전략을 검토하는 단계로 넘어가면 됩니다.