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

사례4건

4건 중 1~4번째

  1. 증상

    2022-03-31 07:53 UTC 에 GitLab.com 웹 서비스의 50x 오류가 늘기 시작했고, 1분 뒤 당직 엔지니어가 오류율 증가 페이지를 받았다. 사용자에게는 GitLab.com 이 느려졌고 간헐적으로 500 오류 페이지가 떴다.

    촉발

    gitlab-com/gl-infra/k8s-workloads/gitlab-com!1643 머지 요청을 배포하는 파이프라인이 07:49 UTC 에 gprd-cny 로 배포를 시작했다. 이 변경에는 GitLab 차트 버전 업그레이드가 들어 있었고, 새 차트는 Prometheus ServiceMonitor, PodMonitor 설정과 관련된 변경으로 Kubernetes Service 와 Deployment 의 포트 이름을 바꿨다.

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

    07:53 UTC 에 50x 오류가 늘기 시작했고 07:54 에 PagerDuty 알림이 당직 엔지니어를 호출했다. 변경을 배포하던 엔지니어가 오류를 보고 그 변경과 연결해 원인을 짚었다.

    조치
    • 08:05 UTC 에 당직 엔지니어가 카나리 환경을 트래픽에서 뺐다가 08:25 에 다시 넣었다.
    • 08:15 에 cluster c 와 cluster d 배포 작업을 시작 전에 취소했다.
    • 08:51 에 HAProxy 에서 gprd-us-east1-d 클러스터를 뺐다가 08:56 에 다시 넣었다.
    • 09:31 에 최소 레플리카 수를 100 넘게 올리는 변경 !1650 을 머지했다.
    • 09:53 에 HAProxy 에서 gprd-us-east1-c 클러스터를 빼고, 09:57 에 이 클러스터에 !1643 을 배포한 뒤 10:07 에 다시 넣었다.
    • 10:19 에 최소 레플리카 상향을 되돌리는 변경 !1652 를 머지했다.
    후속 조치
    • 리전 클러스터 하나에서 트래픽을 통째로 안전하게 빼는 graceful drain 기능을 만든다.
    • PagerDuty 를 유지보수 모드로 바꾸는 ChatOps 명령을 만든다.
    교훈

    알림은 정확하게 울렸다. 원인이 바로 직전에 배포한 변경이라는 것을 알았기 때문에, 그 변경을 배포하던 파이프라인을 멈추는 것이 자연스러운 첫 대응이었다.

  2. 증상

    옛 노드 세 개를 하나씩 새 StorageClass 의 볼륨으로 바꾸다가 마지막으로 elasticsearch-0 파드를 지우고 다시 띄웠다. elasticsearch-0 안에서 `_cat/nodes` 를 조회하자 자기 자신 하나만 마스터로 나왔다.

    촉발

    옛 PVC 와 파드를 지운 뒤 elasticsearch-0 이 새 StorageClass 의 빈 볼륨을 받아 다시 시작했다.

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

    마지막으로 0번 파드를 교체한 뒤 엔지니어가 elasticsearch-0 과 elasticsearch-1 안에서 각각 `_cat/nodes` 를 조회했다. 두 결과가 서로 다른 노드 목록과 다른 마스터를 보여 스플릿 브레인을 알아챘다.

    조치
    • 데이터를 실시간으로 쓰는 워커의 디플로이먼트 세 개를 0으로 줄여 새 데이터가 갈라진 클러스터로 들어가지 않게 했다.
    • ConfigMap 의 initial_master_nodes 를 elasticsearch-0, elasticsearch-1, elasticsearch-2 로 바꾸고 StatefulSet 에서 cluster.initial_master_nodes 환경 변수를 지웠다.
    • 롤링 업데이트가 도는 동안 디스커버리용 elasticsearch Service 를 YAML 로 저장한 뒤 지우고, 0번 파드의 PVC 도 지웠다.
    • ConfigMap 의 initial_master_nodes 를 elasticsearch-3, elasticsearch-4, elasticsearch-5 로 바꾸고, 0번 파드의 빈 PVC 를 만든 뒤 파드 6개를 모두 지워 다시 띄웠다.
    • 저장해 둔 YAML 로 Service 를 다시 만들었다. 노드 6개가 한 클러스터로 모였고 green 이 아닌 인덱스는 0개였다.
    후속 조치
    • ConfigMap 의 initial_master_nodes 를 원래 값으로 되돌리고 모든 파드를 한 번 더 재시작한다.
    • 모든 샤드를 0, 1, 2번 노드로 옮기고 클러스터를 노드 6개에서 3개로 줄인다.
    • 손으로 바꾼 내용을 모두 저장소에 커밋한다.
    교훈

    운영 데이터를 옮길 때는 설정 오류, 데이터센터 장애, 네트워크 단절처럼 무언가 잘못될 수 있다는 전제로 준비해야 한다. 이번에 쓴 다운타임 없는 절차는 예상하지 못한 문제에 약했다. 팀은 ConfigMap 을 꼼꼼히 확인했지만 Helm 차트 템플릿이 StatefulSet 에 넣는 환경 변수는 보지 않았다. cluster.initial_master_nodes 가 파드 하나로 고정된 상태에서 그 파드가 빈 볼륨으로 다시 뜨면 기존 클러스터에 합류하지 않고 새 클러스터를 만든다.

  3. 증상

    2021년 2월 19일, DB 노드 한 대가 내려갔다는 에러 알람이 슬랙에 올라왔다. CockroachDB 는 노드 일부가 죽어도 견디도록 만든 분산 데이터베이스라서 데브옵스 엔지니어들은 처음에는 크게 걱정하지 않았다.

    촉발

    AWS 도쿄 리전 데이터센터의 냉각 시스템이 고장 나 그 범위에 있던 DB 서버들이 과열로 멈췄다.

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

    회식 중에 DB 노드 한 대가 내려갔다는 에러 알람이 슬랙에 올라왔고, 이어서 다른 노드들의 장애 알람이 Ops 채널에 연달아 들어왔다.

    조치
    • INSERT INTO new_table SELECT * FROM old_table WHERE {unaffected_ranges} 같은 쿼리로 깨진 Range 를 뺀 데이터를 새 테이블로 옮겼다.
    • Cockroach Labs 엔지니어와 화면을 공유하며 debug unsafe-remove-dead-replicas 명령을 실행해 사라진 Data Range 2개 중 하나를 살렸다.
    • 남은 Data Range 하나는 1차 장애 때와 같은 방법으로 스토리지 레이어에서 직접 복원해 유저 데이터를 잃지 않고 복구했다.
    후속 조치
    • Locality 를 설정해 데이터를 여러 AZ 에 고르게 나눠 저장했다.
    • 차트 한 벌에 배포할 AZ 마다 다른 values 파일을 쓰는 방식으로 Locality 를 설정했다.
    • Locality 를 설정한 노드를 기존 클러스터에 넣고 모든 AZ 에 레플리카가 복제된 것을 확인한 뒤, Locality 가 없는 기존 노드를 클러스터에서 뺐다.
    교훈

    쿠키런: 킹덤 정도 규모의 게임에서 멀티 AZ 전략은 선택이 아니라 필수다. 분산 DB 라도 레플리카가 어느 AZ 에 놓이는지 정하지 않으면, AZ 하나가 죽을 때 동시에 잃는 노드 수가 안전 마진을 넘을 수 있다. 차트를 깔끔하게 만드는 것보다 반복적이더라도 확실하게 토폴로지를 설정하는 쪽이 낫다. 클라우드 업체를 믿기보다 스스로 할 수 있는 최대한의 대비를 해 두어야 한다.

  4. 증상

    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 와 디스크 공간이 모두 모자랐다. 새로 만드는 파드 수를 조절할 수단이 있으면 문제를 풀지는 못해도 위험을 줄일 수 있고, 무언가 잘못됐을 때 제 손으로 클러스터를 마비시키는 일을 막을 수 있다.

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

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

