Topology First: Why Ops Cuts Operations in the Wrong Place

Topology First: Why Ops Cuts Operations in the Wrong Place

Cartoon of an office in meltdown: an exhausted on-call engineer wrestles a bundle of red cables that used to be a single local function call; a manager hand-stuffs envelopes into per-ring mailboxes that were already routing correctly on their own; hand-copied address books, a coordinate table, and sync-loop robots crowd the whiteboard, which is headed "Draw the boundary before the boxes"; teams sit sealed inside separate glass snow globes, unable to reach each other.

Everything in that picture is the four backlog stories below. Note the speech bubble coming out of the mailboxes — “I already route by ring!” — because that is the punchline: the platform was doing the job the stories were built to do.

A team had one small routing operation that worked fine. A scaling problem forced a move, and the operation came out the other side as four backlog stories — a message queue, a database, an addressing scheme, a sync job — none of which delivered anything a customer could see. Then someone noticed the message queue they’d built all of it on had already solved the exact problem the four stories were solving. This is the most common way Kubernetes infrastructure rots, and it has a fingerprint you can learn to spot in a design review. It comes from a single blind spot: an inability to see the isolation boundaries the system hands you for free — a ring, a branch, an environment. You can’t see them, so you collapse them and rebuild their effect by hand. By the end you’ll have a one-line test that catches it.

This is Part 7 of “Why Kubernetes Infrastructure Rots.” Part 1 established the two mental models; Part 4 showed one capability smeared across five repos. Read those first if you want the theory; this part starts with a story and derives the theory from it.


The Whiteboard

Here’s the setup, stripped to essentials. A platform runs two things that work together:

  • an operator — a controller that creates one Kubernetes object per customer domain and turns it into live cloud routing;
  • a manager — a service that receives “provision this domain” requests and tells the operator to make the object.

They live in the same cluster. So “create a route” is one local function call: the manager calls the operator, the object gets created, done. One operation, one hop, nothing fancy. It shipped years ago and has been quiet ever since.

Then a scaling problem lands: the operator is doing too much per cloud account and will hit hard rate limits as tenant counts grow. It has to be split up — run one operator per deployment ring, each in its own cluster, so the load spreads out. Reasonable. Nobody disputes the operator needs to move.

The only real question is: where does the manager go, and how does it now reach the operators? Two engineers at the whiteboard, same constraints, answer it differently — and the difference is the whole article.

Engineer A places the boxes

A starts by drawing where things live. “The operator is an edge component — it belongs in the edge clusters, one per ring. The manager is a central service — it stays in the central cluster with the other services.” Two kinds of thing, two homes. Clean. Boxes placed.

Now A notices a problem created by the boxes: the manager and the operators are in different clusters, so the old local function call is gone. “OK, they need to talk over the network. We have a message queue — the manager publishes a ‘create’ request, the operator subscribes and does it.” Fine. But there are many operators now (one per ring), so: “the manager has to send the request to the right one, so it needs to know each operator’s address, so we need to store those addresses somewhere, so we need a database table for them, and a job to fill it in.”

A writes four stories:

Story What it adds
Operator subscribes to a queue code to receive a “create” request and make the object
Manager publishes instead of calling stop the local call; send a message instead
Manager figures out which operator to address build the destination address for the right ring’s operator
Manager stores each operator’s location in a database new table + a job to populate it

Every story is honest. Each one follows necessarily from the one before it. And not one of them creates anything a customer would notice — they exist entirely to rebuild the single local call that used to just work, now that it has a cluster boundary running through the middle of it.

🔍 View Fullscreen

The single call became a network hop, an address lookup, a database, and a fan-out — the red machinery is the four stories. And look at what the fan-out costs: the one central manager is wired into both mailboxes, so it can no longer tell the rings apart by which mailbox it’s on — which is exactly why it needs ② and the database to tell them apart in code. The one operation now runs across three clusters and a queue.

Engineer B draws the boundary

B starts somewhere else entirely — not with where things live but with what has to stay together. “Creating a route is one operation. It has a rule that must always hold: the routing object has to land in the ring that the domain belongs to. That rule is the boundary. It goes around the whole operation.”

