Bare Metal Instances in Overcloud

This documentation explains installing Ironic for providing bare metal instances in the overcloud to end users. This feature is supported starting with Newton.

You need at least 3 nodes to use bare metal provisioning: one for the undercloud, one for the controller and one for the actual instance. This guide assumes using both virtual and bare metal computes, so to follow it you need at least one more node, 4 in total.

It is recommended to have at least 12 GiB of RAM on the undercloud and at least 8 GiB of RAM on the controllers. The controllers should have enough disk space to keep a cache of user instance images, at least 50 GiB is recommended.

It’s also highly recommended that you use at least two networks:

  • Undercloud provisioning network (connects undercloud and overcloud nodes)
  • Overcloud provisioning network (connects overcloud nodes and tenant bare metal instances)

This guide, however, uses one network for simplicity. If you encounter weird DHCP, PXE or networking issues with such a single-network configuration, try shutting down the introspection DHCP server on the undercloud after the initial introspection is finished:

sudo systemctl stop openstack-ironic-inspector-dnsmasq

Preparing environment

If you already have an instackenv.json file with all nodes prepared, you might want to leave some of the nodes for overcloud instances. E.g. if you have three nodes in the instackenv.json, you can split them:

jq '.nodes[0:2] | {nodes: .}' instackenv.json > undercloud.json

The format of the remaining nodes is TripleO-specific, so we need to convert it to something Ironic can understand without using TripleO workflows:

jq '.nodes[2:3] | {nodes: map({driver: .pm_type, name: .name,
    driver_info: {ssh_username: .pm_user, ssh_address: .pm_addr,
                  ssh_key_contents: .pm_password, ssh_virt_type: "virsh"},
    properties: {cpus: .cpu, cpu_arch: .arch, local_gb: .disk, memory_mb: .memory},
    ports: .mac | map({address: .})})}' instackenv.json > overcloud-nodes.yaml


This command intentionally omits the capabilities, as they are often TripleO-specific, e.g. they force local boot instead of network boot used by default in Ironic.

Then enroll only undercloud.json in your undercloud:

source stackrc
openstack overcloud node import --provide undercloud.json

Stable Branch


For TripleO release Mitaka, the nodes should be imported with:

openstack baremetal import undercloud.json


If you used tripleo-quickstart, you may have to delete the nodes that did not end up in undercloud.json.

Configuring and deploying overcloud

A few things can be configured in advance for overcloud Ironic in an environment file (ironic-config.yaml in this guide):

  • IronicEnabledDrivers parameter sets the list of enabled drivers. The most often used bare metal driver is pxe_ipmitool. Also enabled by default are pxe_ilo and pxe_drac drivers.

    Other drivers might require additional configuration to work properly. See Ironic drivers documentation for details.


    Testing on a virtual environment requires the pxe_ssh driver to be explicitly enabled, for example:

            - pxe_ssh

    If you used tripleo-quickstart to build your environment, the resulting configuration is a bit different:

            - pxe_ssh
            ironic::drivers::ssh::libvirt_uri: 'qemu:///session'
  • NovaSchedulerDefaultFilters configures available scheduler filters. For a hybrid deployment it’s important to prepend AggregateInstanceExtraSpecsFilter to the default list:

            - RetryFilter
            - AggregateInstanceExtraSpecsFilter
            - AvailabilityZoneFilter
            - RamFilter
            - DiskFilter
            - ComputeFilter
            - ComputeCapabilitiesFilter
            - ImagePropertiesFilter

    For a deployment with only bare metal hosts you might want to replace some filters with their Exact counterparts. In such case the scheduler will require a strict match between bare metal nodes and flavors. Otherwise, any bare metal node with higher or equal specs would match.

            - RetryFilter
            - AvailabilityZoneFilter
            - ExactRamFilter
            - ExactDiskFilter
            - ExactCoreFilter
            - ComputeFilter
            - ComputeCapabilitiesFilter
            - ImagePropertiesFilter
  • IronicCleaningDiskErase configures erasing hard drives before the first and after every deployment. There are two recommended values: full erases all data and metadata erases only disk metadata. The former is more secure, the latter is faster.


    It is highly recommended to set this parameter to metadata for virtual environments, as full cleaning can be extremely slow there.

  • IronicCleaningNetwork sets the name or UUID of the overcloud network to use for node cleaning. Initially is set to provisioning and should be set to an actual UUID later when Configuring cleaning.


    In the Newton release this parameter was not available, and no default value was set for the cleaning network.

  • IronicIPXEEnabled parameter turns on iPXE (HTTP-based) for deployment instead of PXE (TFTP-based). iPXE is more reliable and scales better, so it’s on by default. Also iPXE is required for UEFI boot support.

Add the ironic environment file when deploying:

