Architecture
Big picture
The baremetal-operator is an Ironic orchestrator. It publishes the BareMetalHost CRD as a Kubernetes API, then runs a controller that reconciles each host through an explicit finite state machine. BMO itself only calls the Ironic API. The real work, power control over IPMI or Redfish, PXE or virtual media boot, and disk writes, is done by Ironic.
Components
CRD types (apis/metal3.io/v1alpha1/)
The BareMetalHost type and its companions live in their own Go module (apis/metal3.io/). BareMetalHost is defined at apis/metal3.io/v1alpha1/baremetalhost_types.go:865, carrying a Spec and a Status. Keeping the types in a separate module lets external controllers such as cluster-api-provider-metal3 depend on the API without importing the operator itself.
Controller and state machine (internal/controller/metal3.io/)
This package holds the reconcile logic and the finite state machine. The controller entry point is Reconcile at internal/controller/metal3.io/baremetalhost_controller.go:119, and the state machine is host_state_machine.go, whose state-to-handler map is built at host_state_machine.go:44.
Webhooks (internal/webhooks/metal3.io/)
Validating and defaulting admission webhooks for the CRD.
Provisioner (pkg/provisioner/)
The Provisioner interface (pkg/provisioner/provisioner.go:143) and its implementations: ironic for production, fixture for compile-time bypass in tests, and demo. It also contains the plugin loader (pkg/provisioner/plugin.go:99).
Hardware utilities (pkg/hardwareutils/)
BMC protocol handling, in its own Go module.
How a request flows
A single provisioning step of one host runs as follows:
- controller-runtime invokes
Reconcile, which fetches the BMH (baremetalhost_controller.go:119, fetch at:132). Ametal3.io/pausedannotation returns immediately (:148). - The controller resolves and validates the BMC credentials secret (
buildAndValidateBMCCredentials,:200). - It builds a
reconcileInfo, then creates a provisioner withProvisionerFactory.NewProvisioner(...)(:240). OnErrNotReadyit requeues after a delay (:242). newHostStateMachine(...)is built andstateMachine.ReconcileState(ctx, info)advances the machine by one step (:250-:251).actResult.Result()returns the controller-runtimectrl.Result(:252).- Inside
ReconcileState(host_state_machine.go:177), adeferschedulesupdateHostStateFrom, then delete, detached, and registration checks run in order before the current state's handler is looked up in thehandlers()map (:44) and executed. - For the provisioning state,
handleProvisioning(host_state_machine.go:540) callsactionProvisioning(baremetalhost_controller.go:1365), which callsprov.Provision(...)(:1392). If the result isDirty, the host is requeued (:1417); when there is no more work, the handler advances the next state toProvisioned(host_state_machine.go:548). - At the end of
Reconcile, ifactResult.Dirty()or conditions changed,saveHostStatuswrites the status subresource back and fires post-save callbacks and events (baremetalhost_controller.go:270-:286).
Key design decisions
The model is pull-based reconcile plus an explicit finite state machine. State is persisted in Status.Provisioning.State, and each reconcile is an idempotent single step. The actionResult interface expresses, by type, whether to requeue and whether to save status. The maintainers note that status is saved only when told to, otherwise an unrecoverable error would create an infinite loop reconciling the same object (baremetalhost_controller.go:266-:269).
State transitions for (de)provisioning are gated by capacity. When the next state is inspecting, provisioning, or deprovisioning, ensureCapacity checks for a free slot on the provisioner; if none is free the action is delayed rather than pushed (host_state_machine.go:87, :107-:114). This keeps BMO from overloading Ironic.
Extension points
- The
BareMetalHostCRD itself, consumed by external controllers such ascluster-api-provider-metal3. - Validating and defaulting webhooks under
internal/webhooks/metal3.io/. - The
Provisionerinterface (pkg/provisioner/provisioner.go:143), loaded at runtime as a Go.soplugin (pkg/provisioner/plugin.go:99). Third parties can ship their own provisioner backend. See Internals for how the loader validates a plugin.