Ubuntu's autoinstall allows for automated OS installations using a cloud-init YAML file, which is particularly useful for managing multiple servers or ensuring consistent setup with minimal manual input. Below, I describe my configuration, setup steps, and tips for efficient deployment. - Thanks ChatGPT
What I would like the process of installing a new system be like:
There are two main reasons I want to achieve a fully hands-off installation:
When any system Ubuntu system is installed using the Ubuntu installer, an autoinstall file for repeating the installation is created at /var/log/installer/autoinstall-user-data
. So in theroy you can install Ubuntu once, copy the file somewhere and the next time Ubuntu installs itselfe, right?
Well actually if it was that simple, I would not be writing this. Turns out my love for Arch Linux is actually based on something and my aversion to Ubuntu / Debian is somewhat based on some facts. But I do see how a rolling release distro could cause problems in a production environment and Ubuntu is kind of the go to, so here we go.
According to the official documentation the steps are as follows:
/var/log/installer/autoinstall-user-data
to the root of the installation mediaautoinstall.yaml
But at least in my case, this just crashes. Also it turns out, debugging this is almost impossible. In my experince there are 2 failure modes:
Ubuntu autoinstall is actually a great if it works. A few critical points to note: the autoinstall.yaml
file is strictly for answering all the questions typically asked by the graphical Ubuntu installer. This file also has to be placed in the root of the installation media, which is not practical in my opinion. Of course, there is a solution: Cloud-init
According to the official docs, Cloud-init is the industry-standard, cross-platform method for initializing cloud instances. It works across public cloud providers, provisioning systems, private cloud setups, and bare-metal installations. Here, Cloud-init becomes the tool that not only supplies our autoinstall configuration but also enables more advanced setup steps.
Here’s what I ended up with: a dedicated USB drive that delivers the autoinstall and other cloud-init configurations to the Ubuntu installer, allowing for flexible, reproducible installations.
There are actually not that many things that need to be done:
My entire configuration can be found here: https://github.com/Hetzter/autoinstall/
Some things that are good to know before getting into it:
The Ubuntu installer will still ask for confirmation to perform the install (before formatting the disk), even if a valid cloud-init config is provided. This is to prevent it from creating an installation loop. This prompt will look like this:
Continue with autoinstall? (yes|no)
This can be avoided by adding autoinstall
to the kernel commandline. Do this by pressing e on your keyboard during the GRUB screen.
Press F10 to save end exit.
If you are working on a computer running Linux with cloud-init installed, be sure to unplug the USB drive before rebooting. My computer just won't start if the USB is plugged in.
Let's quickly mention something about a feasable test environment. I dont think anyone would like to be installing an OS on bare metal just for it to fail. I found a KVM Virtual Maschine a good way to quickly test the latest configuration. Pass through the cloud-init USB drive in the options, then it is just like if the USB drive would be plugged into the physical maschine.
The Ubuntu installer is as standard as it comes. The default Ubuntu installer actually comes with all the necessary dependencies to run cloud-init on the first boot. Nothing has to be done other than provide the cloud-init data. Download an ISO, in my case the latest build of ubuntu-server24.04
. Write it to a spare USB drive.
This can be any old USB drive, the following details what should be done to it.
This USB drive has to be formatted in FAT32
and named CIDATA
. I am not entirely sure why this is but if memory searves I remember reading that cloud-init looks at a few places to find its data. One of those is a USB drive named CIDATA
.
First, figure out the device lable? of the USB drive. There are a number of ways this can be done, use lsblk
, fdisk
, parted
or even lsusb
at hearts content. Mine will be /dev/sdc
lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 931,5G 0 disk
├─sda1 8:1 0 931,5G 0 part
└─sda9 8:9 0 8M 0 part
sdb 8:16 0 931,5G 0 disk
├─sdb1 8:17 0 931,5G 0 part
└─sdb9 8:25 0 8M 0 part
sdc 8:32 1 7,5G 0 disk
Format the USB drive:
mkfs.fat -I -F 32 -n 'CIDATA' /dev/sdc
Note:
Cloud-init expects to files to be present in the root of the USB drive: meta-data
and user-data
, both files with no extension.
ls -l CIDATA
total 4
-rw-r--r-- 1 max max 0 Okt 24 21:27 meta-data
-rw-r--r-- 1 max max 1814 Okt 25 20:20 user-data
Those files should be present.
Now we can actually get into configuring cloud-init.
This is also not really complex, at least for my setup. But there are a few things to be weary about. The meta-data
file can remain empty, not sure what it does but it has to be present. Read the docs if you want to know. Open and edit user-data
(Yaml syntax, yay).
You need to begin the file with #cloud-config
. Now, the autoinstall
section can be configured with the default Ubuntu autoinstall options. Accodring to the docs, version
has to be specified (there is only 1). In addition, either identity
or user-data
have to be set.
#cloud-config
autoinstall:
version: 1
identity:
realname: 'Ubuntu User'
username: ubuntu
password: '$6$wdAcoXrU039hKYPd$508Qvbe7ObUnxoj15DRCkzC3qO7edjH0VV7BPNRDYK4QR8ofJaEEF2heacn0QgD.f8pO8SNp83XNdWG6tocBM1'
hostname: ubuntu
identity is the autoinstall way of providing a user to the installer
Note:
identity
with user-data
later. user-data
is the cloud-init way to provide a user.There are a whole bunch of things that can be done, here is what I did and found to be working. The following are only the most important changes I made, for the whole file, visit https://github.com/Hetzter/autoinstall/
My computers will only be having a small and single SSD. The default behaviour of the Ubuntu installer is to leave some space on the disk for snapshots. I don't wan't this as the entire idea of this configuration is to make the host replaceable.
storage:
swap:
size: 0
layout:
name: lvm
sizing-policy: all
swap
size
: Sets the amount of available swap. As I wan't to use this as Kubernetes in the future, I have to turn swap off. Kubernetes does not currently support swap.layout
name
: Can be lvm
, direct
, zfs
sizing-policy
: Can be scaled
, all
Note:
sizing-policy: all
uses the remaining disk space for the root partitionsizing-policy: scaled
adjusts the remaining space like documentedI wan't Ubuntu Server with the bare minimum of bloat. Ubuntu provides an option to select this called source
.
source:
search_drivers: true
id: ubuntu-server-minimal
Enable dhcp for IPv4 and IPv6
network:
ethernets:
enp1s0:
dhcp4: true
dhcp6: true
This option dictates what happens if the installer finishes, I wan' the computer to restart.
shutdown: reboot
Enable an ssh server and disallow password login.
ssh:
allow-pw: false
install-server: true
Note:
user-data
Allows one to install a couple of packages into the finished installed operating system. As the ubuntu-server-minimal
version really is rather bare, I will be installing some tools.
packages:
- vim
- git
- tmux
This section is not native to the autoinstall provided by Ubuntu but is provided by cloud-init. Documentation for the entire thing can be found here. There is a lot that can be done, I wan't the following things:
user-data:
disable_root: true
timezone: Europe/Vienna
package_upgrade: false
users:
- name: ubuntu
gecos: Default
passwd: $6$pKNepoJrO6lUgl9/$ejlDAry.6Q6.WjQ1qWfjbqNt7DMH3cd2RSHlYi/Qn0gCK/ybuNTe7e8J8e48bjjQ9sDMeBcZXrqwBQRFvVMRi1
shell: /bin/bash
groups: adm, cdrom, dip, lxd, plugdev, sudo
lock_passwd: false
ssh_authorized_keys:
- ssh-ed25519 YOUR (PUBLIC)KEY HERE you@yourname
sudo: ALL=(ALL) NOPASSWD:ALL
- name: ansible
gecos: Ansible User
groups: users,admin,wheel
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
lock_passwd: true
ssh_authorized_keys:
- ssh-ed25519 YOUR OTHER (PUBLIC)KEY HERE you@yourname
Note:
This is a way to execute commands after the installation is complete. The newly installed filesystem will be mounted at /target
. You can also use curtin in-target -- $shell_command
to execute a command in the target file system.
What I wanted to do:
server-XXXX
Sounds simple, right? Just echo a few things into /etc/hostname
and /etc/issue
. This infact is the step I had the most issues with. To name a few:
\n
, \n\r
, EOL
...) are not honouredDue to those restriction, I ended up with this:
late-commands:
- sudo bash -c "echo 'server-$(openssl rand -hex 2)' > /target/etc/hostname"
- sudo bash -c "echo 'Host - $(cat /target/etc/hostname)' > /target/etc/issue"
- sudo bash -c "echo 'Kernel - $(uname -sr)' >> /target/etc/issue"
- sudo bash -c "echo 'IP - $(hostname -I)' >> /target/etc/issue"
- sudo bash -c "echo '' >> /target/etc/issue"
You might have nothiced that there still are some manual steps, for example putting autoinstall
in the kernel commandline. There actually is a solution to that. Follow this guide if that is of interest to you: https://www.jimangel.io/posts/automate-ubuntu-22-04-lts-bare-metal/
For me personally, I will still have to change the boot priority of the computer in the BIOS so a keyboard has to be attached anyways.