ES /docs

batch_pull! error on batch arn:aws:ecs:us-west-2:002596530511:task/cupix-tesla-ece/ab866da276af40d4b

RCA: batch_pull! error — ecs:DescribeTasks IAM authorization denied

Overview#

What Happened#

2026-07-13 16:06 KST 부터 약 40분간 cupixworks-worker 서비스에서 AwsTask.batch_pull! 이 377회 연속 실패했다. 원인은 IAM 사용자 tesla-api-production-5nodecs:DescribeTasks 권한을 가지고 있지 않아 AWS ECS API 호출이 AccessDeniedException 으로 거부된 것이다. 동일한 권한 오류가 최소 2026-06-29 부터 존재해 왔고 (Datadog retention 상한), 2분마다 실행되는 cron (Cupix::Cron::AwsTask.pull_pending, pull_blank) 이 pending/blank 상태인 AwsTask 레코드를 반복 조회하면서 그 시점에 큐잉된 대량의 task 를 배치로 처리하는 순간마다 스파이크가 발생한다.

Quick Facts#

Field Value
exception.class Aws::ECS::Errors::AccessDeniedException (rescued as StandardError)
exception.message User: arn:aws:iam::002596530511:user/system/tesla-api-production-5nod is not authorized to perform: ecs:DescribeTasks on resource: * because no identity-based policy allows the ecs:DescribeTasks action
top_frame app/models/aws_task.rb:55 (ecs.describe_tasks)
runtime Rails / Sidekiq (cupixworks-worker)
env production, ECS cluster cupix-tesla-ece in us-west-2
iam_user arn:aws:iam::002596530511:user/system/tesla-api-production-5nod

Affected Teams#

Team / Domain Error Count Impact
cupixworks-worker (ECS task lifecycle tracking) 377 AwsTask 레코드의 last_status/desired_status 가 실제 ECS task 상태와 동기화되지 않음. Job.run_task_stopped_callbacks 가 트리거되지 않아 stopped task 에 대한 후속 처리(콜백)가 지연될 위험

Timeline#

  1. 2026-06-29 20:21 KSTPullTaskWorker::perform | error on 9836 ... not authorized to perform: ecs:DescribeTasks — 동일 권한 오류 최초 관측 (Datadog 14일 retention 상한이라 실제 시작 시점은 더 이를 수 있음).
  2. 2026-07-13 16:03 KSTPullTaskWorker::perform | error on 10256 (개별 워커 경로에서도 동일 오류) 재확인.
  3. 2026-07-13 16:06:30 KSTAwsTask.batch_pull! 배치 경로에서 오류 스파이크 시작 (cluster first_seen).
  4. 2026-07-13 16:46:18 KST — 스파이크 종료 (cluster last_seen). 40분 동안 377건.
  5. 2026-07-13 16:34–16:46 KST — 상태 보드에 cupixworks-worker service degraded 인시던트 자동 생성 (id 2026-07-13-svc-cupixworks-worker--unknown-2, 3 clusters 그룹핑).

Error Log#

Datadog Logs

text
batch_pull! error on batch arn:aws:ecs:us-west-2:002596530511:task/cupix-tesla-ece/ab866da276af40d4bf8db1f485e4c09a,arn:aws:ecs:us-west-2:002596530511:task/cupix-tesla-ece/16e50e1224b04b01bd504689163f1a5b,arn:aws:ecs:us-west-2:002596530511:task/cupix-tesla-ece/eca61fa88eed4831b37fd91b608bee2a - User: arn:aws:iam::002596530511:user/system/tesla-api-production-5nod is not authorized to perform: ecs:DescribeTasks on resource: * because no identity-based policy allows the ecs:DescribeTasks action

