{"id":"IL-0442","org":"LIFULL","date":"2021-09-22","docs":[],"title":{"en":"Older pods on LIFULL's KEEL Kubernetes cluster were preempted all at once and the external error rate spiked","ko":"운영 중인 KEEL 쿠버네티스 클러스터에서 priority 0 파드가 한꺼번에 선점되어 istio-ingressgateway 를 비롯한 파드가 재시작되고 서비스가 잠시 끊겼다"},"domain":["kubernetes","aws"],"impact":{"scope":"Requests from outside the KEEL cluster failed at a sharply higher rate while istio-ingressgateway and many other pods restarted; services were briefly down","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"Adding PriorityClasses to a running cluster needs care because every existing pod has priority 0, and a globalDefault class above 0 makes all pods created before it candidates for preemption. PodDisruptionBudgets do not reliably protect pods from preemption. In a multi-tenant cluster, the permission to deploy PriorityClasses should be restricted, and the PriorityClasses tenants may use should be controlled with ResourceQuota or OPA Gatekeeper.","ko":"운영 중인 클러스터에 PriorityClass 를 설정할 때는 조심해야 한다. 모든 파드의 기본 priority 는 0 이어서 PriorityClass 설정이 의도하지 않은 선점을 일으킬 수 있다. globalDefault: true 인 PriorityClass 의 값이 0 을 넘으면 도입 전의 모든 파드가 선점 대상이 된다. 멀티 테넌시 클러스터라면 사용자가 PriorityClass 를 마음대로 배포하지 못하게 권한을 좁히고, ResourceQuota 나 OPA Gatekeeper 로 쓸 수 있는 PriorityClass 를 세밀하게 통제해야 한다."},"related":[],"sources":[{"url":"https://www.lifull.blog/entry/2021/09/22/114924","kind":"blog","archived":"https://web.archive.org/web/20260209114815/https://www.lifull.blog/entry/2021/09/22/114924"}],"summary":{"en":"KEEL runs most of its pods on AWS spot instances, and the team had added a spot fleet, a fallback for when spot instances are sold out and safe termination for pods on spot instances. Scheduling waits were still expected, so the team introduced PriorityClasses to order pods: low (value: 100) for pods that may be preempted, medium (globalDefault: true, value: 1000) for standard pods and high (value: 10000) for important pods. Preemption is an ordinary pod termination run by kube-scheduler: preStop, then SIGTERM, then SIGKILL if the pod has not stopped after terminationGracePeriodSeconds.\n\nA pod's priority defaults to 0 and is set by the admission controller when the pod is created, so every pod deployed before the PriorityClasses kept priority: 0. With medium as the global default, newly deployed pods got priority 1000, and when they landed on nodes without enough free resources the scheduler preempted the older priority 0 pods all at once, despite their PodDisruptionBudgets.\n\nKEEL then settled on PriorityClasses low (value: -10), medium (value: 0, globalDefault: true) and high (value: 10), each with a -nonpreempting variant that sets preemptionPolicy: Never. Because the global default has value 0, adding PriorityClasses later cannot preempt existing pods. The lower bound is -10 because cluster-autoscaler ignores pods whose priority is below --expendable-pods-priority-cutoff, whose default is -10. Two weeks before this failure the team had shared a Grafana Labs postmortem of a similar failure, and the engineer who shared it was the one who introduced the PriorityClasses.","ko":"LIFULL 의 KEEL 팀은 쿠버네티스 클러스터 KEEL 의 파드 대부분을 AWS 스폿 인스턴스에서 돌리려고 여러 가지를 만들어 왔다. 스폿 플릿을 도입했고, 스폿 인스턴스 용량이 바닥났을 때의 폴백 기능과 스폿 인스턴스 위 파드를 안전하게 종료하는 처리를 개발했다. 스케줄링 대기를 줄이려고 팀은 PriorityClass 세 개를 만들어 적용했다. 선점해도 되는 파드에 붙일 value: 100 인 low, 표준 파드에 붙일 globalDefault: true 이고 value: 1000 인 medium, 우선순위가 높은 파드에 붙일 value: 10000 인 high 였다.\n\n파드의 priority 는 Admission Controller 가 파드를 배포할 때 넣는다. 그래서 PriorityClass 를 도입하기 전에 배포된 파드는 모두 priority: 0 으로 남아 있었다. globalDefault: true 때문에 새로 배포되는 파드는 priority: 1000 을 받았다. 리소스가 모자란 노드에 이런 파드가 배포되자 kube-scheduler 가 기존 파드를 한꺼번에 선점했다. 선점은 kube-scheduler 가 파드를 종료하는 것이고, 보통의 종료 순서를 그대로 따른다. preStop 을 실행하고 SIGTERM 을 보낸 뒤, terminationGracePeriodSeconds 가 지나도 끝나지 않으면 SIGKILL 을 보낸다. istio-ingressgateway 를 비롯한 많은 파드가 재시작했고, 리버스 프록시 대수가 줄어 클러스터 밖에서 들어오는 요청의 오류율이 급격히 올랐다.\n\nKEEL 의 파드에는 거의 모두 PodDisruptionBudget 이 있었지만 선점을 막지 못했다. 쿠버네티스 문서에 따르면 선점은 PodDisruptionBudget 을 되도록 지키려 할 뿐이고, 지킬 수 있는 희생 파드를 찾지 못하면 PodDisruptionBudget 을 어기고도 낮은 우선순위 파드를 내린다. 팀은 globalDefault: true 인 PriorityClass 의 value 가 0 이 되도록 PriorityClass 정의를 바꿨다. 글쓴이는 이 PriorityClass 도입 작업을 맡은 사람이었고, 장애 2주 전에 같은 종류의 장애를 다룬 Grafana 의 포스트모템을 팀에 공유했었다."},"trigger":{"en":"The KEEL team deployed three PriorityClasses to the running cluster: low with value: 100, medium with globalDefault: true and value: 1000, and high with value: 10000.","ko":"운영 중인 KEEL 클러스터에 globalDefault: true 이고 value: 1000 인 medium PriorityClass 를 배포했다."},"severity":"unknown","symptoms":["pods-evicted","high-error-rate"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":{"en":"External monitoring sent the team a large number of alerts about a sharp rise in the error rate of requests from outside the cluster.","ko":"외부 모니터링에서 클러스터 밖 요청의 오류율이 급격히 올랐다는 알림이 팀에 대량으로 왔다."},"components":["pod-priority","ingress"],"root_cause":"config-change","remediation":[],"action_items":[{"text":{"en":"Redefine the PriorityClasses as low (-10), medium (0, globalDefault) and high (10), each with a non-preempting variant","ko":"PriorityClass 를 여섯 개로 다시 정의했다. low 와 low-nonpreempting 은 value: -10, medium 과 medium-nonpreempting 은 value: 0, high 와 high-nonpreempting 은 value: 10 이고, nonpreempting 쪽에는 preemptionPolicy: Never 를 뒀다. globalDefault: true 는 value 가 0 인 medium 에 두어, 나중에 PriorityClass 를 설정해도 기존 파드가 선점되지 않게 했다. cluster-autoscaler 는 priority 가 --expendable-pods-priority-cutoff 기본값보다 낮은 파드를 무시하므로, 하한을 -10 으로 정했다."}}],"contributing":["assumption-unverified"],"recurrence_of":null,"symptoms_text":{"en":"Shortly after the KEEL team applied new PriorityClasses to KEEL, LIFULL's Kubernetes cluster, and saw pods with the low class being preempted as intended, the team received a flood of alerts from external monitoring: the error rate of requests coming from outside the cluster had risen sharply.\n\nThe investigation found that many pods, starting with istio-ingressgateway, the reverse proxy that accepts requests from outside the cluster, showed signs of having restarted. Requests inside the cluster were retried by Istio, the service mesh KEEL uses, so they were barely affected, but with fewer istio-ingressgateway pods the error rate of external requests jumped.\n\nAlmost every pod on KEEL had a PodDisruptionBudget, which the team expected to prevent this kind of mass disruption. It did not, because the scheduler respects PodDisruptionBudgets during preemption only on a best-effort basis and still preempts lower-priority pods when it finds no victims that keep their budgets.","ko":"새 PriorityClass 를 적용하고 low PriorityClass 파드의 선점을 확인한 직후 팀에 알림이 대량으로 왔다. 알림은 외부 모니터링에서 왔고, 클러스터 밖에서 들어오는 요청의 오류율이 급격히 올라가 있었다.\n\n조사해 보니 클러스터 밖의 요청을 받는 리버스 프록시 istio-ingressgateway 를 비롯해 많은 파드에 재시작한 흔적이 있었다.\n\n클러스터 안의 요청은 서비스 메시인 Istio 가 재시도해서 큰 영향이 없었다. 클러스터 밖의 요청은 리버스 프록시 대수가 줄어든 탓에 오류율이 급격히 올랐다.\n\nKEEL 의 파드에는 거의 모두 PodDisruptionBudget 이 설정되어 있었으므로, 팀은 파드가 한꺼번에 내려가는 일은 막혀 있다고 생각하고 있었다."},"updated_at":"2026-11-14T12:34:09.174425Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0442","org_slug":"lifull"}