I recently tried to get the openFATE client working in a container. I know it may sound stupid, but I didn't want to pollute my Gnome machine with KDE libraries (end of troll) and wanted to use this to seriously play with application containers. My constraints were:
- minimize the duplication of the root file system. I know docker already does this, but I'm a libvirt hacker after all.
- get the application show up on the host display for a smooth use.
Creating the root file system
To minimze the file system duplication, I went with a qcow2 disk image with a backing file. The first step is to create the base image with the file system to be reused by other containers.
Create the disk image:
qemu-img create -f qcow2 test-base.qcow2 15G
Create the nbd device from the disk image, format it and mount it. Depending on your linux distribution you may even need to load the nbd kernel module, hence the first line.
modprobe nbd
/usr/bin/qemu-nbd --format qcow2 -n -c /dev/nbd0 $PWD/test-base.qcow2
mkfs.ext3 /dev/nbd0
mount /dev/nbd0 /mnt
Populate the image with the openSUSE 13.2 Minimal_base pattern:
zypper --root /mnt ar http://download.opensuse.org/distribution/13.2/repo/oss/ main
zypper --root /mnt ar http://download.opensuse.org/update/13.2/ updates
zypper --root /mnt in -t pattern Minimal_base
Unmount and clean up the nbd device:
umount /mnt
pkill qemu-nbd
Now, we will create an overlay image on top of the one we just created. In this image, we will only install fate, and create an unprivileged user to run it.
Create the image. Note the backing_file
and backing_fmt
options as they
will actually setup the backing chain of qcow2 images.
qemu-img create -f qcow2 \
-o backing_fmt=qcow2,backing_file=$PWD/test-base.qcow2 \
myapp.qcow2
Mount the new image via qemu-nbd
again. Note that there is no need to format
that new image: it is really only a diff with the test-base.qcow2
file.
/usr/bin/qemu-nbd --format qcow2 -n -c /dev/nbd0 $PWD/myapp.qcow2
mount /dev/nbd0 /mnt
Install the application (fate in this example):
zypper --root /mnt ar http://download.opensuse.org/repositories/FATE/openSUSE_13.2/ FATE
zypper --root /mnt in fate
Add an unprivileged user:
useradd -m myuser
For the application to find the X server display, set the DISPLAY in the user's
profile. The localhost:0
value here can be adjusted depending on the network
settings of the container. In this case, I will use a container without the
netns namespace to simplify, but with the default libvirt network the value
would be 192.168.122.1:0
.
echo 'export DISPLAY=localhost:0' > ~myuser/.profile
Unmount and clean up the nbd device:
umount /mnt
pkill qemu-nbd
Setting up the container
Sadly, there is no other possible way to create the container than by manually feeding the XML definition to libvirt. Here is a template for the definition:
<domain type='lxc'>
<name>myapp</name>
<memory unit='MiB'>256</memory>
<vcpu placement='static'>1</vcpu>
<resource>
<partition>/machine</partition>
</resource>
<os>
<type arch='x86_64'>exe</type>
<init>/usr/bin/su</init>
<initarg>-</initarg>
<initarg>myuser</initarg>
<initarg>-c</initarg>
<initarg>/usr/bin/fate</initarg>
</os>
<clock offset='utc'/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>destroy</on_crash>
<devices>
<controller type='ide' index='0'/>
<filesystem type='file'>
<driver type='nbd' format='qcow2'/>
<source file='/path/to/myapp.qcow2'/>
<target dir='/'/>
</filesystem>
<filesystem type='mount'>
<source dir='/home/myuser/.Xauthority'/>
<target dir='/home/myuser/.Xauthority'/>
</filesystem>
<filesystem type='ram'>
<source usage='10240' units='KiB'/>
<target dir='/run'/>
</filesystem>
<filesystem type='ram'>
<source usage='102400' units='KiB'/>
<target dir='/tmp'/>
</filesystem>
<console type='pty'/>
</devices>
<seclabel type='dynamic' model='apparmor' relabel='yes'/>
</domain>
In this template, the memory, vcpu and paths to images need to be adapted to your setup. Note that the qcow2 image is mounted in the container using the filesystem nbd driver.
The host user's .Xauthority file is mounted in the container's user home. This is needed for the X applications to connect to the host display.
For the application to be automatically launched as the unprivileged user, the definition init command is set to
/usr/bin/su - myuser -c /usr/bin/fate
Running the application
Before being able to connect to the host Xorg server, we need to have it listen
for TCP connection. In openSUSE, this can be achieved by changing
DISPLAYMANAGER_XSERVER_TCP_PORT_6000_OPEN
to yes
in
/etc/sysconfig/displaymanager
.
The application needs to be able to authenticate on the host Xorg server. For
my tests I manually crafted the .Xauthority using xauth. Just remember that
the cookie will change, so either regenerate it before each start or mount
$XAUTHORITY
directly to the container's .Xauthority
.
xauth extract - $DISPLAY | xauth -f $HOME/.Xauthority merge -
Running the application is as easy as starting the container:
virsh -c lxc:/// start --console myapp
Of course it would be much more convenient to have it wrapped in a script with proper sudo configuration to let normal users run the container without needing to become root.
Limitations
This approach is still pretty complex and not that easy for newcomers.
- The image creation process will be simplified as part of Google Summer of Code 2015 with libvirt.
- The container definition and start could be done with virt-sandbox, but the host-image mount parameter should first be able to take qcow2 images.
- libvirt doesn't stop the
qemu-nbd
process when the container is stopped, but hey, that's a bug I can work on!