OpenKeel: the sovereign French Cloud solution of the inherent group, with a European reach Discover inherent

Resources

Ironic design

Ironic in OpenKeel: upstream Python in a Helm chart.

Ironic in OpenKeel: design note

Status: decided and deployed on a test cluster (2026-10-31): Kolla images, Redfish virtual media, flat and noop, json-rpc, sushy-tools on a new kind cluster. Ironic stays upstream Python at OpenStack 2026.2 (user decision); openkeel-ironic is a Helm chart around it, not a Go rewrite. Evidence below was measured on this host on 2026-10-31.

Decision 1: image source

Kolla quay.io/openstack.kolla/ironic-{api,conductor,pxe}Metal3 quay.io/metal3-io/ironic:release-39.0
Ironic version39.0.1.dev38 (stable/2026.2)39.0.1.dev33 (39.0.0-33-g39703221e)
Architecturesamd64 and aarch64 (-aarch64 tag suffix, not one multi-arch tag)amd64 only: emulated on this host, unusable natively on arm64 nodes
Base, sizeDebian trixie, Python 3.13; 535 MB (api), 573 MB (conductor)CentOS Stream 9; 206 MB, one image for everything
Keystonekeystonemiddleware present, standard usemiddleware present, but ironic.conf.j2 only renders noauth or http_basic: Keystone means bypassing Metal3's scripts
Configurationnone imposed once kolla_start is bypassed: ironic.conf from a ConfigMapenvironment variables through configure-ironic.sh and Jinja templates, single-process oriented
Hardeningdefault entrypoint uses sudo kolla_set_configs, but ironic-api and ironic-conductor --version run with uid 42422, read-only root, all capabilities dropped and no-new-privileges (only --version tested so far)image default user is root; not tested further
Boot toolingipmitool, mkisofs, qemu-img in conductor; iPXE and TFTP in ironic-pxe; no HTTP server: needs a separate imagednsmasq, httpd, ipmitool, xorrisofs and ESP images all included
Tagsmoving, rebuilt daily, one per series: pin by digestmoving release-39.0, plus dated tags

**Decision: Kolla, debian-trixie, pinned by digest, with binaries run directly** (no kolla_start, no sudo), plus a standard unprivileged nginx image for the conductor's HTTP server. Metal3 solves a standalone, Keystone-less use case and would run only under emulation here.

Consequences for okadm: it assumes <imageRepository>/openkeel-<project>:<imageTag> (internal/deploy/values.go:78) and an image per architecture is new. The manifest gets an external image declaration per architecture for this component, and okadm selects it from the node architecture, as it does for the base image.

Decision 2: first boot mode

Redfish virtual media first (redfish-virtual-media): no DHCP or TFTP in the cluster, only conductor to BMC and node to conductor HTTP. iPXE and DHCP come next (ipxe, dnsmasq sidecar with NET_ADMIN, NET_RAW, Multus attachment).

Limits of virtual media:

  • the BMC must implement Redfish VirtualMedia InsertMedia by URL (on some vendors this needs a licence) and must reach the conductor's HTTP server;
  • the IPA ramdisk still needs an IP address: DHCP from the physical network, or a static network_data on the node (DHCP-less boot, not tested here). It must then reach the Ironic API and the conductor's HTTP server;
  • UEFI ISOs need an ESP image ([conductor]/bootloader) or a prebuilt deploy_iso.

QCOW2 images: the server never boots the QCOW2 itself. It boots the IPA ISO, IPA writes the image to the local disk, then the server reboots from disk. With the defaults (force_raw_images, [agent]/stream_raw_images, [agent]/image_download_source = http) the conductor converts a Glance image to raw with qemu-img in its cache, so the PVC must hold the raw size. An image given by URL goes directly to IPA, which converts it in the ramdisk's memory, unless image_download_source = local. The image must be a whole-disk image with a bootloader matching the server's architecture and firmware. Go Glance serves the original QCOW2 only with glance_store.csi.keep_original (default true); otherwise, and for images made by Cinder upload-to-image, it serves raw while disk_format may say otherwise (to test).

Decision 3: network interface

The brief recommends flat. The code adds a constraint: flat is a Neutron interface. ironic/drivers/modules/network/flat.py requires [neutron]/cleaning_network in validate and raises NetworkError when no port has a VIF to bind, so a deployment without Nova or Neutron VIFs fails. noop needs no Neutron.

**Decision: enable flat and noop, default flat,** with noop on standalone nodes (tests, operator-driven deployments). neutron and ironic-networking come later.

Decision 4: RPC

