deploy/gitbay-runner.override.conf
124 lines · 6842 bytes
1# Drop-in for gitbay-runner.service, installed by `make deploy-runner` to
2# /etc/systemd/system/gitbay-runner.service.d/override.conf.
3#
4# A build must never starve the host: the e2e suite alone starts sixty
5# daemon instances, and with nothing holding it back a deploy's scp on
6# the admin sshd stalled at 1%. Lower CPU and IO weight keep sshd,
7# gitbayd and the backup timers responsive while a build runs.
8#
9# These weights are for the service, not per build, so `-jobs N` divides
10# them among N builds rather than taking N times as much. Raising -jobs
11# does not need them raised; it makes each build slower, not the host
12# busier.
13#
14# A build runs whatever the repository's ci.yml says, as the runner's
15# own user. Keep that user unprivileged: its key is added with
16# `keys add --scope runner`, which confines it to the runner protocol
17# and read-only git, and the sandboxing below keeps a step from
18# touching the system outside its workspace.
19#
20# Delegate=yes and the storage path below are what rootless podman needs
21# (#144): it manages its own cgroups for a container, and its image and
22# container store lives under the runner's home, which ProtectSystem
23# would otherwise make read-only. Prepare the host with
24# deploy/runner-podman-setup.sh before deploying a runner that isolates.
25[Service]
26# cmc/ci-smoke is the nightly isolation canary; a runner scoped to named
27# repositories never claims a build it is not scoped to, so the canary
28# must be listed or its scheduled build waits forever.
29#
30# Two layers of resource caps. MemoryMax and CPUQuota bound the unit —
31# the runner and every build together — which is what keeps the forge
32# alive when a build allocates without bound. -memory and -cpus on
33# ExecStart cap each build's own cgroup, which the runner creates under
34# this unit's delegated cgroup (Delegate=yes below); podman's own --memory
35# and --cpus never applied here, since under rootless cgroupfs the
36# container ran in the service cgroup itself (#188).
37#
38# 6G of the host's 7.7GB, no swap: the e2e suite peaks past 5GB, so the
39# cap sits above that rather than at a fair share. CPUQuota=300% and
40# -cpus 3 are three of the four cores, leaving one for gitbayd and sshd
41# (#144, #184). OOMPolicy=continue: systemd's default stops the whole
42# service when any process in it is OOM-killed, which would end the
43# runner mid-build; the build fails and the runner carries on.
44MemoryMax=6G
45CPUQuota=300%
46OOMPolicy=continue
47#
48# ExecStart is overridden here rather than left in the unit so the flags
49# and the sandboxing that has to match them live in one file: -isolation
50# podman needs NoNewPrivileges=no below, and -image needs an image the
51# host has been given (deploy/runner-podman-setup.sh, Containerfile.ci).
52# podman's run root is pinned under the runner's home by storage.conf
53# (runner-podman-setup.sh), not taken from XDG_RUNTIME_DIR or /tmp: this
54# unit has PrivateTmp, so a /tmp run root is a per-instance tmpfs.
55#
56# The cgroupfs manager puts podman's pause process under the user slice,
57# outside this unit's cgroup, so a stop does not end it and the next
58# start joins its namespaces — including a /tmp that no longer exists.
59# End it with the service.
60ExecStopPost=-/usr/bin/pkill -u ci-runner -x catatonit
61# On stop the runner drains: it claims nothing more and finishes the
62# build in flight, then exits. Give it long enough — the per-build limit
63# is 45m plus half a minute of report retries — before systemd kills it.
64# `make deploy-runner` therefore waits for a running build (#179).
65TimeoutStopSec=50min
66# The drain only works if the stop signal reaches the runner alone. The
67# default control-group mode sends SIGTERM to every process in the
68# cgroup at once — the ssh session streaming the log and the build's
69# container with it — so the runner drained a build whose steps were
70# already dead. mixed signals the main process only; whatever is left
71# when it exits is killed.
72KillMode=mixed
73# -untrusted: this runner isolates in podman, so it takes merge request
74# heads from forks; a runner without a container must not.
75ExecStart=
76ExecStart=/usr/local/bin/gitbay-runner -remote git@127.0.0.1 -workdir /var/lib/gitbay-runner/work -poll 5s -timeout 45m -repos krz/gitbay,cmc/ci-smoke -isolation podman -image localhost/gitbay-ci:1 -cpus 3 -memory 6g -untrusted
77Nice=10
78CPUWeight=30
79IOWeight=30
80# NoNewPrivileges is off, and that is a deliberate trade (#144).
81#
82# Rootless podman sets up its user namespace with newuidmap, a setuid
83# helper; NoNewPrivileges=yes blocks it and podman fails with
84# "newuidmap: write to uid_map failed: Operation not permitted", so the
85# runner refuses to start. The choice is between this flag and running
86# builds in containers at all.
87#
88# Containers are the stronger boundary by a wide margin. NoNewPrivileges
89# constrained a process that was already executing arbitrary repository
90# code as this user; a container confines that code to an image and a
91# bind-mounted workspace. What is lost is one hardening layer on the
92# runner process itself, which is ours rather than a build's — a build no
93# longer runs in this process's context at all.
94#
95# Under -isolation none there is no container, and this flag should be
96# yes. Set it back if you run that way.
97NoNewPrivileges=no
98ProtectSystem=full
99# ProtectKernelTunables is off, for the same reason NoNewPrivileges is
100# (#144). It overmounts /proc/sys and friends in this unit's namespace,
101# and the kernel then refuses a fresh proc mount in any child user
102# namespace ("mount too revealing"): crun fails with "mount `proc` to
103# `proc`: Operation not permitted". There is no podman setting for it.
104# What the flag protected — /proc/sys from a build running on the host
105# as this user — the container now covers: a build gets its own proc,
106# with those paths masked by the runtime. Under -isolation none, set it
107# back to yes.
108# ProtectControlGroups is off because the runner writes cgroups: it
109# creates one per build under this unit's delegated cgroup to carry
110# -memory and -cpus (#188). The flag mounts /sys/fs/cgroup read-only in
111# the unit's namespace, which makes even a delegated cgroup unwritable,
112# and the runner then refuses to start when a limit is set. Delegate=yes
113# already hands this unit its subtree; what the flag protected beyond
114# that is other units' cgroups, which are root-owned and not writable
115# by this user regardless.
116ProtectControlGroups=no
117RestrictSUIDSGID=yes
118Delegate=yes
119# The runner's home is /var/lib/gitbay-runner (see the Admin page), and
120# the leading - makes a missing path ignored rather than fatal: this
121# drop-in installs on hosts that have not been prepared for podman yet,
122# and a unit that refuses to start would stop every build on the
123# instance.
124ReadWritePaths=-/var/lib/gitbay-runner/.local/share/containers -/var/lib/gitbay-runner/.config/containers