{"id":"IL-0003","org":"Monzo","date":"2017-10-27","docs":["https://github.com/kubernetes/kubernetes/issues/47131","https://github.com/linkerd/linkerd/issues/1219"],"title":{"en":"linkerd lost Kubernetes service discovery after an etcd reconfiguration, and restarting every linkerd instance turned failed payments into a full platform outage","ko":"etcd 클러스터 재구성 뒤 쿠버네티스 버그로 linkerd 가 서비스 위치 갱신을 받지 못했고, linkerd 를 모두 재시작하자 엔드포인트가 없는 서비스를 파싱하지 못하는 오류가 드러나 플랫폼 전체가 멈췄다"},"domain":["kubernetes","aws"],"impact":{"scope":"whole platform: current account and prepaid card payments, bank transfers, app feed and balance, internal Customer Operations tools","users":"unknown","region":"AWS, three availability zones"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"A large failure in a distributed system can be very hard to understand, and well-meant human action can compound it, as restarting every linkerd instance did here by exposing a second incompatibility. Because Monzo deploys often and its network changes constantly, the deployment and request-routing systems have to be trustworthy. Monzo committed to publishing a similar post-mortem for any incident of this severity.","ko":"분산 시스템의 대규모 장애는 이해하기 매우 어렵고, 사람이 좋은 뜻으로 한 조치가 오히려 문제를 키울 수 있다. 이번에는 linkerd 를 모두 재시작한 결정이 또 다른 버전 비호환 문제를 드러내 장애를 더 키웠다.\n\n배포가 잦고 노드와 애플리케이션 장애에 자동으로 대응하는 백엔드에서는 네트워크가 매우 동적이다. 그래서 배포와 요청 라우팅 시스템을 믿을 수 있는 것이 무엇보다 중요하다. Monzo 는 인터페이스를 바꿀 때 앞뒤 버전과 호환되게 해서 롤백을 안전하게 할 수 있게 해 두었지만, 이번에는 롤백한 뒤에도 오류가 계속됐다."},"related":[],"sources":[{"url":"https://community.monzo.com/t/resolved-current-account-payments-may-fail-major-outage-27-10-2017/26296/95","kind":"blog","license":"unknown","archived":"http://web.archive.org/web/20260924222434/https://community.monzo.com/t/resolved-current-account-payments-may-fail-major-outage-27-10-2017/26296/95"},{"url":"https://www.youtube.com/watch?v=OUYTNywPk-s","kind":"talk","license":"unknown","archived":null}],"summary":{"en":"Two weeks before the outage, Monzo's Platform team upgraded etcd to a new version and grew the cluster from three nodes (one per zone) to nine (three per zone). The upgrade caused no downtime, but it set up a bug in Kubernetes and the etcd client that can make requests time out after this kind of cluster reconfiguration. Because of those timeouts, linkerd did not receive the update from Kubernetes about where the newly deployed payments service was running, and kept routing its requests to old addresses. Rolling back the deploy did not help.\n\nAt 14:26, assuming every linkerd instance had the same problem, engineers decided to restart all of them, several hundred in total. The replacements could not start until the three Kubernetes API server processes were restarted at 14:37. When all linkerd pods had restarted at 15:13 and loaded service discovery afresh, they hit a second, unrelated problem: the linkerd version in use threw a NullPointerException on Kubernetes services with no endpoints, such as the service scaled to zero replicas the day before. Routing stopped across the whole platform. Monzo later called the mass restart an unfortunate and poor decision that made the outage worse.\n\nThe Platform team began deploying a newer linkerd version, tested in staging for several weeks, that fixed the incompatibility. At 15:31, after reading that code change, engineers realised that deleting the Kubernetes services with no endpoints would avoid the parsing error. They deleted the service, linkerd loaded service discovery, traffic flowed between services again and payments worked. During the outage, engineers used internal backup processes so that most customers could still use their card. The current account and the prepaid product were affected for around 1½ hours.","ko":"2017년 10월 27일 금요일 오후, Monzo 의 입출금 계좌와 선불 카드가 약 1시간 반 동안 장애를 겪었다. Monzo 백엔드는 마이크로서비스 수백 개로 이루어져 있다. 이 서비스들은 컨테이너로 돌고, 쿠버네티스가 AWS 노드 위에서 이 컨테이너들을 관리한다. 쿠버네티스는 서비스 배치 정보를 etcd 에 저장한다. 서비스 사이의 호출은 linkerd 가 라우팅하고 부하를 나누며, linkerd 는 서비스 위치를 쿠버네티스에서 받는다.\n\n장애 2주 전 플랫폼 팀은 etcd 를 새 버전으로 올리면서 노드를 가용 영역 세 곳에 한 대씩 세 대에서 가용 영역마다 세 대씩 아홉 대로 늘렸다. 이 작업은 다운타임 없이 끝났다. 하지만 쿠버네티스와 etcd 클라이언트에는 이런 클러스터 재구성 뒤 요청이 타임아웃 날 수 있는 버그가 있었다. 장애 전날에는 한 팀이 문제를 보이던 새 서비스를 레플리카 0개로 줄였고, 쿠버네티스 서비스는 그대로 남겨 두었다.\n\n14:10 에 결제 서비스 변경을 배포하자 그 서비스로 가는 요청이 모두 실패했다. etcd 클라이언트 버그로 생긴 타임아웃 때문에 linkerd 는 새 파드 위치를 받지 못했고, 롤백해도 오류는 그대로였다. 팀은 linkerd 수백 개를 모두 재시작했고, kubelet 이 설정을 받지 못하자 apiserver 세 개도 재시작했다. 재시작한 linkerd 는 엔드포인트가 없는 빈 서비스를 파싱하지 못해 NullPointerException 을 냈다. 사용 중인 쿠버네티스와 linkerd 버전이 서로 맞지 않았기 때문이다. 15:13 에 서비스 사이 트래픽이 모두 끊겨 플랫폼 전체가 멈췄다.\n\n15:31 에 엔지니어들은 수정 코드를 살펴본 뒤 엔드포인트가 없는 서비스를 지우면 파싱 오류를 피할 수 있다는 것을 알았다. 레플리카 0개로 줄여 둔 그 서비스를 지우자 linkerd 가 서비스 디스커버리 정보를 읽어 들였고 플랫폼이 회복했다. Monzo 는 linkerd 를 모두 재시작한 결정이 좋은 뜻에서 나왔지만 잘못이었고, 또 다른 버전 비호환 문제를 드러내 장애를 키웠다고 밝혔다."},"trigger":{"en":"At 14:10 an engineer deployed a change to a service needed to process current account payments. Two weeks earlier the etcd cluster had been upgraded and grown from three to nine nodes, and one day earlier another team had scaled a new service down to zero replicas while leaving its Kubernetes service in place.","ko":"14:10 에 엔지니어가 입출금 계좌 결제 처리에 필요한 서비스에 변경을 배포했다. 2주 전 etcd 클러스터를 재구성한 뒤로 쿠버네티스와 etcd 클라이언트 버그가 요청 타임아웃을 낼 수 있는 상태였고, 이 때문에 linkerd 는 배포 뒤 새 파드의 위치를 받지 못했다."},"severity":"unknown","symptoms":["high-error-rate","stale-service-routing","service-unavailable"],"timeline":{"detected_after":"6m","resolved_after":"1h21m"},"date_kind":"incident","detection":{"en":"Payment failures began right after the 14:10 deploy. The team rolled back at 14:12 and declared an outage internally at 14:16. At 15:13, when no service could serve requests, practically all automated alerts fired.","ko":"14:10 배포 직후 그 서비스로 가는 요청이 모두 실패했고 입출금 계좌 결제가 실패하기 시작했다. 14:12 롤백 뒤에도 오류가 계속되자 14:16 에 내부적으로 장애를 선언했다. 15:13 에 플랫폼 전체가 멈추자 자동 알림이 거의 모두 울렸다."},"components":["etcd","kube-apiserver","linkerd","kubelet"],"root_cause":"software-bug","remediation":[{"text":{"en":"Rolled back the payments service change (the errors continued)","ko":"14:12 에 배포한 변경을 롤백했지만 오류는 계속됐다."}},{"text":{"en":"Activated internal backup processes so most customers could still use their card","ko":"고객 영향을 줄이려고 장애 때 쓰는 내부 백업 절차를 가동해 대부분의 고객이 계속 카드를 쓸 수 있게 했다."}},{"text":{"en":"Restarted all linkerd instances (this made the outage worse)","ko":"14:26 에 linkerd 인스턴스 수백 개를 모두 재시작하기 시작했고, 이 조치가 장애를 키웠다."}},{"text":{"en":"Restarted the three Kubernetes API server processes so replacement linkerd instances could start","ko":"14:37 에 kubelet 이 apiserver 에서 설정을 받지 못하자 apiserver 프로세스 세 개를 재시작했다."}},{"text":{"en":"Began deploying a newer linkerd version with the fix for empty services","ko":"15:27 에 플랫폼 팀이 새 linkerd 버전을 배포하기 시작했다. 이 버전에는 비호환 문제를 고친 코드가 들어 있었고, 스테이징에서 몇 주째 시험하던 버전이었다."}},{"text":{"en":"Deleted the Kubernetes service that had no endpoints, which let linkerd load service discovery again","ko":"15:31 에 엔드포인트가 없는 쿠버네티스 서비스를 지워 linkerd 가 서비스 디스커버리 정보를 읽게 했고, 플랫폼이 회복했다."}}],"action_items":[{"text":{"en":"Fix the bug in Kubernetes that can trigger timeouts after a cluster reconfiguration","ko":"클러스터 재구성 뒤 타임아웃을 일으킬 수 있는 쿠버네티스 버그를 고친다."}},{"text":{"en":"Roll out a new version of linkerd that fixes the parsing error","ko":"파싱 오류를 고친 새 linkerd 버전을 배포한다."}},{"text":{"en":"Create better health checks, dashboards and alerts for the affected components so they give clearer signals about what is wrong and prevent human error","ko":"영향을 받은 구성 요소에 더 나은 헬스 체크, 대시보드, 알림을 만들어 무엇이 잘못됐는지 더 분명한 신호를 주고 사람의 실수를 막는다."}},{"text":{"en":"Improve procedures to communicate outages internally and externally as clearly and quickly as possible","ko":"장애를 내부와 외부에 더 분명하고 빠르게 알리도록 절차를 개선한다."}}],"contributing":["revert-did-not-restore","manual-action-worsened","overlapping-failures","monitoring-blind-spot","single-point-of-failure"],"recurrence_of":null,"symptoms_text":{"en":"At 14:10 an engineer deployed a change to a service that Monzo's current account needs to process payments, and every request to that service started to fail. Current account customers saw payment failures; the prepaid card was not affected at first because it does not use that service. The change was rolled back at 14:12, but the errors continued.\n\nEngineers saw that linkerd, which routes and load-balances the calls between Monzo's several hundred microservices, was in an unhealthy state. It turns a logical name such as service.foo into an IP address and port, but it had not received the update from Kubernetes about where the new pods were running, so it kept sending requests to IP addresses where no process was running any more.\n\nReplacement linkerd instances then could not start, because the kubelet on each node failed to fetch their configuration from the Kubernetes API servers. After the three API server processes were restarted the replacements came up, but at 15:13 services that normally handle thousands of requests per second were receiving no traffic. Customers could not refresh their feed or balance in the Monzo app, the internal Customer Operations (COps) tools stopped working, and practically all automated alerts fired.\n\nAt 15:27 engineers noticed linkerd logging NullPointerException while parsing the service discovery response from the Kubernetes API server. The running versions of Kubernetes and linkerd were incompatible: linkerd failed to parse services that had no endpoints.","ko":"14:10 에 입출금 계좌 결제 처리에 필요한 서비스 하나를 배포하자 그 서비스로 가는 요청이 모두 실패했다. 이때부터 입출금 계좌 고객의 결제가 실패했다. 선불 카드는 이 서비스를 쓰지 않아 아직 영향이 없었다.\n\n14:12 에 변경을 롤백했지만 오류는 그대로였고 결제는 계속 되지 않았다. 14:16 에 내부적으로 장애를 선언했다. 14:18 에 엔지니어들은 linkerd 가 비정상 상태로 보인다는 것을 확인하고, 문제 있는 노드를 찾아 재시작하는 내부 도구를 썼다. linkerd 는 새 파드가 네트워크 어디에서 도는지 쿠버네티스에서 갱신받지 못한 상태였고, 프로세스가 더는 없는 IP 주소로 요청을 보내고 있었다.\n\n14:26 에 팀은 linkerd 인스턴스 수백 개가 모두 같은 문제를 겪는다고 보고 전부 재시작하기로 했다. 14:37 에는 재시작한 linkerd 가 뜨지 못했다. 각 노드의 kubelet 이 쿠버네티스 apiserver 에서 설정을 가져오지 못했기 때문이다. 팀은 쿠버네티스나 etcd 에 문제가 더 있다고 의심하고 apiserver 프로세스 세 개를 재시작했고, 그 뒤 linkerd 가 정상적으로 떴다.\n\n15:13 에 linkerd 파드가 모두 재시작됐지만, 초당 수천 건을 처리하던 서비스들로 트래픽이 하나도 들어오지 않았다. 고객은 Monzo 앱에서 피드와 잔액을 새로 고칠 수 없었고, Customer Operations 팀의 내부 도구도 멈췄다. 장애는 플랫폼 전체 장애로 커졌고 자동 알림이 거의 모두 울렸다. 15:27 에 linkerd 가 쿠버네티스 apiserver 의 서비스 디스커버리 응답을 파싱하다 NullPointerException 을 로그에 남기는 것이 보였다."},"updated_at":"2026-11-10T00:00:31.326693Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0003","org_slug":"monzo"}