Project

General

Profile

Setting up systemd-nspawn VMs » History » Version 12

Peter Amstutz, 05/08/2025 01:38 AM

1 1 Brett Smith
h1. Setting up systemd-nspawn VMs
2
3
This page describes how to use systemd-nspawn to create VMs for development and testing. This page is a guide, *not* step-by-step instructions. *If you just copy+paste commands without actually reading the instructions, you will BREAK YOUR OWN NETWORKING and I will not be held responsible.*
4
5 5 Brett Smith
{{toc}}
6
7 1 Brett Smith
h2. One-time supervisor host setup
8
9
h3. Install systemd-nspawn and image build tools
10
11
<pre>sudo apt install systemd-container debootstrap
12
</pre>
13
14
@systemd-container@ packages systemd-nspawn and friends. @debootstrap@ is used to build VMs.
15
16
"Install Ansible":https://dev.arvados.org/projects/arvados/wiki/Hacking_prerequisites#Install-Ansible the same way we do for development. I'm fobbing you off to that page so you know what version of Ansible we're standardized on.
17
18
h3. Enable systemd network services
19
20
Unsurprisingly systemd-nspawn integrates well with other systemd components. The easiest way to get your VMs networked is to install systemd's network services:
21
22
<pre>sudo systemctl enable --now systemd-networkd systemd-resolved
23
</pre>
24
25
Note systemd-networkd only manages configured interfaces. On Debian the default configuration should play nice with NetworkManager. systemd-resolved and NetworkManager also cooperate.
26
27
If you refuse to do this, refer to the "Networking Options of systemd-nspawn":https://www.freedesktop.org/software/systemd/man/latest/systemd-nspawn.html#Networking%20Options to evaluate alternatives.
28
29
h3. NAT and firewall
30
31
systemd-networkd runs a DHCP server that provides private addresses to the virtual machines. You will need to configure your firewall to allow these DHCP requests, and to NAT traffic from those interfaces. These steps are specific to the host firewall; if yours isn't documented below, feel free to add it.
32
33
h4. ufw
34
35
For NAT, make sure these lines in @/etc/ufw/sysctl.conf@ are all set to @1@:
36
37
<pre>net/ipv4/ip_forward=1
38
net/ipv6/conf/default/forwarding=1
39
net/ipv6/conf/all/forwarding=1
40
</pre>
41
42
If you changed any, restart ufw. Then these are the rules you need:
43
44
<pre><code class="sh">for iface in vb-+ ve-+ vz-+; do
45
  sudo ufw rule  allow in on "$iface" proto udp to 0.0.0.0/0 port 67,68 comment "systemd-nspawn DHCP"
46
  sudo ufw route allow in on "$iface"
