Cloud init basics
Introduction
Cloud-init (CI) is a multi-platform package responsible for the initial setup of VPS. Support for CI can be found in most popular OS or cloud providers. It significantly facilitates automation and initial deployment. It is also used for personalizing VPS with different providers.
Some of the basic configurations that we want to have established during the initial VPS deployment include automatic configuration of everything that is difficult to do while the machine is running:
- IP addresses
- DNS
- Hostname
- Hosts
- SSH keys
- User login and passwords
Some OS developers provide pre-prepared CI images, such as Canonical, which develops the Ubuntu OS. Here, we will show how to deploy CI on Proxmox, which is one of the few hypervisors that is well-prepared for CI deployment.
How and what to install?
There is a philosophical divide between what should be included in the image from the beginning and what should be added through some orchestration after the VPS deployment. In other words, if we have a new OS, does it make sense to install a set of packages, or is it better to send them to the VPS later, for example, through Ansible? The answer to this depends on how often changes occur. For example, if we install a Zabbix agent on a CI VPS, we can let CI update all packages every time we deploy the VPS, which can lead to version discrepancies over time. For some versions, this does not matter. The vim/nano package is not so important to have the same version everywhere, but such an environment is not desirable in production either. This can be solved in two ways:
- In CI, we set that we want to install a specific version of a tool after deploying the VPS, and then we set it so that after the second reboot of the VPS, apt-upgrade is no longer initialized automatically.
- We will not solve the installation of packages through CI, and after deploying the VPS, we will install them via Ansible using a playbook we create.
However, if you are an advanced company, you have already implemented the process of patching servers, which means that if you deploy a new VPS with the latest package set, you will likely quickly align the rest of the infrastructure with these packages because you want to have everything in the infrastructure properly unified. Otherwise, you will incur technical debt, which is very dangerous in the IT field. Of course, version deployment and patch management are relative. Insignificant packages for work, such as vim, nano, etc., can be compared on all servers and probably will not affect production. However, comparing versions of PHP, MariaDB may not be agreeable to developers, and extra care must be taken to avoid breaking production. Such packages do not make sense to have in CI, but it makes sense to orchestrate them.
Cloud-init on Proxmox
Preparation of CI image
CI image preparation can be done in two ways:
- Downloading a ready-made CI image
- Creating a custom CI
The basis for preparing a CI image is to create a clean OS installation, which we will then mount via the cloud-init CDROM button in PVE.
Deploying a ready-made Ubuntu image
Deploying a ready-made image is easy, and we will just copy the material from the documentation for demonstration purposes. We log in to our PVE and proceed as follows:
# Download CI image Ubuntu
wget https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img
# Create a new VM in PVE
qm create 900 --memory 2048 --net0 virtio,bridge=vmbrX
# Import the downloaded disc
qm importdisk 900 bionic-server-cloudimg-amd64.img nazevPole
# Add discs to the CI image
qm set 900 --scsihw virtio-scsi-pci --scsi0 storage:vm-900-disk-1.raw
# Add to CI a CDROM
qm set 999 --ide2 local-lvm:cloudinit
# We shall allow booting from the CI image
qm set 999 --boot c --bootdisk scsi0
# Wa can or can not configure the console
qm set 999 --serial0 socket --vga serial0
# Make a template
qm template 999
The template can be easily cloned and additional configurations can be added to it.
# Do a clone
qm clone 999 123 --name MyNameOfCI
# Add IP, SSH key
qm set 123 --sshkey ~/.ssh/id_rsa-ansible.pub
qm set 123 --ipconfig0 ip=1.1.1.2/24,gw=1.1.1.1
From this template, we obtain a clean machine that is minimal in size (up to 3GB) and ready for work. It has only one partition (either sda or vda) by default.
The machine in default settings also has a DHCP client on the Ethernet port. Here is an example of what /etc/network/interfaces
looks like. There is only one directive there on the last line. If we want to assign a static IP from CI to the machine, we just need to comment out the information about Ethernet interfaces and their DHCP clients.
</code> <code>root@127connectica:/etc/network# cat interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).
# The loopback network interface
auto lo
iface lo inet loopback
# The normal eth0
allow-hotplug eth0
iface eth0 inet dhcp
#
## Additional interfaces, just in case we're using
## multiple networks
allow-hotplug eth1
iface eth1 inet dhcp
#
allow-hotplug eth2
iface eth2 inet dhcp
# Set this one last, so that cloud-init or user can
# override defaults.
source /etc/network/interfaces.d/*
Here is an example of
</code> <code></code><code>/etc/network/interfaces.d/50-cloud-init</code> <code>
root@127connecica1:/etc/network/interfaces.d# cat 50-cloud-init
# This file is generated from information provided by the datasource. Changes
# to it will not persist across an instance reboot. To disable cloud-init's
# network configuration capabilities, write a file
# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following:
# network: {config: disabled}
auto lo
iface lo inet loopback
dns-nameservers 2.2.2.2 8.8.8.8
dns-search cnnc.local
auto eth0
iface eth0 inet static
address 1.1.1.1.2/24
gateway 1.1.1.1
The default configuration of GRUB in /etc/default/grub
is much more interesting. This is desirable to change with orchestration.
root@127connectica1:~# cat /etc/default/grub
# If you change this file, run 'update-grub' afterwards to update
# /boot/grub/grub.cfg.
# For full documentation of the options in this file, see:
# info -f grub -n 'Simple configuration'
GRUB_DEFAULT=0
GRUB_TIMEOUT=0
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="nosplash text biosdevname=0 net.ifnames=0 console=tty0 console=ttyS0,115200 earlyprintk=ttyS0,115200 consoleblank=0 systemd.show_status=true "
GRUB_CMDLINE_LINUX=""
# Uncomment to enable BadRAM filtering, modify to suit your needs
# This works with Linux (no patch required) and with any kernel that obtains
# the memory map information from GRUB (GNU Mach, kernel of FreeBSD ...)
#GRUB_BADRAM="0x01234567,0xfefefefe,0x89abcdef,0xefefefef"
# Uncomment to disable graphical terminal (grub-pc only)
#GRUB_TERMINAL=console
# The resolution used on graphical terminal
# note that you can use only modes which your graphic card supports via VBE
# you can see them in real GRUB with the command `vbeinfo'
#GRUB_GFXMODE=640x480
# Uncomment if you don't want GRUB to pass "root=UUID=xxx" parameter to Linux
#GRUB_DISABLE_LINUX_UUID=true
# Uncomment to disable generation of recovery mode menu entries
#GRUB_DISABLE_RECOVERY="true"
# Uncomment to get a beep at grub start
#GRUB_INIT_TUNE="480 440 1"
# openstack-debian-images disables OS prober to avoid
# loopback detection which breaks booting
GRUB_DISABLE_OS_PROBER=true
Creating your own CI on Debian 10
To create a custom cloud-init on Debian 10, we perform a clean installation of Debian and install only one main partition of 5 GB. Here is a nice video:
We create a root user with any password, which we later overwrite with cloud-init. We can also configure swap later using the mounts module in CI. We can install our basic packages for work, edit and modify GRUB, remove all users except root, install cloud-init support, and clean logs, temps, and keys in /etc/ssh/
. Lets go step by step together
Update and upgrade
apt update
apt-upgrade
apt dist-upgrade
Installation of favourite packages
We ca install the manually: </code> <code>apt install htop mc sysstat vim nano qemu-guest-agent</code> <code>
or we can edit the CI config:
package_upgrade: true
packages:
- htop
- mc
- sysstat
- vim
- nano
- qemu-guest-agent
GRUB editation
Lets edit the GRUB
# Open the file
vim /etc/default/grub
GRUB_DEFAULT=0
GRUB_TIMEOUT=0
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="quiet"
GRUB_CMDLINE_LINUX="net.ifnames=0 biosdevname=0"
# update grub
update-grub
Delete everybody except root and add CI support
We shall leave only the root user
</code> <code></code><code>userdel -r SomeRandomDefaultUser</code> <code>
and then we shall install CI support with
</code> <code></code><code>apt install cloud-init</code> <code>
CI is very general, so we configure it by commenting out what we consider unnecessary. More details can be found the the documentation
Here are a few tips for the beginning what to comment out:
# - disable-ec2-metadata
# - byobu
# - fan
# - puppet
# - chef
# - salt-minion
# - mcollective
Here is a CI config example that is in /etc/cloud/cloud.cfg
# The top level settings are used as module
# and system configuration.
# A set of users which may be applied and/or used by various modules
# when a 'default' entry is found it will reference the 'default_user'
# from the distro configuration specified below
users:
- default
# If this is set, 'root' will not be able to ssh in and they
# will get a message to login instead as the above $user (debian)
disable_root: true
# This will cause the set+update hostname module to not operate (if true)
preserve_hostname: false
# This preverts apt/sources.list to be updated at boot time, which
# may be annoying.
apt_preserve_sources_list: true
# Example datasource config
# datasource:
# Ec2:
# metadata_urls: [ 'blah.com' ]
# timeout: 5 # (defaults to 50 seconds)
# max_wait: 10 # (defaults to 120 seconds)
# The modules that run in the 'init' stage
cloud_init_modules:
- migrator
- seed_random
- bootcmd
- write-files
- growpart
- resizefs
- disk_setup
- mounts
- set_hostname
- update_hostname
- update_etc_hosts
- ca-certs
- rsyslog
- users-groups
- ssh
# The modules that run in the 'config' stage
cloud_config_modules:
# Emit the cloud config ready event
# this can be used by upstart jobs for 'start on cloud-config'.
- emit_upstart
- ssh-import-id
- locale
- set-passwords
- grub-dpkg
- apt-pipelining
- apt-configure
- ntp
- timezone
# - disable-ec2-metadata
- runcmd
# - byobu
# The modules that run in the 'final' stage
cloud_final_modules:
- package-update-upgrade-install
#- fan
#- puppet
#- chef
#- salt-minion
#- mcollective
- rightscale_userdata
- scripts-vendor
- 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 and/or distro specific settings
# (not accessible to handlers/transforms)
system_info:
# This will affect which distro class gets used
distro: debian
# Default user name + that default users groups (if added/used)
default_user:
name: ansible
lock_passwd: True
gecos: Debian
groups: [adm, audio, cdrom, dialout, dip, floppy, netdev, plugdev, sudo, video]
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
shell: /bin/zsh
# Other config here will be given to the distro class and/or path classes
paths:
cloud_dir: /var/lib/cloud/
templates_dir: /etc/cloud/templates/
upstart_dir: /etc/init/
package_mirrors:
- arches: [default]
failsafe:
primary: http://deb.debian.org/debian
security: http://security.debian.org/
ssh_svcname: ssh
The topic of CI scripting is extensive, and we won’t delve into modifying parameters in this article. For our initial launch, this will suffice.
We will only mention that the configurations:
- scripts-per-once
- scripts-per-boot
are used to run a script either once or every time after reboot. The script can be stored in the directory:
</code> <code>/var/lib/cloud/scripts</code> <code>
.
├── per-boot
├── per-instance
├── per-once
└── vendor
Why would we want this? For example, the hostname variable in the Zabbix-agent configuration file (/etc/zabbix/zabbix-agent.conf
) is configured poorly after installation, and it needs to be modified post-ex via Ansible. We can think of additional scripts as if they were in cron and executed after reboot, either once or every time, as needed.
Editing GRUB
To achieve the naming convention of eth0, eth1, etc., we need to enter the parameter “net.ifnames=0 biosdevname=0” into GRUB.
# Edit GRUB file
vim /etc/default/grub
GRUB_DEFAULT=0
GRUB_TIMEOUT=0
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="quiet"
GRUB_CMDLINE_LINUX="net.ifnames=0 biosdevname=0"
# Update GRUB
update-grub
Cleaning the Machine
We clean logs, temps, history, and other clutter.
# Clear logs
rm /var/log/*.log.*
rm /var/log/apt/*.*
cat /dev/null > /var/log/btmp
cat /dev/null > /var/log/dmesg
/bin/cat /dev/null > /var/log/lastlog
/bin/cat /dev/null > /var/log/syslog
# Clear temps
rm –rf /tmp/*
rm –rf /var/tmp/*
# Clear SSH keys
rm –f /etc/ssh/*key*
# Clean apt
apt-get autoremove
apt-get autoclean
apt-get clean
# Clear BASH history
rm -f ~root/.bash_history
# Clear history
history -c
Before we finish, we should mention the existence of machine-id. This is a unique ID of the machine that serves to create a DHCP host identifier, GNOME saves some sessions using this ID, and the systemd-boot EFI bootloader saves installation directories of the kernel named using machine-id. Machine-id will not bother us if we assign static addresses to machines, which is desirable for servers. However, if we need a DHCP client for assigning addresses or have Ubuntu specifically, we need to leave empty directories for machine-id in the image.
/etc/machine-id
/var/lib/dbus/machine-id
and Create symlink </code> <code>ln -s /etc/machine-id /var/lib/dbus/machine-id</code> <code>
We have completed the image. It is much better than downloading a finished image. This way, we can better understand the CI issue. Now we turn off the machine, create a template from it, and set up cloud-init in PVE for the VM. For our simplicity, the example ID of our machine will be 100, and our disk will be named ssd.
# Add Cloud-init
qm set 100 --ide2 ssd:cloudinit
update VM 100: -ide2 ssd:cloudinit
Formatting '/mnt/pve/ssd/images/100/vm-100-cloudinit.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off preallocation=metadata compression_type=zlib size=4194304 lazy_refcounts=off refcount_bits=16
# Set VM to boot primarily from its main partition and not from cloud-init
qm set 100 --boot c --bootdisk scsi0
# Create a template from the machine
qm template 100
We then assign it manually:
- An IP address
- An SSH key for authorization
- A user to log in as
qm set 100 --sshkey ~/.ssh/id_rsa.pub
qm set 100 --ipconfig0 ip=1.2.3.4/24,gw=1.1.1.1
qm set 100 --ciuser root
The ciuser can be any user, not necessarily root. When you choose any user other than root as ciuser, you will not log in as root by default but as sudo under the new user. But when you choose root as ciuser, you will only get access to the machine as root.
Conclusion
By reading this, you have gained the necessary minimum for working with CI. The issue is extensive, and the possibilities for modification are much deeper than what we have covered here. For basic work, current information is more than sufficient. If you need deeper knowledge, we recommend studying the online documentation