How I use systemd-nspawn
First things first
About one year ago, after years with Fedora 18, I refreshed my laptop and installed a brand new Fedora 22. My first thought went to all the mess there was before the refresh because I tried tons of applications and changed my mind thousands of times in those three years.
This time, I wanted to take my time to improve the process and after a few minutes thinking I had a light-bulb moment and I just started creating a Dockerfile for every application I needed !
Well, after some time I had 27 images including:
- google-chrome
- spotify
- dropbox
- NetworkManager
- pulseaudio
- gnome-terminal-server
- nautilus
- feh
- i3wm
- lightdm
- crond
- VirtualBox
- compton
- parcellite
- guake
- and… many more!
As you can imagine, I started each one with the right options (I hope!) allowing it to use the X server and other resources.
In the next days I did some fine tuning and ended up having most of the containers I listed starting as startup system services.
What happened ?
My computer took minutes to undefined time to boot depending on the state of the Docker daemon, and that wasn’t acceptable for me so, sad but full of hope I started thinking at a possible solution by identifying why Docker wasn’t performing well as I expected in such situation.
The main problem, wasn’t that the Docker daemon itself is slow (in fact it isn’t) but a mix of factors due to the intrinsic docker’s caracteristic that it wants to manage everything for you, like setting up namespaces for existing containers, setting up volumes, managing and connecting to plugins, mounting the layered filesystems, setting up missing network devices and so on..
All this obviously slows down startup times in certain situations and given the fact that I use docker a lot for software development and for docker development itself there are a lot of ways that the state of my machine Docker daemon is pretty messy and things are likely to be broken and slow.
Example container: Spotify
Let’s say that I need to listen to some music and I’m on Fedora (looks like me now :D)
I Google for the Spotify Linux client aaaaand that’s IT! Spotify does have a Linux client, great!
Oh, damn, they only have a Debian package :(
…Looking for possible solutions…
So the first thing I did was in fact to create a Dockerfile for spotify.
Q: Wait Lorenzo, but you’ve just said you are not using Docker for your listening needs.
A: In fact I don’t, I’m just using Docker to create a Docker image, which I will export to a tar and use as a base filesystem for my container
Here’s the Dockerfile:
FROM debian:jessie
RUN apt-get update -y
RUN gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys BBEBDCB318AD50EC6865090613B00F1FD2C19886
RUN gpg --export --armor BBEBDCB318AD50EC6865090613B00F1FD2C19886 | apt-key add -
RUN echo deb http://repository.spotify.com stable non-free | tee /etc/apt/sources.list.d/spotify.list
RUN apt-get update -y
RUN apt-get install spotify-client -y
RUN apt-get install pulseaudio -y
RUN apt-get install -f -y
RUN echo enable-shm=no >> /etc/pulse/client.conf
ENV PULSE_SERVER /run/pulse/native
ENV HOME /home/spotify
RUN useradd --create-home --home-dir $HOME spotify \
&& gpasswd -a spotify audio \
&& chown -R spotify:spotify $HOME
WORKDIR $HOME
USER spotify
ENTRYPOINT [ "spotify" ]
Run the thing
docker run -d \
-v /etc/localtime:/etc/localtime:ro \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-e DISPLAY=unix$DISPLAY \
-v /run/user/1000/pulse:/run/pulse:ro \
-v /var/lib/dbus:/var/lib/dbus \
-v $HOME/.spotify/config:/home/spotify/.config/spotify \
-v $HOME/.spotify/cache:/home/spotify/spotify \
--name spotify \
fntlnz/spotify
Now that I have my image and I can use it with Docker seeing that it works I can try it with systemd-nspawn
The first thing to do is to export the docker image to a folder we’ll call rootfs
mkdir -p /var/lib/machines
cd /var/lib/machines
mkdir spotify
docker export $(docker create fntlnz/spotify) | tar -C spotify -xvf -
Then we have to give the right permissions to /home/spotify
systemd-nspawn -D spotify/ bash -c "chown -R spotify:spotify /home/spotify"
Now each time we want to start that container we can do it with:
systemd-nspawn \
--setenv=DISPLAY=unix$DISPLAY \
--bind=/tmp/.X11-unix:/tmp/.X11-unix \
--bind /run/user/1000/pulse:/run/pulse \
--bind /var/lib/dbus:/var/lib/dbus \
-u spotify -D spotify/ \
spotify
Additional notes
- We haven’t used any layered filesystem and the container is actually writing into the
spotify
directory. - The network stack is not isolated
- The 1000 user id needs to be changed with the id of the user connected to the X session (your user id on that machine)
- I’m not mounting
$HOME/.spotify
things inside mysystemd-nspawn
container since I decided to keep the state in thespotify
directory
machinectl
There’s another tool, invokable via machinectl
which allows you to manage your “machines” aka containers and vms managed
by the systemd machine manager
Container services
Using machinectl you can even create startup services, for example I use this for the NetworkManagr (image not included)
machinectl enable network-manager
Output:
Created symlink from /etc/systemd/system/machines.target.wants/[email protected] to /usr/lib/systemd/system/[email protected].
Management
machinectl allows you to list, terminate and show the status of machines.
List all the machines
machinectl list
Output:
MACHINE CLASS SERVICE
spotify container nspawn
1 machines listed.
Terminate the machine
machinectl terminate spotify
Get the status
machinectl status spotify
Output:
spotify
Since: Mon 2016-11-14 02:13:54 CET; 7s ago
Leader: 11308 (spotify)
Service: nspawn; class container
Root: /var/lib/machines/spotify
OS: Debian GNU/Linux 8 (jessie)
Unit: machine-spotify.scope
├─11308 /usr/share/spotify/spotif
├─11321 /usr/share/spotify/spotify --type=zygote --no-sandbox --lang=en-US --log-file=/usr/share/spotify/debug.log --log-severity=disable --product-version=Spotify/1.0.42.151
├─11344 /proc/self/exe --type=gpu-process --channel=1.0.1413324922 --mojo-application-channel-token=7BA7725BB9581D934FDAECBCAC0E2C8B --no-sandbox --window-depth=24 --x11-visual-id=32 --lang=en-
└─11372 /usr/share/spotify/spotify --type=renderer --disable-pinch --no-sandbox --primordial-pipe-token=431AFC3F7268B33A8765213F7926A54A --lang=en-US --lang=en-US --log-file=/usr/share/spotify/
Nov 14 02:13:54 fntlnz systemd[1]: Started Container spotify.
Nov 14 02:13:54 fntlnz systemd[1]: Starting Container spotify.
Pull images
machinectl can pull images using pull-raw
, pull-tar
and pull-dkr
from remote urls.
machinectl pull-raw --verify=no http://ftp.halifax.rwth-aachen.de/fedora/linux/releases/21/Cloud/Images/x86_64/Fedora-Cloud-Base-20141203-21.x86_64.raw.xz
systemd-nspawn -M Fedora-Cloud-Base-20141203-21
for more, see machinectl
What’s next
In this post I showed you something like the top 1% of the things that can be done with systemd-nspawn
, there’s moar!!, like:
- Usage of btrfs as container root and ephemeral containers
- Network isolation and advanced network interfaces (macvlan, ipvlan)
- Integration with SELinux
- bootable images
see man machinectl
and man systemd-nspawn
for more
What I achieved ?
- Portability and zero setup time: most linux distributions today are using systemd that means that my containers will work without having to install anything
- Faster startup times: as I said, starting just a process is faster than setting up a Docker container, not because Docker is slow (it’s not) but because it does actually more than just starting the process
- Reuse: I can reuse any docker image I want just by exporting it to a tar file, I can even reuse virtual machine images.
Now let’s listen some music!