Creating Clusters on Bare Metal
This document explains how to create Kubernetes clusters on physical servers using the bare-metal provider. The workflow is YAML-only — there is no Fleet Essentials UI for bare-metal clusters at this time.
TOC
Prerequisites1. Required Plugin Installation2. OS Images Imported and Image Catalog Confirmed3. Network Connectivity4. TPM Decision5. Public Registry Credential (Only When Installing Platform Components Later)6. Host Disk and Boot PreparationCluster Creation WorkflowResolving Placeholder ValuesStep 1: Build the SeedImage and Register HostsOptional: Prepare Managed Data DisksStep 2: CreateMachineInventoryPool ResourcesStep 3: Create the Control-Plane Cluster ResourcesStep 4: Deploy Worker NodesCluster VerificationUsing kubectlVerify the Control Plane EndpointExpected ResultsCommon Failure ModesNext StepsAppendixComplete KubeadmControlPlane ConfigurationPrerequisites
Before creating clusters, ensure all of the following prerequisites are met.
1. Required Plugin Installation
Install the following plugins on the global cluster:
- Alauda Container Platform Kubeadm Provider
- Alauda Container Platform Bare Metal Infrastructure Provider (umbrella chart that installs both the bare-metal manager and
elemental-operator)
See the Installation Guide for details.
2. OS Images Imported and Image Catalog Confirmed
Before creating any cluster resources, obtain the matching OS image pair from Alauda technical support and import both images into the target platform registry. Importing only one image is not sufficient:
Use the image references in the target platform registry after the import completes. Do not point cluster resources at a build registry, derive one image reference from the other, or substitute an independently built image. Verify in the registry or from the image-import result that both final references exist before continuing. Adding an image-catalog entry or a SeedImage reference does not import the corresponding image.
The bare-metal provider chart ships an elemental-image-catalog ConfigMap that maps Machine.spec.version to the elemental upgrade image used to (re)provision a node. You do not need to create this ConfigMap separately — confirm that the target Kubernetes version is present:
Every value used as Machine.spec.version (for both the control plane and worker MachineDeployment resources) must appear as a key in this ConfigMap, with the leading v preserved. Confirm that the key resolves to the imported base-image in the target platform registry. The provider resolves the image at reprovision time by substituting the platform registry address for the registry portion of the entry. If a target version is missing, no reprovision plan is written and BaremetalMachine ends up in Failed / Reason=ImageCatalogMiss until the entry is added.
3. Network Connectivity
- Every physical host must be able to reach
global.platformUrl(elemental-system-agentregistration, plan secret polling). - Every physical host must be able to pull from the platform registry (
global.registry.address) for bothelemental install(during the first boot) andelemental upgrade(during every reprovision). - For
InternalSelf-built VIP, the VIP must live in the same Layer-2 broadcast domain as the control-plane node IPs. Thevridmust be unique in that domain, the network must allow VRRP and gratuitous ARP updates, and the node image must expose IPVS and allow Alive to setnet.ipv4.conf.all.arp_accept=1andnet.ipv4.vs.conntrack=1. - For
ExternalLoadBalancer, provision the listener and control-plane backends before cluster creation. Follow Plan the Control Plane Endpoint.
If the target VM or physical host does not receive an address from DHCP while booted into the live ISO, configure the network manually from the host console before waiting for registration. Check the NetworkManager connection name first, then apply the site-specific address, gateway, and DNS values:
4. TPM Decision
Set MachineRegistration.spec.config.elemental.registration.emulate-tpm from whether the host exposes a real hardware TPM (/dev/tpm0), not from whether it is physical or virtual:
emulate-tpm: false(or omit the field) — only when the host has a working hardware TPM, soelemental-registercan use it forauth: tpm.emulate-tpm: truewithemulated-tpm-seed: -1— for any host without a hardware TPM. This includes both virtual machines and physical servers that ship without a TPM module (for example a Dell R620). On such a host,auth: tpmcombined withemulate-tpm: falsemakeselemental-registerfail TPM attestation and never send the registration request, soelemental-operatorrecords zero registration POSTs.
5. Public Registry Credential (Only When Installing Platform Components Later)
public-registry-credential is not required to create a bare-metal cluster. It only becomes necessary when later platform components on the new workload cluster need to pull from a credentialed public registry. If your test scope ends at cluster + node Ready, you can ignore this prerequisite.
6. Host Disk and Boot Preparation
Classify every disk and virtual disk (VD) on each host before you boot the SeedImage ISO. Record which device is the OS install target and which devices, if any, must retain application data:
- Wipe the selected OS install disk and any other obsolete boot disks so that no bootable previous operating system remains. The host must boot the ISO, not an old on-disk OS. Set
MachineRegistration.spec.config.elemental.install.deviceto the exact OS disk. - Do not wipe a data disk that you intend to manage with the
Adoptpolicy. Back it up independently, record its stable ID and filesystem UUID, and make sure it cannot be selected as the install device. - A disk intended for
InitializeIfBlankmust be disposable and objectively blank. Formatting still requires a separate initialization approval after the host registers; the registration manifest does not authorize it. - Remove residual Elemental partition labels —
COS_STATE,COS_PERSISTENT,COS_OEM,COS_RECOVERY— from all disks, not only the intended install disk. Elemental resolves partitions by label (blkid -L COS_STATE); a stale label left on a second disk (for example from a previous install or a leftover multipath member) makes it resolve the wrong device and the reprovision snapshotter fails. - On hosts where the same disk can appear through more than one path (multipath), make sure none of those paths exposes a disk that still carries a
COS_*label; a residual label on any path can be resolved ahead of the intended install disk. Clean the disk rather than disabling multipath — the image keeps it enabled for network-attached storage boot. - On a host with multiple disks, do not leave
install.deviceempty and do not use/dev/sdaas a persistent identity. Follow Select a Fixed System Disk on Multi-Disk Bare-Metal Hosts to reuse one ISO while selecting each host's system disk by WWN. - Set the boot order so the host boots the virtual CD / ISO first.
Use only stable storage identities reported by the registered inventory. Linux paths such as /dev/sdX, /dev/vdX, /dev/nvmeXnY, and /dev/mapper/mpathX are runtime paths and must not be placed in MachineInventory.spec.storage.
Cluster Creation Workflow
When using YAML, import the paired OS images first, then proceed through five required resource steps, with an optional storage-preparation step before pool membership. Every Kubernetes resource must be applied in the cpaas-system namespace.
Important Namespace Requirement
All bare-metal resources must be applied in the cpaas-system namespace. The provider and elemental-operator only reconcile objects in that namespace.
Workload Cluster Naming
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. As a convention, keep the CAPI Cluster and BaremetalCluster named exactly <cluster-name>, and prefix dependent resources (KubeadmControlPlane, KubeadmConfigTemplate, MachineDeployment, machine templates, pools, registrations) with <cluster-name>-.
Resolving Placeholder Values
The example manifests below use <placeholder> syntax for environment-specific values:
Step 1: Build the SeedImage and Register Hosts
Create a MachineRegistration that describes the registration URL and first-install cloud-config, and a SeedImage that points elemental-operator at the matching ISO base image.
Set SeedImage.spec.baseImage to the final target-platform registry reference of the imported base-image-iso. It must be paired with the imported base-image used by the image catalog for later reprovisioning; do not construct the ISO reference by changing the repository name or tag yourself.
Do not add /etc/resolv.conf to SeedImage.spec.cloud-config. Keep the ISO generic, and put site-specific resolver configuration in MachineRegistration.spec.config.cloud-config only when the first-boot registration path needs it. During the tested Global deployment flow, the ISO did not carry resolver files; node DNS was configured later by the kubeadm bootstrap data.
install.device, install.eject-cd, and install.reboot are intentional. If the target disk is omitted, elemental install can select an unintended device. On multi-disk physical hosts, use the fixed system disk workflow instead of a changing /dev/sdX name. If eject-cd or reboot is false, a host can remain in the live environment after the first install and never become usable inventory for Cluster API.
Apply the manifest and wait for the SeedImage build to finish:
When status.state reaches Completed, fetch the download URL and ISO checksum:
Boot every target host from this ISO. elemental-register runs first (creates the MachineInventory and uploads observedNetwork), then elemental install writes the on-disk OS. After install completes, the host stays available for plan execution. If the live ISO environment has no DHCP address, configure NetworkManager manually on the host console as described in Network Connectivity before waiting for the MachineInventory.
Confirm registration:
Every inventory you intend to use must:
- Show
Ready=True. - Have a non-empty
status.plan.secretRef.name. - Have a
spec.observedNetworkthat matches the host's expected NIC (only required when you want the install-time IP to survive across reprovisions). - When managed data disks are required, run an observer-capable OS image, report a fresh
status.observedStorage, and reachstatus.storage.phase=Preparedbefore pool allocation.
Record the exact MachineInventory names — they are referenced by name in the next step.
Optional: Prepare Managed Data Disks
Managed storage belongs to the long-lived MachineInventory, not to a CAPI Machine or BaremetalMachineTemplate. Configure it while the inventory is unallocated and before adding that inventory to a production pool.
Follow Manage Data Disks on Bare-Metal Hosts to:
- Verify
elemental-storage-observer.serviceand inspectstatus.observedStorage. - Select each device by a stable ID and choose
AdoptorInitializeIfBlank. - Use the matching
storagectlbinary to validate and atomically patch the declaration and, when required, its initialization approval. - Wait for
StoragePrepared=True/AllRequiredVolumesPreparedandstatus.storage.phase=Prepared.
If no disks should be managed, leave spec.storage absent or set volumes: []. The storage controller converges that inventory to Unmanaged with StoragePrepared=True/NoManagedVolumes and performs no disk operation.
Step 2: Create MachineInventoryPool Resources
Create one pool per role. The pool reconciler validates that every member exists, computes capacity counters, and writes the baremetal.alauda.io/pool=<pool-name> annotation onto the inventory.
Key parameters:
Apply and verify:
A healthy pool reports Ready=True, total = len(spec.machineInventories), and available = total - allocated - preparing - reprovisioning - unavailable. Inventories listed in spec.machineInventories that fail validation (missing, plan secret missing, Ready=False, or storage not prepared) raise the pool's unavailable counter and surface in the MembersValid condition. A non-empty storage declaration is an allocation gate: the inventory is not Available to CAPI until Prepare succeeds against a fresh observation.
Size the control-plane pool to at least KubeadmControlPlane.spec.replicas. Size the worker pool to at least MachineDeployment.spec.replicas. For rolling upgrades the pool must hold the entire replica count — the provider uses delete-then-add semantics from the same pool, never both at once.
Step 3: Create the Control-Plane Cluster Resources
Create the BaremetalCluster (declares the selected control-plane endpoint mode), the control-plane BaremetalMachineTemplate (points at the control-plane pool), the KubeadmControlPlane (replicas + kubeadm config), and the CAPI Cluster.
The pre-GA Bare Metal API supports both endpoint modes in the same workflow:
Internal: the provider deploys Alive and reconciles the VIP and control-plane backend membership.External: the provider skips Alive; the load balancer owner maintains the listener, health check, and backend membership.
There is no legacy provider-version tab because Bare Metal has not had an earlier GA release. Select the mode with <control-plane-load-balancer-type> in the manifest below.
Use the Kubernetes images built into the OS image
The supported bare-metal OS image preloads the kubeadm control-plane, CoreDNS, and etcd images under cloud.alauda.io/alauda. Keep that repository in the KCP configuration and use the component tags from the OS Support Matrix. Do not replace it with <registry-address>/tkestack: that produces image references that do not match the images preloaded in the OS. The OS configures its built-in pause image through containerd; do not add pod-infra-container-image to the KCP kubelet arguments.
Full Configuration Reference
The example below uses a minimal KubeadmControlPlane. For the full hardening profile recommended in production — admission, audit, kubelet patches, encryption provider — see Complete KubeadmControlPlane Configuration in the Appendix.
Cluster annotations. The bare-metal provider relies on a small set of Cluster annotations during reconcile. Authoritative ones the operator must set:
BaremetalCluster parameters:
Apply and watch:
Each new control-plane BaremetalMachine advances Pending → Allocated → Reprovisioning → Running. Watch:
BaremetalMachine.status.machineInventoryRef.name— which inventory was picked.BaremetalMachine.status.planSecretRef.name— plan secret being driven. The secret carriesbaremetal.alauda.io/plan.type=reprovision.MachineInventory.status.plan.state—Appliedonce the host completescloud-init clean,elemental upgrade, reboot, andkubeadm init/join.BaremetalCluster.status.conditions[EndpointReady]— true once the configured control-plane endpoint is reachable.
The bare-metal provider does not support single-node control planes. Provision at least three control-plane replicas (KubeadmControlPlane.spec.replicas: 3) so that etcd retains quorum. In Internal mode, the same nodes also participate in Alive VIP arbitration.
Step 4: Deploy Worker Nodes
After the control plane is Ready, create the worker BaremetalMachineTemplate, the worker KubeadmConfigTemplate, and the MachineDeployment. The full worker YAML and parameter table are in Managing Nodes on Bare Metal → Worker Node Deployment.
Cluster Verification
Using kubectl
Verify the Control Plane Endpoint
Confirm the configured mode and endpoint:
For Internal, verify Alive and the node prerequisites:
Run the following command on each control-plane node, not on the management cluster:
For External, verify the load balancer frontend and each backend according to the External LoadBalancer contract. Confirm that the load balancer owner has registered every current control-plane node and that HTTPS /healthz returns HTTP 200 for each backend.
Expected Results
A successfully created cluster shows:
Cluster.status.conditions[Ready]=True.KubeadmControlPlanereplicas allReady.- Every
BaremetalMachine.status.phase=Runningandstatus.ready=true. - Every used
MachineInventory.status.plan.state=Appliedwithbaremetal.alauda.io/plan.type=reprovisionon its plan secret. - Every used inventory with non-empty
spec.storage.volumes[]reportsstatus.storage.phase=Active,StoragePrepared=True, andStorageActive=True; the correspondingBaremetalMachinereportsStorageReady=True/AllVolumesReady. MachineInventoryPool.statussatisfiesavailable + allocated + preparing + reprovisioning + unavailable = total.- Kubernetes Nodes Ready.
- For
Internal, Alive Pods are Ready and the API is reachable through the Self-built VIP. - For
External, the load balancer frontend is reachable and its backend list matches the current control-plane nodes.
Common Failure Modes
For the full operator-side state machine reference (every condition reason and recovery action), see Provider Overview → clean / reprovision plans.
Next Steps
After creating a cluster:
Appendix
Complete KubeadmControlPlane Configuration
The hardened configuration recommended for production bare-metal clusters — admission control, audit policy, kubelet patches, encryption provider, and IPv6 bind addresses. Substitute the placeholders from the table in Resolving Placeholder Values.
imagePullCredentialsVerificationPolicy: NeverVerify is required only starting with Kubernetes 1.35. Omit this parameter when creating a cluster with Kubernetes 1.34 or earlier.
Worker bootstrap is symmetric — see Managing Nodes on Bare Metal → Bootstrap Template for the worker KubeadmConfigTemplate.