Incident Library

IL-0219 GoCardless · 2017-10-10database

Postgres 프라이머리의 디스크 어레이가 망가진 뒤 Pacemaker 가 새 프라이머리를 승격하지 못해 GoCardless API 와 대시보드가 1시간 50분 멈췄다

GoCardless 는 가장 중요한 데이터를 프라이머리, 동기 복제본, 비동기 복제본 세 노드의 Postgres 클러스터에 두고, 각 노드의 Pacemaker 가 장애 때 새 프라이머리를 승격하고 클라이언트가 접속하는 가상 IP 를 옮긴다. 평소라면 프라이머리가 망가져도 1~2분이면 동기 복제본이 승격된다. 2017년 10월 10일 15:09 BST 에 API 와 대시보드가 완전히 멈췄다. 프라이머리의 RAID 컨트롤러가 디스크 3개를 동시에 잃었지만 클러스터는 동기 복제본을 승격하지 않았다.

대응 팀은 15:17 BST 에 망가진 프라이머리를 끄고, 15:18 BST 에 crm resource cleanup 으로 오류 횟수를 지웠지만 승격은 일어나지 않았다. 한 시간 동안 여러 방법을 해 봤고, 유지보수 모드에서 recovery.conf 의 복제본 설정을 지워도 Pacemaker 가 원래 설정으로 되돌렸다. 16:18 BST 에 마지막으로 유지보수 모드로 두고 동기 복제본을 프라이머리로 설정해 Postgres 를 직접 띄웠고, 가상 IP 대신 새 프라이머리의 실제 IP 로 백엔드 애플리케이션을 다시 설정했다. 16:46 BST 에 수동 승격한 프라이머리가 동작했고, 16:59 BST 에 API 와 대시보드가 돌아와 장애는 1시간 50분 이어졌다. 그 뒤 세 번째 노드를 비동기 복제본으로 띄우고, 손으로 관리할 가상 IP 를 하나 더 두고, 데이터 무결성을 점검해 손상이 없음을 확인했다.

팀은 Docker 컨테이너로 띄운 클러스터에서 장애를 재현했다. 동기 복제본이 유효하지 않은 WAL 파일을 받은 것은 망가진 프라이머리가 마지막으로 백업 서버에 보낸 파일 때문이었고, 승격 실패와는 관계가 없었다. 승격을 막은 조건은 셋이었다. 첫째는 0이 아닌 default-resource-stickiness 설정이었다. 둘째는 Backup VIP 에 건 -INF colocation 규칙이었다. 이 규칙은 백업 프로세스를 복제본에서 돌리려고 Backup VIP 를 Postgres 프라이머리와 같은 서버에 두지 않게 했다. 셋째는 프라이머리와 동기 복제본의 프로세스가 거의 동시에 죽었다는 점이었다. 장애 때 Backup VIP 는 승격돼야 할 동기 복제본에 있었다. 규칙을 Backup VIP 와 복제본 사이의 INF 선호로 뒤집자 같은 조건에서도 승격이 됐다. 팀은 기존 클러스터에 손을 너무 많이 댔다고 보고 Pacemaker 가 관리하는 새 클러스터를 만들었다. PgBouncer 로 쿼리를 잠시 멈추는 무중단 페일오버 절차를 고쳐 써서 새 클러스터로 옮겼고, 옛 클러스터를 내렸다.

심각도
–
영향 범위
GoCardless API and Dashboard: every request failed with an error for 1 hour and 50 minutes; no data corruption found
영향 사용자
–
지역
–
감지까지
–
복구까지
1h 50m

증상

2017년 10월 10일 15:09 BST 에 모니터링이 API 와 대시보드가 완전히 멈춘 것을 찾았다. 그동안 들어온 요청은 모두 실패하고 오류를 받았다.

15:11 BST 에 프라이머리의 디스크 어레이가 망가진 증거가 보였다. RAID 컨트롤러는 어레이에서 디스크 3개를 동시에 잃었다고 기록했고, 그 뒤의 읽기와 쓰기는 모두 실패했다. Linux 커널은 그 컨트롤러 위의 파일시스템을 읽기 전용으로 바꿨지만 읽기조차 되지 않았다.

클러스터는 동기 복제본으로 페일오버하지 않았다. Pacemaker 는 프라이머리의 Postgres 가 비정상이라는 것을 제대로 알아채고 새 프라이머리를 거듭 승격하려 했지만, 매번 프라이머리를 어디서 돌릴지 정하지 못했다.

동기 복제본에서는 프라이머리의 디스크 어레이가 망가질 무렵 Postgres 하위 프로세스 하나가 죽어 Postgres 가 재시작했다. 재시작 뒤 동기 복제본은 restore_command 로 WAL 파일을 복원하려 했고, 시도마다 파일이 유효하지 않다는 메시지를 내며 실패했다. 이 메시지는 나중에 승격 실패와 관계가 없는 것으로 밝혀졌다.

대응 팀은 15:17 BST 에 망가진 프라이머리 노드의 전원을 끄면 클러스터가 새 프라이머리를 승격할 것으로 봤지만 그렇지 않았다. 15:18 BST 에 crm resource cleanup 으로 Pacemaker 의 오류 횟수를 지워도 아무 효과가 없었다.

대응 팀이 유지보수 모드에서 동기 복제본의 recovery.conf 에서 복제본 설정을 지우고 유지보수 모드를 풀 때마다, Pacemaker 는 원래 recovery.conf 로 복제본을 다시 띄웠다. 클러스터에는 프라이머리가 없는 채로 남았다.

증상 서비스 중단장애 조치 실패

원인

촉발
10월 10일 오후 Postgres 프라이머리 노드의 RAID 컨트롤러가 디스크 3개를 동시에 잃어 디스크 어레이가 망가졌다. 거의 같은 때 동기 복제본에서 Postgres 하위 프로세스 하나가 죽었다.

감지

15:09 BST 에 모니터링이 API 와 대시보드의 완전한 중단을 찾았고 엔지니어들이 조사를 시작했다.

조치

후속 조치

교훈

GoCardless 는 단순해 보이는 Pacemaker 설정이 매우 이상한 동작을 부를 수 있다는 것을 배웠다. 두 자원이 함께 돌면 안 된다는 규칙과 함께 돌아야 한다는 규칙은 반대말처럼 보이지만, 특정 장애 조건에서는 시스템이 완전히 다르게 움직였다.

프로세스 두 개가 거의 동시에 죽을 때만 드러나는 설정 오류는 기본 시험이나 평소 운영으로는 찾을 수 없고, 장애 주입으로만 드러나는 버그가 있다. 자동화가 2년 동안 장애를 잘 처리하자 그 아래 인프라를 손으로 다루는 기술이 녹슬었다. 런북을 써 두어도 몇 년 뒤에 꺼내 보면 거의 확실히 낡아 있다.

출처

관련 사례

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

kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이 사례와 같은 상태를 점검합니다. 이 사례의 구성 요소: 데이터베이스, 데이터베이스 장애 조치 관리자. 점검 항목의 근거는 이 사례 같은 공개 보고서입니다.

AI 도구와 스크립트용

API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.