Impact#

  • Service: cupixworks-worker
  • 발생 횟수: 377
  • 최초 발생: 2026-07-13 16:06 KST
  • 최근 발생: 2026-07-13 16:46 KST
  • 부수 영향: 각 배치의 describe_tasks 호출이 실패하므로 AwsTasklast_status 가 갱신되지 않는다. after_save :task_stopped? 콜백이 발화하지 않아, 실제로 STOPPED 로 전이한 ECS task 에 대해 job.run_task_stopped_callbacks(task_id) 가 지연된다. 다만 Cupix::Cron::AwsTask.pull_blankblank scope(신규 생성 직후 상태) 만 대상으로 하고 pull_pending 은 pending scope 만 다루므로, 각 배치가 실패해도 대상 task 는 계속 스캔 대상에 남아 다음 cron 주기에 다시 시도된다 — 즉 데이터 손실이 아닌 반복 실패 상태다.

Root Cause Summary#

AwsTask.batch_pull!Aws::ECS::Client#describe_tasks 를 호출하여 배치로 ECS task 상태를 조회한다. 프로덕션 IAM principal tesla-api-production-5nod 의 identity-based policy 에 ecs:DescribeTasks 가 포함되어 있지 않아 AWS 가 요청을 거부한다. 이 권한 결함은 최소 2026-06-29 부터 계속 존재해 왔으며(직접 관측 가능한 retention 상한), 애플리케이션 코드는 변경되지 않았다 (app/models/aws_task.rb 마지막 수정은 2026-05-12, 커밋 f9b7cdd3a). 07-13 의 스파이크는 pending/blank scope 에 다수의 AwsTask 레코드가 축적된 시점에 2분 주기 cron 이 100개씩(BATCH_SIZE = 100) 배치로 조회하여 각 slice 마다 rescue 로그가 남은 결과다.

Technical Analysis#

Code Path#

  • Entry point: config/schedule.rb:29-35 — cron every 2.minutesCupix::Cron::AwsTask.pull_pending / pull_blank 실행
  • Call: lib/cupix/cron/aws_task.rb:21-25::AwsTask.batch_pull!(tasks)
  • Failure point: app/models/aws_task.rb:54-59ecs.describe_tasks(...)Aws::ECS::Errors::AccessDeniedException 을 raise → rescue StandardError (line 86-88) 에서 error 레벨로 로그
  • Alternate path: PullTaskWorker#perform (app/workers/pull_task_worker.rb:5-15) 도 AwsTask#pull!fetch! → 동일한 describe_tasks 호출을 하여 같은 예외를 rescue 로 기록한다.
app/models/aws_task.rb:54-88ruby
task_ids.each_slice(BATCH_SIZE) do |batch_ids|
  resp = ecs.describe_tasks({
    tasks: batch_ids,
    cluster: cluster_name,
    include: ['TAGS']
  })

  found_task_ids = Set.new
  resp.tasks.each do |resp_task|
    resp_json = resp_task.as_json
    record = tasks_by_task_id[resp_json['task_id']]
    next unless record
    # ...
  end

  (batch_ids - found_task_ids.to_a).each do |missing_id|
    # ...
  end
rescue StandardError => e
  Cupix::Logger.error("batch_pull! error on batch #{batch_ids.first(3).join(',')} - #{e.message}", class: name, function: __method__)
end
app/models/aws_task.rb:91-93ruby
def ecs_client
  Aws::ECS::Client.new region: ENV['CUPIX_REGION'] || 'us-west-2'
end
config/schedule.rb:29-35ruby
every 2.minutes do
  # runner 'Cupix::Cron::AwsTask.pull_all'
  runner 'Cupix::Cron::AwsTask.pull_pending'
  runner 'Cupix::Cron::AwsTask.pull_blank'
  runner 'Cupix::Cron::CopyRequest.run_waiting_copy_reqeusts'
  runner 'Cupix::Cron::SiteinsightsService.publish_polling_events_by_schedule'
end

기대 동작: describe_tasks 가 성공적으로 응답을 반환하고 각 AwsTask 레코드의 last_status/desired_status/stopped_at 이 실제 ECS 상태와 일치하도록 갱신되어야 한다.

실제 동작: AWS 가 IAM 정책 부재로 AccessDeniedException 을 반환한다. 각 100개 slice 당 한 번의 rescue 로그가 남고, 상태 동기화는 이 cron 주기 동안 수행되지 않는다.

Log Evidence#

Datadog query used:

text
service:cupixworks-worker @class:AwsTask "not authorized"

