StudyDad Loop 제품을 만들며 배운 운영과 설계를 기록합니다.

FamBlend를 중심으로 실제 구현, 운영 메모, GitHub 포트폴리오를 연결해 쌓아가는 StudyDad의 작업 기록입니다.

성장과 기술/개발과 자동화

AI 장애 분석은 접근 권한 설계부터 시작합니다

박세식 2026. 7. 21. 12:00

AI 장애 분석 포스트모템 시리즈 1/9

Datadog에서 GET /heartbeat trace 하나가 30초 timeout 경계에 붙어 있는 화면을 보고 있었습니다. 같은 시점에 JVM Old Gen이 올라와 있었습니다.

초기 단서: 일부 GET /heartbeat span이 30초 timeout 경계에 붙어 있었습니다.

이 스크린샷 한 장을 Claude Code에 붙여두고 원인을 물어보려고 했습니다. 그런데 프롬프트를 어떻게 써야 하나 잠깐 고민하다가 이상한 점이 보였습니다. AI가 볼 수 있는 건 이 화면 한 장뿐이었습니다.

프롬프트보다 먼저 막힌 지점

먼저 Datadog API Key와 Application Key를 환경변수로 붙였습니다. /api/v1/validate는 성공했습니다. metric query와 hosts API는 403을 돌려줬습니다.

이 상태로 원인을 분석하라고 시키면 AI는 trace와 log 안에서만 그럴듯한 설명을 만들 것 같았습니다. 특히 /heartbeat 30초와 Old Gen 증가 두 단서에 끌려 GC나 DB ping을 원인으로 못박는 문서가 나올 가능성이 컸습니다. 실제로 /heartbeat는 단순 생존 확인이 아니라 DB ping()까지 호출하고 있었습니다.

@GetMapping("/heartbeat")
suspend fun heartbeat(): String {
    systemService.ping()
    return "OK"
}

Repository는 R2DBC를 사용했습니다.

class AccountRepository(
    private val accountR2dbcRepository: AccountR2dbcRepository,
) {
    suspend fun ping() {
        accountR2dbcRepository.ping().awaitSingle()
    }
}

이름은 heartbeat인데 동작은 readiness에 가까웠습니다. 그래서 코드만 보면 "DB pool 문제겠지"라고 결론짓기 쉬웠습니다. 그리고 그 결론을 반박할 metric 권한이 아직 열려 있지 않았습니다.

데이터 범위를 넓히자 처음 결론이 흔들렸습니다

그래서 Datadog scope를 보완했습니다. Timeseries query, Hosts read, Monitors read를 열자 JVM metric, request count, task-level tag까지 조회됐습니다. major GC count와 시간이 거의 0이었습니다.

그러자 Old Gen 증가는 그 순간 원인 후보에서 증상 쪽으로 내려갔습니다. 같은 시간대에 GET /v3/auth 요청이 6~10배 늘어 있었고, R2DBC write pool 대기와 ECS task replacement가 그 뒤에 있었습니다. 처음 눈에 들어왔던 /heartbeat와 Old Gen은 분석의 결론이 아니라 출발점이었습니다.

프롬프트가 아니라 조회 범위가 결정했습니다

처음에는 프롬프트를 잘 쓰는 문제인 줄 알았습니다. 하지만 실제로는 AI에게 어디까지 열어주는지의 문제였습니다. Claude Code에 아무리 잘 정리한 지시를 넣어도, 볼 수 있는 데이터가 좁으면 결론은 그 좁은 범위 안에서만 나옵니다.

연결 방식은 감당 가능한 범위로 나눴습니다

이번 조사에서 실제로 고려한 방식은 세 가지였습니다. 각각 붙이는 부담과 조회 범위가 달랐습니다.

MCP로 외부 시스템에 붙이기. Cursor에서 Datadog MCP를 이메일 인증으로 연결해봤습니다. 붙는 건 빨랐습니다. 다만 어떤 API가 실제로 열려 있는지, timeseries와 task tag까지 접근되는지는 따로 확인해야 했습니다. 붙는 게 쉬웠다고 근거가 자동으로 단단해지는 것은 아니었습니다. RDS와 ECS Fargate 사용량을 비용 관점으로 정리해달라고 했을 때 보고서 형식 문서가 바로 나온 것은 이 방식의 장점이었습니다.

에이전트에서 직접 API와 CLI를 호출. 이번 조사는 여기서 시작했습니다. Datadog API Key와 Application Key는 환경변수로 두고, AWS CLI profile은 read-only로 제한했습니다. AI가 실행한 명령이 그대로 보이니 실패했을 때 같은 명령을 사람이 다시 돌려 확인할 수 있었습니다. 앞서 metric query 403이 났던 것도 이 방식이었기에 눈에 띄었습니다.

AWS Bedrock Agent로 내부 자동화. 이번 조사에는 넣지 않았습니다. IAM, 네트워크, 감사 로그, 알림 채널 권한을 같이 설계해야 해서 처음부터 붙이기에는 무거웠습니다. read-only workbench 하나를 사람이 여러 번 안정적으로 돌려본 뒤로 미뤘습니다.

ReadOnly는 좁게 시작해 필요한 만큼만 열었습니다

권한을 넓게 열어두면 조사할 때는 편하지만 사고 반경도 같이 커집니다. 그래서 시작은 아래 정도로 좁혔습니다.

AWS:

  • CloudWatchReadOnlyAccess
  • AmazonRDSReadOnlyAccess
  • AWSXRayReadOnlyAccess
  • 필요 시 ECS 또는 EC2 ReadOnly

Datadog:

  • Logs read
  • APM read
  • Metrics read
  • Events read
  • Monitors read
  • Timeseries query
  • Hosts read

GitLab 또는 사내 Git:

  • Repository read
  • Merge requests read
  • Pipelines read
  • Deployments read

내부 알림 채널:

  • 장애 채널 read
  • 자동 전송은 별도 Bot 또는 webhook으로 분리

이번 조사에서 결정적이었던 건 Datadog metric 계열 scope였습니다. Timeseries query와 Hosts read가 없었으면 major GC count가 0이라는 걸 확인할 방법이 없었습니다. 첫 결론을 그대로 문서로 만들었을 것입니다.

요청할 때 고정해서 넣는 정보

권한을 넓히고 나서도 매번 프롬프트를 새로 쓰지 않았습니다. 대신 조사할 때마다 아래 정보는 형식을 고정해서 같이 넣었습니다.

항목 예시
시간 범위 2026-06-12 13:00~14:00 KST
서비스 account-service
초기 단서 GET /heartbeat trace 30초, Old Gen 증가
조회 대상 Datadog APM/logs/metrics, AWS ECS/ALB/RDS/CloudWatch, GitLab deploy
출력 형식 Timeline, Evidence, Hypotheses, Actions
제약 사실과 추정을 분리한다. 초기 단서를 결론으로 못박지 않는다.

프롬프트 자체보다 이 형식이 결과를 안정시켰습니다.

다음 조사부터 미리 하는 것

두 가지를 미리 합니다.

  • 프롬프트를 짜기 전에 metric, host, task tag까지 API가 실제로 응답을 주는지 한 번 호출해서 확인합니다.
  • 조회 가능한 범위를 문서로 남기고, 그 범위 안에서만 AI에게 결론을 맡깁니다.

권한이 좁은 상태에서 나온 결론은 근거가 좁다는 뜻이지, 결론이 맞다는 뜻이 아닙니다. 이 기준을 바탕으로 다음 편에서는 Datadog을 Claude Code에 붙이고 실제 evidence summary를 어디까지 뽑을 수 있었는지 기록합니다.

Next Step 이 글은 StudyDad 작업 루프의 한 조각입니다.

글에서 정리한 생각은 GitHub의 코드와 포트폴리오로 이어지고, 일부는 FamBlend 같은 제품 실험으로 확장됩니다.