Cloud-init
Cloud-init is a program that configures a guest virtual machine when it first boots based on configuration data that's supplied to it via network or a storage volume. It is capable of setting things like the guest's network configuration (IP addresses, routes, resolvers), account credentials and SSH authorized keys.
Building Cloud-Init images
Linux images that support Cloud-Init should have the Cloud-Init package installed and enabled on start up.
| Distro | Commands |
|---|---|
| RHEL / Rocky Linux / CentOS | ## Cloud Init
# yum -y install cloud-init cloud-utils-growpart gdisk
# systemctl enable cloud-init.service
|
| Alpine Linux | ## Cloud Init
# apk add cloud-init
## Start Cloud-Init on Boot
# rc-update add cloud-init default
# rc-update add cloud-init-local default
# rc-update add cloud-config default
# rc-update add cloud-final default
|
Default user
The default username for the non-super user account is 'cloud-user'. Some distros use a different default user such as 'alpine' for Alpine Linux or 'ubuntu' for Ubuntu. If you wish to override this username, you need to edit the cloud.cfg configuration file. For my Rocky Linux image, I ran the following to change the default username.
## The default user is 'cloud-user'. we'll change this to 'rocky'.
# sed -i "s/name: cloud-user/name: rocky/" /etc/cloud/cloud.cfg
Do not create the default user account
I noticed that some Packer builds create the default user account as part of the initial image set up. Don't do this. It's intended for the username to be overridden by the initial VM cloud-init configuration. Cloud-Init will take care of the user account creation, SSH authorized key setup, and also setting up the "Please login as the user X rather than the user Y" message on the root account.The default user has their password locked, thereby requiring authentication using SSH keys. You may change this by modifying cloud.cfg as well:
# sed -i "s/lock_passwd: true/lock_passwd: false/" /etc/cloud/cloud.cfg
Hypervisor support
Proxmox
Proxmox supports cloud-init using either the NoCloud (v1, used for Linux based guest OSes) or ConfigDrive (v2, used for Windows based guest OSes) data sources. To learn more about different Cloud-Init data sources, see https://cloudinit.readthedocs.io/en/latest/topics/datasources.html#known-sources.
A NoCloud configuration data is passed to the guest using a 4MB CD-ROM image. If you are creating a new VM, you must attach this CD-ROM device under 'Hardware' -> 'Add' -> 'CloudInit Drive'. Any changes made to the Cloud-Init settings will require this CD-ROM image to be regenerated.
When building images for use with Proxmox, it's recommended to add the following cloud.cfg.d file to restrict the data sources to only the supported options. Otherwise, you may have the VM try downloading the configs using some other method until it times out.
# /etc/cloud/cloud.cfg.d/99_pve.cfg
datasource_list: [ NoCloud, ConfigDrive ]
CloudStack
CloudStack has its own data source type.
# cat <<EOF > /etc/cloud/cloud.cfg.d/99_cloudstack.cfg
datasource:
CloudStack: {}
None: {}
datasource_list:
- CloudStack
EOF
Issues:
- I can't get the root password unlocked and set. Account is always locked even with the following:
#cloud-config
users:
- name: root
lock_passwd: false
inactive: false
password: $1$ADUODeAy$Tp1Tp9Sqb42DV3ux2it1L.
Cloud Configs
See: https://cloudinit.readthedocs.io/en/latest/topics/examples.html
Modules: https://cloudinit.readthedocs.io/en/latest/topics/modules.html
Example cloud-config snippets
Here are some cloud config snippets. You can combine any number of these into a single file.
Create a new user
Create a new user in the wheel group. The passwd hash can be obtained by running 'openssl passwd -1 -salt CUSTOMSALT'.
#cloud-config
users:
- name: leo
lock_passwd: false
inactive: false
gecos: Leo Leung
primary_group: leo
groups: users, wheel
passwd: $1$ADUODeAy$eCJ1lPSxhSGmSvrmWxjLC1
Enable the root account and set a root password
Most VM templates disable the root account. To enable the root account and set the root password:
#cloud-config
disable_root: false
chpasswd:
list:
- root:password
expire: false
Install specific packages
You can add additional packages or perform system updates when the system first comes up.
#cloud-config
packages:
- tmux
- git
package_update: true
package_upgrade: true
Create a file
Use the write_files module to create files. You can use other encodings like base64 as well.
#cloud-config
write_files:
- path: /hello
content: |
Hello CloudInit
permissions: '0644'
Resize the root LVM volume on startup
Assuming that your LVM is the 3rd partition, you could do something like this:
#cloud-config
write_files:
- path: /usr/bin/expand_lvm_root
content: |
#!/bin/bash
/usr/bin/growpart /dev/vda 3
/usr/sbin/pvresize -y -q /dev/vda3
/usr/sbin/lvresize -y -q -r -l +100%FREE /dev/mapper/*root
/usr/sbin/xfs_growfs /
permissions: '0700'
runcmd:
- /usr/bin/expand_lvm_root
POST data when done booting
Cloud Init can POST to a web server once the system is ready. This could be useful to automate an Ansible call on the new VM, for example.
#cloud-config
phone_home:
url: http://webhook-server/$INSTANCE_ID/
post: [ pub_key_dsa, pub_key_rsa, pub_key_ecdsa, instance_id ]
tries: 10
Troubleshooting
Cloud-Init isn't setting the static IP address
On Alpine Linux, Cloud-Init runs but the VM is still obtaining the IP address via DHCP.
After much debugging, I believe the issue comes down to the networking service starting before cloud-init. As a result, the network configs in /etc/network/interfaces isn't being written to by Cloud-Init in time and the networking service uses DHCP. The fix here is to make networking start after cloud-init. To do this, remove the networking service from the boot runlevel and re-add it to the default runlevel. Because 'networking' comes after 'cloud-init', cloud-init should run before networking.
As part of my packer provisioning script, I added the following lines:
## Start ntworking after cloudinit?
# rc-update del networking boot
# rc-update add networking default