openstack overcloud deploy --templates \
    -e /usr/share/openstack-tripleo-heat-templates/environments/services/ironic.yaml \
    -e ironic-config.yaml


We don’t require any virtual compute nodes for the bare metal only case, so feel free to set ComputeCount: 0 in your environment file, if you don’t need them.

Checking deployment

Check that Ironic works by connecting to the overcloud and trying to list the nodes (you should see an empty response, but not an error):

source overcloudrc
openstack baremetal node list

You can also check the enabled driver list:

$ openstack baremetal driver list
| Supported driver(s) | Active host(s)          |
| pxe_drac            | overcloud-controller-0. |
| pxe_ilo             | overcloud-controller-0. |
| pxe_ipmitool        | overcloud-controller-0. |

For HA configuration you should see all three controllers:

$ openstack baremetal driver list
| Supported driver(s) | Active host(s)                                                                                             |
| pxe_drac            | overcloud-controller-0.localdomain, overcloud-controller-1.localdomain, overcloud-controller-2.localdomain |
| pxe_ilo             | overcloud-controller-0.localdomain, overcloud-controller-1.localdomain, overcloud-controller-2.localdomain |
| pxe_ipmitool        | overcloud-controller-0.localdomain, overcloud-controller-1.localdomain, overcloud-controller-2.localdomain |

If this list is empty or does not show any of the controllers, then the openstack-ironic-conductor service on this controller failed to start. The likely cause is missing dependencies for vendor drivers.

Preparing networking

Next, we need to create at least one network for nodes to use. By default Ironic uses the tenant network for the provisioning process, and the same network is often configured for cleaning. Using separate networks is beyond the scope of this guide.

As already mentioned, this guide assumes only one physical network shared between undercloud and overcloud. In this case the subnet address must match the one on the undercloud, but the allocation pools must not overlap (including the pool used by undercloud introspection).

For example, the following commands will work with the default undercloud parameters:

source overcloudrc
openstack network create --share --provider-network-type flat \
    --provider-physical-network datacentre --external provisioning
openstack subnet create --network provisioning \
    --subnet-range --gateway \
    --allocation-pool start=,end= provisioning-subnet


Network types other than “flat” are not supported.

We will use this network for bare metal instances (both for provisioning and as a tenant network), as well as an external network for virtual instances. In a real situation you will only use it as provisioning, and create a separate physical network as external.

Now you can create a regular tenant network to use for virtual instances and a router between provisioning and tenant networks:

openstack network create tenant-net
openstack subnet create --network tenant-net --subnet-range \
    --allocation-pool start=,end= tenant-subnet
openstack router create default-router
openstack router add subnet default-router provisioning-subnet
openstack router add subnet default-router tenant-subnet

Configuring cleaning

Starting with the Ocata release, Ironic is configured to use network called provisioning for node cleaning. However, network names are not unique. A user creating another network with the same name will break bare metal provisioning. Thus, it’s highly recommended to update the deployment, providing the provider network UUID.

Use the following command to get the UUID:

openstack network show provisioning -f value -c id

Update the environment file you’ve created, setting IronicCleaningNetwork to the this UUID, for example:

    IronicCleaningNetwork: c71f4bfe-409b-4292-818f-21cdf910ee06


In the Newton release this parameter was not available, use cleaning_network_uuid hieradata value instead, for example:

        ironic::conductor::cleaning_network_uuid: c71f4bfe-409b-4292-818f-21cdf910ee06

This variable does not support node names and does not have a default value in this release.

Finally, run the deploy command with exactly the same arguments as before (don’t forget to include the environment file if it was not included previously).

Adding deployment images

Ironic requires the ironic-python-agent image stored in Glance. You can use the same images you already have on the undercloud:

source overcloudrc
openstack image create --public --container-format aki \
    --disk-format aki --file ~/ironic-python-agent.kernel deploy-kernel
openstack image create --public --container-format ari \
    --disk-format ari --file ~/ironic-python-agent.initramfs deploy-ramdisk


These commands assume that the images are in the home directory, which is often the case for TripleO.

Creating flavors and host aggregates

As usual with OpenStack, you need to create at least one flavor to be used during deployment. As bare metal resources are inherently not divisible, the flavor will set minimum requirements (CPU count, RAM and disk sizes) that a node must fulfil. Creating a single flavor is sufficient for the simplest case:

source overcloudrc
openstack flavor create --ram 1024 --disk 20 --vcpus 1 baremetal

If you don’t plan on using virtual instances, this is where you can stop.

For a hybrid bare metal and virtual environment, you have to set up host aggregates for virtual and bare metal hosts. We will use a property called baremetal to link flavors to host aggregates:

