Published: January 12, 2019
Updated: April 07, 2019
Tags: OpenStack, cloud-init
16 min read

How Guest Instances Configure Themselves In OpenStack

Demonstrations are shown on Red Hat OpenStack Platform 14 which is based on Upstream RDO Rocky release

Using RHEL7 cloud image for guest instance with network access to Nova Metadata Server

User Requesting An Instance In A Cloud Environment

One of the basic expectations regarding clouds is once an end-user requests an instance, it should be supplied with the least effort involved and in a timely manner.

While the process is transparent to the end-user and might seem like magic, behind the scenes there are several components that attempt to ensure that a properly configured instance is supplied.

In this blog post we'll discuss how this is achieved in the OpenStack cloud.

Before getting into the technical details, it's important to understand the flow that occurs in cloud platforms.

Public clouds provide prebuilt image catalog from which the end-user must choose when spawning an instance, they have to ensure that the instance is spawned without any involvement, and support additional customization provided that will alter the instance.

Successful boot in a cloud environment is achieved by the cloud images(special images designed for the cloud) having a component that can communicate with the cloud infrastructure, and apply necessary configuration(network, storage, user supplied actions and etc).

In OpenStack, the component which is part of the cloud images is cloud-init [1] and the infrastructure component is Nova Metadata Service [2].

Guest instance accessing Nova Metadata Service

When OpenStack spawns an instance, if the instance is using a cloud image with cloud-init, the instance can leverage the Nova Metadata Service to retrieve necessary metadata needed for configuration and additional user data that allow user defined customization to occur during spawn.

Configuration components in OpenStack

OpenStack is able to inject data into images if they contain a cloud-init package, most popular distributions offer prebuilt images containing cloud-init.
Refer to OpenStack documentation on retrieving cloud images [3].

Cloud-init

cloud-init is a robust tool with many capabilities, this blog post describes some basic capabilities.

cloud-init is a python utility that is distributed in the form of a package which aims to be the defacto multi-distribution package that handles early initialization of a cloud instance(as described in the documentation).

It operates during various boot stages [4] to make sure that the necessary configuration is applied.

cloud-init is a multi cloud solution due to its ability to retrieve necessary metadata and configuration from various data sources [5].
OpenStack is one of the default datasrouces available [6].

Nova Metadata Service

Nova Metadata Service is the infrastructure component that provides the metadata to instances.

This service holds two sets of APIs that the instance could consume, an OpenStack metadata API and an EC2 compatible API.

cloud-init is able to access the OpenStack datasource and local Config Drive datasource in order to retrieve metadata:

Metadata Server

Metadata is exposed to the instance via a URL, which by default is hosted on http://169.254.169.254.

Instances that have network connectivity and routing to the metadata server can perform an HTTP GET request for the relevant API.

http://169.254.169.254/openstack will provide OpenStack API.
http://169.254.169.254/ec2 will provide EC2 compatible API.

Config Drive

Instances which do not have network connectivity could still consume metadata via a CD-ROM like device(Config Drive).

When booting up an instance in OpenStack, there is an option to attach a Config Drive [7] which contains all the metadata information provided by Nova Metadata Service.

Config Drive is useful in a scenario where your instance lacks external connectivity which can be configured later while still providing all the necessary metadata.

For example:
I use Config Drive in an environment where an instance is connected to a set of switches with only one of them allowing external connectivity to Nova Metadata Service.
During boot, an instance might be launched with the first NIC attached to a network which can't access Nova Metadata Service which will prevent cloud-init from accessing metadata.
By using Config Drive, I'm able to supply the metadata and configure external network using a script which locates the NIC connected to an external network [8].

How metadata is applied to instance

cloud-init in systemd based distributions is a collection of daemons which are enabled by default:

systemctl list-units | grep cloud-init
  cloud-init-local.service                                          loaded active exited    Initial cloud-init job (pre-networking)
  cloud-init.service                                                loaded active exited    Initial cloud-init job (metadata service crawler)

While the daemons are running, they invoke the cloud-init CLI which logs the output by default into /var/log/cloud-init.log [9].

How cloud-init discovers distribution

cloud-init must discover the distribution of the instance in order to apply configuration:

