Docker container中mount文件内容无法同步的问题解决
这篇博文主要讲述了我遇到的一个Docker使用mount volume中的一个“坑”,即在宿主机中修改了某个已经mount进container的文件,但在container中发现却没有改变。我们尝试找解决方法,也顺便尝试知道是什么原因。
1. 问题描述
在Linux(Oracle Linux Server release 7.7)本地创建一个文件"test.txt"(默认权限664),然后再用如下命令mount进一个container:
docker run -v "/home/ec2-user/deleme_me/test.txt:/test.txt" --rm -it ubuntu:18.04 /bin/bash
在container中看到了"/test.txt"的内容与宿主机相同。
然后在宿主机上用vim(7.4.1099)修改文件内容,增加一行内容,再到container中,发现/test.txt并没有随之变化。
很显然,这不是我们所期望的,按照我们的设想,在这样的mount之后,宿主机和container中文件的变更应该是双向同步的,即在容器中改变文件内容,宿主机中应当即时得到更新,反之亦然,因为mount本身就表示同一个文件,只是借由docker使其其出现在了不同的 Mount namespaces 中。
那究竟是什么原因导致这样与设想大相径庭的结果呢?
2. 问题分析
我们知道,在Docker中,mount volume的原理是借用了Linux Namespace中的 Mount NameSpace,隔离系统中不同进程的挂载点视图,实际文件是没有变化的,比如上面的例子,在container中,/bin/bash实际就是一个运行在宿主机上的进程,被Docker用Linux分别隔离了Mount Namespace、UTS Namespace、IPC Namespace、PID Namespace、Network Namespace和User Namespace,使得它看上去好像运行在了一个独立的、相对隔离的系统上,但实际它的一切资源都是宿主机在不同Namespace中的一个投影,文件也不例外。
所谓文件没有变化,我们如何证明呢?在Linux中,证明文件是否相同的根本途径是,判断其inode,如果两个文件的inode相同,两个文件必定为同一文件,从而两个文件的内容也必然相同。
那我们来看看宿主机和container中看到的text.txt是不是一样:
// container中
root@e365010a98f6:/# stat test.txt File: test.txt Size: 7 Blocks: 8 IO Block: 4096 regular file Device: ca01h/51713d Inode: 4510751 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1000/ UNKNOWN) Gid: ( 1000/ UNKNOWN) Access: 2020-04-26 14:03:12.898485003 +0000 Modify: 2020-04-26 14:03:12.898485003 +0000 Change: 2020-04-27 01:35:38.779485729 +0000 Birth: -
// 宿主机中
$ stat test.txt File: 'test.txt' Size: 7 Blocks: 8 IO Block: 4096 regular file Device: ca01h/51713d Inode: 4510751 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1000/ec2-user) Gid: ( 1000/ec2-user) Access: 2020-04-26 14:03:12.898485003 +0000 Modify: 2020-04-26 14:03:12.898485003 +0000 Change: 2020-04-27 01:35:38.779485729 +0000 Birth: -
以上两个代码块中,上图是container中的,下图时候宿主机中的。我们看到在容器中和宿主机中的test.txt的inode号都是4510745,即可认为是同一文件。
这时,回到我们刚开始遇到的那个问题,我们在宿主机中修改test.txt文件,用Vim打开文件,追加一行,然后保存退出,然后打开container中的文件,发现出现了怪事,container中的文件没有更新,也就是重现了我们一开始描述的问题。
那我们再来看看他们的文件信息:
// container中
root@e365010a98f6:/# stat test.txt File: test.txt Size: 7 Blocks: 8 IO Block: 4096 regular file Device: ca01h/51713d Inode: 4510751 Links: 0 Access: (0664/-rw-rw-r--) Uid: ( 1000/ UNKNOWN) Gid: ( 1000/ UNKNOWN) Access: 2020-04-26 14:03:12.898485003 +0000 Modify: 2020-04-26 14:03:12.898485003 +0000 Change: 2020-04-27 01:42:25.252402227 +0000 Birth: -
// 宿主机中
$ stat test.txt File: 'test.txt' Size: 14 Blocks: 8 IO Block: 4096 regular file Device: ca01h/51713d Inode: 6553165 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1000/ec2-user) Gid: ( 1000/ec2-user) Access: 2020-04-27 01:42:25.252402227 +0000 Modify: 2020-04-27 01:42:25.252402227 +0000 Change: 2020-04-27 01:42:25.252402227 +0000 Birth: -
由上面的输出可以看到,container中的inode没有变化,但宿主机中的已经变化了,也就是说,从文件系统层面来看,此时container中和宿主机中它们已经不是同一个文件了。既然不是同一个文件,那又有什么同步可言?
那好了,那问题的初步原因明白了,就是用vim在宿主机编辑了被mount进container的文件后,宿主机上的文件inode变化了,导致两个文件内容无法同步。
那问题就变成vim为何有什么神奇的逻辑呢?
原来,Linux默认情况下,vim为了防止在你修改文件的过程中,由于磁盘或者系统出现问题而导致当前被修改的文件的损坏,它做了类似如下逻辑:
- 复制出一个需要修改文件的副本,命名为在原来文件的基础上增加".swp"后缀以及"."前缀
- 修改内容保存到有".swp"后缀的文件,并flush到磁盘
- 交换原文件和swp文件的名称
- 删除swp文件
如此一来,在宿主机看来,原来inode就被释放掉了,可是在container看来,他还是需要留着这个inode。
3. 解决方法
提到解决,实际上无解的,因为以上出现的每一方站在其自身的角度看,都是合理的,这些逻辑也是by design的。于是乎,我们就寻找workaround的角度来尝试绕过我们的问题吧。
以下几种方法均可以成功绕过该问题:
-
用echo等代替vim文件修改
既然这个修改文件的逻辑是vim引入的,那么我们替换vim就可以解决问题了,增加文件内容的方法很多,比如我们可以echo代替:
root@2c8d3dfc7d57:/# echo hello >> test.txt
-
修改vim配置
打开vim,输入
:scriptnames
找到vimrc的路径,例如是"/etc/vimrc",再打开"/etc/vimrc",添加如下两行:
set backup
set backupcopy=yes
这样可以解决问题,不过也有一个很大的副作用,那就是每次用vim编辑文件保存之后,vim会生成一个类似该被修改文件,但末尾增加了一个"~"后缀,用以保存修改之前的文件内容。
-
修改文件权限
我们发现,当文件的权限修改为"其他user有写权限"后,以上说的vim逻辑就变了,修改保存后,原文件的inode不再变化,这就使得我们workaround变得非常简单:
$ chmod 666 test.txt
这个应该是目前为止,最为轻量的workaround了。
4. 参考
2. https://unix.stackexchange.com/questions/36467/why-inode-value-changes-when-we-edit-in-vi-editor