openstack aggregate create --property baremetal=true baremetal-hosts
openstack aggregate create --property baremetal=false virtual-hosts
openstack flavor set baremetal --property baremetal=true


This association won’t work without AggregateInstanceExtraSpecsFilter enabled as described in Configuring and deploying overcloud.

Then for all flavors you’ve created for virtual instances set the same baremetal property to false, for example:

openstack flavor create --ram 1024 --disk 20 --vcpus 1 virtual
openstack flavor set virtual --property baremetal=false

Creating instance images

You can build your images using diskimage-builder tool already available on the undercloud, for example:

disk-image-create centos7 baremetal dhcp-all-interfaces grub2 -o centos-image


The following elements are actually optional:

  • dhcp-all-interfaces makes the resulting instance get IP addresses for all NICs via DHCP.
  • grub2 installs the grub bootloader on the image, so that local boot can be used in additional to PXE booting.

This command creates a so called partition image, i.e. an image containing only root partition. Ironic also supports whole disk images, i.e. images with the whole partition table embedded. This may be the only option when running non-Linux images. Please check Ironic images documentation for more details on building and using images.

Three components are created for every partition image: the main image with qcow2 extension, the kernel with vmlinuz extension and the initrd image with initrd extension.

Upload them with the following command:

source overcloudrc
KERNEL_ID=$(openstack image create --file centos-image.vmlinuz --public \
    --container-format aki --disk-format aki -f value -c id \
RAMDISK_ID=$(openstack image create --file centos-image.initrd --public \
    --container-format ari --disk-format ari -f value -c id \
openstack image create --file centos-image.qcow2 --public \
    --container-format bare --disk-format qcow2 \
    --property kernel_id=$KERNEL_ID --property ramdisk_id=$RAMDISK_ID \


A whole disk image will only have one component - the image itself with qcow2 extension. Do not set kernel_id and ramdisk_id properties for such images.

Enrolling nodes

For all nodes you’re enrolling you need to know:

  • BMC (IPMI, iDRAC, iLO, etc) address and credentials,
  • MAC address of the PXE booting NIC,
  • CPU count and architecture, memory size in MiB and root disk size in GiB,
  • Serial number or WWN of the root device, if the node has several hard drives.

In the future some of this data will be provided by the introspection process, which is not currently available in the overcloud.

This guide uses inventory files to enroll nodes. Alternatively, you can enroll nodes directly from CLI, see Ironic enrollment documentation for details.

Preparing inventory

If you have not prepared overcloud-nodes.yaml while Preparing environment, do it now in the following format:

    - name: node-0
      driver: pxe_ipmitool
        ipmi_address: <BMC HOST>
        ipmi_username: <BMC USER>
        ipmi_password: <BMC PASSWORD>
        cpus: <CPU COUNT>
        cpu_arch: <CPU ARCHITECTURE>
        memory_mb: <MEMORY IN MIB>
        local_gb: <ROOT DISK IN GIB>
            serial: <ROOT DISK SERIAL>
        - address: <PXE NIC MAC>

The driver field must be one of IronicEnabledDrivers, which we set when Configuring and deploying overcloud.

The root_device property is optional, but it’s highly recommended to set it if the bare metal node has more than one hard drive. There are several properties that can be used instead of the serial number to designate the root device, see Ironic root device hints documentation for details.

Enrolling nodes

The overcloud-nodes.yaml file prepared in the previous steps can now be imported in Ironic:

source overcloudrc
openstack baremetal create overcloud-nodes.yaml


This command is provided by Ironic, not TripleO. It also does not feature support for updates, so if you need to change something, you have to use openstack baremetal node set and similar commands.

The nodes appear in the enroll provision state, you need to check their BMC credentials and make them available:

DEPLOY_KERNEL=$(openstack image show deploy-kernel -f value -c id)
DEPLOY_RAMDISK=$(openstack image show deploy-ramdisk -f value -c id)

for uuid in $(openstack baremetal node list -f value -c UUID);
    openstack baremetal node manage $uuid
    openstack baremetal node set $uuid \
        --driver-info deploy_kernel=$DEPLOY_KERNEL \
        --driver-info deploy_ramdisk=$DEPLOY_RAMDISK
    openstack baremetal node provide $uuid

The deploy kernel and ramdisk were created as part of Adding deployment images.


The baremetal node provide command makes a node go through cleaning procedure, so it might take some time depending on the configuration.

If a node gets stuck in the enroll state, and you see the following error:

The requested action "provide" can not be performed on node "<UUID>" while it is in state "enroll".

then the power credentials validation failed for this node. Use the following command to get the last error:

openstack baremetal node show <UUID> -f value -c last_error

With the switch to cell_v2 in Ocata (and on), it’s now needed to search for cells, or a single cell, and map found hosts, which are not currently mapped. The following command needs to run once on one controller node:

sudo nova-manage cell_v2 discover_hosts --verbose


More information about cells is available here:

Checking available resources

Check that nodes are really enrolled and the power state is reflected correctly (it may take some time):

$ source overcloudrc
$ openstack baremetal node list
| UUID                                 | Name       | Instance UUID | Power State | Provisioning State | Maintenance |
| a970c5db-67dd-4676-95ba-af1edc74b2ee | instance-0 | None          | power off   | available          | False       |
| bd99ec64-4bfc-491b-99e6-49bd384b526d | instance-1 | None          | power off   | available          | False       |

After a few minutes, new hypervisors should appear in Nova and the stats should display the sum of bare metal and virtual resources:

$ openstack hypervisor list
| ID | Hypervisor Hostname                  |
|  2 | overcloud-novacompute-0.localdomain  |
| 17 | bd99ec64-4bfc-491b-99e6-49bd384b526d |
| 20 | a970c5db-67dd-4676-95ba-af1edc74b2ee |

$ openstack hypervisor stats show
| Field                | Value |
| count                | 3     |
| current_workload     | 0     |
| disk_available_least | 146   |
| free_disk_gb         | 149   |
| free_ram_mb          | 16047 |
| local_gb             | 149   |
| local_gb_used        | 0     |
| memory_mb            | 18095 |
| memory_mb_used       | 2048  |
| running_vms          | 0     |
| vcpus                | 3     |
| vcpus_used           | 0     |


Each bare metal node becomes a separate hypervisor in Nova. The hypervisor host name always matches the associated node UUID.

Assigning host aggregates

For hybrid bare metal and virtual case you need to specify which host belongs to which host aggregates (virtual or baremetal as created in Creating flavors and host aggregates).

When the default host names are used, we can take advantage of the fact that every virtual host will have compute in its name. All bare metal hypervisors will be assigned to one (non-HA) or three (HA) controller hosts. So we can do the assignment with the following commands:

source overcloudrc
for vm_host in $(openstack hypervisor list -f value -c "Hypervisor Hostname" | grep compute);
    openstack aggregate add host virtual-hosts $vm_host

openstack aggregate add host baremetal-hosts overcloud-controller-0.localdomain
# Ignore the following two for a non-HA environment
openstack aggregate add host baremetal-hosts overcloud-controller-1.localdomain
openstack aggregate add host baremetal-hosts overcloud-controller-2.localdomain


Every time you scale out compute nodes, you need to add newly added hosts to the virtual-hosts aggregate.

Booting a bare metal instance

You will probably want to create a keypair to use for logging into instances. For example, using SSH public key from undercloud:

source overcloudrc
openstack keypair create --public-key ~/.ssh/ undercloud-key

Now you’re ready to boot your first bare metal instance:

openstack server create --image centos-image --flavor baremetal \
    --nic net-id=$(openstack network show provisioning -f value -c id) \
    --key-name undercloud-key instance-0

After some time (depending on the image), you will see the prepared instance:

$ openstack server list
| ID                                   | Name       | Status | Networks                    |
| 2022d237-e249-44bd-b864-e7f536a8e439 | instance-0 | ACTIVE | provisioning=  |


If you encounter “No valid host found” error from Nova, make sure to read the undercloud troubleshooting guide on this topic: No Valid Host Found Error.

Let’s check that it actually got scheduled on a bare metal machine:

$ openstack server show instance-0 -c "OS-EXT-SRV-ATTR:host" -c "OS-EXT-SRV-ATTR:hypervisor_hostname"
| Field                               | Value                                |
| OS-EXT-SRV-ATTR:host                | overcloud-controller-0.localdomain   |
| OS-EXT-SRV-ATTR:hypervisor_hostname | bd99ec64-4bfc-491b-99e6-49bd384b526d |

You can now log into it:

$ ssh centos@
The authenticity of host ' (' can't be established.
ECDSA key fingerprint is eb:35:45:c5:ed:d9:8a:e8:4b:20:db:06:10:6f:05:74.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '' (ECDSA) to the list of known hosts.
[centos@instance-0 ~]$

Now let’s try the same with a virtual instance:

openstack server create --image centos-image --flavor virtual \
    --nic net-id=$(openstack network show tenant-net -f value -c id) \
    --key-name undercloud-key instance-1

This instance gets scheduled on a virtual host:

$ openstack server show instance-1 -c "OS-EXT-SRV-ATTR:host" -c "OS-EXT-SRV-ATTR:hypervisor_hostname"
| Field                               | Value                               |
| OS-EXT-SRV-ATTR:host                | overcloud-novacompute-0.localdomain |
| OS-EXT-SRV-ATTR:hypervisor_hostname | overcloud-novacompute-0.localdomain |