Bumping Kubernetes and Cluster API
This document describes how to bump the Kubernetes and Cluster API (CAPI) versions across the project. It is primarily intended to be consumed by an AI coding agent (e.g. via /bump-k8s-capi 1.35 1.13), but the steps can also be followed manually.
The two arguments are the target Kubernetes minor version (e.g. 1.35) and the target CAPI minor version (e.g. 1.13).
Convention
- Kubernetes module version:
v0.MINOR.PATCH(e.g.v0.35.4for k8s 1.35) - CAPI version:
v1.MINOR.PATCH(e.g.v1.13.2) - Infrastructure provider dev version:
v1.CAPI_MINOR.99(e.g.v1.13.99) - CCM version major matches the Kubernetes minor (e.g. k8s 1.35 → CCM
v35.x.y)
Step 1: Research
Perform these lookups before making any changes. All values determined here are referenced in later steps.
Kubernetes-version-derived variables in test/e2e/config/gcp-ci.yaml
(KUBERNETES_VERSION, KUBERNETES_VERSION_GKE, CCM_VERSION,
KUBERNETES_VERSION_MANAGEMENT, and the upgrade-test FROM/TO/etcd/coredns/
image variables) are not looked up here — they derive automatically at
CI time from KUBERNETES_MINOR via hack/resolve-e2e-versions.sh. The one
thing to check by hand before setting KUBERNETES_MINOR is that GKE
actually supports it yet (1f below) — that’s a precondition for the bump,
not something to route around if it doesn’t.
1a. Latest CAPI patch
gh api repos/kubernetes-sigs/cluster-api/tags --paginate -q '.[].name' | grep '^v1.MINOR\.' | head -5
Pick the latest stable tag. Call it CAPI_VERSION (e.g. v1.13.2).
1b. CAPI dependency versions
Fetch CAPI’s go.mod to determine aligned dependency versions:
gh api "repos/kubernetes-sigs/cluster-api/contents/go.mod?ref=CAPI_VERSION" --jq '.content' | base64 -d
Extract:
sigs.k8s.io/controller-runtimeversion →CONTROLLER_RUNTIME_VERk8s.io/apiversion →K8S_MODULE_VER(e.g.v0.35.4)
1c. setup-envtest version
Fetch CAPI’s Makefile to align the setup-envtest CLI version:
gh api "repos/kubernetes-sigs/cluster-api/contents/Makefile?ref=CAPI_VERSION" --jq '.content' | base64 -d | grep '^SETUP_ENVTEST_VER :='
Call this SETUP_ENVTEST_VER. The controller-gen and conversion-gen versions are selected by hack/tools/go.mod, not copied from CAPI’s Makefile.
1d. GCP k8s-cloud-provider version
gh api repos/GoogleCloudPlatform/k8s-cloud-provider/tags -q '.[].name' | head -5
Pick the latest matching the target k8s minor. Call it K8S_CLOUD_PROVIDER_VER.
1e. kind version
Kind follows the version selected by the root go.mod; do not choose a separate Makefile pin. After updating dependencies in Step 3, check the selected version with go list -m sigs.k8s.io/kind.
1f. Confirm GKE supports the target minor
gcloud container get-server-config --region=us-central1 --format=json | \
jq -r '.channels[] | select(.channel=="REGULAR") | .validVersions[]' | \
grep '^K8S_MINOR\.' | sort -V | tail -5
If nothing matches, GKE hasn’t caught up to this minor yet. Don’t bump
KUBERNETES_MINOR past it — wait for GKE to catch up instead. This
project bumps to the trailing edge of Kubernetes releases, not the
bleeding edge, so this should be rare; the more likely failure mode over
time is the opposite one (GKE eventually dropping support for a minor
this project sat on too long), which the same check catches just as well.
Step 2: Review CAPI migration guide
Read the upstream CAPI migration guide for the version jump being performed:
https://cluster-api.sigs.k8s.io/developer/providers/migrations/v1.OLD_CAPI_MINOR-to-v1.NEW_CAPI_MINOR
For example, for a jump from CAPI v1.12 to v1.13: https://cluster-api.sigs.k8s.io/developer/providers/migrations/v1.12-to-v1.13
Do NOT apply everything listed there blindly. Instead:
- Removals and API changes: these are mandatory. Fix anything that applies to this project — the build will likely fail otherwise.
- Deprecation, Cluster API Contract changes, and Suggested changes for providers: review these carefully. Evaluate whether each suggestion applies to this project and whether it makes sense to adopt now. Some may be best deferred to a follow-up.
Step 3: Update go.mod (root)
Update the direct dependencies:
sigs.k8s.io/cluster-api CAPI_VERSION
sigs.k8s.io/cluster-api/test CAPI_VERSION
sigs.k8s.io/controller-runtime CONTROLLER_RUNTIME_VER
k8s.io/api K8S_MODULE_VER
k8s.io/apimachinery K8S_MODULE_VER
k8s.io/client-go K8S_MODULE_VER
k8s.io/component-base K8S_MODULE_VER
github.com/GoogleCloudPlatform/k8s-cloud-provider K8S_CLOUD_PROVIDER_VER
Then run:
go mod tidy
Indirect dependencies, including Kind, will be resolved automatically.
Step 4: Update hack/tools/go.mod
Update the sigs.k8s.io/cluster-api/hack/tools pseudo-version. To find the right version:
GOPROXY=https://proxy.golang.org go list -m -json "sigs.k8s.io/cluster-api/hack/tools@CAPI_VERSION"
Update the setup-envtest tool pin to the version found in Step 1c, then run:
cd hack/tools
go get -tool sigs.k8s.io/controller-runtime/tools/setup-envtest@SETUP_ENVTEST_VER
go mod tidy
Step 5: Verify derived tool versions
Do not manually edit KUBEBUILDER_ENVTEST_KUBERNETES_VERSION, CONTROLLER_GEN_VER, CONVERSION_GEN_VER, KIND_VER, KUBECTL_VER, or SETUP_ENVTEST_VER in the Makefile. They follow the root or tools module:
go list -m k8s.io/client-go sigs.k8s.io/kind github.com/onsi/ginkgo/v2
(cd hack/tools && go list -m sigs.k8s.io/controller-tools k8s.io/code-generator sigs.k8s.io/controller-runtime/tools/setup-envtest)
The k8s.io/client-go version v0.MINOR.PATCH determines kubectl v1.MINOR.PATCH and the envtest 1.MINOR selector. Verify the kubectl binary and a matching envtest release are available. The Ginkgo CLI follows the root go.mod; controller-gen, conversion-gen, and setup-envtest follow hack/tools/go.mod. Check that their selected versions are appropriate before regenerating files.
Do NOT update the independent pins GOLANG_VERSION, KUSTOMIZE_VER, CERT_MANAGER_VER, or CALICO_VERSION as part of the k8s/CAPI bump. GOLANGCI_LINT_VER follows .github/workflows/lint.yml and is handled by the Go bump guide.
Step 6: Update metadata.yaml
Add a new release series entry at the end of the list for the new CAPG minor version:
- major: 1
minor: NEW_CAPI_MINOR
contract: v1beta1
Step 7: Update test/e2e/data/shared/v1beta1/metadata.yaml
Add a new release series entry at the top of the list (this file is ordered newest-first):
- major: 1
minor: NEW_CAPI_MINOR
contract: v1beta1
Step 8: Update test/e2e/config/gcp-ci.yaml
Provider versions
Update CAPI core, bootstrap, and control-plane provider versions and URLs from OLD_CAPI_VERSION to CAPI_VERSION.
Update the GCP infrastructure provider dev version from v1.OLD_CAPI_MINOR.99 to v1.NEW_CAPI_MINOR.99.
Variables
KUBERNETES_MINOR: "K8S_MINOR"
That’s the only line to touch here. Every other Kubernetes-version
variable in this file (KUBERNETES_VERSION, KUBERNETES_VERSION_GKE,
CCM_VERSION, KUBERNETES_VERSION_MANAGEMENT, the upgrade-test FROM/TO/
etcd/coredns/image variables) is already "${VAR}" with no default, and
derives automatically at CI time via hack/resolve-e2e-versions.sh — don’t
add a hand-pinned fallback to any of them; that would just recreate the
staleness problem this whole scheme exists to avoid.
Optionally, run hack/resolve-e2e-versions.sh locally first (needs
gcloud/docker/git access; E2E_FLAVOR=all GCP_PROJECT=... GCP_REGION=...)
to catch a missing nightly image, CCM tag, or kindest/node image before
pushing, rather than waiting on a full CI run to find out.
Step 9: Update CCM manifest
test/e2e/data/ccm/gce-cloud-controller-manager.yaml already references
${CCM_VERSION} with no default — nothing to change here either, for the
same reason as Step 8.
Step 10: Regenerate CRDs
Changes to the derived controller-gen or conversion-gen versions may update generated files:
make generate
make manifests
Step 11: Build and verify
go build ./...
If go build fails with API changes (e.g. breaking changes in controller-runtime or CAPI), fix the Go source files to match the new API using the migration guide from Step 2.
Step 12: Fix lint issues
make lint
If lint reports deprecation warnings (e.g. SA1019 for deprecated interfaces or types), fix what can be fixed (migrate to new APIs) and add .golangci.yml exclusions for deprecations that cannot be resolved yet (e.g. upstream CAPI types still using the deprecated form).
Step 13: Run tests
make test
Step 14: Branch and commit
Create a branch named bump-k8s-MINOR-capi-MINOR (e.g. bump-k8s-135-capi-113) and commit the changes as two separate commits:
-
The version bump itself:
chore(bump): bump k8s to K8S_MINOR, CAPI to CAPI_VERSION -
Lint and build fixes (if any):
fix(lint): <describe the migration or fix>
Do NOT push or create a PR unless the user asks. When creating a PR, follow the template in .github/PULL_REQUEST_TEMPLATE.md.