Tool to manage OverlayFS layers
42b5d2dd4d06 — Alain Leufroy default tip a month ago
edit: add `--force` option
be1c72cb3108 — Alain Leufroy a month ago
refactorization command declaration
252c5ab163a5 — Alain Leufroy a month ago
status: add status command

heads

tip
browse log

clone

read-only
https://hg.sr.ht/~alainl/overlayctl
read/write
ssh://hg@hg.sr.ht/~alainl/overlayctl

#OverlayCtl

#Overview

Manage images as OverlayFS point for machinectl using systemd units.

OverlayFS is a is a union mount filesystem. It combines multiple different underlying mount points into one, resulting in single directory structure that contains underlying files and sub-directories from all sources.

The idea behind OverlayCtl is quite similar to as https://github.com/LEW21/siren-sh.

For each overlay it creates new var-lib-machines-{imagename}.mount and var-lib-machines-{imagename}.automount systemd units, and starts the second one. Systemd automatically mounts overlayfs at /var/lib/machines/{imagename} when somebody tries to access it, for example by starting the container.

To get around OverlayFS limitation (https://lists.ubuntu.com/archives/kernel-team/2013-March/025775.html) we stack the upper dir of lowered overlays instead of their mount point.

#Use case

Let's see how we can use it. systemd-journal-remote First of all, you'll need a base root point containing a fresh new system root. Let's say that we want a testing debian to start with.

host$ debootstrap --include=systemd-container --include=dbus-user-session --include=systemd-journal-remote --include=libpam-systemd testing /var/lib/machines/debian

Note: --include=systemd-container will help you about host <-> container communication.

Due to systemd-nspawn limitations, I recommand to not use ., -, _ in the layer names.

The root login with password is disabled by default in Debian, as I'm writting this documentation. So, we'll create a first layer that allows us to automatically login as root.

host$ overlayctl create autologin

Don't be afraid, overlayctl will help us fixing this security hole later.

To enable automatic login as root, create the following file at /var/lib/overlayctl/upper/var-lib-machines-autologin/etc/systemd/system/console-getty.service.d/override.conf

host$ mkdir -p /var/lib/overlayctl/upper/var-lib-machines-autologin2/etc/systemd/system/console-getty.service.d/
host$ cat > /var/lib/overlayctl/upper/var-lib-machines-autologin2/etc/systemd/system/console-getty.service.d/override.conf << OEF
[Service]
ExecStart=-/sbin/agetty --noclear --autologin root --keep-baud console 115200,38400,9600 $TERM
EOF

Ok, we now will be able to connect to the container system directly as root.

Now, we'll need networking, in order to install more stuff. We'll create a layer for that. Note that you may need to use systemd-networkd and systemd-resolved on your host to apply the following method.

host$ overlayctl create network autologin debian
host$ overlayctl start -s network
host$ systemd-nspawn -M network -b -n
network# systemctl enable --now systemd-networkd
network# systemctl enable --now systemd-resolved
network# resolvectl query linuxfoundation.org
network# systemctl halt

Continue with a layer containing build-essentials allowing us to compile things.

host$ overlayctl create buildessential network
host$ overlayctl start -i buildessential
host$ systemd-nspawn -M buildessential -b -n
buildessential# apt install -y build-essential cargo vim
buildessential# systemctl halt

We now have everything to build our application container. Let say that our project is a simple rust file in /home/myproject/hello.rs on the host.

fn main() {
    println!("Hello world!");
}

We create a container for our project:

host$ overlayctl create myproject buildessential
host$ overlayctl start -i myproject
host$ systemd-nspawn -M myproject -b -n --bind-ro=/home/myproject:/app
myproject# hostnamectl set-hostname myproject
myproject# cd /app
myproject# rustc --out-dir=/usr/local/bin hello.rs
myproject# hello
Hello world!

Then create a service file to execute our code:

myproject# vim /etc/systemd/system/myproject.service

[Unit]
Description=myproject
[Service]
ExecStart=/usr/local/bin/hello
# Hardening: http://0pointer.de/blog/projects/security.html
ProtectSystem=strict
[Install]
WantedBy=multi-user.target

myproject# systemctl start myproject.service
myproject# journalctl -e -u myproject.service
App 1 00:00:00 myproject systemd[1]: Started myproject.
App 1 00:00:00 myproject hello[113]: Hello world!
App 1 00:00:00 myproject systemd[1]: myproject.service: Succeeded.
myproject# systemctl halt

Everything works. We can clean up things for security reason. We do not need networking, neigher autologin, neither build tools.

In fact, looking for our overlay, it looks like this:

host$ overlayctl list myproject
myproject 🖴  → buildessentials → network, autologin, debian

So, lets remove buildessential as a lower layer and put debian directly

host$ overlayctl edit --delete buildessential myproject
host$ overlayctl edit --append debian myproject
host$ overlayctl list myproject
myproject 🖴  → debian

By this way, root is not logged by default, there are not network nor compilers.

For convenience, we create a unit file in the host to easily start our container in /etc/systemd/nspawn/myproject.nspawn containing:

[Exec]
Boot=True
[Network]
Private=True

We can now start the container with:

host$ systemctl start systemd-nspawn@myproject

And execute our code:

host$ systemctl -M myproject start myproject
host$ journalctl -M myproject -u myproject
App 1 00:00:00 myproject systemd[1]: Started myproject.
App 1 00:00:00 myproject hello[113]: Hello world!
App 1 00:00:00 myproject systemd[1]: myproject.service: Succeeded.
App 1 00:02:00 myproject systemd[1]: Started myproject.
App 1 00:02:00 myproject hello[113]: Hello world!
App 1 00:02:00 myproject systemd[1]: myproject.service: Succeeded.

Now let say we need to update our project code. We need the rst compiler. But we do not need networking anymore. We can cleanup overlay dependencies:

host$ overlayctl list
autologin →
network → autologin, debian
buildessential → networking ⇢ autologin, debian
myproject → debian
host$ overlayctl edit networking -d autologin -d debian
host$ overlayctl edit buildessential -d network
host$ overlayctl list
autologin →
network →
buildessential →
myproject → debian

We now "activate" things independently.

host$ systemctl stop systemd-nspawn@myproject
host$ overlayctl edit myproject
# -*- encoding: utf-8 -*-
# Note: Leave unchanged or empty to ignore changes
buildessential
autologin      # <- Remove this line
network        # <- Remove this line
debian
host$ overlayctl list myproject
myproject → buildessential, debian

And update the project:

host$ systemd-nspawn -M myproject -b --bind-ro=/home/myproject:/app
myproject# cd /app
myproject# rustc --out-dir=/usr/local/bin hello.rs
myproject# systemctl halt

Then clean up the overlay and restart the container.

host$ overlayctl edit -d autologin -d network myproject
host$ systemctl start systemd-nspawn@myproject
host$ systemctl -M myproject start myproject