IL-0506 UC Berkeley · 2017-02-09kubernetesdatabase
UC Berkeley DataHub 의 JupyterHub sqlite 데이터베이스가 빈 데이터베이스로 덮어써져 로그인이 실패하고 홈 디렉터리가 비어 보이고 다른 사용자로 로그인되는 문제가 났다
2017년 2월 9일 0:25 무렵, 엔지니어가 앞서 난 400 오류를 디버깅하려고 hub 파드에서 sqlite3 로 servers 테이블의 ip 와 base_url 을 일부러 틀리게 바꿔 봤다. 바뀐 것이 없어 보이자 새 sqlite3 세션에서 .save jupyterhub.sqlite 를 실행했고, 먼저 ATTACH 를 하지 않았기 때문에 JupyterHub sqlite 데이터베이스가 빈 데이터베이스로 덮어써졌다. 나중에 로컬에서 sqlite3 의 .save <filename> 이 확인 없이 파일을 덮어쓴다는 것을 확인했다.
테이블이 모두 사라지자 hub 는 sqlite 오류를 냈고 사용자는 프록시 오류를 받았다. 로그인한 사용자는 새 사용자 id 를 받았고, 그 때문에 새 빈 디스크가 만들어졌다. 1:28 에 hub 파드의 replicas 를 0으로 줄이고, PVC 이름에 담긴 사용자 이름과 사용자 id 를 짝지어 데이터베이스를 다시 만들었다. 2:42 에 다시 만든 데이터베이스를 붙이자 관리자 화면의 사용자 수가 1000명쯤으로 돌아왔다.
다시 만든 데이터베이스에서는 여러 사용자의 cookie_id 가 비어 있었다. 이 사용자들은 id 순으로 맨 앞에 있고 cookie_id 가 빈 사용자 한 명의 노트북으로 로그인됐다. 10:11 에 cookie secret 을 바꿔 모두 로그아웃시키려 했는데, 끝 근처 글자를 바꿨을 때는 효과가 없었고 앞쪽 글자를 바꾸자 로그아웃됐다. JupyterHub 는 cookie secret 의 일정 길이만 쓰고 나머지를 버렸다. 10:45 에 모든 사용자의 cookie_id 를 채우고 hub 를 재시작한 뒤 사용자 파드를 다시 모두 지우자 대부분의 사용자가 돌아왔다.
이름에 '.' 이 들어간 사용자는 옛 레코드에 '.' 대신 '-' 가 들어가 있었고, 로그인하면서 '.' 이 들어간 새 사용자가 생겨 빈 PVC 를 받았다. 11:15 에 '.' 이 들어간 새 사용자를 지우고 '-' 를 '.' 으로 바꾸는 스크립트를 돌렸고, 11:17 에 DataHub 가 모두 복구됐다고 알렸다. 그 사이 학생들에게 ds8.berkeley.edu 를 쓰라는 안내가 나갔지만, ds8.berkeley.edu 는 data8 규모 수업을 감당할 자원이 없어 곧 다운됐다.
- 심각도
- –
- 영향 범위
- UC Berkeley DataHub: logins failed with proxy errors, users saw empty home directories, and some were logged in as another user
- 영향 사용자
- DataHub users, about 1000 accounts
- 지역
- –
- 감지까지
- 35m
- 복구까지
- 10h 52m
증상
새벽 1시쯤 DataHub 에 로그인하려는 사용자가 프록시 오류를 받았다. hub 파드는 떠 있었지만 로그가 sqlite 오류로 가득했다.
hub 로그의 오류는 sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: proxies [SQL: 'SELECT proxies.id AS proxies_id, proxies._public_server_id AS proxies__public_server_id, proxies._api_server_id AS proxies__api_server_id \nFROM proxies \nWHERE proxies.id = ?'] [parameters: (1,)] 였다. 가장 자주 쓰이는 hub 테이블에도 같은 오류가 이어졌다.
1:12 에 한 사용자가 로그인은 되지만 노트북이 모두 사라졌다고 알렸다. JupyterHub 관리자 화면의 사용자는 1000명쯤에서 50명쯤으로 줄어 있었다. 사용자 데이터베이스가 지워져서 hub 가 로그인한 사용자를 새 계정으로 만들었다. hub 는 예전 디스크를 찾지 못해 새 영구 디스크를 만들었고, 그래서 홈 디렉터리가 비어 보였다.
kubectl --namespace=datahub get pvc 로 보면 같은 사용자의 PVC 가 claim-<username>-257 과 claim-<username>-51 두 개였고, 뒤의 것은 만든 지 5분밖에 되지 않았다.
데이터베이스를 다시 만든 뒤에도 일부 사용자가 로그인하면 특정 사용자 한 명의 계정으로 들어갔다. cookie secret 을 바꿔 모두 로그아웃시키고 사용자 파드를 모두 지워도 계속됐다.
다른 사용자가 모두 정상이 된 뒤에도 이름에 '.' 이 들어간 사용자는 빈 홈 디렉터리를 받았다.
원인
- 촉발
- 엔지니어가 앞서 난 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 를 쓰기 시작한다.
- 모니터링과 호출 체계를 개선한다.
- 에스컬레이션 절차를 분명하게 문서로 남긴다.
교훈
–
출처
참고 문서
관련 사례
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0242primary 와 secondary PostgreSQL 데이터베이스를 모두 잃어 matrix.org homeserver 가 약 24시간 동안 멈췄다Matrix.org Foundation · 2025같은 원인 · 공통 데이터베이스
IL-0095엔지니어가 kubectl 이 다른 클러스터를 가리키는 줄 모르고 prod 네임스페이스를 지워 LEAP JupyterHub 가 약 1시간 동안 멈췄다.2i2c · 2025같은 원인 · 공통 영구 볼륨과 스테이트풀셋
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0531West US 의 Azure Database for MySQL Single Server 와 MariaDB 일부 서버로 가는 새 연결이 모두 거부됐다Microsoft Azure · 2024같은 원인 · 공통 데이터베이스
IL-0565쿠키런: 킹덤의 CockroachDB 클러스터가 노드 24대 중 16대의 파일 시스템을 잃고 멈춰 게임이 36시간 넘게 점검에 들어갔다Devsisters · 2022같은 원인 · 공통 데이터베이스
IL-0207로컬 개발 환경에서 돌린 작업이 운영 컨텍스트로 넘어가 Kubernetes 클러스터의 Consul 릴리스를 통째로 지웠고, Patroni 가 무너져 GitLab.com 전체가 5XX 오류를 냈다.GitLab · 2022같은 원인 · 공통 데이터베이스
IL-0197Elasticsearch StatefulSet 을 새 퍼시스턴트 볼륨으로 옮기던 중 빈 볼륨으로 다시 뜬 elasticsearch-0 이 혼자 새 클러스터를 만들어 스플릿 브레인이 났다.Palark · 2021같은 원인 · 공통 영구 볼륨과 스테이트풀셋
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0218복제를 다시 맞추다 PostgreSQL 프라이머리의 데이터 디렉터리가 지워지고 백업이 작동하지 않아 GitLab.com 이 약 18시간 멈추고 데이터를 잃었다GitLab · 2017같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0319mysql2 를 다시 적재하려고 데이터베이스를 지우는 동안 mysql1 의 데이터베이스도 함께 지워졌다Oregon State University Open Source Lab · 2015같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
내 클러스터에도 있는지 확인하기
AI 도구와 스크립트용
- 이 사례의 JSON: https://incidentlib-api.benchlet.com/cases/IL-0506
- 가장 비슷한 사례들: https://incidentlib-api.benchlet.com/cases/IL-0506/similar
- MCP 서버(Streamable HTTP, 키 없음):
https://incidentlib-mcp.benchlet.com/mcp
API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.