Incident Library
적용된 필터 원인운영 실수구성 요소영구 볼륨과 스테이트풀셋도메인쿠버네티스모두 풀기검색어 없이 필터만 적용한 목록입니다. 최근 사례부터입니다.
추천 필터 검색하면 검색어에서 찾은 분류 값이 여기에 나옵니다.

그래프

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

사례3건

3건 중 1~3번째

  1. 증상

    새 temple 클러스터에 허브를 배포하던 엔지니어가 NFS 문제를 만나 prod 네임스페이스를 지웠는데 삭제가 멈춘 채 끝나지 않았다. 파드 목록을 보니 다른 클러스터의 사용자 파드가 잔뜩 있었다.

    촉발

    2i2c 엔지니어가 새 허브의 NFS 배포 버그를 우회하던 중, kubectl 이 LEAP 클러스터를 가리키고 있는 줄 모르고 prod 네임스페이스를 지웠다.

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

    네임스페이스를 지운 엔지니어가 삭제가 멈춘 것을 보고 파드 목록을 확인하다 다른 클러스터의 사용자 파드를 발견했고, 17:00 BST 에 장애를 선언했다. 모니터링 알림보다 먼저였다. 업타임 검사 실패 알림은 17:32 에 왔고 사용자의 404 제보는 17:37 에 들어왔다.

    조치
    • prod 네임스페이스에 남아 있던 사용자 파드 하나를 지워 멈춰 있던 네임스페이스 삭제를 끝냈다.
    • 재배포를 막고 있던 nfs-share-creator 잡을 종료했다.
    • 바뀐 NFS 서버 IP 주소로 prod 허브를 다시 배포했다.
    • 배포 도중 사용자가 서버를 띄우지 못하게 prod 인그레스를 지웠다.
    후속 조치
    • 장애 보고서를 쓴다.
    • "I deleted prod, what next?" 같은 상황에서 바로 볼 수 있는 짧은 장애 대응 문서를 쓴다.
    • jupyterhub-home-nfs 를 업그레이드하고 nfs-share-creator 잡을 없앤다.
    • deployer 시스템에 kubeconfig 컨텍스트가 중첩되는 것을 막는 보호 장치를 넣었다. 기본으로는 꺼져 있고 쓰는 사람이 켜야 동작한다.
    • 해당 엔지니어의 셸 설정을 바꿔 kubeconfig 컨텍스트를 더 쉽게 구분하게 했다.
    교훈

    쿠버네티스 리소스를 잃는 경우에 대비해 영구 스토리지를 Terraform 으로 따로 프로비저닝해 둔 설계 덕분에 사용자 데이터가 살아남았다. 반면 엔지니어에게 지금 어느 클러스터를 다루고 있는지 알려 주는 단서가 없었던 것이 실수로 이어졌다. 복구 방법을 알고 있어도 데이터 손실 위험을 확인해 줄 사람이 다른 시간대에 있어 재배포가 늦어졌다. NFS 배포의 순환 의존과 nfs-share-creator 잡도 복구를 늦췄다.

  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. 증상

    새벽 1시쯤 DataHub 에 로그인하려는 사용자가 프록시 오류를 받았다. hub 파드는 떠 있었지만 로그가 sqlite 오류로 가득했다.

    촉발

    엔지니어가 앞서 난 400 오류를 디버깅하려고 hub 파드 안에서 sqlite3 를 다시 열고, 데이터베이스를 ATTACH 하지 않은 채 .save jupyterhub.sqlite 를 실행했다. 이 명령이 JupyterHub 데이터베이스 파일을 빈 데이터베이스로 덮어썼다.

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

    새벽 1시쯤 DataHub 가 다운됐다는 신고가 들어왔다. 1:12 에는 한 사용자가 로그인은 되지만 노트북이 모두 사라졌다고 알렸다.

    조치
    • hub 파드의 replicas 를 0으로 줄여 hub 를 내렸다.
    • 쿠버네티스 PVC 를 사용자 id 와 다시 짝지어 JupyterHub sqlite 데이터베이스를 다시 만들었다. PVC 이름에 정리된 사용자 이름과 사용자 id 가 들어 있어서 할 수 있었다.
    • 처음 넣은 가짜 cookie_id 값을 null 로 바꾸고, sqlite 파일을 DataHub 에 다시 붙였다.
    • 모든 사용자를 로그아웃시키려고 hub 파드의 /srv/jupyterhub/jupyterhub_cookie_secret 을 바꿨다. 끝 근처 한 글자를 바꿨을 때는 효과가 없었고, 앞쪽 한 글자를 바꾸고 재시작하자 모두 로그아웃됐다.
    • 사용자 파드를 모두 지워 모든 사용자를 확실히 로그아웃시켰다.
    • 모든 사용자의 cookie_id 를 채우는 스크립트를 돌렸고, 메모리에 남은 오래된 캐시를 없애려고 hub 를 재시작한 뒤 사용자 파드를 다시 모두 지웠다.
    • 이름에 '.' 이 들어간 새 사용자를 지우고, 이름에 '-' 가 있는 모든 사용자의 '-' 를 '.' 으로 바꾸는 스크립트를 돌렸다.
    후속 조치
    • JupyterHub 가 cookie secret 의 일정 길이만 쓰고 나머지를 버려서, 로그아웃시키려고 값을 바꿀 때 혼란이 생긴다는 문제를 JupyterHub 에 보고한다.
    • users 테이블의 cookie_id 열에 UNIQUE 와 NOT NULL 제약을 두자고 JupyterHub 에 제안한다.
    • 데이터베이스가 지워져도 파드와 PVC 이름이 그대로 남도록, 이름에 사용자 id 대신 사용자 이름 해시를 쓰는 기능을 KubeSpawner 에 요청한다.
    • 사용자 테이블의 id 를 Google 인증이 주는 'id' 와 같게 두어, 처음 로그인한 때와 상관없이 사용자 id 가 바뀌지 않게 하는 기능을 OAuthenticator 에 요청한다.
    • 관리형 MySQL 서비스인 Google Cloud SQL 로 옮긴다.
    • 데이터베이스를 정기적으로 백업하고 그 백업을 시험한다.
    • 해야 할 일과 하지 말아야 할 일을 담은 운영 FAQ 를 쓰기 시작한다.
    • 모니터링과 호출 체계를 개선한다.
    • 에스컬레이션 절차를 분명하게 문서로 남긴다.
내 클러스터에도 있는지 확인하기

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

원인별1