Cluster API on OpenStack: Part 4 - Day-2 Operations
With clusters running in production, you’ll need to perform ongoing maintenance. CAPI’s immutable infrastructure model means changes like disk expansion require creating new templates and performing rolling updates.
This is part 4 of the Cluster API on OpenStack series:
- Introduction and Architecture
- Building CAPI Images
- Deploying Workload Clusters
- Day-2 Operations (this post)
Expanding Worker Disks
A common operational task is expanding the root disk on worker nodes. Since CAPI uses immutable infrastructure, you can’t resize disks in place. Instead, you create a new template with the larger disk and update the MachineDeployment.
Check Current Configuration
# Find your MachineDeployment
kubectl get machinedeployment
# See which template it references
kubectl get machinedeployment prod-cluster-md-0 \
-o jsonpath='{.spec.template.spec.infrastructureRef.name}'Export and Modify the Template
# Export current template
TEMPLATE=$(kubectl get machinedeployment prod-cluster-md-0 \
-o jsonpath='{.spec.template.spec.infrastructureRef.name}')
kubectl get openstackmachinetemplate $TEMPLATE -o yaml > worker-template.yamlEdit worker-template.yaml to add or increase rootVolume:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: OpenStackMachineTemplate
metadata:
name: prod-cluster-md-0-50gb # New name
spec:
template:
spec:
flavor: m1.medium-disk
image:
filter:
name: ubuntu-2404-kube-v1.32.0
rootVolume:
sizeGiB: 50 # Increased from 20
volumeType: ceph-hdd # Your Cinder volume typeRemove metadata that would prevent creation:
# Remove resourceVersion, uid, creationTimestamp
sed -i '/resourceVersion:/d' worker-template.yaml
sed -i '/uid:/d' worker-template.yaml
sed -i '/creationTimestamp:/d' worker-template.yamlApply and Update the Reference
# Create the new template
kubectl apply -f worker-template.yaml
# Verify it exists
kubectl get openstackmachinetemplates
# Patch the MachineDeployment to use it
kubectl patch machinedeployment prod-cluster-md-0 \
--type=json \
-p='[{"op": "replace", "path": "/spec/template/spec/infrastructureRef/name", "value": "prod-cluster-md-0-50gb"}]'Watch the Rolling Update
kubectl get machines -wCAPI will:
- Create new machines with the updated template
- Wait for them to become ready
- Drain and delete old machines one at a time
The maxSurge and maxUnavailable settings on the MachineDeployment control the rollout pace.
Template Isolation: One Template Per Cluster
A word of caution on template management: avoid sharing templates between clusters. While it might seem efficient to have a single worker-50gb template used by multiple clusters, this creates several problems:
- Coupled lifecycles - Deleting cluster A’s templates breaks cluster B
- No independent updates - You can’t change disk size for one cluster without affecting others
- Accidental cascading changes - Updating “your” template triggers rolling updates everywhere
Instead, use a clear naming convention:
prod-cluster-md-0-50gb
staging-cluster-md-0-30gb
dev-cluster-md-0-20gbThe storage overhead is negligible—these are small Kubernetes objects—and the operational clarity is worth it.
Scaling Workers
Scaling is straightforward with MachineDeployments:
# Scale to 5 workers
kubectl scale machinedeployment prod-cluster-md-0 --replicas=5
# Or patch the spec
kubectl patch machinedeployment prod-cluster-md-0 \
--type=merge \
-p='{"spec":{"replicas":5}}'Kubernetes Version Upgrades
Upgrades are a two-part change: bump the control plane version, then roll workers by pointing the MachineDeployment at a new template built from a matching image.
1) Prepare a New Worker Template
Create a new OpenStackMachineTemplate that references an image built for the target Kubernetes version (for example, v1.33.0).
# Identify the current worker template name
TEMPLATE=$(kubectl get machinedeployment prod-cluster-md-0 \
-o jsonpath='{.spec.template.spec.infrastructureRef.name}')
# Export it so we can edit a copy
kubectl get openstackmachinetemplate "$TEMPLATE" -o yaml > worker-template-v1.33.0.yamlEdit worker-template-v1.33.0.yaml:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: OpenStackMachineTemplate
metadata:
name: prod-cluster-md-0-v1.33.0
spec:
template:
spec:
image:
filter:
name: ubuntu-2404-kube-v1.33.0Remove fields that prevent creation:
sed -i '/resourceVersion:/d' worker-template-v1.33.0.yaml
sed -i '/uid:/d' worker-template-v1.33.0.yaml
sed -i '/creationTimestamp:/d' worker-template-v1.33.0.yamlApply the new template:
kubectl apply -f worker-template-v1.33.0.yaml2) Prepare a New Control Plane Template
The control plane also uses an OpenStackMachineTemplate, referenced by the KubeadmControlPlane. Create a new template that points at the target image.
# Identify the current control plane template name
CP_TEMPLATE=$(kubectl get kubeadmcontrolplane prod-cluster-control-plane \
-o jsonpath='{.spec.machineTemplate.infrastructureRef.name}')
# Export it so we can edit a copy
kubectl get openstackmachinetemplate "$CP_TEMPLATE" -o yaml > controlplane-template-v1.33.0.yamlEdit controlplane-template-v1.33.0.yaml:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: OpenStackMachineTemplate
metadata:
name: prod-cluster-control-plane-v1.33.0
spec:
template:
spec:
image:
filter:
name: ubuntu-2404-kube-v1.33.0Remove fields that prevent creation:
sed -i '/resourceVersion:/d' controlplane-template-v1.33.0.yaml
sed -i '/uid:/d' controlplane-template-v1.33.0.yaml
sed -i '/creationTimestamp:/d' controlplane-template-v1.33.0.yamlApply the new template:
kubectl apply -f controlplane-template-v1.33.0.yaml3) Upgrade the Control Plane
Patch both the Kubernetes version and the control plane template reference. CAPI will roll the control plane nodes one by one.
kubectl patch kubeadmcontrolplane prod-cluster-control-plane \
--type=merge \
-p='{"spec":{"version":"v1.33.0","machineTemplate":{"infrastructureRef":{"name":"prod-cluster-control-plane-v1.33.0"}}}}'4) Roll Workers to the New Template
Point the MachineDeployment at the new template to trigger a rolling update of worker nodes.
kubectl patch machinedeployment prod-cluster-md-0 \
--type=json \
-p='[{"op": "replace", "path": "/spec/template/spec/infrastructureRef/name", "value": "prod-cluster-md-0-v1.33.0"}]'5) Watch the Upgrade
kubectl get machines -wIf you want to track control plane nodes specifically:
kubectl get kubeadmcontrolplane prod-cluster-control-plane -o wideConclusion
Cluster API transforms Kubernetes cluster management from imperative scripts to declarative resources. On OpenStack, this means:
- Version-controlled cluster definitions
- Automated rolling updates for Kubernetes upgrades
- Consistent day-2 operations across environments
- Natural integration with GitOps workflows
The learning curve is real—understanding the resource hierarchy takes time—but the operational benefits compound over time. Start with a simple workload cluster, get comfortable with the resource relationships, and gradually adopt more advanced patterns like ClusterClass for standardized configurations.
The combination of OpenStack’s infrastructure capabilities with CAPI’s declarative management creates a powerful platform for running Kubernetes at scale.