跳过正文

疑难杂症|Docker挂载文件与主机不同步问题

·501 字·3 分钟
陌白
作者
陌白
一只热爱技术的码农
目录

一、先说解决方法
#

直接挂载目录(推荐)
#

当修改目录中的文件时, 即使文件的inode发生改变, 目录文件中的目录项也会自己改变, 此时容器中也可以从挂载目录中访问目录下面的文件。

从vim下手(不推荐)
#

可以在~/.vimrc中添加set backupcopy=yes 此时修改文件后inode不变。可解决在宿主机才有vim编辑造成的文件不同步问题,但如果绕过vim直接修改文件,则不生效。

修改文件权限为777(不推荐)
#

如果真要挂载文件,那么要将文件权限修改成777,即 -rwxrwxrwx

最省事的解决办法。命令如下:

 chmod 777 filename

二、原因
#

1.stackoverflow:
#

将单个文件挂载到容器中时,挂载的是该文件在 Linux 文件系统中的 inode。如果你替换了文件(许多编辑器都会这样做),那么 inode 就会改变。如果挂载的是整个目录,这通常不是问题,因为目录指向 inode 的指针会被更新。但如果只挂载单个文件,启动容器后对文件的写入将不会在容器内外部反映出来。(The same file on host and inside container is different, it is not in sync with its original

2.网友博客:
#

当使用Docker挂载单个文件到容器中时, 实际上挂载的是文件的inode(详情见Linux文件系统). 显而易见, 当一个文件被替换时, 即使文件大小 内容完全相同, 他们的inode不同, 那就是不同的文件. 许多文本编辑器在编辑时, 并不是在源文件上做修改, 比如vim. 使用vim修改文件时, 它会创建一个文件的副本, 并在该文件上进行修改. 等到修改完成, 保存时, 对副本文件进行重命名并替换原来的文件. 这样做的好处就是即使保存过程中出现崩溃, 原始文件依然存在, 不会丢失. 正是vim的这一行为导致了文件的inode发生改变, 从而使docker无法从启动时挂载的inode对应的文件中获得新的更新.(Docker挂载的文件与主机不同步问题

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, 文件不会被删除. 直到容器停止, 文件才真正被删除, 因此重新启动容器时就无法找到相应文件了.

三、疑问点解答
#

问:docker修改挂载文件,容器内查看未更改是什么原因?
#

答:
#

如果你在 Docker 容器内修改了挂载的文件,但容器内查看时没有看到更改,这通常是由以下几个原因引起的:

1. 挂载问题(挂载路径是否正确)
#

首先,确保你挂载的宿主机目录或者文件路径正确。如果挂载路径不对,容器中的文件可能与宿主机的文件不一致。

检查容器的挂载点是否正确:

 docker inspect 

查看返回结果中的 Mounts 部分,确保宿主机路径和容器路径正确匹配。例如:

 "Mounts": [
   {
     "Source": "/path/on/host",
     "Destination": "/path/in/container",
     "Mode": "rw",
     "RW": true,
     "Propagation": "rprivate"
   }
 ]

2. 缓存或文件系统延迟
#

有时候,文件系统可能会由于缓存机制或延迟更新导致容器内无法及时看到宿主机文件的变化。尤其是当你修改的是某些挂载目录下的文件时,可能需要重新加载或刷新容器的文件系统来查看更改。

你可以尝试以下方法来解决这个问题:

  • 重新加载容器:重新启动容器,确保容器可以重新加载挂载的文件。
 docker restart 
  • 强制刷新容器内的挂载文件:你也可以通过 docker exec 进入容器,尝试执行命令来强制刷新挂载目录下的内容。例如,使用 ls 命令查看文件列表:
 docker exec -it  ls /path/in/container

3. 挂载文件的权限问题
#

如果文件的权限设置不正确,容器内的进程可能无法访问文件的更改。在挂载文件时,确保宿主机和容器的用户权限一致。可以使用 chmodchown 来设置正确的权限。

例如,设置容器中的目录为可读写:

 chmod -R 777 /path/in/container

4. Docker 挂载类型
#

当你使用 Docker 时,文件挂载有两种常见方式:绑定挂载(bind mount)和 卷挂载(volume mount)。

  • 绑定挂载(bind mount):这将直接将宿主机的文件或目录映射到容器中。在这种情况下,宿主机文件的更改应该会立刻反映到容器中。
  • 卷挂载(volume mount):这种方式是 Docker 管理的特殊存储卷,通常会与宿主机文件系统分离。更改卷内容时,通常不会直接影响宿主机中的文件。

在使用 绑定挂载 时,宿主机上的文件修改会即时反映到容器中,但如果你使用的是 卷挂载,你可能需要在容器内部或外部使用 docker volume 命令查看卷的内容。

5. 容器内部缓存
#

某些应用程序(如 Nginx、MySQL 等)可能会缓存文件内容。如果你修改了配置文件或其他重要文件,容器内的应用可能需要重新加载才能识别更改。

例如,对于 Nginx,修改配置文件后你需要使用以下命令重新加载 Nginx:

 nginx -s reload

对于其他应用程序,查阅相关文档来确保它们在文件修改后能够重新加载。

6. 解决方法
#

  1. 确认挂载路径正确。
  2. 检查文件权限是否允许容器内进程读取和写入。
  3. 尝试重新启动容器以应用更改。
  4. 使用容器的 docker exec 命令查看挂载点是否正确。
  5. 确认容器内部的应用程序已重新加载配置。

问:修改挂载文件权限为777为何可以避免该问题?
#

答:
#

将文件或目录的权限修改为 777 可以暂时避免容器内无法访问或修改挂载文件的问题,因为它赋予了所有用户(包括容器中的进程)对该文件或目录的完全读、写和执行权限。具体来说,777 权限表示:

  • 第一个 7:文件所有者具有读、写、执行权限。
  • 第二个 7:文件所属的用户组具有读、写、执行权限。
  • 第三个 7:其他用户具有读、写、执行权限。

为什么修改为 777 可以避免问题?
#

  1. 避免权限不足:当文件或目录的权限设置不正确时,容器中的进程可能无法读取或修改文件。例如,如果文件的权限为 644,那么只有文件所有者有写入权限,其他用户只有读取权限。容器中的进程(特别是非 root 用户)可能没有权限访问或修改这些文件,导致容器内的应用无法正常读取或应用挂载的文件。而 777 允许任何用户对文件进行读、写、执行操作,因此容器内的任何进程都能够对这些文件进行操作。
  2. 容器内用户和宿主机用户权限不一致:有时宿主机的文件权限和容器内进程运行的用户权限可能不一致。宿主机上的文件可能是由 root 用户创建的,而容器内的进程可能是以非 root 用户身份运行。如果挂载的文件权限没有给予其他用户写入权限,容器内的进程就无法修改文件。通过将文件权限设置为 777,无论容器进程使用什么用户,都可以访问这些文件。
  3. 避免文件系统限制:有些文件系统(尤其是挂载自某些网络存储或共享存储)可能会有权限限制,导致容器无法访问或修改文件。设置 777 权限能够绕过这种问题,确保文件可以被容器的任何进程访问。

但是,为什么不推荐总是设置为 777
#

虽然设置 777 可以解决很多权限相关的问题,但这种做法存在明显的安全风险:

  1. 过度暴露权限777 使得文件对所有用户都开放,包括任何可能在容器内或宿主机上运行的进程。这意味着任何人都可以读取、修改甚至删除这些文件,增加了潜在的安全风险。例如,恶意进程或未经授权的用户可以轻松修改这些文件,导致容器或应用程序遭到破坏。
  2. 泄露敏感信息:如果容器或宿主机中有敏感数据(如密钥、密码等),将文件权限设置为 777 会让这些敏感数据易受攻击。
  3. 不符合最小权限原则:在安全最佳实践中,应该遵循“最小权限原则”,即只有必要的用户和进程才应该具有访问权限。将权限设置为 777 违背了这一原则,因为它将所有权限都赋予了所有用户。

更安全的做法
#

为了避免潜在的安全问题,可以考虑以下更安全的方法:

  1. 确保容器内的用户有正确的权限:可以通过设置容器内进程的用户和组权限,确保它们与宿主机上的文件权限匹配。例如,使用 Dockerfile 设置容器内进程的用户和组:
USER <username>
  1. 修改宿主机文件权限:可以将文件的权限调整为只对特定用户(如容器中的运行用户)开放:
chown <username>:<groupname> /path/to/mounted/file
chmod 755 /path/to/mounted/file
  1. 这样,只有特定的用户和进程才能访问这些文件,而不会暴露给所有用户。
  2. 使用 Docker 的卷管理:如果使用的是卷挂载而非直接的绑定挂载,可以通过 Docker 提供的卷管理工具来处理文件和权限。Docker 卷本身会有更细粒度的权限控制,避免直接操作宿主机的文件系统。

总结
#

虽然将文件权限设置为 777 可以暂时解决容器无法访问或修改挂载文件的问题,但它也带来了安全风险。为了确保系统的安全性和稳定性,建议仅在必要时使用 777,并根据实际需求调整文件和用户权限,尽量避免过度暴露权限。

参考:

1.Docker挂载的文件与主机不同步问题

2.The same file on host and inside container is different, it is not in sync with its original

3.【博客609】docker volume挂载本机文件出现不同步的问题解决

相关文章