Commit 74b01ee3dc

74b01ee3dcfb2bf35cebcc49d605ccbd2994b64b

parent: 270e3df2d5

Verified · cmc ci/build: success ci/test: failure

cmc <hello@cleberg.net> · 2026-09-06 23:38 UTC

runner: podman with the cgroupfs manager, not systemd

The runner is a system service, so there is no user session slice for
podman's systemd cgroup manager to create a scope under and crun fails
creating the container's cgroup. The service's own cgroup is delegated,
which is what cgroupfs needs.

Ref #144

Layout: unified · split

cmd/gitbay-runner/env_test.go +16
@@ -88,3 +88,19 @@ func TestStepEnvHomeIsNotTheWorkspace(t *testing.T) {
88 } 88 }
89 } 89 }
90} 90}
91
92// podman runs from a system service, where the systemd cgroup manager
93// has no user slice to work in. Every invocation must say so, or crun
94// fails creating the container's scope (#144).
95func TestPodmanUsesCgroupfs(t *testing.T) {
96 got := podmanGlobal()
97 found := false
98 for _, f := range got {
99 if f == "--cgroup-manager=cgroupfs" {
100 found = true
101 }
102 }
103 if !found {
104 t.Errorf("podmanGlobal() = %v, missing the cgroupfs manager", got)
105 }
106}
cmd/gitbay-runner/isolate.go +17 −4
@@ -104,13 +104,13 @@ func (r *runner) runStepsPodman(j job, dir string, env []string, sink io.Writer,
104 name := fmt.Sprintf("gitbay-build-%d", j.ID) 104 name := fmt.Sprintf("gitbay-build-%d", j.ID)
105 // --rm so a container cannot outlive its build; the explicit rm below 105 // --rm so a container cannot outlive its build; the explicit rm below
106 // covers the case where the daemon-less run itself fails. 106 // covers the case where the daemon-less run itself fails.
107 start := exec.Command(podman, "run", "--detach", "--rm", 107 start := exec.Command(podman, append(podmanGlobal(), "run", "--detach", "--rm",
108 "--name", name, 108 "--name", name,
109 "--env-file", envFile, 109 "--env-file", envFile,
110 "--volume", dir+":/workspace:rw", 110 "--volume", dir+":/workspace:rw",
111 "--workdir", "/workspace", 111 "--workdir", "/workspace",
112 "--entrypoint", "sh", 112 "--entrypoint", "sh",
113 image, "-c", "sleep infinity") 113 image, "-c", "sleep infinity")...)
114 start.Env = []string{"PATH=" + os.Getenv("PATH"), "HOME=" + r.podmanHome()} 114 start.Env = []string{"PATH=" + os.Getenv("PATH"), "HOME=" + r.podmanHome()}
115 if out, err := start.CombinedOutput(); err != nil { 115 if out, err := start.CombinedOutput(); err != nil {
116 // A pull failure lands here. Fail the build with what podman 116 // A pull failure lands here. Fail the build with what podman
@@ -118,11 +118,11 @@ func (r *runner) runStepsPodman(j job, dir string, env []string, sink io.Writer,
118 fmt.Fprintf(sink, "starting the build container from %s failed:\n%s\n", image, strings.TrimSpace(string(out))) 118 fmt.Fprintf(sink, "starting the build container from %s failed:\n%s\n", image, strings.TrimSpace(string(out)))
119 return false 119 return false
120 } 120 }
121 defer exec.Command(podman, "rm", "--force", name).Run() 121 defer exec.Command(podman, append(podmanGlobal(), "rm", "--force", name)...).Run()
122 122
123 for _, step := range j.Steps { 123 for _, step := range j.Steps {
124 fmt.Fprintf(sink, "$ %s\n", step) 124 fmt.Fprintf(sink, "$ %s\n", step)
125 cmd := exec.Command(podman, "exec", "--workdir", "/workspace", name, "sh", "-c", step) 125 cmd := exec.Command(podman, append(podmanGlobal(), "exec", "--workdir", "/workspace", name, "sh", "-c", step)...)
126 cmd.Env = []string{"PATH=" + os.Getenv("PATH"), "HOME=" + r.podmanHome()} 126 cmd.Env = []string{"PATH=" + os.Getenv("PATH"), "HOME=" + r.podmanHome()}
127 cmd.Stdout, cmd.Stderr = sink, sink 127 cmd.Stdout, cmd.Stderr = sink, sink
128 if ok, why := runStep(cmd, deadline); !ok { 128 if ok, why := runStep(cmd, deadline); !ok {
@@ -133,6 +133,19 @@ func (r *runner) runStepsPodman(j job, dir string, env []string, sink io.Writer,
133 return true 133 return true
134} 134}
135 135
136// podmanGlobal are the flags every podman invocation needs, before the
137// subcommand.
138//
139// The cgroup manager is cgroupfs, not systemd: the runner is a *system*
140// service, so there is no user session and no user@<uid>.service slice
141// for podman to create a scope under. With the systemd manager crun
142// fails with "create directory .../libpod-<id>.scope/container: No such
143// file or directory". The service's own cgroup is delegated
144// (Delegate=yes in the drop-in), which is what cgroupfs needs (#144).
145func podmanGlobal() []string {
146 return []string{"--cgroup-manager=cgroupfs"}
147}
148
136// podmanHome is where podman keeps its own storage: the runner's home, 149// podmanHome is where podman keeps its own storage: the runner's home,
137// not a build's. The container store is the runner's business, and a 150// not a build's. The container store is the runner's business, and a
138// build never sees this path. 151// build never sees this path.