원인별4

설정 변경1건

기여 요인
조직
  1. IL-0208 GitLab Helm 차트 업그레이드가 Service 와 Deployment 의 포트 이름을 바꿔 GCP 로드 밸런서가 다시 구성되는 동안 GitLab.com 요청이 느려지고 50x 오류가 났다
    GitLab2022

운영 실수1건

기여 요인
조직
  1. IL-0197 Elasticsearch StatefulSet 을 새 퍼시스턴트 볼륨으로 옮기던 중 빈 볼륨으로 다시 뜬 elasticsearch-0 이 혼자 새 클러스터를 만들어 스플릿 브레인이 났다.
    Palark2021

클라우드 제공자 장애1건

  1. IL-0172 AWS 도쿄 리전 한 AZ 의 냉각 장애로 쿠버네티스 위에서 돌던 CockroachDB 노드 60대 중 6대가 22분 만에 죽었고, AZ 토폴로지 설정이 없어 일부 Range 가 정족수를 잃었다.

용량 한계1건

조직
  1. IL-0040 비운영 쿠버네티스 클러스터에 Helm 차트를 한꺼번에 설치하자 작은 gp2 루트 볼륨의 IOPS 가 바닥나, 워커 노드 세 대에서 kubelet 이나 Docker 가 내려가 세 대 모두 NotReady 가 됐다