]>
Commit | Line | Data |
---|---|---|
c3e270f4 FB |
1 | --- |
2 | title: Portable Services Introduction | |
4cdca0af | 3 | category: Concepts |
b41a3f66 | 4 | layout: default |
0aff7b75 | 5 | SPDX-License-Identifier: LGPL-2.1-or-later |
c3e270f4 FB |
6 | --- |
7 | ||
44d565ed LP |
8 | # Portable Services Introduction |
9 | ||
66e093de LB |
10 | systemd (since version 239) supports a concept of "Portable Services". |
11 | "Portable Services" are a delivery method for system services that uses | |
12 | two specific features of container management: | |
44d565ed | 13 | |
66e093de LB |
14 | 1. Applications are bundled. I.e. multiple services, their binaries and all |
15 | their dependencies are packaged in an image, and are run directly from it. | |
44d565ed LP |
16 | |
17 | 2. Stricter default security policies, i.e. sand-boxing of applications. | |
18 | ||
66e093de LB |
19 | The primary tool for interacting with Portable Services is `portablectl`, |
20 | and they are managed by the `systemd-portabled` service. | |
44d565ed LP |
21 | |
22 | Portable services don't bring anything inherently new to the table. All they do | |
8c7e2b48 | 23 | is put together known concepts to cover a specific set of use-cases in a |
ba669952 | 24 | slightly nicer way. |
44d565ed | 25 | |
991b4350 | 26 | ## So, what *is* a "Portable Service"? |
44d565ed | 27 | |
8c7e2b48 ZJS |
28 | A portable service is ultimately just an OS tree, either inside of a directory, |
29 | or inside a raw disk image containing a Linux file system. This tree is called | |
30 | the "image". It can be "attached" or "detached" from the system. When | |
31 | "attached", specific systemd units from the image are made available on the | |
32 | host system, then behaving pretty much exactly like locally installed system | |
33 | services. When "detached", these units are removed again from the host, leaving | |
34 | no artifacts around (except maybe messages they might have logged). | |
44d565ed LP |
35 | |
36 | The OS tree/image can be created with any tool of your choice. For example, you | |
37 | can use `dnf --installroot=` if you like, or `debootstrap`, the image format is | |
38 | entirely generic, and doesn't have to carry any specific metadata beyond what | |
39 | distribution images carry anyway. Or to say this differently: the image format | |
40 | doesn't define any new metadata as unit files and OS tree directories or disk | |
41 | images are already sufficient, and pretty universally available these days. One | |
42 | particularly nice tool for creating suitable images is | |
43 | [mkosi](https://github.com/systemd/mkosi), but many other existing tools will | |
44 | do too. | |
45 | ||
8c7e2b48 ZJS |
46 | Portable services may also be constructed from layers, similarly to container |
47 | environments. See [Extension Images](#extension-images) below. | |
48 | ||
44d565ed LP |
49 | If you so will, "Portable Services" are a nicer way to manage chroot() |
50 | environments, with better security, tooling and behavior. | |
51 | ||
991b4350 | 52 | ## Where's the difference to a "Container"? |
44d565ed LP |
53 | |
54 | "Container" is a very vague term, after all it is used for | |
55 | systemd-nspawn/LXC-type OS containers, for Docker/rkt-like micro service | |
56 | containers, and even certain 'lightweight' VM runtimes. | |
57 | ||
8c7e2b48 ZJS |
58 | "Portable services" do not provide a fully isolated environment to the payload, |
59 | like containers mostly intend to. Instead, they are more like regular system | |
60 | services, can be controlled with the same tools, are exposed the same way in | |
61 | all infrastructure, and so on. The main difference is that they use a different | |
62 | root directory than the rest of the system. Hence, the intent is not to run | |
63 | code in a different, isolated environment from the host — like most containers | |
64 | would — but to run it in the same environment, but with stricter access | |
65 | controls on what the service can see and do. | |
66 | ||
67 | One point of differentiation: since programs running as "portable services" are | |
68 | pretty much regular system services, they won't run as PID 1 (like they would | |
69 | under Docker), but as normal processes. A corollary of that is that they aren't | |
70 | supposed to manage anything in their own environment (such as the network) as | |
71 | the execution environment is mostly shared with the rest of the system. | |
44d565ed LP |
72 | |
73 | The primary focus use-case of "portable services" is to extend the host system | |
74 | with encapsulated extensions, but provide almost full integration with the rest | |
8c7e2b48 ZJS |
75 | of the system, though possibly restricted by security knobs. This focus |
76 | includes system extensions otherwise sometimes called "super-privileged | |
44d565ed LP |
77 | containers". |
78 | ||
79 | Note that portable services are only available for system services, not for | |
00473ac8 PL |
80 | user services (i.e. the functionality cannot be used for the stuff |
81 | bubblewrap/flatpak is focusing on). | |
44d565ed | 82 | |
991b4350 | 83 | ## Mode of Operation |
44d565ed | 84 | |
00473ac8 | 85 | If you have a portable service image, maybe in a raw disk image called |
44d565ed LP |
86 | `foobar_0.7.23.raw`, then attaching the services to the host is as easy as: |
87 | ||
88 | ``` | |
a00ff671 | 89 | # portablectl attach foobar_0.7.23.raw |
44d565ed LP |
90 | ``` |
91 | ||
92 | This command does the following: | |
93 | ||
8c7e2b48 ZJS |
94 | 1. It dissects the image, checks and validates the `os-release` file of the |
95 | image, and looks for all included unit files. | |
44d565ed LP |
96 | |
97 | 2. It copies out all unit files with a suffix of `.service`, `.socket`, | |
98 | `.target`, `.timer` and `.path`. whose name begins with the image's name | |
8c7e2b48 ZJS |
99 | (with `.raw` removed), truncated at the first underscore if there is one. |
100 | This prefix name generated from the image name must be followed by a ".", | |
101 | "-" or "@" character in the unit name. Or in other words, given the image | |
102 | name of `foobar_0.7.23.raw` all unit files matching | |
44d565ed LP |
103 | `foobar-*.{service|socket|target|timer|path}`, |
104 | `foobar@.{service|socket|target|timer|path}` as well as | |
105 | `foobar.*.{service|socket|target|timer|path}` and | |
106 | `foobar.{service|socket|target|timer|path}` are copied out. These unit files | |
83f72cd6 LP |
107 | are placed in `/etc/systemd/system.attached/` (which is part of the normal |
108 | unit file search path of PID 1, and thus loaded exactly like regular unit | |
109 | files). Within the images the unit files are looked for at the usual | |
110 | locations, i.e. in `/usr/lib/systemd/system/` and `/etc/systemd/system/` and | |
111 | so on, relative to the image's root. | |
44d565ed LP |
112 | |
113 | 3. For each such unit file a drop-in file is created. Let's say | |
114 | `foobar-waldo.service` was one of the unit files copied to | |
83f72cd6 LP |
115 | `/etc/systemd/system.attached/`, then a drop-in file |
116 | `/etc/systemd/system.attached/foobar-waldo.service.d/20-portable.conf` is | |
117 | created, containing a few lines of additional configuration: | |
44d565ed LP |
118 | |
119 | ``` | |
120 | [Service] | |
121 | RootImage=/path/to/foobar.raw | |
122 | Environment=PORTABLE=foobar | |
123 | LogExtraFields=PORTABLE=foobar | |
124 | ``` | |
125 | ||
126 | 4. For each such unit a "profile" drop-in is linked in. This "profile" drop-in | |
127 | generally contains security options that lock down the service. By default | |
8c7e2b48 ZJS |
128 | the `default` profile is used, which provides a medium level of security. |
129 | There's also `trusted`, which runs the service with no restrictions, i.e. in | |
130 | the host file system root and with full privileges. The `strict` profile | |
131 | comes with the toughest security restrictions. Finally, `nonetwork` is like | |
132 | `default` but without network access. Users may define their own profiles | |
133 | too (or modify the existing ones). | |
44d565ed LP |
134 | |
135 | And that's already it. | |
136 | ||
00473ac8 | 137 | Note that the images need to stay around (and in the same location) as long as the |
44d565ed | 138 | portable service is attached. If an image is moved, the `RootImage=` line |
8c7e2b48 ZJS |
139 | written to the unit drop-in would point to an non-existent path, and break |
140 | access to the image. | |
44d565ed LP |
141 | |
142 | The `portablectl detach` command executes the reverse operation: it looks for | |
8c7e2b48 | 143 | the drop-ins and the unit files associated with the image, and removes them. |
44d565ed | 144 | |
00473ac8 | 145 | Note that `portablectl attach` won't enable or start any of the units it copies |
5d55791e LB |
146 | out by default, but `--enable` and `--now` parameter are available as shortcuts. |
147 | The same is true for the opposite `detach` operation. | |
148 | ||
8c7e2b48 ZJS |
149 | The `portablectl reattach` command combines a `detach` with an `attach`. It is |
150 | useful in case an image gets upgraded, as it allows performing a `restart` | |
151 | operation on the units instead of `stop` plus `start`, thus providing lower | |
152 | downtime and avoiding losing runtime state associated with the unit such as the | |
153 | file descriptor store. | |
44d565ed | 154 | |
991b4350 | 155 | ## Requirements on Images |
44d565ed LP |
156 | |
157 | Note that portable services don't introduce any new image format, but most OS | |
158 | images should just work the way they are. Specifically, the following | |
159 | requirements are made for an image that can be attached/detached with | |
160 | `portablectl`. | |
161 | ||
957848db | 162 | 1. It must contain an executable that shall be invoked, along with all its |
8c7e2b48 ZJS |
163 | dependencies. Any binary code needs to be compiled for an architecture |
164 | compatible with the host. | |
44d565ed LP |
165 | |
166 | 2. The image must either be a plain sub-directory (or btrfs subvolume) | |
167 | containing the binaries and its dependencies in a classic Linux OS tree, or | |
168 | must be a raw disk image either containing only one, naked file system, or | |
169 | an image with a partition table understood by the Linux kernel with only a | |
170 | single partition defined, or alternatively, a GPT partition table with a set | |
171 | of properly marked partitions following the [Discoverable Partitions | |
19ac32cd | 172 | Specification](https://systemd.io/DISCOVERABLE_PARTITIONS). |
44d565ed LP |
173 | |
174 | 3. The image must at least contain one matching unit file, with the right name | |
175 | prefix and suffix (see above). The unit file is searched in the usual paths, | |
176 | i.e. primarily /etc/systemd/system/ and /usr/lib/systemd/system/ within the | |
177 | image. (The implementation will check a couple of other paths too, but it's | |
178 | recommended to use these two paths.) | |
179 | ||
6f61b14d ДГ |
180 | 4. The image must contain an os-release file, either in `/etc/os-release` or |
181 | `/usr/lib/os-release`. The file should follow the standard format. | |
182 | ||
183 | 5. The image must contain the files `/etc/resolv.conf` and `/etc/machine-id` | |
184 | (empty files are ok), they will be bind mounted from the host at runtime. | |
44d565ed | 185 | |
957848db LP |
186 | 6. The image must contain directories `/proc/`, `/sys/`, `/dev/`, `/run/`, |
187 | `/tmp/`, `/var/tmp/` that can be mounted over with the corresponding version | |
188 | from the host. | |
189 | ||
190 | 7. The OS might require other files or directories to be in place. For example, | |
191 | if the image is built based on glibc, the dynamic loader needs to be | |
192 | available in `/lib/ld-linux.so.2` or `/lib64/ld-linux-x86-64.so.2` (or | |
193 | similar, depending on architecture), and if the distribution implements a | |
194 | merged `/usr/` tree, this means `/lib` and/or `/lib64` need to be symlinks | |
195 | to their respective counterparts below `/usr/`. For details see your | |
196 | distribution's documentation. | |
197 | ||
198 | Note that images created by tools such as `debootstrap`, `dnf --installroot=` | |
8c7e2b48 ZJS |
199 | or `mkosi` generally satisfy all of the above. If you wonder what the most |
200 | minimal image would be that complies with the requirements above, it could | |
201 | consist of this: | |
44d565ed LP |
202 | |
203 | ``` | |
570ee29c LP |
204 | /usr/bin/minimald # a statically compiled binary |
205 | /usr/lib/systemd/system/minimal-test.service # the unit file for the service, with ExecStart=/usr/bin/minimald | |
206 | /usr/lib/os-release # an os-release file explaining what this is | |
207 | /etc/resolv.conf # empty file to mount over with host's version | |
208 | /etc/machine-id # ditto | |
209 | /proc/ # empty directory to use as mount point for host's API fs | |
210 | /sys/ # ditto | |
211 | /dev/ # ditto | |
212 | /run/ # ditto | |
213 | /tmp/ # ditto | |
214 | /var/tmp/ # ditto | |
44d565ed LP |
215 | ``` |
216 | ||
217 | And that's it. | |
218 | ||
219 | Note that qualifying images do not have to contain an init system of their | |
220 | own. If they do, it's fine, it will be ignored by the portable service logic, | |
221 | but they generally don't have to, and it might make sense to avoid any, to keep | |
222 | images minimal. | |
223 | ||
957848db | 224 | If the image is writable, and some of the files or directories that are |
8c7e2b48 ZJS |
225 | overmounted from the host do not exist yet they will be automatically created. |
226 | On read-only, immutable images (e.g. squashfs images) all files and directories | |
227 | to over-mount must exist already. | |
957848db | 228 | |
44d565ed | 229 | Note that as no new image format or metadata is defined, it's very |
00473ac8 | 230 | straightforward to define images than can be made use of in a number of |
44d565ed LP |
231 | different ways. For example, by using `mkosi -b` you can trivially build a |
232 | single, unified image that: | |
233 | ||
234 | 1. Can be attached as portable service, to run any container services natively | |
235 | on the host. | |
236 | ||
237 | 2. Can be run as OS container, using `systemd-nspawn`, by booting the image | |
238 | with `systemd-nspawn -i -b`. | |
239 | ||
240 | 3. Can be booted directly as VM image, using a generic VM executor such as | |
241 | `virtualbox`/`qemu`/`kvm` | |
242 | ||
243 | 4. Can be booted directly on bare-metal systems. | |
244 | ||
245 | Of course, to facilitate 2, 3 and 4 you need to include an init system in the | |
8c7e2b48 ZJS |
246 | image. To facilitate 3 and 4 you also need to include a boot loader in the |
247 | image. As mentioned, `mkosi -b` takes care of all of that for you, but any | |
248 | other image generator should work too. | |
44d565ed | 249 | |
8a129c80 LP |
250 | The |
251 | [os-release(5)](https://www.freedesktop.org/software/systemd/man/os-release.html) | |
252 | file may optionally be extended with a `PORTABLE_PREFIXES=` field listing all | |
253 | supported portable service prefixes for the image (see above). This is useful | |
254 | for informational purposes (as it allows recognizing portable service images | |
255 | from their contents as such), but is also useful to protect the image from | |
256 | being used under a wrong name and prefix. This is particularly relevant if the | |
257 | images are cryptographically authenticated (via Verity or a similar mechanism) | |
258 | as this way the (not necessarily authenticated) image file name can be | |
259 | validated against the (authenticated) image contents. If the field is not | |
260 | specified the image will work fine, but is not necessarily recognizable as | |
261 | portable service image, and any set of units included in the image may be | |
262 | attached, there are no restrictions enforced. | |
263 | ||
5d55791e LB |
264 | ## Extension Images |
265 | ||
266 | Portable services can be delivered as one or multiple images that extend the base | |
267 | image, and are combined with OverlayFS at runtime, when they are attached. This | |
268 | enables a workflow that splits the base 'runtime' from the daemon, so that multiple | |
269 | portable services can share the same 'runtime' image (libraries, tools) without | |
270 | having to include everything each time, with the layering happening only at runtime. | |
271 | The `--extension` parameter of `portablectl` can be used to specify as many upper | |
272 | layers as desired. On top of the requirements listed in the previous section, the | |
8c7e2b48 | 273 | following must be also be observed: |
5d55791e | 274 | |
8c7e2b48 ZJS |
275 | 1. The base/OS image must contain an `os-release file`, either in `/etc/os-release` |
276 | or `/usr/lib/os-release`, in the standard format. | |
5d55791e LB |
277 | |
278 | 2. The upper extension(s) image(s) must contain an extension-release file in | |
279 | `/usr/lib/extension-release.d/`, with an `ID=` and `SYSEXT_LEVEL=`/`VERSION_ID=` | |
280 | matching the base image. | |
281 | ||
282 | 3. The base/OS image does not need to have any unit files. | |
283 | ||
284 | 4. The upper extension(s) image(s) must at least contain one matching unit file each, | |
285 | with the right name prefix and suffix (see above). | |
286 | ||
2ef20244 LB |
287 | 5. As with the base/OS image, the upper extension(s) image(s) must be a plain |
288 | sub-directory, a btrfs subvolume or a raw disk image. | |
289 | ||
5d55791e | 290 | ``` |
a00ff671 | 291 | # portablectl attach --extension foobar_0.7.23.raw debian-runtime_11.1.raw foobar |
2ef20244 | 292 | # portablectl attach --extension barbaz_7.0.23/ debian-runtime_11.1.raw barbaz |
5d55791e LB |
293 | ``` |
294 | ||
991b4350 | 295 | ## Execution Environment |
44d565ed LP |
296 | |
297 | Note that the code in portable service images is run exactly like regular | |
8c7e2b48 | 298 | services. Hence there's no new execution environment to consider. And, unlike |
44d565ed LP |
299 | Docker would do it, as these are regular system services they aren't run as PID |
300 | 1 either, but with regular PID values. | |
301 | ||
991b4350 | 302 | ## Access to host resources |
44d565ed LP |
303 | |
304 | If services shipped with this mechanism shall be able to access host resources | |
305 | (such as files or AF_UNIX sockets for IPC), use the normal `BindPaths=` and | |
306 | `BindReadOnlyPaths=` settings in unit files to mount them in. In fact the | |
307 | `default` profile mentioned above makes use of this to ensure | |
308 | `/etc/resolv.conf`, the D-Bus system bus socket or write access to the logging | |
309 | subsystem are available to the service. | |
310 | ||
991b4350 | 311 | ## Instantiation |
44d565ed LP |
312 | |
313 | Sometimes it makes sense to instantiate the same set of services multiple | |
314 | times. The portable service concept does not introduce a new logic for this. It | |
8c7e2b48 | 315 | is recommended to use the regular systemd unit templating for this, i.e. to |
44d565ed LP |
316 | include template units such as `foobar@.service`, so that instantiation is as |
317 | simple as: | |
318 | ||
319 | ``` | |
a00ff671 | 320 | # portablectl attach foobar_0.7.23.raw |
44d565ed LP |
321 | # systemctl enable --now foobar@instancea.service |
322 | # systemctl enable --now foobar@instanceb.service | |
323 | … | |
324 | ``` | |
325 | ||
326 | The benefit of this approach is that templating works exactly the same for | |
327 | units shipped with the OS itself as for attached portable services. | |
328 | ||
991b4350 | 329 | ## Immutable images with local data |
44d565ed LP |
330 | |
331 | It's a good idea to keep portable service images read-only during normal | |
332 | operation. In fact all but the `trusted` profile will default to this kind of | |
333 | behaviour, by setting the `ProtectSystem=strict` option. In this case writable | |
334 | service data may be placed on the host file system. Use `StateDirectory=` in | |
335 | the unit files to enable such behaviour and add a local data directory to the | |
336 | services copied onto the host. | |
6d6104e0 ZJS |
337 | |
338 | ## Links | |
339 | ||
340 | [portablectl(1)](https://www.freedesktop.org/software/systemd/man/portablectl.html)<br> | |
341 | [systemd-portabled.service(8)](https://www.freedesktop.org/software/systemd/man/systemd-portabled.service.html)<br> | |
342 | [Walkthrough for Portable Services](https://0pointer.net/blog/walkthrough-for-portable-services.html)<br> | |
343 | [Repo with examples](https://github.com/systemd/portable-walkthrough) |