Skip to content

Centralize the gathering of mounts and devices and move them to the create phase#835

Open
cmainas wants to merge 3 commits into
mainfrom
ref_host_rootfs_mount_actions
Open

Centralize the gathering of mounts and devices and move them to the create phase#835
cmainas wants to merge 3 commits into
mainfrom
ref_host_rootfs_mount_actions

Conversation

@cmainas

@cmainas cmainas commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Description

This is yet another refactor of the way we handle the rootfs for the monitor process. The core idea is to gather all mounts and devices and apply them from a single function rather than setting them up one by one. This is also necessary in order to move the logic of mounts and devices gathering ti the create phase. Since, we move this logic in "urunc create" we need to pass the findings to the "reexec" process. For that purpose, this PR introduces a new file monitor.json which is stored in the bundle directory and simply holds the information for the mounts, devices and block mounts to attach to the sandbox. Such file will be necessary later for libcontainers migration because we need to distinguish the host and the guest environment configuration.

The fact that we now unmount the block-based mounts in the create phase also has the side effect that we mess up with the mount of someone else's. These mounts do not belong to urunc and we should restore them back. Furthermore, as shown from the CI testing, block-based mounts which are backed from a disk file (e.g. a custom made ext2 file image) are mounted as loop devices and hence unmounting them might remove the loop device. For that reason, we unset the autoclear flag of the loop device to ensure that the loop device will remain and we will be able to attach it later in the sandbox. We bring back the original flag during cleanup.

Some other important notes:

  • The unmounting of block devices in the mount list of the container;s spec is now moved to the "urunc create" and therefore we avoid ay similar issues with unmount event propogations (as we had in the past with rootfs).
  • The /dev/net/tun device is always listed as device from the "urunc create" because it does not have the necessary information to determine if the container has network. This is later determined by the Exec process when it sets up the network. In that phase the CNI hooks have been executed and we can know if there is a virtual ethernet device inside the network namespace (meaning we have network).
  • Some mount flags (e.g. gid=5) have been removed, since they are unnecessary.

Related issues

How was this tested?

with e2e tests

LLM usage

Opus 4.8 for applying the same change to multiple similar functions (e.g. rootfsBuilder interface) and grouping the logic that retrieves the mounts and the devices

Checklist

  • I have read the contribution guide.
  • The linter passes locally (make lint).
  • The e2e tests of at least one tool pass locally (make test_ctr, make test_nerdctl, make test_docker, make test_crictl).
  • If LLMs were used: I have read the llm policy.

Gather all the mounts for the monitor execution environment and the
contianer in a single list which is later given as an argument in
applyMOunts to perform all the necessary mounts.

Signed-off-by: Charalampos Mainas <cmainas@nubificus.co.uk>
@netlify

netlify Bot commented Jul 17, 2026

Copy link
Copy Markdown

Deploy Preview for urunc canceled.

Name Link
🔨 Latest commit b7c2e2b
🔍 Latest deploy log https://app.netlify.com/projects/urunc/deploys/6a5fa472936b3100088678c0

@cmainas

cmainas commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

Amazing regression issues here https://github.com/urunc-dev/urunc/actions/runs/29594391629/job/87931466697?pr=835

A normal file getting mounted and then passed as volume in the container is a loopback device. However, now that we finally properly unmount block devices from the original mount namespace, the llopback device gets removed. Therefore, we setup a device that does not exist. anymore...

We need to trace back to the backing file.

On top of that, we totally unmount the device and the second test does not even find the device, since it no longer exeists and therefore skips it...

@cmainas
cmainas force-pushed the ref_host_rootfs_mount_actions branch 4 times, most recently from 81b9fc3 to 55a3ef0 Compare July 17, 2026 19:10
@cmainas
cmainas marked this pull request as ready for review July 17, 2026 19:19
@cmainas
cmainas requested a review from ananos July 17, 2026 19:19
@cmainas
cmainas force-pushed the ref_host_rootfs_mount_actions branch from 55a3ef0 to 4e3d1d9 Compare July 20, 2026 07:50
@ananos

ananos commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Tested this on our dev box: standard qemu/fc boots are fine, and I also exercised the new block volume path with a loop-mounted ext2 file bind-mounted through ctr. The unmount-at-create, autoclear handling and the restore on delete all behave as described -- nice work handling that loop device quirk!

A few comments:

  1. getMonitorDevices now always stats /dev/net/tun, so on a host without the tun module urunc create fails even for containers that will never get network. Before this PR we only touched tun when needsTAP was set. Since the decision is deferred to Exec anyway, we could make the tun lookup best-effort at create and only fail in Exec if the network setup actually needs it.

  2. If urunc dies between create and delete, the host mount stays unmounted and the loop device leaks (autoclear is cleared). The best-effort restore in Delete is fine with me, but maybe add a short comment in restoreBlockVolumes documenting this, so nobody hunts for it later.

  3. restoreBlockVolumes remounts with zero flags and empty data, so the original mount options (eg. ro, noatime) are lost on restore. Probably fine for the common case, but we could stash the original options in BlockDevParams while we are at it.

  4. One thing I noticed while testing: nerdctl generates bind mounts with type: "none" (+ rbind option), so both getBlockVolumes and filterBindMounts skip them and a nerdctl -v volume never reaches the guest. This predates the PR (main has the same check), so no need to address it here -- but since we now centralize the mount logic, it is a good moment to open a follow-up issue and match runc's behavior (treat "none" with a bind option as a bind mount).

  5. Minor polish before merge: the second commit type is "refactord", and the saveMonitorResources docstring says "persists stores res". A squash pass would clean both.

