Kubernetes’ ExternalName Service Turns a Risky Namespace Migration Into a DNS Problem
One engineer's account of moving a critical Kubernetes service without downtime shows how the overlooked ExternalName Service type turns a namespace migration from a coordination problem into a DNS...
A Kubernetes Service type most teams forget exists, ExternalName, let one engineer move a critical authentication service out of the default namespace without a single second of downtime, by treating the problem as a DNS redirect instead of a coordination exercise. The account, published on CNCF’s blog by George Sims, is a useful reminder that some of Kubernetes’ oldest, least-used primitives solve production problems that look far harder than they actually are.
Table Of Content
- A Service Nobody Could Afford to Move
- Two Paths Into a Single Service
- Why the Obvious Plan Fails
- The Fix Kubernetes Already Ships: ExternalName
- The Ingress Side Still Needs a Real Answer
- A Deliberate Overlap Window
- Boring by Design: The Actual Rollout
- The Real Lesson: Look for the DNS-Shaped Seam
- Sources
A Service Nobody Could Afford to Move
The setup is familiar to anyone who has run a cluster for more than a year. A deployment lands in the default namespace early on, before anyone has opinions about namespace hygiene, and stays there because nothing forces the issue. In Sims’ account, that deployment was an authentication service he calls auth-svc: dozens of other services called it constantly, it had sat in default for years, and it was about to become a real problem the moment it needed namespace-scoped resources, its own ingress rules, its own policies, that default structurally could not give it.
Moving it was not optional forever. But it also could not go down, not even briefly. auth-svc handled authentication for an entire region’s cluster, so an outage meant nobody in that region could log in. Full stop.
Two Paths Into a Single Service
Before explaining why the migration was hard, it is worth being precise about what “moving it” actually meant. There were two completely separate paths into auth-svc, and both had to keep working throughout the move, or fixing one would just create an outage in the other.
Traffic from inside the cluster reached auth-svc the ordinary way: other services resolved auth-svc.default.svc.cluster.local through Kubernetes’ own internal DNS and got routed to a pod. That naming pattern is not specific to this cluster. Kubernetes’ own documentation on DNS for Services and Pods describes it the same way, assigning “normal” Services a DNS record of the form my-svc.my-namespace.svc.cluster-domain.example that resolves to the Service’s cluster IP.
Traffic from outside the cluster reached auth-svc through an ingress instead, a completely separate mechanism that has nothing to do with that DNS name. Kubernetes describes Ingress as “an API object that manages external access to the services in a cluster,” one that “exposes HTTP and HTTPS routes from outside the cluster to services within the cluster.” Whatever the fix turned out to be, it had to solve for both paths, not just the one that is easier to reason about.
Why the Obvious Plan Fails
The obvious plan, move the deployment and update every reference, falls apart the moment you look at who is actually calling the service. Dozens of consumers had auth-svc.default.svc.cluster.local baked into their own configuration. Getting every one of those teams to change their code on the same day, with zero mistakes, was not realistic for a service that could not go down.
A second constraint made a slow, staged migration harder too. The cluster ran an Open Policy Agent (OPA) admission policy that enforced identical ingress rules could not exist live in two namespaces at once, a sane rule meant to stop the kind of half-finished migration that leaves routing ambiguous. Whatever the fix looked like, it had to fit inside a single-namespace deploy rather than requiring a rewrite of shared infrastructure.
The Fix Kubernetes Already Ships: ExternalName
The insight that made this solvable: Sims did not need to migrate every consumer’s understanding of where auth-svc lived. He needed to migrate the service itself and quietly redirect anyone still asking for the old address. Kubernetes has a Service type built for exactly this, one that is easy to forget exists because teams almost never need it: ExternalName.
Instead of pointing at pods, an ExternalName Service points at another DNS name. Kubernetes’ own Service documentation is specific about the mechanism: it “maps the Service to the contents of the externalName field,” and “the mapping configures your cluster’s DNS server to return a CNAME record with that external hostname value.” Critically, the documentation adds, “no proxying of any kind is set up.” That last point matters: this is a pure DNS-level redirect, not a running proxy service sitting in the traffic path, so it adds no new infrastructure that has to be operated, scaled, or kept alive.
The pattern Sims used deploys the real service at its new home, then converts the old Service object into a forwarding address:
apiVersion: v1
kind: Service
metadata:
name: auth-svc
namespace: default
spec:
type: ExternalName
externalName: auth-svc.authentication.svc.cluster.local
Every consumer still calling auth-svc.default.svc.cluster.local gets silently redirected to the real Service in its new namespace. Nobody has to change a line of code, redeploy, or even know the move happened.
The Ingress Side Still Needs a Real Answer
ExternalName solves the internal DNS path, but it does nothing for the ingress path, since Ingress is a wholly separate mechanism from Service DNS. That left a chicken-and-egg problem: a working ingress had to exist in the new authentication namespace before external traffic could safely cut over, but the cluster’s OPA policy blocked identical ingress rules from being live in two namespaces at the same time.
A Deliberate Overlap Window
The fix was a scoped, temporary exception rather than a permanent policy change: a namespace annotation that let the old and new ingress rules coexist for a brief, deliberate window, instead of leaving a gap where neither existed.
apiVersion: v1
kind: Namespace
metadata:
name: authentication
annotations:
policy.example.com/allow-duplicate-ingress: "true"
That gave the team a real overlap period to confirm the new ingress worked before retiring the old one, instead of betting a region’s worth of logins on a single atomic cutover.
Boring by Design: The Actual Rollout
None of this went straight to production. It ran in dev first, then a staging cutover, with a couple of weeks between the staging success and the real one, mostly to see whether anything subtle showed up under real traffic before betting a critical path on the design.
The production cutover itself, according to Sims, “ended up being the boring part.” All the actual difficulty was front-loaded into getting the design right; once the plan was solid, executing it was closer to a formality than an event. Once the ExternalName redirect was live, the next step was not scaling anything down. It was watching metrics through the crossover window, confirming that traffic hitting the old address was genuinely landing on the new deployment and not silently failing or looping somewhere.
Only once that looked clean did the old pods get scaled to zero, rather than deleted outright. Scaling to zero costs nothing and buys an instant rollback: just scale back up if anything downstream looks wrong later. Deleting the old deployment outright would have meant rebuilding it from scratch if something went sideways, giving up that safety net for no real benefit.
The Real Lesson: Look for the DNS-Shaped Seam
Sims frames the broader lesson around why namespace sprawl happens in the first place: it is rarely carelessness. It is the complete absence of pressure to ever fix it. A team creates a policy restricting new services from landing in default, which is good practice, but nobody sets a deadline for the services that are already there. They just sit, in this case for years, until a team needs something namespace-scoped that default structurally cannot give them.
His conclusion generalizes past this one incident: “The next time someone tells you a service can’t be moved because too many things depend on it, that’s usually a sign nobody’s looked for the DNS-shaped seam it can be split along.” The caveat is just as important as the trick. This approach only works cleanly because every consumer in Sims’ cluster talked to auth-svc through its DNS name rather than a hardcoded IP address or ClusterIP. Legacy systems that skip DNS entirely need a different, harder fix.
It is also worth noting that Ingress itself, the mechanism handling one of the two paths in this story, is now a frozen API. Kubernetes’ own documentation states plainly that the Ingress API is no longer being developed, and recommends using Gateway instead. That does not change anything about how the ExternalName trick works, since it operates entirely at the Service and DNS layer, independent of whichever north-south routing layer sits in front of a cluster. But it is a reminder that half of this migration’s two paths is itself mid-migration, on a much longer timescale.
Sources
- CNCF Blog: Migrating a critical Kubernetes deployment from the default namespace without any downtime
- Kubernetes documentation: Service (including the ExternalName type)
- Kubernetes documentation: DNS for Services and Pods
- Kubernetes documentation: Ingress
Featured image: Fingerpost on B5152 near Kingsley by Sandy Gerrard, licensed under CC BY-SA 2.0 via Geograph Britain and Ireland / Wikimedia Commons.








No Comment! Be the first one.