fix(atelet): set OCI CgroupsPath so each actor gets its own cgroup#161
fix(atelet): set OCI CgroupsPath so each actor gets its own cgroup#161ArnaudBger (ArnaudBger) wants to merge 1 commit into
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
e662fc3 to
7d3e5cd
Compare
7d3e5cd to
3164926
Compare
|
This seems plausible to me, but I am not deep in this area of the code yet. What I don't follow is:
How would that happen? |
|
I'm not sure about the root cause, but also observed the same behaviour in #288 |
Privileged containers have the host cgroups mounted. I think we should pare down the privileges, even if it remains highly permissioned, it would be better to avoid |
|
The behaviour before this code change was #288, which had multiple runsc-sandboxes under the "pause" cgroup. I pulled the PR locally and checked the actor's cgroup, the actor's cgroup is under the ateom pod's directory, but not the container's directory: which means the output of container_cpu_usage_seconds_total metric of the Ateom container will not include the actor's resource usage.
|
I don't see that - I see what Zoe Zhao (@zoez7) said -- ateom is in the per-pod cgroup, but runsc-gofer and runsc-sandbox are in /pause. Fabricio Voznika (@fvoznika) Is this something gvisor is doing internally? Is this "correct" in some way I don't understand? |
|
Oh, I see, it's mounted AS the cgroupfs not some extra mount |
|
Tracking thru gvisor by code, since I don't know how to side-load a custom gvisor binary.
It then sees the When I add the following: I get different behavior. Now the current cgroup is: So it took the current cgroup value, went up one level and then added the value of
So there it is, but it doesn't make a lot of sense to me. We ARE allowed to have nested groups with processes, I think? Testing shows yes. Why is it going up one level to the pod's cgroup?? This was added in the first cgroups v2 PR: google/gvisor@881a271 Fabricio Voznika (@fvoznika) or someone from gVisor -- can you explain this logic? Both paths seem wrong to me. |
|
There are 2 modes of operation with cgroupsv2: regular and systemd. Because cgroupsv2 cannot have concurrent access, most people use it through systemd, which is what K8s nodes use. When nesting cgroups inside containers, you cannot map the host cgroup (with privileged containers) to manipulate it, because changes to cgroups do not synchronize with the host's systemd. The correct way is to use pod cgroups (which Ben mentioned above). It creates a cgroup directory tree dedicated to the pod, which can be modified within the pod without racing with the host. This would also prevent the code from falling outside the pod's cgroups. The code looking for the parent directory is actually correct. Cgroupsv2 doesn't allow nesting. Once a cgroup has procs, sub-directories cannot be created inside it anymore. This forces the code to look for the parent directory, which is the closest one that can be used to create a sub-directory in. The empty cgroup case is not being handled correctly though. Instead of staying in the parent's cgroups, it should create a subgroup with a generic name. |
|
Thanks!
I don't know what that means - can you explain? Is it creating a new cgroup inside the cgroup which is managed by kubelet? Or is it somehow outside of that?
Hmm, I thought I was testing against v2, but I guess I wasn't. This behavior is very awkward. It means that the accounting for the sandbox goes to the Pod rather than the particular container. |
|
So I was test v2, but it seems to work. But I was not setting controllers. What if: Now we have parallel trees under the container instead of under the pod. I don't know what that will do to Kubernetes... |
cgroupsv2 allows a subtree to be delegated to another process (see
That's interesting. In theory you shouldn't be allowed to have |
|
AIUI writing to cgroup.subtree_control changes the "responsible" cgroup to the next level of children, so this sequence is correct. I will retry it to confirm: So the question -- is this "too clever" ? What should gvisor do by default? Should we just run the actor in the current cgroup? There's something nice about having it totally wrapped in its own cgroup, but IMO going up to the parent is totally suspect. |
|
What are we going to do with this? Doing the little dance I described above seems like it requires some deeper factoring of gvisor (mostly just passes the path around now, need to do more than that). I tried using |
So for other reasons I am looking at running without I have a POC locally, will follow up tomorrow. |
|
#496 is my suggested solution |
Fixes #50
Issue
When the OCI spec leaves
Linux.CgroupsPathempty, runsc uses whatever cgroup it inherited from its parent process and does not record it as one it owns. At teardown, runsc only attemptsRmdiron paths it owns:That makes the failure mode asymmetric and that is why it is hard to spot:
Uninstallwith an emptyc.Own, the loop body never runs, the function returns nil — no log, no error.Rmdiron the shared cgroup, and getsEBUSYbecause the non-owners' processes are still incgroup.procs. This is the only log line you ever see.From logs alone it looks like one specific actor is broken — when in reality the bug is shared-cgroup ownership across actors.
How to reproduce
Linux.CgroupsPath. runsc creates the pause cgroup; A owns it.Rmdiron the pause path.RmdirreturnsEBUSY(B is still incgroup.procs), surfacingremoving cgroup path "/sys/fs/cgroup/pause": device or resource busyand leaving stale state — matching the failure in Actor stuck in STATUS_SUSPENDING #50.Solution
Set a relative, per-actor path:
c.Own, and is created/cleaned up cleanly with no cross-actorEBUSY.<namespace>/<name>convention (actors/<ns>/<template>/<actor>/<container>), so per-namespace and per-template totals are queryable at each level.Spec construction is extracted into
buildSpecso the new behavior is unit-testable.