Virtualization · Platform Engineering
Sunsetting a Golden Path Without Breaking the Tenants on It
Retiring a path means migrating every instantiated copy. How to inventory the fleet, freeze adoption, migrate in waves, and decide when a path is retired rather than repaired.
A golden path is retired when the cost of keeping it working exceeds the cost of moving its tenants. That comparison is rarely made explicitly; instead the path decays, accumulating exceptions, until it is a template nobody recommends and a fleet nobody can upgrade. The alternative is a planned retirement with a sequence that every tenant can follow, and the sequence is the same shape regardless of what the path contains.
Deciding whether to retire at all
The decision has three inputs, and the first two are usually known while the third is not.
The maintenance cost is the effort to keep the path’s guarantees true: the tests that exercise every supported parameter combination, the admission rules that reference it, the documentation, and the migrations required when a dependency changes underneath it. This cost is roughly constant per path, so a path with few tenants is expensive per tenant.
The replacement’s readiness is whether there is a path that covers the same workloads with equal or better guarantees. Retiring a path without a replacement is not a retirement; it is an abandonment, and the tenants move to something the platform does not support, which is worse than the path.
The fleet inventory is the input most often missing: how many tenants are on the path, how much they have drifted, and which parameters they actually use. Without it the decision is a guess, and the migration plan has no denominator.
The inventory is also where a path’s real value shows. A path with two hundred tenants who have never edited their generated output is cheap to migrate, because their repositories can be re-rendered. A path with forty tenants and pervasive drift is expensive, because each one is a bespoke migration. Tenant count is a poor proxy for migration cost; drift is the better one.
Freezing adoption before migrating
The first step of any retirement is to stop new instantiations. This is not a formality. A migration whose target moves while it is being executed does not converge, and every new tenant on the retiring path is work added to the migration after its scope was estimated.
Freezing is a change to the path’s schema or to the platform’s instantiation entry point: the path is marked as not accepting new tenants, and the entry point refuses it with a message naming the replacement. The refusal must be actionable, because a tenant who tries to instantiate a retired path is a tenant who has not heard about the replacement, and the error is the only channel guaranteed to reach them at the moment of need.
The exception that has to be planned for is the tenant who genuinely cannot move: a workload whose requirements the replacement does not cover, often because the workload is unusual in a way the platform did not anticipate. These become supported exceptions with an owner and a review date, not silent continuations. A freeze with no exception process produces shadow instantiation, where tenants copy the last version of the template into their own repository and the platform loses visibility entirely.
Migrating in waves by shape rather than by team
The migration is a fleet operation, and the natural ordering is by the shape of the tenant’s usage rather than by organizational unit. Three waves follow from the inventory.
The first wave is the tenants whose repositories match their pinned render exactly. They have no drift, so the migration is a re-render against the new path and a diff. This wave can be automated end to end: render, diff, and if the diff is confined to expected changes, open a change for review. It is also the wave that validates the migration procedure, because it exercises the path translation without the complication of tenant edits.
The second wave is the tenants with drift confined to fields the new path also exposes. Their migration is a merge, and the merge’s conflicts are resolvable mechanically because both sides are expressed in the same parameter space. This wave is where the translation rules get tested against real variation, and where the platform discovers the parameters that do not have a clean equivalent.
The third wave is the tenants whose drift includes fields the new path does not expose, or whose workloads use a capability the new path lacks. Each is a design conversation, and the number of these conversations is the true cost of the retirement. If that number is large, the replacement is not ready and the retirement should be reconsidered rather than forced.
Wave ordering by shape has a second benefit: the first wave’s tooling is reusable for the second, and the third wave’s findings feed back into the replacement’s schema. Migrating by team instead produces a long tail of partially completed migrations, each blocked on a different conversation.
Making the old path fail loudly at a known date
The mechanism that completes a migration is the one that makes the old path stop working, and its date must be published before the migration starts rather than after it stalls.
The failure should be at the earliest possible point in the tenant’s workflow. A retired path that fails at instantiation is discovered when a tenant tries to create something new. A retired path that fails at render time is discovered when a tenant deploys. A retired path that is simply deleted from the platform is discovered when a controller that referenced it breaks, which is the worst case because the failure appears in the fleet rather than in the tenant’s pipeline.
Render-time failure is the right point for most retirements: it is before any infrastructure changes, it is in the tenant’s own pipeline output, and it names the replacement. The tenant’s existing workloads continue running, which means the retirement does not cause an outage; it causes a blocked deploy, which is a cue rather than an incident.
The final step, removing the old path’s definition and its admission rules, should happen only after the fleet inventory reports zero tenants, or after the remaining tenants have been converted to declared exceptions with their own support arrangement. Removing it earlier converts a planned migration into an unplanned one for whoever is left, and the failure surfaces during a deploy that had nothing to do with the platform.
What the retirement must leave behind
A retirement that only deletes the old path leaves the next retirement harder. Three artifacts are worth keeping.
A translation table from the old path’s parameters to the new one’s, including the cases that had no direct equivalent and what was done instead. This is the document a tenant reads when they find an old repository and need to understand what changed.
A record of the exceptions, with the reason each workload could not move. This is the input to the replacement’s roadmap, because a recurring exception is a missing capability rather than an unusual tenant.
A measured baseline from before and after, on the same tenant-facing indicator the platform uses for its objectives. A retirement that halves the number of supported paths but doubles the journey completion time has traded maintenance cost for tenant cost, and the trade should be visible rather than assumed. The indicator to use is the one defined in service level objectives for a platform, because it is the only one that measures what the tenant experiences.
The retirement also has to update the platform’s own surface: the admission rules that referenced the old path, the dashboards that grouped by it, the documentation, and the inventory that counted its tenants. A retirement that leaves these in place has removed the path’s definition and kept its cost, which is the worst of both.
The versioning discipline that makes retirement cheap
Every retirement is cheaper when the path was versioned from the start. A path whose versions were published with declared compatibility, whose tenants pinned them, and whose fleet inventory tracked pinned version and drift can be retired by a sequence of ordinary version steps rather than by a one-off migration project.
The path that was never versioned has no pinned versions to inventory, no drift measurement, and no way to translate parameters, because there is nothing to translate from. Its retirement is a discovery exercise, and the discovery happens tenant by tenant.
This is the argument for the versioning discipline described in propagating template changes across an instantiated fleet: the cost is paid continuously and in small amounts, and the benefit arrives once, at the end, when the path is retired. A platform that skips it does not avoid the cost; it defers it to the least convenient moment, which is the moment someone decides the path should stop existing.
References
- Semantic Versioning 2.0.0 — SemVer
- ParallelChange — Martin Fowler
- Kubernetes documentation: Deprecated API Migration Guide — Kubernetes project