I've been using sbuild for a while to build my Debian packages,
mainly because it's what is used by the Debian autobuilders, but
also because it's pretty powerful and efficient. Configuring it just
right, however, can be a challenge. In my quick Debian development
guide, I had a few pointers on how to
configure sbuild with the normal schroot setup, but today I finished
a qemu based configuration.
Why
I want to use qemu mainly because it provides better isolation than a chroot. I sponsor packages sometimes and while I typically audit the source code before building, it still feels like the extra protection shouldn't hurt.
I also like the idea of unifying my existing virtual machine setup with my build setup. My current VM is kind of all over the place: libvirt, vagrant, GNOME Boxes, etc?). I've been slowly converging over libvirt however, and most solutions I use right now rely on qemu under the hood, certainly not chroots...
I could also have decided to go with containers like LXC, LXD, Docker (with conbuilder, whalebuilder, docker-buildpackage), systemd-nspawn (with debspawn), or whatever: I didn't feel those offer the level of isolation that is provided by qemu.
The main downside of this approach is that it is (obviously) slower than native builds. But on modern hardware, that cost should be minimal.
How
Basically, you need this:
sudo mkdir -p /srv/sbuild/qemu/
sudo apt install sbuild-qemu
sudo sbuild-qemu-create -o /srv/sbuild/qemu/unstable.img unstable https://deb.debian.org/debian
Then to make this used by default, add this to ~/.sbuildrc:
# run autopkgtest inside the schroot
$run_autopkgtest = 1;
# tell sbuild to use autopkgtest as a chroot
$chroot_mode = 'autopkgtest';
# tell autopkgtest to use qemu
$autopkgtest_virt_server = 'qemu';
# tell autopkgtest-virt-qemu the path to the image
# use --debug there to show what autopkgtest is doing
$autopkgtest_virt_server_options = [ '--', '/srv/sbuild/qemu/%r-%a.img' ];
# tell plain autopkgtest to use qemu, and the right image
$autopkgtest_opts = [ '--', 'qemu', '/srv/sbuild/qemu/%r-%a.img' ];
# no need to cleanup the chroot after build, we run in a completely clean VM
$purge_build_deps = 'never';
# no need for sudo
$autopkgtest_root_args = '';
Note that the above will use the default autopkgtest (1GB, one core) and qemu (128MB, one core) configuration, which might be a little low on resources. You probably want to be explicit about this, with something like this:
# extra parameters to pass to qemu
# --enable-kvm is not necessary, detected on the fly by autopkgtest
my @_qemu_options = ['--ram-size=4096', '--cpus=2'];
# tell autopkgtest-virt-qemu the path to the image
# use --debug there to show what autopkgtest is doing
$autopkgtest_virt_server_options = [ @_qemu_options, '--', '/srv/sbuild/qemu/%r-%a.img' ];
$autopkgtest_opts = [ '--', 'qemu', @qemu_options, '/srv/sbuild/qemu/%r-%a.img'];
This configuration will:
- create a virtual machine image in
/srv/sbuild/qemuforunstable - tell
sbuildto use that image to create a temporary VM to build the packages - tell
sbuildto runautopkgtest(which should really be default) - tell
autopkgtestto useqemufor builds and for tests
Remaining work
One thing I haven't quite figured out yet is the equivalent of those
two schroot-specific commands from my quick Debian development
guide:
sbuild -c unstable-amd64-sbuild- build in theunstablechroot even though another suite is specified (e.g.UNRElEASED,unstable-backportsorunstable-security)schroot -c unstable-amd64-sbuild- enter theunstablechroot to make tests, changes will be discardedsbuild-shell unstable- enter theunstablechroot to make permanent changes, which will not be discarded
In other words: "just give me a shell in that VM". It seems to me
autopkgtest-virt-qemu should have a magic flag that does that, but
it doesn't look like that's a thing. When that program starts, it just
says ok and sits there. When autopkgtest massages it just the
right way, however, it will do this funky commandline:
qemu-system-x86_64 -m 4096 -smp 2 -nographic -net nic,model=virtio -net user,hostfwd=tcp:127.0.0.1:10022-:22 -object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-pci,rng=rng0,id=rng-device0 -monitor unix:/tmp/autopkgtest-qemu.w1mlh54b/monitor,server,nowait -serial unix:/tmp/autopkgtest-qemu.w1mlh54b/ttyS0,server,nowait -serial unix:/tmp/autopkgtest-qemu.w1mlh54b/ttyS1,server,nowait -virtfs local,id=autopkgtest,path=/tmp/autopkgtest-qemu.w1mlh54b/shared,security_model=none,mount_tag=autopkgtest -drive index=0,file=/tmp/autopkgtest-qemu.w1mlh54b/overlay.img,cache=unsafe,if=virtio,discard=unmap,format=qcow2 -enable-kvm -cpu kvm64,+vmx,+lahf_lm
... which is a typical qemu commandline, I regret to announce. I
managed to somehow boot a VM similar to the one autopkgtest
provisions with this magic incantation:
mkdir tmp
cd tmp
qemu-img create -f qcow2 -F qcow2 -b /srv/sbuild/qemu/unstable-amd64.img overlay.img
mkdir shared
qemu-system-x86_64 -m 4096 -smp 2 -net nic,model=virtio -net user,hostfwd=tcp:127.0.0.1:10022-:22 -object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-pci,rng=rng0,id=rng-device0 -monitor unix:$PWD/monitor,server,nowait -serial unix:$PWD/ttyS0,server,nowait -serial unix:$PWD/ttyS1,server,nowait -virtfs local,id=autopkgtest,path=$PWD/shared,security_model=none,mount_tag=autopkgtest -drive index=0,file=$PWD/overlay.img,cache=unsafe,if=virtio,discard=unmap,format=qcow2 -enable-kvm -cpu kvm64,+vmx,+lahf_lm
That gives you a VM like autopkgtest which has those peculiarities:
- the
shareddirectory is, well, shared with the VM - port
10022is forward to the VM's port22, presumably for SSH, but not SSH server is started by default - the
ttyS1andttyS2UNIX sockets are mapped to the first two serial ports (usenc -Uto talk with those) - the
monitorsocket is a qemu control socket (see the QEMU monitor documentation)
So I guess I could make a script out of this but for now this will have to be good enough.
Nitty-gritty details no one cares about
I'm having a hard time making heads or tails of this, but please bear with me.
In sbuild + schroot, there's this notion that we don't really need
to cleanup after ourselves inside the schroot, as the schroot will
just be delted anyways. This behavior seems to be handled by the
internal "Session Purged" parameter.
At least in lib/Sbuild/Build.pm, we can see this:
my $is_cloned_session = (defined ($session->get('Session Purged')) &&
$session->get('Session Purged') == 1) ? 1 : 0;
[...]
if ($is_cloned_session) {
$self->log("Not cleaning session: cloned chroot in use\n");
} else {
if ($purge_build_deps) {
# Removing dependencies
$resolver->uninstall_deps();
} else {
$self->log("Not removing build depends: as requested\n");
}
}
The schroot builder defines that parameter as:
$self->set('Session Purged', $info->{'Session Purged'});
... which is ... a little confusing to me. $info is:
my $info = $self->get('Chroots')->get_info($schroot_session);
... so I presume that depends on whether the schroot was correctly cleaned up? I stopped digging there...
ChrootUnshare.pm is way more explicit:
$self->set('Session Purged', 1);
I wonder if we should do something like this with the autopkgtest backend. I guess people might technically use it with something else than qemu, but qemu is the typical use case of the autopkgtest backend, in my experience. Or at least certainly with things that cleanup after themselves. Right?
For some reason, before I added this line to my configuration:
$purge_build_deps = 'never';
... the "Cleanup" step would just completely hang. It was quite bizarre.
Who
Thanks lavamind for the introduction to the sbuild-qemu package.