Contents

Docker容器中的文件所有权

1. 概述

在本教程中,我们将查看 Docker 容器中已挂载文件和文件夹的所有权。特别是,当我们将文件从主机挂载到容器上时,我们将研究文件所有权的变化。

2. Host和Container之间的文件所有权差异

**当我们将一个目录挂载到一个 Docker 容器中时,其中的文件和目录的所有者有时会被另一个用户所拥有。**例如,对于给定目录,所有者可以是主机上的用户blogdemo。但是,相同的文件和文件夹在挂载时可能会由容器内的不同用户拥有。此观察的一个例外是 root 拥有的文件和文件夹将始终继续由容器内的 root 用户拥有。

让我们通过将包含文件和目录的文件夹挂载到 Docker 容器中来演示该问题。首先,我们将创建一个目录作为主机上的挂载点:

$ mkdir container-mount

随后,我们将创建文件和目录并将它们放入container-mount文件夹中。最后,我们将运行以下命令来获取他们的所有权信息:

$ ls -l container-mount 
total 4
drwxrwxr-x 2 blogdemo blogdemo 4096 Dec 27 08:48 inner-dir
-rw-rw-r-- 1 blogdemo blogdemo    0 Dec 27 08:48 log1.txt
-rw-rw-r-- 1 blogdemo blogdemo    0 Dec 27 08:48 log2.txt

从主机的角度来看,我们可以看到容器挂载中的每个文件和文件夹都属于blogdemo用户,也就是创建这些文件和文件夹的用户。

然后,我们启动一个运行 Ubuntu 镜像的 Docker 容器,将container-mount文件夹挂载到 /opt/mount路径:

$ sudo docker run -it -v /home/blogdemo/container-mount:/opt/mount ubuntu:latest sh
/ #

在容器中,我们可以使用相同的命令检查挂载文件和文件夹的所有者信息:

/ # ls -l /opt/mount
total 4
drwxrwxr-x    2 1000     1000          4096 Dec 27 07:48 inner-dir
-rw-rw-r--    1 1000     1000             0 Dec 27 07:48 log1.txt
-rw-rw-r--    1 1000     1000             0 Dec 27 07:48 log2.txt

有趣的是,相同文件和文件夹的所有者现在是用户 1000,而不是主机中的 blogdemo

3. 了解用户 ID 在 Linux 中的工作原理

在 Linux 中,所有用户都有一个与之关联的用户 ID (uid)。例如,root 用户的 uid 值为 0。要检查用户的 uid,我们可以运行以下命令:

$ id -u blogdemo
1000

Linux 存储文件和目录的所有者信息时,它们在内部存储的是用户的 uid。

为了在调用ls -l 命令时显示用户名,在/etc/passwd上执行查找以将 uid 映射到用户名。同样,当我们使用chown 更改文件的所有者时,该命令会查询*/etc/passwd以获取给定用户名的 uid。有趣的是,因为从文件所有权的角度来看,用户名是多余的,我们可以**在/etc/passwd*文件中将文件的所有者更改为不属于任何用户的 uid 。**

例如,我们可以 将文件chown为 uid  2888,它不属于系统中的任何用户:

$ id -nu 2888
id: ‘2888’: no such user
$ sudo chown 2888:2888 container-mount
$ ls -l
total 4
drwxrwxr-x 3 2888 2888 4096 Dec 27 08:48 container-mount

相反,将文件或文件夹更改为不存在的用户名将不起作用。这是因为用户名不会映射到任何有效的 uid,从而阻止 Linux 设置文件的权限位:

$ sudo chown bob:bob container-mount
chown: invalid user: ‘bob:bob’

*为了解释挂载文件的差异,我们只需要认识到 Docker 容器维护/etc/passwd的独立副本。*换句话说,主机上的 uid 1000不会转换为容器内的相同用户名。事实上,存在于主机上的用户名并不存在于 Docker 容器中,除非显式创建。这就是为什么我们看到文件为 uid 1000而不是 blogdemo拥有的原因, 因为 容器内的/etc/passwd中不存在blogdemo

4. 解决方法

处理此问题的一种直接方法是确保同一用户也出现在具有相同 uid 的容器的*/etc/passwd*文件中。

4.1.在容器中创建具有相同 UID 的用户

在容器中,我们可以使用带有*-u*标志 的useradd 命令创建具有特定 uid 的用户。例如,在容器内,我们可以创建一个 uid 为 1000的用户blogdemo

/ # useradd blogdemo -u 1000

一旦我们创建了用户,挂载的文件和文件夹现在将显示blogdemo作为所有者:

/ # ls -l /opt/mount
total 4
drwxrwxr-x    2 blogdemo blogdemo      4096 Dec 27 07:48 inner-dir
-rw-rw-r--    1 blogdemo blogdemo         0 Dec 27 07:48 log1.txt
-rw-rw-r--    1 blogdemo blogdemo         0 Dec 27 07:48 log2.txt

此解决方案的一个警告是,在重新创建容器后,我们需要重新运行该命令。

4.2. 使用 Dockerfile 自定义镜像

我们可以将脚本放入 Dockerfile 并将其烘焙到映像本身中。例如,我们可以将创建uid 为1000的用户blogdemo的命令放入 Dockerfile:

FROM ubuntu:latest
RUN useradd blogdemo -u 1000

或者,**我们可以 使用ARG Dockerfile 指令使用环境变量参数化uid值。*在构建期间,我们将使用–build-arg*标志传递值。为此,我们只需将值 1000更改为环境变量:

FROM ubuntu:latest
ARG HOST_UID
RUN useradd blogdemo -u $HOST_UID

使用ARG指令,我们可以定义一个我们将在构建期间传递的变量。然后,我们可以在指定*–build-arg*标志的同时运行 docker build命令 :

$ sudo docker build --build-arg HOST_UID=$(id -u) --tag ubuntu-custom:latest .

id -u命令只返回当前用户 uid,然后将该值设置为构建参数HOST_UID