Introduction
Once in a while, I get to tackle issues that have little or no documentation other than the official documentation of the product and the product’s source code. You may know from experience that product documentation is not always sufficient to get a complete configuration working. This article intend to flesh out a solution to customizing disk configurations using Curtin.
This article take for granted that you are familiar with Maas install mechanisms, that you already know how to customize installations and deploy workloads using Juju.
While my colleagues in the Maas development team have done a tremendous job at keeping the Maas documentation accurate (see Maas documentation), it does only cover the basics when it comes to Maas’s preseed customization, especially when it comes to Curtin’s customization.
Curtin is Maas’s fastpath installer which is meant to replace Debian’s installer (familiarly known as d-i). It does a complete machine installation much faster than with the standard debian method. But while d-i is well known and it is easy to find example of its use on the web, Curtin does not have the same notoriety and, hence, not as much documentation.
Theory of operation
When the fastpath installer is used to install a maas unit (which is now the default), it will send the content of the files prefixed with curtin_ to the unit being installed. The curtin_userdata contains cloud-config type commands that will be applied by cloud-init when the unit is installed. If we want to apply a specific partitioning scheme to all of our unit, we can modify this file and every unit will get those commands applied to it when it installs.
But what if we only have one or a few servers that have specific disk layout that require partitioning ? In the following example, I will suppose that we have one server, named curtintest which has a one terabyte disk (1 TB) and that we want to partition this disk with the following partition table :
- Partition #1 has the /boot file system and is bootable
- Partition #2 has the root (/) file system
- Partition #3 has a 31 Gb file system
- Partition #4 has 32 Gb of swap space
- Partition #5 has the remaining disk space
Since only one server has such a disk, the partitioning should be specific to that curtintest server only.
Setting up Curtin development environment
To get to a working Maas partitioning setup, it is preferable to use Curtin’s development environment to test the curtin commands. Using Maas deployment to test each command quickly becomes tedious and time consuming. There is a description on how to set it up in the README.txt but here are more details here.
Aside from putting all the files under one single directory, the steps described here are the same as the one in the README.txt file :
$ mkdir -p download
$ DLDIR=$(pwd)/download
$ rel="trusty"
$ arch=amd64
$ burl="http://cloud-images.ubuntu.com/$rel/current/"
$ for f in $rel-server-cloudimg-${arch}-root.tar.gz $rel-server-cloudimg-{arch}-disk1.img; do wget "$burl/$f" -O $DLDIR/$f; done
$ ( cd $DLDIR && qemu-img convert -O qcow $rel-server-cloudimg-${arch}-disk1.img $rel-server-cloudimg-${arch}-disk1.qcow2)
$ BOOTIMG="$DLDIR/$rel-server-cloudimg-${arch}-disk1.qcow2"
$ ROOTTGZ="$DLDIR/$rel-server-cloudimg-${arch}-root.tar.gz"
$ mkdir src
$ bzr init-repo src/curtin
$ (cd src/curtin && bzr branch lp:curtin trunk.dist )
$ (cd src/curtin && bzr branch trunk.dist trunk)
$ cd src/curtin/trunk
You now have an environment you can use with Curtin to automate installations. You can test it by using the following command which will start a VM and run “curtin install” in it. Once you get the prompt, login with :
username : ubuntu
password : passw0rd
$ sudo ./tools/launch $BOOTIMG --publish $ROOTTGZ -- curtin install "PUBURL/${ROOTTGZ##*/}"
Using Curtin in the development environment
To test Curtin in its environment, simply remove — curtin install “PUBURL/${ROOTTGZ##*/}” at the end of the statement. Once logged in, you will find the Curtin executable in /curtin/bin :
ubuntu@ubuntu:~$ sudo -s
root@ubuntu:~# /curtin/bin/curtin --help
usage: main.py [-h] [--showtrace] [--verbose] [--log-file LOG_FILE]
{block-meta,curthooks,extract,hook,in-target,install,net-meta,pac
k,swap}
...
positional arguments:
{block-meta,curthooks,extract,hook,in-target,install,net-meta,pack,swap}
optional arguments:
-h, --help show this help message and exit
--showtrace
--verbose, -v
--log-file LOG_FILE
Each of Curtin’s commands have their own help :
ubuntu@ubuntu:~$ sudo -s
root@ubuntu:~# /curtin/bin/curtin install --help
usage: main.py install [-h] [-c FILE] [--set key=val] [source [source ...]]
positional arguments:
source what to install
optional arguments:
-h, --help show this help message and exit
-c FILE, --config FILE
read configuration from cfg
--set key=val define a config variable
Creating Maas’s Curtin preseed commands
Now that we have our Curtin development environment available, we can use it to come up with a set of commands that will be fed to Curtin by Maas when a unit is created.
Maas uses preseed files located in /etc/maas/preseeds on the Maas server. The curtin_userdata preseed file is the one that we will use as a reference to build our set of partitioning commands. During the testing phase, we will use the -c option of curtin install along with a configuration file that will mimic the behavior of curtin_userdata.
We will also need to add a fake 1TB disk to Curtin’s development environment so we can use it as a partitioning target. So in the development environment, issue the following command :
$ qemu-img create -f qcow2 boot.disk 1000G Formatting ‘boot.disk’, fmt=qcow2 size=1073741824000 encryption=off cluster_size=65536 lazy_refcounts=off
sudo ./tools/launch $BOOTIMG –publish $ROOTTGZ
ubuntu: ubuntu password: passw0rd
ubuntu@ubuntu:~$ sudo -s root@ubuntu:~# cat /proc/partitions
major minor #blocks name
253 0 2306048 vda 253 1 2305024 vda1 253 16 426 vdb 253 32 1048576000 vdc 11 0 1048575 sr0
We can see that the 1000G /dev/vdc is indeed present. Let’s now start to craft the conffile that will receive our partitioning commands. To test the syntax, we will use two simple commands :
root@ubuntu:~# cat << EOF > conffile partitioning_commands: builtin: [] 01_partition_make_label: ["/sbin/parted", "/dev/vdc", "-s", "'","mklabel","msdos","'"] 02_partition_make_part: ["/sbin/parted", "/dev/vdc", "-s", "'","mkpart","primary","1049K","538M","'"] sources: 01_primary: http://192.168.0.13:9923//trusty-server-cloudimg-amd64-root.tar.gz EOF
The sources: statement is only there to avoid having to repeat the SOURCE portion of the curtin command and is not to be used in the final Maas configuration. The URL is the address of the server from which you are running the Curtin development environment.
WARNING
The builtin [] statement is VERY important. It is there to override Curtin’s native builtin statement which is to partition the disk using “block-meta simple”. If it is removed, Curtin will overwrite he partitioning with its default configuration. This comes straight from Scott Moser, the main developer behind Curtin.
Now let’s run the Curtin command :
root@ubuntu:~# /curtin/bin/curtin install -c conffile
Curtin will run its installation sequence and you will see a display which you should be familiar with if you installed units with Maas previously. The command will most probably exit on error, comlaining about the fact that install-grub received an argument that was not a block device. We do not need to worry about that at the motent.
Once completed, have a look at the partitioning of the /dev/vdc device :
root@ubuntu:~# parted /dev/vdc print
Model: Virtio Block Device (virtblk)
Disk /dev/vdc: 1074GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Number Start End Size Type File system Flags
1 1049kB 538MB 537MB primary ext4
The partitioning commands were successful and we have the /dev/vdc disk properly configured. Now that we know that the mechanism works, let try with a complete configuration file. I have found that it was preferable to start with a fresh 1TB disk :
root@ubuntu:~# poweroff
$ rm -f boot.img
$ qemu-img create -f qcow2 boot.disk 1000G
Formatting ‘boot.disk’, fmt=qcow2 size=1073741824000 encryption=off cluster_size=65536 lazy_refcounts=off
sudo ./tools/launch $BOOTIMG –publish $ROOTTGZ
ubuntu@ubuntu:~$ sudo -s
root@ubuntu:~# cat << EOF > conffile partitioning_commands: builtin: [] 01_partition_announce: ["echo", "'### Partitioning disk ###'"] 01_partition_make_label: ["/sbin/parted", "/dev/vda", "-s", "'","mklabel","msdos","'"] 02_partition_make_part: ["/sbin/parted", "/dev/vda", "-s", "'","mkpart","primary","1049k","538M","'"] 02_partition_set_flag: ["/sbin/parted", "/dev/vda", "-s", "'","set","1","boot","on","'"] 04_partition_make_part: ["/sbin/parted", "/dev/vda", "-s", "'","mkpart","primary","538M","4538M","'"] 05_partition_make_part: ["/sbin/parted", "/dev/vda", "-s", "'","mkpart","extended","4538M","1000G","'"] 06_partition_make_part: ["/sbin/parted", "/dev/vda", "-s", "'","mkpart","logical","25.5G","57G","'"] 07_partition_make_part: ["/sbin/parted", "/dev/vda", "-s", "'","mkpart","logical","57G","89G","'"] 08_partition_make_part: ["/sbin/parted", "/dev/vda", "-s", "'","mkpart","logical","89G","1000G","'"] 09_partition_announce: ["echo", "'### Creating filesystems ###'"] 10_partition_make_fs: ["/sbin/mkfs", "-t", "ext4", "/dev/vda1"] 11_partition_label_fs: ["/sbin/e2label", "/dev/vda1", "cloudimg-boot"] 12_partition_make_fs: ["/sbin/mkfs", "-t", "ext4", "/dev/vda2"] 13_partition_label_fs: ["/sbin/e2label", "/dev/vda2", "cloudimg-rootfs"] 14_partition_mount_fs: ["sh", "-c", "mount /dev/vda2 $TARGET_MOUNT_POINT"] 15_partition_mkdir: ["sh", "-c", "mkdir $TARGET_MOUNT_POINT/boot"] 16_partition_mount_fs: ["sh", "-c", "mount /dev/vda1 $TARGET_MOUNT_POINT/boot"] 17_partition_announce: ["echo", "'### Filling /etc/fstab ###'"] 18_partition_make_fstab: ["sh", "-c", "echo 'LABEL=cloudimg-rootfs / ext4 defaults 0 0' >> $OUTPUT_FSTAB"] 19_partition_make_fstab: ["sh", "-c", "echo 'LABEL=cloudimg-boot /boot ext4 defaults 0 0' >> $OUTPUT_FSTAB"] 20_partition_make_swap: ["sh", "-c", "mkswap /dev/vda6"] 21_partition_make_fstab: ["sh", "-c", "echo '/dev/vda6 none swap sw 0 0' >> $OUTPUT_FSTAB"] sources: 01_primary: http://192.168.0.13:9923//trusty-server-cloudimg-amd64-root.tar.gz EOF
You will note that I have added a few statement like [“echo”, “‘### Partitioning disk ###'”] that will display some logs during the execution. Those are not necessary.
Now let’s try a second test with the complete configuration file :
root@ubuntu:~# /curtin/bin/curtin install -c conffile
root@ubuntu:~# parted /dev/vdc print
Model: Virtio Block Device (virtblk)
Disk /dev/vdc: 1074GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Number Start End Size Type File system Flags
1 1049kB 538MB 537MB primary ext4 boot
2 538MB 4538MB 4000MB primary ext4
3 4538MB 1000GB 995GB extended lba
5 25.5GB 57.0GB 31.5GB logical
6 57.0GB 89.0GB 32.0GB logical
7 89.0GB 1000GB 911GB logical
We now have a correctly partitioned disk in our development environment. All we need to do now is to carry that over to Maas to see if it works as expected.
Customization of Curtin execution in Maas
The section “How preseeds work in MAAS” give a good outline on how to select the name of the a preseed file to restrict its usage to specific sub-groups of nodes. In our case, we want our partitioning to apply to only one node : curtintest. So by following the description in the section “User provided preseeds“, we need to use the following template :
{prefix}_{node_arch}_{node_subarch}_{release}_{node_name}
The fileneme that we need to choose needs to end with our hostname, curtintest. The other elements are :
- prefix : curtin_userdata
- osystem : amd64
- node_subarch : generic
- release : trusty
- node_name : curtintest
So according to that, our filename must be curtin_userdata_amd64_generic_trusty_curtintest
On the MAAS server, we do the following :
root@maas17:~# cd /etc/maas/preseeds
root@maas17:~# cp curtin_userdata curtin_userdata_amd64_generic_trusty_curtintest
We now edit this newly created file and add our previously crafted Curtin configuration file just after the following block :
{{if third_party_drivers and driver}} early_commands: {{py: key_string = ''.join(['\\x%x' % x for x in map(ord, driver['key_binary'])])}} driver_00_get_key: /bin/echo -en '{{key_string}}' > /tmp/maas-{{driver['package']}}.gpg driver_01_add_key: ["apt-key", "add", "/tmp/maas-{{driver['package']}}.gpg"] driver_02_add: ["add-apt-repository", "-y", "deb {{driver['repository']}} {{node.get_distro_series()}} main"] driver_03_update_install: ["sh", "-c", "apt-get update --quiet && apt-get --assume-yes install {{driver['package']}}"] driver_04_load: ["sh", "-c", "depmod && modprobe {{driver['module']}}"] {{endif}}
The complete section should look just like this :
{{if third_party_drivers and driver}} early_commands: {{py: key_string = ''.join(['\\x%x' % x for x in map(ord, driver['key_binary'])])}} driver_00_get_key: /bin/echo -en '{{key_string}}' > /tmp/maas-{{driver['package']}}.gpg driver_01_add_key: ["apt-key", "add", "/tmp/maas-{{driver['package']}}.gpg"] driver_02_add: ["add-apt-repository", "-y", "deb {{driver['repository']}} {{node.get_distro_series()}} main"] driver_03_update_install: ["sh", "-c", "apt-get update --quiet && apt-get --assume-yes install {{driver['package']}}"] driver_04_load: ["sh", "-c", "depmod && modprobe {{driver['module']}}"] {{endif}} partitioning_commands: builtin: [] 01_partition_announce: ["echo", "'### Partitioning disk ###'"] 01_partition_make_label: ["/sbin/parted", "/dev/vda", "-s", "'","mklabel","msdos","'"] 02_partition_make_part: ["/sbin/parted", "/dev/vda", "-s", "'","mkpart","primary","1049k","538M","'"] 02_partition_set_flag: ["/sbin/parted", "/dev/vda", "-s", "'","set","1","boot","on","'"] 04_partition_make_part: ["/sbin/parted", "/dev/vda", "-s", "'","mkpart","primary","538M","4538M","'"] 05_partition_make_part: ["/sbin/parted", "/dev/vda", "-s", "'","mkpart","extended","4538M","1000G","'"] 06_partition_make_part: ["/sbin/parted", "/dev/vda", "-s", "'","mkpart","logical","25.5G","57G","'"] 07_partition_make_part: ["/sbin/parted", "/dev/vda", "-s", "'","mkpart","logical","57G","89G","'"] 08_partition_make_part: ["/sbin/parted", "/dev/vda", "-s", "'","mkpart","logical","89G","1000G","'"] 09_partition_announce: ["echo", "'### Creating filesystems ###'"] 10_partition_make_fs: ["/sbin/mkfs", "-t", "ext4", "/dev/vda1"] 11_partition_label_fs: ["/sbin/e2label", "/dev/vda1", "cloudimg-boot"] 12_partition_make_fs: ["/sbin/mkfs", "-t", "ext4", "/dev/vda2"] 13_partition_label_fs: ["/sbin/e2label", "/dev/vda2", "cloudimg-rootfs"] 14_partition_mount_fs: ["sh", "-c", "mount /dev/vda2 $TARGET_MOUNT_POINT"] 15_partition_mkdir: ["sh", "-c", "mkdir $TARGET_MOUNT_POINT/boot"] 16_partition_mount_fs: ["sh", "-c", "mount /dev/vda1 $TARGET_MOUNT_POINT/boot"] 17_partition_announce: ["echo", "'### Filling /etc/fstab ###'"] 18_partition_make_fstab: ["sh", "-c", "echo 'LABEL=cloudimg-rootfs / ext4 defaults 0 0' >> $OUTPUT_FSTAB"] 19_partition_make_fstab: ["sh", "-c", "echo 'LABEL=cloudimg-boot /boot ext4 defaults 0 0' >> $OUTPUT_FSTAB"] 20_partition_make_swap: ["sh", "-c", "mkswap /dev/vda6"] 21_partition_make_fstab: ["sh", "-c", "echo '/dev/vda6 none swap sw 0 0' >> $OUTPUT_FSTAB"]
Now that maas is properly configured for curtintest, complete the test by deploying a charm in a Juju environment where curtintest is properly comissionned. In that example, curtintest is the only available node so maas will systematically pick it up :
caribou@avogadro:~$ juju status
environment: maas17
machines:
“0”:
agent-state: started
agent-version: 1.24.0
dns-name: state-server.maas
instance-id: /MAAS/api/1.0/nodes/node-2555c398-1bf9-11e5-a7c4-525400214658/
series: trusty
hardware: arch=amd64 cpu-cores=1 mem=1024M
state-server-member-status: has-vote
services: {}
networks:
maas-eth0:
provider-id: maas-eth0
cidr: 192.168.100.0/24
caribou@avogadro:~$ juju deploy mysql
Added charm “cs:trusty/mysql-25” to the environment.
Once the mysql charm has been deployed, connect to the unit to confirm that the partitioning was successful
caribou@avogadro:~$ juju ssh mysql/0
ubuntu@curtintest:~$ sudo -s
root@curtintest:~# parted /dev/vda print
Model: Virtio Block Device (virtblk)
Disk /dev/vda: 1074GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Number Start End Size Type File system Flags
1 1049kB 538MB 537MB primary ext4 boot
2 538MB 4538MB 4000MB primary ext4
3 4538MB 1000GB 995GB extended lba
5 25.5GB 57.0GB 31.5GB logical
6 57.0GB 89.0GB 32.0GB logical
7 89.0GB 1000GB 911GB logical
ubuntu@curtintest:~$ swapon -s
Filename Type Size Used Priority
/dev/vda6 partition 31249404 0 -1
Conclusion
Customizing disks and partition using curtin is possible but currently not sufficiently documented. I hope that this write up will be helpful. Sustained development on Curtin is currently done to improve these functionalities so things will definitively get better.
thanks for the nice post, unfortunately I got an error that I did not succeeded in solving:
./tools/launch $BOOTIMG –publish $ROOTTGZ
gives me an error:
cannot create connection on virbr0.
bridges do not exist.
can you give me some good head up ?
thanks in advance
Hello,
I’m very sorry for being so late in replying, looks like my blog engine never told me about the comment.
Looks to me like your qemu environment is expecting the virbr0 bridge to be available so the NIC in the VM can be configured.
Off the top of my head, this is the only think I can think of.
…Louis
hi
does this somehow still work in maas 2.2 beta or 2.1?
i want to ask before i spend alot of time trying.
christian
Hi christian,
Did you have any luck with MaaS 2.1? Having the same question as you now.
Thanks,
Roman