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

사례4건

4건 중 1~4번째

  1. 증상

    2025년 2월 18일 08:41 UTC, Grafana Labs 의 애플리케이션 팀 여러 곳이 자기 엔드포인트 일부에 접속이 되지 않는 것을 알아챘다.

    촉발

    로드 밸런서의 TLS 정책 버전을 올리면서, 기본 TLS 정책을 덮어쓸 수 있도록 Crossplane 컴포지션의 병합 정책을 바꾼 변경이 개발, 스테이징, 운영 환경에 동시에 적용됐다.

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

    08:41 UTC 에 애플리케이션 팀 여러 곳이 자기 엔드포인트 일부에 접속이 되지 않는 것을 알아챘다. 조사해 보니 최근의 TLS 정책 설정 변경 때문에 그 엔드포인트의 로드 밸런서가 사라져 있었다.

    조치
    • 문제의 코드 변경을 롤백했다.
    • 영향받은 쿠버네티스 Service 를 다시 만들어 쿠버네티스가 로드 밸런서를 새로 만들게 했다.
    후속 조치
    • 한 번에 두 개 이상의 웨이브로 배포하는 것을 막는 CI 검사를 넣어, 개발에서 스테이징, 카나리, 운영으로 이어지는 순서를 벗어나는 배포를 잡아내게 했다.
    • 논의만 하던 더 정교한 웨이브 설계의 우선순위를 올려 개선안을 합의하고 구현한다.
    • 실수로 지워지지 않도록 로드 밸런서에 삭제 보호를 켜기 시작했다. Crossplane 수준의 삭제 보호보다 한 층 아래에서 막는다.
    • 로드 밸런서 말고도 중요한 클라우드 리소스를 하나씩 살펴 필요한 곳에 삭제 보호를 켠다.
    • 변경 검증과 가시성을 높이려고 만든 Crossplane Managed Resources exporter 를 플랫폼 엔지니어링 안에서 베타로 시험하고 있다. 이번 장애를 일으킨 변경에 적용해 보면 차이가 분명히 드러난다.
    • TLS 정책을 다시 설정하는 기능을 제대로 완성하고 시험해, 애플리케이션 팀이 배포할 수 있게 했다.
    교훈

    작은 변경이라 위험이 낮다고 가정하고 충분한 시험과 영향 범위 줄이기를 건너뛴 것이 장애로 이어졌다. 웨이브 배포는 절차로 기대하는 데 그치지 않고 시스템이 강제해야 한다. 비상 예외는 웨이브를 고르지 않았을 때의 기본 동작이 아니라 말 그대로 예외여야 한다. 이번 변경은 시험을 안 한 것이 아니라 시험한 범위가 충분하지 않았다. 클라우드 로드 밸런서 컨트롤러와 자체 도구까지 처음부터 끝까지 돌려 봐야 의도한 작은 변화만 일어나는지 알 수 있었다. 개발 웨이브부터만 적용하도록 강제했다면 제대로 시험할 수밖에 없었을 것이다.

  2. 증상

    Grafana Cloud Logs 의 한 환경에서 Grafana Loki 의 쓰기 경로가 내려갔다. 롤아웃 중이던 distributor 파드가 OOM으로 죽었다가 다시 뜨기를 되풀이했다.

    촉발

    키-값 저장소에서 내장 memberlist 로 옮기는 마이그레이션의 1단계가 distributor 디플로이먼트와 서비스에 새 레이블을 붙였다. 서비스를 만드는 함수가 이 레이블을 셀렉터로도 복사했기 때문에, 롤아웃이 시작되자 distributor 서비스는 새 레이블을 단 새 파드만 가리켰다.

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

    처음에는 distributor 가 OOM으로 죽는 장애로 보였다. 첫 단서는 kubectl get pods 와 kube_deployment_status_replicas_updated 지표에서 distributor 의 10%만 재시작됐다는 점이었다. 팀은 PR 마다 Kubernetes 매니페스트 diff 를 만들어 PR 에 올리고 별도 레포에도 저장하는 자동화를 갖고 있었다. 이 diff 에서 distributor 서비스에 셀렉터가 추가되고 서비스가 다시 만들어진 것을 찾았다. 평소 배포에서는 서비스가 다시 만들어지지 않는다.

    조치
    • distributor 디플로이먼트의 레플리카를 늘렸다. 새로 뜬 파드도 새 레이블을 달고 있어서 부하가 더 많은 파드로 나뉘었고, OOM으로 죽는 파드가 줄면서 Kubernetes 가 옛 파드 교체를 이어 갔다.
    • 마이그레이션 단계의 변경을 되돌려 장애를 완전히 끝내고 원인을 조사할 시간을 벌었다.
    • service_ignored_labels:: [$._config.gossip_member_label] 을 설정하고 서비스 생성 함수 k.util.serviceFor 에 넘겨, memberlist 레이블이 서비스 셀렉터로 복사되지 않게 했다.
    후속 조치
    • 디플로이먼트나 스테이트풀셋의 레이블을 서비스 셀렉터로 복사하던 함수가 이번 같은 예외 상황도 처리하도록 고쳤다.
    교훈

    서비스 셀렉터를 디플로이먼트 레이블에서 자동으로 만들면, 롤아웃 중에 새 레이블이 붙는 순간 서비스가 새 파드만 가리킬 수 있다. 다른 환경에서 본 작은 오류를 롤아웃 때 흔한 잡음으로 넘기면 이런 신호를 놓친다. 환경마다 수평 확장과 메모리 한도 설정이 다르면 다른 환경에서 문제없던 변경이 특정 환경에서만 장애를 낸다. 원인을 빨리 찾는 데에는 Loki 와 Kubernetes 지표, PR 마다 남겨 둔 매니페스트 diff 가 도움이 됐다.

  3. 증상

    Metrictank 인스턴스 일부가 쓰기 큐를 비우지 못한다는 알림이 왔다. Metrictank 가 Cassandra 에 쓰지 못하고 있다는 뜻이었다.

    촉발

    GCP SSD 영구 디스크의 성능이 크게 떨어져 많은 I/O 가 오랫동안 실패했고, 그 때문에 노드가 자동으로 재부팅됐다.

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

    팀은 Metrictank 쓰기 인스턴스가 쓰기 큐를 비우지 못한다는 알림을 받고 장애를 알았다. Metrictank 가 대부분의 쿼리를 메모리에서 계속 처리해서 오류율은 아주 천천히 올라갔기 때문에, 이 알림이 없었다면 발견이 훨씬 늦었을 것이다.

    조치
    • 새 노드라면 디스크를 붙일 수 있을 것이라 보고 클러스터의 컴퓨트 노드를 늘렸지만 효과가 없었다.
    • GCP 장애가 풀린 뒤 Cassandra StatefulSet 을 지우고 Pod Management Policy 를 바꿔 다시 만들었다. 파드 하나를 빼고 모두 떠서 Cassandra 클러스터가 정상으로 돌아왔다.
    • 긴 장애 동안 데이터를 잃지 않도록 Kafka 토픽 보존 기간을 늘렸다.
    • Metrictank 재시작이 CPU 부족으로 느려지지 않도록 클러스터에 노드를 임시로 더 붙였다.
    • 모든 Metrictank 인스턴스를 재시작해 새 Cassandra IP 로 연결되게 했다. 재시작은 20시간 넘게 걸렸다.
    후속 조치
    • 모든 Cassandra IP 가 바뀌면 Metrictank 가 다시 연결하지 못하는 버그를 고쳤다. 연결이 죽은 것을 감지하면 gocql 연결을 새로 만든다.
    • 쓰기 큐가 가득 찬 상태에서 새 데이터가 들어오면 생기던 교착을 고쳤다. 내부 자료 구조 하나를 잠금으로 보호하는 대신 atomic 연산으로 갱신한다.
    교훈

    받은 데이터를 처리하기 전에 쓰기 선행 로그인 Kafka 에 먼저 저장해 둔 덕분에, Cassandra 를 되살린 뒤 그동안 받은 데이터를 모두 처리해 저장할 수 있었다. Kafka 가 SSD 가 아닌 디스크를 쓴 것은 운이 좋았다. 그렇지 않았다면 Kafka 도 Cassandra 와 같은 장애를 겪었을 수 있다. 23시간 동안 파라과이, 알바니아, 호주에 있는 세 사람이 시간대를 나눠 대응했다. 두 버그를 고친 지금은 같은 상황이 와도 복구가 23시간이 아니라 3시간 안에 끝날 것으로 본다. 오래 미룬 문제도 새로 보면 생각보다 간단히 풀릴 수 있다. 장애가 주말에 나서 고객 부하가 대부분 캐시 데이터를 읽는 알림 쿼리뿐이었던 것도 운이 좋았다.

  4. 증상

    2019년 7월 19일 금요일, Grafana Cloud 의 Hosted Prometheus 서비스가 장애를 겪었다. 오류 예산 기반 Prometheus 알림이 장애 시작 4분 안에 당직자를 호출했다.

    촉발

    7월 19일 한 엔지니어가 큰 고객을 위한 전용 Cortex 클러스터를 새로 띄웠다. 새 클러스터 설정에는 새로 도입한 파드 우선순위 지정이 없어서, 모든 파드가 기본값인 medium 우선순위를 받았다. 쿠버네티스 클러스터에는 새 Cortex 클러스터를 올릴 자원이 부족했다.

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

    새로 도입한 오류 예산 기반 Prometheus 알림이 장애 시작 4분 안에 당직자를 호출했다.

    조치
    • 쿠버네티스 클러스터를 늘려 새 Cortex 클러스터와 기존 운영 Cortex 클러스터가 모두 들어가게 했다.
    • Cortex 앞의 인증 리버스 프록시에서 난 OOM 을 찾아 고쳤다.
    후속 조치
    • 운영 Ingester 에 high 우선순위 지정을 반영했다.
    • 클러스터 전역 설정처럼 네임스페이스 밖에 있는 객체의 배포는 더 많은 사람이 리뷰한다.
    • 네임스페이스 밖 설정 변경에는 모두 설계 문서를 요구한다.
    • 인증 리버스 프록시의 규모 조정을 자동화해 과부하 때 OOM 이 나지 않게 한다.
    • 한꺼번에 몰려드는 재시도를 막으려고 Prometheus 의 기본 백오프와 확장 동작을 조사한다.
    교훈

    운영 Ingester 에 high 우선순위를 지정하기 전에 medium 을 기본 우선순위로 만들지 말았어야 했다. 운영 Ingester 의 high 지정도 더 일찍 배포했어야 했다. 팀은 이 변경이 설계 문서를 쓸 만큼 크지 않다고 보고 GitHub 이슈에서만 논의했다. 좋은 점도 있었다. Cortex 는 자원이 충분해지자 추가 조치 없이 스스로 회복했다. 팀은 새 로그 수집 시스템인 Grafana Loki 로 Ingester 가 정상적으로 종료됐는지 확인했다.

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

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

원인별2

설정 변경3건

  1. IL-0192 Crossplane 컴포지션의 병합 정책 변경이 Service 타입을 ClusterIP 로 바꿔 클라우드 로드 밸런서가 지워지고 Grafana Cloud 서비스의 약 25%가 끊겼다.
  2. IL-0191 Loki distributor 서비스에 레이블 셀렉터가 하나 더 들어가 롤아웃 중 새 파드 몇 개에만 쓰기 트래픽이 몰렸고, 그 파드들이 OOM으로 죽으면서 로그 쓰기 경로가 약 25분간 멈췄다.
  3. IL-0004 새 Cortex 클러스터에서 기본 우선순위를 받은 파드가 우선순위가 없는 운영 Ingester 를 연쇄적으로 선점해 Hosted Prometheus 쓰기가 실패했다

클라우드 제공자 장애1건

  1. IL-0193 GCP SSD 영구 디스크 장애로 Cassandra 파드가 볼륨을 붙이지 못했고, OrderedReady 정책 때문에 나머지 파드도 다시 뜨지 못한 데다 Metrictank 가 바뀐 Cassandra IP 로 다시 연결하지 못해, Grafana Cloud Graphite 쿼리 일부가 최대 23시간 실패했다.