Then B places boxes, and the boundary decides the placement: “If create-a-route must stay one clean operation, the manager and the operator that carry it out belong in the same cluster — one manager per ring, sitting next to that ring’s operator. And the provisioning request already arrives tagged with its ring, so routing it to the right ring’s manager is free.”

Result: “create a route” is still one local call, exactly as before the migration. The operator got its per-ring split (the thing scaling actually required) and the operation never got cut. B writes zero reconnection stories, because there’s no gap to reconnect.

🔍 View Fullscreen

The operation stays whole. The queue’s own per-ring mailboxes route each request to the right ring for free, so the manager and operator sit together and “create a route” is a local call again. No address lookup, no database, no fan-out — nothing to reconnect.

What B really bought isn’t fewer lines — it’s a seam in the right place. Each ring is now a sealed, self-contained environment, exactly like a branch: its code is pure internal logic — create-a-route, for this ring, end to end — and it is blind to every other ring. There is no “which ring” concept anywhere inside it, because a ring never has to reason about rings other than itself.

And everything that is genuinely cross-ring — which ring receives a given request, blue/green cutover between an old ring and a new one, migrating traffic from one to another — lives outside the rings entirely, at the routing layer that chooses a mailbox. So the design cleanly separates two concerns that have no business touching:

  • inside-the-ring — the operation itself, sealed and simple, with no notion that other rings exist;
  • among-the-rings — traffic placement and migration, handled once, at the edge.

Engineer A’s design fused the two. The central manager does both the operation and the cross-ring routing, in one process, so every request drags the “which ring, which operator” question through code that should only have been doing the work. All four of A’s stories are just that fusion made concrete: among-the-rings concerns smeared permanently into what should have been the inside-the-ring path.

Same problem. Same constraints. Opposite result.

Both engineers are good at their jobs. Neither made a mistake you could point to in a code review. The entire difference is the order they thought in:

  • A placed the boxes first, and the boundary between operations ended up wherever the boxes happened to land — straight through the middle of “create a route.”
  • B drew the boundary first, and the boxes landed wherever they had to for the boundary to hold.

A’s order is what this series calls topology-first thinking — decide where everything lives, then deal with the consequences. It’s the default for engineers who came up through operations, and the rest of this article is about why it’s the default, why it feels right from the inside, what it really costs, and how to catch it.


The Pattern: Cut, then Reconnect

Give the two beats of Engineer A’s whiteboard their names, because you’ll see them together every time. Note the word cut: A’s mistake wasn’t splitting the operator — that per-ring split was the legitimate scaling move. The mistake was letting that placement cut through an operation that should have stayed whole. Splitting a component is fine; cutting an operation is the wound.

  1. Cut — place components by what each one is (edge vs. central, stateful vs. stateless, our team’s vs. theirs). The boundary between operations ends up wherever that placement happened to fall — and here it fell straight through the middle of “create a route,” severing one operation into two halves in two clusters.
  2. Reconnect — notice that an operation which used to be one call is now cut in two, and build machinery to rejoin the halves: a message queue, a shared table, an addressing scheme, a feature flag, a sync loop.

The thing to internalize is why the reconnecting machinery always looks justified. Go back to A’s four stories. Every one is locally airtight — you genuinely do need a wire protocol for a remote call, you genuinely do need an address to reach one of many operators. You cannot object to any single story in its own review, because by the time the story exists, the cut is already a fact and the story is its honest consequence.

The decision that actually mattered — should the operation have been cut at all? — happened one beat earlier, when the boxes were placed, and it never appeared as a story. It wasn’t debated and rejected; it was never on the board. That’s the trap in one sentence: the cut is invisible, so only the reconnection gets reviewed — and the reconnection is always defensible.


Why the Reconnection Feels Like Progress

