Skip to content

Instantly share code, notes, and snippets.

@euank
Last active October 22, 2017 22:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save euank/3cafdd8a318b1722f3d84f1d962272ac to your computer and use it in GitHub Desktop.
Save euank/3cafdd8a318b1722f3d84f1d962272ac to your computer and use it in GitHub Desktop.
Based on https://github.com/moby/moby/issues/34672#issuecomment-331527204, what if it was rslave in both parts?
#!/bin/bash
[ $UID == 0 ] || (echo "Must be root" && exit 1)
# c1 and c2 represent two different docker containers starting at once
c1=1
c2=2
function ovlOpts() {
echo -n "lowerdir=$tmpdir/lower,upperdir=$tmpdir/$1/diff,workdir=$tmpdir/$1/work"
}
tmpdir=$(mktemp -d) # 'overlay2' graphdriver dir
# overlay2 driver in its setup code does this
# https://github.com/moby/moby/blob/ba317637de9b9918cdc2139466dd51c6200bd158/daemon/graphdriver/overlay2/overlay.go#L178
echo "Graphdriver setup making $tmpdir rslave"
mount --bind "$tmpdir" "$tmpdir"
mount --make-rslave "$tmpdir"
echo "Containers creating rootfs's in $tmpdir"
mkdir -p "${tmpdir}/$c1/"{diff,merged,work}
mkdir -p "${tmpdir}/$c2/"{diff,merged,work}
mkdir -p "$tmpdir/lower"
# https://github.com/moby/moby/blob/ba317637de9b9918cdc2139466dd51c6200bd158/daemon/graphdriver/overlay2/overlay.go#L589
# Container 1 starts setting up
echo "c1: dockerd creates mounts"
mount -t overlay overlay -o "$(ovlOpts $c1)" "$tmpdir/$c1/merged"
# Container 2 sets up
echo "c2: dockerd creates mounts"
mount -t overlay overlay -o "$(ovlOpts $c2)" "$tmpdir/$c2/merged"
# Container 2 runs 'runc init' code in parallel
(
# https://github.com/opencontainers/runc/blob/8b47a242a9aebdfe1c0c2b6513368f736d505bf0/libcontainer/nsenter/nsexec.c#L823
unshare -m --propagation unchanged -- bash <<EOF
echo "c2: 'runc init' now in new mount namespace"
# Now runc init remounts /
# https://github.com/opencontainers/runc/blob/e385f67a0e45fa1d8ef8154e2aea5128ea1d331b/libcontainer/rootfs_linux.go#L599-L605
# Due to how the config conversion works, config.RootPropagation is never 0,
# and defaults instead to MS_PRIVATE | MS_REC. I'll PR a fix
mount --make-rslave /
echo "c2: runc init initial view of mounts: "
cat /proc/self/mountinfo | grep "${tmpdir}"
echo -e "===\n\n"
sleep 0.7
echo "c2: still in runc init, current view of mounts: "
cat /proc/self/mountinfo | grep "${tmpdir}"
echo -e "===\n\n"
sleep 0.3
echo -e "c2: init done; unmounting old root\n"
# .. and then pivot root happens which cleans up our old root
# It's hard to do in shell, so we'll just pretend an umount of / is close enough
# https://github.com/opencontainers/runc/blob/e385f67a0e45fa1d8ef8154e2aea5128ea1d331b/libcontainer/rootfs_linux.go#L676
cd /
umount -l .
EOF
) &
sleep 0.5
# While container2 is doing its init, container 1 unmounts and remounts its overlay
echo "c1: dockerd still setting up, remounting graphdriver"
umount "$tmpdir/$c1/merged"
mount -t overlay overlay -o "$(ovlOpts $c1)" "$tmpdir/$c1/merged" && echo "c1: re-mount succeeded" || echo "c1: re-mount failed"
echo
# Boom, EBUSY on 4.13+ because `unshare -m` above has a private copy of the mount
# Wait long enough to cleanup
sleep 0.7
# Cleanup
umount "$tmpdir/$c2/merged"
umount "$tmpdir"
rm -rf "$tmpdir"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment