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

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

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

GitLab 배포 이력과 장애 시점을 연결하기

박세식 2026. 7. 24. 18:00

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

장애가 나면 최근 배포부터 의심하고 싶어집니다. 이번 사례도 그랬습니다. 서비스는 ECS blue/green 방식으로 배포되고 있었고, 장애 중간에도 CodeDeploy deployment가 있었습니다.

먼저 확인한 질문은 두 가지였습니다.

1. 장애 직전에 새 코드가 배포됐는가?
2. 장애 중간에 배포가 회복을 도왔는가, 아니면 악화시켰는가?

배포 이력에서 실제로 본 것

GitLab이나 사내 Git에서 아래 데이터를 봤습니다.

  • merge request
  • pipeline
  • deployment job
  • image tag
  • task definition revision
  • deployment start/end time
  • rollback 여부

AWS ECS/CodeDeploy까지 연결해서 아래도 붙였습니다.

  • task definition revision 등록 시각
  • image tag 변경 시각
  • blue/green task set 생성 시각
  • deployment failure reason
  • rollback deployment

GitLab, GitHub Actions, Jenkins, ArgoCD 어느 도구든 확인 방식은 크게 다르지 않았습니다. 필요한 건 특정 도구가 아니라 시간순 변경 이력이었습니다.

직전 배포는 아니었습니다

조사 결과 incident 당시 task definition/image는 약 17일 전에 배포된 버전이었습니다. 그래서 장애 직전 새 API 배포가 바로 trigger였다는 가설은 그 자리에서 약해졌습니다.

fresh deploy regression = unlikely

원인은 새 코드가 아니라 특정 traffic condition에서 기존 code path가 버티지 못한 쪽으로 좁혀졌습니다.

장애 중간의 blue/green은 실패했습니다

직전 배포가 root cause는 아니었지만, 장애 중간의 배포는 영향을 줬습니다. blue/green deployment가 시작됐고 새 task set이 올라왔지만 health check를 통과하지 못했습니다. CodeDeploy는 실패 후 rollback했습니다.

여기서 두 가지가 드러났습니다. 첫째, 문제는 특정 old task instance에만 있지 않았습니다. 새 task도 같은 traffic과 같은 code path를 만나면 실패했습니다. 둘째, CodeDeploy deployment group에는 CloudWatch alarm gate가 없었습니다. 장애가 진행 중인데 latency/error monitor가 firing 중이라면 deployment가 자동으로 멈추는 편이 안전했습니다.

배포 이력만으로는 부족했습니다

이 시점에 남은 질문은 하나였습니다.

그럼 왜 그 순간에 부하가 왔는가?

Datadog trace metric은 GET /v3/auth가 baseline 대비 6~10배 증가했음을 보여줬습니다. 그래서 배포가 아니라 traffic source 쪽을 봐야 했습니다.

traffic source를 분류한 결과

AI에게 Datadog span/log sample과 AWS 정보를 넘겨 source를 분류하게 했습니다. 표본 기준으로 traffic은 대부분 mobile이었습니다.

  • Chrome Mobile WebView
  • Mobile Safari / WKWebView
  • KakaoTalk in-app browser user-agent
  • Samsung Galaxy / iPhone 계열 device

Referer는 대부분 consumer web origin이었습니다. IP는 넓게 분산되어 있었고 한 IP나 CIDR이 traffic을 지배하지 않았습니다. status code도 대부분 200이었습니다.

method는 GET과 CORS OPTIONS였습니다. 따라서 이 패턴은 아래와 달랐습니다.

  • bot traffic
  • brute-force
  • single-client retry storm
  • internal backend caller

더 자연스러운 설명은 아래 흐름이었습니다.

모바일 사용자가 특정 시점에 consumer web으로 유입됐다.
페이지 로드 시 JS bundle이 /v3/auth를 호출했다.

가능한 trigger:

  • push notification
  • deep link
  • marketing campaign
  • mobile web entry event

traffic source에 따라 대응이 달라집니다

"요청이 많았다"로 끝내면 대응 방향이 흐려집니다. 이번엔 organic mobile user traffic으로 보는 쪽이 자연스러웠습니다.

원인 대응
bot 차단, rate limit, WAF
retry storm client retry policy 수정
internal caller service-to-service circuit breaker
organic user traffic hot path 성능, capacity, backpressure
campaign/push 사전 capacity planning, traffic ramp

root cause는 "이상한 외부 공격"이 아니라 "정상 사용자 traffic surge를 auth path가 처리하지 못함"에 가까웠습니다.

API repo만 봐서는 다 보이지 않습니다

API image는 오래된 버전이었지만 traffic source 분석에서 frontend 유입 가능성이 나왔습니다. 그런데 API repo만 보면 여기까지는 보이지 않습니다. consumer web release나 push/marketing log를 같이 봐야 했습니다.

배포 확인은 API repo에서 시작하되, 유입 채널 쪽 릴리스와 캠페인 이벤트를 놓치지 않아야 했습니다.

배포는 원인 후보이면서 배제 근거였습니다

배포 이력은 root cause를 찾기 위해서만 보는 게 아니라 root cause가 아닌 것을 제거하기 위해서도 봤습니다. 이번 사례에서는 API fresh deploy 가능성이 낮아졌고, traffic surge와 code path 쪽으로 원인 후보가 좁혀졌습니다. 동시에 장애 중간에 실행된 blue/green deploy가 실패하면서 deployment guard가 부족했다는 운영 개선 항목이 나왔습니다.

다음 조사부터 고정해서 묻는 것

배포와 traffic을 볼 때 아래를 형식으로 남겼습니다.

무엇이 바뀌었는가?
무엇은 바뀌지 않았는가?
장애 중 사람이 한 조치가 회복을 도왔는가, 방해했는가?

다음 편에서는 이렇게 좁혀진 결론을 어떤 문서 형식으로 남길지 정리합니다.

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

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