첫 3건 (07-13 스파이크 초반):

text
2026-07-13 16:06:30 KST  error
batch_pull! error on batch arn:aws:ecs:us-west-2:002596530511:task/cupix-tesla-ece/ab866da276af40d4bf8db1f485e4c09a,arn:aws:ecs:us-west-2:002596530511:task/cupix-tesla-ece/16e50e1224b04b01bd504689163f1a5b,arn:aws:ecs:us-west-2:002596530511:task/cupix-tesla-ece/eca61fa88eed4831b37fd91b608bee2a - User: arn:aws:iam::002596530511:user/system/tesla-api-production-5nod is not authorized to perform: ecs:DescribeTasks on resource: * because no identity-based policy allows the ecs:DescribeTasks action
@class=AwsTask @function=batch_pull!

동일 principal 로 개별 워커 경로에서도 실패:

text
2026-07-13 16:03:22 KST  error
PullTaskWorker::perform | error on 10256 - User: arn:aws:iam::002596530511:user/system/tesla-api-production-5nod is not authorized to perform: ecs:DescribeTasks on resource: * because no identity-based policy allows the ecs:DescribeTasks action
@class=PullTaskWorker @function=perform

만성적 발생 여부 확인 (retention 상한 근처 샘플):

text
service:cupixworks-worker "not authorized to perform: ecs:DescribeTasks"
text
2026-06-29 20:21:58 KST  error
PullTaskWorker::perform | error on 9836 - User: arn:aws:iam::002596530511:user/system/tesla-api-production-5nod is not authorized to perform: ecs:DescribeTasks on resource: * because no identity-based policy allows the ecs:DescribeTasks action

2026-06-29 부터 2026-07-13 까지 동일 오류가 지속적으로 관측되므로 이번 스파이크는 신규 회귀가 아니라 만성 IAM 결함이 특정 순간 배치 볼륨과 겹쳐 표면화된 것이다.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 프로덕션 IAM principal tesla-api-production-5nodecs:DescribeTasks 권한이 부여되지 않은 정책 결함 AWS 응답 메시지가 명시적으로 "no identity-based policy allows the ecs:DescribeTasks action" 이라고 밝힘; 동일 principal/동일 permission 오류가 최소 2026-06-29 부터 07-13 까지 지속됨 Confirmed
H2 최근 코드 변경이 describe_tasks 호출을 새로 추가하여 발생 app/models/aws_task.rb 마지막 수정은 2026-05-12 (f9b7cdd3a); describe_tasks 호출부(line 55, 118)는 그보다 오래됨 2026-06-29 부터 동일 오류 존재 → 07-13 이 신규 회귀가 아님 Rejected
H3 크로스 리전 호출 문제 (worker 가 다른 리전에서 실행 중) 클러스터 frontmatter 의 regions: ap-southeast-1 과 리소스 리전 us-west-2 가 다름 AWS IAM 은 리전 관계없이 policy attach 여부만 검사; 에러 메시지가 "no identity-based policy" 라고 명시하여 리전이 아닌 policy 부재가 원인임을 명확히 함. ecs_client (line 91-93) 도 us-west-2 로 명시 Rejected
H4 AWS 측 rate limiting / throttling 과거 커밋 5a7d5a8d5 ("Rate exceeded") 존재 이번 오류 메시지는 Rate exceeded 가 아니라 not authorized — 완전히 다른 예외 Rejected

Infrastructure Configuration (cupix-infrastructure)#

Revision 1 에서 실제 인프라 저장소를 조사한 결과, tesla-api-production-5nod IAM 사용자와 그 정책은 cupix-infrastructure 저장소에서 Terraform 으로 관리되고 있음을 확인했다.

IAM user 정의 위치: cupix-service/api-eb/main.tf:642-651

cupix-service/api-eb/main.tf:642-651hcl
resource "aws_iam_user" "api_user" {
  name = "tesla-api-${var.service_env}-${random_string.random.result}"
  path = "/system/"

  tags = local.tags
}

resource "aws_iam_access_key" "api_user" {
  user = aws_iam_user.api_user.name
}

