本文将详细解释如何向正在运行的Docker容器动态添加Volume。桂哥网络觉得很实用,分享给大家参考。希望你看完这篇文章能有所收获。
之前有人问我是不是可以在Docker容器启动后挂载一个卷。考虑到mnt命名空间的工作原理,一开始我觉得很难实现。但是现在我觉得实现了。
简单地说,要在运行的容器上装载磁盘卷,我们需要:
使用nsenter将包含此磁盘卷的整个文件系统装载到临时装载点;
从我们要用作磁盘卷的特定文件夹创建到该磁盘卷位置的绑定装载;
在卸载的第一步中创建的临时装载点。
注意事项
在下面的例子中,我特意加入了$符号来表示这是一个Shell命令提示符,以帮助大家区分哪些是你需要输入的,哪些是机器回复的。有一些多行命令,我会继续使用。我知道这使得复制和粘贴示例中的命令变得不容易。如果您想复制并粘贴代码,请查看文章末尾的示例脚本。
详细步骤
以下示例的前提是您已经使用以下命令启动了一个名为charlie的简单容器:
$ Docker Run-name Charlie-Tiubuntuash我们需要做的是将主机文件夹/home/jpetazzo/work/Docker/Docker挂载到容器中的/src目录。好了,我们开始吧。
nsenter
首先,我们需要输入帮助脚本。为啥?因为我们想从容器装载文件系统。出于安全原因,容器不允许我们这样做。有了nsenter,我们可以突破上面的安全限制,在容器的上下文中(严格来说是命名空间)运行任意命令。当然,这一定要要求Docker主机的根权限。
安装n enter的最简单方法是与docker-enter脚本一起执行:
$ dockerrun-RM-v/usr/local/bin :/targetjpetazzo/n有关更多详细信息,请查看n enter项目主页。
找到文件系统
我们希望在容器中装入包含主机文件夹(/home/jpetazzo/work/docker/docker)的文件系统。然后我们需要找出哪个文件系统包含这个目录。
首先,我们需要规范化(或取消引用)文件,以防它是一个符号链接或其路径包含符号链接:
$readlink -规范化/home/jpetazzo/Work/DOCKER/DOCKER
/home/jpetazzo/go/src/github.com/docker/docker,这确实是一个符号链接!让我们把它放在一个环境变量中:
$ HOSTPATH=/home/jpetazzo/Work/DOCKER/DOCKER
$ realpath=$(readlink-canonicalize $ host path)接下来,我们需要找出哪个文件系统包含此路径。我们使用了一个有点意想不到的工具,它是df:
$df$REALPATH
文件系统1K-块可用使用%挂载
/sda 2245115308156692700861570065%/home/jpetazzo使用-P参数(POSIX格式是强制的,以防止它成为别人在Solaris或BSD系统上安装Docker时运行的外来df或df),并将结果放入变量:
$ FILEYS=$(df-P $ REALPATH | tail-n1 | awk ' { print $ 6 } ')找到文件系统的设备(和sub-root)
现在,系统中没有绑定装载和BTRFS子卷。我们只需要看看/proc/mounts,找到/home/jpetazzo文件系统对应的设备。但是在我的系统中,/home/jpetazzo是BTRFS池的子卷。要获取子卷的信息(或绑定挂载信息),需要检查/proc/self/moutinfo。
如果您从未听说过mountinfo,您可以查看内核文档的P
roc.txt。首先,得到文件系统设备信息:
$whilereadDEVMOUNTJUNK >do[$MOUNT=$FILESYS]&&break >done</proc/mounts $echo$DEV /dev/sda2
接下来,得到sub-root信息(比如,已挂载文件系统的路径):
$whilereadABCSUBROOTMOUNTJUNK >do[$MOUNT=$FILESYS]&&break >done</proc/self/mountinfo $echo$SUBROOT /jpetazzo
很好。现在我们知道需要挂载/dev/sda2
。在文件系统内部,进入/jpetazzo,从这里可以得到到所需文件的剩余路径(示例中是/go/src/github.com/docker/docker
)。
让我们计算出剩余路径:
$SUBPATH=$(echo$REALPATH|seds,^$FILESYS,,)
注意:这个方法只适用于路径里没有符号“,”的。如果你的路径里有“,”并且想使用本文方法挂载目录,请告诉我。(我需要调用Shell Triad来解决这个问题:jessie,soulshake,tianon?)
在进入容器之前最后需要做的是找到这个块设备的主和次设备号。可以使用stat:
$stat--format"%t%T"$DEV 82
注意这两个数字是十六进制的,我们之后需要的是二进制。可以这么转换:
$DEVDEC=$(printf"%d%d"$(stat--format"0x%t0x%T"$DEV))
总结
还有最后一步。因为某些我无法解释的原因,一些文件系统(包括BTRFS)在挂载多次之后会更新/proc/mounts里面的设备字段。也就是说,如果我们在容器里创建了名为/tmpblkdev的临时块设备,并用其挂载我们自己的文件系统,那么文件系统(在宿主机器里!)会显示为/tmpblkdev,而不是/dev/sda2。这听起来无所谓,但实际上这会让之后试图得到文件系统块设备的操作都失败。
长话短说,我们想要确保块设备节点在容器里位于和宿主机器上的同一个路径下。
需要这么做:
$docker-entercharlie--sh-c\ >"[-b$DEV]||mknod--mode0600$DEVb$DEVDEC"
创建临时挂载点挂载文件系统:
$docker-entercharlie--mkdir/tmpmnt $docker-entercharlie--mount$DEV/tmpmnt
确保卷挂载点存在,bind mount卷:
$docker-entercharlie--mkdir-p/src $docker-entercharlie--mount-obind/tmpmnt/$SUBROOT/$SUBPATH/src
删除临时挂载点:
$docker-entercharlie--umount/tmpmnt $docker-entercharlie--rmdir/tmpmnt
(我们并不清除设备节点。一开始就检查设备是不是存在可能有点多余,但是现在再检查就已经很复杂了。)
大功告成!
让一切自动化
下面这段可以直接拷贝粘贴了。
#!/bin/sh set-e CONTAINER=charlie HOSTPATH=/home/jpetazzo/Work/DOCKER/docker CONTPATH=/src REALPATH=$(readlink--canonicalize$HOSTPATH) FILESYS=$(df-P$REALPATH|tail-n1|awk'{print$6}') whilereadDEVMOUNTJUNK do[$MOUNT=$FILESYS]&&break done</proc/mounts [$MOUNT=$FILESYS]#Sanitycheck! \whilereadABCSUBROOTMOUNTJUNK \do[$MOUNT=$FILESYS]&&break \done</proc/self/mountinfo [$MOUNT=$FILESYS]#Moarsanitycheck! SUBPATH=$(echo$REALPATH|seds,^$FILESYS,,) DEVDEC=$(printf"%d%d"$(stat--format"0x%t0x%T"$DEV)) docker-enter$CONTAINER--sh-c\ "[-b$DEV]||mknod--mode0600$DEVb$DEVDEC" docker-enter$CONTAINER--mkdir/tmpmnt docker-enter$CONTAINER--mount$DEV/tmpmnt docker-enter$CONTAINER--mkdir-p$CONTPATH docker-enter$CONTAINER--mount-obind/tmpmnt/$SUBROOT/$SUBPATH$CONTPATH docker-enter$CONTAINER--umount/tmpmnt docker-enter$CONTAINER--rmdir/tmpmnt
关于“怎么给一个正在运行的Docker容器动态添加Volume的案例”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。
TikTok千粉号购买平台:https://tiktokusername.com/
TOP