edit: add `--force` option
refactorization command declaration
status: add status command
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.
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