{"id":"IL-0006","org":"Neon","date":"2025-05-16","docs":["https://github.com/aws/amazon-vpc-cni-k8s/blob/v1.18.6/docs/eni-and-ip-target.md","https://docs.aws.amazon.com/eks/latest/userguide/cni-increase-ip-addresses.html"],"title":{"en":"VPC subnets ran out of IP addresses because the AWS VPC CNI kept IPs on full nodes, and WARM_IP_TARGET changes then blocked pod starts after every pod deletion","ko":"AWS VPC CNI 가 자원이 찬 노드에 IP 를 붙잡아 두어 us-east-1 서브넷 IP 가 바닥났고, WARM_IP_TARGET 을 바꾼 조치가 파드 시작 실패를 더 오래 끌었다"},"domain":["kubernetes","aws","networking"],"impact":{"scope":"us-east-1 cluster: new compute pods could not start and idle databases could not be woken; on 19 May about 10% of pod starts failed for hours","users":"unknown","region":"us-east-1"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"With default settings AWS CNI can keep many IPs on nodes that cannot use them, so the share of subnet IPs assigned to pods does not tell how close the subnet is to running out. WARM_IP_TARGET and WARM_ENI_TARGET follow different logic, a warm IP target of zero switches to the ENI logic, and IPs in cooldown count toward the target, which behaves badly under high pod churn. Changing these settings during an incident without knowing that logic prolonged the outage, and reverting a setting did not bring back the earlier healthy state.","ko":"AWS CNI 기본 설정에서는 노드마다 적어도 ENI 한두 개 분량의 IP 가 더 잡히고, IP 는 ENI 가 통째로 비어야만 반납된다. 그래서 파드에 배정된 IP 수만 보고 서브넷 여유를 판단하면 안 된다. 이번에는 IP 의 71% 만 파드에 배정된 상태에서 서브넷이 바닥났다.\n\nWARM_IP_TARGET 은 지정하지 않을 때, 0 일 때, 1 일 때 동작이 서로 크게 다르다. cooldown 중인 IP 도 여유 IP 로 세기 때문에 WARM_IP_TARGET=1 은 파드가 지워질 때마다 그 노드의 파드 시작을 30초 동안 막을 수 있다. 문서를 잘못 읽고 바꾼 설정도, 원래대로 돌린 설정도 예상과 다르게 동작했고, 되돌리는 것만으로는 망가진 상태가 회복되지 않았다.\n\n서브넷 크기는 부하 시험에서 정한 상한인 1만 컴퓨트를 넘지 않는다는 가정에 맞춰 잡혀 있었다. 서비스가 예상보다 빨리 커지면서 이 가정이 맞지 않게 됐다."},"related":["IL-0007","IL-0015","IL-0017"],"sources":[{"url":"https://neon.com/blog/aws-cni-lessons-from-a-production-outage","kind":"blog","license":"unknown","archived":"http://web.archive.org/web/20260508092749/https://neon.com/blog/aws-cni-lessons-from-a-production-outage"}],"summary":{"en":"Neon's us-east-1 cluster usually peaks at about 6,000 running computes, starting about 500 pods a minute and terminating idle ones at a similar rate. As one of Neon's first regions, its three subnets were /20 blocks, 12,273 usable addresses in total, half the size used in other regions, on the assumption from load tests that a cluster would not go beyond 10k computes. Growth had been faster than expected. The cluster ran AWS VPC CNI v1.18.6 with the default settings: WARM_ENI_TARGET=1 and WARM_IP_TARGET unset. Nodes were m6id.metal instances with 49 pod IPs per ENI.\n\nWith these defaults, ipamd, the AWS CNI daemon on each node, keeps at least one ENI's worth of spare IPs and can only release IPs when a whole ENI has no pod on it. Pods are spread randomly over ENIs (ipamd keeps ENIs in a Go map, whose iteration order is random), and with a steady flow of new pods nearly every ENI keeps at least one pod, so IPs once allocated to a node are never returned. When the idle-termination job failed and computes jumped to about 8.1k, cluster-autoscaler added nodes, while older nodes that were full on CPU and memory held about 3.4k allocated IPs that no pod could use. The subnets ran out.\n\nTo free those IPs the team set WARM_IP_TARGET=1, having misread the documentation to mean that the unset default was 5. Thousands of IPs were released and pods started until about 10k concurrent pods, where limits known from load testing, such as kube-proxy sync latency, slowed starts. After the control-plane job was stabilised and about 7k idle computes were terminated, the rate of successful pod starts stayed far below normal. Neon later found the reason in the AWS CNI source: an IP is held in a 30-second cooldown after a pod is removed, and IPs in cooldown count toward the warm target, so WARM_IP_TARGET=1 can block all pod starts on a node for 30 seconds after each pod deletion. The control plane retried compute creation after its 2-minute timeout, so many pods that had started were deleted before the rest of setup finished, each deletion restarting the 30-second wait.\n\nAs a last attempt the team set WARM_IP_TARGET=0, MINIMUM_IP_TARGET=0 and WARM_ENI_TARGET=0. A warm IP target of zero is the same as not setting it, so ipamd went back to ENI-based logic, stopped returning IPs and allocated as many IPs as fit on an ENI at a time. Allocated IPs per node rose from about 75 to about 250, pod starts returned to normal, and the cluster stayed stable over the weekend.\n\nOn 19 May, unsure whether zero was a defined setting and suspecting the recovery on Friday had been a coincidence, the team set WARM_IP_TARGET=1 again, and pod starts broke the same way. Reverting to 0 did not recover the cluster: with all three targets at zero, ipamd only allocates more IPs when every IP on the node is assigned and no pod has been removed in the last 30 seconds, which never happened under constant pod churn. The team hid the errors from users by enlarging the pool of pre-created warm compute pods. Hours later the errors stopped when the control plane, slowed by an expensive query to its Postgres database, did not stop any pods for 86 seconds; with no IPs in cooldown, ipamd could allocate IPs across the cluster.","ko":"Neon 은 스토리지와 컴퓨트를 나눈 서버리스 Postgres 를 제공한다. 컴퓨트는 하나하나가 쿠버네티스 파드 하나 안에서 가벼운 VM 으로 돈다. us-east-1 클러스터는 하루 최대 약 6천 개의 컴퓨트를 돌리고, 분당 약 500개의 새 파드가 뜨며 비슷한 속도로 유휴 데이터베이스가 종료된다. 당시 AWS VPC CNI 는 v1.18.6 이었고 기본 설정인 WARM_ENI_TARGET=1 에 WARM_IP_TARGET 은 지정하지 않은 상태였다. 이 리전의 서브넷 세 개는 각각 /20 이라 전체 IP 가 12,273개였고, 다른 리전에는 24k 개가 있다.\n\n5월 16일 유휴 컴퓨트를 종료하는 주기 작업이 실패해 활성 컴퓨트가 약 8.1천 개로 늘었다. 기본 설정에서 VPC CNI 의 IP 관리 데몬인 ipamd 는 노드마다 ENI 하나 분량의 여유 IP 를 유지하려 하고, IP 는 ENI 단위로만 반납한다. m6id.metal 노드는 ENI 하나에 IPv4 49개를 쓴다. ipamd 는 ENI 를 Go 해시맵에 담아 두는데, Go 해시맵은 순회 순서가 무작위라서 파드도 여러 ENI 에 무작위로 배정된다. 파드가 계속 들어오는 환경에서는 ENI 마다 파드가 적어도 하나씩 남아, 한번 붙은 ENI 의 IP 가 서브넷으로 돌아가지 않았다. 그 결과 IP 약 3.4천 개가 CPU 와 메모리가 꽉 찬 노드에 묶였고, 서브넷이 바닥나 새 컴퓨트 파드를 시작할 수 없었다.\n\n팀은 노드에 묶인 IP 를 풀려고 WARM_IP_TARGET=1 을 설정했다. 문서를 잘못 읽어 기본값이 5 라고 믿었고, 이를 1 로 줄인다고 생각했다. 실제로는 이 값을 지정하지 않으면 동작이 전혀 다른 WARM_ENI_TARGET 로직을 쓴다. WARM_IP_TARGET=1 은 IP 수천 개를 서브넷으로 돌려보내 파드가 다시 뜨게 했다. 그러나 배정이 풀린 IP 는 30초 동안 cooldown 상태가 되어 다시 쓸 수 없고, cooldown 중인 IP 도 여유 IP 로 셈한다. 그래서 파드가 하나 지워질 때마다 그 노드에서 30초 동안 파드 시작이 막혔다. 2분 타임아웃 뒤 컴퓨트 생성을 재시도하는 컨트롤 플레인 동작이 문제를 더 키웠다. 마지막 수단으로 WARM_IP_TARGET, MINIMUM_IP_TARGET, WARM_ENI_TARGET 을 모두 0 으로 바꾸자 ipamd 가 WARM_ENI_TARGET 로직으로 돌아가 IP 를 ENI 하나에 들어가는 만큼 한꺼번에 할당했다. 노드당 평균 할당 IP 가 약 75개에서 약 250개로 늘었고 파드 시작 성공률이 정상으로 돌아왔다.\n\n5월 19일 팀은 문서만으로는 WARM_IP_TARGET=0 의 동작이 분명하지 않다고 보고 후속 조치로 WARM_IP_TARGET=1 을 다시 배포했다. 같은 파드 시작 실패가 바로 나타났고 0 으로 되돌렸지만 오류는 계속됐다. 세 값이 모두 0 이면 ipamd 는 노드의 모든 IP 가 파드에 배정되었고 지난 30초 동안 지워진 파드가 없을 때만 IP 를 더 할당한다. 파드 교체가 잦아 이 조건이 채워지지 않았다. 팀은 미리 만들어 두는 컴퓨트 풀을 늘려 사용자 영향을 줄였다. 몇 시간 뒤 컨트롤 플레인이 무거운 Postgres 쿼리 때문에 86초 동안 컴퓨트를 종료하지 못했다. 그동안 cooldown 중인 IP 가 사라져 클러스터 전체에서 IP 가 할당됐고 오류가 거의 0 으로 떨어졌다."},"trigger":{"en":"On 16 May the Neon control plane's periodic job that terminates idle computes started failing. Compute creations continued, and the number of active computes rose from about 5k to about 8.1k within minutes. On 19 May, a follow-up change that set WARM_IP_TARGET back to 1 started the second round of failures.","ko":"5월 16일 유휴 컴퓨트를 종료하는 Neon 컨트롤 플레인의 주기 작업이 실패했다. 종료는 멈췄는데 생성은 계속되어 활성 컴퓨트가 몇 분 만에 약 8천 개로 늘었다. 5월 19일에는 후속 조치로 WARM_IP_TARGET=1 을 다시 배포한 것이 계기가 되어 같은 장애가 다시 났다."},"severity":"unknown","symptoms":["no-available-ip","pod-pending"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"incident","detection":{"en":"As cluster-autoscaler added nodes, the team saw IP allocation errors on new pods that were scheduled but could not start. The rate of successful pod starts and VPC subnet metrics were then used to follow the problem.","ko":"노드가 추가되면서 새 파드가 스케줄링은 되지만 시작하지 못하고 IP 할당 오류를 내는 것이 보였다. 5월 19일에는 WARM_IP_TARGET=1 배포 직후 IP 배정 실패와 파드 시작 성공률 하락이 바로 나타났다."},"components":["vpc-cni","subnet","cluster-autoscaler"],"root_cause":"capacity","remediation":[{"text":{"en":"Set WARM_IP_TARGET=1 to release IPs held by nodes without free CPU or memory","ko":"5월 16일 노드에 묶인 IP 를 풀려고 WARM_IP_TARGET=1 을 설정했다."}},{"text":{"en":"Fixed the control plane's periodic job so idle computes were terminated again","ko":"컨트롤 플레인의 실패하던 주기 작업을 안정시켜 유휴 컴퓨트 약 7천 개를 종료했다."}},{"text":{"en":"Restarted the aws-node DaemonSet several times, each with a temporary improvement","ko":"aws-node DaemonSet 을 재시작해 ipamd 를 다시 초기화했고, 재시작할 때마다 잠시 나아졌다."}},{"text":{"en":"Set WARM_IP_TARGET=0, MINIMUM_IP_TARGET=0 and WARM_ENI_TARGET=0, which let nodes allocate enough IPs and stabilised the cluster","ko":"WARM_IP_TARGET, MINIMUM_IP_TARGET, WARM_ENI_TARGET 을 모두 0 으로 바꿔 파드 시작 성공률을 정상으로 되돌렸다."}},{"text":{"en":"On 19 May, set WARM_IP_TARGET back to 0 after setting it to 1 failed again","ko":"5월 19일 WARM_IP_TARGET=1 을 다시 배포했다가 같은 증상이 나오자 0 으로 되돌렸다."}},{"text":{"en":"Enlarged the pool of pre-created warm compute pods to absorb failed pod starts","ko":"미리 만들어 두는 컴퓨트 풀을 늘려 약 10% 의 파드 시작 실패를 메우고 사용자 영향을 줄였다."}}],"action_items":[{"text":{"en":"Open a pull request to AWS CNI to improve how ipamd releases IPs in this situation","ko":"ipamd 의 동작을 개선하는 PR 을 AWS CNI 에 올렸다."}},{"text":{"en":"Follow up with AWS Support about the roughly 100 subnet IPs that were never allocated, for reasons Neon could not explain","ko":"서브넷에서 IP 약 100개가 왜 할당되지 않고 남았는지 AWS Support 와 확인한다."}},{"text":{"en":"Change the architecture to support horizontal scaling","ko":"수평 확장을 지원하도록 아키텍처를 바꾼다."}}],"contributing":["default-config-unchanged","no-capacity-planning","assumption-unverified","manual-action-worsened","revert-did-not-restore","retry-storm"],"recurrence_of":null,"symptoms_text":{"en":"On 16 May 2025, in Neon's Kubernetes cluster in AWS us-east-1, new compute pods were scheduled but could not start and failed with IP allocation errors. Each Neon compute is a Postgres database running in a lightweight virtual machine in its own pod, so without new pods, idle customer databases could not be woken up.\n\nThe numbers were confusing at the time. About 8.8k IP addresses (71%) were assigned to pods, yet about 12,200 of the 12,273 addresses in the cluster's three VPC subnets (99%) were allocated to nodes. The subnets seemed to have unassigned IPs to spare, but the new nodes that cluster-autoscaler added could not get enough IPs to match their free CPU and memory. The cluster had been tested for 10k computes, and it ran out of IPs at about 8k.\n\nAfter WARM_IP_TARGET was set to 1, thousands of IPs went back to the subnets and pods started again. But once the control plane resumed terminating idle computes, the rate of successful pod starts stayed far below normal. Most new pods failed on IP assignment even though VPC subnet metrics showed thousands of unallocated addresses. The AWS CNI documentation warns that WARM_IP_TARGET can cause EC2 API rate limiting, but that did not seem likely at 1-2 pod starts per node per minute. Each restart of the aws-node DaemonSet, which reinitialises ipamd, brought a temporary improvement.\n\nOn 19 May, setting WARM_IP_TARGET=1 again made IP assignment fail immediately, this time from a stable state. Changing it back to 0 did not stop the errors: about 10% of pods kept failing to start for hours.","ko":"2025년 5월 16일, Neon 의 AWS us-east-1 클러스터에서 새 파드가 스케줄링은 되지만 IP 할당 오류를 내며 시작하지 못했다. 새 컴퓨트 파드를 시작할 수 없어 유휴 데이터베이스를 깨울 수도 없었다.\n\n그 직전 유휴 컴퓨트를 종료하는 컨트롤 플레인 주기 작업이 실패했다. 종료는 멈추고 생성은 계속되어 활성 컴퓨트가 몇 분 만에 약 5천 개에서 약 8.1천 개로 늘었다. 파드가 CPU 와 메모리를 다 쓰자 cluster-autoscaler 가 노드를 추가했고, 노드가 늘면서 IP 할당 오류가 나타났다. 세 가용 영역 가운데 두 곳의 VPC 서브넷에서 IP 가 바닥났다.\n\n당시 팀은 이 상황을 이해하지 못했다. 클러스터는 1만 컴퓨트까지 시험했고 서브넷 IP 는 모두 12,273개였는데, 컴퓨트 약 8천 개에서 IP 가 바닥났다. 파드에 배정된 IP 는 약 8.8천 개로 71% 였지만, 세 서브넷 IP 의 99% 인 약 12,200개가 이미 노드에 할당되어 있었다. 배정되지 않은 IP 가 넉넉해 보였지만, 그 IP 는 CPU 와 메모리가 꽉 찬 옛 노드에 묶여 있었다.\n\nWARM_IP_TARGET=1 로 바꾸자 파드가 다시 떴지만, 컨트롤 플레인이 유휴 컴퓨트를 종료하기 시작하자 파드 시작 성공률이 크게 떨어졌다. VPC 서브넷 지표에는 할당되지 않은 IP 가 수천 개 있었는데도 새 파드 대부분이 IP 배정 문제로 시작하지 못했다. 문서에는 WARM_IP_TARGET 을 쓰면 EC2 API 요청이 속도 제한에 걸릴 수 있다고 적혀 있었지만, 노드마다 분당 시작하는 파드가 한두 개뿐이라 그럴 것 같지 않았다. aws-node DaemonSet 을 재시작할 때마다 잠깐 나아졌다. 5월 19일에는 WARM_IP_TARGET=1 을 다시 배포하자 IP 배정이 바로 실패했고, 0 으로 되돌린 뒤에도 파드 시작의 약 10% 가 몇 시간 동안 실패했다."},"updated_at":"2026-11-10T00:00:31.507920Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0006","org_slug":"neon"}