random_string.random (line 636-640, length = 4) 이 5nod 접미사를 생성한다 → 사용자 이름 tesla-api-production-5nod 와 path /system/ 이 에러 메시지의 principal ARN arn:aws:iam::002596530511:user/system/tesla-api-production-5nod 와 정확히 일치한다.

ECS 관련 정책 정의 위치: cupix-service/api-eb/main.tf:769-809

cupix-service/api-eb/main.tf:769-809hcl
resource "aws_iam_policy" "api_ecs_policy" {
  name        = "${var.tenant}-tesla-${var.service_env}-ecs-policy-${random_string.random.result}"
  path        = "/tesla/"
  description = "${var.tenant} tesla ${var.service_env} ecs policy by terraform"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "IamPassRole",
        Effect = "Allow",
        Action = "iam:PassRole",
        Resource = [
          "arn:aws:iam::*:role/ecsTaskExecutionRole"
        ]
      },
      {
        Sid    = "HandleECS",
        Effect = "Allow",
        Action = [
          "ecs:*"
        ],
        Resource = [
          local.resource_ecs,
          "arn:aws:ecs:*:*:task/*",
          "arn:aws:ecs:*:*:container-instance/*",
          "arn:aws:ecs:*:*:service/*",
          "arn:aws:ecs:*:*:task-set/*/*/*",
          "arn:aws:ecs:*:*:task-definition/*:*"
        ]
      }
    ]
  })

  tags = local.tags
}

resource "aws_iam_user_policy_attachment" "api_ecs_attach" {
  user       = aws_iam_user.api_user.name
  policy_arn = aws_iam_policy.api_ecs_policy.arn
}

여기서 local.resource_ecs = "arn:aws:ecs:*:*:cluster/${var.ece_cluster_name}" (line 74) 이고, 프로덕션 (cupix-aws2/production/terragrunt.hcl:218) 은 ecs_cluster_name = "cupix-tesla-ece" 로 설정되어 있다.

핵심 발견 — Terraform 정의 vs. 실제 배포 상태 불일치 (drift 의심):

