Kubernetes Secret 드리프트로 생긴 CrashLoopBackOff, 35분 안에 멈추는 복구 플레이북
💡 Key Takeaways
- CrashLoopBackOff 대응은 이미지 재배포보다 환경값 무결성 검증을 먼저 해야 복구 시간이 짧아진다.
- Secret 드리프트는 값 자체보다 참조 경로, 리전, 롤링 순서가 함께 어긋날 때 크게 터진다.
- 복구 후에는 Secret 변경 감지와 기동 전 검증 단계를 배포 파이프라인에 고정해야 재발을 줄일 수 있다.
1) 문제 정의: 배포는 녹색인데 서비스는 죽어 있는 상태
수요일 오전 9시 40분, 결제 웹훅 소비 서비스 배포가 CI에서 모두 통과됐는데 프로덕션 파드가 2~3분 간격으로 CrashLoopBackOff에 빠졌다. 대시보드만 보면 CPU와 메모리는 정상 범위였고, 신규 이미지 SHA도 직전 스테이징 검증과 동일해서 처음엔 애플리케이션 버그로 보이지 않았다. 하지만 온콜 채널 로그를 보면 공통적으로 failed to decrypt credential과 missing env: WEBHOOK_AES_KEY가 교차로 등장했다. 현장에서 자주 하는 실수는 이미지 롤백부터 반복하는 것인데, 이번처럼 런타임 환경 주입 실패가 원인이면 롤백을 다섯 번 해도 증상만 반복된다. 우리 팀은 장애 문서 첫 줄에 장애 시작 시각, 마지막 정상 릴리스, 실패 컨테이너 공통 에러 문자열 세 가지를 고정했고, 한 명은 쿠버네티스 이벤트와 Secret 버전을, 다른 한 명은 앱 로그와 외부 의존성 상태를 병렬로 확인했다. 이 역할 분리가 없으면 모두가 kubectl logs만 붙잡고 시간만 소모한다.
2) 판단 기준과 원인 좁히기: 값이 아니라 연결 고리가 끊겼는지 본다
판단은 네 단계로 진행했다. 첫째, 파드가 참조하는 Secret 이름과 키가 매니페스트와 실제 클러스터 객체에서 완전히 일치하는지 확인한다. 둘째, 같은 이름 Secret이 리전별로 다른 값으로 존재하는지 본다. 우리 사고에서는 payment-webhook-secret이 서울 리전과 도쿄 리전에 동시에 있었고, 전날 야간 작업에서 도쿄 리전 키만 회전된 상태였다. 셋째, External Secrets Operator 동기화 시각과 배포 시각의 선후 관계를 맞춘다. 배포가 먼저 나가고 동기화가 4분 늦게 오면 기동 초기에 빈 값이 주입될 수 있다. 넷째, 앱이 부팅 시점에 필수 환경값을 검증하고 즉시 종료하는 구조인지 확인한다. 이 구조 자체는 맞지만, 에러 메시지가 모호하면 온콜이 원인 범위를 잘못 잡는다. 실제로 한 주 전 회고에서 "decryption failed"만 남기지 말고 누락된 키 이름을 함께 출력하도록 바꾼 덕분에 이번에는 10분 안에 Secret 축으로 수렴할 수 있었다.
3) 실행 절차: 35분 복구를 만든 순서
복구는 유입 완화 -> 참조 정합성 복구 -> 점진 롤링 -> 검증 순서를 지켰다. 1단계에서 웹훅 인입을 큐에 임시 적재하도록 게이트웨이 라우팅을 전환해 데이터 유실을 막고 파드 재기동 압박을 낮췄다. 2단계에서 문제 네임스페이스의 Secret을 즉시 수정하지 않고, 먼저 kubectl get deploy -o yaml로 참조 키를 고정 확인한 다음 동일 키셋으로 새 Secret 리비전을 만들어 적용했다. 기존 Secret을 덮어쓰지 않은 이유는 롤백 경로를 남기기 위해서다. 3단계에서 모든 파드를 한 번에 재시작하지 않고 maxUnavailable=1로 롤링해 에러율이 2분 이동창에서 1%를 넘으면 즉시 중단하도록 조건을 걸었다. 실제로 두 번째 파드에서 복호화 지연이 보여 90초 대기 후 재시도했고, 그 사이 큐 적재량이 임계치 아래를 유지하는지 함께 확인했다. 4단계에서 소비 지연, 중복 처리율, DLQ 유입량을 15분 동안 관찰해 정상 범위 복귀를 확인한 뒤 인입 라우팅을 원복했다. 이 순서를 지키면 "일단 전체 재시작" 같은 고위험 조치를 피할 수 있다.
4) 운영 체크리스트와 주의사항: 다음 사고를 더 짧게 끝내기
장애 종료 후에는 복구보다 예방 설계가 중요했다. 첫째, 배포 파이프라인에 기동 전 Secret 스키마 검증 단계를 추가해 필수 키 누락 시 배포 자체를 차단했다. 둘째, Secret 회전 작업에는 리전별 동기화 완료 이벤트가 찍히기 전까지 애플리케이션 롤아웃을 보류하는 가드를 넣었다. 셋째, 런북에 "CrashLoopBackOff 발생 시 5분 내 확인할 명령 6개"를 고정해 교대 온콜이 동일한 순서로 움직이게 했다. 넷째, 임시 완화로 쌓아둔 큐 메시지는 장애 종료 직후 배속 소비를 걸지 않고, 정상 처리율의 1.3배 한도에서 천천히 배출해 2차 스파이크를 막았다. 마지막 주의사항은 Secret 값을 코드 저장소에 남기지 않는 기본 원칙보다, Secret 변경의 "전파 순서"를 통제하는 운영 원칙이 더 자주 사고를 막는다는 점이다. 값이 맞아도 타이밍이 틀리면 서비스는 똑같이 죽는다. 그래서 우리 팀은 이제 Secret 작업을 "보안 작업"이 아니라 "배포 작업"으로 취급하고 동일한 승인·검증 체계를 적용한다.