IL-0442 LIFULL · 2021-09-22 (published)kubernetesaws
Older pods on LIFULL's KEEL Kubernetes cluster were preempted all at once and the external error rate spiked
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.
A 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.
KEEL 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.
- Severity
- –
- 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 affected
- –
- Region
- –
- Time to detect
- –
- Time to resolve
- –
Symptoms
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.
The 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.
Almost 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.
Symptom Pod evictionHigh error rate
Root cause
Root cause Configuration change
- Trigger
- 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.
Detection
External monitoring sent the team a large number of alerts about a sharp rise in the error rate of requests from outside the cluster.
Remediation
–
Action items
- Redefine the PriorityClasses as low (-10), medium (0, globalDefault) and high (10), each with a non-preempting variant
Lessons
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.
Sources
Related cases
IL-0513PyPI backends on Kubernetes were unreachable over HTTPS for 20 minutes when the Ingress ELB lost its HTTPS listenerPython Software Foundation · 2022same root cause · shared Ingress controller
IL-0177WebSocket connections to LINE's messaging-hub on Kubernetes dropped all at once whenever another service's change reloaded a shared HAProxy L7 load balancer, and the reconnect wave overloaded a token API and filled nf_conntrack on NginxLINE Plus · 2022same root cause · shared Ingress controller
IL-0111Switching the main Kubernetes ingress controller from Traefik v1 to v2 stopped X-Forwarded headers being passed, which broke the Okta login flow for every service using Okta and stopped DFDS online bookings for over an hourDFDS · 2021same root cause · shared Ingress controller
IL-0004A new default pod priority let a new Cortex cluster preempt every production ingester in a cascade, taking Grafana Cloud Hosted Prometheus downGrafana Labs · 2019same root cause · shared Pod priority and preemption
Check your cluster for this
For AI tools and scripts
- This case as JSON: https://incidentlib-api.benchlet.com/cases/IL-0442
- The cases most like it: https://incidentlib-api.benchlet.com/cases/IL-0442/similar
- MCP server (Streamable HTTP, no key):
https://incidentlib-mcp.benchlet.com/mcp
The API needs no key. The MCP server offers search_incidents, get_incident, list_incidents, get_topic and vocab; both are explained on API and MCP.