Terraform 코드상 이 정책은 ecs:* (와일드카드) 를 arn:aws:ecs:*:*:task/* 를 포함한 다양한 리소스에 부여하고 있어 ecs:DescribeTasks 가 허용되어야 한다. api_ecs_policy 는 2021-07-14 (TSLA-3027, commit 13658353) 부터 이 형태를 유지하고 있고, 그 이후 최근 변경은 TSLA-11560 (030403df, style-only whitespace 정렬) 뿐이다 — 실질적 정책 변경 없음.

그러나 프로덕션에서 AWS 가 반환하는 오류 메시지 no identity-based policy allows the ecs:DescribeTasks action 은 실제 IAM 평가 시점에 매칭되는 Allow 문이 없다는 뜻이다. Terraform 정의와 배포된 실제 정책 사이에 drift 가 발생했을 가능성이 매우 높다:

  1. api_ecs_policy 리소스가 손으로 삭제되었거나
  2. api_ecs_attach (user → policy attachment) 가 손으로 해제되었거나
  3. 프로덕션 stack 이 최신 Terraform 을 apply 하지 못한 채 IAM 사용자만 잔존 (drift)

셋 다 코드 자체 결함이 아니라 applied state 확인이 필요한 운영 이슈이다.

참고 — 관련 정책들 (같은 파일 내):

Policy resource Path Actions
api_default_policy (line 653) /tesla/ Lambda invoke, SES, STS, Cognito, SSM, StepFunctions
api_cognito_policy (line 740) /tesla/ Cross-account sts:AssumeRole
api_ecs_policy (line 769) /tesla/ ecs:* — 이번 이슈 대상
api_s3_policy (line 811) /tesla/ s3:* (다양한 버킷)
api_sqs_policy (line 865) /tesla/ SQS SendMessage

즉 ECS 권한은 별도 파일이 아니라 같은 api-eb/main.tf 안의 api_ecs_policy 하나로 관리된다. Datadog IAM 지침이나 별도 module 이 아니라 이 파일이 유일한 소스임을 확인했다.

Fix Recommendation#

즉시 조치 (Critical)#

  • 인프라(IAM policy) drift 확인 및 재-apply — 코드 변경 아님. Terraform 코드는 이미 ecs:* (with task/*) 를 부여하도록 정의되어 있다 (cupix-infrastructure/cupix-service/api-eb/main.tf:769-809). 따라서 신규 정책을 작성할 필요는 없다. 대신:

    1. 인프라 팀에 cupix-aws2/production (account 002596530511, region us-west-2) 의 실제 IAM 사용자 정책 목록을 확인 요청:
      bash
      aws iam list-attached-user-policies --user-name tesla-api-production-5nod \
        --profile cupix-aws2-production
      
      기대 출력에 cupix-tesla-production-ecs-policy-5nod (또는 유사한 이름, random suffix 5nod) 가 포함되어야 한다. 누락 시 drift 이므로 terragrunt apply 로 재-apply 하면 해결된다.
    2. 정책은 존재하지만 attachment 만 없는 경우:
      bash
      aws iam list-policies --scope Local --path-prefix /tesla/
      
      api_ecs_policy 존재를 확인 후 aws_iam_user_policy_attachment.api_ecs_attach 만 재적용한다.
    3. 정책 자체가 삭제된 경우, 해당 리소스만 targeted apply:
      bash
      cd cupix-aws2/production
      terragrunt apply \
        -target='module.tesla.module.api_eb.aws_iam_policy.api_ecs_policy' \
        -target='module.tesla.module.api_eb.aws_iam_user_policy_attachment.api_ecs_attach'
      
      (모듈 경로는 실제 terragrunt tree 에 맞춰 조정)
  • 최소권한 검토는 별개 이슈: 현재 코드는 ecs:* 라는 매우 넓은 액션을 부여한다. 이번 오류 복구와 무관하게, 장기적으로는 실제로 tesla 가 호출하는 액션 (DescribeTasks, RunTask, StopTask, ListTasks, RegisterTaskDefinition 등) 만 열거하도록 정책을 좁히는 리팩터링을 별도 티켓으로 검토 가능.

단기 개선 (1주 이내)#

  • app/models/aws_task.rb:86-88rescue StandardErrorAws::ECS::Errors::AccessDeniedException 와 그 외 예외로 분기해 IAM 결함은 Cupix::Logger.warn 대신 error 로 유지하되, 스팸을 줄이기 위해 배치 실패 시 첫 발생만 error, 이후 반복은 warn 으로 downgrade 하는 rate-limited 로깅을 검토. 다만 IAM 이슈는 조용해지면 알아채기 어려우므로 downgrade 대신 아래 모니터링을 우선 도입한다.
  • PullTaskWorker (app/workers/pull_task_worker.rb:13-15) 의 rescue 도 AccessDeniedException 을 별도 로그 필드로 태깅하여 Datadog facet 으로 필터링 가능하게 할 것.

장기 개선 (재발 방지)#

  • IAM policy drift 감지: Terraform state 와 실제 배포된 policy 를 주기적으로 비교하는 awspec / iam-lint 등 검증 파이프라인을 CI 에 추가.
  • ECS 접근이 필요한 새 코드 경로 (describe_tasks, stop_task, list_tasks 등) 도입 시 인프라 팀 리뷰 필수화 (repo-level CODEOWNERS 또는 PR 템플릿 체크리스트).
  • AwsTask 동기화 실패가 실제 job 콜백 지연으로 이어지는지 관찰하기 위해 Job.run_task_stopped_callbacks 호출 지연 시간을 메트릭화.

Monitoring#

  • Datadog 알림: ecs:DescribeTasks AccessDenied 관련 error 가 5분 window 에 10건 이상이면 페이지. 다음 timeseries 쿼리를 dashboard widget 에 사용:
text
sum:trace.rack.request.errors{service:cupixworks-worker,resource_name:aws_task.batch_pull}.as_count()
text
logs("service:cupixworks-worker @class:AwsTask \"not authorized to perform: ecs:DescribeTasks\"").index("*").rollup("count").by("host").rollup("count", "5m")
  • PullTaskWorker 경로:
text
logs("service:cupixworks-worker @class:PullTaskWorker \"not authorized\"").index("*").rollup("count").by("environment").rollup("count", "5m")
  • ECS API 4xx 비율 (인프라 측):
text
sum:aws.ecs.api.4xx_error{account_id:002596530511,region:us-west-2}.as_count()

각 쿼리는 writing-datadog-monitoring-queries 가이드에 맞춰 monitor-only 문법(| stats, threshold suffix) 을 사용하지 않았다.

Risk Assessment#

  • Risk level: medium — 실제 데이터 파괴는 없으나 ECS task 상태 동기화가 지속 실패하여 run_task_stopped_callbacks 트리거가 지연될 수 있고, PullTaskWorker 큐에 재시도(retry: 1) 트래픽이 누적된다. 로그 노이즈로 다른 실질 오류가 묻힐 위험도 존재.
  • 예상 복잡도: standard — 인프라(IAM policy) 변경 위주. 애플리케이션 코드 변경은 선택적(로깅 개선)이며 breaking change 없음. 인프라 저장소 위치 확인 및 리뷰가 필요하므로 소규모 크로스팀 조율이 필요하다.

Revision History#

Revision 1#

Feedback: cupix-infra repo 에서 어떻게 설정되어있는건지 확인좀

판정:

피드백 항목 판정 근거
cupix-infra (실제 저장소명 cupix-infrastructure) 에서 IAM 정책이 어떻게 설정되어 있는지 확인 수용 cupix-infrastructure/cupix-service/api-eb/main.tf:642-651 에 IAM user tesla-api-${service_env}-${random_string} (path /system/) 정의 확인 — 프로덕션 service_env=production, random_string=5nod 로 에러의 principal ARN 과 일치. 같은 파일 line 769-809api_ecs_policy (Action ecs:*, Resource arn:aws:ecs:*:*:task/* 포함) 정의, line 806-809 에 user attachment 확인. 프로덕션 cluster 이름은 cupix-aws2/production/terragrunt.hcl:218 에서 ecs_cluster_name = "cupix-tesla-ece" 로 설정 — 에러 로그의 task ARN 클러스터 이름과 일치. Terraform 정의상 정책은 ecs:DescribeTasks 를 허용해야 하지만 AWS 응답은 "no identity-based policy allows..." — Terraform 정의와 배포된 실제 IAM 상태 간 drift 가능성이 매우 높다는 결론 반영. git log develop -L 로 확인 시 api_ecs_policy 는 2021-07-14 (commit 13658353, TSLA-3027) 이후 style-only whitespace 정렬 (030403df) 외에는 변경 없음 — 최근 코드 회귀 아님.

변경 사항:

  • ## Infrastructure Configuration (cupix-infrastructure) 섹션을 신규 추가하여 IAM user/policy Terraform 정의 위치, random_string 접미사 매커니즘, prod cluster 이름 매핑, drift 가설을 코드 참조와 함께 명시.
  • ## Fix Recommendation### 즉시 조치 (Critical) 를 재작성. Revision 0 은 "저장소 위치 미확인, 새 정책 작성 필요"로 서술했지만, 조사 결과 정책은 이미 Terraform 에 있으므로 조치를 (1) drift 확인 명령, (2) attachment 재적용, (3) targeted terragrunt apply 세 단계로 구체화. 최소권한 검토는 별개 이슈로 분리.

추가 조사 내용:

  • 탐색한 저장소: /home/ec2-user/repos/cupix-infrastructure
  • 참조한 파일: cupix-service/api-eb/main.tf, cupix-service/api-eb/outputs.tf, cupix-service/main.tf, cupix-aws2/production/terragrunt.hcl
  • git log develop -L '/api_ecs_policy/,/^}$/:cupix-service/api-eb/main.tf' 로 정책 이력 확인 (Rev 이래 실질 변경 없음)
  • 프로덕션 이외에도 stage/qa/dev/au/eu/sg/jp/ca 리전별 ecs_cluster_name 값 확인 (cupix-tesla-ece-{env}, cupix-tesla-ece-{region}) — 같은 module 인스턴스가 리전마다 별도의 IAM user/policy 를 생성함을 확인