
K8s Cluster Api
- 40 installs
- 22 repo stars
- Updated August 1, 2026
- itechmeat/llm-code
Provision, upgrade, and operate Kubernetes clusters with Cluster API (CAPI) using clusterctl, ClusterClass, and GitOps integration.
About
Covers Kubernetes Cluster API v1.12 for declarative cluster lifecycle management via clusterctl and ClusterClass, with scripts for health checks, backup, and migration. A developer uses it when managing Kubernetes clusters across providers.
- Declarative cluster lifecycle (create, scale, upgrade, destroy) via CAPI
- clusterctl and ClusterClass workflows with DR and Prometheus templates
K8s Cluster Api by the numbers
- 40 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #811 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/itechmeat/llm-code --skill k8s-cluster-apiAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 40 |
|---|---|
| repo stars | ★ 22 |
| Last updated | August 1, 2026 |
| Repository | itechmeat/llm-code ↗ |
What it does
Provision, upgrade, and operate Kubernetes clusters with Cluster API (CAPI) using clusterctl, ClusterClass, and GitOps integration.
Files
Kubernetes Cluster API
Kubernetes Cluster API (CAPI) is a Kubernetes sub-project focused on providing declarative APIs and tooling to simplify provisioning, upgrading, and operating multiple Kubernetes clusters.
Overview
Started by SIG Cluster Lifecycle, Cluster API uses Kubernetes-style APIs and patterns to automate cluster lifecycle management. The infrastructure (VMs, networks, load balancers, VPCs) and Kubernetes configuration are defined declaratively, enabling consistent and repeatable cluster deployments across environments.
Why Cluster API?
While kubeadm reduces installation complexity, it doesn't address day-to-day cluster management:
- How to consistently provision infrastructure across providers and locations?
- How to automate cluster lifecycle (upgrades, deletion)?
- How to scale processes to manage any number of clusters?
Cluster API addresses these gaps with declarative, Kubernetes-style APIs that automate cluster creation, configuration, and management.
Goals
- Manage lifecycle (create, scale, upgrade, destroy) of Kubernetes-conformant clusters via declarative API
- Work in different environments (on-premises and cloud)
- Define common operations with swappable implementations
- Reuse existing ecosystem components (cluster-autoscaler, node-problem-detector)
- Provide transition path for existing tools to adopt incrementally
Non-Goals
- Add APIs to Kubernetes core
- Manage infrastructure unrelated to Kubernetes clusters
- Force all lifecycle products to use these APIs
- Manage non-CAPI provisioned clusters
- Manage single cluster spanning multiple providers
- Configure machines after create/upgrade
Quick Navigation
| Topic | Reference |
|---|---|
| Getting Started | getting-started.md |
| Concepts & Architecture | concepts.md |
| Certificates | certificates.md |
| Bootstrap (Kubeadm/MicroK8s) | bootstrap.md |
| Cluster Operations | cluster-operations.md |
| Experimental Features | experimental.md |
| clusterctl CLI | clusterctl.md |
| Developer Guide | developer.md |
| Troubleshooting | troubleshooting.md |
| API Reference & Providers | api-reference.md |
| Security & PSS | security.md |
| Controllers | controllers.md |
| Version Migrations | migrations.md |
| FAQ | faq.md |
| Best Practices | best-practices.md |
When to Use
- Provisioning Kubernetes clusters across multiple infrastructure providers
- Managing cluster lifecycle (create, scale, upgrade, destroy)
- Automating cluster operations with declarative APIs
- Implementing GitOps workflows for cluster management
- Building custom infrastructure providers
Core Concepts
Architecture
┌─────────────────────────────────────────┐
│ Management Cluster │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ CAPI Core │ │ Infrastructure │ │
│ │ Controllers │ │ Provider │ │
│ └─────────────┘ └─────────────────┘ │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ Bootstrap │ │ Control Plane │ │
│ │ Provider │ │ Provider │ │
│ └─────────────┘ └─────────────────┘ │
└─────────────────────┬───────────────────┘
│ manages
┌───────────┴───────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Workload │ │ Workload │
│ Cluster 1 │ │ Cluster N │
└─────────────────┘ └─────────────────┘Key Components
| Component | Purpose |
|---|---|
| Management Cluster | Hosts CAPI controllers, manages workloads |
| Workload Cluster | User clusters managed by CAPI |
| Infrastructure Provider | Provisions VMs, networks, load balancers |
| Bootstrap Provider | Generates cloud-init/ignition configs |
| Control Plane Provider | Manages control plane nodes lifecycle |
Core Resources
| Resource | Description |
|---|---|
| Cluster | Represents a Kubernetes cluster |
| Machine | Represents a single node/VM |
| MachineSet | Manages replicas of Machines |
| MachineDeployment | Declarative updates for MachineSets |
| MachineHealthCheck | Automatic remediation of unhealthy nodes |
Quick Start
# Install clusterctl
curl -L https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.12.0/clusterctl-linux-amd64 -o clusterctl
chmod +x clusterctl
sudo mv clusterctl /usr/local/bin/
# Initialize management cluster
clusterctl init --infrastructure docker
# Create workload cluster
clusterctl generate cluster my-cluster --kubernetes-version v1.32.0 --control-plane-machine-count 1 --worker-machine-count 3 | kubectl apply -f -
# Get cluster kubeconfig
clusterctl get kubeconfig my-cluster > my-cluster.kubeconfig
# Delete cluster
kubectl delete cluster my-clusterCommon Workflows
Cluster Lifecycle
# Create cluster from template
clusterctl generate cluster prod-cluster \
--infrastructure aws \
--kubernetes-version v1.32.0 \
--control-plane-machine-count 3 \
--worker-machine-count 5 \
| kubectl apply -f -
# Scale workers
kubectl scale machinedeployment prod-cluster-md-0 --replicas=10
# Upgrade Kubernetes version
kubectl patch cluster prod-cluster --type merge -p '{"spec":{"topology":{"version":"v1.33.0"}}}'
# Move cluster to new management cluster
clusterctl move --to-kubeconfig target-mgmt.kubeconfigHealth Monitoring
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineHealthCheck
metadata:
name: my-cluster-mhc
spec:
clusterName: my-cluster
maxUnhealthy: 40%
nodeStartupTimeout: 10m
selector:
matchLabels:
cluster.x-k8s.io/cluster-name: my-cluster
unhealthyConditions:
- type: Ready
status: "False"
timeout: 5m
- type: Ready
status: Unknown
timeout: 5mCritical Prohibitions
- Do NOT modify management cluster directly without proper backup
- Do NOT delete Machine objects directly (use MachineDeployment scale)
- Do NOT mix provider versions without checking compatibility
- Do NOT skip cluster upgrade steps (control plane before workers)
- Do NOT ignore MachineHealthCheck alerts
Release Highlights (1.13.x)
- Kubernetes compatibility moves to management clusters
v1.32.x -> v1.36.xand workload clustersv1.30.x -> v1.36.xby the1.13.2line. v1alpha3andv1alpha4API versions are now removed; providers should keep moving toward thev1beta2contract becausev1beta1remains on the path to becoming unserved in a later release.- Cluster topology can now drive
rolloutAfterfor both control plane andMachineDeploymentresources. - KubeadmControlPlane improves remediation tolerance for multiple failures and better surfaces common join/remediation symptoms.
PriorityQueueandReconcilerRateLimitingare now beta defaults in the1.13line, which can change reconciliation behavior under load.
Scripts
Go-based tools in scripts/. Run via go run ./tool-name from the scripts directory.
| Tool | Purpose |
|---|---|
validate-manifests | Validate YAML manifests against CRD schemas |
run-clusterctl-diagnose | Run clusterctl describe and save diagnostic report |
migration-checker | Check v1beta1→v1beta2 migration readiness |
check-cluster-health | Analyze conditions across all cluster objects |
analyze-conditions | Parse and report False/Unknown conditions |
scaffold-provider | Generate new provider directory structure |
generate-cluster-template | Generate templates from ClusterClass |
export-cluster-state | Export cluster state for backup/move |
audit-security | Check PSS compliance and security posture |
timeline-events | Build provisioning event timeline |
compare-versions | Compare CAPI version specs and API changes |
check-provider-contract | Verify provider CRD compliance with contracts |
lint-cluster-templates | Lint and validate CAPI manifests |
Assets
Reusable templates in assets/:
- Cluster templates:
cluster-minimal.yaml,cluster-production.yaml,cluster-clusterclass.yaml,clusterclass-example.yaml - Provider configs:
docker-quickstart.yaml,aws-credentials.yaml,azure-credentials.yaml,provider-matrix.md - Operations:
upgrade-checklist.md,migration-v1beta2.md,troubleshooting-flow.md,security-audit-report.md,dr-backup-restore.md,etcd-backup.yaml - GitOps:
argocd-cluster-app.yaml,flux-kustomization.yaml,gitops-rbac.yaml - Monitoring:
prometheus-alerts.yaml
Links
# ArgoCD Application for Cluster API Managed Cluster
#
# This Application manages a workload cluster through GitOps.
# The cluster manifests are stored in a Git repository and
# ArgoCD applies them to the management cluster.
#
# Prerequisites:
# - ArgoCD installed on management cluster
# - Git repository with cluster manifests
# - ServiceAccount with CAPI permissions
#
# Usage:
# 1. Store your Cluster/ClusterClass manifests in Git
# 2. Customize this Application with your repo URL
# 3. Apply to management cluster: kubectl apply -f argocd-cluster-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: workload-cluster-prod
namespace: argocd
# Finalizer ensures resources are deleted when App is deleted
finalizers:
- resources-finalizer.argocd.argoproj.io
labels:
app.kubernetes.io/component: cluster
cluster-api.cattle.io/managed: "true"
spec:
project: default
source:
# Repository containing cluster manifests
repoURL: https://github.com/your-org/clusters.git
targetRevision: main
path: clusters/prod
# Optional: use Kustomize
# kustomize:
# namePrefix: prod-
# Optional: use Helm
# helm:
# releaseName: prod-cluster
# valueFiles:
# - values-prod.yaml
destination:
# Management cluster where CAPI runs
server: https://kubernetes.default.svc
namespace: clusters
syncPolicy:
automated:
# Auto-create namespace if missing
prune: false # CAUTION: Set to true only if you want cluster deletion on manifest removal
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- PruneLast=true
- ApplyOutOfSyncOnly=true
# Respect CAPI ordering
- RespectIgnoreDifferences=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
# Ignore status fields managed by CAPI controllers
ignoreDifferences:
- group: cluster.x-k8s.io
kind: Cluster
jsonPointers:
- /status
- group: cluster.x-k8s.io
kind: Machine
jsonPointers:
- /status
- group: cluster.x-k8s.io
kind: MachineDeployment
jsonPointers:
- /status
- group: cluster.x-k8s.io
kind: MachineSet
jsonPointers:
- /status
- group: controlplane.cluster.x-k8s.io
kind: KubeadmControlPlane
jsonPointers:
- /status
- group: infrastructure.cluster.x-k8s.io
kind: "*"
jsonPointers:
- /status
# Health checks for CAPI resources
# ArgoCD will show cluster as healthy when conditions are met
# Note: Requires ArgoCD 2.4+ with custom health checks configured
# See: https://argo-cd.readthedocs.io/en/stable/operator-manual/health/
---
# ApplicationSet for multi-cluster management
# Generates one Application per cluster definition in Git repository
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: workload-clusters
namespace: argocd
spec:
generators:
# Generate from directories in clusters/ path
- git:
repoURL: https://github.com/your-org/clusters.git
revision: main
directories:
- path: clusters/*
- path: clusters/archive/*
exclude: true
template:
metadata:
name: "cluster-{{path.basename}}"
namespace: argocd
labels:
cluster-name: "{{path.basename}}"
spec:
project: default
source:
repoURL: https://github.com/your-org/clusters.git
targetRevision: main
path: "{{path}}"
destination:
server: https://kubernetes.default.svc
namespace: clusters
syncPolicy:
automated:
prune: false
selfHeal: true
syncOptions:
- CreateNamespace=true
---
# AppProject for cluster management with restricted permissions
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: clusters
namespace: argocd
spec:
description: Cluster API managed clusters
sourceRepos:
- "https://github.com/your-org/clusters.git"
destinations:
- namespace: clusters
server: https://kubernetes.default.svc
- namespace: "cluster-*"
server: https://kubernetes.default.svc
# Only allow CAPI resources
clusterResourceWhitelist:
- group: cluster.x-k8s.io
kind: "*"
- group: controlplane.cluster.x-k8s.io
kind: "*"
- group: infrastructure.cluster.x-k8s.io
kind: "*"
- group: bootstrap.cluster.x-k8s.io
kind: "*"
- group: addons.cluster.x-k8s.io
kind: "*"
- group: ""
kind: Namespace
- group: ""
kind: Secret
namespaceResourceWhitelist:
- group: "*"
kind: "*"
# Deny changes to management cluster components
clusterResourceBlacklist:
- group: ""
kind: Node
- group: rbac.authorization.k8s.io
kind: ClusterRole
- group: rbac.authorization.k8s.io
kind: ClusterRoleBinding
# AWS Provider Credentials Template
# Create Secret for AWS credentials used by CAPA (Cluster API Provider AWS)
# IMPORTANT: Replace placeholder values before applying
---
apiVersion: v1
kind: Secret
metadata:
name: capa-manager-bootstrap-credentials
namespace: capa-system # Default namespace for AWS provider
type: Opaque
stringData:
# AWS programmatic access credentials
# Create an IAM user with appropriate permissions (see below)
credentials: |
[default]
aws_access_key_id = <YOUR_ACCESS_KEY_ID>
aws_secret_access_key = <YOUR_SECRET_ACCESS_KEY>
# Optional: for session tokens (temporary credentials)
# aws_session_token = <YOUR_SESSION_TOKEN>
region = us-east-1
---
# Alternative: Using eksctl to create the required IAM resources
# This creates the policies and roles needed by CAPA
#
# eksctl utils associate-iam-oidc-provider --cluster <management-cluster> --approve
#
# eksctl create iamserviceaccount \
# --name capa-controller-manager \
# --namespace capa-system \
# --cluster <management-cluster> \
# --attach-policy-arn arn:aws:iam::aws:policy/AmazonEC2FullAccess \
# --attach-policy-arn arn:aws:iam::aws:policy/IAMFullAccess \
# --attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
# --approve
---
# IAM Policy for CAPA controller (minimum permissions)
# Create this policy and attach to the IAM user/role
#
# {
# "Version": "2012-10-17",
# "Statement": [
# {
# "Effect": "Allow",
# "Action": [
# "ec2:*",
# "elasticloadbalancing:*",
# "autoscaling:*",
# "iam:CreateServiceLinkedRole",
# "iam:PassRole",
# "iam:GetInstanceProfile",
# "iam:CreateInstanceProfile",
# "iam:DeleteInstanceProfile",
# "iam:AddRoleToInstanceProfile",
# "iam:RemoveRoleFromInstanceProfile",
# "ssm:GetParameter"
# ],
# "Resource": "*"
# },
# {
# "Effect": "Allow",
# "Action": [
# "iam:CreateRole",
# "iam:DeleteRole",
# "iam:AttachRolePolicy",
# "iam:DetachRolePolicy",
# "iam:GetRole",
# "iam:ListAttachedRolePolicies"
# ],
# "Resource": "arn:aws:iam::*:role/control-plane.cluster-api-provider-aws.sigs.k8s.io"
# },
# {
# "Effect": "Allow",
# "Action": [
# "iam:CreateRole",
# "iam:DeleteRole",
# "iam:AttachRolePolicy",
# "iam:DetachRolePolicy",
# "iam:GetRole",
# "iam:ListAttachedRolePolicies"
# ],
# "Resource": "arn:aws:iam::*:role/nodes.cluster-api-provider-aws.sigs.k8s.io"
# }
# ]
# }
---
# Environment variables for clusterctl (place in ~/.bashrc or export before init)
#
# export AWS_REGION=us-east-1
# export AWS_ACCESS_KEY_ID=<YOUR_ACCESS_KEY_ID>
# export AWS_SECRET_ACCESS_KEY=<YOUR_SECRET_ACCESS_KEY>
#
# # For EKS-based control plane
# export AWS_SESSION_TOKEN=<YOUR_SESSION_TOKEN> # if using temporary credentials
#
# # SSH key for node access
# export AWS_SSH_KEY_NAME=<YOUR_SSH_KEY_NAME>
#
# # Control plane settings
# export AWS_CONTROL_PLANE_MACHINE_TYPE=m5.large
# export CONTROL_PLANE_MACHINE_COUNT=3
#
# # Worker settings
# export AWS_NODE_MACHINE_TYPE=m5.xlarge
# export WORKER_MACHINE_COUNT=3
---
# Initialize AWS provider:
#
# export AWS_B64ENCODED_CREDENTIALS=$(clusterctl generate provider aws -f)
# clusterctl init --infrastructure aws
---
# ClusterAWS identity types available:
#
# 1. AWSClusterStaticIdentity - static AWS credentials (this template)
# 2. AWSClusterRoleIdentity - assume IAM role
# 3. AWSClusterControllerIdentity - use controller's credentials
#
# Example AWSClusterRoleIdentity:
#
# apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
# kind: AWSClusterRoleIdentity
# metadata:
# name: cross-account-role
# spec:
# allowedNamespaces:
# list:
# - default
# roleARN: arn:aws:iam::ACCOUNT_ID:role/CAPIRole
# sourceIdentityRef:
# kind: AWSClusterControllerIdentity
# name: default
# Azure Provider Credentials Template
# Create Secret for Azure credentials used by CAPZ (Cluster API Provider Azure)
# IMPORTANT: Replace placeholder values before applying
---
apiVersion: v1
kind: Secret
metadata:
name: capz-manager-bootstrap-credentials
namespace: capz-system
type: Opaque
stringData:
# Service Principal credentials (JSON format)
# Create using: az ad sp create-for-rbac --sdk-auth
clientSecret: |
{
"clientId": "<YOUR_CLIENT_ID>",
"clientSecret": "<YOUR_CLIENT_SECRET>",
"subscriptionId": "<YOUR_SUBSCRIPTION_ID>",
"tenantId": "<YOUR_TENANT_ID>",
"activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
"resourceManagerEndpointUrl": "https://management.azure.com/",
"activeDirectoryGraphResourceId": "https://graph.windows.net/",
"sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
"galleryEndpointUrl": "https://gallery.azure.com/",
"managementEndpointUrl": "https://management.core.windows.net/"
}
---
# Steps to create Azure Service Principal:
#
# 1. Login to Azure CLI:
# az login
#
# 2. Set subscription:
# az account set --subscription "<SUBSCRIPTION_ID>"
#
# 3. Create Service Principal with Contributor role:
# az ad sp create-for-rbac \
# --name "cluster-api-provider-azure" \
# --role Contributor \
# --scopes "/subscriptions/<SUBSCRIPTION_ID>" \
# --sdk-auth > azure-credentials.json
#
# 4. The output JSON contains all required credentials
#
# Optional: For more restrictive permissions, use custom role:
#
# az role definition create --role-definition '{
# "Name": "CAPZ Minimum Permissions",
# "Description": "Minimum permissions for Cluster API Provider Azure",
# "Actions": [
# "Microsoft.Compute/*",
# "Microsoft.Network/*",
# "Microsoft.Resources/*",
# "Microsoft.Authorization/*/read",
# "Microsoft.ManagedIdentity/userAssignedIdentities/*"
# ],
# "NotActions": [],
# "AssignableScopes": ["/subscriptions/<SUBSCRIPTION_ID>"]
# }'
---
# Environment variables for clusterctl (place in ~/.bashrc or export before init)
#
# # Azure subscription
# export AZURE_SUBSCRIPTION_ID="<YOUR_SUBSCRIPTION_ID>"
# export AZURE_TENANT_ID="<YOUR_TENANT_ID>"
# export AZURE_CLIENT_ID="<YOUR_CLIENT_ID>"
# export AZURE_CLIENT_SECRET="<YOUR_CLIENT_SECRET>"
#
# # Cluster settings
# export AZURE_LOCATION="eastus"
# export AZURE_RESOURCE_GROUP="my-cluster-rg"
#
# # Control plane settings
# export AZURE_CONTROL_PLANE_MACHINE_TYPE="Standard_D2s_v3"
# export CONTROL_PLANE_MACHINE_COUNT=3
#
# # Worker settings
# export AZURE_NODE_MACHINE_TYPE="Standard_D4s_v3"
# export WORKER_MACHINE_COUNT=3
#
# # SSH key
# export AZURE_SSH_PUBLIC_KEY_B64=$(cat ~/.ssh/id_rsa.pub | base64 | tr -d '\n')
---
# Initialize Azure provider:
#
# # Generate base64 encoded credentials
# export AZURE_SUBSCRIPTION_ID_B64=$(echo -n "$AZURE_SUBSCRIPTION_ID" | base64)
# export AZURE_TENANT_ID_B64=$(echo -n "$AZURE_TENANT_ID" | base64)
# export AZURE_CLIENT_ID_B64=$(echo -n "$AZURE_CLIENT_ID" | base64)
# export AZURE_CLIENT_SECRET_B64=$(echo -n "$AZURE_CLIENT_SECRET" | base64)
#
# clusterctl init --infrastructure azure
---
# Azure identity types available:
#
# 1. AzureClusterIdentity with Service Principal
# 2. AzureClusterIdentity with User-Assigned Managed Identity
# 3. AzureClusterIdentity with System-Assigned Managed Identity (AKS)
#
# Example Service Principal identity:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AzureClusterIdentity
metadata:
name: cluster-identity
namespace: default
spec:
type: ServicePrincipal
allowedNamespaces:
list:
- default
tenantID: "<YOUR_TENANT_ID>"
clientID: "<YOUR_CLIENT_ID>"
clientSecret:
name: capz-manager-bootstrap-credentials
namespace: capz-system
---
# Example User-Assigned Managed Identity:
#
# apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
# kind: AzureClusterIdentity
# metadata:
# name: cluster-identity-msi
# namespace: default
# spec:
# type: UserAssignedMSI
# allowedNamespaces:
# list:
# - default
# tenantID: "<YOUR_TENANT_ID>"
# clientID: "<MANAGED_IDENTITY_CLIENT_ID>"
# resourceID: "/subscriptions/<SUB_ID>/resourceGroups/<RG>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<IDENTITY_NAME>"
# Cluster using ClusterClass Topology
# Requires ClusterClass to be deployed first (see clusterclass-example.yaml)
# This is the modern recommended approach for cluster management
---
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: ${CLUSTER_NAME:=topology-cluster}
namespace: ${NAMESPACE:=default}
labels:
environment: ${ENVIRONMENT:=development}
spec:
# Use topology instead of direct references
topology:
# Reference the ClusterClass
class: ${CLUSTER_CLASS:=quick-start}
version: ${KUBERNETES_VERSION:=v1.29.0}
# Control plane configuration
controlPlane:
replicas: ${CONTROL_PLANE_REPLICAS:=1}
# Override specific control plane settings
# metadata:
# labels:
# custom-label: value
# Worker configuration
workers:
machineDeployments:
- class: default-worker
name: md-0
replicas: ${WORKER_REPLICAS:=2}
# Optional: failure domain (availability zone)
# failureDomain: us-east-1a
# Optional: override worker metadata
# metadata:
# labels:
# workload-type: general
# Optional: override machine deployment strategy
# strategy:
# type: RollingUpdate
# rollingUpdate:
# maxSurge: 1
# maxUnavailable: 0
# Additional worker pool example (uncomment to use)
# - class: default-worker
# name: md-gpu
# replicas: 1
# metadata:
# labels:
# workload-type: gpu
# Variables - customize ClusterClass behavior
variables:
# Pod Security Standards
- name: podSecurityStandard
value:
enabled: true
enforce: baseline
audit: restricted
warn: restricted
# CNI configuration (if ClusterClass supports)
# - name: cni
# value: calico
# Custom variables defined in ClusterClass
# - name: sshKey
# value: my-ssh-key
# - name: controlPlaneMachineType
# value: m5.xlarge
---
# Example: Using variables for environment-specific settings
# Uncomment and modify based on your ClusterClass variables
#
# For staging environment:
# variables:
# - name: environment
# value: staging
# - name: controlPlaneMachineType
# value: m5.large
# - name: workerMachineType
# value: m5.large
#
# For production environment:
# variables:
# - name: environment
# value: production
# - name: controlPlaneMachineType
# value: m5.xlarge
# - name: workerMachineType
# value: m5.2xlarge
# - name: enableMonitoring
# value: true
# Minimal Cluster Template
# Suitable for development/testing with Docker provider
# Usage: kubectl apply -f cluster-minimal.yaml
---
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: ${CLUSTER_NAME:=dev-cluster}
namespace: ${NAMESPACE:=default}
spec:
clusterNetwork:
pods:
cidrBlocks:
- 192.168.0.0/16
services:
cidrBlocks:
- 10.96.0.0/12
controlPlaneRef:
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
name: ${CLUSTER_NAME}-control-plane
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerCluster
name: ${CLUSTER_NAME}
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerCluster
metadata:
name: ${CLUSTER_NAME}
namespace: ${NAMESPACE}
spec: {}
---
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
metadata:
name: ${CLUSTER_NAME}-control-plane
namespace: ${NAMESPACE}
spec:
replicas: 1
version: ${KUBERNETES_VERSION:=v1.28.0}
machineTemplate:
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerMachineTemplate
name: ${CLUSTER_NAME}-control-plane
kubeadmConfigSpec:
clusterConfiguration:
apiServer:
certSANs:
- localhost
- 127.0.0.1
controllerManager:
extraArgs:
enable-hostpath-provisioner: "true"
initConfiguration:
nodeRegistration:
criSocket: unix:///var/run/containerd/containerd.sock
kubeletExtraArgs:
eviction-hard: nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0%
joinConfiguration:
nodeRegistration:
criSocket: unix:///var/run/containerd/containerd.sock
kubeletExtraArgs:
eviction-hard: nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0%
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerMachineTemplate
metadata:
name: ${CLUSTER_NAME}-control-plane
namespace: ${NAMESPACE}
spec:
template:
spec: {}
---
# Worker MachineDeployment
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineDeployment
metadata:
name: ${CLUSTER_NAME}-md-0
namespace: ${NAMESPACE}
spec:
clusterName: ${CLUSTER_NAME}
replicas: ${WORKER_COUNT:=1}
selector:
matchLabels: null
template:
spec:
clusterName: ${CLUSTER_NAME}
version: ${KUBERNETES_VERSION}
bootstrap:
configRef:
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
name: ${CLUSTER_NAME}-md-0
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerMachineTemplate
name: ${CLUSTER_NAME}-md-0
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerMachineTemplate
metadata:
name: ${CLUSTER_NAME}-md-0
namespace: ${NAMESPACE}
spec:
template:
spec: {}
---
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
metadata:
name: ${CLUSTER_NAME}-md-0
namespace: ${NAMESPACE}
spec:
template:
spec:
joinConfiguration:
nodeRegistration:
kubeletExtraArgs:
eviction-hard: nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0%
# Production Cluster Template
# HA configuration with 3 control plane nodes and configurable workers
# Suitable for production workloads
---
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: ${CLUSTER_NAME:=prod-cluster}
namespace: ${NAMESPACE:=default}
labels:
environment: production
spec:
clusterNetwork:
pods:
cidrBlocks:
- 10.244.0.0/16
services:
cidrBlocks:
- 10.96.0.0/12
serviceDomain: cluster.local
controlPlaneRef:
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
name: ${CLUSTER_NAME}-control-plane
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AWSCluster # Replace with your provider
name: ${CLUSTER_NAME}
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AWSCluster
metadata:
name: ${CLUSTER_NAME}
namespace: ${NAMESPACE}
spec:
region: ${AWS_REGION:=us-east-1}
sshKeyName: ${SSH_KEY_NAME}
network:
vpc:
cidrBlock: 10.0.0.0/16
subnets:
- availabilityZone: ${AWS_REGION}a
cidrBlock: 10.0.0.0/24
isPublic: false
- availabilityZone: ${AWS_REGION}b
cidrBlock: 10.0.1.0/24
isPublic: false
- availabilityZone: ${AWS_REGION}c
cidrBlock: 10.0.2.0/24
isPublic: false
- availabilityZone: ${AWS_REGION}a
cidrBlock: 10.0.10.0/24
isPublic: true
---
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
metadata:
name: ${CLUSTER_NAME}-control-plane
namespace: ${NAMESPACE}
spec:
replicas: 3 # HA: odd number for etcd quorum
version: ${KUBERNETES_VERSION:=v1.29.0}
machineTemplate:
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AWSMachineTemplate
name: ${CLUSTER_NAME}-control-plane
kubeadmConfigSpec:
clusterConfiguration:
apiServer:
extraArgs:
audit-log-maxage: "30"
audit-log-maxbackup: "10"
audit-log-maxsize: "100"
audit-log-path: /var/log/kubernetes/audit.log
enable-admission-plugins: NodeRestriction,PodSecurity
extraVolumes:
- name: audit-log
hostPath: /var/log/kubernetes
mountPath: /var/log/kubernetes
readOnly: false
pathType: DirectoryOrCreate
controllerManager:
extraArgs:
bind-address: "0.0.0.0"
terminated-pod-gc-threshold: "100"
scheduler:
extraArgs:
bind-address: "0.0.0.0"
etcd:
local:
extraArgs:
listen-metrics-urls: http://0.0.0.0:2381
initConfiguration:
nodeRegistration:
kubeletExtraArgs:
cloud-provider: external
joinConfiguration:
nodeRegistration:
kubeletExtraArgs:
cloud-provider: external
files:
- content: |
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps"]
- level: Request
verbs: ["create", "update", "patch", "delete"]
- level: None
users: ["system:kube-proxy"]
verbs: ["watch"]
resources:
- group: ""
resources: ["endpoints", "services", "services/status"]
owner: root:root
path: /etc/kubernetes/audit-policy.yaml
permissions: "0644"
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AWSMachineTemplate
metadata:
name: ${CLUSTER_NAME}-control-plane
namespace: ${NAMESPACE}
spec:
template:
spec:
instanceType: ${CONTROL_PLANE_INSTANCE_TYPE:=m5.large}
iamInstanceProfile: control-plane.cluster-api-provider-aws.sigs.k8s.io
rootVolume:
size: 100
type: gp3
throughput: 125
iops: 3000
sshKeyName: ${SSH_KEY_NAME}
---
# Worker MachineDeployment
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineDeployment
metadata:
name: ${CLUSTER_NAME}-md-0
namespace: ${NAMESPACE}
spec:
clusterName: ${CLUSTER_NAME}
replicas: ${WORKER_COUNT:=3}
selector:
matchLabels: null
template:
spec:
clusterName: ${CLUSTER_NAME}
version: ${KUBERNETES_VERSION}
bootstrap:
configRef:
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
name: ${CLUSTER_NAME}-md-0
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AWSMachineTemplate
name: ${CLUSTER_NAME}-md-0
failureDomain: ${AWS_REGION}a
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AWSMachineTemplate
metadata:
name: ${CLUSTER_NAME}-md-0
namespace: ${NAMESPACE}
spec:
template:
spec:
instanceType: ${WORKER_INSTANCE_TYPE:=m5.xlarge}
iamInstanceProfile: nodes.cluster-api-provider-aws.sigs.k8s.io
rootVolume:
size: 200
type: gp3
sshKeyName: ${SSH_KEY_NAME}
---
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
metadata:
name: ${CLUSTER_NAME}-md-0
namespace: ${NAMESPACE}
spec:
template:
spec:
joinConfiguration:
nodeRegistration:
kubeletExtraArgs:
cloud-provider: external
---
# MachineHealthCheck for automatic remediation
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineHealthCheck
metadata:
name: ${CLUSTER_NAME}-mhc
namespace: ${NAMESPACE}
spec:
clusterName: ${CLUSTER_NAME}
maxUnhealthy: 40%
nodeStartupTimeout: 10m
selector:
matchLabels:
cluster.x-k8s.io/cluster-name: ${CLUSTER_NAME}
unhealthyConditions:
- type: Ready
status: "False"
timeout: 5m
- type: Ready
status: Unknown
timeout: 5m
# ClusterClass Example with Variables
# Defines reusable cluster templates with customizable variables
# Deploy this before creating Clusters that reference it
---
apiVersion: cluster.x-k8s.io/v1beta1
kind: ClusterClass
metadata:
name: quick-start
namespace: ${NAMESPACE:=default}
spec:
# Infrastructure template for the Cluster
infrastructure:
ref:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerClusterTemplate
name: quick-start-cluster
# Control plane template
controlPlane:
ref:
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlaneTemplate
name: quick-start-control-plane
machineInfrastructure:
ref:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerMachineTemplate
name: quick-start-control-plane
# Worker templates
workers:
machineDeployments:
- class: default-worker
template:
bootstrap:
ref:
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
name: quick-start-worker
infrastructure:
ref:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerMachineTemplate
name: quick-start-worker
# Variable definitions - allow cluster customization
variables:
# Pod Security Standards variable
- name: podSecurityStandard
required: false
schema:
openAPIV3Schema:
type: object
default:
enabled: true
enforce: baseline
audit: restricted
warn: restricted
properties:
enabled:
type: boolean
default: true
enforce:
type: string
enum: [privileged, baseline, restricted]
default: baseline
audit:
type: string
enum: [privileged, baseline, restricted]
default: restricted
warn:
type: string
enum: [privileged, baseline, restricted]
default: restricted
# Image repository override
- name: imageRepository
required: false
schema:
openAPIV3Schema:
type: string
default: registry.k8s.io
description: Container image repository for Kubernetes components
# SSH key for node access
- name: sshKey
required: false
schema:
openAPIV3Schema:
type: string
default: ""
description: SSH public key for node access
# Patches - apply variable values to templates
patches:
# Apply Pod Security Standards to KubeadmControlPlane
- name: podSecurityStandard
enabledIf: "{{ if .podSecurityStandard.enabled }}true{{ end }}"
definitions:
- selector:
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlaneTemplate
matchResources:
controlPlane: true
jsonPatches:
- op: add
path: /spec/template/spec/kubeadmConfigSpec/clusterConfiguration/apiServer/extraArgs/admission-control-config-file
value: /etc/kubernetes/admission/admission-control-config.yaml
- op: add
path: /spec/template/spec/kubeadmConfigSpec/files/-
valueFrom:
template: |
content: |
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
configuration:
apiVersion: pod-security.admission.config.k8s.io/v1
kind: PodSecurityConfiguration
defaults:
enforce: "{{ .podSecurityStandard.enforce }}"
enforce-version: "latest"
audit: "{{ .podSecurityStandard.audit }}"
audit-version: "latest"
warn: "{{ .podSecurityStandard.warn }}"
warn-version: "latest"
exemptions:
usernames: []
runtimeClasses: []
namespaces: [kube-system]
owner: root:root
path: /etc/kubernetes/admission/admission-control-config.yaml
permissions: "0644"
---
# Supporting templates
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerClusterTemplate
metadata:
name: quick-start-cluster
namespace: ${NAMESPACE}
spec:
template:
spec: {}
---
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlaneTemplate
metadata:
name: quick-start-control-plane
namespace: ${NAMESPACE}
spec:
template:
spec:
kubeadmConfigSpec:
clusterConfiguration:
apiServer:
certSANs:
- localhost
- 127.0.0.1
controllerManager:
extraArgs:
enable-hostpath-provisioner: "true"
initConfiguration:
nodeRegistration:
criSocket: unix:///var/run/containerd/containerd.sock
kubeletExtraArgs:
eviction-hard: nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0%
joinConfiguration:
nodeRegistration:
criSocket: unix:///var/run/containerd/containerd.sock
kubeletExtraArgs:
eviction-hard: nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0%
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerMachineTemplate
metadata:
name: quick-start-control-plane
namespace: ${NAMESPACE}
spec:
template:
spec:
extraMounts:
- containerPath: /var/run/docker.sock
hostPath: /var/run/docker.sock
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: DockerMachineTemplate
metadata:
name: quick-start-worker
namespace: ${NAMESPACE}
spec:
template:
spec: {}
---
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
metadata:
name: quick-start-worker
namespace: ${NAMESPACE}
spec:
template:
spec:
joinConfiguration:
nodeRegistration:
kubeletExtraArgs:
eviction-hard: nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0%
# Docker Provider Quick Start Configuration
# Use with: clusterctl init --infrastructure docker
# Location: ~/.cluster-api/clusterctl.yaml
---
# clusterctl configuration for Docker provider (CAPD)
# This is for development/testing only, not production
# Provider repositories
providers:
- name: docker
type: InfrastructureProvider
url: https://github.com/kubernetes-sigs/cluster-api/releases/latest/infrastructure-components-development.yaml
# Override default image repository if needed
# images:
# all:
# repository: my-registry.example.com/capi
# Feature gates
CLUSTER_TOPOLOGY: "true"
EXP_MACHINE_POOL: "true"
# Docker provider specific variables (for templates)
# These are set when generating clusters with clusterctl generate cluster
# Example usage:
# export CLUSTER_NAME=my-cluster
# export KUBERNETES_VERSION=v1.29.0
# export CONTROL_PLANE_MACHINE_COUNT=1
# export WORKER_MACHINE_COUNT=3
#
# clusterctl generate cluster ${CLUSTER_NAME} \
# --flavor development \
# --kubernetes-version ${KUBERNETES_VERSION} \
# --control-plane-machine-count ${CONTROL_PLANE_MACHINE_COUNT} \
# --worker-machine-count ${WORKER_MACHINE_COUNT} \
# > cluster.yaml
#
# kubectl apply -f cluster.yaml
---
# Minimal environment setup for Docker provider:
#
# 1. Install Docker Desktop or Docker Engine
# 2. Install kind: brew install kind (or see kind.sigs.k8s.io)
# 3. Install clusterctl: brew install clusterctl
# 4. Create management cluster:
#
# kind create cluster --name capi-management
# clusterctl init --infrastructure docker
#
# 5. Generate and apply workload cluster:
#
# clusterctl generate cluster dev-cluster \
# --infrastructure docker \
# --kubernetes-version v1.29.0 \
# --control-plane-machine-count 1 \
# --worker-machine-count 1 | kubectl apply -f -
#
# 6. Get kubeconfig:
#
# clusterctl get kubeconfig dev-cluster > dev-cluster.kubeconfig
# kubectl --kubeconfig=dev-cluster.kubeconfig get nodes
Disaster Recovery: Backup and Restore Playbook
Complete playbook for backing up and restoring Cluster API management clusters.
Overview
Management cluster failures can orphan workload clusters. This playbook covers:
- Regular backups of management cluster state
- Restoring CAPI controllers to new management cluster
- Reconnecting orphaned workload clusters
- Pivoting ownership between management clusters
Prerequisites
kubectlconfigured for management clusterclusterctlv1.6+velero(optional, for full cluster backup)- Backup storage (S3, GCS, Azure Blob)
---
Part 1: Backup Procedures
1.1 Export CAPI Resources
# Export all CAPI resources from management cluster
cd scripts && go run ./export-cluster-state --all -o ../backup/
# Or export specific cluster
go run ./export-cluster-state -n my-cluster -ns clusters -o ../backup/my-cluster/1.2 Backup Secrets
# Export kubeconfig secrets (contains cluster access credentials)
kubectl get secrets -n clusters -l cluster.x-k8s.io/cluster-name -o yaml > backup/cluster-secrets.yaml
# Export provider credentials
kubectl get secrets -n capi-system -o yaml > backup/capi-secrets.yaml
# IMPORTANT: Encrypt secrets before storing!
# Using SOPS:
sops --encrypt backup/cluster-secrets.yaml > backup/cluster-secrets.enc.yaml1.3 Backup etcd (Management Cluster)
# For kubeadm-based management clusters
# Run on control plane node:
ETCDCTL_API=3 etcdctl snapshot save /tmp/etcd-backup.db \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key
# Copy backup off the node
kubectl cp kube-system/etcd-<node>:/tmp/etcd-backup.db ./backup/etcd-backup.db1.4 Automated Backup with CronJob
Apply etcd-backup.yaml for scheduled backups.
---
Part 2: Restore Procedures
2.1 Scenario: New Management Cluster
When creating a fresh management cluster to take over orphaned workload clusters.
Step 1: Create New Management Cluster
# Option A: Use kind for temporary management
kind create cluster --name capi-recovery
# Option B: Use existing cluster
kubectl config use-context recovery-clusterStep 2: Initialize CAPI Components
# Install same providers as original management cluster
clusterctl init \
--infrastructure aws \
--control-plane kubeadm \
--bootstrap kubeadm
# Wait for controllers
kubectl wait --for=condition=Available deployment -n capi-system --all --timeout=300sStep 3: Restore Provider Credentials
# Decrypt and apply secrets
sops --decrypt backup/capi-secrets.enc.yaml | kubectl apply -f -
sops --decrypt backup/cluster-secrets.enc.yaml | kubectl apply -f -Step 4: Import Cluster Definitions
# Apply exported CAPI resources
kubectl apply -f backup/clusters/
# Verify cluster objects exist
kubectl get clusters -AStep 5: Reconnect to Workload Clusters
# For each workload cluster, verify connectivity
clusterctl describe cluster my-cluster -n clusters
# If cluster shows "Paused", unpause it
kubectl patch cluster my-cluster -n clusters --type=merge -p '{"spec":{"paused":false}}'2.2 Scenario: Pivot Between Management Clusters
Move CAPI resources from one management cluster to another.
# On source management cluster
clusterctl move \
--to-kubeconfig=/path/to/target-management.kubeconfig \
--namespace=clusters
# Verify on target
kubectl --kubeconfig=/path/to/target-management.kubeconfig get clusters -A2.3 Scenario: etcd Restore
Restore management cluster from etcd snapshot.
# On control plane node, stop API server
sudo mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/
# Restore etcd
ETCDCTL_API=3 etcdctl snapshot restore /tmp/etcd-backup.db \
--data-dir=/var/lib/etcd-restore \
--name=<node-name> \
--initial-cluster=<node-name>=https://<node-ip>:2380 \
--initial-advertise-peer-urls=https://<node-ip>:2380
# Replace etcd data
sudo mv /var/lib/etcd /var/lib/etcd.bak
sudo mv /var/lib/etcd-restore /var/lib/etcd
# Restart API server
sudo mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/---
Part 3: Verification Checklist
After restore, verify:
- [ ] All Cluster objects exist:
kubectl get clusters -A - [ ] All Machines are Running:
kubectl get machines -A - [ ] Control planes healthy:
kubectl get kcp -A - [ ] Kubeconfigs work:
clusterctl get kubeconfig <cluster> -n <ns> - [ ] Workload cluster API reachable
- [ ] Node count matches expected
- [ ] Cluster conditions all True
# Quick health check script
cd scripts && go run ./check-cluster-health --all---
Part 4: Orphaned Cluster Recovery
When management cluster is completely lost.
4.1 Workload Cluster Still Running
The workload cluster continues to function; only management is lost.
# 1. Create new management cluster and init CAPI
kind create cluster --name capi-recovery
clusterctl init --infrastructure <provider>
# 2. Recreate cluster manifests from backup or documentation
kubectl apply -f backup/my-cluster/
# 3. CAPI will discover existing infrastructure via provider
# The InfrastructureRef objects will reconcile with existing VMs/nodes4.2 Adopt Pattern (Manual)
If no backup exists, manually recreate CAPI objects:
# 1. Get cluster info from workload cluster
kubectl --kubeconfig=workload.kubeconfig get nodes -o wide
kubectl --kubeconfig=workload.kubeconfig cluster-info
# 2. Create minimal Cluster object (paused)
cat <<EOF | kubectl apply -f -
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: recovered-cluster
namespace: clusters
spec:
paused: true
controlPlaneEndpoint:
host: <api-server-ip>
port: 6443
EOF
# 3. Create Machine objects for existing nodes
# Match providerID from node spec---
Part 5: Prevention Best Practices
1. Regular Backups: Schedule daily backups via CronJob 2. Multiple Management Clusters: Run HA or standby management 3. GitOps: Store all manifests in Git for easy recreation 4. Documentation: Document provider-specific recovery steps 5. Test Restores: Quarterly disaster recovery drills
---
Related Assets
- etcd-backup.yaml - CronJob for automated etcd backups
- upgrade-checklist.md - Pre-upgrade backup checklist
Related Scripts
scripts/export-cluster-state- Export CAPI resources (go run ./export-cluster-state)scripts/check-cluster-health- Verify cluster health post-restore (go run ./check-cluster-health)
# etcd Backup CronJob for CAPI Management Cluster
#
# Automated scheduled backups of management cluster etcd to S3/GCS/Azure.
# Deploy to management cluster for disaster recovery protection.
#
# Prerequisites:
# - etcd accessible from within cluster
# - S3/GCS/Azure credentials configured
# - PVC or emptyDir for temporary storage
#
# Customization:
# 1. Update BACKUP_BUCKET with your storage location
# 2. Configure credentials secret
# 3. Adjust schedule (default: daily at 2 AM)
---
# Namespace for backup operations
apiVersion: v1
kind: Namespace
metadata:
name: capi-backup
labels:
app.kubernetes.io/name: capi-backup
---
# Secret with backup destination credentials
# Create with: kubectl create secret generic backup-credentials \
# --from-literal=AWS_ACCESS_KEY_ID=xxx \
# --from-literal=AWS_SECRET_ACCESS_KEY=xxx \
# -n capi-backup
apiVersion: v1
kind: Secret
metadata:
name: backup-credentials
namespace: capi-backup
type: Opaque
stringData:
# AWS S3
AWS_ACCESS_KEY_ID: "<your-access-key>"
AWS_SECRET_ACCESS_KEY: "<your-secret-key>"
# Or GCS
# GOOGLE_APPLICATION_CREDENTIALS: |
# <service-account-json>
# Or Azure
# AZURE_STORAGE_ACCOUNT: "<account>"
# AZURE_STORAGE_KEY: "<key>"
---
# ConfigMap with backup configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: backup-config
namespace: capi-backup
data:
BACKUP_BUCKET: "s3://your-bucket/capi-backups"
# Or: gs://your-bucket/capi-backups
# Or: https://account.blob.core.windows.net/capi-backups
RETENTION_DAYS: "30"
ETCD_ENDPOINTS: "https://127.0.0.1:2379"
---
# ServiceAccount for backup jobs
apiVersion: v1
kind: ServiceAccount
metadata:
name: etcd-backup
namespace: capi-backup
---
# CronJob: etcd snapshot backup
apiVersion: batch/v1
kind: CronJob
metadata:
name: etcd-backup
namespace: capi-backup
labels:
app.kubernetes.io/name: etcd-backup
spec:
# Run daily at 2:00 AM UTC
schedule: "0 2 * * *"
# Keep last 3 job runs
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
concurrencyPolicy: Forbid
jobTemplate:
spec:
backoffLimit: 2
activeDeadlineSeconds: 1800 # 30 min timeout
template:
metadata:
labels:
app.kubernetes.io/name: etcd-backup
spec:
serviceAccountName: etcd-backup
restartPolicy: OnFailure
# Must run on control plane node with etcd access
nodeSelector:
node-role.kubernetes.io/control-plane: ""
tolerations:
- key: node-role.kubernetes.io/control-plane
effect: NoSchedule
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: backup
image: bitnami/etcd:3.5
command:
- /bin/bash
- -c
- |
set -euo pipefail
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_FILE="/backup/etcd-snapshot-${TIMESTAMP}.db"
echo "Starting etcd backup at ${TIMESTAMP}"
# Create snapshot
etcdctl snapshot save "${BACKUP_FILE}" \
--endpoints="${ETCD_ENDPOINTS}" \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key
# Verify snapshot
etcdctl snapshot status "${BACKUP_FILE}" --write-out=table
# Get snapshot size
SNAPSHOT_SIZE=$(du -h "${BACKUP_FILE}" | cut -f1)
echo "Snapshot size: ${SNAPSHOT_SIZE}"
# Upload to cloud storage
echo "Uploading to ${BACKUP_BUCKET}..."
# AWS S3
if [[ "${BACKUP_BUCKET}" == s3://* ]]; then
aws s3 cp "${BACKUP_FILE}" "${BACKUP_BUCKET}/"
aws s3 cp "${BACKUP_FILE}" "${BACKUP_BUCKET}/latest.db"
fi
# GCS
if [[ "${BACKUP_BUCKET}" == gs://* ]]; then
gsutil cp "${BACKUP_FILE}" "${BACKUP_BUCKET}/"
gsutil cp "${BACKUP_FILE}" "${BACKUP_BUCKET}/latest.db"
fi
# Cleanup old local backup
rm -f "${BACKUP_FILE}"
echo "Backup completed successfully"
env:
- name: ETCDCTL_API
value: "3"
- name: ETCD_ENDPOINTS
valueFrom:
configMapKeyRef:
name: backup-config
key: ETCD_ENDPOINTS
- name: BACKUP_BUCKET
valueFrom:
configMapKeyRef:
name: backup-config
key: BACKUP_BUCKET
envFrom:
- secretRef:
name: backup-credentials
volumeMounts:
- name: etcd-certs
mountPath: /etc/kubernetes/pki/etcd
readOnly: true
- name: backup-volume
mountPath: /backup
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: etcd-certs
hostPath:
path: /etc/kubernetes/pki/etcd
type: Directory
- name: backup-volume
emptyDir:
sizeLimit: 10Gi
---
# CronJob: CAPI resource backup
apiVersion: batch/v1
kind: CronJob
metadata:
name: capi-resources-backup
namespace: capi-backup
labels:
app.kubernetes.io/name: capi-resources-backup
spec:
# Run daily at 2:30 AM UTC (after etcd backup)
schedule: "30 2 * * *"
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
concurrencyPolicy: Forbid
jobTemplate:
spec:
backoffLimit: 2
activeDeadlineSeconds: 900 # 15 min timeout
template:
metadata:
labels:
app.kubernetes.io/name: capi-resources-backup
spec:
serviceAccountName: etcd-backup
restartPolicy: OnFailure
containers:
- name: backup
image: bitnami/kubectl:1.28
command:
- /bin/bash
- -c
- |
set -euo pipefail
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_DIR="/backup/capi-${TIMESTAMP}"
mkdir -p "${BACKUP_DIR}"
echo "Starting CAPI resources backup at ${TIMESTAMP}"
# Export all CAPI resources
CAPI_KINDS=(
"clusters.cluster.x-k8s.io"
"machines.cluster.x-k8s.io"
"machinesets.cluster.x-k8s.io"
"machinedeployments.cluster.x-k8s.io"
"machinepools.cluster.x-k8s.io"
"machinehealthchecks.cluster.x-k8s.io"
"clusterclasses.cluster.x-k8s.io"
"kubeadmcontrolplanes.controlplane.cluster.x-k8s.io"
"kubeadmconfigs.bootstrap.cluster.x-k8s.io"
)
for KIND in "${CAPI_KINDS[@]}"; do
echo "Exporting ${KIND}..."
kubectl get "${KIND}" -A -o yaml > "${BACKUP_DIR}/${KIND//\//_}.yaml" 2>/dev/null || true
done
# Export cluster secrets (kubeconfigs)
echo "Exporting cluster secrets..."
kubectl get secrets -A -l cluster.x-k8s.io/cluster-name -o yaml > "${BACKUP_DIR}/cluster-secrets.yaml"
# Create tarball
TARBALL="/backup/capi-backup-${TIMESTAMP}.tar.gz"
tar -czf "${TARBALL}" -C /backup "capi-${TIMESTAMP}"
# Upload
echo "Uploading to ${BACKUP_BUCKET}..."
if [[ "${BACKUP_BUCKET}" == s3://* ]]; then
aws s3 cp "${TARBALL}" "${BACKUP_BUCKET}/"
fi
if [[ "${BACKUP_BUCKET}" == gs://* ]]; then
gsutil cp "${TARBALL}" "${BACKUP_BUCKET}/"
fi
# Cleanup
rm -rf "${BACKUP_DIR}" "${TARBALL}"
echo "CAPI backup completed successfully"
env:
- name: BACKUP_BUCKET
valueFrom:
configMapKeyRef:
name: backup-config
key: BACKUP_BUCKET
envFrom:
- secretRef:
name: backup-credentials
volumeMounts:
- name: backup-volume
mountPath: /backup
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: backup-volume
emptyDir:
sizeLimit: 5Gi
---
# RBAC for CAPI resource backup
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: capi-backup-reader
rules:
- apiGroups:
- cluster.x-k8s.io
- controlplane.cluster.x-k8s.io
- bootstrap.cluster.x-k8s.io
- infrastructure.cluster.x-k8s.io
- addons.cluster.x-k8s.io
resources:
- "*"
verbs:
- get
- list
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: capi-backup-reader
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: capi-backup-reader
subjects:
- kind: ServiceAccount
name: etcd-backup
namespace: capi-backup
# Flux Kustomization for Cluster API Managed Clusters
#
# This configuration tells Flux to apply cluster manifests from a Git repository
# to the management cluster where CAPI controllers are running.
#
# Prerequisites:
# - Flux v2 installed on management cluster
# - GitRepository resource configured
# - Optional: SOPS/age for secret encryption
#
# Usage:
# 1. Configure GitRepository pointing to your clusters repo
# 2. Customize this Kustomization for your setup
# 3. Apply: kubectl apply -f flux-kustomization.yaml
---
# GitRepository source definition
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: clusters
namespace: flux-system
spec:
interval: 1m
url: https://github.com/your-org/clusters.git
ref:
branch: main
# For private repos:
# secretRef:
# name: clusters-repo-auth
---
# Main Kustomization for all clusters
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: clusters
namespace: flux-system
spec:
interval: 10m
timeout: 5m
retryInterval: 2m
sourceRef:
kind: GitRepository
name: clusters
path: ./clusters
prune: false # CAUTION: Enable pruning only if you want cluster deletion
wait: true
# Health checks for CAPI resources
healthChecks:
- apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
namespace: clusters
name: "*"
# Decrypt secrets with SOPS
# decryption:
# provider: sops
# secretRef:
# name: sops-age
# Substitute variables from ConfigMaps/Secrets
# postBuild:
# substitute:
# CLUSTER_NAME: prod-cluster
# substituteFrom:
# - kind: ConfigMap
# name: cluster-vars
# - kind: Secret
# name: cluster-secrets
---
# Kustomization for production clusters
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: clusters-production
namespace: flux-system
spec:
interval: 10m
dependsOn:
- name: clusters # Wait for base to be ready
sourceRef:
kind: GitRepository
name: clusters
path: ./clusters/production
prune: false
wait: true
timeout: 15m # Production clusters may take longer
# Require approval for production changes
# suspend: true # Uncomment to require manual resume
healthChecks:
- apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
namespace: clusters
name: prod-cluster
---
# Kustomization for staging clusters
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: clusters-staging
namespace: flux-system
spec:
interval: 5m
sourceRef:
kind: GitRepository
name: clusters
path: ./clusters/staging
prune: true # OK to prune staging clusters
wait: true
timeout: 10m
---
# Alert for cluster provisioning failures
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
name: cluster-alerts
namespace: flux-system
spec:
providerRef:
name: slack # or: teams, discord, webhook
eventSeverity: error
eventSources:
- kind: Kustomization
name: clusters
- kind: Kustomization
name: clusters-production
- kind: Kustomization
name: clusters-staging
exclusionList:
- ".*upgrade.*pending.*"
---
# Provider for Slack notifications
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Provider
metadata:
name: slack
namespace: flux-system
spec:
type: slack
channel: cluster-alerts
secretRef:
name: slack-webhook-url
---
# ImagePolicy for automatic Kubernetes version updates (optional)
# Watches for new Kubernetes versions and updates cluster manifests
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: kubernetes
namespace: flux-system
spec:
image: registry.k8s.io/kube-apiserver
interval: 1h
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: kubernetes-stable
namespace: flux-system
spec:
imageRepositoryRef:
name: kubernetes
filterTags:
pattern: "^v1\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)$"
extract: "$minor.$patch"
policy:
semver:
range: ">=1.28.0 <1.32.0"
---
# ReceiverConfiguration for webhook-based updates
apiVersion: notification.toolkit.fluxcd.io/v1
kind: Receiver
metadata:
name: clusters-webhook
namespace: flux-system
spec:
type: github
events:
- push
secretRef:
name: webhook-token
resources:
- kind: GitRepository
name: clusters
# GitOps RBAC for Cluster API
#
# Service accounts and roles for ArgoCD/Flux to manage CAPI resources.
# Apply to management cluster before configuring GitOps.
#
# Includes:
# - ServiceAccount for GitOps controller
# - ClusterRole with CAPI permissions
# - Namespace-scoped Role for cluster secrets
#
# Usage:
# kubectl apply -f gitops-rbac.yaml
---
# Namespace for cluster resources
apiVersion: v1
kind: Namespace
metadata:
name: clusters
labels:
app.kubernetes.io/managed-by: gitops
cluster-api.sigs.k8s.io/namespace: "true"
---
# ServiceAccount for GitOps controller
apiVersion: v1
kind: ServiceAccount
metadata:
name: gitops-cluster-manager
namespace: clusters
labels:
app.kubernetes.io/name: gitops-cluster-manager
app.kubernetes.io/component: rbac
---
# ClusterRole for managing CAPI resources
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gitops-capi-manager
labels:
app.kubernetes.io/name: gitops-capi-manager
rules:
# Core Cluster API resources
- apiGroups:
- cluster.x-k8s.io
resources:
- clusters
- machines
- machinesets
- machinedeployments
- machinepools
- machinehealthchecks
- clusterclasses
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
# Cluster API status (read-only for status)
- apiGroups:
- cluster.x-k8s.io
resources:
- clusters/status
- machines/status
- machinesets/status
- machinedeployments/status
- machinepools/status
verbs:
- get
- list
- watch
# Control Plane providers
- apiGroups:
- controlplane.cluster.x-k8s.io
resources:
- kubeadmcontrolplanes
- kubeadmcontrolplanetemplates
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
# Bootstrap providers
- apiGroups:
- bootstrap.cluster.x-k8s.io
resources:
- kubeadmconfigs
- kubeadmconfigtemplates
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
# Infrastructure providers (generic wildcard)
- apiGroups:
- infrastructure.cluster.x-k8s.io
resources:
- "*"
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
# Addons (ClusterResourceSet)
- apiGroups:
- addons.cluster.x-k8s.io
resources:
- clusterresourcesets
- clusterresourcesetbindings
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
# Runtime extensions
- apiGroups:
- runtime.cluster.x-k8s.io
resources:
- extensionconfigs
verbs:
- get
- list
- watch
# IPAM
- apiGroups:
- ipam.cluster.x-k8s.io
resources:
- ipaddresses
- ipaddressclaims
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
# Namespaces for cluster isolation
- apiGroups:
- ""
resources:
- namespaces
verbs:
- create
- get
- list
- watch
# Events for debugging
- apiGroups:
- ""
resources:
- events
verbs:
- get
- list
- watch
---
# ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: gitops-capi-manager
labels:
app.kubernetes.io/name: gitops-capi-manager
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: gitops-capi-manager
subjects:
- kind: ServiceAccount
name: gitops-cluster-manager
namespace: clusters
# ArgoCD application controller
- kind: ServiceAccount
name: argocd-application-controller
namespace: argocd
# Flux kustomize controller
- kind: ServiceAccount
name: kustomize-controller
namespace: flux-system
---
# Role for managing secrets in clusters namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: gitops-cluster-secrets
namespace: clusters
labels:
app.kubernetes.io/name: gitops-cluster-secrets
rules:
# Kubeconfig secrets
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
# ConfigMaps for cluster addons
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
---
# RoleBinding for secrets
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: gitops-cluster-secrets
namespace: clusters
labels:
app.kubernetes.io/name: gitops-cluster-secrets
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: gitops-cluster-secrets
subjects:
- kind: ServiceAccount
name: gitops-cluster-manager
namespace: clusters
- kind: ServiceAccount
name: argocd-application-controller
namespace: argocd
- kind: ServiceAccount
name: kustomize-controller
namespace: flux-system
---
# Read-only ClusterRole for monitoring/dashboards
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gitops-capi-viewer
labels:
app.kubernetes.io/name: gitops-capi-viewer
rules:
- apiGroups:
- cluster.x-k8s.io
- controlplane.cluster.x-k8s.io
- bootstrap.cluster.x-k8s.io
- infrastructure.cluster.x-k8s.io
- addons.cluster.x-k8s.io
resources:
- "*"
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
- namespaces
verbs:
- get
- list
- watch
Migration Guide: v1beta1 → v1beta2 API
Guide for migrating CAPI resources from v1beta1 to v1beta2 API format.
Overview
The v1beta2 API introduces:
- Structured conditions with clearer semantics
- TypedObjectReference for references
- Duration fields as strings
- Improved status reporting
Preparation
1. Check Current State
# Run migration checker script
cd scripts && go run ./migration-checker -ns <namespace>
# Or manually check API versions in use
kubectl get clusters -A -o yaml | grep apiVersion2. Backup Everything
# Export cluster state
cd scripts && go run ./export-cluster-state -n <cluster-name> -o ../backup/
# Or use clusterctl move
clusterctl move --to-kubeconfig backup.kubeconfig -n <namespace>API Changes Reference
Conditions (v1beta2 format)
Old (v1beta1):
status:
conditions:
- type: Ready
status: "True"
reason: ClusterReady
message: Cluster is ready
lastTransitionTime: "2024-01-01T00:00:00Z"New (v1beta2):
status:
v1beta2:
conditions:
- type: Available
status: "True"
reason: Available
message: Cluster is available
lastTransitionTime: "2024-01-01T00:00:00Z"Key changes:
- Conditions moved to
status.v1beta2.conditions - New condition types:
Available,Ready,Deleting - Clearer positive/negative polarity
TypedObjectReference
Old:
spec:
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AWSCluster
name: my-cluster
namespace: defaultNew:
spec:
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AWSCluster
name: my-cluster
# namespace is optional if same as parentDuration Fields
Old (integer seconds):
spec:
nodeStartupTimeout: 600New (string duration):
spec:
nodeStartupTimeout: 10mPhase → Conditions
Old:
status:
phase: RunningNew (use conditions instead):
status:
v1beta2:
conditions:
- type: Available
status: "True"Migration Steps
Step 1: Update CAPI to v1.8+
v1beta2 conditions require CAPI v1.8 or later.
clusterctl upgrade plan
clusterctl upgrade apply --contract v1beta1Step 2: Enable v1beta2 Conditions
Feature gate (if not default):
# In provider deployment
--feature-gates=V1Beta2Conditions=trueStep 3: Update Manifests
Cluster Resources
# Before
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
spec:
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AWSCluster
name: my-cluster
namespace: default
controlPlaneRef:
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
name: my-cluster-cp
namespace: default
# After (namespace optional)
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
spec:
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AWSCluster
name: my-cluster
controlPlaneRef:
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
name: my-cluster-cpMachineHealthCheck
# Before
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineHealthCheck
spec:
nodeStartupTimeout: 600
# After
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineHealthCheck
spec:
nodeStartupTimeout: 10mStep 4: Update Monitoring/Alerting
Update queries that check conditions:
# Before: check status.conditions
kubectl get cluster -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}'
# After: check status.v1beta2.conditions (when available)
kubectl get cluster -o jsonpath='{.status.v1beta2.conditions[?(@.type=="Available")].status}'Step 5: Update Automation
Update scripts that parse status:
// Before
ready := status.Phase == "Provisioned"
// After
for _, c := range status.V1Beta2.Conditions {
if c.Type == "Available" && c.Status == "True" {
ready = true
break
}
}Validation
# Check for deprecated patterns
cd scripts && go run ./migration-checker -ns default
# Verify conditions format
kubectl get cluster -o yaml | grep -A10 v1beta2
# Check no errors
kubectl logs -n capi-system -l control-plane=controller-manager | grep -i errorCondition Type Mapping
| v1beta1 Condition | v1beta2 Equivalent | Notes |
|---|---|---|
| Ready | Available | For Cluster, positive polarity |
| ControlPlaneReady | ControlPlaneAvailable | |
| InfrastructureReady | InfrastructureAvailable | |
| MachinesReady | WorkersAvailable | |
| Initialized | Initialized | Unchanged |
Troubleshooting
Conditions Not Appearing in v1beta2
Check feature gate is enabled:
kubectl get deploy -n capi-system capi-controller-manager -o yaml | grep -A5 argsOld Automations Breaking
During transition, check both locations:
// Compatibility check
func getConditions(status map[string]interface{}) []interface{} {
// Try v1beta2 first
if v1beta2, ok := status["v1beta2"].(map[string]interface{}); ok {
if conditions, ok := v1beta2["conditions"].([]interface{}); ok {
return conditions
}
}
// Fall back to v1beta1
if conditions, ok := status["conditions"].([]interface{}); ok {
return conditions
}
return nil
}Mixed Version Clusters
If some clusters show v1beta2 and others don't:
- Ensure all providers are upgraded
- Check clusterctl versions match
- Verify feature gates consistent
Rollback
If issues arise:
1. Revert to previous CAPI version 2. Revert manifest changes 3. Restore from backup if needed
clusterctl upgrade apply \
--core cluster-api:v1.7.x \
--bootstrap kubeadm:v1.7.x \
--control-plane kubeadm:v1.7.xTimeline
- v1.8: v1beta2 conditions alpha
- v1.9: v1beta2 conditions beta
- v1.12: v1beta2 conditions GA
- v1.14+: v1beta1 conditions deprecated (planned)
- v2.0: v1beta1 conditions removed (planned)
# Prometheus Alerting Rules for Cluster API
#
# Alerts for monitoring CAPI management and workload cluster health.
# Deploy to Prometheus instance monitoring the management cluster.
#
# Prerequisites:
# - Prometheus Operator or raw Prometheus
# - kube-state-metrics with CAPI CRD support
# - Node exporter on management cluster
#
# Usage:
# kubectl apply -f prometheus-alerts.yaml
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: cluster-api-alerts
namespace: monitoring
labels:
app.kubernetes.io/name: cluster-api
prometheus: main
role: alert-rules
spec:
groups:
# =========================================================================
# Cluster Provisioning Alerts
# =========================================================================
- name: capi.cluster.provisioning
rules:
- alert: ClusterProvisioningStuck
expr: |
(
kube_customresource_cluster_status_phase{phase!="Provisioned"}
* on (cluster_name, namespace) group_left()
(time() - kube_customresource_cluster_created > 1800)
) > 0
for: 5m
labels:
severity: warning
category: provisioning
annotations:
summary: "Cluster {{ $labels.cluster_name }} provisioning stuck"
description: "Cluster {{ $labels.cluster_name }} in namespace {{ $labels.namespace }} has been in {{ $labels.phase }} state for over 30 minutes."
runbook_url: "https://cluster-api.sigs.k8s.io/troubleshooting"
- alert: ClusterProvisioningFailed
expr: |
kube_customresource_cluster_status_phase{phase="Failed"} == 1
for: 2m
labels:
severity: critical
category: provisioning
annotations:
summary: "Cluster {{ $labels.cluster_name }} provisioning failed"
description: "Cluster {{ $labels.cluster_name }} in namespace {{ $labels.namespace }} has entered Failed state."
- alert: ClusterDeleting
expr: |
(
kube_customresource_cluster_status_phase{phase="Deleting"}
* on (cluster_name, namespace) group_left()
(time() - kube_customresource_cluster_deletion_timestamp > 900)
) > 0
for: 5m
labels:
severity: warning
category: lifecycle
annotations:
summary: "Cluster {{ $labels.cluster_name }} deletion taking too long"
description: "Cluster deletion has been in progress for over 15 minutes. Check for stuck finalizers."
# =========================================================================
# Machine Health Alerts
# =========================================================================
- name: capi.machine.health
rules:
- alert: MachineNotRunning
expr: |
kube_customresource_machine_status_phase{phase!~"Running|Deleting"} == 1
for: 10m
labels:
severity: warning
category: machine
annotations:
summary: "Machine {{ $labels.machine }} not running"
description: "Machine {{ $labels.machine }} in cluster {{ $labels.cluster_name }} is in {{ $labels.phase }} state."
- alert: MachineProvisioningFailed
expr: |
kube_customresource_machine_status_phase{phase="Failed"} == 1
for: 2m
labels:
severity: critical
category: machine
annotations:
summary: "Machine {{ $labels.machine }} failed"
description: "Machine {{ $labels.machine }} in cluster {{ $labels.cluster_name }} has failed to provision."
- alert: MachineHealthCheckTriggered
expr: |
increase(capi_machinehealthcheck_remediation_total[5m]) > 0
for: 1m
labels:
severity: warning
category: remediation
annotations:
summary: "MachineHealthCheck remediation triggered"
description: "MachineHealthCheck {{ $labels.machinehealthcheck }} has triggered remediation in cluster {{ $labels.cluster_name }}."
- alert: MachineUnhealthyCondition
expr: |
kube_customresource_machine_status_condition{condition="Ready", status="False"} == 1
for: 15m
labels:
severity: warning
category: machine
annotations:
summary: "Machine {{ $labels.machine }} unhealthy"
description: "Machine {{ $labels.machine }} Ready condition is False for over 15 minutes."
# =========================================================================
# Control Plane Alerts
# =========================================================================
- name: capi.controlplane
rules:
- alert: ControlPlaneNotReady
expr: |
kube_customresource_kubeadmcontrolplane_status_condition{condition="Ready", status="False"} == 1
for: 10m
labels:
severity: critical
category: controlplane
annotations:
summary: "Control plane {{ $labels.kubeadmcontrolplane }} not ready"
description: "KubeadmControlPlane {{ $labels.kubeadmcontrolplane }} is not ready for over 10 minutes."
- alert: ControlPlaneReplicasMismatch
expr: |
kube_customresource_kubeadmcontrolplane_spec_replicas
!=
kube_customresource_kubeadmcontrolplane_status_replicas
for: 30m
labels:
severity: warning
category: controlplane
annotations:
summary: "Control plane replica count mismatch"
description: "KubeadmControlPlane {{ $labels.kubeadmcontrolplane }} has {{ $value }} ready replicas, expected {{ $labels.spec_replicas }}."
- alert: ControlPlaneUpgradeStuck
expr: |
(
kube_customresource_kubeadmcontrolplane_status_condition{condition="RollingUpdate", status="True"} == 1
) * on (kubeadmcontrolplane, namespace) group_left()
(time() - kube_customresource_kubeadmcontrolplane_status_updated > 3600) > 0
for: 10m
labels:
severity: warning
category: upgrade
annotations:
summary: "Control plane upgrade stuck"
description: "KubeadmControlPlane {{ $labels.kubeadmcontrolplane }} rolling update in progress for over 1 hour."
# =========================================================================
# Certificate Alerts
# =========================================================================
- name: capi.certificates
rules:
- alert: ClusterCertificateExpiringSoon
expr: |
(
capi_cluster_certificate_expiry_seconds > 0
and
capi_cluster_certificate_expiry_seconds < 2592000
)
for: 1h
labels:
severity: warning
category: certificate
annotations:
summary: "Cluster certificate expiring within 30 days"
description: "Certificate {{ $labels.certificate }} for cluster {{ $labels.cluster_name }} expires in {{ $value | humanizeDuration }}."
- alert: ClusterCertificateCritical
expr: |
(
capi_cluster_certificate_expiry_seconds > 0
and
capi_cluster_certificate_expiry_seconds < 604800
)
for: 10m
labels:
severity: critical
category: certificate
annotations:
summary: "Cluster certificate expiring within 7 days"
description: "Certificate {{ $labels.certificate }} for cluster {{ $labels.cluster_name }} expires in {{ $value | humanizeDuration }}. Rotate immediately!"
# =========================================================================
# CAPI Controller Alerts
# =========================================================================
- name: capi.controllers
rules:
- alert: CAPIControllerDown
expr: |
up{job=~".*capi.*"} == 0
for: 5m
labels:
severity: critical
category: controller
annotations:
summary: "CAPI controller {{ $labels.job }} is down"
description: "The CAPI controller {{ $labels.job }} has been down for over 5 minutes."
- alert: CAPIControllerHighRestarts
expr: |
increase(kube_pod_container_status_restarts_total{namespace=~"capi.*", container=~"manager"}[1h]) > 3
for: 10m
labels:
severity: warning
category: controller
annotations:
summary: "CAPI controller restarting frequently"
description: "Controller {{ $labels.pod }} has restarted {{ $value }} times in the last hour."
- alert: CAPIReconcileErrors
expr: |
increase(controller_runtime_reconcile_errors_total{controller=~".*cluster.*|.*machine.*"}[5m]) > 10
for: 10m
labels:
severity: warning
category: controller
annotations:
summary: "High reconcile errors in {{ $labels.controller }}"
description: "Controller {{ $labels.controller }} has {{ $value }} reconcile errors in the last 5 minutes."
- alert: CAPIReconcileDurationHigh
expr: |
histogram_quantile(0.99, sum(rate(controller_runtime_reconcile_time_seconds_bucket{controller=~".*cluster.*"}[5m])) by (le, controller)) > 60
for: 15m
labels:
severity: warning
category: performance
annotations:
summary: "CAPI reconciliation slow"
description: "99th percentile reconcile time for {{ $labels.controller }} is {{ $value }}s."
# =========================================================================
# Infrastructure Provider Alerts
# =========================================================================
- name: capi.infrastructure
rules:
- alert: InfrastructureProviderDown
expr: |
up{job=~"cap[a-z]-.*"} == 0
for: 5m
labels:
severity: critical
category: provider
annotations:
summary: "Infrastructure provider {{ $labels.job }} is down"
description: "Infrastructure provider {{ $labels.job }} has been unavailable for 5 minutes."
- alert: InfrastructureResourceStuck
expr: |
(
kube_customresource_status_condition{group=~"infrastructure.cluster.x-k8s.io", condition="Ready", status="False"} == 1
)
for: 30m
labels:
severity: warning
category: provider
annotations:
summary: "Infrastructure resource not ready"
description: "{{ $labels.kind }} {{ $labels.name }} is not ready for over 30 minutes."
# =========================================================================
# Workload Cluster Connectivity
# =========================================================================
- name: capi.connectivity
rules:
- alert: WorkloadClusterUnreachable
expr: |
probe_success{job="workload-clusters"} == 0
for: 5m
labels:
severity: critical
category: connectivity
annotations:
summary: "Workload cluster {{ $labels.cluster }} unreachable"
description: "Cannot reach API server of workload cluster {{ $labels.cluster }}."
- alert: WorkloadClusterAPILatencyHigh
expr: |
probe_http_duration_seconds{job="workload-clusters", phase="transfer"} > 2
for: 10m
labels:
severity: warning
category: performance
annotations:
summary: "Workload cluster API latency high"
description: "API server latency for cluster {{ $labels.cluster }} is {{ $value }}s."
---
# Recording rules for CAPI metrics
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: cluster-api-recording
namespace: monitoring
labels:
app.kubernetes.io/name: cluster-api
prometheus: main
role: recording-rules
spec:
groups:
- name: capi.recording
rules:
- record: capi:cluster:count
expr: count(kube_customresource_cluster_info) or vector(0)
- record: capi:cluster:by_phase
expr: count by (phase) (kube_customresource_cluster_status_phase)
- record: capi:machine:count
expr: count(kube_customresource_machine_info) or vector(0)
- record: capi:machine:by_phase
expr: count by (phase) (kube_customresource_machine_status_phase)
- record: capi:machine:unhealthy_ratio
expr: |
(
count(kube_customresource_machine_status_condition{condition="Ready", status="False"})
/
count(kube_customresource_machine_info)
) or vector(0)
- record: capi:controlplane:ready_ratio
expr: |
(
sum(kube_customresource_kubeadmcontrolplane_status_ready_replicas)
/
sum(kube_customresource_kubeadmcontrolplane_spec_replicas)
) or vector(0)
Provider Version Compatibility Matrix
Version compatibility between Cluster API and providers.
CAPI Core Version Support
| CAPI Version | Kubernetes Support | Go Version | Release Date | EOL |
|---|---|---|---|---|
| v1.12.x | v1.31 - v1.35 | 1.24 | Oct 2025 | Current |
| v1.11.x | v1.30 - v1.34 | 1.24 | Jul 2025 | Apr 2026 |
| v1.10.x | v1.29 - v1.33 | 1.23 | Apr 2025 | Jan 2026 |
| v1.9.x | v1.28 - v1.32 | 1.23 | Jan 2025 | Oct 2025 |
| v1.8.x | v1.27 - v1.31 | 1.22 | Oct 2024 | Jul 2025 |
| v1.7.x | v1.26 - v1.30 | 1.22 | Jul 2024 | Apr 2025 |
| v1.6.x | v1.25 - v1.29 | 1.21 | Apr 2024 | Jan 2025 |
Infrastructure Providers
AWS (CAPA)
| CAPA Version | CAPI Compatibility | AWS SDK | Notes |
|---|---|---|---|
| v2.7.x | v1.8+ | v2.x | Current |
| v2.6.x | v1.7+ | v2.x | |
| v2.5.x | v1.6+ | v2.x | |
| v2.4.x | v1.5+ | v2.x |
Azure (CAPZ)
| CAPZ Version | CAPI Compatibility | Notes |
|---|---|---|
| v1.18.x | v1.8+ | Current |
| v1.17.x | v1.7+ | |
| v1.16.x | v1.6+ |
vSphere (CAPV)
| CAPV Version | CAPI Compatibility | vSphere Version | Notes |
|---|---|---|---|
| v1.12.x | v1.8+ | 7.0+ | Current |
| v1.11.x | v1.7+ | 7.0+ | |
| v1.10.x | v1.6+ | 7.0+ |
Docker (CAPD)
| CAPD Version | CAPI Compatibility | Notes |
|---|---|---|
| v1.12.x | v1.12 | Development only, included in CAPI releases |
GCP (CAPG)
| CAPG Version | CAPI Compatibility | Notes |
|---|---|---|
| v1.9.x | v1.8+ | Current |
| v1.8.x | v1.7+ |
Bootstrap Providers
Kubeadm (CABPK)
Included in CAPI core releases. Version matches CAPI.
K3s (CABP-K3s)
| Version | CAPI Compatibility | K3s Version |
|---|---|---|
| v0.2.x | v1.6+ | v1.27+ |
Talos
| Version | CAPI Compatibility | Talos Version |
|---|---|---|
| v0.6.x | v1.6+ | 1.6+ |
Control Plane Providers
Kubeadm (KCP)
Included in CAPI core releases. Version matches CAPI.
K3s
| Version | CAPI Compatibility |
|---|---|
| v0.2.x | v1.6+ |
Upgrade Paths
CAPI Upgrades
Direct upgrades supported between minor versions (e.g., v1.6 → v1.7).
v1.6.x → v1.7.x → v1.8.x → v1.9.x → v1.10.x → v1.11.x → v1.12.xNote: Skipping minor versions not recommended.
Provider Upgrades
Always upgrade CAPI core before providers:
1. Upgrade CAPI core 2. Upgrade bootstrap provider 3. Upgrade control plane provider 4. Upgrade infrastructure provider
Pre-upgrade Checklist
- [ ] Review release notes for breaking changes
- [ ] Verify Kubernetes version compatibility
- [ ] Backup cluster state (
clusterctl moveor export) - [ ] Check provider version compatibility
- [ ] Run
clusterctl upgrade plan
API Version Timeline
| API Version | Status | CAPI Version |
|---|---|---|
| v1beta2 | Current | v1.8+ |
| v1beta1 | Deprecated | v1.0 - v1.12 |
| v1alpha4 | Removed | < v1.0 |
| v1alpha3 | Removed | < v0.4 |
Finding Compatible Versions
# Show upgrade plan
clusterctl upgrade plan
# Check provider contract version
kubectl get providers -A -o wide
# Verify installed versions
clusterctl versionLinks
Security Audit Report Template
Cluster API Security Assessment Report
---
Report Information
| Field | Value |
|---|---|
| Cluster Name | |
| Namespace | |
| Audit Date | |
| Auditor | |
| CAPI Version | |
| Provider |
---
Executive Summary
Overall Security Posture: [ ] Acceptable [ ] Needs Improvement [ ] Critical Issues
| Category | Status | Findings |
|---|---|---|
| Pod Security | ||
| Network Security | ||
| Authentication | ||
| Authorization | ||
| Secrets Management | ||
| Audit Logging |
Critical Issues:
Recommendations:
---
1. Pod Security Standards (PSS)
1.1 Configuration Check
| Setting | Value | Recommended | Status |
|---|---|---|---|
| PSS Enforcement | baseline/restricted | ||
| PSS Audit Level | restricted | ||
| PSS Warn Level | restricted | ||
| Exempt Namespaces | kube-system only |
1.2 Findings
Run: cd scripts && go run ./audit-security -n <name>
# Paste output here1.3 Recommendations
- [ ]
- [ ]
- [ ]
---
2. Control Plane Security
2.1 API Server Configuration
| Setting | Current | Recommended | Status |
|---|---|---|---|
| Authorization Mode | RBAC,Node | ||
| Anonymous Auth | false | ||
| Audit Logging | enabled | ||
| Admission Controllers | NodeRestriction,PodSecurity | ||
| Encryption at Rest | enabled |
2.2 etcd Security
| Setting | Current | Recommended | Status |
|---|---|---|---|
| Client Cert Auth | true | ||
| Peer Cert Auth | true | ||
| Encryption | enabled |
2.3 Findings
# KubeadmControlPlane analysis output2.4 Recommendations
- [ ]
- [ ]
---
3. Network Security
3.1 Cluster Network
| Setting | Current | Recommended | Status |
|---|---|---|---|
| CNI Plugin | Calico/Cilium | ||
| Network Policies | enabled | ||
| Pod CIDR | |||
| Service CIDR |
3.2 External Access
| Endpoint | Exposure | Protection | Status |
|---|---|---|---|
| API Server | LB + Auth | ||
| Node Ports | Filtered | ||
| Ingress | TLS |
3.3 Findings
- -
3.4 Recommendations
- [ ]
- [ ]
---
4. Authentication & Authorization
4.1 Service Accounts
| Item | Status |
|---|---|
| Default SA token automount disabled | |
| Custom SAs for workloads | |
| SA token rotation |
4.2 RBAC
| Item | Status |
|---|---|
| cluster-admin usage minimized | |
| Namespace-scoped roles preferred | |
| Role bindings reviewed |
4.3 Findings
-
- ***
5. Secrets Management
5.1 Cluster Secrets
| Secret Type | Count | Protection | Status |
|---|---|---|---|
| kubeconfig | Labeled, access controlled | ||
| CA certificates | Rotated, backed up | ||
| Bootstrap tokens | Time-limited | ||
| Provider credentials | Scoped, rotated |
5.2 Secret Exposure Check
Run: cd scripts && go run ./audit-security -n <name> -o report.json
5.3 Findings
-
- ***
6. Availability & Resilience
6.1 Control Plane HA
| Setting | Current | Recommended | Status |
|---|---|---|---|
| CP Replicas | 3 (odd number) | ||
| Multi-AZ distribution | yes | ||
| etcd backup | daily |
6.2 Worker Availability
| Setting | Current | Recommended | Status |
|---|---|---|---|
| Worker count | ≥3 | ||
| MachineHealthCheck | enabled | ||
| maxUnhealthy | 40% | ||
| nodeStartupTimeout | 10m |
6.3 Findings
-
- ***
7. Audit Logging
7.1 Kubernetes Audit
| Setting | Current | Recommended | Status |
|---|---|---|---|
| Audit enabled | yes | ||
| Audit policy | metadata+request | ||
| Log retention | 30 days | ||
| Log storage | external/secure |
7.2 CAPI Component Logging
| Component | Log Level | Status |
|---|---|---|
| CAPI controller | ||
| Provider controller | ||
| Bootstrap controller |
---
8. Compliance Checklist
CIS Kubernetes Benchmark
| Control | Status | Notes |
|---|---|---|
| 1.1 Control Plane | ||
| 1.2 API Server | ||
| 1.3 Controller Manager | ||
| 2.x Worker Nodes | ||
| 3.x Network | ||
| 4.x Policies | ||
| 5.x Authentication |
---
9. Risk Assessment
Critical (Immediate Action Required)
| Finding | Risk | Remediation | Owner | Due |
|---|---|---|---|---|
High
| Finding | Risk | Remediation | Owner | Due |
|---|---|---|---|---|
Medium
| Finding | Risk | Remediation | Owner | Due |
|---|---|---|---|---|
Low
| Finding | Risk | Remediation | Owner | Due |
|---|---|---|---|---|
---
10. Action Items
Immediate (0-7 days)
- [ ]
Short-term (1-4 weeks)
- [ ]
Long-term (1-3 months)
- [ ]
---
Appendix
A. Commands Used
# List commands run during audit
clusterctl describe cluster <name>
cd scripts && go run ./audit-security -n <name>
kubectl get psp # if applicable
# ...B. Raw Outputs
<details> <summary>clusterctl describe output</summary>
# paste here</details>
<details> <summary>Security audit script output</summary>
# paste here</details>
C. References
---
Report Generated: date
Next Audit Due:
Troubleshooting Decision Flowchart
Quick reference for diagnosing CAPI cluster issues.
Primary Diagnosis Flow
START: Cluster Issue Reported
│
▼
┌─────────────────────────┐
│ Run: clusterctl describe│
│ cluster <name> │
└─────────────────────────┘
│
▼
┌───────────┐
│ Cluster │──No──▶ Section A: Cluster Not Found
│ Exists? │
└───────────┘
│Yes
▼
┌───────────┐
│ Cluster │──No──▶ Section B: Cluster Not Ready
│ Ready? │
└───────────┘
│Yes
▼
┌───────────┐
│ Control │──No──▶ Section C: Control Plane Issues
│ Plane OK? │
└───────────┘
│Yes
▼
┌───────────┐
│ Workers │──No──▶ Section D: Worker Issues
│ Ready? │
└───────────┘
│Yes
▼
┌───────────┐
│ Workloads │──No──▶ Section E: Workload Issues
│ Running? │
└───────────┘
│Yes
▼
Issue may be application-level---
Section A: Cluster Not Found
Cluster not found
│
▼
┌─────────────────┐
│ Check namespace │
│ kubectl get │
│ clusters -A │
└─────────────────┘
│
▼
┌─────────┐
│ Found │──Yes──▶ Wrong namespace, switch context
│ in other│
│ ns? │
└─────────┘
│No
▼
┌─────────────────┐
│ Check API │
│ kubectl api- │
│ resources │
└─────────────────┘
│
▼
┌─────────┐
│ CRDs │──No──▶ CAPI not installed: clusterctl init
│ exist? │
└─────────┘
│Yes
▼
Cluster was deleted or never created
→ Review apply command, check events---
Section B: Cluster Not Ready
Cluster exists but not Ready
│
▼
┌─────────────────────────┐
│ Check conditions: │
│ kubectl get cluster -o │
│ yaml | grep -A20 │
│ conditions │
└─────────────────────────┘
│
▼
┌─────────────┐
│Infrastructure│──False──▶ B1: Infrastructure Issue
│Ready? │
└─────────────┘
│True
▼
┌─────────────┐
│ControlPlane │──False──▶ B2: Control Plane Issue
│Ready? │
└─────────────┘
│True
▼
Check error message in condition reasonB1: Infrastructure Issue
InfrastructureReady=False
│
▼
┌─────────────────────────┐
│ Check infra cluster: │
│ kubectl get <provider> │
│ cluster -o yaml │
└─────────────────────────┘
│
▼
┌─────────┐
│ Status │──"Pending"──▶ Cloud credentials issue
│ phase? │
└─────────┘
│
"Failed"│
▼
┌─────────────────────┐
│ Check provider logs │
│ kubectl logs -n │
│ <provider>-system │
└─────────────────────┘
│
▼
Common causes:
• Invalid credentials (401/403)
• Quota exceeded
• Region/zone unavailable
• Network/VPC conflict
• Permission denied---
Section C: Control Plane Issues
Control plane not ready
│
▼
┌─────────────────────────┐
│ kubectl get kcp │
│ kubectl get machines │
│ -l control-plane │
└─────────────────────────┘
│
▼
┌───────────┐
│ KCP │──No──▶ KCP creation failed
│ exists? │ Check events, provider logs
└───────────┘
│Yes
▼
┌───────────┐
│ Machines │
│ count? │
└───────────┘
│ │
0 │ │ >0
▼ ▼
C1: No C2: Machines
Machines Not ReadyC1: No Control Plane Machines
Machines count = 0
│
▼
Check KCP events:
kubectl describe kcp <name>
│
▼
Common causes:
• InfrastructureMachineTemplate not found
• Invalid machine template spec
• Provider quota exceededC2: Machines Not Ready
Machines exist but not Ready
│
▼
┌─────────────────────────┐
│ kubectl get machine │
│ <name> -o yaml │
└─────────────────────────┘
│
▼
┌───────────┐
│Bootstrap │──False──▶ Bootstrap data not ready
│Ready? │ Check kubeadmconfig
└───────────┘
│True
▼
┌───────────┐
│Infra │──False──▶ VM not provisioned
│Ready? │ Check <provider>machine
└───────────┘
│True
▼
┌───────────┐
│NodeRef │──Empty──▶ Node not joined
│ set? │ Check kubeadm logs
└───────────┘---
Section D: Worker Issues
Workers not ready
│
▼
┌─────────────────────┐
│ kubectl get │
│ machinedeployment │
└─────────────────────┘
│
▼
┌───────────┐
│Replicas │──Mismatch──▶ D1: Scaling issue
│correct? │
└───────────┘
│Match
▼
┌───────────┐
│Machines │──Not Ready──▶ D2: Machine issue
│Ready? │
└───────────┘
│Ready
▼
Check workload cluster directlyD1: Scaling Issue
Desired ≠ Current replicas
│
▼
Check MachineSet:
kubectl get machineset
│
▼
┌─────────────┐
│MachineSet │──Not Ready──▶ Template issue
│Ready? │
└─────────────┘
│
▼
┌─────────────┐
│Machines │──Pending──▶ Provider capacity
│creating? │
└─────────────┘
│
▼
Check MachineHealthCheck:
kubectl get mhc
→ May be deleting unhealthy machines---
Section E: Workload Issues
Workloads not running
│
▼
┌─────────────────────┐
│ Get workload │
│ kubeconfig: │
│ clusterctl get │
│ kubeconfig <name> │
└─────────────────────┘
│
▼
┌─────────────┐
│Can connect │──No──▶ API endpoint issue
│to API? │ Check load balancer/DNS
└─────────────┘
│Yes
▼
┌─────────────┐
│Nodes │──Not Ready──▶ CNI not installed
│Ready? │ or kubelet issue
└─────────────┘
│Ready
▼
┌─────────────┐
│CoreDNS │──Not Running──▶ Network/CNI issue
│Running? │
└─────────────┘
│Running
▼
Application-level troubleshooting---
Quick Commands Reference
# Overall status
clusterctl describe cluster <name> --show-conditions all
# Check all resources
kubectl get clusters,machines,machinedeployments,kcp -A
# Provider logs
kubectl logs -n capi-system -l control-plane=controller-manager --tail=100
kubectl logs -n <provider>-system -l control-plane=controller-manager --tail=100
# Events
kubectl get events --sort-by='.lastTimestamp' -n <namespace>
# Machine details
kubectl describe machine <name>
# Bootstrap status
kubectl get kubeadmconfig -o wide
# Health checks triggering
kubectl get mhc -A -o wide---
Common Error Patterns
| Error Pattern | Likely Cause | Fix |
|---|---|---|
no matches for kind | CRDs not installed | clusterctl init |
connection refused | API not accessible | Check LB, security groups |
unauthorized | Bad credentials | Update provider secret |
quota exceeded | Cloud limits | Request quota increase |
subnet not found | Network mismatch | Check VPC/subnet config |
timeout waiting | Slow provisioning | Increase timeouts |
bootstrap not ready | Cloud-init failed | Check machine serial console |
Cluster Upgrade Checklist
Pre-flight and execution checklist for upgrading CAPI-managed clusters.
Pre-Upgrade Assessment
Environment Check
- [ ] Verify current CAPI version:
clusterctl version - [ ] List installed providers:
kubectl get providers -A - [ ] Check target version compatibility (see
provider-matrix.md) - [ ] Review release notes for breaking changes
- [ ] Verify Kubernetes version support matrix
Cluster Health Check
- [ ] All clusters Ready:
kubectl get clusters -A - [ ] All machines Ready:
kubectl get machines -A - [ ] No unhealthy conditions:
kubectl get clusters -A -o yaml | grep -A5 conditions - [ ] Control planes initialized:
kubectl get kubeadmcontrolplanes -A - [ ] MachineHealthChecks not triggering:
kubectl get mhc -A
Backup (CRITICAL)
- [ ] Export cluster state:
# Option 1: clusterctl move to backup cluster
clusterctl move --to-kubeconfig backup-cluster.kubeconfig
# Option 2: Export manifests
cd scripts && go run ./export-cluster-state -n <cluster-name> -o ../backup/- [ ] Save kubeconfigs for all clusters
- [ ] Document current configuration
Upgrade Plan
1. Generate Upgrade Plan
clusterctl upgrade planReview output:
- [ ] Note current versions
- [ ] Note target versions
- [ ] Identify any warnings
2. Upgrade Management Cluster CAPI Components
# Upgrade CAPI core
clusterctl upgrade apply --contract v1beta1
# Or specify versions
clusterctl upgrade apply \
--core cluster-api:v1.12.0 \
--bootstrap kubeadm:v1.12.0 \
--control-plane kubeadm:v1.12.0 \
--infrastructure <provider>:v<version>- [ ] Core upgraded successfully
- [ ] Bootstrap provider upgraded
- [ ] Control plane provider upgraded
- [ ] Infrastructure provider upgraded
3. Verify Management Cluster
- [ ] All pods running:
kubectl get pods -n capi-system - [ ] Provider pods running:
kubectl get pods -n <provider>-system - [ ] CRDs updated:
kubectl get crds | grep cluster.x-k8s.io - [ ] No errors in logs:
kubectl logs -n capi-system -l control-plane=controller-manager
Workload Cluster Upgrades
Per-Cluster Upgrade
For each workload cluster:
Pre-Upgrade
- [ ] Cluster name: *\\\\*\_\\\\**
- [ ] Current K8s version: *\\\\*\_\\\\**
- [ ] Target K8s version: *\\\\*\_\\\\**
- [ ] Backup completed: [ ]
Upgrade Control Plane
# Update KubeadmControlPlane version
kubectl patch kcp <cluster>-control-plane -n <namespace> \
--type merge -p '{"spec":{"version":"v1.XX.Y"}}'- [ ] Version field updated
- [ ] Rollout started:
kubectl get machines -l cluster.x-k8s.io/control-plane - [ ] Monitor progress:
clusterctl describe cluster <name>
Wait for Control Plane
- [ ] All control plane machines updated
- [ ] Control plane Ready
- [ ] API server accessible
Upgrade Workers
# Update MachineDeployment version
kubectl patch machinedeployment <cluster>-md-0 -n <namespace> \
--type merge -p '{"spec":{"template":{"spec":{"version":"v1.XX.Y"}}}}'- [ ] Version field updated
- [ ] Rolling update started
- [ ] Old machines draining
- [ ] New machines Ready
Post-Cluster Verification
- [ ] All nodes updated:
kubectl --kubeconfig=<cluster>.kubeconfig get nodes - [ ] Workloads healthy
- [ ] No pod disruptions
Post-Upgrade Validation
Management Cluster
- [ ]
clusterctl describe clustershows no issues - [ ] All clusters transitioned to Ready
- [ ] Provider logs clean
Workload Clusters
For each cluster:
- [ ] Kubernetes version correct
- [ ] All nodes Ready
- [ ] CoreDNS running
- [ ] CNI functioning
- [ ] Workloads healthy
- [ ] Ingress working
- [ ] Persistent volumes accessible
Monitoring & Alerts
- [ ] Metrics being collected
- [ ] Dashboards updated
- [ ] No new alerts triggered
Rollback Plan (If Needed)
CAPI Component Rollback
# Revert to previous version
clusterctl upgrade apply \
--core cluster-api:v<previous> \
--bootstrap kubeadm:v<previous> \
--control-plane kubeadm:v<previous> \
--infrastructure <provider>:v<previous>Cluster Rollback
1. Restore from backup (clusterctl move back) 2. Or re-create from saved manifests
Sign-Off
| Role | Name | Date | Signature |
|---|---|---|---|
| Operator | |||
| Reviewer | |||
| Approver |
Notes
_Record any issues, deviations, or observations:_
---
Upgrade completed: [ ] Yes [ ] No (with issues)
Date: *\\*\_\_\_\\**
Duration: *\\*\_\_\_\\**
API Reference
Version Support
Release Support Matrix
| Release | Status | Support End |
|---|---|---|
| v1.12.x | Supported | EOL when v1.15.0 releases |
| v1.11.x | Supported | EOL when v1.14.0 releases |
| v1.10.x | Maintenance | EOL when v1.13.0 releases |
| v1.9.x | EOL | Since 2025-12-18 |
Support lifecycle:
- Standard support: 8 months (CI, bug fixes, patches)
- Maintenance mode: 4 months (critical fixes only)
- Release cadence: ~4 months (3 releases/year)
API Versions
| API Version | Status | Notes |
|---|---|---|
| v1beta2 | Supported | Current |
| v1beta1 | Deprecated | Stopped serving in v1.14 |
| v1alpha4 | Removed | Removed in v1.13 |
| v1alpha3 | Removed | Removed in v1.13 |
Contract Versions
| Contract | Status | Notes |
|---|---|---|
| v1beta2 | Supported | Compatible with v1beta1 temporarily |
| v1beta1 | Deprecated | Compatibility dropped in v1.14 |
---
Kubernetes Compatibility
Management Cluster
| CAPI Release | Kubernetes Versions |
|---|---|
| v1.13.x | v1.32 - v1.36 |
| v1.12.x | v1.28 - v1.32 |
| v1.11.x | v1.27 - v1.31 |
| v1.10.x | v1.26 - v1.30 |
Workload Cluster
| CAPI Release | Kubernetes Versions |
|---|---|
| v1.13.x | v1.30 - v1.36 |
| v1.12.x | v1.26 - v1.32 |
| v1.11.x | v1.25 - v1.31 |
| v1.10.x | v1.24 - v1.30 |
Upgrade Rules
- Maximum skip: 3 minor versions (e.g., v1.6→v1.9)
- Downgrades: Not supported
- Kubernetes upgrades: Must be sequential (e.g., v1.28→v1.29→v1.30)
---
Provider Implementations
Bootstrap Providers
| Provider | Description |
|---|---|
| Kubeadm | Official, uses kubeadm for bootstrap |
| K3s | Lightweight Kubernetes |
| RKE2 | Rancher Kubernetes Engine 2 |
| Talos | Immutable Kubernetes OS |
| MicroK8s | Canonical's lightweight K8s |
| k0smotron/k0s | Zero-friction Kubernetes |
| EKS | Amazon EKS bootstrap |
Control Plane Providers
| Provider | Description |
|---|---|
| Kubeadm | Official control plane provider |
| K3s | K3s control plane |
| RKE2 | RKE2 control plane |
| Talos | Talos control plane |
| Kamaji | Hosted control planes |
| Nested | Nested clusters |
Infrastructure Providers
| Provider | Cloud/Platform |
|---|---|
| AWS | Amazon Web Services |
| Azure | Microsoft Azure |
| GCP | Google Cloud Platform |
| vSphere | VMware vSphere |
| Docker | Development/testing |
| Metal3 | Bare metal (Ironic) |
| OpenStack | OpenStack clouds |
| Hetzner | Hetzner Cloud |
| DigitalOcean | DigitalOcean |
| Linode/Akamai | Akamai Cloud |
| Nutanix | Nutanix AHV |
| KubeVirt | VM-based workloads |
| Proxmox | Proxmox VE |
| BYOH | Bring Your Own Host |
| Harvester | HCI platform |
| OCI | Oracle Cloud |
| Vultr | Vultr Cloud |
IPAM Providers
| Provider | Description |
|---|---|
| In-Cluster | Built-in IPAM |
| Metal3 | Metal3 IPAM |
| Nutanix | Nutanix IPAM |
Addon Providers
| Provider | Description |
|---|---|
| Helm | Helm chart deployment (CAAPH) |
| Fleet | Rancher Fleet |
---
Core Resources
Cluster
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: my-cluster
namespace: default
spec:
clusterNetwork:
pods:
cidrBlocks: ["192.168.0.0/16"]
services:
cidrBlocks: ["10.96.0.0/12"]
serviceDomain: "cluster.local"
controlPlaneRef:
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
name: my-cluster-control-plane
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: <InfraCluster>
name: my-clusterMachine
apiVersion: cluster.x-k8s.io/v1beta1
kind: Machine
metadata:
name: my-machine
spec:
clusterName: my-cluster
version: v1.28.0
bootstrap:
configRef:
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfig
name: my-machine-bootstrap
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: <InfraMachine>
name: my-machineMachineDeployment
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineDeployment
metadata:
name: my-cluster-md-0
spec:
clusterName: my-cluster
replicas: 3
selector:
matchLabels:
cluster.x-k8s.io/cluster-name: my-cluster
template:
spec:
clusterName: my-cluster
version: v1.28.0
bootstrap:
configRef:
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
name: my-cluster-md-0
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: <InfraMachineTemplate>
name: my-cluster-md-0MachineHealthCheck
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineHealthCheck
metadata:
name: my-cluster-mhc
spec:
clusterName: my-cluster
selector:
matchLabels:
cluster.x-k8s.io/cluster-name: my-cluster
unhealthyConditions:
- type: Ready
status: "False"
timeout: 5m
- type: Ready
status: Unknown
timeout: 5m
maxUnhealthy: 40%
nodeStartupTimeout: 10mKubeadmControlPlane
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
metadata:
name: my-cluster-control-plane
spec:
replicas: 3
version: v1.28.0
machineTemplate:
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: <InfraMachineTemplate>
name: my-cluster-control-plane
kubeadmConfigSpec:
clusterConfiguration:
apiServer:
extraArgs:
cloud-provider: external
controllerManager:
extraArgs:
cloud-provider: external
initConfiguration:
nodeRegistration:
kubeletExtraArgs:
cloud-provider: external
joinConfiguration:
nodeRegistration:
kubeletExtraArgs:
cloud-provider: external---
Glossary
| Term | Definition |
|---|---|
| Management Cluster | Kubernetes cluster running CAPI controllers |
| Workload Cluster | Kubernetes cluster managed by CAPI |
| Bootstrap Cluster | Temporary cluster for initial setup |
| Provider | Component implementing cloud-specific logic |
| InfraCluster | Cloud-specific cluster infrastructure resource |
| InfraMachine | Cloud-specific machine resource |
| ControlPlane | Control plane management resource |
| MachineClass | Deprecated, replaced by ClusterClass |
| ClusterClass | Template for cluster topology |
| Pivot | Moving CAPI resources between management clusters |
| Contract | API compatibility rules for providers |
---
Labels and Annotations
Standard Labels
| Label | Purpose |
|---|---|
cluster.x-k8s.io/cluster-name | Identifies cluster membership |
cluster.x-k8s.io/control-plane | Marks control plane machines |
cluster.x-k8s.io/provider | Provider identifier |
topology.cluster.x-k8s.io/owned | ClusterClass managed |
Standard Annotations
| Annotation | Purpose |
|---|---|
cluster.x-k8s.io/paused | Pause reconciliation |
cluster.x-k8s.io/delete-machine | Mark for deletion |
cluster.x-k8s.io/cloned-from-name | Source template name |
clusterctl.cluster.x-k8s.io/block-move | Block move operation |
Autoscaler Annotations
| Annotation | Purpose |
|---|---|
cluster.x-k8s.io/cluster-api-autoscaler-node-group-min-size | Minimum replicas |
cluster.x-k8s.io/cluster-api-autoscaler-node-group-max-size | Maximum replicas |
capacity.cluster-autoscaler.kubernetes.io/memory | Memory capacity |
capacity.cluster-autoscaler.kubernetes.io/cpu | CPU capacity |
module k8s-cluster-api-tools
go 1.22
require gopkg.in/yaml.v3 v3.0.1
require gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQo/ZuKHPYJ0Spo/dA0o=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJE68CQ=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=