在docker中,在容器内创建的文件在从主机检查时往往具有不可预测的所有权。默认情况下,卷上文件的所有者是root(uid 0),但只要非root用户帐户涉及容器并写入文件系统,所有者就会从主机角度变得或多或少随机 。
您需要使用调用docker命令的同一用户帐户从主机访问卷数据时,这是一个问题。
典型的解决方法是

  • 在Dockerfiles中创建时强制用户uID(非可移植)
  • 将主机用户的UID作为环境变量传递给docker run命令,然后在入口点脚本中的卷上运行一些chown 命令 。

这两种解决方案都可以控制容器外的实际权限。
用户命名空间(user namespace)是这个问题的最终解决方案.。
通过 user namespace 技术,把宿主机中的一个普通用户(只有普通权限的用户)映射到容器中的 root 用户。在容器中,该用户在自己的 user namespace 中认为自己就是 root,也具有 root 的各种权限,但是对于宿主机上的资源,它只有很有限的访问权限(普通用户)。

使用user namespace进行权限隔离设置如下:

1、kernel内核开启namespace

grubby --args="namespace.unpriv_enable=1 user_namespace.enable=1" --update-kernel="$(grubby --default-kernel)"
echo "user.max_user_namespaces=15076" >> /etc/sysctl.conf

重启系统生效

reboot

2、新建用户(这里的用户名为:dku)

useradd -u 5000 dku
groupadd -g 500000 dku-root
useradd -u 500000 -g dku-root dku-root

在/etc/subuid 和 /etc/subgid 文件中修改自动生成的从属ID范围:
dku:500000:65536
如果这两个文件有内容,就可以执行以下sed命令进行修改,没内容手动添加进入即可。

sed -ri '/dku/s/:[0-9]+:/:500000:/' /etc/subuid
sed -ri '/dku/s/:[0-9]+:/:500000:/' /etc/subgid

用户dku 在当前的 user namespace 中具有 65536 个从属用户,用户 ID 为 500000-565535,在一个子 user namespace 中,这些从属用户被映射成 ID 为 0-65535 的用户。subgid 的含义和 subuid 相同。

3、使用docker权限映射(注意:使用映射代表容器root用户映射到宿主机docker指定的用户uid上)

这里有3步:
(1)在/etc/default/docker上追加配置DOCKER_OPTS
(2)修改 /usr/lib/systemd/system/docker.service 启动文件,在ExecStart前添加 EnvironmentFile变量文件
(3)修改 /usr/lib/systemd/system/docker.service 启动文件中的ExecStart,最后添加$DOCKER_OPTS

echo 'DOCKER_OPTS="--userns-remap=dku"' >> /etc/default/docker
sed -i '/ExecStart/iEnvironmentFile=/etc/default/docker' /usr/lib/systemd/system/docker.service
sed -i 's/ExecStart.*/& $DOCKER_OPTS/'  /usr/lib/systemd/system/docker.service

重新启动docker:

systemctl daemon-reload
systemctl restart docker.service

4、验证是否启动生效

执行 systemctl status docker 命令查看进程状态

[root@localhost ~]# systemctl status docker.service 
● docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
   Active: active (running) since Mon 2022-05-09 19:07:11 CST; 18h ago
     Docs: https://docs.docker.com
 Main PID: 1100 (dockerd)
    Tasks: 18
   Memory: 175.2M
   CGroup: /system.slice/docker.service
           └─1100 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --userns-remap=dku

带有 --userns-remap=dku 表示启动成功

5、启动用户命名空间后的状态

5.1 访问数据卷中的文件

假如,在容器里面创建一个 UID 为1000 的 test 用户,他能读取挂载在容器里面的什么权限的文件呢?
测试文件权限:在系统的 /data/test 目录上,创建几个不同的测试文件
1. 一个属主和属组都为 501000 的文件,权限为600,文件名为:501000.txt 。
2. 一个属主和属组都为 dku-root (UID为500000)的文件,权限为600,文件名为:dku-root.txt 。
3. 一个在系统上权限为 root (UID为0)的文件,权限为600,文件名为:root.txt 。
4. 一个在系统上权限为 root (UID为0)的文件,权限为666,文件名为:test.txt 。

[root@localhost test]# ll
total 16
-rw------- 1   501000   501000 3 Nov  2  2021 501000.txt
-rw------- 1 dku-root dku-root 3 Nov  2  2021 dku-root.txt
-rw------- 1 root     root     3 Nov  2  2021 root.txt
-rw-rw-rw- 1 root     root     3 Nov  2  2021 test.txt

6.启动一个centos 7.9 的容器,并在里面创建一个 UID 为 1000 的test 用户,进入挂载的 /test 目录。
查看权限如下:

[root@localhost test]# docker run -it -v /data/test:/test centos:7.9.2009 bash
[root@cfbc05808fd3 /]# 
[root@cfbc05808fd3 /]# useradd -u 1000 test
[root@cfbc05808fd3 /]# 
[root@cfbc05808fd3 /]# su - test
[test@cfbc05808fd3 ~]$ 
[test@cfbc05808fd3 ~]$ cd /test
[test@cfbc05808fd3 test]$ 
[test@cfbc05808fd3 test]$ ls -l
total 16
-rw------- 1 test  test  3 Nov  2  2021 501000.txt
-rw------- 1 root  root  3 Nov  2  2021 dku-root.txt
-rw------- 1 65534 65534 3 Nov  2  2021 root.txt
-rw-rw-rw- 1 65534 65534 3 Nov  2  2021 test.txt
[test@cfbc05808fd3 test]$ 
[test@cfbc05808fd3 test]$ cat 501000.txt 
ok
[test@cfbc05808fd3 test]$ 
[test@cfbc05808fd3 test]$ cat dku-root.txt 
cat: dku-root.txt: Permission denied
[test@cfbc05808fd3 test]$ 
[test@cfbc05808fd3 test]$ cat root.txt 
cat: root.txt: Permission denied
[test@cfbc05808fd3 test]$ 
[test@cfbc05808fd3 test]$ cat test.txt 
ok
[test@cfbc05808fd3 test]$

结果是只有 501000.txt 和 test.txt 这两个文件可以看到里面内容。剩下两个均没有权限。其中501000.txt 有权限,是因为在系统上这个文件的属主和属组都是 501000,其映射在容器里面的属主和属组就是1000,所以UID 为 1000 的 test 用户可以查看里面的内容。
test.txt 能查看,因为文件的权限是666,other 位的权限是 rw,所以其他用户都有权限查看。

所以要给容器里面挂载一个有root 权限的目录,在系统上就需要给这个目录 权限给dku-root。比如要挂载的目录是 /data,那命令就是命令如下:

chown -R dku-root:dku-root /data

5.2 宿主机的进程 UID 和 容器的 UID

当启动了user namespace 后,容器里面的进程是怎样的呢?指定root 用户启动一个 centos 7.9 的容器,里面执 sleep 1000 命令。

[root@localhost test]# docker run -it centos:7.9.2009 bash
[root@0c3f7f0e7031 /]# sleep 1000

在系统中查看该sleep 1000 的进程属主时发现不是 root,而是定义的UID 为 500000 的 dku-root 用户。

[root@localhost ~]# ps -elf | grep sleep
0 S dku-root  71438  71403  0  80   0 -  1091 hrtime 14:03 pts/0    00:00:00 sleep 1000
0 S root      71494  71443  0  80   0 - 28170 pipe_w 14:04 pts/5    00:00:00 grep --color=auto sleep

有没有办法可以在启动 user namespace 情况下,里面的进行又想恢复 root 运行呢?也是有的。

6、在容器中禁用 user namespace

一旦为docker daemon设置了"userns-remap"参数,所有的容器默认都会启用用户隔离的功能(默认创建一个新的 user namespace)。有些情况下我们可能需要回到没有开启用户隔离的场景,,这时可以通过--userns=host参数为单个的容器禁用用户隔离功能。在version: "3"版本的docker-compose.yml文件中对应的容器增加userns_mode: "host"

以同样的 sleep 1000 为例子,启动的时候加上--userns=host

[root@localhost test]# docker run -it --userns=host centos:7.9.2009 bash
[root@09a0fb6ef75f /]# sleep 1000

在系统中,查看 sleep 1000 进程时,进程的属主恢复为 root。

[root@localhost ~]# ps -elf | grep sleep
0 S root      71664  71630  0  80   0 -  1091 hrtime 14:06 pts/0    00:00:00 sleep 1000
0 S root      71666  71443  0  80   0 - 28170 pipe_w 14:06 pts/5    00:00:00 grep --color=auto sleep

参考地址:https://www.shuzhiduo.com/A/ZOJPrrrxdv/