Skip to main content

Kubernetes 1.36 Just Removed gitRepo Volumes and IPVS kube-proxy — Here's the Audit Your Cluster API Fleet Needs Before You Upgrade

8 min readDora NodaDora Noda
Share
On this page

Kubernetes v1.36 "Haru" doesn't warn you before it breaks things — it just breaks them. Two features that used to fail soft, with a deprecation notice in kubectl apply output, now fail hard the moment the kubelet starts: a Pod referencing a gitRepo volume gets scheduled and then refuses to run, and a node still configured for IPVS-mode kube-proxy doesn't get a warning banner, it gets a kube-proxy that won't start.

Neither removal is news in the sense of being unannounced — gitRepo volumes have been deprecated since Kubernetes v1.11, and IPVS mode was flagged for removal back in v1.35. What's news is that v1.36, released April 22, 2026, is the release where "deprecated" became "gone," and a Cluster API-managed fleet running on owned hardware has a narrower, more specific blind spot on both than the release notes suggest. Here's exactly what changed, why it's overdue on the security side, and the commands to run against your fleet before you roll a single MachineDeployment to 1.36.

What Actually Broke, and When

Both removals landed in the same release, but they fail differently — and knowing which failure mode to expect changes how you audit for it:

What changed in v1.36Failure mode
gitRepo volumesPermanently disabled. The API server still accepts a Pod spec that references one, but the kubelet refuses to run it and returns an error.Pod gets scheduled, then sits in a failed/erroring state — looks like a runtime problem, not a spec problem
IPVS kube-proxy modeFully removed after deprecation in v1.35. iptables and nftables are now the only supported proxy backends.kube-proxy fails to start on any node still configured for --proxy-mode=ipvs

Both are permanent — there's no feature gate to flip back on, unlike a normal Kubernetes deprecation cycle that gives you a release or two of "still works if you ask for it." If either one is live anywhere in your fleet, you find out at upgrade time, not before.

gitRepo Volumes: Why It Took Eight Years to Kill a Known RCE

