Creating Clusters on VMware vSphere
This document explains how to create a VMware vSphere workload cluster by applying Cluster API manifests to the ACP global management cluster. The procedure does not create the global cluster itself. It covers a minimum supported topology with one datacenter, one NIC per node, and static IP allocation through VSphereMachineConfigPool. The provider behavior and fields on this page are validated against VMware vSphere Provider v1.0.17.
TOC
Provider RequirementsScenariosHow to Use This PagePrerequisitesControl Plane Endpoint ModesKey ObjectsClusterResourceSetvSphere CPI componentProvider-managed fields on your manifestsmachine config poolVM template requirementsLocal File LayoutStepsVerificationTroubleshootingAdmission rejectionsCreation-Time Topology VariantsUse a Self-built VIPSelf-built VIP prerequisitescontrolPlaneLoadBalancer fieldsEnable the Self-built VIP in 10-cluster.yamlVerify the Self-built VIPAdd a second NICMultiple datacenters and failure domainsAdd data disksChoose persistent or ephemeralPin a SCSI unit numberRelocating the pod log directoryVerify creation-time variantsNext StepsProvider Requirements
Read the installed provider version from the provider AppRelease on the management cluster; do not infer it from the ACP release. See Identify the Installed Provider Version.
The following rules apply when the VSphereMachineTemplate referenced by a controller sets machineConfigPoolRef — the fixed-IP topology this page teaches. The provider's admission webhooks enforce them on CREATE and UPDATE; the last rule is enforced on DELETE.
KubeadmControlPlane.spec.rolloutStrategy.rollingUpdate.maxSurgeis the integer0, andspec.replicasis3or more. An unsetmaxSurgemeans the Cluster API default1, which is not accepted.MachineDeployment.spec.strategy.rollingUpdate.maxSurgeis0andmaxUnavailableis1or more.strategy.type: OnDeleteis exempt from both.VSphereMachineConfigPool.spec.configsdeclares at least one slot, and every slot declaresnetwork. Hostnames and primary IP addresses are unique across every pool bound to the sameCluster, and one pool serves one controller.- A slot that is already
InUseorReleasedaccepts additions to either disk list, and nothing else. See Allocated slots are immutable. - A pool that still owns
InUseslots cannot be deleted.
Confirm the webhooks are registered in your environment with the webhook check in Verify the installation. Where a webhook is not registered, the same values are still required: the fixed-IP slot model has no free slot for a surge machine, so a non-compliant rollout stalls instead of being rejected up front.
Scenarios
Use this document in the following scenarios:
- You want to create the first baseline VMware vSphere workload cluster in your environment.
- You use one datacenter and one NIC per node for the initial validation.
- You want to keep the first deployment simple before enabling advanced placement or networking features.
This document applies to the following deployment model:
- CAPV connects directly to vCenter.
- Control plane and worker nodes both use
VSphereMachineConfigPoolfor static IP allocation and data disks. ClusterResourceSetdelivers the vSphere CPI component automatically.- The first validation uses one datacenter and one NIC per node.
This document does not apply to the following scenarios:
- A deployment that depends on vSphere Supervisor or
vm-operator. - A deployment that does not use
VSphereMachineConfigPool.
This document is written for the current platform environment. The kube-ovn delivery path depends on platform controllers that consume annotations on the Cluster resource, so this workflow is not intended to be a generic standalone CAPV deployment guide outside the platform context.
How to Use This Page
- Complete the infrastructure and parameter checklist.
- Prepare the baseline manifest files in the procedure below.
- Before applying them, add only the required creation-time topology variants.
- Apply the complete manifest set and finish the verification checks.
- After the cluster is running, use Managing Nodes on VMware vSphere for scale-out, immutable template replacement, and runtime topology changes.
The baseline is the validation reference. If the target cluster needs several optional topology features, introduce and validate one manifest change at a time before combining them.
Prerequisites
Before you begin, ensure the following conditions are met:
- You completed VMware vSphere Infrastructure Preparation.
- The
globalcluster can reach vCenter. - The target template, networks, datastores, and vCenter resource pool are available.
- The control plane endpoint mode is selected. For an external LoadBalancer, it satisfies the endpoint contract. For a Self-built VIP, the Self-built VIP prerequisites are met. The mode cannot be changed once the cluster exists, so decide it now: see Control Plane Endpoint Modes.
- All required static IP addresses are allocated, are not in use, and are not already declared by a slot in another
VSphereMachineConfigPoolbound to the sameCluster. ClusterResourceSet=trueis enabled.- The image registry for this cluster is decided. By default the cluster inherits the registry of the
globalcluster, which requires a validpublic-registry-credentialSecret when that registry is authenticated. To pull platform images from a dedicated registry instead, follow Choose the Image Registry for a Workload Cluster before you create the cluster; the binding cannot be changed afterwards. - The registry chosen above is reachable from workload nodes and contains the required Kube-OVN, CoreDNS, and vSphere CPI tags.
- The platform can process the cluster annotations required to install the network plugin.
- The vCenter account referenced by
VSphereCluster.spec.identityRefsatisfies the Custom Fields privilege requirement in VMware vSphere Provider Installation. - The
globalcluster is the active management cluster, not a disaster-recovery standby. See Standby Management Clusters.
Control Plane Endpoint Modes
Every cluster reaches its API server through a stable endpoint. Select the mode before you write 10-cluster.yaml, because it cannot be corrected afterwards. The choice is recorded in VSphereCluster.spec.controlPlaneLoadBalancer, and leaving the field out is itself a choice: an absent field means an external LoadBalancer.
The Self-built VIP exists from provider v1.0.17, requires ACP v4.4 or later, and is available only to clusters created with it. An existing cluster cannot adopt it, and neither can a cluster whose provider was upgraded to v1.0.17 from an earlier release: the VIP is injected while the first control-plane node bootstraps, and Alive then takes it over, so there is no in-place migration path. Those clusters keep their external LoadBalancer, and moving one onto a Self-built VIP means creating a new cluster. See Upgrading the Provider.
None of the three can be changed once the cluster exists. The field is write-once; see controlPlaneLoadBalancer fields.
Recommendation: for an external LoadBalancer, leave the field out rather than writing type: external. An absent field already means an external LoadBalancer, and writing it only to record that intent pins the endpoint to a frozen second copy of the same address.
For type: internal, the prerequisites, the field reference, the manifest block, and the verification commands are in Use a Self-built VIP.
Key Objects
The top-level Cluster references VSphereCluster and the control-plane or worker controllers. Those controllers reference immutable VSphereMachineTemplate objects. Each template references one VSphereMachineConfigPool, whose slots provide hostnames, static network configuration, and persistent disks to the runtime Machine, VSphereMachine, and VSphereVM objects.
ClusterResourceSet
ClusterResourceSet is a Cluster API resource in the global cluster. After the workload API server becomes reachable, it applies the referenced ConfigMap and Secret resources to the workload cluster.
In this workflow, ClusterResourceSet is used to deliver the vSphere CPI resources automatically.
vSphere CPI component
The vSphere CPI component is delivered to the workload cluster through ClusterResourceSet. It connects workload nodes to the vSphere infrastructure so the cluster can report infrastructure identities and complete cloud-provider initialization.
Provider-managed fields on your manifests
Some fields on the objects you apply are owned by the provider after creation. Knowing which ones prevents a repository fight during upgrades.
- The provider patches your
KubeadmControlPlaneafter you apply it. Expect the CoreDNS and kube-proxy skip annotations set to"true", andspec.kubeadmConfigSpec.clusterConfiguration.dns.imageRepositoryset by the controller. - Set
dns.imageTagyourself and leavedns.imageRepositoryunset. Provide the CoreDNS tag published for the target ACP release: at ACPv4.4and later, the tag staged underacp/coredns; belowv4.4, the one undertkestack/coredns. The provider reads the-v<acp>suffix in the tag to select that repository, so a tag without the suffix resolves totkestack/coredns— on ACPv4.4or later that repository does not carry the tag, and the CoreDNS pods stay inImagePullBackOff. - The kube-proxy repository is derived from the Kubernetes version:
acp/k8satv1.35and later,tkestackbelow it. The Kube-OVN chart isacp/chart-kube-ovnat chartv4.4and later,acp/chart-cpaas-kube-ovnbelow it. - The registry prefix for all three comes from the
Clusterannotationcpaas.io/registry-address. - The workload cluster's
kube-system/kube-proxyDaemonSet andkube-system/corednsDeployment image repositories are reconciled as well, but only after the control-plane rollout completes. A lag between theKubeadmControlPlanereaching the target version and those workloads changing repository is expected. - Image pull credentials for these managed components come from the workload cluster's
cpaas-system/sentryServiceAccount.
Do not remove the CoreDNS and kube-proxy skip annotations, and do not re-apply a stored KubeadmControlPlane manifest that omits them. Returning CoreDNS or kube-proxy to kubeadm's management while the provider also manages them produces a repository conflict that flips the images back and forth.
machine config pool
The machine config pool is the VSphereMachineConfigPool custom resource. In the baseline workflow:
- One machine config pool is used for control plane nodes.
- One machine config pool is used for worker nodes.
Each node slot includes the hostname, datacenter, static IP assignment, and optional data disk definitions.
For network configuration, distinguish the following fields:
networkNameis the vCenter network or port group name.deviceNameis the NIC name inside the guest operating system.
If deviceName is set, CAPV writes that value into the generated guest-network metadata. If it is omitted, the current implementation typically uses NIC names such as eth0, eth1, and eth2 by NIC order.
Also distinguish the following value formats:
- A node IP address is used together with a prefix length, for example
10.10.10.11/24. - The gateway field contains only the gateway IP address, for example
10.10.10.1.
spec.configs must declare at least one slot, and every slot must declare network with a non-empty network.primary.networkName.
Each slot can declare two disk lists. persistentDisks[] survive VM replacement and are reattached to the machine that replaces the previous one; ephemeralDisks[] are deleted with the VM and recreated empty. The standard set declares /var/cpaas as a persistent disk and /var/lib/kubelet, /var/lib/containerd, and /var/lib/etcd as ephemeral disks. Disk names and mount paths share one namespace across both lists within a slot. ephemeralDisks[] is new in provider v1.0.17, so this split is available only to a cluster created on v1.0.17 or later. See Choose persistent or ephemeral.
One pool serves exactly one controller. When a machine allocates a slot, the provider records the owning KubeadmControlPlane or MachineDeployment in status.consumerRef. A second controller whose template references the same pool is not accepted. Use one pool for the control plane and one pool per MachineDeployment.
VM template requirements
The VM template used by this workflow should meet the following minimum requirements:
- It uses the required operating system for the target platform environment.
- It includes
cloud-init. - It includes VMware Tools or
open-vm-tools. - It includes
containerd. - It includes the baseline components required by kubeadm bootstrap.
- It includes pre-exported container image tar files under
/root/images/. These files are imported into containerd bycapv-load-local-images.shbefore kubeadm runs, so that node bootstrap does not depend on pulling images from a remote registry. - The tar files include the kubeadm control-plane images supplied with the OS template, including etcd and kube-apiserver, under the exact image references generated from
clusterConfiguration.imageRepository, the Kubernetes version, and the component image tags. Although etcd and kube-apiserver run as static Pods, they still require container images. A missing or mismatched local reference causes the runtime to try the configured Registry instead. - The
/root/images/*.tarfiles must include the sandbox (pause) image whose reference exactly matches thesandbox_imagevalue (containerd v1) orsandboxvalue (containerd v2) configured in/etc/containerd/config.toml. For example, if containerd is configured withsandbox_image = "registry.example.com/tkestack/pause:3.10", one of the tar files must contain that exact image reference. A mismatch causes containerd to pull the sandbox image from the network, which defeats the purpose of local preloading and fails in air-gapped environments.
Static IP configuration, hostname injection, and other initialization settings depend on cloud-init. Node IP reporting depends on guest tools.
Local File Layout
The workload cluster_name must not be global. That name is reserved for the global cluster, and reusing it causes the workload cluster's resources to collide with global cluster resources in cpaas-system. The global- prefix is reserved for resources owned by the global cluster's DR workflow; see Common Prerequisites. Do not use global- for workload-cluster resources, because failover operations can select those resources as if they belonged to the global cluster.
As a convention, keep the CAPI Cluster and provider cluster resource (VSphereCluster) named exactly <cluster_name>, and prefix non-root CAPI and provider resources (KubeadmControlPlane, KubeadmConfigTemplate, MachineDeployment, VSphereMachineTemplate, VSphereMachineConfigPool, etc.) with <cluster_name>- — for example, the example manifests use <cluster_name>-kcp and <cluster_name>-md-0. This is a recommendation rather than a controller-enforced rule, but it prevents same-namespace collisions when multiple workload clusters live in cpaas-system and makes resource ownership obvious during operations.
Create a local working directory and store the manifests with the following layout:
Use the following commands to create the directory:
Steps
Validate the environment
Run the following commands from the global cluster to verify the minimum prerequisites:
Export the permanent platform Registry address returned by the Cluster annotation and the image Registry used by the CPI and kubeadm manifests. If both placeholders identify the same Registry, use the same value for both variables.
A NAME_UNKNOWN response means the repository name does not match the target version; correct KUBE_OVN_CHART_REPO or COREDNS_REPO from the mapping above and retry, rather than reading it as a missing tag.
The repository boundaries above are the same ones the provider applies at runtime; see Provider-managed fields on your manifests for the selection rules, and Upgrade Kube-OVN Before the Control Plane for the authoritative chart-name table.
The /v2/ segment is part of the Registry HTTP API URL only. In the workload Cluster manifest, set the Registry annotation to <registry_address> without a scheme or path.
Confirm the following results:
- The
globalcluster is reachable. - Alauda Container Platform Kubeadm Provider and Alauda Container Platform VMware vSphere Infrastructure Provider are running.
- The controller arguments include
ClusterResourceSet=true. - The
public-registry-credentialSecret exists; its contents are not printed by this procedure. - The
globalcluster is not a disaster-recovery standby. Thekube-public/etcd-syncConfigMap must not exist. See Standby Management Clusters. - The permanent platform Registry contains the required Kube-OVN, CoreDNS, and vSphere CPI tags.
- The VM template passed the executable local-image validation, including exact etcd, kube-apiserver, and sandbox image references. Their static-Pod deployment does not remove the image requirement.
Before you continue, also verify the following items:
- The vCenter server address is reachable.
- The vCenter username and password are valid.
- The thumbprint is correct.
- The template name is correct.
- The template is resolvable in the target datacenter.
- If the VM is cloned as a
fullClone, the template system disk is not larger than thediskGiBvalue used later in the manifests. If CAPV completes alinkedClone, the system disk size stays at the template's size anddiskGiBis ignored. - VMware Tools or
open-vm-toolsis installed in the template. - The external LoadBalancer follows the Layer 4 listener, backend, health-check, reachability, and ownership requirements in Plan the Control Plane Endpoint.
- If you selected
type: internal, the Self-built VIP prerequisites are satisfied instead: the VIP is unclaimed, VRRP and gratuitous ARP are not blocked by the dvPortGroup security policy or an NSX-T distributed firewall rule, the template can loadip_vs, and thealiveplugin is deployable on theglobalcluster.
Create the namespace and vCenter credential secret
Create the namespace that stores the workload cluster objects.
This workflow stores workload cluster objects in the cpaas-system namespace. In the manifests and commands below, replace every <namespace> placeholder with cpaas-system.
Apply the namespace:
Create the vCenter credential secret referenced by VSphereCluster.spec.identityRef.
Author the credential file with an editor rather than typing the values at a shell prompt. A shell records the whole command, password included, in its history; a file written by an editor leaves no such copy. Keep it outside capv-cluster/ so it is never mistaken for a manifest.
Each line is key=value. Do not quote the values — kubectl takes quote characters literally. A value may contain =; only the first = on a line separates the key from the value.
Restrict the file, create the Secret from it, then remove it:
Client-side apply stores the manifest it applied — including the vCenter password — in the kubectl.kubernetes.io/last-applied-configuration annotation on the Secret itself. kubectl create writes no such annotation. This applies whether the values were written as stringData or as base64 data; base64 is an encoding, not protection. The same applies to the CPI credential later on this page.
Create the Cluster and VSphereCluster objects
Create the base cluster manifest with the workload cluster network settings, the control plane endpoint, and the vCenter connection settings. Set cpaas.io/registry-address to the permanent platform Registry in the form <registry_address> (<host>:<port> only).
The manifest below uses an external LoadBalancer, which is the baseline on this page. For a Self-built VIP, add the spec.controlPlaneLoadBalancer block from Enable the Self-built VIP in 10-cluster.yaml to the VSphereCluster object.
Apply the manifest:
Create the vSphere CPI delivery resources
Create a ClusterResourceSet so the workload cluster receives the vSphere CPI configuration and manifests automatically after the workload API server becomes reachable.
In the baseline workflow VSphereCluster.spec.failureDomainSelector is intentionally not set and the CPI vsphere.conf does not include a [Labels] block. Both are required only after you enable failure domains; configure them together as described in Multiple datacenters and failure domains. Adding [Labels] to vsphere.conf without matching VSphereFailureDomain objects causes the CPI to look up zone and region tags that do not exist.
The vSphere CPI TLS bypass option is insecure-flag. Keep insecure-flag = "1" in the [Global] section of <cluster_name>-vsphere-cpi-config so the CPI can connect when the vCenter certificate is self-signed or not trusted by the workload cluster nodes. The CPI applies the global value to vCenter entries that do not set their own insecure-flag.
The CPI ConfigMap, Secret, and ClusterResourceSet resources must be created in the same namespace as the Cluster resource. In this guide that namespace is cpaas-system. A ClusterResourceSet can only match clusters within its own namespace; deploying it in a different namespace will silently prevent resource delivery.
The kube-ovn configuration in the Cluster annotations is consumed by platform controllers. This document does not install the network plugin directly.
This manifest is long and contains nested YAML inside data fields. Validate the manifest before applying: kubectl apply --dry-run=client -f 15-vsphere-cpi-clusterresourceset.yaml.
The manifest declares the two ConfigMaps and the ClusterResourceSet. It does not declare the CPI credential Secret that the ClusterResourceSet also references — that Secret carries the vCenter password and is created separately in the next step.
Create the CPI credential Secret. It wraps the vsphere-cloud-secret that the vSphere CPI reads on the workload cluster, so its payload carries the vCenter password. Author the payload with an editor, for the reason given earlier on this page, and keep it outside capv-cluster/ so it is not mistaken for a manifest:
Restrict the file, create the Secret from it, then remove it:
Use the same <cluster_name> as everywhere else on this page. VSPHERE_NAMESPACE was set when you created the vCenter credential; set it again if you are in a new shell. The Secret's only key must be data, which --from-file=data= produces.
Then apply the manifest:
Create the machine config pools
Create the control plane machine config pool.
Each node slot declares its NIC layout under network.primary (required) and network.additional (optional list). The primary NIC's networkName is required, and the provider derives the Kubernetes node name, the kubelet serving certificate DNS SAN, and the kubelet node-ip from hostname and the resolved primary NIC addresses. The hostname must be a valid DNS-1123 subdomain.
deviceName is optional. If you do not need to force the guest NIC name, remove the deviceName line from every node slot. The provider assigns NIC names such as eth0, eth1 by NIC order.
The dns entries in VSphereMachineConfigPool are kept in the static network configuration, but they might not update the guest operating system's /etc/resolv.conf reliably in affected VMware deployments. The control plane and worker bootstrap manifests below therefore also write /etc/resolv.conf explicitly through kubeadm files.
The provider validates the following:
spec.configsdeclares at least one slot, and every slot declaresnetworkwith a non-emptynetwork.primary.networkName.- Every disk declares a
sizeGiBof1or more. - A persistent disk's
unitNumber, when set, is between0and15and is never7, which is reserved for the SCSI controller. - Disk names and mount paths are unique across
persistentDisksandephemeralDiskswithin a slot. - Hostnames and primary IP addresses are unique within the pool and across every other pool bound to the same
Clusterin the namespace.
Once a slot's status.configStatuses[].state is InUse or Released, you cannot remove the slot, change its network.primary.ip or .ipv6, remove a disk from persistentDisks[] or ephemeralDisks[], or change a disk's sizeGiB, mountPath, or assigned unitNumber. Adding a disk to either list is accepted, but it takes effect only when the VM is next recreated.
Plan hostnames, addresses, and disk sizes before the first Machine allocates the slot. For scale-out and runtime changes, see Managing Nodes on VMware vSphere.
Create the worker machine config pool.
Apply both manifests:
Verify the pool binding and slot state before you create Machines:
The list output carries TOTAL, AVAILABLE, and ALLOCATED columns, taken from status.total, status.available, and status.allocated. Before you create Machines, expect ClusterRefReady=True, VCenterAvailable=True, MembersValid=True, MembersUnique=True, and Ready=True.
SlotAvailable is deliberately not part of the Ready summary. A pool whose slots are all allocated still reports Ready=True with SlotAvailable=False, and that combination is normal during a maxSurge: 0 rollout rather than a contradiction.
The pool is also the authority for persistent-disk replacement state. When a VM is replaced, its slot changes from InUse to Released; a replacement Machine that references the same existing pool can reuse that slot and its VMDKs immediately. releaseDelayHours controls reclaim when the slot remains unused; it is not a reuse delay.
Do not create a new pool name as a routine retry for Machine replacement. A different VSphereMachineConfigPool.metadata.name creates independent slots and VMDKs, so repeated teardown and redeployment can temporarily consume multiple complete disk sets. Before another deployment on a capacity-constrained datastore, wait for earlier pools and their VMDKs to finish provider-managed deletion. See Persistent disk lifecycle for the teardown checks. The live signal is status.persistentDiskStatuses[].phase, whose values are Creating, Attached, Available, Reclaiming, Reclaimed, and Error. The status.configStatuses[].reclaimStatus subtree and the spec.configs[].persistentDisks[].volumePath and .diskUUID fields are deprecated, frozen, and scheduled for removal.
Apply the optional failure-domain objects
Skip this step for the baseline single-datacenter topology.
If failure domains are enabled, complete Multiple datacenters and failure domains, then apply the failure-domain objects before creating the control plane:
Verify that every VSphereFailureDomain and VSphereDeploymentZone referenced by the cluster exists. Do not proceed until the deployment zones are available. Treat the following as one configuration set:
- The
VSphereFailureDomainandVSphereDeploymentZoneobjects in18-failure-domains.yaml VSphereCluster.spec.failureDomainSelectorin10-cluster.yaml- The CPI
[Labels]block in15-vsphere-cpi-clusterresourceset.yaml
Do not add failureDomainSelector or the CPI [Labels] block to the baseline manifests when failure domains are not enabled.
Create the control plane objects
Create the VSphereMachineTemplate and KubeadmControlPlane objects. Replace the placeholders in the following full template with the values collected in the checklist document.
The baseline control-plane and worker manifests omit imagePullCredentialsVerificationPolicy and are valid for Kubernetes 1.34 or earlier.
For Kubernetes 1.35 or later, add the following field to the KubeletConfiguration JSON in both 20-control-plane.yaml and 30-workers-md-0.yaml before applying either manifest:
cloneMode and diskGiB both remain present in the template because CAPV accepts both fields. In practice, diskGiB only affects the system disk when the actual clone operation is fullClone. If cloneMode is linkedClone and the template has a usable snapshot, CAPV completes a linked clone and the system disk size remains equal to the source template. If no usable snapshot exists, CAPV falls back to fullClone, and diskGiB applies again.
VSphereMachineTemplate.spec.template.spec.diskGiB sets only the VM system disk size. It is not the total capacity of all disks on the node.
Data disks are declared separately under VSphereMachineConfigPool.spec.configs[].persistentDisks[] and .ephemeralDisks[]. Do not add the data disk sizes to diskGiB; otherwise the VM can receive a larger system disk plus the separate data disks, which doubles the intended capacity.
For fullClone, diskGiB must be greater than or equal to the system disk size in the OS image template. For linkedClone, the system disk remains at the template size and diskGiB is ignored.
rolloutStrategy.rollingUpdate.maxSurge: 0 and a replicas value of 3 or more are not stylistic choices. A KubeadmControlPlane whose infrastructure template sets machineConfigPoolRef requires both. See Provider Requirements.
Apply the manifest:
Create the worker objects
Create the worker machine template, bootstrap template, and MachineDeployment.
The baseline worker kubelet patch omits the Kubernetes 1.35-only field. If the selected version is Kubernetes 1.35 or later, add imagePullCredentialsVerificationPolicy: NeverVerify here as well as in 20-control-plane.yaml, as described in the control-plane step.
strategy.rollingUpdate.maxSurge: 0 with maxUnavailable: 1 is likewise required for a MachineDeployment whose infrastructure template sets machineConfigPoolRef. The Cluster API default maxUnavailable is 0, which deadlocks against maxSurge: 0.
Apply the manifest:
In the baseline workflow, note the following worker-specific rules:
failureDomainis not set by default in the main worker manifest because the baseline workflow assumes a single datacenter. If you need a workerMachineDeploymentto land in a specificVSphereDeploymentZone, addfailureDomainas described in Multiple datacenters and failure domains.- Some environments add extra runtime-image replacement commands or service-restart commands to
KubeadmConfigTemplate. Those commands are intentionally not included in the baseline sample. Add them only when the platform requirements in your environment explicitly require them.
Wait for the cluster to become ready
After all manifests are applied, the cluster creation is asynchronous. Monitor the progress with:
Wait until KubeadmControlPlane reports the expected number of ready replicas and all Machine objects reach the Running phase before proceeding to verification.
Verification
Use the following commands to verify the cluster creation workflow.
- Check the CPI delivery resources in the
globalcluster: - Export the workload kubeconfig:
- Check whether the vSphere CPI daemonset is created in the workload cluster:
- Check the
globalcluster objects: - Check the workload nodes:
- Check the pool slot counters and the per-disk state of both lists:
Confirm the following results:
vsphere-cloud-controller-managerappears in the workload cluster.- Control plane and worker nodes are created.
- The nodes eventually become
Ready. - Each pool reports
Ready=True, and itsALLOCATEDcount equals the number of running Machines drawn from that pool. - Every persistent disk on an allocated slot reports
phase: Attached. - Every ephemeral disk on an allocated slot appears in
status.ephemeralDiskStatuses[]with an assigned unit number. Ephemeral disks carry no phase; they are created with the VM.
Troubleshooting
Use the following commands first when the workflow fails:
Prioritize the following checks:
- If the CPI resources are not delivered, verify
ClusterResourceSet=true,ClusterResourceSet, andClusterResourceSetBinding. - If
ClusterResourceSetexists but noClusterResourceSetBindingis created, check whether the controller has the required delete permission on the referencedConfigMapandSecretresources. - If the network plugin is not installed, verify that the required cluster annotations are present and that the platform controllers processed them.
- If the
cpaas.io/registry-addressannotation is missing or incorrect, verify that it is set to the permanent<registry_address>without a scheme or/v2/. Then verify the credential that backs it:public-registry-credentialwhen the cluster inherits the registry of theglobalcluster, or the Secret named bycpaas.io/registry-referencewhen the cluster uses a dedicated registry. - If a machine is stuck in
Provisioning, checkVSphereMachineconditions forMachineConfigPoolReady— it shows whether slot allocation failed due to pool binding or datacenter mismatch. Then read the pool's own conditions:MembersValid,MembersUnique,PersistentDisksReady,ClusterRefReady, andVCenterAvailable. - If a machine never bootstraps, check the
BootstrapReadycondition onVSphereVMandVSphereMachine. The reasonBootstrapSecretGetFailedmeans the bootstrap Secret is missing or unreadable;BootstrapSecretContentInvalidmeans it exists but cannot be used. Inspect theKubeadmConfigfor that Machine and its bootstrap data Secret before looking at vCenter. - If a node was powered off in vCenter and stays off, that is expected behavior.
VSphereVMlatchesInitialPowerOnCompleted=Trueafter its first successful power-on and never clears it; from then on a powered-off VM is treated as a deliberate out-of-band action and is not powered back on.PoweredOn=Falsepropagates intoVSphereVM.Readyand then intoVSphereMachine.Ready, so reporting not-ready is correct. Power the VM on in vCenter to recover; a rollout is not required. A replacement VM created by a rollout has an unset latch, so its first boot is unaffected. - If
VSphereMachineclearsReadywithout an obvious cause, note thatReadyis cleared while the backingVSphereVM, itsproviderID, or its network information is unavailable, and that out-of-band power changes are now observed. Read theVSphereVMconditions before treating it as a controller fault. - If nothing reconciles at all and no condition changes, check whether the management cluster is a disaster-recovery standby. See Standby Management Clusters.
- If a VM is waiting for IP allocation, verify VMware Tools, the static IP settings, and
VSphereVM.status.addresses. - If workload
Nodeobjects remain withoutspec.providerID, first verify the CPI delivery resources and then check for duplicate vCenter guest hostnames. When an old VM in the same datacenter still reports the same guest hostname as a new node,cloud-provider-vspherecan fall back to node-name lookup, cache the old VM, and reject the new node because the VM IP does not match the kubelet node IP. Check the leadervsphere-cloud-controller-managerlogs, the nodeSystemUUID, the real VM UUID, and vCenter guest hostname/IP values. After you fix or remove the duplicate hostname or old VM conflict, restart the workload cluster'svsphere-cloud-controller-managerPods to clear the bad in-memory cache: - If datastore space is exhausted, first list all current and terminating
VSphereMachineConfigPoolobjects and compare their names with everyVSphereMachineTemplate.spec.template.spec.machineConfigPoolRef.name. Different pool names own independent slots and VMDKs; repeated deployments can therefore accumulate multiple disk sets. Then inspectstatus.configStatuses[]forstateandlastReleasedTime, andstatus.persistentDiskStatuses[]forphase,lastError,retryAfter, andtaskRef, together with related Events and the vCenter attachment state.insufficient disk availableconfirms a datastore capacity failure but does not by itself prove that reclaim failed. Do not manually delete a VMDK or remove a pool finalizer while reuse or provider-managed reclaim is pending. - Do not delete a
phase: Reclaimedentry fromstatus.persistentDiskStatuses[]. It is a deliberate tombstone: the backing VMDK is already gone, and the record is what stops the controller from re-seeding the disk from the frozenspecvolumePath. Deleting it produces a seed, delete, and re-seed loop that prevents the pool finalizer from ever clearing. - If the template system disk size does not match the manifest values, first check the actual clone mode. When the VM was created as
linkedClone, the system disk stays at the template's size anddiskGiBis ignored. OnlyfullCloneusesdiskGiB, and in that casediskGiBmust not be smaller than the template disk size. - If the control plane endpoint does not come up, verify TCP
6443passthrough, all control-plane backends, HTTPS/healthzresults, DNS and certificate SANs, and reachability from theglobalcluster and control-plane nodes. - If the TLS connection to vCenter fails, verify the thumbprint, the vCenter address, and whether proxy settings interfere with the connection.
When you review controller logs, use the following rules:
deploy/capi-controller-managerruns in thecpaas-systemnamespace of theglobalcluster.- Do not use the workload-cluster kubeconfig to inspect
capi-controller-managerlogs. - If platform controllers process the cluster network annotations, also inspect the platform network-controller logs and the platform cluster-lifecycle-controller logs.
Admission rejections
An apply or edit can be rejected before anything is created. Match a fragment of the rejection message against the following table.
Creation-Time Topology Variants
The baseline manifest intentionally starts with one datacenter and one NIC. Before you create the cluster, use the following variants when the initial topology requires a Self-built control plane VIP, additional NICs, multiple datacenters, failure domains, or extra data disks. Apply one variant at a time and validate the complete manifest set before combining them.
Use a Self-built VIP
Use this variant when the cluster is created with VSphereCluster.spec.controlPlaneLoadBalancer.type: internal instead of an external LoadBalancer. Decide the mode first in Control Plane Endpoint Modes; it cannot be changed after the cluster exists.
Self-built VIP prerequisites
Complete all of the following before you select type: internal.
- The platform is ACP
v4.4or later. An earlier platform release does not support the Self-built VIP, whatever the provider version. - The VIP is IPv4, is currently unclaimed, and lives in the same Layer-2 broadcast domain as the control-plane node IPs. IPv6 and dual-stack endpoints are not accepted.
- The VIP is not one of the slot addresses (
network.primary.ipornetwork.additional[].ip) declared in this cluster'sVSphereMachineConfigPoolobjects, and it falls outside every external IPAM pool range. The provider checks the slot addresses; it cannot read external IPAM pools and will not detect that conflict. - The
vridis unique in that Layer-2 domain. - The network path allows VRRP (IP protocol
112, multicast224.0.0.18) and gratuitous ARP between the control-plane nodes. Confirm that the dvPortGroup security policy and any NSX-T distributed firewall rule do not block them. - The VM template exposes the Linux IPVS subsystem (the
ip_vsmodule family is loadable) and allows Alive to setnet.ipv4.conf.all.arp_accept=1andnet.ipv4.vs.conntrack=1. - The
alivecluster plugin is present and deployable on theglobalcluster.
The provider does not carry Alive itself. It creates a ModuleInfo on the global cluster, and the platform renders the workload cpaas-system/alive AppRelease from it. Confirm the plugin on the global cluster:
The provider resolves <alive_version> in this order: the --plugin-alive-version override on the provider deployment when it is set, then the ModulePlugin/alive entry status.targetClusterVersions[<ClusterModule spec.version>].version, and only then status.latestVersion. Read the targetClusterVersions entry that matches the version printed by the first command first; latestVersion applies only when the map has no entry for it.
The workload cluster's own ClusterModule does not exist until the cluster is created, so at planning time read the global cluster's version as the planning value. It is the closest available proxy, not a guarantee: once the cluster exists, read its own version with kubectl get clustermodule <cluster_name> and repeat the readyForDeploy check against the alive version that this version resolves to. Do that before the control plane finishes bootstrapping, because type: internal cannot be changed afterwards. When you are creating the global cluster itself, confirm the component version and the presence of the alive plugin from the installation package instead.
The last command must print true for the version that resolution selects. If ModulePlugin/alive is absent, or the matching ModuleConfig is missing or reports false, the Self-built VIP cannot be installed in this environment. Create the cluster with an external LoadBalancer instead, and leave spec.controlPlaneLoadBalancer absent. Contact Alauda technical support for the plugin package that matches your platform release.
controlPlaneLoadBalancer fields
The provider accepts spec.controlPlaneLoadBalancer only when the cluster is created. After that, type, host, port, vrid, and interface are all frozen, the field cannot be cleared, and a cluster created without it cannot have it added. Correcting a mistake requires rebuilding the cluster.
Two consequences follow. On multi-NIC nodes, set interface explicitly at creation — a wrong auto-detected interface cannot be patched afterwards. And host and port must describe the same address as spec.controlPlaneEndpoint; a mismatch is not accepted.
The vrid range is enforced by the provider's admission webhook rather than by the CRD schema. A client-side dry run contacts no server, so it catches neither this nor the schema rules; use kubectl apply --dry-run=server to see the rejection without creating the object.
Enable the Self-built VIP in 10-cluster.yaml
For type: internal, add the following block to the VSphereCluster object in Create the Cluster and VSphereCluster objects. Everything else in the baseline manifest is unchanged.
The provider also reconciles the workload cluster's kube-system/kube-proxy configuration for you, setting ipvs.strictARP: true and adding <vip>/32 to ipvs.excludeCIDRs, then rolling the kube-proxy DaemonSet. Do not revert those values by hand; the provider reapplies them and rolls the DaemonSet again.
Verify the Self-built VIP
Expect SelfBuiltLoadBalancerReady=True, one Alive pod per control-plane node, and a successful API response through the VIP without disabling TLS verification.
The Alive pods run in kube-system, not in cpaas-system; cpaas-system holds the alive AppRelease the platform renders from the provider's ModuleInfo. SelfBuiltLoadBalancerReady covers the whole chain, the AppRelease and the pods, so check both namespaces when it is False.
SelfBuiltLoadBalancerReady reports the VIP chain only. It does not gate VSphereCluster.status.ready, so the infrastructure cluster can report ready while the VIP is still converging. The condition reasons are SelfBuiltLoadBalancerReady, SelfBuiltLoadBalancerReconciling, SelfBuiltLoadBalancerNotReady, and InvalidSelfBuiltLoadBalancerConfiguration.
While the VIP is not ready, the provider also defers the workload-cluster CoreDNS and kube-proxy repository reconciliation described in Provider-managed fields on your manifests.
Add a second NIC
When nodes require an additional management, storage, or service network, extend the manifests in the following resources:
16-vspheremachineconfigpool-control-plane.yaml17-vspheremachineconfigpool-worker.yaml20-control-plane.yaml30-workers-md-0.yaml18-failure-domains.yamlif failure domains are enabled
Each node slot declares its NIC layout under network.primary and network.additional. The primary NIC is used to derive the kubelet node-ip and remains the node's primary identity; additional NICs are merged after it in the order listed.
Add the second NIC to each control plane node slot in the machine config pools:
Apply the same pattern to the worker node slots:
Add the second NIC to the machine templates:
If the DNS server used by the node changes when you add the second NIC, update the /etc/resolv.conf file entries in both 20-control-plane.yaml and 30-workers-md-0.yaml. The dns values in the machine config pool network blocks do not replace the explicit /etc/resolv.conf bootstrap file entries.
If failure domains are enabled, update the network list in VSphereFailureDomain.spec.topology.networks:
When you define the second NIC values, prepare the following placeholders in the infrastructure checklist and manifests:
<master_01_nic2_ip><master_02_nic2_ip><master_03_nic2_ip><worker_01_nic2_ip><worker_02_nic2_ip>when you also expand the worker pool
For a running cluster, change all three network definitions together during immutable replacement. See Runtime Topology Changes.
Multiple datacenters and failure domains
Use multiple datacenters and failure domains when you need node placement across different vCenter datacenters or compute clusters.
The following principles apply:
- One cluster can define multiple
VSphereFailureDomainobjects. - Each
VSphereDeploymentZonereferences oneVSphereFailureDomain. - The control plane uses
VSphereCluster.spec.failureDomainSelector. - A worker
MachineDeploymentusesspec.template.spec.failureDomainwhen it must target a specific deployment zone.
Prepare the following placeholders for the first datacenter:
<compute_cluster_1><default_datastore_1><resource_pool_path_1><fd_name_1><dz_name_1>
Prepare the following placeholders for the second datacenter:
<dc_name_2><fd_name_2><dz_name_2><compute_cluster_2><default_datastore_2><resource_pool_path_2>
If you add a third datacenter, continue with the same placeholder pattern:
<dc_name_3><fd_name_3><dz_name_3><compute_cluster_3><default_datastore_3><resource_pool_path_3>
Create the failure-domain objects in 18-failure-domains.yaml. The first datacenter also needs a VSphereFailureDomain and VSphereDeploymentZone when failure domains are enabled:
Enable control plane selection across the available failure domains by adding failureDomainSelector to the VSphereCluster spec in 10-cluster.yaml:
An empty selector {} matches every VSphereDeploymentZone that has controlPlane: true. Use match labels to restrict the control plane to a subset of zones.
Also add the [Labels] block to the CPI ConfigMap so the vSphere CPI publishes the matching zone and region labels on workload nodes. The keys must match the tagCategory values used in VSphereFailureDomain.spec.zone.tagCategory and VSphereFailureDomain.spec.region.tagCategory. Update the vsphere.conf data in 15-vsphere-cpi-clusterresourceset.yaml:
failureDomainSelector and the CPI [Labels] block must be enabled together. Adding either one alone leaves the cluster in an inconsistent state: nodes get unresolved zone or region labels, or the control plane cannot select a deployment target.
Set a worker deployment zone when a worker MachineDeployment must be pinned to one deployment target. Add failureDomain to spec.template.spec in 30-workers-md-0.yaml:
Use a VSphereDeploymentZone name for <worker_failure_domain>, not a VSphereFailureDomain name.
Before you enable multiple datacenters, confirm all of the following prerequisites:
- The template is already synchronized to every target datacenter.
- The network names are resolvable in every target datacenter.
- The datastore names are resolvable in every target datacenter.
- The vSphere CPI datacenter list covers every target datacenter.
Add data disks
The baseline deployment includes the following required data disks:
- Control plane nodes (4 disks per node):
var-cpaasinpersistentDisks[], plusvar-lib-kubelet,var-lib-containerd, andvar-lib-etcdinephemeralDisks[]. Do not remove any of these disks. - Worker nodes (3 disks per node):
var-cpaasinpersistentDisks[], plusvar-lib-kubeletandvar-lib-containerdinephemeralDisks[]. Do not remove any of these disks.
VSphereMachineTemplate.spec.template.spec.diskGiB is the system disk size, not the total disk capacity of the VM. Keep the data disks under VSphereMachineConfigPool.spec.configs[].persistentDisks[] and .ephemeralDisks[]. Do not add the data disk sizes to diskGiB unless you intentionally want a larger system disk.
If a node needs additional data disks beyond the required set, append more entries to the matching list in the corresponding VSphereMachineConfigPool node slot: persistentDisks when the data must survive VM replacement, ephemeralDisks when it is rebuildable. The following optional fields are especially relevant here:
mountPath: If set, the disk is formatted and mounted at the specified path. If omitted, the disk is attached as a raw device with a symlink at/dev/disk/by-capv/<name>, allowing an external process to manage it at runtime.wipeFilesystem: Persistent disks only. Whentrue, disk content is wiped on the first boot of a new VM. Normal reboots and manual service restarts are not affected. Defaults tofalse.
To attach a raw disk without formatting or mounting, omit mountPath and fsFormat:
The disk is accessible inside the guest OS at /dev/disk/by-capv/app-data. On rolling updates, the same VMDK is re-attached to the new VM and the symlink is recreated. The disk is never formatted or mounted automatically; the application is responsible for managing it at runtime.
Choose persistent or ephemeral
A slot declares two disk lists: spec.configs[].persistentDisks[] and spec.configs[].ephemeralDisks[].
Use ephemeralDisks for rebuildable node-local data that should not sit on the system disk — the standard /var/lib/kubelet, /var/lib/containerd, and /var/lib/etcd disks, plus any scratch space. Use persistentDisks for anything whose loss is a data loss.
/var/cpaas must stay in persistentDisks[]: it holds platform state that has to survive VM replacement. /var/lib/kubelet, /var/lib/containerd, and /var/lib/etcd belong in ephemeralDisks[]; they are rebuilt on every new VM, and declaring them as persistent disks carries stale kubelet, runtime, and etcd member data onto the replacement node.
This split is a creation-time choice. ephemeralDisks[] is new in provider v1.0.17, so a cluster created on an earlier provider cannot use the standard ephemeral set at all: it declares /var/cpaas, /var/lib/containerd, and /var/lib/etcd as persistent disks, with wipeFilesystem: true on the etcd disk. That cluster keeps its layout: a provider upgrade migrates no disk, and an allocated slot rejects the removal or the reshaping of a disk in either list. See Upgrading the Provider.
The following example adds one ephemeral scratch disk alongside the standard worker set:
Disk names and mount paths must be unique across both lists within one slot.
Pin a SCSI unit number
unitNumber on a persistent disk keeps guest device ordering stable across VM recreations. Valid values are 0 through 15, excluding 7, which is reserved for the SCSI controller, and each value must be unique within the slot. Once the slot is allocated, the assigned value is immutable.
If you omit unitNumber, the controller assigns one at clone time, records it in status.persistentDiskStatuses[].unitNumber, and reuses that value when the VM is recreated.
Ephemeral disks have no unitNumber field at all. The controller assigns their unit at every clone and reports it in status.ephemeralDiskStatuses[].unitNumber, so the standard /var/lib/kubelet, /var/lib/containerd, and /var/lib/etcd disks cannot be pinned.
Relocating the pod log directory
When any disk in a slot mounts at /var/lib/containerd, the guest disk-reconcile script points /var/log/pods at a directory on that disk, so pod logs land on the data disk instead of the root filesystem. In the standard layout that disk is ephemeral, so relocated pod logs are destroyed with the VM they belong to; ship anything you need to keep to the platform log store.
This relocation is written into the guest through guestinfo user data before first boot only. Upgrading the provider does not retrofit it onto existing machines; the change is picked up when nodes are rolled and replaced. See OS Image Update Without a Kubernetes Version Change. Remediating a running node in place requires an out-of-band script and is not part of this workflow.
Verify creation-time variants
After you apply a topology variant, validate the cluster state with the following commands:
Confirm the following results:
- The placement, NIC, or disk definitions are reflected in the target resources.
- New nodes reach the
Readystate. - Existing nodes remain healthy after the change.
- Every declared ephemeral disk has an assigned unit number and is addressable in the guest at
/dev/disk/by-capv/<name>.
Next Steps
For worker scale-out and later template or runtime-topology changes, see Managing Nodes on VMware vSphere. Apply one change at a time and validate the result before you combine multiple changes in the same cluster.