The problem

Ubuntu modern auto-installation is not a perfectly documented system. Some features also appear to be broken, so these examples rely on extensive use of user-data passed directly into cloud-init for things that are advertised to work as actual autoinstall settings.

This page seeks to provide some filler to the gaps in documentation encountered when I started down the path of hands-off server provisioning into a traditional VMware vSphere environment on par with what is done for RHEL.

The main example also provides thorough example of a LVM deployment more common in the Enterprise Linux ecosystem than what is typically found on Ubuntu deployments.

For those coming from Enterprise Linux variants

Anaconda & Kickstart, as well as their documentation, are far more feature-complete, usable, and thorough than what you will find for Ubuntu. While you can technically use Kickstart for Ubuntu 18.04 LTS and Ubuntu 20.04 LTS, it’s obvious that Canonical wish to phase this out, and I wouldn’t count on the possibility in Ubuntu 22.04 LTS. For more information about legacy kickstart deployments of Ubuntu, please check out this handy GitHub repository: vrillusions/ubuntu-kickstart

For folks coming from the Enterprise Linux world, and new to Ubuntu Autoinstall and Cloud-Init, here are some quick hits:

  • Cloud-Init: A software system for passing OS customization to freshly instantiated systems. The goal is a standard configuration abstraction to configure system on any cloud, hypervisor, or bare metal. 3 YAML files are fed to cloud-init from a web server or metadata server depending on where your VM is being instantiated. The most important file is user-data, which is where your customizations will live. The other two files, vendor-data and meta-data are used by cloud providers to pass along environment-specific data.

  • Ubuntu Autoinstall : A further abstraction of Ubuntu-isms that complement Cloud-Init. Think of this as the Kickstart file.

  • Subiquity: The Ubuntu installer, Canonical’s equivalent of Anaconda. The docs aren’t stellar, so I’ve instead linked a good blog post covering the topic.

  • Curtin: The Ubuntu / Cloud-Init disk management and partitioning system.

Helpful feature: Much like Anaconda, Subiquity also provides an Autoinstall file after a manual installation, capturing all of the settings you interactively used, which can be used as a starting point in future deployments.
Instead of /root/anaconda-ks.cfg, you’ll need to pluck this from /var/log/installer/autoinstall-user-data.

Boot args to pull in Autoinstall file

Assuming that you’ve published your autoinstall user-data file on a web server at http://kickstart-server.college.edu/autoinstall/, you can use this kernel boot argument to ensure that Subiquity knows where to pull it from:

1
autoinstall ds=nocloud-net;s=http://kickstart-server.college.edu/autoinstall/

In Grub, use e to edit, insert in the linux line prior to the 3 hyphens, like so:

linux /casper/vmlinuz quiet autoinstall ds=nocloud-net\;s=http://kickstart-server.college.edu/autoinstall/ ---

Static IP deployments

Since Cloud-Init and Autoinstall are focused on “cloud” deployments, all documentation and examples seem to take for granted that you have a fully dynamic environment using DHCP and dynamic hostnames. Since neither of these are true or ideal for more traditional vSphere (“pets not cattle”) environments, I’ve focused on providing examples that expect static IPs and DNS hostnames. The key for static IP deployments is declaring the IP details on the kernel command line, which will be carried forward into the Autoinstall.

In Ubuntu 20.04 LTS, the kernel command line should look somewhat familiar to EL7+ kernel command line IP settings:

1
ip=192.168.122.75::192.168.122.1:255.255.255.0:jimbob.local:::1.1.1.1:8.8.8.8

Above, we see these IP settings:

ip=[system static IP]::[system gateway IP]:[full netmask]:[system FQDN]:::[DNS server 1]:[DNS server 2]

Place these settings on the kernel command line in grub alongside the autoinstall kernel settings.

Further settings are optional but not necessary. See dracut.cmdline(7) for more details.

LVM layout

Given the complexity of Curtin disk partitioning customization, it’s best to start off with an example autoinstall file or to automatically generate one as described above, by interactively deploying a system and setting up partitions as you’d like them to be. The complete autoinstall file example below is a good starting point.

Password setting tip

On Ubuntu, ensure the whois package is installed, which provides mkpasswd. (On EL, the package is expect…). Generate a salted SHA512 password hash interactively using mkpasswd:

1
2
3
mkpasswd -m sha512crypt
Password: 
$6$qLcZm0QXHHZ643b7$KnTWCZXP.1439am0X7Oukb7M7IJtxavmhke3uO1YMZxevHVy714HA29bSpBbev90rT2yahnvm25LcFl3Jyhiw0

Example complete Autoinstall

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#cloud-config
autoinstall:
  apt:
    geoip: true
    preserve_sources_list: false
    primary:
    - arches: [amd64, i386]
      uri: http://us.archive.ubuntu.com/ubuntu
    - arches: [default]
      uri: http://ports.ubuntu.com/ubuntu-ports
  user-data:
    users:
      - name: jimbob
        passwd: '$6$qLcZm0QXHHZ643b7$KnTWCZXP.1439am0X7Oukb7M7IJtxavmhke3uO1YMZxevHVy714HA29bSpBbev90rT2yahnvm25LcFl3Jyhiw0'
        lock_passwd: false
        shell: /bin/bash
        sudo: ALL=(ALL) NOPASSWD:ALL
        ssh_authorized_keys:
          - |
                        ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPxR9X9ZVnMLsuaWaGFgiIsj6gIwq9pvQrC9LNYVT7nV kodiak@firesmith.org
    package_update: true
    package_upgrade: true
    package_reboot_if_required: true
    packages:
      - bind9-dnsutils
      - curl
      - glances
      - postfix
    runcmd:
      - hostnamectl set-hostname $(dig +short -x "$(curl -s https://api.ipify.org)" | sed 's/.$//g')
      - sed -i 's/GRUB_TIMEOUT_STYLE=hidden/GRUB_TIMEOUT_STYLE=menu/g' /etc/default/grub
      - sed -i 's/GRUB_TIMEOUT=0/GRUB_TIMEOUT=5/g' /etc/default/grub
      - update-grub
    final_message: "Cloud Init completed.  System now ready to use. Datasource: $DATASOURCE Version: $VERSION Timestamp: $TIMESTAMP Uptime: $UPTIME"
  keyboard: {layout: us, toggle: null, variant: ''}
  locale: en_US.UTF-8
  timezone: US/Eastern
  network:
    ethernets:
      enp1s0: {dhcp4: false}
    version: 2
  ssh:
    allow-pw: true
    authorized-keys: []
    install-server: true
  storage:
    config:
    - {ptable: gpt, path: /dev/sda, wipe: superblock-recursive,
      preserve: false, name: '', grub_device: true, type: disk, id: disk-sda}
    - {device: disk-sda, size: 1M, flag: bios_grub, number: 1, preserve: false,
      grub_device: false, type: partition, id: partition-0}
    - {device: disk-sda, size: 1G, wipe: superblock, flag: '', number: 2,
      preserve: false, grub_device: false, type: partition, id: partition-1}
    - {fstype: ext4, volume: partition-1, preserve: false, type: format, id: format-0}
    - {device: disk-sda, size: -1, wipe: superblock, flag: '', number: 3,
      preserve: false, grub_device: false, type: partition, id: partition-2}
    - name: system
      devices: [partition-2]
      preserve: false
      type: lvm_volgroup
      id: lvm_volgroup-0
    - {name: root, volgroup: lvm_volgroup-0, size: 12G, wipe: superblock,
      preserve: false, type: lvm_partition, id: lvm_partition-0}
    - {fstype: ext4, volume: lvm_partition-0, preserve: false, type: format, id: format-1}
    - {path: /, device: format-1, type: mount, id: mount-1}
    - {name: home, volgroup: lvm_volgroup-0, size: 2G, wipe: superblock,
      preserve: false, type: lvm_partition, id: lvm_partition-1}
    - {fstype: ext4, volume: lvm_partition-1, preserve: false, type: format, id: format-2}
    - {path: /home, device: format-2, type: mount, id: mount-2}
    - {name: opt, volgroup: lvm_volgroup-0, size: 2G, wipe: superblock, preserve: false,
      type: lvm_partition, id: lvm_partition-2}
    - {fstype: ext4, volume: lvm_partition-2, preserve: false, type: format, id: format-3}
    - {path: /opt, device: format-3, type: mount, id: mount-3}
    - {name: tmp, volgroup: lvm_volgroup-0, size: 2G, wipe: superblock, preserve: false,
      type: lvm_partition, id: lvm_partition-3}
    - {fstype: ext4, volume: lvm_partition-3, preserve: false, type: format, id: format-4}
    - {path: /tmp, device: format-4, type: mount, id: mount-4}
    - {name: var, volgroup: lvm_volgroup-0, size: 4G, wipe: superblock, preserve: false,
      type: lvm_partition, id: lvm_partition-4}
    - {fstype: ext4, volume: lvm_partition-4, preserve: false, type: format, id: format-5}
    - {path: /var, device: format-5, type: mount, id: mount-5}
    - {name: varlog, volgroup: lvm_volgroup-0, size: 2G, wipe: superblock,
      preserve: false, type: lvm_partition, id: lvm_partition-5}
    - {fstype: ext4, volume: lvm_partition-5, preserve: false, type: format, id: format-6}
    - {path: /var/log, device: format-6, type: mount, id: mount-6}
    - {name: varlogaudit, volgroup: lvm_volgroup-0, size: 2G, wipe: superblock,
      preserve: false, type: lvm_partition, id: lvm_partition-6}
    - {fstype: ext4, volume: lvm_partition-6, preserve: false, type: format, id: format-7}
    - {path: /var/log/audit, device: format-7, type: mount, id: mount-7}
    - {name: swap, volgroup: lvm_volgroup-0, size: 2G, wipe: superblock,
      preserve: false, type: lvm_partition, id: lvm_partition-7}
    - {fstype: swap, volume: lvm_partition-7, preserve: false, type: format, id: format-8}
    - {path: '', device: format-8, type: mount, id: mount-8}
    - {path: /boot, device: format-0, type: mount, id: mount-0}
    swap: {swap: 0}
  version: 1


Debugging

Subiquity failed autoinstaller logs live in /var/log/installer/subiquity-server-debug.log