The cut-then-reconnect pattern is not just tolerated; it feels like good engineering from the inside, for reasons worth naming because they’re what make it self-reinforcing:

  • The reconnection is legible work. “Add a message-bus responder,” “add a coordinate table” are crisp, estimable, demoable tickets. “Don’t cut the operation” is not a ticket — it’s the absence of four tickets, and absence doesn’t show up on a board. The methodology rewards the thing that generates cards.
  • The cut matches the org chart. “Central services live centrally, edge components live at the edge” mirrors how teams and ownership are already drawn (Conway’s law, running forward). A boundary that matches the org feels natural — which is exactly the feeling that stops you interrogating it.
  • Each box is independently reasonable. Topology-first produces components that each pass their own code review, deploy on their own cadence, and have clean unit tests against a fake client that only knows their own types (see Part 1 on why that green CI is a mirage). The wrongness is only visible at the seam, and no single box owns the seam.
  • It looks like decoupling. Two services talking over a bus reads as loose coupling, a virtue. But they aren’t decoupled — they’re one operation that now fails in the middle. It’s a distributed monolith (Part 4) wearing the costume of a microservice boundary.

So the pattern survives not despite looking like good practice but because it does. Every incentive in the room — planning, org structure, review granularity, vocabulary — points at cut-then-reconnect and away from the question that would dissolve it.


The Blast Radius Isn’t Just Code — It’s People

There’s a cost that never shows up in the diff, and it’s the one that hurts longest: the cut turns a boundary between pieces of code into a boundary between the people who own them. And people-boundaries are multi-dimensional in a way code-boundaries are not.

Go back to Engineer A’s four stories. In a real org they don’t stay one team’s work. The operator half is “an edge component,” so it drifts to whoever owns the edge clusters. The manager half is “a central service,” so it stays with central services. The message queue is “messaging infra,” maybe a third group. Cut an operation along what each piece is, and the pieces get owned by the teams organized around those same categories — Conway’s law running forward: the system’s seams become the org’s seams, and then the org’s seams calcify the system’s.

Now compare the two worlds when someone needs to change “create a route”:

Before the cut (Engineer B’s world): it’s one operation in one place, owned by one team. Changing it is a code review among people who share the whole picture. The only dimension of dependency is the code.

After the cut (Engineer A’s world): the same change now needs the manager team, the operator team, and possibly the infra team to coordinate — and not just on code. They now depend on each other across every dimension the cut created:

  • Deploy ordering — the new message format has to land on the receiver before the sender emits it, so the two teams must sequence their releases. A dependency that didn’t exist when it was one call.
  • Schema/contract — the request payload is now a shared contract between two codebases. Change it and you need the other team’s sign-off, a versioning story, and a migration window.
  • Release trains — the two halves ship on different cadences to different clusters, so “the feature is live” now means “both teams shipped, in order, and nothing drifted in between.”
  • On-call and incidents — a failure in the middle of the operation now pages someone, and “which team owns a request that was published but never consumed?” is a new, genuinely ambiguous question. The seam has no owner.
  • Review latency — the operation can no longer be changed in one PR. It’s N PRs across N repos across N review queues, each gated on a different team’s bandwidth.

None of these are code. They’re human dependencies — each one a channel through which one team can now block, break, or wait on another. The cut didn’t just distribute the operation across services; it distributed the ability to make a change across teams and calendars. That’s the real blast radius: not the lines the reconnection added, but the coordination surface it created between people who used to just talk to each other and ship.

And it compounds the trap from two sections ago. Once the cut maps onto team ownership, healing it isn’t a refactor — it’s a reorg. The boundary that was never a design decision has quietly become an org-chart decision, and org charts are far harder to redraw than code. That’s why topology-first boundaries outlive everyone agreeing they’re wrong: by the time the cost is visible, the boundary has teams, roadmaps, and on-call rotations built on top of it.


The Twist: They Rebuilt Something They Already Had

So far A’s design is just longer than B’s. Here’s what makes it genuinely wasteful. Two of A’s four stories — address the right operator, store each operator’s location — exist to solve one problem: given a request, find the right operator to send it to. Now look at what they built all of it on top of: the message queue.

That queue was already sorted by ring. It has a separate, sealed mailbox per ring, and a sender on ring X’s mailbox can only be heard by a receiver on ring X’s mailbox — nothing crosses between them. In other words, the queue already delivers each request to exactly the right ring, by construction, for free — the same problem A’s two stories set out to solve.

