{"id":"IL-0031","org":"Zalando","date":"2018-12-05","docs":["https://github.com/zalando/skipper","https://github.com/zalando-incubator/kubernetes-on-aws"],"title":{"en":"Kubernetes API server OOM-killed, leaving the Skipper ingress without routes; the talk points to a misconfigured CronJob manifest","ko":"API 서버가 OOMKill로 죽자 Skipper 인그레스가 라우트를 잃었고, 고객 트래픽이 끊기며 IAM 서비스에서 404 오류가 났다"},"domain":["kubernetes","aws"],"impact":{"scope":"customer-facing traffic through the cluster ingress; IAM service returned 404","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"An AWS ALB routes traffic to all hosts when all hosts report unhealthy, so the ingress should stay healthy during API server problems. An ingress controller that reads routes from the API server should keep its last known set of routes. Use a quota for the number of pods.","ko":"ALB는 등록된 호스트가 모두 unhealthy를 보고하면 모든 호스트로 트래픽을 보낸다. 인그레스는 API 서버에 문제가 생겨도 healthy 상태를 유지해야 하고, 마지막으로 받은 라우트 목록을 계속 써야 한다. 파드 수에는 ResourceQuota로 상한을 걸어 둔다."},"related":[],"sources":[{"url":"https://www.slideshare.net/try_except_/running-kubernetes-in-production-a-million-ways-to-crash-your-cluster-devopscon-munich-2018","kind":"talk","license":"unknown","archived":null}],"summary":{"en":"This is the first of six incidents in Henning Jacobs' talk on running about 100 Kubernetes clusters at Zalando. A team's CronJob manifest, which the talk calls innocent, put concurrencyPolicy: Forbid, successfulJobsHistoryLimit and failedJobsHistoryLimit under the pod template's spec, where they do not belong. The fixed manifest moves those three fields to the CronJob spec, adds activeDeadlineSeconds: 120 to the job spec, and changes the schedule to \"7 8-18 * * Mon-Fri\". The talk shows this manifest after a slide on the number of pods in the cluster and the API server being OOM-killed.\n\nBecause Skipper takes its routes from the API server, the API server outage became an ingress outage: customer traffic failed and the IAM service returned 404. The lessons note that the AWS ALB in front routes traffic to all hosts when all of them report unhealthy. The talk's lessons are to keep the ingress healthy and serving its last known routes during API server problems, and to cap the number of pods with a ResourceQuota.","ko":"Henning Jacobs가 2018년 12월 DevOpsCon Munich에서 발표한 Zalando 장애 사례 가운데 첫 번째다. Zalando는 클러스터 100개와 AWS 계정 373개를 운영했다. 요청은 ALB를 거쳐 노드마다 떠 있는 Skipper 인그레스로 들어오고, Skipper는 API 서버에서 라우트 정보를 받아 앱 파드로 요청을 보낸다. 이 구조에서 API 서버가 OOMKill로 죽자 Skipper가 라우트를 유지하지 못했다. 고객 트래픽이 끊겼고 IAM 서비스에서 404 오류가 났다.\n\n발표는 원인으로 CronJob 매니페스트 하나를 보여 주었다. 원래 매니페스트에서는 동시 실행 정책과 이력 보존 개수 필드가 파드 템플릿 안에 있었다. 고친 매니페스트는 이 필드들을 CronJob spec으로 옮기고, Job에 activeDeadlineSeconds: 120을 넣고, 일정을 \"*/15 9-19 * * Mon-Fri\"에서 \"7 8-18 * * Mon-Fri\"로 바꿨다. 발표는 이 사고의 교훈으로, 모든 호스트가 unhealthy를 보고하면 ALB가 모든 호스트로 트래픽을 보낸다는 점을 꼽았다."},"trigger":{"en":"A CronJob (apiVersion batch/v2alpha1, schedule \"*/15 9-19 * * Mon-Fri\") whose concurrencyPolicy: Forbid and job history limits sat inside the pod template spec instead of the CronJob spec. The talk shows it right after the slides on the number of pods and the API server being OOM-killed, and the lessons add a quota on the number of pods.","ko":"발표는 문제의 출발점으로 CronJob 매니페스트 하나를 보여 주었다. 이 매니페스트에는 concurrencyPolicy, successfulJobsHistoryLimit, failedJobsHistoryLimit 필드가 CronJob spec이 아니라 파드 템플릿 spec 안에 들어 있었다. 실행 일정은 평일 9시부터 19시까지 15분마다였다."},"severity":"unknown","symptoms":["high-error-rate","control-plane-unresponsive","oom-killed"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":null,"components":["kube-apiserver","ingress"],"root_cause":"capacity","remediation":[{"text":{"en":"fixed the CronJob manifest: moved concurrencyPolicy: Forbid and the job history limits to the CronJob spec, added activeDeadlineSeconds: 120 and changed the schedule to \"7 8-18 * * Mon-Fri\"","ko":"CronJob 매니페스트를 고쳤다. concurrencyPolicy와 이력 보존 개수 필드를 CronJob spec으로 옮기고, Job에 activeDeadlineSeconds: 120을 넣고, 실행 일정을 바꿨다."}}],"action_items":[{"text":{"en":"Make the ingress stay healthy during API server problems","ko":"API 서버에 문제가 생겨도 인그레스가 healthy 상태를 유지하도록 고친다."}},{"text":{"en":"Make the ingress keep the last known set of routes","ko":"API 서버가 응답하지 않아도 인그레스가 마지막으로 받은 라우트 목록을 계속 쓰도록 고친다."}},{"text":{"en":"Use a ResourceQuota on the number of pods (the example sets pods: \"1500\")","ko":"ResourceQuota로 파드 수 상한을 1500개로 둔다."}}],"contributing":["single-point-of-failure","blast-radius-too-wide"],"recurrence_of":null,"symptoms_text":{"en":"Customers were affected, and Zalando's IAM service answered requests with HTTP 404. The talk shows this on slides titled \"Customer impact\" and \"IAM returning 404\", followed by a slide on the number of pods in the cluster.\n\nThe Kubernetes API server had been OOM-killed. Traffic reached applications through an AWS ALB and then Skipper, the HTTP router that Zalando runs as its ingress controller on the nodes, and Skipper loads its routes from the API server. With the API server gone, Skipper lost its source of routes.\n\nThe lessons slide adds that when every host behind an AWS ALB reports unhealthy, the ALB sends traffic to all hosts anyway, and the ingress did not stay healthy or keep its last known routes while the API server was down.","ko":"고객에게 영향이 간 장애였다. IAM 서비스에서 404 오류가 나기 시작했다.\n\n발표는 이어서 클러스터의 파드 수를 다룬 슬라이드를 보여 주었다.\n\n그다음 API 서버가 OOMKill로 죽었다. Zalando의 요청은 ALB를 지나 각 노드의 Skipper 인그레스로 들어오고, Skipper는 API 서버에서 받은 라우트로 앱 파드에 요청을 보낸다. API 서버가 죽자 Skipper가 라우트를 받을 곳이 사라졌고, 클러스터 인그레스를 지나는 고객 트래픽이 영향을 받았다."},"updated_at":"2026-11-10T00:00:37.960711Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0031","org_slug":"zalando"}