Skip to content

    Back to articles

    7 min read

    Audit who owns your Kubernetes service IPs

    A deprecated field points to an ownership gap

    A Service with .spec.externalIPs can make a workload reachable at an address without making clear who is responsible for that address. That is the part I would investigate first.

    In Kubernetes v1.36’s announcement on deprecating Service externalIPs, the project explains that the field is deprecated and that kube-proxy support is expected to be disabled in a future release. The announcement also distinguishes this field from other uses of the phrase “external IP,” including addresses associated with LoadBalancer Services. So the first task is not to replace every address that looks external. It is to find Services that set this specific field, then trace what actually makes their addresses reachable.

    The risk is broader than future incompatibility. The Kubernetes announcement connects externalIPs with CVE-2020-8554, a security issue involving the ability to intercept traffic. My architectural reading is that the field can obscure an important boundary: Kubernetes can direct matching traffic to Service endpoints, but the field itself does not establish who is entitled to use an address or how the surrounding network routes it.

    That leaves questions the manifest may not answer. Who allocates the address? Which network equipment advertises or routes it? Who prevents two Services from claiming it? Which team is allowed to expose a service there? If the answers live in separate systems and nobody owns the whole path, changing the Service type alone may preserve the same weakness under a new name.

    Inventory the traffic path, not just the YAML

    I would start by listing every Service that sets externalIPs, with its namespace, name, address, type, and owning workload or team. A quick query can find the field, but it cannot tell you whether traffic reaches that address, whether the address is still in use, or what external infrastructure makes it work.

    kubectl get services --all-namespaces -o jsonpath='{range .items[?(@.spec.externalIPs)]}{.metadata.namespace}{"/"}{.metadata.name}{"\t"}{.spec.type}{"\t"}{.spec.externalIPs}{"\n"}{end}'

    Treat that list as the start of an investigation, not a migration plan. Compare it with the source of truth for address allocation and with network configuration outside the cluster. Check the routes, firewall rules, load balancers, DNS records, and monitoring that might depend on each address. Then establish whether clients connect directly to it, whether another proxy forwards traffic to it, and what happens if the Service or its endpoints are unavailable.

    I would record the result as an ownership map. For each address, name the system that allocates it, the system that advertises or routes it, the controller that reconciles the Kubernetes object, and the team accountable for approving exposure. If one address is managed manually, say so. If a detail cannot be established, mark it as unknown rather than assuming Kubernetes owns it because the address appears in a Service manifest.

    This distinction matters in both cloud-backed and self-hosted clusters. A cloud controller may provision an external load balancer for a LoadBalancer Service, while a bare-metal cluster may rely on a separate implementation and network integration. The Kubernetes Service documentation describes how LoadBalancer Services are typically implemented through an external load balancer. MetalLB’s configuration documentation shows one bare-metal approach: allocate addresses from configured pools and advertise them through network protocols. These are different ownership arrangements, not interchangeable spellings for the same thing.

    Choose the replacement after you know the owner

    Once the traffic path is understood, choose a mechanism that puts address allocation and reachability under an explicit owner. I would not start by changing externalIPs to type: LoadBalancer in every manifest. That might be right when a load-balancer controller can allocate and advertise the address, but it can also change who controls allocation, how an address is reserved, or which network boundary receives traffic.

    For a cloud-backed cluster, a LoadBalancer Service may be the natural replacement if the cloud integration owns the load balancer and the required address lifecycle. Confirm how it handles existing addresses, access restrictions, health checks, and deletion before migrating. The Kubernetes guide to creating an external load balancer explains that Kubernetes automates configuration through the relevant external load-balancer integration; that does not mean every provider has identical behavior or supports every reservation requirement.

    For a self-hosted cluster, first identify which component should own the address pool and route advertisement. MetalLB is one possible implementation: its documentation describes address pools for assigning IPs to Services and separate configuration for announcing them. The key decision is not “cloud or bare metal”; it is whether the controller and network configuration together provide a clear, reviewable allocation and announcement path.

    Other traffic may belong behind an ingress controller, gateway, or an existing network load balancer. In that design, the Service can remain an internal backend while the gateway owns the client-facing address and policy. I would choose this when the organization needs a deliberate application-layer boundary, not just a stable IP. The cost is an additional component and a dependency to operate; the benefit is that exposure policy and address ownership can be managed at that boundary rather than inferred from arbitrary Service fields.

    Before changing anything, verify that the selected owner can satisfy the actual requirements: address preservation where necessary, protocols in use, source-address handling, allowed clients, and failure behavior. These are properties of the complete path. A manifest that applies successfully is not evidence that clients will keep reaching the same destination or that the same security controls remain in force.

    Make the migration prove the boundary

    I would migrate one clearly understood path at a time. Keep the old and new paths distinguishable during testing, and verify reachability from the client networks that matter, not only from inside the cluster. Check that the new controller reports the expected allocation, that routing and firewall policy match the intended exposure, and that observability can identify failures at the new boundary. I would also test deletion and recreation behavior where retaining an address is a requirement; address lifecycle is part of the contract, not an incidental implementation detail.

    A small manifest change can illustrate the destination, but the controller and network setup determine whether it works:

    apiVersion: v1
    kind: Service
    metadata:
      name: example
    spec:
      type: LoadBalancer
      selector:
        app: example
      ports:
        - port: 80
          targetPort: 8080

    This example does not reserve an address, configure a provider, or define who may expose the Service. Those details must be expressed through the chosen implementation and the surrounding infrastructure. I would make the ownership decision reviewable alongside the Service configuration, rather than leaving it in an undocumented operational convention.

    After migration, prevent the old pattern from returning. The Kubernetes deprecation announcement recommends considering the DenyServiceExternalIPs admission controller as a way to block future use of the field. That is a useful guardrail once existing dependencies have been found and addressed. A blanket block applied before the inventory, however, could reject changes without revealing which production traffic still depends on the old path.

    The useful outcome of this audit is not a uniform Service type. It is a defensible answer, for every externally reachable Service, to who assigns its address, who makes it reachable, and who is accountable for that exposure. Once those owners are explicit, the migration choice becomes a consequence of the traffic design rather than a hurried search for a replacement field.

    Drafted with AI assistance, reviewed and edited by Ali.