Engineer B’s design just uses that: one manager per ring, connected to its own ring’s mailbox, and the request lands at the right operator automatically — no address to compute, no table to store.

Engineer A’s design threw that away, then rebuilt it. A single central manager has to connect to every ring’s mailbox at once — and once you’re plugged into all of them, the “which ring” distinction the separate mailboxes were handing you is gone. So A reconstructs it in application code: compute the address, store the locations, build the lookup. Two of the four stories exist purely to rebuild routing the queue was doing for free, right up until the central placement flattened it. (The other two — subscribe, publish-instead-of-call — are the honest cost of a remote hop; under B’s design there is no remote hop, so they vanish too.)

This is the signature blindness of topology-first thinking: it sees the platform’s boundaries as plumbing to route bytes through, never as semantics to route with. Per-ring mailboxes, cluster locality, DNS host keys, namespace scoping — these are a type system the infrastructure hands you. The ops mind treats them as inert pipe, so it merges across them and never notices the free structure it just destroyed. In a topology-first worldview that structure was never information — just where the wires happened to run.


The Tell: The Right Fix Is Subtractive

Here is the cleanest diagnostic for a topology-first boundary, and it doubles as the proof that the complexity was accidental rather than essential: price the correct alternative in lines of code, and watch the meter run backwards.

Run the manager once per ring instead of once centrally, and the entire reconnection apparatus evaporates:

  • The manager connects to one mailbox (its own ring’s), not all of them — you delete the code that fans out across every ring.
  • One operator per manager, same cluster → “create a route” is a local call again, exactly as before the migration.
  • One local operator → nothing to address, nothing to look up → no database, no addressing scheme, no backfill job.
  • The sender doesn’t change at all — it already dropped its request into its own ring’s mailbox.

No new queue plumbing. No new fields. The fix removes code.

Be honest about the one thing it does add: copies. N rings run N deployments of the same manager — N sets of credentials, dashboards, alarms. That’s a real bill, worth naming before someone else names it for you (“so now I upgrade six managers instead of one?”). But it’s replication of one artifact, not new code paths: the same manager N times, along a boundary the platform is already maintaining for N operators anyway. It grows linearly, every copy is the same thing to reason about, and it adds nothing to the 2ᴺ interleaved state space the next section gets to. The four stories are the other kind of bill — new code, in the critical path, forever, whose only job is rejoining what the placement cut.

And the subtraction is the general law, not a quirk of this case:

If undoing a design deletes more code than it adds, what you deleted was reconnection.

The cut behind reconnection was never a requirement — it was a topology drawn before anyone asked what had to stay whole. But be precise about what the test detects: accidental reconnection, not all complexity. It does not claim essential work is always additive — standing up a second isolated environment adds code and is entirely real work, not a reconnection. The signature is narrower: machinery whose only job is to rejoin two things a placement pushed apart, so putting them back leaves the system doing the same job with less code. When your “simplification” proposal is a diff that’s mostly red and the behavior is unchanged, you’ve found a cut-then-reconnect. This is the argument that can’t be met story-by-story, because it reprices the premise underneath all of them at once.


The Root Cause: They Can’t See the Isolation

Everything so far has been topology-first as an order of operations: place boxes, then reconnect what the placement cut. But why does placing the boxes first feel safe — why doesn’t the engineer flinch when a placement cuts an operation in half? The answer is one layer down, and it’s the real root of the rot: the ops mind does not perceive isolation boundaries as real. Move the boxes around and there appear to be no walls to knock down.

Look again at the fatal move: one central manager connected to every ring’s mailbox. To make that move you have to not notice that a ring is a sealed system, and that reaching across rings is reaching through a wall the platform deliberately built. Engineer A didn’t decide to violate ring isolation; A never saw a wall to violate. A ring, to that mindset, is a label on some boxes — a thing you address. So of course you wire one service to all of them.

