From 1fe4580c482a4ca1c5dff25df4dd005755b8dbb6 Mon Sep 17 00:00:00 2001 From: Kurt Garloff Date: Fri, 24 Apr 2026 17:25:30 +0000 Subject: [PATCH 1/2] Add optional availabilityZone.from:Machine to disks. Previously capo defaulted to creating disks in the failureDomain (availability zone) of the virtual machine that is created. In some clouds this is a requirement for good performance or to be able to attach the disk at all. Other clouds have a global AZ for disks, so we need a pararmeter to control this. So we introduce three new boolean variables, all defaulting to false: controlPlaneRootDiskPin workerRootDiskPin workerAdditionalBlockDevicesPin which control whether we set availabilityZone/from: Machine for the respective disks. Signed-off-by: Kurt Garloff --- .../templates/cluster-class.yaml | 111 +++++++++++++++++- 1 file changed, 108 insertions(+), 3 deletions(-) diff --git a/providers/openstack/scs2/cluster-class/templates/cluster-class.yaml b/providers/openstack/scs2/cluster-class/templates/cluster-class.yaml index d7fbd338..ae7b36aa 100644 --- a/providers/openstack/scs2/cluster-class/templates/cluster-class.yaml +++ b/providers/openstack/scs2/cluster-class/templates/cluster-class.yaml @@ -133,6 +133,19 @@ spec: Root disk size in GiB for control-plane nodes. OpenStack volume will be created and used instead of an ephemeral disk defined in flavor. Should only be used for the diskless flavors (>= 20), otherwise set to 0. + - name: controlPlaneRootDiskPin + required: false + schema: + openAPIV3Schema: + type: boolean + example: true + default: false + description: |- + Pass availabilityZone (failureDomain) of the worker machine to the root disk. + Some clouds have a 1:1 relationship of storage availability zones with compute AZs. + In these, attaching a disk from another AZ often comes with a performance penalty + or is not possible at all. You want to set to true to tell capo to create the root + disk in the stated failureDomain of this machine deployment. - name: controlPlaneServerGroupID required: false schema: @@ -180,6 +193,19 @@ spec: Root disk size in GiB for worker nodes. OpenStack volume will be created and used instead of an ephemeral disk defined in flavor. Should be used for the diskless flavors (>= 20), otherwise set to 0. + - name: workerRootDiskPin + required: false + schema: + openAPIV3Schema: + type: boolean + example: true + default: false + description: |- + Pass availabilityZone (failureDomain) of the machine to the root disk. + Some clouds have a 1:1 relationship of storage availability zones with compute AZs. + In these, attaching a disk from another AZ often comes with a performance penalty + or is not possible at all. You want to set to true to tell capo to create the root + disk in the wanted AZ of the machine. - name: workerServerGroupID required: false schema: @@ -193,6 +219,9 @@ spec: schema: openAPIV3Schema: type: array + description: |- + You may pass an array with additional volumes (disks) here, consisting of + name, sizeGiB (default: 20) and type (aka storage class, default: __DEFAULT__). default: [] items: type: object @@ -206,6 +235,19 @@ spec: type: string default: "__DEFAULT__" required: ["name"] + - name: workerAdditionalBlockDevicesPin + required: false + schema: + openAPIV3Schema: + type: boolean + example: true + default: false + description: |- + Pass availabilityZone (failureDomain) of the machine to the additional disks. + Some clouds have a 1:1 relationship of storage availability zones with compute AZs. + In these, attaching a disk from another AZ often comes with a performance penalty + or is not possible at all. You want to set to true to tell capo to create the + additional disks in the wanted AZ of the machine. # Access management - name: sshKeyName required: false @@ -570,7 +612,22 @@ spec: variable: controlPlaneFlavor - name: controlPlaneRootDisk description: "Sets the root disk size in GiB for control-plane nodes." - enabledIf: {{ `'{{ if .controlPlaneRootDisk }}true{{end}}'` }} + enabledIf: {{ `'{{ if and .controlPlaneRootDisk (not .controlPlaneRootDiskPin) }}true{{end}}'` }} + definitions: + - selector: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: OpenStackMachineTemplate + matchResources: + controlPlane: true + jsonPatches: + - op: add + path: "/spec/template/spec/rootVolume" + valueFrom: + template: | + sizeGiB: {{ `{{ .controlPlaneRootDisk }}` }} + - name: controlPlaneRootDiskPinned + description: "Sets the root disk size in GiB for control-plane nodes and pins them to the failureDomain of the machine." + enabledIf: {{ `'{{ if and .controlPlaneRootDisk .controlPlaneRootDiskPin }}true{{end}}'` }} definitions: - selector: apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 @@ -583,6 +640,8 @@ spec: valueFrom: template: | sizeGiB: {{ `{{ .controlPlaneRootDisk }}` }} + availabilityZone: + from: Machine - name: controlPlaneServerGroupID description: "Sets the server group to assign the control plane nodes to." enabledIf: {{ `'{{ ne .controlPlaneServerGroupID "" }}'` }} @@ -697,7 +756,7 @@ spec: variable: workerFlavor - name: workerRootDisk description: "Sets the root disk size in GiB for worker nodes." - enabledIf: {{ `'{{ if .workerRootDisk }}true{{end}}'` }} + enabledIf: {{ `'{{ if and .workerRootDisk (not .workerRootDiskPin) }}true{{end}}'` }} definitions: - selector: apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 @@ -713,6 +772,26 @@ spec: valueFrom: template: | sizeGiB: {{ `{{ .workerRootDisk }}` }} + - name: workerRootDiskPinned + description: "Sets the root disk size in GiB for worker nodes and pins the to the AZ of the machine." + enabledIf: {{ `'{{ if and .workerRootDisk .workerRootDiskPin }}true{{end}}'` }} + definitions: + - selector: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: OpenStackMachineTemplate + matchResources: + controlPlane: false + machineDeploymentClass: + names: + - default-worker + jsonPatches: + - op: add + path: "/spec/template/spec/rootVolume" + valueFrom: + template: | + sizeGiB: {{ `{{ .workerRootDisk }}` }} + availabilityZone: + from: Machine - name: workerServerGroupID description: "Sets the server group to assign the worker nodes to." enabledIf: {{ `'{{ ne .workerServerGroupID "" }}'` }} @@ -732,7 +811,31 @@ spec: template: | id: {{ `{{ .workerServerGroupID }}` }} - name: workerAdditionalBlockDevices - enabledIf: {{ `'{{ if .workerAdditionalBlockDevices }}true{{end}}'` }} + enabledIf: {{ `'{{ if and .workerAdditionalBlockDevices (not .workerAdditionalBlockDevicesPin) }}true{{end}}'` }} + definitions: + - selector: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: OpenStackMachineTemplate + matchResources: + controlPlane: false + machineDeploymentClass: + names: + - default-worker + jsonPatches: + - op: add + path: /spec/template/spec/additionalBlockDevices + valueFrom: + template: | + {{ `{{- range .workerAdditionalBlockDevices }}` }} + - name: {{ `{{ .name }}` }} + sizeGiB: {{ `{{ .sizeGiB }}` }} + storage: + type: Volume + volume: + type: {{ `{{ .type }}` }} + {{ `{{- end }}` }} + - name: workerAdditionalBlockDevicesPinned + enabledIf: {{ `'{{ if and .workerAdditionalBlockDevices .workerAdditionalBlockDevicesPin }}true{{end}}'` }} definitions: - selector: apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 @@ -750,6 +853,8 @@ spec: {{ `{{- range .workerAdditionalBlockDevices }}` }} - name: {{ `{{ .name }}` }} sizeGiB: {{ `{{ .sizeGiB }}` }} + availabilityZone: + from: Machine storage: type: Volume volume: From c7df8ed9e7e69076f2be927d0cf2cdabac22722a Mon Sep 17 00:00:00 2001 From: Kurt Garloff Date: Thu, 28 May 2026 14:44:22 +0200 Subject: [PATCH 2/2] Use one setting diskPinAZ to control all volumes. This was suggested by @Nils98Ar. There are few imaginable reasons why one would want to pin volumes into the AZs of the VMs they are attached to for some types but not for others. So have one setting, controlling all of controlPlaneNodes root disks, workerMachines root disks and worker additionalBlockDevices. Also add a bit of documentation. Signed-off-by: Kurt Garloff --- docs/providers/openstack/configuration.md | 1 + .../templates/cluster-class.yaml | 73 +++++++------------ 2 files changed, 26 insertions(+), 48 deletions(-) diff --git a/docs/providers/openstack/configuration.md b/docs/providers/openstack/configuration.md index f72b29f2..3a206128 100644 --- a/docs/providers/openstack/configuration.md +++ b/docs/providers/openstack/configuration.md @@ -80,5 +80,6 @@ topology: | `oidc_config.username_prefix` | string | oidc: | oidc: | Prefix prepended to username claims to prevent cla shes with existing names (such as system: users). For example, the value oid c: will create usernames like oidc:jane.doe. If this flag isn't provided and --o idc-username-claim is a value other than email the prefix defaults to ( Iss uer URL )# where ( Issuer URL ) is the value of --oidc-issuer-url. The value - c an be used to disable all prefixing. | | | `oidc_config.groups_prefix` | string | oidc: | oidc: | Prefix prepended to group claims to prevent clashes wit h existing names (such as system: groups). For example, the value oidc: will cre ate group names like oidc:engineering and oidc:infra. | | | `network_mtu` | integer | | 1500 | NetworkMTU sets the maximum transmission unit (MTU) value to address fragmentation for the private network ID. | False | +| `diskPinAZ` | boolean | | False | This tells capo to create volumes in the same AZ as the VM that they are attached to. This affects cinder volumes used as controlPlane and worker machine root disks as well as additionalBlockDevices. Only works for clouds where volume AZs are matched with compute AZs. There it might be a performance optimization or a requirement. | `controlPlaneAvailabilityZones` | array | | ['nova'] | ControlPlaneAvailabilityZones is the set of availability zones which control plane machines may be deployed to. | False | | `controlPlaneOmitAvailabilityZone` | boolean | | True | ControlPlaneOmitAvailabilityZone causes availability zone to be omitted when creating control plane nodes, allowing the Nova scheduler to make a decision on which availability zone to use based on other scheduling constraints. | False | diff --git a/providers/openstack/scs2/cluster-class/templates/cluster-class.yaml b/providers/openstack/scs2/cluster-class/templates/cluster-class.yaml index ae7b36aa..88e289d7 100644 --- a/providers/openstack/scs2/cluster-class/templates/cluster-class.yaml +++ b/providers/openstack/scs2/cluster-class/templates/cluster-class.yaml @@ -110,6 +110,21 @@ spec: Cluster actuator will create a network, a subnet with nodeCIDR, and a router connected to this subnet. If you leave this empty, no network will be created. + - name: diskPinAZ + required: false + schema: + openAPIV3Schema: + type: boolean + example: true + default: false + description: |- + Pass availabilityZone (failureDomain) of the machine to its (root) disk volumes. + Some clouds have a 1:1 relationship of storage availability zones with compute AZs. + In these, attaching a disk from another AZ often comes with a performance penalty + or is not possible at all. You can set this to true to tell capo to create the root + disk in the stated failureDomain of this machine. This setting applies to controlPlane + root disks (if they use root volumes), worker machine root disks (if they use root + volumes) and workerAdditionalBlockDevices (if any are configured). # Control plane - name: controlPlaneFlavor required: false @@ -119,8 +134,9 @@ spec: default: "SCS-2V-4" example: "SCS-2V-4-20s" description: |- - OpenStack instance flavor for control plane nodes. - (Default: SCS-2V-4, replace by SCS-2V-4-20s or specify a controlPlaneRootDisk.) + OpenStack instance flavor for control plane nodes. (The default, SCS-2V-4, is not ideal; + recommendation is to replace this by SCS-2V-4-20s and set controlPlaneRootDisk to 0 + or specify a non-zero controlPlaneRootDisk with a diskless flavor.) - name: controlPlaneRootDisk required: false schema: @@ -132,20 +148,7 @@ spec: description: |- Root disk size in GiB for control-plane nodes. OpenStack volume will be created and used instead of an ephemeral disk defined in flavor. - Should only be used for the diskless flavors (>= 20), otherwise set to 0. - - name: controlPlaneRootDiskPin - required: false - schema: - openAPIV3Schema: - type: boolean - example: true - default: false - description: |- - Pass availabilityZone (failureDomain) of the worker machine to the root disk. - Some clouds have a 1:1 relationship of storage availability zones with compute AZs. - In these, attaching a disk from another AZ often comes with a performance penalty - or is not possible at all. You want to set to true to tell capo to create the root - disk in the stated failureDomain of this machine deployment. + Should only be used for the diskless flavors (and then be set >= 20), otherwise set to 0. - name: controlPlaneServerGroupID required: false schema: @@ -193,19 +196,6 @@ spec: Root disk size in GiB for worker nodes. OpenStack volume will be created and used instead of an ephemeral disk defined in flavor. Should be used for the diskless flavors (>= 20), otherwise set to 0. - - name: workerRootDiskPin - required: false - schema: - openAPIV3Schema: - type: boolean - example: true - default: false - description: |- - Pass availabilityZone (failureDomain) of the machine to the root disk. - Some clouds have a 1:1 relationship of storage availability zones with compute AZs. - In these, attaching a disk from another AZ often comes with a performance penalty - or is not possible at all. You want to set to true to tell capo to create the root - disk in the wanted AZ of the machine. - name: workerServerGroupID required: false schema: @@ -235,19 +225,6 @@ spec: type: string default: "__DEFAULT__" required: ["name"] - - name: workerAdditionalBlockDevicesPin - required: false - schema: - openAPIV3Schema: - type: boolean - example: true - default: false - description: |- - Pass availabilityZone (failureDomain) of the machine to the additional disks. - Some clouds have a 1:1 relationship of storage availability zones with compute AZs. - In these, attaching a disk from another AZ often comes with a performance penalty - or is not possible at all. You want to set to true to tell capo to create the - additional disks in the wanted AZ of the machine. # Access management - name: sshKeyName required: false @@ -612,7 +589,7 @@ spec: variable: controlPlaneFlavor - name: controlPlaneRootDisk description: "Sets the root disk size in GiB for control-plane nodes." - enabledIf: {{ `'{{ if and .controlPlaneRootDisk (not .controlPlaneRootDiskPin) }}true{{end}}'` }} + enabledIf: {{ `'{{ if and .controlPlaneRootDisk (not .diskPinAZ) }}true{{end}}'` }} definitions: - selector: apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 @@ -627,7 +604,7 @@ spec: sizeGiB: {{ `{{ .controlPlaneRootDisk }}` }} - name: controlPlaneRootDiskPinned description: "Sets the root disk size in GiB for control-plane nodes and pins them to the failureDomain of the machine." - enabledIf: {{ `'{{ if and .controlPlaneRootDisk .controlPlaneRootDiskPin }}true{{end}}'` }} + enabledIf: {{ `'{{ if and .controlPlaneRootDisk .diskPinAZ }}true{{end}}'` }} definitions: - selector: apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 @@ -756,7 +733,7 @@ spec: variable: workerFlavor - name: workerRootDisk description: "Sets the root disk size in GiB for worker nodes." - enabledIf: {{ `'{{ if and .workerRootDisk (not .workerRootDiskPin) }}true{{end}}'` }} + enabledIf: {{ `'{{ if and .workerRootDisk (not .diskPinAZ) }}true{{end}}'` }} definitions: - selector: apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 @@ -774,7 +751,7 @@ spec: sizeGiB: {{ `{{ .workerRootDisk }}` }} - name: workerRootDiskPinned description: "Sets the root disk size in GiB for worker nodes and pins the to the AZ of the machine." - enabledIf: {{ `'{{ if and .workerRootDisk .workerRootDiskPin }}true{{end}}'` }} + enabledIf: {{ `'{{ if and .workerRootDisk .diskPinAZ }}true{{end}}'` }} definitions: - selector: apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 @@ -811,7 +788,7 @@ spec: template: | id: {{ `{{ .workerServerGroupID }}` }} - name: workerAdditionalBlockDevices - enabledIf: {{ `'{{ if and .workerAdditionalBlockDevices (not .workerAdditionalBlockDevicesPin) }}true{{end}}'` }} + enabledIf: {{ `'{{ if and .workerAdditionalBlockDevices (not .diskPinAZ) }}true{{end}}'` }} definitions: - selector: apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 @@ -835,7 +812,7 @@ spec: type: {{ `{{ .type }}` }} {{ `{{- end }}` }} - name: workerAdditionalBlockDevicesPinned - enabledIf: {{ `'{{ if and .workerAdditionalBlockDevices .workerAdditionalBlockDevicesPin }}true{{end}}'` }} + enabledIf: {{ `'{{ if and .workerAdditionalBlockDevices .diskPinAZ }}true{{end}}'` }} definitions: - selector: apiVersion: infrastructure.cluster.x-k8s.io/v1beta1