How do I dynamically add Volume to a running Docker container

  • 2021-01-14 07:00:50
  • OfStack

Someone asked me earlier if I could still mount the volume once the Docker container has been started, and considering how the mnt namespace works, I first thought this would be difficult to do. But now I think it did.

Simply put, to mount a disk volume on a running container, we need: Use nsenter to put the entire file system mount containing this disk volume on a temporary mount point; Create a binding mount (bind mount) from the specific folder we want to use as a volume to the location of this volume;

Temporary mount point created in step 1 of umount.

Matters needing attention

In the example below, I have deliberately included the $sign to indicate that this is the Shell command line prompt, to help you distinguish between what you need to type and what the machine replies to. There are a few multi-line commands that I will continue to use > . I know this makes it hard to copy and paste the commands in this example. If you want to copy and paste code, check out the sample script at the end of this article.

The detailed steps

The following example assumes that you have started a simple container named charlie with the following command:


$ docker run --name charlie -ti ubuntu bash

All we need to do is host the folder /home/jpetazzo/Work/DOCKER/docker Mounted into the container /src Directory. All right, let's get started.

nsenter

First, we need nsenter and docker-enter help scripts. Why is that? Because we're going to get the mount file system from the container. For security reasons, the container doesn't allow us to do this. With ES39en, we can break through these security restrictions and run arbitrary commands in the context of the container (strictly speaking, the namespace). Of course, this requires root permissions on the Docker host.

The simplest way to install nsenter is to associate it with an docker-enter script:


$ docker run --rm -v /usr/local/bin:/target jpetazzo/nsenter

For more details, see the nsenter project home page.

Find the file system

We want to mount in containers containing the host folder (/ home jpetazzo/Work/DOCKER/docker) file system. Then we need to find out which file system contains this directory.

First, we need the canonicalize (or dereference) file in case this is a symbolic link or its path contains a symbolic link:


$ readlink --canonicalize /home/jpetazzo/Work/DOCKER/docker
/home/jpetazzo/go/src/github.com/docker/docker

Ah, that's a symbolic link! Let's put it in an environment variable:


$ HOSTPATH=/home/jpetazzo/Work/DOCKER/docker
$ REALPATH=$(readlink --canonicalize $HOSTPATH)

Next, we need to find out which file system contains this path. We did this using a somewhat unexpected tool called ES73en:


$ df $REALPATH
Filesystem   1K-blocks   Used Available Use% Mounted on
/sda2     245115308 156692700 86157700 65% /home/jpetazzo

Using the -P parameter (enforce the POSIX format, in case it's exotic df, or someone else running df when Docker is installed on an Solaris or BSD system), put the results into a variable as well:


$ FILESYS=$(df -P $REALPATH | tail -n 1 | awk '{print $6}')

Find the device for the file system (and sub-root)

Now that there is no binding mount (bind mounts) and BTRFS subvolumes, we just need to look at /proc/mounts and find the device that corresponds to the file system of /home/jpetazzo. However, on my system, /home/jpetazzo is a sub-volume of the BTRFS pool. To get information about the sub-volume (or bind mount information), you need to look at /proc/self/moutinfo.

If you've never heard of mountinfo, check out the kernel documentation for proc.txt.

First, get the file system device information:


$ while read DEV MOUNT JUNK
> do [ $MOUNT = $FILESYS ] && break
> done </proc/mounts
$ echo $DEV
/dev/sda2

Next, get the sub-root information (for example, the path to the mounted file system) :


$ while read A B C SUBROOT MOUNT JUNK
> do [ $MOUNT = $FILESYS ] && break
> done < /proc/self/mountinfo 
$ echo $SUBROOT
/jpetazzo

Very good. Now we know we need to mount it /dev/sda2 . Inside the file system, go to /jpetazzo, from which you get the rest of the path to the required file (in the example) /go/src/github.com/docker/docker ).
Let's calculate the remaining paths:


$ SUBPATH=$(echo $REALPATH | sed s,^$FILESYS,,)

Note: This method only works if there is no ", "sign in the path. If you have a ", "in your path and want to mount the directory using this method, please let me know. (I need to call Shell Triad to fix this: jessie, soulshake, tianon?)

The last thing you need to do before entering the container is find the primary and secondary device numbers of the block device. stat can be used:


$ stat --format "%t %T" $DEV
8 2

Notice that these two numbers are in base 106, and we're going to need base 2 later. The conversion can be as follows:


$ docker run --rm -v /usr/local/bin:/target jpetazzo/nsenter
0

conclusion

There's one last step. For some reason I can't explain, some filesystems (including BTRFS) update the device field in /proc/mounts after mounting it many times. That is, if we create a temporary block device named /tmpblkdev in the container and use it to mount our own file system, then the file system (on the host machine!) It will be /tmpblkdev instead of /dev/sda2. This may sound trivial, but in practice it will fail any subsequent attempts to get the file system block device.

To make a long story short, we want to make sure that the block device node in the container is on the same path as the host machine.

Need to do this:


$ docker run --rm -v /usr/local/bin:/target jpetazzo/nsenter
1

Create a temporary mount point to mount the file system:


$ docker run --rm -v /usr/local/bin:/target jpetazzo/nsenter
2

Ensure that the volume mount point exists. bind mount Volume:


$ docker-enter charlie -- mkdir -p /src
$ docker-enter charlie -- mount -o bind /tmpmnt/$SUBROOT/$SUBPATH /src

Delete temporary mount point:


$ docker run --rm -v /usr/local/bin:/target jpetazzo/nsenter
4

We do not clean up device nodes. 1. Checking for the presence of the device at the beginning may be a bit redundant, but checking it again now is very complicated.

And you're done!

Make 1 cut automatic

I can just copy and paste this.


$ docker run --rm -v /usr/local/bin:/target jpetazzo/nsenter
5

States and Restrictions

The above method does not apply to file systems that are not based on block devices and only works if /proc/mounts correctly gets the block device node (which, as discussed above, is not always the case). Also, I've only tested it in my own environment, not in an environment like a cloud instance, but I'm curious to see if it works there.


Related articles: