How to preconfigure LXD containers with cloud-init

You are creating containers and you want them to be somewhat preconfigured. For example, you want them to run automatically apt update as soon as they are launched. Or, get some packages pre-installed, or run a few commands. Here is how to perform this early initialization with cloud-init through LXD to container images that support cloud-init.

In the following, we are creating a separate LXD profile with some cloud-init instructions, then launch a container using that profile.

How to create a new LXD profile

Let’s see the existing profiles.

$ lxc profile list
+---------+---------+
| NAME    | USED BY |
+---------+---------+
| default | 11      |
+---------+---------+

There is one profile, default. We copy it to a new name, so that we can start adding our instructions on that profile.

$ lxc profile copy default devprofile

$ lxc profile list
+------------+---------+
| NAME       | USED BY |
+------------+---------+
| default    | 11      |
+------------+---------+
| devprofile | 0       |
+------------+---------+

We have a new profile to work on, devprofile. Here is how it looks,

$ lxc profile show devprofile
config:
 environment.TZ: ""
description: Default LXD profile
devices:
 eth0:
 nictype: bridged
 parent: lxdbr0
 type: nic
 root:
 path: /
 pool: default
 type: disk
name: devprofile
used_by: []

Note the main sections, config:, description:, devices:, name:, and used_by:. There is careful indentation in the profile, and when you make edits, you need to take care of the indentation.

How to add cloud-init to an LXD profile

In the config: section of a LXD profile, we can insert cloud-init instructions. Those cloud-init instructions will be passed to the container and will be used when it is first launched.

Here are those that we are going to use in the example,

 package_upgrade: true
 packages:
  - build-essential
 locale: es_ES.UTF-8
 timezone: Europe/Madrid
 runcmd: 
  - [touch, /tmp/simos_was_here]

package_upgrade: true means that we want cloud-init to run sudo apt upgrade when the container is first launched. Under packages: we list the packages that we want to get automatically installed. Then we set the locale and timezone. In the Ubuntu container images, the default locale for root is C.UTF-8, for the ubuntu account it’s en_US.UTF-8. The timezone is Etc/UTC. Finally, we show how to run a Unix command with runcmd.

The part that needs a bit of attention is how to insert the cloud-init instructions into the LXD profile. My preferred way is

$ lxc profile edit devprofile

This opens up a text editor and allows to paste the instructions. Here is how the result should look like,

$ lxc profile show devprofile
config:
  environment.TZ: ""
  user.user-data: |
    #cloud-config
    package_upgrade: true
    packages:
      - build-essential
    locale: es_ES.UTF-8
    timezone: Europe/Madrid
    runcmd:
      - [touch, /tmp/simos_was_here]
description: Default LXD profile
devices:
  eth0:
  nictype: bridged
  parent: lxdbr0
  type: nic
  root:
  path: /
  pool: default
  type: disk
name: devprofile
used_by: []

WordPress can get a bit messed with indentation when you copy/paste, therefore, you may use this pastebin instead.

How to launch a container using a profile

Let’s launch a new container using the profile devprofile.

$ lxc launch --profile devprofile ubuntu:x mydev

Let’s get into the container and figure out whether our instructions took effect.

$ lxc exec mydev bash
root@mydev:~# ps ax
 PID TTY STAT TIME COMMAND
 1 ? Ss 0:00 /sbin/init
 ...
 427 ? Ss 0:00 /usr/bin/python3 /usr/bin/cloud-init modules --mode=f
 430 ? S 0:00 /bin/sh -c tee -a /var/log/cloud-init-output.log
 431 ? S 0:00 tee -a /var/log/cloud-init-output.log
 432 ? S 0:00 /usr/bin/apt-get --option=Dpkg::Options::=--force-con
 437 ? S 0:00 /usr/lib/apt/methods/http
 438 ? S 0:00 /usr/lib/apt/methods/http
 440 ? S 0:00 /usr/lib/apt/methods/gpgv
 570 ? Ss 0:00 bash
 624 ? S 0:00 /usr/lib/apt/methods/store
 625 ? R+ 0:00 ps ax
root@mydev:~#

We connected quite quickly, and ps ax shows that the package update is indeed taking place! We can get the full output at /var/log/cloud-init-output.log and in there,

Generating locales (this might take a while)...
 es_ES.UTF-8... done
Generation complete.

The locale got set. The root user keeps having the C.UTF-8 default locale. It is only the non-root account ubuntu that gets the new locale.

Hit:1 http://archive.ubuntu.com/ubuntu xenial InRelease
Get:2 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [102 kB]
Get:3 http://security.ubuntu.com/ubuntu xenial-security InRelease [102 kB]

Here is apt update that is required before installing packages.

The following packages will be upgraded:
 libdrm2 libseccomp2 squashfs-tools unattended-upgrades
4 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 211 kB of archives.

Here is runs package_upgrade: true and installs any available packages.

The following NEW packages will be installed:
 binutils build-essential cpp cpp-5 dpkg-dev fakeroot g++ g++-5 gcc gcc-5
 libalgorithm-diff-perl libalgorithm-diff-xs-perl libalgorithm-merge-perl

This is from our instruction to install the build-essential meta-package.

What about the runcmd instruction?

root@mydev:~# ls -l /tmp/
total 1
-rw-r--r-- 1 root root 0 Jan 3 15:23 simos_was_here
root@mydev:~#

It worked as well!

Conclusion

When we launch LXD containers, we often need some configuration to be enabled by default and avoid repeated actions. The way to solve this, is to create LXD profiles. Each profile captures those configurations. Finally, when we launch the new container, we specify which LXD profile to use.

Permanent link to this article: https://blog.simos.info/how-to-preconfigure-lxd-containers-with-cloud-init/

6 comments

1 ping

Skip to comment form

  1. Cloud-init works only image ubuntu: How to use images: or other OS (Cetnos, Debian, etc)&

    1. Here is the relevant bug report and discussion on adding cloud-init on Debian,
      https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=850790

      The Debian image from images:debian/stretch does not have cloud-init pre-installed.
      The Centos image is 73MB and I doubt it has cloud-init. I suppose you have checked already.

      I think it is important for different distributions to add cloud-init support to their images.

      If you are interested in smaller Ubuntu images (but still have cloud-init), see the Ubuntu Minimal,
      https://blog.simos.info/a-closer-look-at-minimal-ubuntu-for-lxd/

      1. I tried install cloud-init in centos container and create image from container. Not work.

        lxc exec c2 bash
        yum install python2.7-minimal cloud-init
        exit
        lxc stop c2
        lxc publish c2 –alias centos-7

      2. Ideally, you would use distrobuilder (https://github.com/lxc/distrobuilder) to create a new container for your distribution. There is a template already for Centos.

        I do not know why in your published image, cloud-init is not working. It should be easy to debug this by following the boot-up sequence of the runtime.

  2. Can’t seem to get this to work on Ubuntu Bionic LTS.

    I have this in my LXD profile definition:

    config:
    environment.TZ: America/New_York
    user.user-data: |
    #cloud-init config
    package_upgrade: true
    package_reboot_if_required: true
    locale: es_US.UTF-8
    timezone: America/New_York

    And in the container in /var/lib/cloud/seed/nocloud-net/user-data, I have:

    #cloud-init config
    package_upgrade: true
    package_reboot_if_required: true
    locale: es_US.UTF-8
    timezone: America/New_York

    But none of the packages are updated and there is no mention in the cloud-init logs of an attempt.

    Any ideas?

  3. Does not work for ‘ubuntu:focal’
    Where is it stated which images have cloud-init enabled.
    This changed and I had all sorts of trouble, and looked in all the wrong places to try and fix it. 🙂

  1. […] How to preconfigure LXD containers with cloud-init […]

Leave a Reply to Peter Goodall Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: