Repository navigation
Conversation
A rootless container which can not have a cgroup, and has no PID namespace of its own, can not be killed properly: "runc kill" and "runc delete --force" kill its init, and leave all its other processes running. Since runc 1.2.0 (opencontainers#4398), runc warns about this, and says it will be an error in a future version. Make it an error for a container in the host PID namespace (no PID namespace in its config). A container which joins the PID namespace of another container still gets the warning, as its processes are killed with the init of that namespace (see opencontainers#4394). Assisted-by: Claude Opus 5.5 Signed-off-by: Xiangzhe Zeng <xiangzhedev@gmail.com>
cae43b9 to
4b91a4c
Compare
In the host PID namespace, the cgroup is the only way to find all the container processes. Without it, "youki kill --all" and "youki delete --force" can only kill init, and the other processes keep running. Podman uses "kill --all" to stop such a container (podman-container-tools/podman#30078). Keep ignoring the rootless cgroup errors only when the container has a PID namespace, new or joined. crun (containers/crun#2427) and runc (opencontainers/runc#5594) are changed the same way. Only the cgroup v2 manager changes: the systemd manager uses the rootless flag only to apply device rules on a best effort basis, and the v1 manager ignores it. Signed-off-by: Xiangzhe Zeng <xiangzhedev@gmail.com>
| } | ||
| if !p.config.Config.Namespaces.IsPrivate(configs.NEWPID) { | ||
| // TODO: make this an error in runc 1.3. | ||
| logrus.Warn("Creating a rootless container with no cgroup and no private pid namespace. " + |
There was a problem hiding this comment.
Can we keep the current behavior with an annotation?
BTW, having unkillable processes is not a serious concern for BuildKit, as long as the following conditions are satisfied:
buildkitdprocess is containerized with PIDNS, andbuildkitdis running in the (pseudo-)daemonless modeBecause all the
RUNprocesses are killed on the death of thebuildkitdprocess.
Originally posted by @AkihiroSuda in #5512
There was a problem hiding this comment.
We now also have this PR to mitigate the zombie issue
There was a problem hiding this comment.
Thanks, that is useful context. I see why the outer PID namespace of a containerized, daemonless buildkitd can provide eventual cleanup of all the RUN processes. That makes the compatibility concern concrete. Thanks also for linking moby/buildkit#7087; I have read its cancellation-cleanup approach, but have not tested it.
I was just going through the original discussion:
- The compatibility objection in [v1.2 regression] [cgroup v1 + rootless]
nerdctl run -d --name=bar --pid=container:foo ; nerdctl rm -f barhangs #4394 (comment). - The explanation in libct: Signal: honor RootlessCgroups #4395 (comment) that init-only cleanup can leave processes until the namespace owner exits.
- The later warning/deprecation in 30f8f51.
I think both concerns are valid: runc should not silently promise independent container cleanup that it cannot provide, but there are real callers deliberately relying on a larger enclosing lifecycle.
For now, I have updated this PR in 8cf811d and the corresponding implicit-fallback policy in containers/crun#2427 (containers/crun@910dc07): when eligible rootless cgroup setup fails, only a private PID namespace may continue. Sharing with a working cgroup is unchanged.
The kernel distinction is important here. Linux already provides cgroups and private PID namespaces for whole-container cleanup. Without either, POSIX process groups are not a reliable replacement for container membership: a process can leave the group with setsid or setpgid. FreeBSD's PROC_REAP_KILL is an example of kernel-managed signalling of a reaper's descendants. Linux's subreaper API provides orphan adoption, not that operation. This is not a drop-in solution for runc, but it explains why signalling one PID or scanning its current children does not provide the missing guarantee.
I also made refusal explicit in my youki PR, youki-dev/youki#3862, for new host/joined-PIDns containers that hit the eligible rootless cgroup failure. Private/no-cgroup containers, including exec into them, remain allowed. That is a proposed policy in the PR, not an already-merged upstream decision.
I had also been considering an explicit compatibility opt-in, such as a flag or environment variable, but had not settled on an interface that fits runc's conventions. An annotation may be a better fit. My hesitation is keeping known incomplete per-container cleanup as the implicit default solely for compatibility, rather than making that trade-off explicit. Would a narrowly scoped annotation retaining the existing best-effort behavior and warning be the preferred approach? I have not added such an option in this revision.
There was a problem hiding this comment.
@AkihiroSuda I agree on the need for annotation. Something like this maybe?
"annotations": {
"run.oci.allow_unkillable_processes": "1"
}so we can use the same name across various runtimes.
The name chosen so it says what the caller agrees to, rather than describing the conditions in which it applies.
Ah, and it also needs to be added to PotentiallyUnsafeConfigAnnotations in features.go.
Replace the non-private PID namespace warning with an error when cgroup setup returns ErrRootless. A joined namespace does not provide independent cleanup of the joining container, so it needs the same cgroup protection as a host PID namespace. Keep private-namespace fallback and cgroup-backed sharing unchanged. Extend the shared-namespace test to check refusal without a cgroup, and cleanup of init plus exec work without stopping the namespace owner when separate cgroups are available. Assisted-by: GPT-6.1-sol Signed-off-by: Xiangzhe Zeng <xiangzhedev@gmail.com>
|
I've updated this PR and containers/crun#2427 to apply the same rule to the existing implicit rootless cgroup-error fallback:
The rejection explains that a container without a private PID namespace needs a cgroup to reliably kill all its processes. No new compatibility option is included in these revisions. crun's explicit Revisions: runc 8cf811d8, crun 910dc072. |
Summary
A rootless container without an effective cgroup needs its own private PID namespace to ensure all of its processes can be killed. In a host or joined PID namespace, killing the container's init does not kill its other processes. Waiting for the namespace owner's eventual exit is not independent cleanup of the joining container.
Since #4398, runc has warned about this configuration and said it would become an error. This PR turns that existing warning into an error when
Manager.Applyreturnscgroups.ErrRootlessandNamespaces.IsPrivate(NEWPID)is false. The host-only exception from the earlier revision is removed.No new option and no pidfd changes are included.
Tests
create.batschecks rejection of the existing rootless host/shared case without cgroups.start_hello.batsrequires cgroup access for its rootless host-PID case.kill.bats' shared-PID test now uses distinct A/B cgroups when available, starts extra detached exec work in B, verifies B's processes are gone after SIGKILL, and verifies A remains running. Without rootless cgroup access, it instead expects B admission to fail and A to survive.Local validation of this revision:
go test -tags seccomp ./libcontainer ./libcontainer/configs: pass.Not rerun for this revision: full rootful, systemd, cgroup-v1/hybrid matrices or remote CI. Earlier revision results are not presented as coverage of the new joined restriction.
Compatibility
This is a behavior change for rootless host/joined containers relying on the no-cgroup fallback, including the historical configuration in #4394. The compatibility path previously allowed command completion while admitting leaked processes; the warning's planned error is now enforced. Configurations with working cgroups are unchanged.
BuildKit also has an intentional case: a containerized, daemonless
buildkitdcan rely on its outer PID namespace for eventual cleanup of all build processes (moby/buildkit#5512 (comment)). That is different from independent cleanup of each inner container. An explicit compatibility annotation has been proposed in review; no such option is included in this revision, and its interface remains under discussion.Context: podman-container-tools/podman#30078
crun counterpart: containers/crun#2427