That blindness isn’t specific to rings. It’s the same blind spot, and it shows up anywhere the system hands you an isolation for free:

  • A ring is an isolated system — its own accounts, clusters, blast-radius boundary. The ops mind sees a routing target, so it connects one central thing to all rings and rebuilds per-ring routing in application code (this whole article).
  • A source branch is an isolated line of development — a cheap, first-class fork of history you can diverge on and merge back. The ops mind sees a branch as risky divergence from the one real thing (main/the live cluster), so it avoids branching and mutates the shared line directly — then rebuilds “which version is this” with flags and conditionals instead of letting the branch be the isolation.
  • An environment is an isolated running version — stand two up, point traffic at whichever, and coexistence is structural. The ops mind doesn’t see two environments as a clean way to hold two versions; it sees one system that must be switched, so it smuggles the version fork into application code as a feature flag (Part 5 is the whole essay on this) — a runtime if standing in for an isolation the deployment model could have expressed for free.

Ring, branch, environment, mailbox — these are all the same kind of thing: an isolation boundary the platform provides, so you don’t have to build one. And the pattern is identical in every case:

The system hands you an isolation for free. The ops mind doesn’t perceive it as real — sees a label, a target, a switch — so it collapses the boundary and rebuilds its effect imperatively inside one shared thing: one manager wired to all rings, one branch carrying every version, one codebase full of flags.

This is why removing the reconnection is subtractive (the previous section). You’re not adding a boundary — you’re stopping the collapse of one that already exists. The coordinate database, the feature flag, the all-rings fan-out are all the same artifact: hand-built machinery replacing a free boundary the builder couldn’t see. Learn to see the isolation and the machinery has no reason to exist.

Why it accumulates: isolated coexistence is flat, interleaved coexistence multiplies

Here’s what turns a bad decision into rot. First, what the cost is not: it is not coexistence. Two versions, or ten, can run side by side indefinitely at no compounding cost — blue/green needn’t switch-and-retire, a migration needn’t finish, old and new can both serve real traffic for a year. One property decides whether coexistence is cheap or ruinous: are the versions isolated from each other, or sharing one box?

Isolated coexistence stays flat. N versions, each sealed in its own boundary — its own environment, ring, branch — is N separate state spaces sitting next to each other. Version N can’t see version N+1’s state; a request enters exactly one box and stays there. Inside each box is whatever that version genuinely requires: essential complexity, contained. Ten isolated versions can run live for years and the system stays comprehensible, because “which states are reachable together” never grows — they’re partitioned by construction.

Interleaved coexistence multiplies. Collapse the boundary — both versions in one process, one branch, one mutable state — and they stop being two boxes and become two paths through the same box. Every request can take either, so the reachable states are the product, not the sum. A feature flag is exactly this: not two versions, but two versions in one process, so the paths interleave — a ×2, permanent, because retiring the old path means proving it dead and nobody ever does. A cross-boundary route that lets caller and callee run different versions adds a skew axis — another ×2. Each collapsed boundary is another multiplicand:

flag₁ × flag₂ × … × skew₁ × skew₂ × …N collapsed boundaries, 2ᴺ interleaved states. Almost none tested. Most never intended. Every one a place the system can misbehave.

That’s how accidental complexity accumulates multiplicatively. The multiplier isn’t the number of versions; it’s the number of un-isolated ones. Ten isolated versions is ten essential things, side by side, flat. Ten flags in one codebase is a thousand-plus reachable states nobody can enumerate, let alone test. Same “ten versions” — the whole difference is whether a boundary keeps them apart or a collapse lets them interleave. That is also the difference between infrastructure that ages and infrastructure that rots.


Where the Instinct Comes From

