在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