{"id":"IL-0066","org":"Pusher","date":"2019-05-21","docs":["https://github.com/pusher/wave"],"title":{"en":"Broken ConfigMap change lay dormant for weeks, then took down Pusher's edge proxy when a cluster upgrade replaced its pods","ko":"ConfigMap 만 바꾸고 디플로이먼트는 그대로 둔 탓에 잘못된 설정이 몇 주 뒤 노드 교체 때 모든 브리지 파드에 퍼져 Pusher 의 모든 트래픽이 끊겼다"},"domain":["kubernetes"],"impact":{"scope":"all Pusher products; every request through the bridge proxy","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"A ConfigMap change does not restart the pods that use it, so for an application that cannot reload its configuration the change takes effect only when pods are next replaced, possibly weeks later during unrelated work. Tie configuration to the pod template so a bad change fails at the moment it is made.","ko":"ConfigMap 을 바꿔도 디플로이먼트는 새 파드를 만들지 않는다. 설정을 실행 중에 다시 읽지 못하는 애플리케이션은 파드가 다시 뜰 때에야 새 설정을 읽으므로, 잘못된 설정이 몇 주 뒤에 드러날 수 있다.\n\n설정을 바꾸면 곧바로 새 파드가 뜨게 해야 설정이 맞는지 그 자리에서 확인할 수 있다."},"related":[],"sources":[{"url":"https://www.youtube.com/watch?v=8P7-C44Gjj8","kind":"talk","license":"unknown","archived":null}],"summary":{"en":"In April 2018 an engineer at Pusher deployed a change to the bridge's ConfigMap without changing the deployment that used it. The configuration was valid, passed linting, and the bridge would have started with it, but it broke routing to the backend services. Updating a ConfigMap in Kubernetes does not roll the pods of a deployment that references it: the files mounted in running pods are updated in place, and an application that does not reload its configuration, such as the bridge, keeps running with the old one. Nothing happened until three or four weeks later, when a cluster upgrade replaced the bridge pods and each new pod read the broken configuration. When the last old pod was gone, the whole platform was down.\n\nThe speaker says this kind of incident happened several times during 2018. To stop it, Pusher built Wave, an open-source controller that watches the ConfigMaps and Secrets referenced by deployments, stateful sets and daemon sets, and writes a hash of their data into the pod template. Any configuration change then triggers a normal rollout, so a bad configuration fails immediately instead of weeks later.","ko":"Pusher 의 고객 트래픽은 모두 브리지를 거친다. 브리지는 인증, HTTP/2, WebSocket 업그레이드 같은 Pusher 고유의 로직을 처리하는 자체 개발 7계층 프록시다. 브리지는 실행 중에 설정을 다시 읽지 못한다.\n\n2018년 4월쯤 동료가 브리지 설정을 운영 클러스터에 배포했다. 이 설정은 문법이 맞고 린트도 통과했으며 브리지도 이 설정으로 시작할 수 있었다. 그러나 백엔드 서비스로 가는 트래픽 라우팅을 망가뜨렸다. 쿠버네티스에서 디플로이먼트는 ConfigMap 을 알지만 ConfigMap 은 자신을 쓰는 디플로이먼트를 모른다. ConfigMap 을 바꾸면 몇 초 뒤 각 파드의 파일 시스템에 있는 파일만 바뀔 뿐 새 레플리카셋이나 새 파드가 만들어지지 않는다. 그래서 잘못된 설정은 배포된 뒤에도 바로 드러나지 않았다.\n\n3주에서 4주 뒤 클러스터 업그레이드로 노드를 교체하자 브리지 파드가 차례로 새로 떴고, 새 파드는 잘못된 설정으로 시작했다. 결국 모든 브리지 파드가 잘못된 설정으로 돌았고 클러스터로 가는 모든 트래픽이 멈췄다. 발표자에 따르면 같은 유형의 장애가 한 해 동안 서너 번에서 여섯 번 되풀이됐다.\n\n팀은 이를 막으려고 Wave 라는 오픈소스 컨트롤러를 만들었다. Wave 는 디플로이먼트, 스테이트풀셋, 데몬셋이 참조하는 ConfigMap 과 Secret 을 감시하고, 그 데이터로 해시를 계산해 파드 템플릿에 넣는다. 설정이 바뀌면 해시가 바뀌므로 디플로이먼트 컨트롤러가 곧바로 새 파드를 롤아웃하고, 새 설정이 제대로 동작하는지 바로 알 수 있다."},"trigger":{"en":"A cluster upgrade replaced nodes, so the bridge pods restarted and loaded a ConfigMap that had been changed three or four weeks earlier.","ko":"동료가 ConfigMap 의 설정을 바꿔 운영 클러스터에 배포했지만, 그 설정에 의존하는 디플로이먼트는 갱신하지 않았다. 3주에서 4주 뒤 클러스터 업그레이드로 노드를 교체하면서 브리지 파드가 새로 떴다."},"severity":"unknown","symptoms":["service-unavailable","high-error-rate"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":null,"components":["configmap"],"root_cause":"config-change","remediation":[],"action_items":[{"text":{"en":"Build and run Wave so that changes to ConfigMaps and Secrets roll out new pods immediately","ko":"오픈소스 컨트롤러 Wave 를 만들었다. Wave 는 디플로이먼트, 스테이트풀셋, 데몬셋이 참조하는 ConfigMap 과 Secret 의 해시를 파드 템플릿에 넣어, 설정이 바뀌면 곧바로 새 파드가 롤아웃되게 한다."}}],"contributing":["delayed-effect-change","assumption-unverified"],"recurrence_of":null,"symptoms_text":{"en":"During a cluster upgrade, all traffic to Pusher's products stopped. The speaker recalls that requests were failing with 500 errors.\n\nAll customer traffic passes through a load balancer, then nginx, then the bridge, an in-house layer 7 proxy that handles Pusher-specific authentication, HTTP/2 and WebSocket upgrades, before reaching the product services. As the upgrade replaced nodes, bridge pods were recreated one after another, and once every bridge pod was running the new configuration, routing to the backend services stopped working.","ko":"Pusher 의 모든 제품으로 가는 트래픽이 멈췄다. 고객 트래픽은 ELB 와 nginx 를 지나, 브리지라고 부르는 자체 개발 7계층 프록시를 거쳐 각 제품 서비스로 간다. 그래서 브리지가 망가지면 플랫폼 전체가 내려간다. 발표자는 이때 요청이 모두 500 오류였던 것으로 기억했다.\n\n문제의 설정은 3주에서 4주 전에 이미 운영 클러스터에 배포되어 있었다. 클러스터 업그레이드로 노드를 교체하면서 브리지 파드가 하나씩 새로 떴고, 새 파드는 잘못된 설정을 읽었다. 결국 모든 브리지 파드가 잘못된 설정으로 돌게 되자 클러스터로 가는 트래픽이 모두 멈췄다."},"updated_at":"2026-11-10T00:00:39.262428Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0066","org_slug":"pusher"}