json-rpc (conductor port 8089, per-pod DNS name from the StatefulSet's headless Service, Keystone auth): no RabbitMQ user for a component that needs it for nothing else. Notifications stay off.

Decision 5: networks and test hardware

To be supplied by the user for real hardware: provisioning network (VLAN, IP range, who runs DHCP), BMC network, one Redfish machine. Without them, tests use sushy-tools in a test-only image on a new kind cluster.

What sushy-tools on kind can prove: chart, okadm, Keystone, openstack baremetal, Redfish power and boot-device calls, enroll to available with automated cleaning off. What it cannot prove: a real deployment. Kind nodes have no KVM and the ramdisk would not boot, so IPA, virtual media boot, image writing and active stay unverified.

Facts that shape the chart

  • System scope. Ironic's baremetal:node:create needs system_scope:all, but baremetal:node:create:self_owned_node lets a project-scoped admin or service create nodes its project owns. Measured through Go Keystone: a system-scoped admin (--os-system-scope all) sees and manages everything; a project-scoped admin creates its own nodes, does not see others', and cannot list conductors.
  • API server. ironic-api binds in each worker (Address already in use with api_workers: 2): one worker per pod, scaled by replicas.
  • HTTP server. httpboot/agent_images holds absolute symlinks into images/: nginx mounts the whole volume at the conductor's path, or cached images answer 404.
  • Service user. openkeel-keystone bootstrap.services.ironic (user ironic, role service, catalog type baremetal, port 6385). openkeel-chart adds a database-url key in SQLAlchemy form next to the Go database-dsn. PyMySQL connects without TLS: same network trust as the lab, to review for production.
  • Optional component. okadm deploys it only with --components ironic; the role provisioning is checked by okadm plan.

Verification (kind cluster ok-ironic, arm64, openkeel-ironic/test/smoke.sh)

VerifiedHow
chart renders and lints; okadm tests pass; okadm install from the embedded chart; okadm status healthycommands run
Kolla aarch64 images run as uid 42422, read-only root, no capabilities; dbsync and post-install hook Jobs succeedreal pods
openstack baremetal through Go Keystone, catalog baremetal, JSON-RPC with Keystone authclient pod, python-ironicclient 6.3.0
enroll to manageable to available, power, boot device, over Redfishsushy-tools fake driver
deploy to wait call-back: QCOW2 downloaded and converted to raw, ISO mounted over virtual media and fetched from nginx, node powered on; undeploy back to availablesushy-tools fake driver, image_download_source=local, a test ISO
BMC password masked in the APIdriver_info output
Multus attachment: net1 used for my_ip and http_urlwith ptp, not macvlan, not a real network

| okadm install --components ironic in lab mode (kind, 3 nodes) and in production mode (3 API, 2 conductors, Galera 3), then smoke.sh on each, on 2026-11-02 | production on kind needed two test-only additions: the lab's DNS NetworkPolicy workaround (kindnet drops DNS replies to pods selected by ingress policies; Keystone fails the same way) and automated_clean: false (no ramdisk) |

Production gaps seen on 2026-11-02: okadm checks the provisioning role label (warning only) but no chart receives global.nodeRoles, so conductors are not pinned to provisioning nodes (one ran on an unlabelled node). Two conductors with required anti-affinity need two schedulable nodes, while okadm plan asks for one provisioning node.

Not verified: a real machine or IPA, active, flat with Neutron, Glance as image source, Nova's driver against this chart, amd64 digests (rendered, never run), networkPolicy.bmcCIDRs (rendered only), automated cleaning, okadm upgrade.

Known gaps, to report rather than hide

GapEffectTreatment
No Nova agent publishes Ironic nodes to Placementopenstack server create on bare metal cannot scheduleoutside this chart; Go work in openkeel-nova, proposed as a follow-up
Neutron Go writes no extra_dhcp_opts and has no external portsiPXE through Neutron DHCP impossible; OVN DHCP does not reach a physical server on a flat networkvirtual media avoids the first; the second needs static addressing
openkeel-nova/internal/driver/ironic/ironic.go sends network_data with networks: []with no OVN DHCP on the server, a Nova bare metal server gets no IP configurationsame follow-up as above
No Swiftno temp URLs[agent]/image_download_source = http, [deploy]/configdrive_use_object_store = false, [redfish]/use_swift = false: images served by the conductor
IPA ramdisktarballs.opendev.org answers with a self-signed chain from this host (TLS interception not trusted): the ramdisk could not be fetched herechart takes operator-supplied kernel and ramdisk (config.conductor)
Pinned digestsupstream tags move daily, and quay.io may remove untagged digestsmirror for production (global.imageRegistry); refresh the manifest otherwise
Credentials at restIronic masks driver_info in its API, and its database holds it unencryptedprotect the database and the conductor; the earlier reference wrongly said encrypted
Separate namespaceNODE-PLACEMENT asks for one for the conductor; okadm installs everything in one namespacenot done
JSON-RPC in clearno TLS on port 8089 in this version, tokens travel on the pod networkNetworkPolicy limits it to the API; use_ssl with cert-manager later
Rescue, consoledisabled; Nova's Rescue fails on these nodeslater
Skill reference says Go rewritereferences/backends/ironic-baremetal.md and SKILL.md:32 are wrong after this decisionupdated once decisions are made, diagram model regenerated

Source: openkeel-docs/IRONIC-DESIGN.md