{"id":"IL-0190","org":"Grafana Labs","date":"2020-03-16","docs":[],"title":{"en":"Cortex distributors kept a stuck connection to a dead etcd leader after a Kubernetes upgrade terminated its node, failing writes to Hosted Prometheus for 12 minutes","ko":"쿠버네티스 업그레이드로 etcd 리더가 있던 노드가 갑자기 종료된 뒤, Cortex distributor 두 개가 옛 리더와 맺은 TCP 연결이 멈춘 채 그 연결을 계속 써서 런던 리전 Hosted Prometheus 쓰기가 12분 동안 일부 실패했다."},"domain":["kubernetes","observability","networking"],"impact":{"scope":"Partial write outage of Hosted Prometheus; writes for some customers failed through two distributor replicas, about 20% error rate, data delayed but not lost","users":"Some Grafana Cloud Hosted Prometheus customers","region":"London"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"When a node dies abruptly, clients holding TCP connections to it are not told; without keepalive probes an etcd client can keep sending requests into a dead connection long after a new leader exists. A failover that works on the server side still needs clients that detect the dead peer.\n\nNode replacement during cluster upgrades should drain pods gracefully, including pods owned by operators that the upgrade scripts do not know about, and should give instances enough time to shut down.","ko":"노드가 죽으면 TCP 연결이 정상적으로 닫히지 않고 멈춘 채 남을 수 있다. etcd 클라이언트에 keepalive 를 설정하지 않으면 새 리더가 뽑힌 뒤에도 클라이언트는 죽은 연결을 계속 쓴다. 노드를 내리는 업그레이드 스크립트는 operator 가 관리하는 파드까지 정상 종료할 수 있어야 한다. 장애 대응과 복구, 사후 분석 동안 Grafana Loki 로 로그를 빠르게 뒤질 수 있어서 복구 시간이 줄었다."},"related":[],"sources":[{"url":"https://grafana.com/blog/2020/04/07/how-a-production-outage-in-grafana-clouds-hosted-prometheus-service-was-caused-by-a-bad-etcd-client-setup","kind":"blog","license":"unknown","archived":null}],"summary":{"en":"Grafana Cloud Hosted Prometheus is built on Cortex. To deduplicate samples from pairs of HA Prometheus replicas, the Cortex distributor records in etcd which replica is currently accepted; if no samples arrive from that replica for 15 seconds, the next replica that writes becomes the accepted one. On March 16 a scheduled Kubernetes upgrade terminated the node running the etcd leader. etcd elected a new leader within about 10 seconds, but two distributor replicas kept a TCP connection open to the old leader, because the node died without closing it. Their etcd reads timed out and the writes passing through them failed.\n\nThe etcd client in Cortex supports gRPC keepalive probes, but Cortex had not configured them, so the client never noticed the dead connection. The team reproduced the failure in dev by dropping packets with iptables between a distributor and its etcd instance, and showed that setting DialKeepAliveTime, DialKeepAliveTimeout and PermitWithoutStream in the clientv3 configuration prevents it.\n\nThe upgrade scripts also played a part: the etcd pods are managed by the etcd operator, so the scripts did not know how to move them gracefully, and the API call the scripts used to terminate an instance gave it only about 90 seconds to shut down. Prometheus remote_write treats the timeout as recoverable, so senders retried the failed writes against other distributor replicas and no data was lost; storage was only delayed.","ko":"Grafana Cloud 의 Hosted Prometheus 는 CNCF 프로젝트인 Cortex 위에서 돈다. 2019년 1월부터는 고가용성을 위해 짝지은 Prometheus 레플리카 두 대가 같은 인스턴스로 메트릭을 보내면 받는 쪽에서 중복을 걸러 낸다. 이를 위해 지금 쓰기를 받아 주는 레플리카가 어느 것인지 etcd 에 기록한다. 그 레플리카에서 15초 동안 메트릭이 오지 않으면 다음에 쓰기를 보낸 레플리카로 바꾼다.\n\n3월 16일 11:19:00 UTC에 예약된 쿠버네티스 업그레이드가 etcd 리더가 돌던 노드를 갑자기 종료했다. etcd 는 약 10초 만에 새 리더를 뽑았다. 그러나 distributor 레플리카 두 개는 옛 리더와 맺은 TCP 연결을 계속 쓰고 있었다. 노드가 죽으면서 연결이 정상적으로 닫히지 않았고, 이 연결이 멈춘 채 남았다. 두 레플리카의 etcd 조회는 \"context deadline exceeded\" 로 실패했고, 그 레플리카로 들어온 쓰기도 실패했다.\n\netcd 클라이언트는 gRPC keepalive 프로브를 지원하지만 Cortex 는 이를 제대로 설정하지 않았다. 팀은 개발 환경에서 iptables 규칙으로 distributor 인스턴스와 그 인스턴스가 연결된 etcd 인스턴스 사이의 패킷을 버려 같은 장애를 재현했다. etcd 클라이언트에 keepalive 프로브를 켜면 문제가 생기지 않는 것도 확인했다.\n\n노드가 갑자기 종료된 데에도 이유가 있었다. etcd 파드는 etcd operator 가 관리했는데, 업그레이드 스크립트는 이 파드를 안전하게 다른 노드로 옮기는 방법을 몰랐다. 게다가 스크립트가 인스턴스를 종료할 때 쓰는 API 호출은 인스턴스에 정리할 시간을 약 90초만 줬다."},"trigger":{"en":"A scheduled Kubernetes version upgrade abruptly terminated the node that ran the etcd leader at 11:19:00 UTC.","ko":"예약된 쿠버네티스 버전 업그레이드가 etcd 리더가 돌던 노드를 정상 종료 절차 없이 갑자기 종료했다."},"severity":"unknown","symptoms":["high-error-rate","intermittent-timeouts"],"timeline":{"detected_after":"11m","resolved_after":"12m"},"date_kind":"incident","detection":{"en":"An SLO-based alert paged the on-call engineer at 11:31, 11 minutes after the errors started. At a 20% error rate the monthly SLA would have taken about 18 hours to breach.","ko":"SLO 기반 알림이 11:31 UTC에 당직 엔지니어를 호출했다. 장애가 시작되고 11분이 지난 시점이었다. 오류율이 20% 수준이었기 때문에 이대로 두었다면 월간 SLA 를 넘기기까지 약 18시간이 걸렸을 것이다."},"components":["etcd","node-pool"],"root_cause":"software-bug","remediation":[{"text":{"en":"Restarted the two affected Cortex distributor replicas at 11:32, which stopped the errors","ko":"11:32 UTC에 영향받은 distributor 를 재시작했고, 오류가 멈췄다."}}],"action_items":[{"text":{"en":"Enable gRPC keepalive probes in the Cortex etcd client (DialKeepAliveTime 10s, DialKeepAliveTimeout, PermitWithoutStream)","ko":"Cortex 의 etcd 클라이언트 설정에 gRPC keepalive 프로브를 켠다. DialKeepAliveTime 은 10초, DialKeepAliveTimeout 은 DialTimeout 의 두 배로 두고 PermitWithoutStream 을 true 로 한다."}},{"text":{"en":"Make the Kubernetes upgrade process terminate pods and machines gracefully, including the etcd pods managed by the etcd operator","ko":"쿠버네티스 업그레이드 절차를 고쳐, etcd operator 가 관리하는 etcd 파드까지 포함해 파드와 머신을 정상 종료 절차로 내리게 한다."}}],"contributing":["default-config-unchanged","no-safeguard-on-destructive-change"],"recurrence_of":null,"symptoms_text":{"en":"About a minute after the etcd leader changed, two replicas of the Cortex distributor started logging \"context deadline exceeded\" when they tried to read keys from etcd. Writes from some customers that landed on those two replicas failed, while the other distributor replicas kept working, so the service saw an error rate of about 20% on writes to the London region of Hosted Prometheus.\n\nThe distributor reads etcd to know which replica of a customer's HA Prometheus pair is currently elected to have its writes accepted. Another etcd replica had become the leader about 10 seconds after the old one disappeared, but the two distributors were still holding TCP connections to the old leader, whose node had been terminated without closing them.","ko":"2020년 3월 16일 11:31 UTC에 SLO 기반 알림이 당직 엔지니어를 호출했다. 런던 리전 Hosted Prometheus 로 들어오는 쓰기의 약 20%가 실패하고 있었다. 문제는 이미 11분 전에 시작된 상태였다.\n\n실패는 Cortex distributor 레플리카 두 개에서만 나왔다. distributor 는 쓰기 경로에서 샘플 중복을 걸러 내는 서비스다. 두 레플리카는 etcd 클러스터에서 키를 읽으려 할 때마다 \"context deadline exceeded\" 를 로그에 남겼고, 이 레플리카를 거치는 일부 고객의 쓰기가 실패했다.\n\n시간 순서로 보면 11:19:00 UTC에 etcd 리더가 돌던 노드가 예약된 쿠버네티스 버전 업그레이드로 갑자기 종료됐다. 약 10초 뒤 다른 etcd 레플리카가 설계대로 새 리더가 됐다. 그로부터 약 60초 뒤 두 distributor 에서 타임아웃 오류가 나기 시작했다.\n\n실패한 쓰기는 데이터 유실로 이어지지 않았다. Prometheus 의 remote_write 는 이 타임아웃 오류를 복구할 수 있는 오류로 보고 다시 보냈고, 재시도가 다른 distributor 레플리카로 가면 성공했다. 데이터는 늦게 저장됐을 뿐 사라지지 않았다."},"updated_at":"2026-11-10T00:00:39.928959Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0190","org_slug":"grafana-labs"}