cat /var/log/cloud-init.log
(text is omitted)
2019-01-11 14:14:03,399 - handlers.py[DEBUG]: finish: init-local/check-cache: SUCCESS: no cache found
2019-01-11 14:14:03,399 - util.py[DEBUG]: Attempting to remove /var/lib/cloud/instance
2019-01-11 14:14:03,415 - stages.py[DEBUG]: Using distro class <class 'cloudinit.distros.rhel.Distro'>
(text is omitted)

Once a distribution is discovered, a python class containing methods that apply configuration on said distribution is loaded, in this scenario a rhel [10] distribution was detected.

How cloud-init discovers datasources

After distribution discovery, the next step is discovering a data source:

cat /var/log/cloud-init.log
(text is omitted)
2019-01-11 14:14:03,416 - __init__.py[DEBUG]: Looking for for data source in: ['NoCloud', 'ConfigDrive', 'OpenNebula', 'DigitalOcean', 'Azure', 'AltCloud', 'OVF', 'MAAS', 'GCE', 'OpenStack', 'AliYun', 'Ec2', 'CloudSigma', 'CloudStack', 'SmartOS', 'Bigstep', 'None'], via packages ['', u'cloudinit.sources'] that matches dependencies ['FILESYSTEM']
2019-01-11 14:14:03,509 - __init__.py[DEBUG]: Searching for local data source in: [u'DataSourceNoCloud', u'DataSourceConfigDrive', u'DataSourceOpenNebula', u'DataSourceDigitalOcean', u'DataSourceOVF', u'DataSourceCloudSigma', u'DataSourceSmartOS']
(text is omitted)

All supported datasources are loaded and then cloud-init iterates and attempts to discover a datasource:

cat /var/log/cloud-init.log
(text is omitted)
2019-01-11 14:14:03,576 - __init__.py[DEBUG]: Seeing if we can get any data from <class 'cloudinit.sources.DataSourceDigitalOcean.DataSourceDigitalOcean'>
2019-01-11 14:14:03,576 - util.py[DEBUG]: querying dmi data /sys/class/dmi/id/sys_vendor
2019-01-11 14:14:03,577 - util.py[DEBUG]: Reading from /sys/class/dmi/id/sys_vendor (quiet=False)
2019-01-11 14:14:03,577 - util.py[DEBUG]: Read 8 bytes from /sys/class/dmi/id/sys_vendor
2019-01-11 14:14:03,577 - util.py[DEBUG]: dmi data /sys/class/dmi/id/sys_vendor returned Red Hat
2019-01-11 14:14:03,577 - handlers.py[DEBUG]: finish: init-local/search-DigitalOcean: SUCCESS: no local data found from DataSourceDigitalOcean
(text is omitted)

As the first line describes, cloud-init attempts to discover information if it's hosted on DigitalOcean [11], it fails and proceeds to the next datasource.
At some point, it will attempt to access the OpenStack datasource:

cat /var/log/cloud-init.log
(text is omitted)
2019-01-11 14:14:16,345 - handlers.py[DEBUG]: start: init-network/search-OpenStack: searching for network data from DataSourceOpenStack
2019-01-11 14:14:16,345 - __init__.py[DEBUG]: Seeing if we can get any data from <class 'cloudinit.sources.DataSourceOpenStack.DataSourceOpenStack'>
2019-01-11 14:14:16,346 - url_helper.py[DEBUG]: [0/1] open 'http://169.254.169.254/openstack' with {'url': 'http://169.254.169.254/openstack', 'headers': {'User-Agent': 'Cloud-Init/0.7.9'}, 'allow_redirects': True, 'method': 'GET', 'timeout': 10.0} configuration
2019-01-11 14:14:18,459 - url_helper.py[DEBUG]: Read from http://169.254.169.254/openstack (200, 94b) after 1 attempts
2019-01-11 14:14:18,459 - DataSourceOpenStack.py[DEBUG]: Using metadata source: 'http://169.254.169.254'
2019-01-11 14:14:18,460 - url_helper.py[DEBUG]: [0/6] open 'http://169.254.169.254/openstack' with {'url': 'http://169.254.169.254/openstack', 'headers': {'User-Agent': 'Cloud-Init/0.7.9'}, 'allow_redirects': True, 'method': 'GET', 'timeout': 10.0} configuration
(text is omitted)
2019-01-11 14:14:25,005 - util.py[DEBUG]: Crawl of openstack metadata service took 6.546 seconds
2019-01-11 14:14:25,005 - handlers.py[DEBUG]: finish: init-network/search-OpenStack: SUCCESS: found network data from DataSourceOpenStack
(text is omitted)