47
done
48
</code></pre>
49
50
h3. Filesystem
51
52
systemd-nspawn stores both images and containers under @/var/lib/machines@. It works with any filesystem, but if the filesystem is btrfs, it can optimize various operations with snapshots, etc. "Here's a blog post outlining some of the gains":https://idle.nprescott.com/2022/systemd-nspawn-and-btrfs.html.
53
54
I would recommend any deployment, and especially production deployments, have a btrfs filesystem at @/var/lib/machines@. Since this is likely to grow large, a dedicated partition is a good idea too.
55
56 3 Brett Smith
h3. Resolving VM names
57
58 4 Brett Smith
You can configure your host system to resolve the names of running VMs so you can easily SSH into them, open them in your browser, write them in Ansible inventories, etc. Edit @/etc/nsswitch.conf@, find the @hosts@ line, and make sure that @mymachines@ appears before any @dns@ or @resolve@ entries. See "nss-mymachines(2)":https://www.freedesktop.org/software/systemd/man/latest/nss-mymachines.html.
59 3 Brett Smith
60 1 Brett Smith
h2. Build a systemd-nspawn container image
61
62 6 Brett Smith
The Arvados source includes an Ansible playbook to create an image from scratch with @debootstrap@. Write this variables file as @nspawn-image.yml@ and edit the values as you like:
63 1 Brett Smith
64 11 Peter Amstutz
<pre><code class="yaml">
65 12 Peter Amstutz
### Stuff you probably want to customize ###
66
# The name of the user account to create in the VM.  The default value is "admin".
67
#image_username: "admin"
68
69 11 Peter Amstutz
# SSH public key string or URL which will be provisioned as an authorized key for 
70
# the user account above.
71
image_authorized_keys: "FIXME"
72
73 12 Peter Amstutz
### Stuff you may want to customize. ###
74 1 Brett Smith
# The codename of the release to install.
75 11 Peter Amstutz
debootstrap_suite: bookworm
76
77
# The name of the image that will show up in "machinectl list-images" as well as
78
# "machinectl start" and "machinectl stop"
79 12 Peter Amstutz
# Default name is the distribution version being set up (e.g. debian_bookworm), but 
80
# you can also call this whatever you want, like "my_arvados_test"
81 11 Peter Amstutz
image_name: "debian_{{ debootstrap_suite }}"
82
83 1 Brett Smith
# The mirror to install the release from.
84 6 Brett Smith
# The commented-out setting below is appropriate for Ubuntu.
85
debootstrap_mirror: "http://deb.debian.org/debian"
86
#debootstrap_mirror: "http://archive.ubuntu.com/ubuntu"
87
88 12 Peter Amstutz
### Some additional user account customization ###
89 6 Brett Smith
# A hash of the user's password. The default is no password.
90
# See <https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#how-do-i-generate-encrypted-passwords-for-the-user-module>
91
image_passhash: "!"
92
# Other settings for the created user.
93
image_gecos: ""
94
image_shell: /usr/bin/bash
95 1 Brett Smith
</code></pre>
96
97
With your Ansible virtualenv activated, run:
98
99 8 Peter Amstutz
<pre><code class="sh">ansible-playbook --ask-become-pass --extra-vars @nspawn-image.yml arvados/tools/ansible/build-debian-nspawn-vm.yml
100 1 Brett Smith
</code></pre>
101
102
If this succeeds, you have @/var/lib/machines/MACHINE@ with a base install and configuration.
103
104
h3. Consider Cloning
105
106
This is probably a good time to mention, you should think about these machine subdirectories more like VM disks rather than Docker images. If you simply boot your new VM and start making changes to it, those changes will be permanent. If you want an ephemeral VM you need to explicitly ask for that. Personally I prefer to never boot this bootstrapped VM directly, instead I run @machinectl clone BASE_NAME MACHINE@—then I treat @BASE_NAME@ like an "image" that I never touch, and @MACHINE@ more like a traditional stateful VM.
107
108
h2. Configure the VM
109
110
VMs are configured using the file at @/etc/systemd/nspawn/MACHINE.nspawn@. The defaults are pretty good and you don't have to write much. The main thing you'll want to do is tell it how to resolve DNS, and consider other networking:
111
112
<pre><code class="ini">[Exec]
113
ResolvConf=bind-uplink
114
115
[Network]
116
# If you want multiple VMs to be able to talk to each other,
117
# put them all in the same zone:
118
#Zone=YOURZONE
119
120
[Files]
121
# If you want to make things on the host available in the VM,
122
# do that here:
123
Bind=/dev/fuse
124
#BindReadOnly=/home/YOU/SUBDIR
125
</code></pre>
126
127
Refer to "systemd.nspawn":https://www.freedesktop.org/software/systemd/man/latest/systemd.nspawn.html for all the options.
128
129
h2. Privilege a Container
130
131 10 Peter Amstutz
If you want to run FUSE, Docker, or Singularity inside your VM, that requires additional privileges. We have an Ansible playbook to automate that too. To grant privileges for all these services, with your Ansible virtualenv activated, run (@-e@ is the short version of @--extra-vars@):
132 9 Peter Amstutz
133 1 Brett Smith
<pre><code class="sh">ansible-playbook -e container_name=MACHINE arvados/tools/ansible/privilege-nspawn-vm.yml
134
</code></pre>
135
136
You can exclude some privileges by setting @SERVICE_privileges=absent@. For example, if you don't intend to run Singularity in this VM:
137
138
<pre><code class="sh">ansible-playbook -e "container_name=MACHINE singularity_privileges=absent" arvados/tools/ansible/privilege-nspawn-vm.yml
139
</code></pre>
140
141
See the comments at the top of source:tools/ansible/privilege-nspawn-vm.yml for details.
142
143
h2. Interacting with VMs
144
145
"machinectl":https://www.freedesktop.org/software/systemd/man/latest/machinectl.html is the primary command to interact with both containers and the underlying disk images:
146
147
<pre><code class="sh">machinectl start MACHINE
148
machinectl stop MACHINE
149
machinectl shell YOU@MACHINE
150
151
machinectl clone MACHINE1 MACHINE2
152 2 Brett Smith
machinectl remove MACHINE [MACHINE2 ...]
153
</code></pre>
154
155
Refer to the man page for full details. Note that running containers run under the <code>systemd-nspawn@MACHINE</code> systemd service, and you can interact with that with all the usual tools. (Try <code>journalctl -u systemd-nspawn@MACHINE</code>.)