Incident Library
  • 원인
  • 구성 요소
  • 증상
  • 기여 요인
  • 사례

사례7건

7건 중 6~7번째

  1. 증상

    14:10 에 입출금 계좌 결제 처리에 필요한 서비스 하나를 배포하자 그 서비스로 가는 요청이 모두 실패했다. 이때부터 입출금 계좌 고객의 결제가 실패했다. 선불 카드는 이 서비스를 쓰지 않아 아직 영향이 없었다.

    촉발

    14:10 에 엔지니어가 입출금 계좌 결제 처리에 필요한 서비스에 변경을 배포했다. 2주 전 etcd 클러스터를 재구성한 뒤로 쿠버네티스와 etcd 클라이언트 버그가 요청 타임아웃을 낼 수 있는 상태였고, 이 때문에 linkerd 는 배포 뒤 새 파드의 위치를 받지 못했다.

    원인
    구성 요소
    기여 요인
    감지

    14:10 배포 직후 그 서비스로 가는 요청이 모두 실패했고 입출금 계좌 결제가 실패하기 시작했다. 14:12 롤백 뒤에도 오류가 계속되자 14:16 에 내부적으로 장애를 선언했다. 15:13 에 플랫폼 전체가 멈추자 자동 알림이 거의 모두 울렸다.

    조치
    • 14:12 에 배포한 변경을 롤백했지만 오류는 계속됐다.
    • 고객 영향을 줄이려고 장애 때 쓰는 내부 백업 절차를 가동해 대부분의 고객이 계속 카드를 쓸 수 있게 했다.
    • 14:26 에 linkerd 인스턴스 수백 개를 모두 재시작하기 시작했고, 이 조치가 장애를 키웠다.
    • 14:37 에 kubelet 이 apiserver 에서 설정을 받지 못하자 apiserver 프로세스 세 개를 재시작했다.
    • 15:27 에 플랫폼 팀이 새 linkerd 버전을 배포하기 시작했다. 이 버전에는 비호환 문제를 고친 코드가 들어 있었고, 스테이징에서 몇 주째 시험하던 버전이었다.
    • 15:31 에 엔드포인트가 없는 쿠버네티스 서비스를 지워 linkerd 가 서비스 디스커버리 정보를 읽게 했고, 플랫폼이 회복했다.
    후속 조치
    • 클러스터 재구성 뒤 타임아웃을 일으킬 수 있는 쿠버네티스 버그를 고친다.
    • 파싱 오류를 고친 새 linkerd 버전을 배포한다.
    • 영향을 받은 구성 요소에 더 나은 헬스 체크, 대시보드, 알림을 만들어 무엇이 잘못됐는지 더 분명한 신호를 주고 사람의 실수를 막는다.
    • 장애를 내부와 외부에 더 분명하고 빠르게 알리도록 절차를 개선한다.
    교훈

    분산 시스템의 대규모 장애는 이해하기 매우 어렵고, 사람이 좋은 뜻으로 한 조치가 오히려 문제를 키울 수 있다. 이번에는 linkerd 를 모두 재시작한 결정이 또 다른 버전 비호환 문제를 드러내 장애를 더 키웠다. 배포가 잦고 노드와 애플리케이션 장애에 자동으로 대응하는 백엔드에서는 네트워크가 매우 동적이다. 그래서 배포와 요청 라우팅 시스템을 믿을 수 있는 것이 무엇보다 중요하다. Monzo 는 인터페이스를 바꿀 때 앞뒤 버전과 호환되게 해서 롤백을 안전하게 할 수 있게 해 두었지만, 이번에는 롤백한 뒤에도 오류가 계속됐다.

  2. 증상

    SRE 팀장이 애플리케이션 Helm 차트를 고친 뒤 자기 머신에서 CI 과정을 돌렸다. watch kubectl get pods 로 지켜보니 파드가 모두 Running 이 되었는데, 그중 일부가 다시 Pending 으로 바뀌었다. 그는 저절로 풀릴 일시적인 문제라고 보고 회의에 들어갔다.

    촉발

    CI 과정을 로컬에서 돌려 시장 네 곳의 Helm 차트를 비운영 클러스터에 한꺼번에 설치했다. 그 결과 t2.medium 워커 노드 세 대가 여러 컨테이너의 이미지를 동시에 받고 컨테이너를 동시에 만들었다.

    원인
    구성 요소
    기여 요인
    감지

    담당자가 CI 과정을 돌리며 watch kubectl get pods 로 지켜보다가 Running 이던 파드가 Pending 으로 바뀌는 것을 보았다. 두 시간 뒤 Unknown 상태의 파드를 보고서야 내부에서 무언가 확실히 잘못됐다고 판단했다. 워커 노드 세 대가 모두 NotReady 였지만 어떤 모니터링도 이를 잡지 못했다.

    조치
    • 워커 노드 세 대를 종료하고 오토스케일링 그룹이 새 인스턴스를 띄우게 했고, 약 10분 뒤 클러스터가 정상으로 돌아왔다.
    후속 조치
    • 비운영 AWS 계정과 DataDog 의 연동 문제를 고쳐 지표가 들어오게 하고, 그 지표를 보며 설치 과정을 다시 돌렸다.
    • 워커 노드의 루트 EBS 볼륨을 20GB 에서 120GB 로 늘렸다.
    • ruby:2.4, alpine:3.2 같은 공통 베이스 이미지 안에서 sleep 루프만 도는 DaemonSet 을 배포해 노드에 이미지를 미리 받아 두었다. kops 가 사용자 훅을 지원할 때까지 쓰는 임시 방편이다.
    • 쿠버네티스의 계층마다 할당 한도를 다르게 설정한다.
    • tiller 가 크래시 루프에 빠지면 차트 설치와 업그레이드로 수정을 배포할 수 없으므로 tiller 고가용성을 Helm 팀과 논의한다.
    • 새로 만드는 파드 수를 조절할 방법을 찾는다.
    교훈

    평소 하던 애플리케이션 배포만으로도 클러스터가 무너질 수 있었다. 워커 노드를 종료하는 방법은 효과가 있었지만 파드를 다른 노드로 옮기지 않으므로 운영 환경에서는 고객이 서비스를 못 쓰게 된다. 그래서 이런 장애의 표준 절차가 될 수 없다. 노드 상태, 파드 단계, 노드 리소스 지표가 없으면 이런 장애를 알아채지 못한다. kops 의 기본 20GB gp2 루트 볼륨은 IOPS 와 디스크 공간이 모두 모자랐다. 새로 만드는 파드 수를 조절할 수단이 있으면 문제를 풀지는 못해도 위험을 줄일 수 있고, 무언가 잘못됐을 때 제 손으로 클러스터를 마비시키는 일을 막을 수 있다.

