{"id":"IL-0054","org":"DevOps Hof","date":"2019-02-01","docs":["https://github.com/kubernetes/kubernetes/blob/1c557b9ce866d67ec6088f37058e8594b89606ee/pkg/cloudprovider/providers/gce/gce_loadbalancer_external.go#L209","https://github.com/kubernetes/kubernetes/blob/139a13d312b1b11de7f3af63d9503365ff3a1e6a/pkg/proxy/healthcheck/healthcheck.go#L201","https://github.com/kubernetes/kubernetes/blob/139a13d312b1b11de7f3af63d9503365ff3a1e6a/pkg/proxy/healthcheck/healthcheck.go#L212","https://github.com/kubernetes/kubernetes/blob/00eab3c40bcda9ee73eac060242d36602e627fec/pkg/controller/service/service_controller.go#L588","https://kubernetes.io/docs/concepts/services-networking/service/#proxy-mode-iptables","https://kubernetes.io/docs/tasks/administer-cluster/safely-drain-node/#draining-multiple-nodes-in-parallel"],"title":{"en":"All ingress traffic lost while draining a GKE node pool because the load balancer only targeted new nodes without ingress-nginx pods","ko":"GKE 에서 옛 노드 풀을 드레인하자 로드밸런서가 ingress-nginx 파드가 없는 새 노드만 가리켜 클러스터의 모든 인그레스 트래픽이 끊겼다"},"domain":["kubernetes","gcp","networking"],"impact":{"scope":"all services exposed through ingress-nginx on the cluster","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"With externalTrafficPolicy: Local, the load balancer can only reach nodes that run an ingress pod, and cordoned nodes leave the load balancer backend immediately; if the ingress pods stay behind on cordoned nodes, all ingress traffic stops. Before any change that might touch traffic, look twice into the documentation.","ko":"트래픽에 영향을 줄 수 있는 변경을 하기 전에는 문서를 두 번 확인한다.\n\nexternalTrafficPolicy 가 Local 이면 kube-proxy 는 다른 노드로 트래픽을 넘기지 않고, 로드밸런서 헬스 체크는 엔드포인트가 없는 노드에서 실패한다. cordon 한 노드는 로드밸런서 백엔드에서 빠진다. 이 둘이 겹치면 인그레스 컨트롤러 파드가 cordon 한 노드에만 남아 있을 때 트래픽이 모두 끊긴다."},"related":[],"sources":[{"url":"https://web.archive.org/web/20191023033209/https://www.devops-hof.de/kubernetes-load-balancer-konfiguration-beware-when-draining-nodes/","kind":"blog","license":"unknown","archived":"https://web.archive.org/web/20191023033209/https://www.devops-hof.de/kubernetes-load-balancer-konfiguration-beware-when-draining-nodes/"}],"summary":{"en":"Three Kubernetes behaviours combined. First, the ingress-nginx manifest for GCP sets externalTrafficPolicy: Local on its Service by default, which keeps the client source IP but means traffic is only delivered to ingress-nginx pods on the node that received it, never forwarded to another node. Second, with that policy Kubernetes creates a GCP HTTP health check on the healthCheckNodePort's /healthz, which kube-proxy deliberately fails on every node that has no local ingress-nginx pod. Third, the Kubernetes service controller only puts nodes that are Ready and schedulable into the load balancer backend, so a cordoned node is removed as soon as the drain starts.\n\nDuring the node pool migration the old nodes were cordoned and dropped out of the backend, but the ingress-nginx pods stayed on them because pod disruption budgets blocked their eviction. The backend now held only new nodes, all failing the health check because no ingress-nginx pod ran there, so every ingress on the cluster stopped receiving traffic. Deleting the leftover nginx-ingress-controller pods on the old nodes let them be scheduled on the new nodes and restored traffic.","ko":"DevOps Hof 팀은 GKE 클러스터에 새 노드 풀을 만들고 옛 노드 풀의 워크로드를 옮기려고 옛 노드를 드레인했다. 드레인 도중 ingress-nginx 로 노출된 모든 서비스에 접속할 수 없게 됐다. 세 가지 동작이 겹친 결과였다.\n\n첫째, GCP 용 ingress-nginx 배포 매니페스트는 기본으로 service.spec.externalTrafficPolicy 를 Local 로 둔다. 요청의 원래 클라이언트 IP 를 지키려는 설정으로, 트래픽을 받은 노드에 있는 엔드포인트로만 요청을 보낸다. 둘째, 쿠버네티스는 GCP 로드밸런서에 healthCheckNodePort 의 /healthz 를 호출하는 HTTP 헬스 체크를 만든다. externalTrafficPolicy 설정 때문에 이 헬스 체크는 서비스 엔드포인트가 없는 노드에서 일부러 실패한다. 여기서 서비스 엔드포인트는 ingress-nginx 파드다. 셋째, 쿠버네티스 서비스 컨트롤러는 Ready 이면서 스케줄 가능한 노드만 로드밸런서 백엔드에 넣는다. 드레인하려고 cordon 한 노드는 스케줄할 수 없으므로 백엔드에서 빠진다. 글쓴이는 이 동작이 마스터 서버를 로드밸런서에서 빼려는 것으로 이해했지만, cordon 한 일반 워커 노드에도 똑같이 적용된다.\n\n그 결과 로드밸런서는 ingress-nginx 파드가 아직 없는 새 노드만 가리켰고, 그 노드들의 헬스 체크는 모두 실패했다. ingress-nginx 파드는 Pod Disruption Budget 문제로 옛 노드에서 축출되지 않은 채 남아 있었다. 이 클러스터의 인그레스 트래픽이 사실상 모두 끊겼다.\n\n팀은 옛 노드에 남아 있던 nginx-ingress-controller 파드를 지웠다. 파드는 새로 Ready 상태가 된 노드에 다시 스케줄되었고 문제가 풀렸다."},"trigger":{"en":"Cordoning and draining the old node pool removed those nodes from the GCP load balancer backend while the ingress-nginx pods were still running on them.","ko":"새 노드 풀로 워크로드를 옮기려고 옛 노드를 cordon 하고 드레인했다."},"severity":"unknown","symptoms":["service-unavailable","readiness-probe-failures"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":{"en":"The team noticed during the drain that no service in the cluster was reachable through ingress-nginx.","ko":"옛 노드를 드레인하던 중 클러스터의 어떤 서비스에도 ingress-nginx 를 거쳐 접속할 수 없는 것을 팀이 알아챘다."},"components":["ingress","kube-proxy","node-pool"],"root_cause":"human-operation","remediation":[{"text":{"en":"deleted the nginx-ingress-controller pods still running on the old nodes so they were rescheduled on the new, ready nodes","ko":"옛 노드에 남아 있던 nginx-ingress-controller 파드를 지워 새로 Ready 상태가 된 노드에 다시 스케줄되게 했다."}}],"action_items":[],"contributing":["default-config-unchanged","assumption-unverified"],"recurrence_of":null,"symptoms_text":{"en":"While the old nodes of a GKE cluster were being drained to move workloads to a newly created node pool, none of the services in the cluster could be reached through ingress-nginx any more.\n\nThe LoadBalancer service of ingress-nginx had changed the backend pool of the GCP load balancer so that it pointed only at the new nodes. The nginx-ingress-controller pods, however, were still running on the old nodes, because problems with pod disruption budgets had kept them from being evicted. The load balancer health checks against the new nodes all failed.\n\nThe team first expected kube-proxy to forward the traffic to the right node, since it normally sends non-local traffic on to wherever the pods run, so the question was why that did not happen here.","ko":"GKE 클러스터에서 워크로드를 새 노드 풀로 옮기려고 옛 노드들을 드레인하던 중, 클러스터의 어떤 서비스에도 ingress-nginx 를 거쳐 접속할 수 없게 됐다.\n\n팀은 ingress-nginx 의 LoadBalancer 서비스와 nginx-ingress-controller 파드 엔드포인트를 확인했다. LoadBalancer 서비스는 GCP 로드밸런서의 백엔드 풀 설정을 바꿔 새 노드만 가리키고 옛 노드는 더 이상 가리키지 않았다. 반면 nginx-ingress-controller 파드는 여전히 옛 노드에서 돌고 있었다. Pod Disruption Budget 문제로 이 파드들이 옛 노드에서 축출되지 않았다.\n\n로드밸런서의 헬스 체크는 새 노드만 가리켰고, 새 노드에는 아직 ingress-nginx 파드가 없어서 헬스 체크가 모두 실패했다. 옛 노드로는 트래픽이 가지 않았다.\n\n처음에 팀은 이것이 왜 문제가 되는지 의아했다. 보통은 트래픽이 어느 노드로 들어오든 kube-proxy 가 다른 노드에 있는 엔드포인트로 알아서 넘겨 주기 때문이다."},"updated_at":"2026-11-08T19:10:30.677380Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0054","org_slug":"devops-hof"}