Docker挂载的文件与主机不同步问题
Docker挂载的文件与主机不同步问题
问题
遇到了一个奇怪的问题, 我将某配置文件挂载到Docker容器中, 当修改配置文件时, 容器中的配置文件却没有做同步修改, 使我非常疑惑. 同时, 如果此时将容器暂停, 然后重新运行这个暂停容器时, 会发生'No such file or directory'错误. 怪事, 文件不就在那里吗? 下面是复现问题的操作.
-
在主机上创建测试目录, 并创建一个文本文件, 其中写入内容.
(base) serendipity@SerendipityPC:~/test$ cat ./test.txt This is a txt file. No, you're wrong.
-
将该文本文件挂载到Ubuntu容器中.
(base) serendipity@SerendipityPC:~/test$ docker run --name ubuntu_test -v ~/test/test.txt:/home/ubuntu/test/test.txt -dit ubuntu:23.04 bash 24dd7b4b5cbc43c04a59757319820b9a64f532ae62193d7c9fbd078cb1f81489
查看一下当前容器中被挂载的文件的内容:
(base) serendipity@SerendipityPC:~/test$ docker exec ubuntu_test cat /home/ubuntu/test/test.txt This is a txt file. No, you're wrong.
嗯, 看来挂载完全没问题.
-
在主机中修改文件.
(base) serendipity@SerendipityPC:~/test$ vim ./test.txt (base) serendipity@SerendipityPC:~/test$ cat ./test.txt This is a txt file. No, you're wrong. Modified.....
再次查看容器中文件内容:
(base) serendipity@SerendipityPC:~/test$ docker exec ubuntu_test cat /home/ubuntu/test/test.txt This is a txt file. No, you're wrong.
还是没变. 若此时停止容器, 并使用
docker start
再次启动:(base) serendipity@SerendipityPC:~/test$ docker stop ubuntu_test ubuntu_test (base) serendipity@SerendipityPC:~/test$ docker start ubuntu_test Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: error mounting "/run/desktop/mnt/host/wsl/docker-desktop-bind-mounts/Ubuntu-22.04/b1251a9894fde24ab8dced516c7db22ab22572b4bc12afae10276910878065f2" to rootfs at "/home/ubuntu/test/test.txt": mount /run/desktop/mnt/host/wsl/docker-desktop-bind-mounts/Ubuntu-22.04/b1251a9894fde24ab8dced516c7db22ab22572b4bc12afae10276910878065f2:/home/ubuntu/test/test.txt (via /proc/self/fd/9), flags: 0x5000: no such file or directory: unknown Error: failed to start containers: ubuntu_test
无法找到相应的文件和目录, 因此无法挂载.
分析
当使用Docker挂载单个文件到容器中时, 实际上挂载的是文件的inode(详情见Linux文件系统). 显而易见, 当一个文件被替换时, 即使文件大小 内容完全相同, 他们的inode不同, 那就是不同的文件. 许多文本编辑器在编辑时, 并不是在源文件上做修改, 比如vim. 使用vim修改文件时, 它会创建一个文件的副本, 并在该文件上进行修改. 等到修改完成, 保存时, 对副本文件进行重命名并替换原来的文件. 这样做的好处就是即使保存过程中出现崩溃, 原始文件依然存在, 不会丢失. 正是vim的这一行为导致了文件的inode发生改变, 从而使docker无法从启动时挂载的inode对应的文件中获得新的更新.
(base) serendipity@SerendipityPC:~/test$ ls -i test.txt
3688 test.txt
(base) serendipity@SerendipityPC:~/test$ vim ./test.txt
(base) serendipity@SerendipityPC:~/test$ ls -i test.txt
248726 test.txt
这时候新的疑惑又产生了, 在容器运行过程中对主机文件进行编辑, 编辑完成后主机文件被替换, 老文件已经被覆盖, 为什么此时在容器中还能访问老文件, 难道没有被删除吗? 如果没有被删除, 为什么停止容器后再次启动就爆出找不到文件的错误了呢? 其实我们可以从inode数据结构中一探究竟.
struct inode {
struct hlist_node i_hash; 哈希表
struct list_head i_list; 索引节点链表
struct list_head i_dentry; 目录项链表
unsigned long i_ino; 节点号
atomic_t i_count; 引用记数
umode_t i_mode; 访问权限控制
unsigned int i_nlink; 硬链接数
uid_t i_uid; 使用者id
gid_t i_gid; 使用者id组
kdev_t i_rdev; 实设备标识符
loff_t i_size; 以字节为单位的文件大小
struct timespec i_atime; 最后访问时间
struct timespec i_mtime; 最后修改(modify)时间
struct timespec i_ctime; 最后改变(change)时间
unsigned int i_blkbits; 以位为单位的块大小
unsigned long i_blksize; 以字节为单位的块大小
unsigned long i_version; 版本号
unsigned long i_blocks; 文件的块数
unsigned short i_bytes; 使用的字节数
spinlock_t i_lock; 自旋锁
struct rw_semaphore i_alloc_sem; 索引节点信号量
struct inode_operations *i_op; 索引节点操作表
struct file_operations *i_fop; 默认的索引节点操作
struct super_block *i_sb; 相关的超级块
struct file_lock *i_flock; 文件锁链表
struct address_space *i_mapping; 相关的地址映射
struct address_space i_data; 设备地址映射
struct dquot *i_dquot[MAXQUOTAS];节点的磁盘限额
struct list_head i_devices; 块设备链表
struct pipe_inode_info *i_pipe; 管道信息
struct block_device *i_bdev; 块设备驱动
unsigned long i_dnotify_mask;目录通知掩码
struct dnotify_struct *i_dnotify; 目录通知
unsigned long i_state; 状态标志
unsigned long dirtied_when;首次修改时间
unsigned int i_flags; 文件系统标志
unsigned char i_sock; 套接字
atomic_t i_writecount; 写者记数
void *i_security; 安全模块
__u32 i_generation; 索引节点版本号
union {
void *generic_ip;文件特殊信息
} u;
};
我们可以看到, 有一个属性叫i_count
, 该属性就是用来记录当前文件的引用数. 在Linux中, 当该引用数归零时, 文件就会被删除. 当我们将文件挂载到一个运行容器中时, 该文件处于打开状态, 在内核文件表中保留了一个inode副本, 其引用数被添加进去. 即使此时主机取消了该文件的引用, 引用数仍然不为0, 文件不会被删除. 直到容器停止, 文件才真正被删除, 因此重新启动容器时就无法找到相应文件了.
解决
目前找出了两种解决方案:
- 从vim下手
可以在~/.vimrc
中添加set backupcopy=yes
, 此时修改文件后inode不变. - 直接挂载目录(推荐)
当修改目录中的文件时, 即使文件的inode发生改变, 目录文件中的目录项也会自己改变(这不是废话), 此时容器中也可以从挂载目录中访问目录下面的文件.
参考链接