Knowledge
systemd-run
is needed to run lxc.- lxc is a glorified chroot. It uses cgroups like almost all container solutions.
- UID is user id and GID is group id.
- To run a lxc container you need to delegate cpu, io, memory and pids. By default a user can only delegate memory and pids.
- debian uses
systemd-networkd
- firefox requires
systemd
to start the container. - If you attach with a clear environment the terminal may behave funky,
export TERM=xterm-256color
to fix it.
Example
user@host ~> cat /proc/self/cgroup 1
0::/user.slice/user-1000.slice/session-1.scope
user@host ~> systemd-run --user --scope --collect --shell 1
Running scope as unit: run-r7f23525aef1e46c0aa667f4b2b2a5705.scope
user@host ~> cat /proc/self/cgroup
0::/user.slice/user-1000.slice/user@1000.service/app.slice/run-r7f23525aef1e46c0aa667f4b2b2a5705.scope
user@host ~> lxc-create -t download -n penguin -- --dist debian --release bullseye --arch amd64
Using image from local cache
Unpacking the rootfs
---
You just created a Debian bullseye amd64 (20220603_05:24) container.
To enable SSH, run: apt install openssh-server
No default root or user password are set by LXC.
user@host ~> lxc-ls --fancy
NAME STATE AUTOSTART GROUPS IPV4 IPV6 UNPRIVILEGED
firefox STOPPED 0 - - - true
spyder STOPPED 0 - - - true
penguin STOPPED 0 - - - true
user@host ~> lxc-start penguin
user@host ~> lxc-attach penguin --clear-env
root@penguin:/# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
[...]
user@host ~> lxc-ls --fancy
NAME STATE AUTOSTART GROUPS IPV4 IPV6 UNPRIVILEGED
firefox STOPPED 0 - - - true
spyder STOPPED 0 - - - true
penguin RUNNING 0 - - - true
user@host ~> lxc-stop penguin
user@host ~> lxc-destroy penguin
user@host ~> lxc-ls --fancy
NAME STATE AUTOSTART GROUPS IPV4 IPV6 UNPRIVILEGED
firefox STOPPED 0 - - - true
spyder STOPPED 0 - - - true
If you do not execute systemd-run
you will get an error like the following:
user@host ~> lxc-start firefox -F
Failed to mount cgroup at /sys/fs/cgroup/systemd: Operation not permitted
[!!!!!!] Failed to mount API filesystems.
Exiting PID 1...
Basic commands
lxc
Common options
--logfile|-o
Output to an alternate log FILE.--logpriority|-l
Set log priority to LEVEL. FATAL, ALERT, CRIT, WARN, ERROR (def), NOTICE, INFO, DEBUG, TRACE.--lxcpath|-P
Use an alternate container path.
lxc-ls
lxc-ls [-1] [--active] [--frozen] [--running] [--stopped] [--defined] [-f] [-F format] [-g groups] [--nesting=NUM] [--filter=regex]
lxc-ls --fancy
---
NAME STATE AUTOSTART GROUPS IPV4 IPV6 UNPRIVILEGED
firefox STOPPED 0 - - - true
gui STOPPED 0 - - - true
lxc-start
lxc-start {-n name} [-f config_file] [-c console_device] [-L console_logfile] [-d] [-F] [-p pid_file] [-s KEY=VAL] [-C]
[--share-[net|ipc|uts] name|pid] [command]
lxc-start --foreground --name firefox --logfile=/tmp/test/log --logpriority=DEBUG
--foreground|-F
Run the container in the foreground. Shows errors and console output.--name|-n
(optional) Name of the container.--daemon|-d
Run the container as a daemon without a TTY. It does not show errors.
lxc-attach
lxc-attach {-n, --name name} [-f, --rcfile config_file] [-a, --arch arch] [-e, --elevated-privileges privileges]
[-s, --namespaces namespaces] [-R, --remount-sys-proc] [--keep-env] [--clear-env]
[-v, --set-var variable] [--keep-var variable] [-u, --uid uid] [-g, --gid gid] [-- command]
lxc-attach -n firefox --clear-env
lxc-attach -n firefox --clear-env --uid 1000 -- firefox
--uid|-u
Executes the command with user ID uid inside the container.--gid|--g
Executes the command with group ID gid inside the container.--clear-env
lxc-create
lxc-create {-n name} [-f config_file] {-t template} [-B backingstore] [-- template-options]
lxc-create penguin -t download
lxc-create -t download -n firefox -- --dist debian --release bullseye --arch amd64
- Show all images with
lxc-create -t download -- --list
- The templates are located in
/usr/share/lxc/templates
.
lxc-console
lxc-console {-n name} [-e escape character] [-t ttynum]
lxc-console -n test
Ctrl+a q
to exit.
lxc-info
lxc-info {-n name} [-c KEY] [-s] [-p] [-i] [-S] [-H]
lxc-info firefox
lxc-stop
lxc-stop {-n name} [-W] [-r] [-t timeout] [-k] [--nokill] [--nolock]
lxc-stop firefox
--reboot|-r
Request a reboot of the container.--kill|-k
Rather than requesting a clean shutdown of the container, explicitly kill all tasks in the container.--nokill
Only request a clean shutdown, do not kill the container tasks if the clean shutdown fails.
lxc-destroy
lxc-destroy {-n name} [-f] [-s]
lxc-destroy -n test
lxc-checkconfig
lxc-checkconfig
Check the current kernel for lxc support
This indicates there is not support for cgroups v1.
[...]
Cgroup v1 systemd controller: missing
Cgroup v1 freezer controller: missing
Cgroup ns_cgroup: required
[...]
lxc-checkpoint
lxc-checkpoint {-n name} {-D PATH} [-r] [-s] [-v] [-d] [-F]
systemd
systemd-run
systemd-run --user --scope --collect --shell
systemd-run --user --scope --collect lxc-start container
systemd-run --unit=myshell --user --scope -p "Delegate=yes" lxc-start --name penguin --foreground
systemd-run --unit=myshell1 --user --property=AllowedCPUs=0,1 -p TasksMax=100 -p Delegate=yes lxc-start -n penguin
--user
Talk to the service manager of the calling user, rather than the service manager of the system.--unit
Use this unit name instead of an automatically generated one.--scope
Create a transient .scope unit instead of the default transient .service unit--collect|-G
Unload the transient unit after it completed, even if it failed.--shell
requests an interactive shell in the current working directory--property|-p
Sets a property on the scope or service unit that is created."Delegate=yes"
allow delegation of everything"Delegate=cpu cpuset io memory pids"
allow delegation of cpu, cpuset, io, memory and pids.
For more info check man systemd-run
and man systemd.resource-control
.
Show current systemd scope.
cat /proc/self/cgroup
---
0::/user.slice/user-1000.slice/user@1000.service/app.slice/run-r463d33efe17540179ac04d01f657faaa.scope
lxc config
Defaults
~/.config/lxc/defaults.conf
---
````
Network
/etc/lxc/lxc-usernet
---
# Enable cpu, cpuset and io delegation
By default, a non-root user can only get *memory controller* and *pids controller* to be delegated.
See the currently allowed delegations.
cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/cgroup.controllers
memory pids
To allow delegation of other controllers such as *cpu*, *cpuset*, and *io*, run the following commands:
sudo mkdir -p /etc/systemd/system/user@.service.d cat <<EOF | sudo tee /etc/systemd/system/user@.service.d/delegate.conf [Service] Delegate=cpu cpuset io memory pids EOF sudo systemctl daemon-reload
Delegating *cpuset* is recommended as well as *cpu*.
Delegating *cpuset* requires *systemd* 244 or later.
After changing the *systemd* configuration, you need to re-login or reboot the host.
Rebooting the host is recommended.
---
# Privileged containers as root (default)
*UID* `0` inside the container is *UID* `0` outside the container.
---
# GUI without mapping the user
## Configure subuid & subgid
Authorize your *UIDs* to map ranges of *UIDs* from its namespace into child namespaces.
You can write only `user:100000:100000` but I wanted to do it differently as I have a different setup.
/etc/subuid
root:1000000:65536 lxd:1065536:65536 user:1131072:65536
/etc/subgid
root:1000000:65536 lxd:1065536:65536 user:1131072:65536
`user:1131072:65536` means that user `users` can map `65536` UIDs from `1131072`, so from `1131072` to `1196608`.
You need to reboot to apply the changes.
Some people only map `65530` but that will brake the system as user `nobody` uses `65534` and it is needed.
## Allow the user to map network devices
Allow user *user* to create up to 30 *veth* (virtual ethernet) devices connected to the *virbr2* network bridge.
/etc/lxc/lxc-usernet
yu veth virbr2 30
## Change the default configuration for the containers
~/.config/lxc/default.conf
lxc.net.0.type = veth lxc.net.0.link = virbr2 lxc.net.0.flags = up lxc.net.0.hwaddr = 00:16:3e:xx:xx:xx lxc.idmap = u 0 1131072 65536 lxc.idmap = g 0 1131072 65536
## Configure the host
### pulseaudio
Configure *pulseaudio* to create a socket at `/run/user/1000/pulse-socket`
/etc/pulse/default.pa load-module module-native-protocol-unix auth-anonymous=1 socket=/run/user/1000/pulse-socket
Restart *pulseaudio*.
rm -rf /tmp/pulse /run/user/1000/pulse ~/.pulse* ~/.config/pulse pulseaudio -k pulseaudio --start
### Wayland
Nothing, it already creates the socket at `/run/user/1000/wayland-1`.
### GPU
Get the character
ls -la /dev/dri/ 1
drwxr-xr-x 3 root root 100 Jun 17 11:21 . drwxr-xr-x 20 root root 4460 Jun 17 15:37 .. drwxr-xr-x 2 root root 80 Jun 17 11:21 by-path crw-rw----+ 1 root video 226, 0 Jun 17 11:21 card0 crw-rw-rw- 1 root render 226, 128 Jun 17 11:21 renderD128
`card0` and `rederD128` only do *3D* acceleration, they do not output video.
`card1` does output video but I do not have it.
### ACLs
Allow the UID `1131072` (`0` inside the container) access to mount (`--x`) `/run/user/1000`
and UID `1132072` (`1000` inside the container) to write (`rwx`) to *wayland* and *pulseaudio*.
setfacl -m u:1131072:--x /run/user/1000 setfacl -m u:1132072:rwx /run/user/1000/wayland-1 setfacl -m u:1132072:rwx /run/user/1000/pulse-socket
## Create and configure the container
Create the container
lxc-create -t download -n firefox -- --dist debian --release bullseye --arch amd64
Map the required sockets into the container. The last three lines are only needed for *3D* rendering.
~/.local/share/lxc/firefox/config
lxc.mount.entry = /run/user/1000/pulse-socket home/user/1000/pulse-socket none bind,create=file,rw 0 0 lxc.mount.entry = /run/user/1000/wayland-1 home/user/1000/wayland-1 none bind,create=file,rw 0 0 lxc.mount.entry = /dev/dri/renderD128 dev/dri/renderD128 none bind,create=file,rw 0 0 lxc.cgroup2.devices.allow = c 226:128 rwm lxc.cgroup2.devices.allow = c 226:0 rwm
Start the container and attach
lxc-start firefox lxc-attach firefox --clear-env
## Set up the container
Fix the terminal.
export TERM=xterm-256color
Install dependencies.
apt update apt install -y xwayland weston x11-apps mesa-utils pulseaudio firefox-esr
Configure *pulseaudio*.
/etc/pulse/client.conf
default-server = unix:/home/user/1000/pulse-socket
Create new user.
useradd -u 1000 -m user echo "user:user" | chpasswd passwd -u user
As the folder `/home/user` already exists no new files are created so copy the `skel` directory into the user's home.
cp /etc/skel/.bash_logout /home/user/.bash_logout cp /etc/skel/.bashrc /home/user/.bashrc cp /etc/skel/.profile /home/user/.profile
Configure required environment variables.
/home/user/.bashrc
export TERM=xterm-256color export XDG_RUNTIME_DIR=/home/user/1000 export WAYLAND_DISPLAY=wayland-1 export QT_QPA_PLATFORM=wayland export DISPLAY=:0 export MOZ_ENABLE_WAYLAND=1
Make sure all files are owned by the user.
chown user:user /home/user chmod 0750 /home/user chown user:user /home/user/1000
Start the *GUI* app.
su user -l -c firefox ```