KubeTW All articles
Architecture & Strategy

GitOps Looked Great on the Whiteboard—So Why Is Your Cluster Still a Mess?

KubeTW
GitOps Looked Great on the Whiteboard—So Why Is Your Cluster Still a Mess?

There's a specific kind of pain that GitOps teams know well. You've got ArgoCD or Flux humming along, your manifests are version-controlled, and your README proudly declares that everything is declarative. Then an incident hits. Someone runs a quick kubectl patch to stop the bleeding. Someone else edits a ConfigMap directly because the PR process would take too long. By Monday morning, your cluster looks nothing like your Git repo, and nobody's quite sure which one is lying.

This isn't a tooling problem. It's an architecture problem—and it's a lot more common than the GitOps evangelists want to admit.

The Promise vs. The Parking Lot Reality

The GitOps pitch is genuinely compelling: declare your desired state in Git, let a reconciliation loop enforce it continuously, and get auditability, rollbacks, and collaboration for free. In theory, your cluster becomes a read-only artifact of whatever lives in your repository.

In practice, most teams end up with what you might call aspirational GitOps—Git holds the intended state, but the cluster holds the actual state, and the gap between them is where incidents are born.

The root cause isn't laziness or bad intentions. It's that GitOps tooling was designed for a world where every change goes through a pull request, every deployment is deliberate, and nothing ever needs to happen faster than a CI pipeline can run. Production doesn't live in that world.

Drift: The Silent Killer Nobody Talks About

Drift is what happens when your cluster state diverges from your Git state. ArgoCD calls it "OutOfSync." Flux calls it a reconciliation failure. Engineers call it "we'll fix it later."

The insidious part is that drift accumulates gradually. A manual secret rotation here. A resource limit tweak applied directly to stop a memory leak there. A temporary annotation that became permanent. Each individual change feels justified in the moment. Collectively, they turn your "declarative infrastructure" into a patchwork of undocumented decisions that nobody fully understands.

Worse, most teams configure their GitOps tools to alert on drift rather than remediate it. That means your Slack channel fills with OutOfSync notifications that everyone learns to ignore. The reconciliation loop isn't reconciling—it's just watching the divergence grow and filing incident reports nobody reads.

Why Reconciliation Loops Fail in Real Environments

Let's be honest about what reconciliation actually means in a busy production cluster.

ArgoCD's sync process is powerful, but it's not magic. If your application team has applied a kubectl edit to bump a replica count during a traffic spike, ArgoCD will dutifully revert it on the next sync—possibly at the worst possible moment. So teams start adding ignoreDifferences rules to stop the tool from touching things it "shouldn't." Which is another way of saying: they're manually carving out exceptions to their automated system until the automation barely does anything.

Flux has similar failure modes. Helm releases that drift from their chart values, Kustomize overlays that get manually overridden, image tags that got updated in the cluster but never committed back to the repo. The reconciliation loop runs every few minutes and reports success because it successfully applied a manifest that no longer reflects reality.

The uncomfortable truth: a reconciliation loop that's full of exceptions isn't a reconciliation loop. It's a deployment pipeline with extra steps.

The Multi-Tenancy Trap

Things get dramatically worse when you add multiple teams to the picture. Enterprise Kubernetes environments typically have dozens of teams pushing changes, each with their own release cadence and risk tolerance. GitOps tools handle single-team workflows reasonably well. Multi-team environments expose every architectural shortcut you took during setup.

Who owns the base manifests? Who can merge to the config repo? What happens when Team A's change to a shared namespace breaks Team B's deployment? Most organizations answer these questions with a combination of CODEOWNERS files, tribal knowledge, and hope. That's not a governance model—it's a liability waiting to surface during an audit.

ApplicationSets in ArgoCD and multi-tenancy patterns in Flux exist precisely to address this, but they require deliberate upfront design that most teams skip because they were "just getting started" and never revisited.

What Actually Works: A Brutally Honest Playbook

1. Treat your Git repo like production infrastructure. Branch protection, required reviews, automated linting, and policy checks aren't optional overhead—they're the enforcement layer that makes declarative infrastructure actually declarative. If you can merge a broken manifest to main in 30 seconds, your GitOps setup is purely ceremonial.

2. Make drift remediation automatic, not alerting. Configure your tools to auto-sync and auto-prune by default, then build the discipline to push changes through Git rather than around it. Yes, this requires cultural change. No, there's no shortcut. If auto-sync causes too much chaos, that's a signal your manifests don't accurately reflect intent—fix the manifests, not the sync policy.

3. Build a break-glass process that closes the loop. Emergency manual changes will happen. Accept it. But build a workflow where every out-of-band change triggers an immediate PR to codify it in Git. Make the PR template easy enough that engineers actually fill it out at 3 AM. Tools like kubectl neat can help export clean manifests from live cluster state.

4. Audit your ignoreDifferences configs ruthlessly. Every ignored difference is a place where your declared state and your actual state are allowed to diverge permanently. Review them quarterly. If a field needs to be ignored because something else manages it (like a VPA or HPA), document that dependency explicitly and make it visible in your dashboards.

5. Separate config repos by blast radius, not by team. Organizing your GitOps repos around failure domains—infrastructure, platform services, application workloads—gives you cleaner permissions and reduces the chance that a bad app deployment takes down cluster-level components. Team ownership can live within those boundaries, not define the boundary itself.

GitOps Is Worth It—But Not Like This

None of this means GitOps is a bad idea. The underlying principle—that your cluster state should be auditable, reproducible, and driven by version-controlled intent—is genuinely valuable. Teams that get it right ship faster, recover from incidents more reliably, and spend less time playing archaeology with their own infrastructure.

But the tooling doesn't enforce the discipline. ArgoCD and Flux are excellent at applying manifests. They're not excellent at preventing the organizational habits that make those manifests drift from reality in the first place.

The teams that make GitOps work at scale treat it as an operating model, not a tool installation. They invest in the repo structure, the review process, the break-glass workflows, and the cultural norms around direct cluster access. The ones who struggle installed the tool, pointed it at a repo, and called it done.

If your GitOps setup feels like expensive kubectl apply, that's because right now, it probably is. The good news is that the gap between where you are and where you want to be is mostly process, not technology. That's actually the easier problem to solve—if you're willing to be honest about what you've built so far.

All Articles

Keep Reading

Your API Server Is Quietly Choking—Here's How to Catch It Before Your Cluster Does

Your API Server Is Quietly Choking—Here's How to Catch It Before Your Cluster Does

Pod Placement Roulette: Taking Back Control of Your Kubernetes Scheduler

Pod Placement Roulette: Taking Back Control of Your Kubernetes Scheduler

The Hidden Tax of StatefulSets: Why Stateful Workloads Are Quietly Breaking Your Operations

The Hidden Tax of StatefulSets: Why Stateful Workloads Are Quietly Breaking Your Operations