Why the field is being removed
externalIPs gives a Service a user-chosen address outside the normal cluster IP machinery. kube-proxy still acts on those addresses today, which means traffic can be steered without an administrator-controlled allocation path. That is the part that keeps biting.
The security issue is not subtle. The API assumes every cluster user is trusted enough to claim an address, yet there are no further checks to stop one user from taking another user’s IP. That is why the field was already recommended for shutdown in earlier releases, and why removal is being staged rather than left to rot in place.
Kubernetes 1.36 also emits warnings when .spec.externalIPs is used. Support is expected to disappear later, with v1.40 cited as the earliest point for disabling it. In practice, waiting for the hard removal means waiting until the problem starts breaking things for you.
Recommended short-term actions for cluster operators
The quickest safe move is to stop treating externalIPs as a normal exposure method. Replace it with controller-managed address assignment, then block new ad-hoc bindings before they spread through manifests and automation.
Move existing Services to controller-managed addresses
For simple L4 exposure, Service type: LoadBalancer is the usual replacement path. The address then comes from a controller or cloud integration rather than a field any manifest can claim. That shifts the trust boundary to the people and systems already responsible for address assignment.
If you need on-prem address allocation, MetalLB is the common answer. It allocates IPs from configured pools, which gives you a central place to decide what can be handed out. The pool is the control point, not the Service manifest.
Do not fake the model by leaving a Service as a normal object with a hand-picked external address. A manually managed LoadBalancer Service still exposes the same sort of privilege issue if users can patch or claim addresses freely. The control only works when address ownership sits with the controller and the administrator, not with whoever can apply YAML.
Use Gateway API or Ingress for L7 exposure where appropriate
If the exposure is HTTP or HTTPS, move it to Gateway API or Ingress rather than keeping it on a Service. Gateway API lets you bind administrator-managed addresses through spec.addresses, then route traffic with HTTPRoute. That keeps the IP binding and the routing rule separate, which is the whole point.
For a plain web service, the pattern is straightforward: the Gateway owns the address, the route points at the backend Service, and the Service stops pretending to be public infrastructure. It is a cleaner split than bolting public reachability onto a Service and hoping nobody gets clever.
Enable DenyServiceExternalIPs admission/config to detect and block ad-hoc bindings
DenyServiceExternalIPs is the practical control for stopping new use of the field. It rejects Services that still set externalIPs, which gives you a hard stop before someone adds a fresh exception at 5pm on a Friday.
Use it early. That catches new manifests, prevents drift, and exposes hidden dependencies before you remove the old path. If the cluster already relies on externalIPs, this is the point where the dependency stops being silent.
How to audit and find remaining externalIPs usage
Search more than live Services. The field often hides in manifests, Helm charts, Git repos, CI templates, and bespoke automation that nobody remembers until deployment starts failing.
Look for:
externalIPsin Service manifestsexternalIPsin rendered Helm outputexternalIPsin Terraform, Ansible, or other infrastructure code- any
node:portstyle binding patterns that were built to replace a proper controller - Services exposed through
kube-proxyassumptions rather than through a load balancer or Gateway
Live cluster checks are not enough on their own. A clean current state does not mean the next deployment will stay clean, and the next deployment is usually where the mess returns.
Rollout plan and compatibility considerations
Treat the migration as a change to traffic ownership, not just a field rename. First, tell people what is being removed and what the allowed alternatives are. Then automate the manifest changes so new Services cannot drift back into the old pattern.
During rollout, watch for Services that were depending on externalIPs as a cheap shortcut for public reachability, internal VIPs, or awkward routing fixes. Those are the awkward ones, because they tend to be embedded in old playbooks and forgotten dashboards.
Compatibility matters too. Some replacements are only safe if the surrounding controller actually exists. A LoadBalancer Service without a working controller is just a stalled object with nicer paperwork. MetalLB needs an IPAddressPool and a sensible address range before it can assign anything. Gateway API needs the Gateway and route objects in place, not just a vague plan and a spare afternoon.
RBAC also matters here. If users can still patch Services, then an incomplete migration leaves room for the same bad behaviour under a different field. Lock down who can create or edit exposed Services, and keep the address assignment path under controller control rather than manifest control.
During the migration window, monitor for warning events and rejected admissions. That gives a rough count of what is still trying to use the old binding path, and it is better than finding out from a broken release after the fact.



