IL-0190 Grafana Labs · 2020-03-16kubernetesobservabilitynetworking
Grafana Cloud 의 Hosted Prometheus 는 CNCF 프로젝트인 Cortex 위에서 돈다. 2019년 1월부터는 고가용성을 위해 짝지은 Prometheus 레플리카 두 대가 같은 인스턴스로 메트릭을 보내면 받는 쪽에서 중복을 걸러 낸다. 이를 위해 지금 쓰기를 받아 주는 레플리카가 어느 것인지 etcd 에 기록한다. 그 레플리카에서 15초 동안 메트릭이 오지 않으면 다음에 쓰기를 보낸 레플리카로 바꾼다.
3월 16일 11:19:00 UTC에 예약된 쿠버네티스 업그레이드가 etcd 리더가 돌던 노드를 갑자기 종료했다. etcd 는 약 10초 만에 새 리더를 뽑았다. 그러나 distributor 레플리카 두 개는 옛 리더와 맺은 TCP 연결을 계속 쓰고 있었다. 노드가 죽으면서 연결이 정상적으로 닫히지 않았고, 이 연결이 멈춘 채 남았다. 두 레플리카의 etcd 조회는 "context deadline exceeded" 로 실패했고, 그 레플리카로 들어온 쓰기도 실패했다.
etcd 클라이언트는 gRPC keepalive 프로브를 지원하지만 Cortex 는 이를 제대로 설정하지 않았다. 팀은 개발 환경에서 iptables 규칙으로 distributor 인스턴스와 그 인스턴스가 연결된 etcd 인스턴스 사이의 패킷을 버려 같은 장애를 재현했다. etcd 클라이언트에 keepalive 프로브를 켜면 문제가 생기지 않는 것도 확인했다.
노드가 갑자기 종료된 데에도 이유가 있었다. etcd 파드는 etcd operator 가 관리했는데, 업그레이드 스크립트는 이 파드를 안전하게 다른 노드로 옮기는 방법을 몰랐다. 게다가 스크립트가 인스턴스를 종료할 때 쓰는 API 호출은 인스턴스에 정리할 시간을 약 90초만 줬다.
2020년 3월 16일 11:31 UTC에 SLO 기반 알림이 당직 엔지니어를 호출했다. 런던 리전 Hosted Prometheus 로 들어오는 쓰기의 약 20%가 실패하고 있었다. 문제는 이미 11분 전에 시작된 상태였다.
실패는 Cortex distributor 레플리카 두 개에서만 나왔다. distributor 는 쓰기 경로에서 샘플 중복을 걸러 내는 서비스다. 두 레플리카는 etcd 클러스터에서 키를 읽으려 할 때마다 "context deadline exceeded" 를 로그에 남겼고, 이 레플리카를 거치는 일부 고객의 쓰기가 실패했다.
시간 순서로 보면 11:19:00 UTC에 etcd 리더가 돌던 노드가 예약된 쿠버네티스 버전 업그레이드로 갑자기 종료됐다. 약 10초 뒤 다른 etcd 레플리카가 설계대로 새 리더가 됐다. 그로부터 약 60초 뒤 두 distributor 에서 타임아웃 오류가 나기 시작했다.
실패한 쓰기는 데이터 유실로 이어지지 않았다. Prometheus 의 remote_write 는 이 타임아웃 오류를 복구할 수 있는 오류로 보고 다시 보냈고, 재시도가 다른 distributor 레플리카로 가면 성공했다. 데이터는 늦게 저장됐을 뿐 사라지지 않았다.
원인 ? 소프트웨어 결함
SLO 기반 알림이 11:31 UTC에 당직 엔지니어를 호출했다. 장애가 시작되고 11분이 지난 시점이었다. 오류율이 20% 수준이었기 때문에 이대로 두었다면 월간 SLA 를 넘기기까지 약 18시간이 걸렸을 것이다.
노드가 죽으면 TCP 연결이 정상적으로 닫히지 않고 멈춘 채 남을 수 있다. etcd 클라이언트에 keepalive 를 설정하지 않으면 새 리더가 뽑힌 뒤에도 클라이언트는 죽은 연결을 계속 쓴다. 노드를 내리는 업그레이드 스크립트는 operator 가 관리하는 파드까지 정상 종료할 수 있어야 한다. 장애 대응과 복구, 사후 분석 동안 Grafana Loki 로 로그를 빠르게 뒤질 수 있어서 복구 시간이 줄었다.
같은 원인 · 공통 노드 풀
같은 원인 · 공통 노드 풀
같은 원인 · 공통 etcd
같은 원인 · 공통 노드 풀
같은 원인 · 공통 etcd
같은 원인 · 공통 etcd