Entering images with lchroot
lchroot opens a shell, or runs one command, inside an OS image on the controller: to install a
package, edit a file, rebuild an initramfs. It looks like a chroot and it is used like one, but it
is not one. The image is entered in a sandbox that has its own /dev, /proc, /sys and
/run, its own process and hostname namespaces, and no capability to touch the controller's
mounts. Nothing is mounted on the controller's own mount table, so nothing can be left behind;
nothing started inside outlives the session; and a dnf scriptlet that restarts a service or
kills a process reaches the image's namespace, not the controller's.
# lchroot compute-image # a shell in the image
# lchroot compute-image dnf -y install vim # one command, non-interactive
# lchroot --ro compute-image # inspect, change nothing
# lchroot --path /trinity/images/scratch # a rootfs by path, without Luna
The name is an image known to Luna (luna osimage list); --path enters any directory that
holds a root filesystem, for an image that is still being built or is not registered. Pack the
image afterwards, as always — see Image management.
One architecture is not a limit
An x86_64 controller can enter, customise and pack an aarch64 image, and the other way
round. When the image's architecture differs from the controller's, lchroot sets up user-mode
emulation for the session: the image's own binaries run through a bundled static qemu, which
translates the foreign instructions and issues ordinary calls to the controller's kernel. Inside
the image, dnf, apt, dracut and the rest simply work; from the outside, one controller
serves a mixed fleet. Interpreters for aarch64, riscv64 and x86_64 ship with the utilities,
so nothing has to be installed on the controller for it. --no-emulate refuses a foreign image
instead, for when that is the intended check.
The same mechanism serves the image builds: compute-arm is built on an x86_64 controller by
the ordinary playbooks, and luna osimage pack rebuilds a foreign image's initramfs by running
dracut inside it through lchroot. The qemu-static command exposes the emulation setup for
callers outside lchroot, such as a bootstrap with dnf --installroot --forcearch:
# qemu-static aarch64
# qemu-static --image /trinity/images/compute-arm
One session at a time
An image is locked while a session holds it; luna osimage pack takes the same lock. Two
sessions writing the same package database at once is the corruption this prevents.
# lchroot --status compute-image # who holds it, and the processes inside
# lchroot --force compute-image # stop the holding session, then enter
A lock left by a session that died is reclaimed on the next entry. Against a live session, a
plain entry on a terminal shows the holder and asks before stopping it; --force stops it
without asking. Without a terminal — cron, a pipeline — a held image is refused rather than
taken over silently, unless --force is given. Without a terminal a command is also required: a
bare entry would start a shell that reads nothing and exits, looking like success.
On an HA pair
Images are changed on the active controller. On the standby, a read-write entry is refused
(exit code 10); --ro works on either controller, so inspection and debugging do not depend on
where you are logged in. --path mode does not consult Luna at all and is not gated.
Seeing what it would do
# lchroot --dry-run compute-image
prints the sandbox plan — the binds, the namespaces, the capability drop, the emulation if any — and runs nothing.
| Exit code | Meaning |
|---|---|
| 0 | done |
| 7 | bad arguments, configuration or image |
| 8 | emulation could not be set up |
| 9 | the image is held by another session |
| 10 | read-write entry refused on a standby controller |
| 130 | interrupted |