None of this is stupidity, and the fix is not “hire smarter people.” Both layers — the topology-first order and the isolation-blindness underneath it — are trained, by the same history, and the training is everywhere:

  • The heritage. Ansible → Chef → Puppet → Kubernetes: a career of “which host runs what.” Placement was the job. Domain boundaries never entered because there was no domain model — only machines and the things you put on them. And in that world isolation was physical: a separate host, a separate network. So the abstract isolations software gives you — a ring, a branch, an environment, an account — don’t register as real walls the way a firewall does; they look like configuration, not structure. You can’t respect a boundary you were never trained to see as one.
  • The tooling gradient. Every core K8s tool asks “where does this go,” never “what is this.” kubebuilder create api scaffolds one more box. Helm/Kustomize render placement. Flux/Argo reconcile a directory to a location. The path of least resistance is always another box on the map; nothing in the toolchain prompts “should this be one operation?” (Part 2 on the tooling wall.)
  • The reward function. Domain-first work often shows up as fewer artifacts — and “I made the design smaller” is invisible on a board that counts cards.
  • The invisibility of the alternative. The most damning detail from the real version of this story: the team evaluated five migration options, and all five varied where the operator goes. Not one asked whether the manager should move with it. “Without relocating the manager” was even listed as a benefit. The domain boundary wasn’t rejected — it was never a candidate. Topology-first didn’t lose the argument; it prevented the argument from being had.

That last point is the real cost. It’s not that ops engineers make a bad boundary decision. It’s that in a topology-first frame the boundary is not experienced as a decision at all — it’s a fact that falls out of where the boxes went, and facts don’t get design review.


How to Interrupt It

You can’t retrain an instinct with an article, but you can insert checkpoints that force the domain-first question into a topology-first process. Four that work from outside the team:

  • Before placing boxes, name the operations that must stay whole. One sentence each: “creating a route must be atomic within a ring.” Write them down first. Then place components — and if a placement severs a named operation, that’s not machinery to build, it’s a placement to redo. This simply forces the two orders into the right sequence.

  • When a local call becomes an RPC, treat it as a design smell, not a task. The moment a story says “publish a request to do X, and elsewhere consume it and do X,” stop and ask what the hop buys. If the only answer is “so the two halves could live in different boxes,” ask why they’re in different boxes. A call that got promoted to a message is a boundary confessing itself.

  • Reprice every proposed reconnection as a subtraction. For any RPC layer / coordinate table / sync loop about to be built, ask: “what would it take to make this a local call again, and does that remove more than it adds?” If the subtractive version is smaller, the machinery is accidental complexity and the topology is wrong. Make someone estimate the red diff before approving the green one.

  • Count what each addition multiplies, not what it adds. Before shipping a flag or a cross-boundary hop, ask: “does this let two versions interleave in one box, or keep them in separate boxes?” Interleaving in one box doubles the state space — permanently, unless someone owns proving the old path dead. Give every flag a removal date and every version-skew axis an owner, or it becomes one more multiplicand nobody can retire. The alternative isn’t “don’t coexist” — it’s coexist isolated: two environments, two branches, two rings, each sealed, cost nothing to run side by side.

The unifying move behind all four: make the domain boundary a line item. Topology-first rots precisely because the boundary is never on the board — it hides in the whitespace between the boxes, where no review reaches it. Drag it onto the board, give it a row, force it to be priced, and the cut-then-reconnect pattern loses the one thing that keeps it alive: the ability to be assumed instead of chosen.

And price it in all three currencies. Code — the queue, the table, the addressing scheme — is the cheapest and most reversible. People is second: every cut wires a coordination dependency between teams, and that hardens into the org chart, where it stops being reversible at all. State is third: every boundary you collapse instead of use multiplies the reachable states, and that compounds for as long as the system lives. Three numbers, and only the first one shrinks over time. A boundary that looks cheap in code can be ruinous in people, and quietly fatal in state.


Why This Is the Part AI Can’t Do for You

It’s worth ending on this, because it’s the least obvious consequence — and, right now, the sharpest line between what a coding agent does and what still needs a person.

Look at what the two halves of the pattern demand. Reconnection is the AI-shaped half. “The call is remote now — write the responder.” “There are many operators — build the addressing.” “The address needs a source — add the table and the backfill.” Each of A’s four stories is crisp, local, well-specified, checkable against its own tests. That is exactly the shape today’s models are best at — hand a model Engineer A’s box layout and it produces all four, clean, fast, tidier than a human would. The reconnecting machinery is the most automatable code in the whole story.

The cut is the human-shaped half — human-shaped for the same reason it was invisible to Engineer A: it lives in the whitespace between the boxes. Deciding that “create a route” should never have been severed isn’t a local judgment about any one file; it needs a model of the domain, a prior sense of what must stay whole, that exists in no ticket, no repo layout, no diff, no prompt. That model is the asset — the thing that appreciates while the code around it depreciates (the semantic-model argument is the long form of that claim). A model optimizes the gradient it can see: given a topology, it makes every piece locally excellent. It does not step outside the given topology to ask “should this operation have been one thing?” — that question isn’t in the topology; it’s what a person brings to it.

So the uncomfortable part: AI doesn’t correct cut-then-reconnect — it accelerates it. By making reconnection nearly free, it removes the one brake the pattern ever had: that bridging was tedious to build. The cheaper it gets to reconnect what you cut, the more freely people will cut — and the four-story tax, the coordination surface, and the 2ᴺ state space all still arrive, just faster and with less friction to notice them. An agent that fluently fills in the bridge makes the wrong topology more comfortable to live in, not less.

Which is the actual role left for the human, and it’s not going away: be the one who draws the boundary before the boxes. Name the operations that must stay whole; refuse the cut that no local review would ever flag; supply the domain model that says “this is one thing” when every gradient says “these are two.” The machine can build any bridge you specify. Deciding which bridges should never need to exist — that’s the part that’s still yours.


Series: Why Kubernetes Infrastructure Rots

  • Part 1: The Operator Mindset — Why one domain becomes six repositories. The repo-per-problem anti-pattern as a consequence of thinking in procedures instead of models.

  • Part 2: The Cargo Cult — Why shared repos and better tools don’t fix it. The failed abstraction phase.

  • Part 3: The Abstraction Instinct — What no tool can provide. CDK in the hands of an operator is still operator thinking.

  • Part 4: The Distributed Monolith — Why your GitOps is a monolith wearing a microservices costume. Five repos, five teams, zero transactional boundary, and six incidents in four weeks.

  • Part 5: The Staging Mindset — Routing is atomic. Deployment is not. Why feature flags are what happens when the infrastructure can’t express version coexistence.

  • Part 6: The Shared Mutable State — The CR is a database table with no foreign keys, shared between controllers with no ownership model. Silent data loss as a design consequence.

  • Part 7: Topology First — Why ops cuts operations in the wrong place, then pays a backlog to rejoin them. Placement drawn before the boundary, and the free isolation it destroys. (this article)

  • Part 8: The Slicing Machine — Why the horizontal cuts become departments, and departments become toll booths. Conway’s law applied to aspects, and the equity-not-equality reason discretion is never surrendered.

  • Aside: Operator Stockholm Syndrome — When the K8s control plane becomes the universe. Routing every cloud API through a cluster CR even when the cluster has no semantic role.

  • Aside: The Cron and the Gate — When the operator models itself instead of the domain. One Reconcile() hook, triggered identically by create/resync/requeue, becomes the only place policy can live.

  • Aside: The Configuration Problem — One business rule sliced across Helm, ConfigMap, Flux substitution, and Calico’s dataplane — zero cohesion, load-bearing tribal knowledge.

  • Aside: The Auto-Approve — When the reconcile loop swallows terraform plan. Wrapping a tool with a human-in-the-loop gate in a loop that structurally can’t hold one.

  • Aside: You Can’t Front-Run the Composition Gap — Why correct first-principles reasoning must crash once before it can diagnose.

  • Lab: Verify It Yourself — Copy-pasteable, real-output reproductions of every cluster mechanism the series cites (foreign keys, CEL scope, ownerRefs, SSA, PUT-strips-fields, resourceVersion, CRD versioning, kstatus).

  • Synthesis: The Thermostat That Ate Infrastructure — How a container self-healing pattern became a deployment engine. The missing DAG from node boot to infrastructure blue/green.

Cite This Article

Gary Yang. "Topology First: Why Ops Cuts Operations in the Wrong Place." ONDEMANDENV.dev, July 26, 2026. https://ondemandenv.dev/articles/k8s-boundary-you-already-had/.

📝
Source History
🤖
Analyze with AI