Also, quick question on dropping gid=5 from the devpts options: on most distros gid 5 is the tty group, so pts slaves will now come up group root. For the monitor sandbox I do not see a problem, but did you check the non-root monitor case with a console attached?

All of the above are optional, maybe apart from the typo fixes in (5) -- will approve on your ack.

@cmainas

cmainas commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Hello @ananos ,

thank you for the review and further testing.

1. `getMonitorDevices` now always stats `/dev/net/tun`, so on a host without the tun module `urunc create` fails even for containers that will never get network. Before this PR we only touched tun when `needsTAP` was set. Since the decision is deferred to Exec anyway, we could make the tun lookup best-effort at create and only fail in Exec if the network setup actually needs it.

Yeah, I did not consider a case where a host does not have /dev/net/tun, since we heavily rely on this device for our networking mode. I will update the code based on this suggestion.

2. If urunc dies between create and delete, the host mount stays unmounted and the loop device leaks (autoclear is cleared). The best-effort restore in Delete is fine with me, but maybe add a short comment in `restoreBlockVolumes` documenting this, so nobody hunts for it later.

Yes, this is a side effect that it is not easy to handle. I think we should also document it in our docs.

3. `restoreBlockVolumes` remounts with zero flags and empty data, so the original mount options (eg. ro, noatime) are lost on restore. Probably fine for the common case, but we could stash the original options in `BlockDevParams` while we are at it.

Yeap, I can add that too.

4. One thing I noticed while testing: nerdctl generates bind mounts with `type: "none"` (+ rbind option), so both `getBlockVolumes` and `filterBindMounts` skip them and a nerdctl `-v` volume never reaches the guest. This predates the PR (main has the same check), so no need to address it here -- but since we now centralize the mount logic, it is a good moment to open a follow-up issue and match runc's behavior (treat "none" with a bind option as a bind mount).

This is handled from a follow up PR. #840 which depends on this one.

5. Minor polish before merge: the second commit type is "refactord", and the `saveMonitorResources` docstring says "persists stores res". A squash pass would clean both.

I will fix that.

Also, quick question on dropping gid=5 from the devpts options: on most distros gid 5 is the tty group, so pts slaves will now come up group root. For the monitor sandbox I do not see a problem, but did you check the non-root monitor case with a console attached?

Yeap, but I had the same concerns when I was adding the support for non-root execution of the monitor process, because we had to belong to the KVM group and while usually these group ids have some "usual" values, there is no guarantee they will stay the same across different platforms and they can change (for various reasons) leading to unexpected and unwanted behavior. I think a workaround for this would be to let the user specify these group idss in the configuration. There is also the option that we search and find these gids, but this extra overhead for every container execution.

cmainas added 2 commits July 21, 2026 13:54
Similarly with mounts, gather all devices in a single slice and then
recreate them inside the monitor's execution environment. The only
exception is vAccel related devices. We need to handle the case of
vAccel separately for the libcontainer migration.

Signed-off-by: Charalampos Mainas <cmainas@nubificus.co.uk>
Move the logic that gathers all needed devices and mounts for monitor
execution environment in InitialSetup during "urunc create" and then
write the information to a file (monitor.json) which Exec will read and
apply the mounts, plus create the devices. THis is necessary for the
libcontainer refactor.

One important note, we always include the /dev/net/tun device from
"urunc create" because ein that phase we have no information about the
networking of the container. Therefore, we include the device and then
let Exec (which is part of "urunc reexec" and CNI hooks have been
executed) to choose if it will create or not the device.

The fact that we now unmount the block-based mounts in the create phase
also has the side effect that we mess up with the mount of someone
else's. These mounts do not belong to urunc and we should restore them
back. Furthermore, as shown from the CI testing, block-based
mounts which are backed from a disk file (e.g. a custom made
ext2 file image) are mounted as loop devices and hence
unmounting them might remove the loop device. For that reason,
we unset the autoclear flag of the loop device to ensure that
the loop device will remain and we will be able to attach it
later in the sandbox. We bring back the original flag during
cleanup.

Signed-off-by: Charalampos Mainas <cmainas@nubificus.co.uk>
@cmainas
cmainas force-pushed the ref_host_rootfs_mount_actions branch from 4e3d1d9 to dcb4ce3 Compare July 21, 2026 16:52
@cmainas

cmainas commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

I have addressed the above comments by:

  • simply logging and not failing if /dev/net/tun device was not found in "urunc create" phase.
  • I have added a comment regarding the restoration of the flag and the mount in getBlockVolumes because that is where we currently describe the approach of unmount and autoclear flag. We do not have any documentation page for storage handling, but added a TODO to include it such documentation in the future.
  • I have added logic to store the mount options of a block mount in order to restore them in the delete path when we remount the block.

@cmainas
cmainas force-pushed the ref_host_rootfs_mount_actions branch from dcb4ce3 to b7c2e2b Compare July 21, 2026 16:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Get the mount list and rootfs info from urunc create and then pass it to reexec

2 participants