내 클러스터에도 있는지 확인하기

이 7건에서 가장 많이 나오는 구성 요소: kubelet, 쿠버네티스 API 서버. kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이런 틈을 점검합니다. 점검 항목의 근거는 이 라이브러리와 같은 공개 보고서입니다.

원인별4

용량 한계2건

  1. IL-0027 CoreDNS 파드가 모두 한 노드에 몰린 채 그 노드의 메모리가 고갈되자 DNS 가 응답하지 못해 아이디어스 서비스 전체가 멈췄다
  2. IL-0040 비운영 쿠버네티스 클러스터에 Helm 차트를 한꺼번에 설치하자 작은 gp2 루트 볼륨의 IOPS 가 바닥나, 워커 노드 세 대에서 kubelet 이나 Docker 가 내려가 세 대 모두 NotReady 가 됐다

설정 변경2건

  1. IL-0153 DOKS 1.27.6-do.0 과 1.28.2-do.0 워커 노드에서 매일 아침 도는 apt 자동 업그레이드가 15분 타임아웃에 걸려 강제 종료되면서 노드 네트워킹이 망가지고, 노드가 사흘 연속 NotReady 가 됐다.
  2. IL-0008 응답하지 않는 OPA 검증 웹훅 때문에 GKE 마스터 업그레이드가 멈추고 API 서버가 간헐적으로 끊기면서 노드가 끝없이 재생성됐다

unknown1건

조직
  1. IL-0059 kubeadm이 만든 etcd 정적 파드의 liveness probe가 실패해 kubelet이 약 2분마다 etcd를 재시작했고, 단일 마스터 클러스터의 API 서버가 계속 끊겼다

소프트웨어 결함2건

  1. IL-0074 워커 노드가 잇달아 Not Ready 에 빠지고 네트워크와 DNS 실패로 사이드카가 죽으면서, AirMap 플랫폼의 모든 서비스가 다섯 시간 동안 응답하지 않았다.
    AirMap2018
  2. IL-0003 etcd 클러스터 재구성 뒤 쿠버네티스 버그로 linkerd 가 서비스 위치 갱신을 받지 못했고, linkerd 를 모두 재시작하자 엔드포인트가 없는 서비스를 파싱하지 못하는 오류가 드러나 플랫폼 전체가 멈췄다
    Monzo2017