The gitRepo volume type did exactly one thing: clone a Git repository into a Pod at start time, using the node's own git binary, run as the kubelet's privileges. That convenience — no init container, no sidecar, just declare a repo URL in the volume spec — is also exactly why it was dangerous. The kubelet was executing git clone (and, critically, Git's post-checkout hooks) with attacker-influenced input and no sandboxing beyond whatever the container boundary happened to catch.

The vulnerability history reads like a slow-motion warning the project kept issuing without ever closing the door:

  • 2018 — CVE-2018-11235: a malicious Git submodule could execute arbitrary commands as root via the gitRepo driver.
  • November 2024 — CVE-2024-10220 (CVSS 8.1, High): a path-traversal flaw let any principal with Pod-create permissions run arbitrary commands on the host node by crafting a gitRepo volume spec that abused Git's hooks folder — full node compromise from a namespace-scoped Pod create.
  • March 2025 — CVE-2025-1767: a follow-up disclosure covering inadvertent local-repository access through the same volume type.

Three CVEs against one field, over seven years, on a feature every security advisory recommended disabling rather than patching — because a real fix meant changing what the field did, not hardening it. v1.36 is the release where the project stopped issuing advisories and just removed the code path.

The audit. Before you upgrade, find every gitRepo volume reference across your fleet — in live manifests, Helm charts, and any Cluster API templates that provision workload-cluster add-ons:

bash
kubectl get pods --all-namespaces -o json | \
  jq '.items[] | select(.spec.volumes[]?.gitRepo != null) |
      {name: .metadata.name, namespace: .metadata.namespace,
       repo: (.spec.volumes[] | select(.gitRepo) | .gitRepo.repository)}'

Run that against every tenant cluster your Cluster API management cluster provisions, not just the management cluster itself — gitRepo was a workload-facing feature, so it's tenant Pods you're checking, not platform components. Grep your Helm charts and raw YAML too; a chart that hasn't been touched since 2019 is exactly where this still lives.

The fix is the same one Kubernetes has recommended since v1.21: move the clone into an init container and mount the result.

yaml
# Before — removed in 1.36, Pod will fail to run
volumes:
  - name: repo
    gitRepo:
      repository: "https://github.com/org/app.git"
      revision: "main"
 
# After — init container clones, main container mounts the result
initContainers:
  - name: git-clone
    image: alpine/git
    args: ["clone", "--depth=1", "-b", "main",
           "https://github.com/org/app.git", "/repo"]
    volumeMounts:
      - name: repo
        mountPath: /repo
containers:
  - name: app
    volumeMounts:
      - name: repo
        mountPath: /repo
volumes:
  - name: repo
    emptyDir: {}

This isn't a workaround — it's strictly better. The init container runs as a normal, sandboxed container instead of the kubelet shelling out on the host, and you get to pin the image, set resource limits, and control exactly what git command runs.

IPVS Removal Hits Cluster API Fleets in a Blind Spot the Grep Won't Catch

The gitRepo audit above is straightforward because the feature is used in Pod specs — grep the specs, find every instance. IPVS mode is different, and specifically harder to audit on a Cluster API-managed fleet, for a reason that has nothing to do with Kubernetes itself: Cluster API's own bootstrap provider has never had a native way to configure kube-proxy.

KubeadmConfig and KubeadmConfigTemplate — the CRDs CABPK (Cluster API Bootstrap Provider Kubeadm) uses to generate a node's kubeadm configuration — support InitConfiguration, ClusterConfiguration, and JoinConfiguration. They do not support KubeProxyConfiguration. This is a long-open gap (cluster-api#4512, cluster-api#10237), and the most common reason anyone ran into it was wanting IPVS mode for its performance characteristics on larger clusters.

Since there's no first-class field for it, the way teams actually turned on IPVS on a CAPH-provisioned Hetzner cluster was a workaround: a preKubeadmCommand script injected into the KubeadmConfigTemplate that writes a KubeProxyConfiguration YAML into the kubeadm init file before kubeadm runs. That script doesn't look anything like ipvs in a manifest search — it's a shell snippet buried in a bootstrap CRD's preKubeadmCommands field, several layers removed from anything a "grep my YAML for ipvs" audit would catch. A standard deprecation audit, run against Pod specs and Service manifests, will come back clean on a fleet that's actually running IPVS on every node.

The audit that actually finds it:

bash
# Check what mode is actually running on each node
kubectl get configmap kube-proxy -n kube-system -o yaml | grep -A2 "mode:"
 
# Check every KubeadmConfigTemplate for the preKubeadmCommand workaround
kubectl get kubeadmconfigtemplate -A -o yaml | grep -B5 -A5 "ipvs"
 
# Same check against KubeadmControlPlane objects, if you're not
# using a separate KubeadmConfigTemplate for control-plane nodes
kubectl get kubeadmcontrolplane -A -o yaml | grep -B5 -A5 "ipvs"

If either comes back positive, you have two migration targets, not one preference:

  • nftables — the recommended path. GA since Kubernetes v1.33, built specifically to fix the performance problems that made people reach for IPVS in the first place. Requires Linux kernel 5.13+, which most current node images already ship.
  • iptables — still the upstream default for backward compatibility, and the safe fallback if a node image's kernel doesn't clear the 5.13 bar yet.

For a Hetzner-based fleet on current cloud images, kernel version usually isn't the blocker — the actual work is finding and removing the preKubeadmCommand hack from your KubeadmConfigTemplate, then setting the mode through whatever mechanism your CAPI bootstrap provider does support for post-kubeadm configuration (a KubeProxyConfiguration object applied as a ConfigMap, or your provider's own kube-proxy add-on hook), and rolling it out via a MachineDeployment update rather than hand-editing running nodes.

The Pre-Upgrade Checklist

Before rolling any MachineDeployment or KubeadmControlPlane to Kubernetes 1.36, run all four checks below against every cluster the fleet manages — not just the management cluster:

  1. kubectl get pods --all-namespaces -o json | jq '.items[] | select(.spec.volumes[]?.gitRepo != null)' — zero results before upgrading.
  2. grep -r "gitRepo:" charts/ manifests/ — catch anything not currently deployed but still in source.
  3. kubectl get configmap kube-proxy -n kube-system -o yaml | grep "mode:" — confirm every workload cluster reports iptables or nftables, never ipvs.
  4. kubectl get kubeadmconfigtemplate,kubeadmcontrolplane -A -o yaml | grep -i ipvs — confirm no bootstrap config still tries to set IPVS mode on the next node that joins, even if no current node is running it.

Check four matters even if check three comes back clean: a KubeadmConfigTemplate still carrying the IPVS preKubeadmCommand will silently try to configure a mode that no longer exists the next time a MachineDeployment scales up or replaces a node — turning a clean current fleet into a broken one the next time autoscaling fires.

Why This Belongs in the Runbook, Not the Release Notes

Both of these removals are correct calls — one closes a seven-year-old root-execution vector, the other retires a proxy mode the project no longer wants to maintain in favor of a faster replacement. Neither is a reason to delay upgrading. But "grep the release notes and hope nothing in your fleet matches" isn't an upgrade strategy, and the IPVS case specifically shows why: the place a feature actually gets configured on a Cluster API fleet isn't always the place a normal audit looks.

That's the argument for baking pre-upgrade audits like these into the platform layer itself, not into a checklist an operator has to remember to run. A self-hosted, Cluster API-based PaaS that owns its own node provisioning is in a position to run exactly these checks automatically before a MachineDeployment rolls to a new Kubernetes minor version — catching a stray gitRepo volume or an orphaned preKubeadmCommand before it turns into a failed rollout, instead of after.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, built on Cluster API from the ground up. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex