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