Repository navigation
A reconcile that needs external DNS makes a broken cluster unrepairable by GitOps #3405
Description
🤖 Generated by the Agentic Engineer
Evidence
During today's DNS incident (#3404), a single bad network policy became unrecoverable through GitOps. The failure chain was circular:
- A policy applied by the
infrastructureKustomization broke cluster DNS. infrastructure-controllersthen failed to build, because its kustomize render fetches resources over HTTPS and that needs DNS:
kustomize build failed: ... Could not resolve host: raw.githubusercontent.cominfrastructuredependsOninfrastructure-controllers, so it stopped applying — andinfrastructureis the unit that owns the offending policy.
The change that would fix the cluster could not be applied because the cluster was broken. Recovery required a manual kubectl delete against prod, in a specific order relative to the merge, or Flux would have re-applied the same policy.
Three resources are fetched at reconcile time today:
controllers/kubelet-serving-cert-approver/→raw.githubusercontent.com/.../ha-install.yamlcontrollers/origin-ca-issuer/→ tworaw.githubusercontent.com/.../*.yamlCRDs
Problem
Every reconcile of infrastructure-controllers depends on the cluster reaching raw.githubusercontent.com. That makes cluster-internal networking a hard prerequisite for the mechanism we rely on to repair cluster-internal networking, and it removes the property GitOps is supposed to give us: that reverting the commit restores the cluster.
It is also a supply-chain and availability exposure independent of this incident — an upstream outage, a repo rename, or a deleted tag stalls reconciliation of every controller in that layer.
Hypothesis
Vendoring those manifests into the repository makes infrastructure-controllers build offline. The layer then reconciles from local content alone, so a bad change stays revertible by merge, and recovery does not need a human with cluster credentials.
Success signal
kubectl kustomize k8s/providers/hetzner/infrastructure/controllersrenders with no network access.- Baseline: 3 remote fetches at reconcile time. Target: 0.
- Guardrail: rendered output is byte-identical to the current render, so this is behaviour-preserving.
Smallest useful change
Vendor the three manifests under the directories that consume them, keeping the pinned upstream versions unchanged, and point Renovate's existing github-releases custom manager at the vendored copies so version bumps stay automatic. Record the upstream source and tag next to each vendored file.
Acceptance criteria
- No
kustomization.yamlunderk8s/lists anhttps://resource. - The controllers layer renders identically before and after (diff of the two renders is empty).
- Renovate still proposes upgrades for all three, and a bump updates the vendored content.
- A CI check fails if a remote resource URL is reintroduced, so this cannot regress silently.
Rough size: S–M. Mechanical, but touches three controller directories and the Renovate config.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Effort
Projects
- StatusShow more project fields✅ Done
Activity
devantler commentedon Sep 24, 2026
Verified against
main(93d71cb3): the offline-render goal is delivered by #3635kustomization.yamlunderk8s/lists anhttps://resource. The three former remote fetches are committed files underkubelet-serving-cert-approver/andorigin-ca-issuer/.kubectl kustomize k8s/providers/hetzner/infrastructure/controllersexits 0), so the circular DNS dependency this issue describes is gone.scripts/guard-render-remote-resources.sh k8sruns in CI and reports788 in-repo, 0 tracked exception(s). Its regression suite passes (43 assertions).This issue stays open until #4136 closes.
chore: agent-claim #3405 nonce=4164ef0a2788655e6da84c6e670e3eb4
devantler commentedon Oct 4, 2026
All acceptance criteria are now met: #3635 removed the reconcile-time fetches, and #4136 delivered the remaining source tracking. Today's production controllers render is byte-identical with networking allowed and with operating-system networking denied. The repository census finds 835 local references across 122 kustomizations and zero remote exceptions.
The remote-resource guard's 43 assertions and the source updater's 46 assertions pass, including rejected remote reintroduction, changed source pins, edited vendored bytes, and safe refresh behavior. Both source tracking and the CI guard are present on main. Closing as delivered.