In this scenario, cloud-init accessed Nova Metadata Service via a metadata server.

Once a datastore is accessed, the type and errors accessing the datastore are logged into /run/cloud-init/result.json (which later will be symlimked to /var/lib/cloud/data/result.json:

cat /var/lib/cloud/data/result.json
{
 "v1": {
  "datasource": "DataSourceOpenStack [net,ver=2]",
  "errors": []
 }
}

How cloud-init applies configuration

After a datasource is detected, all the needed information is retrieved and cloud-init starts applying configuration.

Default configuration

cloud-init which is supplied as a part of a cloud image, contains distribution specific configuration files located in /etc/cloud.

User may override the default values by providing user_data file.

Example of default configuration file /etc/cloud/cloud.cfg which comes prepackaged in RHEL cloud image:

cat /etc/cloud/cloud.cfg
users:
 - default

disable_root: 1
ssh_pwauth:   0

mount_default_fields: [~, ~, 'auto', 'defaults,nofail', '0', '2']
resize_rootfs_tmp: /dev
ssh_deletekeys:   0
ssh_genkeytypes:  ~
syslog_fix_perms: ~

cloud_init_modules:
 - migrator
 - bootcmd
 - write-files
 - growpart
 - resizefs
 - set_hostname
 - update_hostname
 - update_etc_hosts
 - rsyslog
 - users-groups
 - ssh

cloud_config_modules:
 - mounts
 - locale
 - set-passwords
 - rh_subscription
 - yum-add-repo
 - package-update-upgrade-install
 - timezone
 - puppet
 - chef
 - salt-minion
 - mcollective
 - disable-ec2-metadata
 - runcmd

cloud_final_modules:
 - rightscale_userdata
 - scripts-per-once
 - scripts-per-boot
 - scripts-per-instance
 - scripts-user
 - ssh-authkey-fingerprints
 - keys-to-console
 - phone-home
 - final-message
 - power-state-change

system_info:
  default_user:
    name: cloud-user
    lock_passwd: true
    gecos: Cloud User
    groups: [wheel, adm, systemd-journal]
    sudo: ["ALL=(ALL) NOPASSWD:ALL"]
    shell: /bin/bash
  distro: rhel
  paths:
    cloud_dir: /var/lib/cloud
    templates_dir: /etc/cloud/templates
  ssh_svcname: sshd

Default configuration is used by cloud-init to configure the minimal necessary configuration that will result in a successful boot.

Network

Nova Metadata Service hosts network_data.json file which describes all the network interfaces attached to the instance and their configuration:

curl http://169.254.169.254/openstack/latest/network_data.json
{
	"services": [],
	"networks": [{
		"network_id": "a2332620-8027-4591-a0a6-dae312bc0944",
		"link": "tap7dbc4527-31",
		"type": "ipv4_dhcp",
		"id": "network0"
	}],
	"links": [{
		"ethernet_mac_address": "fa:16:3e:d8:bb:81",
		"mtu": 8950,
		"type": "vhostuser",
		"id": "tap7dbc4527-31",
		"vif_id": "7dbc4527-319a-45c6-8a0e-a27f92d062d3"
	}]
}

During datasource discovery, network_data.json is read and parsed:

cat /var/log/cloud-init.log
(text is omitted)
2019-01-11 14:14:19,136 - url_helper.py[DEBUG]: [0/6] open 'http://169.254.169.254/openstack/2015-10-15/network_data.json' with {'url': 'http://169.254.169.254/openstack/2015-10-15/network_data.json', 'headers': {'User-Agent': 'Cloud-Init/0.7.9'}, 'allow_redirects': True, 'method': 'GET', 'timeout': 10.0} configuration
2019-01-11 14:14:19,325 - url_helper.py[DEBUG]: Read from http://169.254.169.254/openstack/2015-10-15/network_data.json (200, 317b) after 1 attempts
(text is omitted)

Then cloud-init parses the network interfaces detected by the operating system and attempts to configure them based on the distribution's requirements:

cat /var/log/cloud-init.log
(text is omitted)
019-01-11 14:14:25,049 - util.py[DEBUG]: Read 18 bytes from /sys/class/net/eth0/address
2019-01-11 14:14:25,049 - stages.py[DEBUG]: applying net config names for {'version': 1, 'config': [{'subnets': [{'type': 'dhcp'}], 'type': 'physical', 'name': 'eth0', 'mac_address': 'fa:16:3e:d8:bb:81'}]}
2019-01-11 14:14:25,049 - stages.py[DEBUG]: Using distro class <class 'cloudinit.distros.rhel.Distro'>
2019-01-11 14:14:25,049 - util.py[DEBUG]: Reading from /sys/class/net/eth0/addr_assign_type (quiet=False)
2019-01-11 14:14:25,049 - util.py[DEBUG]: Read 2 bytes from /sys/class/net/eth0/addr_assign_type
2019-01-11 14:14:25,050 - util.py[DEBUG]: Reading from /sys/class/net/eth0/address (quiet=False)
2019-01-11 14:14:25,050 - util.py[DEBUG]: Read 18 bytes from /sys/class/net/eth0/address
2019-01-11 14:14:25,050 - util.py[DEBUG]: Reading from /sys/class/net/lo/addr_assign_type (quiet=False)
2019-01-11 14:14:25,050 - util.py[DEBUG]: Read 2 bytes from /sys/class/net/lo/addr_assign_type
2019-01-11 14:14:25,050 - util.py[DEBUG]: Reading from /sys/class/net/lo/address (quiet=False)
2019-01-11 14:14:25,050 - util.py[DEBUG]: Read 18 bytes from /sys/class/net/lo/address
2019-01-11 14:14:25,050 - util.py[DEBUG]: Reading from /sys/class/net/eth0/operstate (quiet=False)
2019-01-11 14:14:25,050 - util.py[DEBUG]: Read 3 bytes from /sys/class/net/eth0/operstate
2019-01-11 14:14:25,050 - util.py[DEBUG]: Reading from /sys/class/net/lo/operstate (quiet=False)
2019-01-11 14:14:25,050 - util.py[DEBUG]: Read 8 bytes from /sys/class/net/lo/operstate
2019-01-11 14:14:25,051 - util.py[DEBUG]: Running command ['ip', '-6', 'addr', 'show', 'permanent', 'scope', 'global'] with allowed return codes [0] (shell=False, capture=True)
2019-01-11 14:14:25,056 - util.py[DEBUG]: Running command ['ip', '-4', 'addr', 'show'] with allowed return codes [0] (shell=False, capture=True)
2019-01-11 14:14:25,059 - __init__.py[DEBUG]: no work necessary for renaming of [['fa:16:3e:d8:bb:81', 'eth0']]
2019-01-11 14:14:25,060 - stages.py[INFO]: Applying network configuration from fallback bringup=True: {'version': 1, 'config': [{'subnets': [{'type': 'dhcp'}], 'type': 'physical', 'name': 'eth0', 'mac_address': 'fa:16:3e:d8:bb:81'}]}
2019-01-11 14:14:25,062 - util.py[DEBUG]: Writing to /etc/sysconfig/network-scripts/ifcfg-eth0 - wb: [420] 159 bytes
2019-01-11 14:14:25,063 - util.py[DEBUG]: Restoring selinux mode for /etc/sysconfig/network-scripts/ifcfg-eth0 (recursive=False)
2019-01-11 14:14:25,063 - util.py[DEBUG]: Restoring selinux mode for /etc/sysconfig/network-scripts/ifcfg-eth0 (recursive=False)
2019-01-11 14:14:25,064 - util.py[DEBUG]: Reading from /etc/resolv.conf (quiet=False)
2019-01-11 14:14:25,064 - util.py[DEBUG]: Read 115 bytes from /etc/resolv.conf
2019-01-11 14:14:25,064 - util.py[DEBUG]: Writing to /etc/resolv.conf - wb: [420] 186 bytes
(text is omitted)

Metadata, SSH keys, Hostname, Storage(Partitions/filesystems), Devices

Nova Metadata Service hosts meta_data.json file which describes all the metadata, SSH keys, hostname, storage and devices attached to the instance:

curl http://169.254.169.254/openstack/latest/meta_data.json
{
	"random_seed": "aenkmMbb5FmAWiwUgbz8QvSCx+pF9Ny/5d35J1yIEZZzJiZTlZJ+0vkUCN5ytJKkeDLdn+5NfBYWUCU7njYYc/Yt/dCCkBKhWT38vUzqcRQuGlEWWwErAmKtmUUj69E2D1iw818WyPLdVLA7KTnBD5Gp6F+lseG9EgLtahiceK1OTayg5c0Q/DkjnA0MBPCDGfhS4Ef0ekTg7WCoQgL877D42+mI1l+nVgD28SwVh/+zmZ2+S3v2CnNckjFnOgQHtTxxx32MxreK2Hd4qM3CUPnB0qrpJr6298X6K6WQRipV4a7x7Cm9ZjzOMy4p67T5rZly5ggYuCmuW3KvAkOnwBIbGVM7Z8vgoenj+astHJ+HJNuqhyONfNXt0gZZIkAwLmDF9cVIlABZmsM8C0uxDh9yTaOjE6m4XGUmxgq943ZQKFaBJzkWl0dgtryq8y5rl94PVRD2HZHSk48SJBJjIVuQmSfARfqqNbqh0wk1AR/h0Xu8GKw/lIrejE8bMO/mL+0OTIS1I46lcSVZVAYqRo5NNgmetT2jF04eB1TdJU1ygp3/Z1c9cw5ode8ehhSjbTxaVElMdbMv65hhDa2Vjq/7UaVPmFIyJB/Hhz1YEnwkRcVYmolrVONwoOBxcxGxFjE0ToGlCFUSqABUFxzUMwAmWjR0kSsjw820Cj9nOXM=",
	"uuid": "380cc09f-25ce-4a4e-aa88-bba53234d92e",
	"availability_zone": "nova",
	"keys": [{
		"data": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDC/aZPawAn3K6K2OMzsENlcluwztaKYFeT3uASXGKlXeGBniZeidns3DnU4pCb4t+1weq5d+B4Tgyt18IDVWUJ5Ehd1xNdAP4Q6nB26WfVMH7PuHgzRLnMZiwhZz4BoxVAtTPfRv/FQGv7f+AQcpblZLz4LefBdrnYnc90ObDPQFfQNMttrars9CfW/RXanYvnUm+VQwzqftqoD3pI5tGaXjEQxrN1+LCteLHB5rABbVDMczilNz+Bg3KlX/pmUhqtrQvXMVHXBs2WuxklL9ibBGzQGTs9nyHNDI/wNs16cSz/QD2KUnEZajzDRChM+Riu6okeJA0JcyRI1Dae2FUf Generated-by-Nova",
		"type": "ssh",
		"name": "lab-keys"
	}],
	"hostname": "rhel",
	"launch_index": 0,
	"devices": [],
	"public_keys": {
		"lab-keys": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDC/aZPawAn3K6K2OMzsENlcluwztaKYFeT3uASXGKlXeGBniZeidns3DnU4pCb4t+1weq5d+B4Tgyt18IDVWUJ5Ehd1xNdAP4Q6nB26WfVMH7PuHgzRLnMZiwhZz4BoxVAtTPfRv/FQGv7f+AQcpblZLz4LefBdrnYnc90ObDPQFfQNMttrars9CfW/RXanYvnUm+VQwzqftqoD3pI5tGaXjEQxrN1+LCteLHB5rABbVDMczilNz+Bg3KlX/pmUhqtrQvXMVHXBs2WuxklL9ibBGzQGTs9nyHNDI/wNs16cSz/QD2KUnEZajzDRChM+Riu6okeJA0JcyRI1Dae2FUf Generated-by-Nova"
	},
	"project_id": "678e269fc36d498586223d3d72fe694a",
	"name": "RHEL"
}

Afterwards, cloud-init performs the required configuration:

cat /var/log/cloud-init.log
(text is omitted)
2019-01-11 14:14:25,835 - cc_set_hostname.py[DEBUG]: Setting the hostname to rhel (rhel)
2019-01-11 14:14:25,835 - util.py[DEBUG]: Running command ['hostnamectl', 'set-hostname', 'rhel'] with allowed return codes [0] (shell=False, capture=True)
(text is omitted)
2019-01-11 14:14:27,727 - stages.py[DEBUG]: Running module ssh-authkey-fingerprints (<module 'cloudinit.config.cc_ssh_authkey_fingerprints' from '/usr/lib/python2.7/site-packages/cloudinit/config/cc_ssh_authkey_fingerprints.pyc'>) with frequency once-per-instance
2019-01-11 14:14:27,728 - handlers.py[DEBUG]: start: modules-final/config-ssh-authkey-fingerprints: running config-ssh-authkey-fingerprints with frequency once-per-instance
2019-01-11 14:14:27,728 - util.py[DEBUG]: Writing to /var/lib/cloud/instances/380cc09f-25ce-4a4e-aa88-bba53234d92e/sem/config_ssh_authkey_fingerprints - wb: [420] 20 bytes
2019-01-11 14:14:27,729 - util.py[DEBUG]: Restoring selinux mode for /var/lib/cloud/instances/380cc09f-25ce-4a4e-aa88-bba53234d92e/sem/config_ssh_authkey_fingerprints (recursive=False)
2019-01-11 14:14:27,730 - util.py[DEBUG]: Restoring selinux mode for /var/lib/cloud/instances/380cc09f-25ce-4a4e-aa88-bba53234d92e/sem/config_ssh_authkey_fingerprints (recursive=False)
2019-01-11 14:14:27,730 - helpers.py[DEBUG]: Running config-ssh-authkey-fingerprints using lock (<FileLock using file '/var/lib/cloud/instances/380cc09f-25ce-4a4e-aa88-bba53234d92e/sem/config_ssh_authkey_fingerprints'>)
2019-01-11 14:14:27,731 - util.py[DEBUG]: Reading from /etc/ssh/sshd_config (quiet=False)
2019-01-11 14:14:27,731 - util.py[DEBUG]: Read 3906 bytes from /etc/ssh/sshd_config
2019-01-11 14:14:27,733 - util.py[DEBUG]: Restoring selinux mode for /home/cloud-user/.ssh (recursive=True)
2019-01-11 14:14:27,733 - util.py[DEBUG]: Reading from /home/cloud-user/.ssh/authorized_keys (quiet=False)
2019-01-11 14:14:27,733 - util.py[DEBUG]: Read 399 bytes from /home/cloud-user/.ssh/authorized_keys
2019-01-11 14:14:27,760 - handlers.py[DEBUG]: finish: modules-final/config-ssh-authkey-fingerprints: SUCCESS: config-ssh-authkey-fingerprints ran successfully
(text is omitted)

Userdata

cloud-init is extended via modules [12], each module exposes different capabilities.
Besides the regular actions that cloud-init does(which are mentioned above), users can pass additional configuration requests using modules.

Nova Metadata Service hosts user_data file which contains additonal configuration provided by the user.
user_data file may contain a bash script, python script or cloud-init modules:

curl http://169.254.169.254/openstack/latest/user_data
#cloud-config
user: cloud-user
password: cloud-password
chpasswd: {expire: False}
ssh_pwauth: True
disable_root: 0

This user_data will perform the following configuration:

  • Creates the user cloud-user with the password cloud-password
  • Copy SSH keys provided at instance spawning to cloud-user and root
  • Will set that password never expires for cloud-user
  • Will allow to log via ssh using passwords
  • Will enable to connect to user root remotely

In the background cloud-init will apply this configuration:

cat /var/log/cloud-init.log
(text is omitted)
2019-01-11 14:14:25,885 - __init__.py[DEBUG]: Adding user cloud-user
2019-01-11 14:14:25,885 - util.py[DEBUG]: Running hidden command to protect sensitive input/output logstring: ['useradd', 'cloud-user', '--comment', 'Cloud User', '--groups', 'adm,systemd-journal,wheel', '--shell', '/bin/bash', '-m']
2019-01-11 14:14:26,079 - util.py[DEBUG]: Running command ['passwd', '-l', 'cloud-user'] with allowed return codes [0] (shell=False, capture=True)
2019-01-11 14:14:26,151 - util.py[DEBUG]: Reading from /etc/sudoers (quiet=False)
2019-01-11 14:14:26,169 - util.py[DEBUG]: Read 3973 bytes from /etc/sudoers
2019-01-11 14:14:26,170 - util.py[DEBUG]: Restoring selinux mode for /etc/sudoers.d (recursive=False)
2019-01-11 14:14:26,170 - util.py[DEBUG]: Writing to /etc/sudoers.d/90-cloud-init-users - wb: [288] 131 bytes
2019-01-11 14:14:26,171 - util.py[DEBUG]: Restoring selinux mode for /etc/sudoers.d/90-cloud-init-users (recursive=False)
2019-01-11 14:14:26,171 - util.py[DEBUG]: Restoring selinux mode for /etc/sudoers.d/90-cloud-init-users (recursive=False)
2019-01-11 14:14:26,171 - handlers.py[DEBUG]: finish: init-network/config-users-groups: SUCCESS: config-users-groups ran successfully
2019-01-11 14:14:26,172 - stages.py[DEBUG]: Running module ssh (<module 'cloudinit.config.cc_ssh' from '/usr/lib/python2.7/site-packages/cloudinit/config/cc_ssh.pyc'>) with frequency once-per-instance
2019-01-11 14:14:26,172 - handlers.py[DEBUG]: start: init-network/config-ssh: running config-ssh with frequency once-per-instance
2019-01-11 14:14:26,172 - util.py[DEBUG]: Writing to /var/lib/cloud/instances/380cc09f-25ce-4a4e-aa88-bba53234d92e/sem/config_ssh - wb: [420] 19 bytes
2019-01-11 14:14:26,173 - util.py[DEBUG]: Restoring selinux mode for /var/lib/cloud/instances/380cc09f-25ce-4a4e-aa88-bba53234d92e/sem/config_ssh (recursive=False)
2019-01-11 14:14:26,173 - util.py[DEBUG]: Restoring selinux mode for /var/lib/cloud/instances/380cc09f-25ce-4a4e-aa88-bba53234d92e/sem/config_ssh (recursive=False)
2019-01-11 14:14:26,173 - helpers.py[DEBUG]: Running config-ssh using lock (<FileLock using file '/var/lib/cloud/instances/380cc09f-25ce-4a4e-aa88-bba53234d92e/sem/config_ssh'>)
2019-01-11 14:14:26,174 - util.py[DEBUG]: Restoring selinux mode for /home/cloud-user (recursive=True)
2019-01-11 14:14:26,175 - util.py[DEBUG]: Restoring selinux mode for /home/cloud-user/.ssh (recursive=False)
2019-01-11 14:14:26,175 - util.py[DEBUG]: Changing the ownership of /home/cloud-user/.ssh to 1000:1000
2019-01-11 14:14:26,192 - util.py[DEBUG]: Reading from /etc/ssh/sshd_config (quiet=False)
2019-01-11 14:14:26,199 - util.py[DEBUG]: Read 3907 bytes from /etc/ssh/sshd_config
2019-01-11 14:14:26,199 - util.py[DEBUG]: Restoring selinux mode for /home/cloud-user/.ssh (recursive=True)
2019-01-11 14:14:26,199 - util.py[DEBUG]: Restoring selinux mode for /home/cloud-user/.ssh (recursive=False)
2019-01-11 14:14:26,200 - util.py[DEBUG]: Writing to /home/cloud-user/.ssh/authorized_keys - wb: [384] 399 bytes
2019-01-11 14:14:26,200 - util.py[DEBUG]: Restoring selinux mode for /home/cloud-user/.ssh/authorized_keys (recursive=False)
2019-01-11 14:14:26,200 - util.py[DEBUG]: Restoring selinux mode for /home/cloud-user/.ssh/authorized_keys (recursive=False)
2019-01-11 14:14:26,200 - util.py[DEBUG]: Changing the ownership of /home/cloud-user/.ssh/authorized_keys to 1000:1000
2019-01-11 14:14:26,201 - util.py[DEBUG]: Restoring selinux mode for /home/cloud-user/.ssh (recursive=True)
2019-01-11 14:14:26,223 - util.py[DEBUG]: Restoring selinux mode for /root (recursive=True)
2019-01-11 14:14:26,244 - util.py[DEBUG]: Restoring selinux mode for /root/.ssh (recursive=False)
2019-01-11 14:14:26,245 - util.py[DEBUG]: Changing the ownership of /root/.ssh to 0:0
2019-01-11 14:14:26,245 - util.py[DEBUG]: Reading from /etc/ssh/sshd_config (quiet=False)
2019-01-11 14:14:26,245 - util.py[DEBUG]: Read 3907 bytes from /etc/ssh/sshd_config
2019-01-11 14:14:26,245 - util.py[DEBUG]: Restoring selinux mode for /root/.ssh (recursive=True)
2019-01-11 14:14:26,246 - util.py[DEBUG]: Restoring selinux mode for /root/.ssh (recursive=False)
2019-01-11 14:14:26,246 - util.py[DEBUG]: Writing to /root/.ssh/authorized_keys - wb: [384] 399 bytes
2019-01-11 14:14:26,246 - util.py[DEBUG]: Restoring selinux mode for /root/.ssh/authorized_keys (recursive=False)
2019-01-11 14:14:26,246 - util.py[DEBUG]: Restoring selinux mode for /root/.ssh/authorized_keys (recursive=False)
2019-01-11 14:14:26,246 - util.py[DEBUG]: Changing the ownership of /root/.ssh/authorized_keys to 0:0
2019-01-11 14:14:26,247 - util.py[DEBUG]: Restoring selinux mode for /root/.ssh (recursive=True)
2019-01-11 14:14:26,247 - handlers.py[DEBUG]: finish: init-network/config-ssh: SUCCESS: config-ssh ran successfully
(text is omitted)
2019-01-11 14:14:27,035 - cc_set_passwords.py[DEBUG]: Changing password for ['cloud-user']:
2019-01-11 14:14:27,035 - util.py[DEBUG]: Running command ['chpasswd'] with allowed return codes [0] (shell=False, capture=True)
2019-01-11 14:14:27,086 - util.py[DEBUG]: Reading from /etc/ssh/sshd_config (quiet=False)
2019-01-11 14:14:27,086 - util.py[DEBUG]: Read 3907 bytes from /etc/ssh/sshd_config
2019-01-11 14:14:27,087 - cc_set_passwords.py[DEBUG]: Replacing auth line 65 with yes
2019-01-11 14:14:27,088 - util.py[DEBUG]: Writing to /etc/ssh/sshd_config - wb: [384] 3906 bytes
2019-01-11 14:14:27,088 - util.py[DEBUG]: Restoring selinux mode for /etc/ssh/sshd_config (recursive=False)
2019-01-11 14:14:27,089 - util.py[DEBUG]: Restoring selinux mode for /etc/ssh/sshd_config (recursive=False)
2019-01-11 14:14:27,090 - util.py[DEBUG]: Running command ['service', 'sshd', 'restart'] with allowed return codes [0] (shell=False, capture=True)
2019-01-11 14:14:27,243 - cc_set_passwords.py[DEBUG]: Restarted the ssh daemon
2019-01-11 14:14:27,244 - handlers.py[DEBUG]: finish: modules-config/config-set-passwords: SUCCESS: config-set-passwords ran successfully
(text is omitted)

Summary

cloud-init and Nova Metadata Service work together in order to assure a successful spawn of an instance.

Instance customization is allowed due to modularity and robustness of cloud-init.

Every action is logged to disk and can be viewed later when required.


  1. cloud-init Read the Docs ↩︎

  2. OpenStack Documentation - Nova Metadata Service Admin Guide ↩︎

  3. OpenStack Documentation - Get images ↩︎

  4. cloud-init Documentation - Boot Stages ↩︎

  5. cloud-init Documentation - What is a datasource ↩︎

  6. cloud-init Documentation - Datasource Documentation ↩︎

  7. OpenStack Documentation - Enable and access the configuration drive ↩︎

  8. nfv-tempest-plugin GitHub repo - custom_net_config.py ↩︎

  9. /var/log/cloud-init.log sample with OpenStack datasource ↩︎

  10. cloud-init GitHub repo - rhel.py ↩︎

  11. DigitalOcean's website ↩︎

  12. cloud-init Documentation - Modules ↩︎