{"id":"IL-0188","org":"Yonghyun Kim","date":"2025-11-09","docs":[],"title":{"en":"Rolling updates on EKS returned 502 errors for about 30 seconds because Kubernetes deleted old pods as soon as new pods passed readiness, before the ALB had marked the new pod targets healthy","ko":"EKS 롤링 업데이트 때 Kubernetes 는 새 파드가 Ready 가 되자마자 옛 파드를 지웠지만 ALB 는 새 파드를 아직 healthy 로 보지 않아, 배포마다 약 30초 동안 502 가 났다."},"domain":["kubernetes","aws"],"impact":{"scope":"Microservices on Amazon EKS behind an ALB, during every rolling update","users":"About 50 to 100 users per deployment","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"A pod being Ready in Kubernetes does not mean the load balancer will send it traffic. Without a readiness gate, a rolling update can delete the old pods while the ALB still considers the new targets unhealthy, and dashboards that track pod status stay green during the errors.\n\nMaking pods wait longer on shutdown reduced the errors but did not fix the start-up gap and made every deployment much slower; the readiness gate ties the pod's Ready state to the ALB's view of the target.","ko":"Kubernetes 의 Ready 와 ALB 의 healthy 는 서로 다른 시점에 정해지므로, 둘을 잇는 장치가 없으면 롤링 업데이트 때 502 가 날 수 있다. 파드 상태와 대시보드가 모두 정상이어도 사용자는 오류를 볼 수 있다. terminationGracePeriodSeconds 를 늘려 시간을 버는 방식은 배포만 느리게 하고 새 파드가 뜰 때의 공백은 막지 못한다. ReadinessGate 는 ALB 대상 유형이 ip 일 때만 의미가 있고, instance 모드에서는 노드가 대상이라 쓸 수 없다. ReadinessGate 를 쓰면 파드가 Ready 가 되기까지 30초에서 40초가 더 걸리므로 급한 스케일 아웃에 대비해 레플리카나 HPA 를 넉넉히 잡는다. 웹훅의 failurePolicy 를 Ignore 로 두면 컨트롤러가 죽어도 배포가 막히지 않는다."},"related":[],"sources":[{"url":"https://medium.com/@baramboys0615/eks-%EB%B0%B0%ED%8F%AC%EB%8A%94-%EC%B4%88%EB%A1%9D%EB%B6%88%EC%9D%B8%EB%8D%B0-%EA%B3%A0%EA%B0%9D%EC%9D%80-%EC%99%9C-502%EB%A5%BC-%EB%A7%9E%EB%8A%94%EA%B0%80-kubernetes%EC%99%80-aws%EC%9D%98-%EC%8B%9C%EA%B0%84%EC%B0%A8%EA%B0%80-%EB%A7%8C%EB%93%A0-30%EC%B4%88%EC%9D%98-%EC%A7%80%EC%98%A5-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EA%B7%B8-%EC%99%84%EB%B2%BD%ED%95%9C-%ED%95%B4%EA%B2%B0%EB%B2%95-83a486a7aac4","kind":"blog","license":"unknown","archived":null}],"summary":{"en":"Kubernetes treats a pod as Ready once its containers run and its readinessProbe succeeds; it then adds the pod to the EndpointSlice and, during a rolling update, starts deleting an old pod. The ALB works on its own clock: the AWS Load Balancer Controller registers the new pod IP as a target in the initial state, and the ALB only marks it healthy after two consecutive successful health checks, at least 30 to 40 seconds later. In the meantime the old pod was already terminating and draining, the new target was not yet healthy, and the ALB had nowhere to send requests, so users got 502s for about 28 to 32 seconds.\n\nThe author solved it with pod readiness gates. Labelling the namespace with elbv2.k8s.aws/pod-readiness-gate-inject=enabled makes the AWS Load Balancer Controller's mutating webhook add a readiness gate to new pods; the pod stays not Ready (READINESS GATES 0/1, condition message \"Target is in 'initial' state\") until the controller sees the ALB target as healthy, and only then does Kubernetes delete the old pod. After the change, 10 out of 10 deployments ran without errors, and the Datadog monitor \"ALB 5XX Error Rate > 5%\" did not trigger for 30 days. Finding this took three days of debugging, and the incident was about two years before the post.\n\nFor the shutdown side the author also recommends a preStop sleep of 15 seconds with terminationGracePeriodSeconds of 30, lowering the target group deregistration_delay from its 300-second default to 30 seconds, and a 15-second ALB health check interval with a healthy threshold of 2. Readiness gates work only with target-type ip, make a pod take about 45 seconds instead of 10 to become Ready, and the author suggests setting the webhook failurePolicy to Ignore so a controller outage does not block deployments.","ko":"작성자의 팀은 EKS 에서 ALB 를 앞에 두고 Service 와 파드를 뒤에 둔 마이크로서비스를 운영했다. 롤링 업데이트를 할 때마다 배포는 성공으로 끝났지만 사용자는 502 를 받았다.\n\n원인은 Kubernetes 와 ALB 가 준비 상태를 판단하는 시점이 다르다는 데 있었다. Kubernetes 는 컨테이너가 실행 중이고 readinessProbe 가 성공하면 파드를 Ready 로 보고 EndpointSlice 에 넣은 뒤 옛 파드를 지운다. ALB 는 새 파드 IP 를 대상 그룹에 등록한 뒤 initial 상태로 두고, 헬스 체크를 두 번 연속 통과해야 healthy 로 바꿔 트래픽을 보낸다. 이 과정에 최소 30초에서 40초가 걸린다. 그 사이 옛 파드는 이미 사라지거나 draining 중이고 새 파드는 아직 healthy 가 아니어서, ALB 가 트래픽을 보낼 곳이 없었다.\n\n작성자는 파드가 종료될 때도 502 가 날 수 있다고 설명한다. 파드에 SIGTERM 이 가는 시점과 EndpointSlice 에서 빠지는 시점이 같은데, ALB 가 대상을 빼기까지는 몇 초가 더 걸려 그동안 종료 중인 파드로 트래픽이 갈 수 있다.\n\n작성자는 사흘 동안 디버깅한 끝에 네임스페이스에 elbv2.k8s.aws/pod-readiness-gate-inject=enabled 레이블을 붙였다. AWS Load Balancer Controller 의 웹훅이 파드에 ReadinessGate 를 넣고, 파드는 ALB 대상이 healthy 가 된 뒤에야 Ready 가 됐다. 적용 뒤 배포 10번 동안 502 는 한 번도 나지 않았다. 대신 파드가 Ready 가 되기까지 10초에서 45초로 늘었다. 작성자는 종료 쪽을 다듬는 설정으로 preStop 의 sleep 15 와 terminationGracePeriodSeconds 30, 대상 그룹 deregistration_delay 30초, ALB 헬스 체크 주기 15초를 함께 권한다."},"trigger":{"en":"A rolling update of a Deployment served through an ALB that registers pod IPs as targets, with no pod readiness gates.","ko":"롤링 업데이트에서 새 파드가 readinessProbe 를 통과해 Ready 가 되자 Kubernetes 가 곧바로 옛 파드를 지웠다. 그때 ALB 는 새 파드 IP 를 대상 그룹에 막 등록해 initial 상태로 헬스 체크를 하고 있었다."},"severity":"unknown","symptoms":["high-error-rate","service-unavailable"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":{"en":"A Slack message at 3 a.m. reported that the deployment had succeeded but the service was down; Datadog dashboards were green at the time.","ko":"새벽 3시에 \"배포는 성공했는데, 서비스는 죽었다\"는 Slack 메시지로 알았다. 파드 상태와 Datadog 대시보드는 모두 정상이었고, 장애는 사용자가 보는 화면에만 나타났다."},"components":["cloud-load-balancer","ingress","pod-termination"],"root_cause":"deploy-rollout","remediation":[{"text":{"en":"labelled the namespace with elbv2.k8s.aws/pod-readiness-gate-inject=enabled so the AWS Load Balancer Controller injects pod readiness gates and Kubernetes waits for the ALB target to be healthy before deleting old pods","ko":"네임스페이스에 elbv2.k8s.aws/pod-readiness-gate-inject=enabled 레이블을 붙였다. AWS Load Balancer Controller 의 웹훅이 파드에 ReadinessGate 를 넣었다. 새 파드는 ALB 대상이 healthy 가 된 뒤에야 Ready 가 됐고, 그제야 옛 파드가 지워졌다."}}],"action_items":[],"contributing":["assumption-unverified","default-config-unchanged","monitoring-blind-spot"],"recurrence_of":null,"symptoms_text":{"en":"Every rolling update of the team's microservices on Amazon EKS, behind an Application Load Balancer, was followed by intermittent 502 and 503 errors for users. A Slack message at 3 a.m. read \"배포는 성공했는데, 서비스는 죽었다\" (the deployment succeeded, but the service died). Every pod was Ready and Datadog was green while users saw errors.\n\nUsers received \"HTTP/1.1 502 Bad Gateway\" pages. In Datadog the author saw lines such as \"[02:34:11] ALB HealthCheck: 10.0.1.50:8080 → initial\" followed by \"Request → 502 Bad Gateway (no healthy targets)\" at 02:34:11, 02:34:15 and 02:34:23, until \"[02:34:41] ALB HealthCheck: 10.0.1.50:8080 → healthy\" and a 200 OK at 02:34:42. The service was down for about 30 seconds per deploy.\n\nOver 10 deployments, all 10 produced errors, lasting 28 seconds on average, with an average of 156 502 errors and about 50 to 100 affected users per deployment, and about three night-time pages a month.\n\nThe first attempt was to raise terminationGracePeriodSeconds to 300. Errors went down, but a rolling update of three replicas then took about 15 minutes, the application was simply killed with SIGKILL after waiting instead of shutting down cleanly, and the gap when a new pod starts was not addressed at all.","ko":"새벽 3시에 \"배포는 성공했는데, 서비스는 죽었다\"는 Slack 메시지가 왔다. 모든 파드는 Ready 였고 Datadog 도 초록불이었지만, 사용자는 계속 오류 화면을 보고 있었다.\n\n롤링 업데이트 중에 새 파드가 Running 과 Ready 가 되고 옛 파드가 Terminating 으로 바뀌는 바로 그때, 사용자는 HTTP/1.1 502 Bad Gateway 를 받았다. Datadog 로그에는 [02:34:11] ALB HealthCheck: 10.0.1.50:8080 → initial 다음에 Request → 502 Bad Gateway (no healthy targets) 가 이어졌고, [02:34:41] 에 대상이 healthy 가 된 뒤에야 200 OK 가 돌아왔다.\n\n이 현상은 롤링 업데이트마다 났다. 배포 10번 중 10번 모두 오류가 났다. 오류는 평균 28초 이어졌고, 배포 한 번에 502 가 평균 156개 났으며, 영향받은 사용자는 약 50명에서 100명이었다. 새벽 호출도 한 달에 3번 있었다. 서비스는 EKS 에서 ALB 뒤에 Service 와 파드를 둔 구조였다.\n\n처음에는 terminationGracePeriodSeconds 를 300초로 늘려 보았다. 오류는 줄었지만 레플리카 3개 배포가 15분으로 늘었고, 애플리케이션은 정상 종료되지 않고 기다리다 SIGKILL 로 죽었다. 무엇보다 새 파드가 뜰 때 생기는 공백은 그대로 남았다."},"updated_at":"2026-11-09T11:55:57.763176Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0188","org_slug":"yonghyun-kim"}