{"id":"IL-0038","org":"Oath","date":"2018-05-04","docs":[],"title":{"en":"kubelet rollout hit a Kubernetes bug and left every node in a cluster NotReady, triggering mass pod eviction","ko":"kubelet 설정 변경을 롤링 업데이트하다 쿠버네티스 버그로 kubelet 이 올라오지 못해 한 클러스터의 노드가 NotReady 가 되고 파드 절반쯤이 축출됐다"},"domain":["kubernetes"],"impact":{"scope":"one production cluster; about half of its pods evicted and service degraded until the data center was failed over","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"Check kubelet after upgrading it before moving to the next batch; a simple curl against the kubelet API would have caught this. The node controller's eviction throttling and full disruption mode, which the speakers note is not well documented, kept a cluster-wide kubelet failure from evicting every pod.","ko":"kubelet 을 업그레이드한 뒤에는 반드시 테스트한다. kubelet API 에 간단한 curl 명령 하나만 보내 봐도 충분하다.\n\n노드 컨트롤러는 노드의 50% 이상이 NotReady 가 되면 축출 속도를 늦추고, 노드가 모두 내려가면 full disruption 모드로 들어가 축출을 멈춘다. 이 동작 덕분에 이번 장애에서 파드의 절반이 살아남았다.\n\n데이터 센터와 클러스터가 여럿이면 품질이 떨어진 데이터 센터에서 다른 데이터 센터로 페일오버해 사용자를 빠르게 옮길 수 있다."},"related":[],"sources":[{"url":"https://www.youtube.com/watch?v=likHm-KHGWQ","kind":"talk","license":"unknown","archived":null}],"summary":{"en":"Oath ran Kubernetes on premises for media sites such as its sports, finance and home pages, in 12 production clusters across six regions behind anycast routing. While rolling out a kubelet change in one cluster, kubelet hit a Kubernetes bug during its cgroup cleanup at restart and did not come back. The rolling update kept going batch by batch with no check that the updated kubelets worked, so it reached the whole cluster.\n\nWith every kubelet down, the API server saw every node as NotReady and the node controller began evicting pods. Its built-in protection limited the damage: above 50% NotReady nodes it slowed eviction, and with all nodes down it entered full disruption mode and stopped evicting altogether. About half the pods had been evicted by then; the rest kept serving because the pods and the ingress path were healthy. The team failed the data center over so users were served from the next one.","ko":"Oath 의 플랫폼팀은 쿠버네티스를 1.5 무렵부터 써 왔고 1.9 까지 여러 번 업그레이드했다. 그중 kubelet 변경을 롤링 업데이트로 배포하다가 쿠버네티스 버그를 만났다. kubelet 은 재시작할 때 임시 cgroup 파일을 정리한 뒤 올라오는데, 이 버그 때문에 kubelet 이 멈췄다. 롤링 업데이트는 한 클러스터의 노드 전체로 퍼졌다.\n\nkubelet 이 올라오지 않자 쿠버네티스 API 는 노드가 죽었다고 보았고, 컨트롤러는 NotReady 노드를 eviction 큐에 넣어 파드를 축출하기 시작했다. 컨트롤러는 클러스터 노드의 50% 이상이 NotReady 가 되면 클러스터 자체에 문제가 있다고 보고 축출 속도를 늦춘다. 매초 하던 축출을 15초, 20초 간격으로 늦추는 식이다. 노드가 모두 내려간 것을 보면 full disruption 모드로 들어가 축출을 아예 멈춘다. 발표자는 이 동작이 문서에 잘 나와 있지 않다고 했다.\n\n이 동작 덕분에 파드 전체가 축출되지는 않았다. 파드의 50% 정도가 축출되어 Pending 상태로 남았고, 나머지 50% 가 버텨 준 덕분에 서비스는 완전히 멈추지 않고 품질만 떨어졌다. 팀은 해당 데이터 센터에서 다음 데이터 센터로 페일오버해 사용자를 옮겼다.\n\n팀은 원래 한 번에 30% 씩 업데이트했지만, 업그레이드 뒤 kubelet 이 제대로 동작하는지 확인하는 기능 테스트가 없었다. kubelet API 에 curl 명령 하나만 보내 봤어도 충분히 잡을 수 있었다."},"trigger":{"en":"A change to kubelet was rolled out across the nodes of one cluster; on restart, kubelet cleans up transient cgroup files, and a Kubernetes bug in that step left kubelet stuck instead of starting.","ko":"kubelet 변경을 롤링 업데이트로 배포했다. kubelet 은 재시작할 때 시작 전에 임시 cgroup 파일을 정리하는데, 이 과정에서 쿠버네티스 버그로 멈췄다. 이 업데이트가 한 클러스터의 노드 전체로 퍼졌다."},"severity":"unknown","symptoms":["node-not-ready","pods-evicted","pod-pending"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":{"en":"Nodes reported NotReady and the eviction graphs climbed while the kubelet rollout was in progress.","ko":"업데이트 중 kubelet 이 올라오지 않으면서 노드가 NotReady 로 바뀌었고, 그래프에서 eviction 큐에 들어가 축출되는 노드 수가 늘어나는 것이 보였다."},"components":["kubelet","kube-controller-manager"],"root_cause":"software-bug","remediation":[{"text":{"en":"failed the affected data center over so users were routed to the next available data center","ko":"해당 데이터 센터에서 다음 데이터 센터로 페일오버해 사용자를 옮겼다."}}],"action_items":[{"text":{"en":"Run a functional test of kubelet after each upgrade batch and certify it before the next 30% of nodes are updated","ko":"kubelet 업데이트를 적용한 뒤 테스트 프레임워크로 kubelet 을 검증하고, 검증이 끝나야 다음 30% 묶음의 업데이트로 넘어가게 했다."}}],"contributing":["no-health-probe","blast-radius-too-wide"],"recurrence_of":null,"symptoms_text":{"en":"During a rolling update of a kubelet change in one of Oath's on-premises production clusters, nodes started going NotReady, and soon all nodes in the cluster were NotReady.\n\nThe graphs showed a growing number of nodes entering the eviction queue and pods being evicted from them. The node controller in the Kubernetes controller manager tries to move pods off a NotReady node so they can run elsewhere, but with no Ready nodes left the evicted pods could only sit in Pending.\n\nThe workloads themselves were still running: only the link between kubelet and the API server was broken, and traffic from the ingress to the pods still worked. Once half of the nodes were NotReady, the node controller slowed its eviction rate, and when it saw the whole cluster down it entered full disruption mode and stopped evicting. By then about half of the pods had been evicted, so users saw a degraded service rather than a full outage.","ko":"kubelet 변경을 클러스터 전체에 롤링 업데이트하던 중 kubelet 이 다시 올라오지 못했다. 쿠버네티스 API 는 노드가 죽었다고 판단했고 노드들이 NotReady 상태로 바뀌었다.\n\n그래프에서는 eviction 큐에 들어가 축출되는 노드 수를 나타내는 선이 계속 올라갔다. 컨트롤러는 NotReady 노드를 eviction 큐에 넣고 그 노드의 파드를 축출해 다른 노드에 다시 스케줄하려 했다.\n\n축출된 파드는 갈 곳이 없었다. Ready 상태인 노드가 없었으므로 다시 스케줄되지 못하고 Pending 상태에 머물렀다.\n\n실제로 망가진 것은 kubelet 프로세스와 API 사이의 통신뿐이었다. 인그레스에서 파드로 가는 경로는 살아 있었고 축출되지 않은 파드는 계속 실행 중이었다."},"updated_at":"2026-11-10T00:00:38.190958Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0038","org_slug":"oath"}