以非root用户身份运行Docker守护程序(无根模式)

预计阅读时间:17分钟

无根模式允许以非root用户身份运行Docker守护程序和容器,以减轻守护程序和容器运行时中的潜在漏洞。

只要满足先决条件,即使在Docker守护程序安装期间,无根模式也不需要root特权。

无根模式是Docker Engine v19.03中引入的一项实验功能。无根模式从Docker Engine v20.10的实验中毕业。

这个怎么运作

无根模式在用户名称空间内执行Docker守护程序和容器。这与userns-remapmode非常相似,除了模式之外,userns-remap守护进程本身以root特权运行,而在无根模式下,守护程序和容器都在没有root特权的情况下运行。

无根模式不使用具有SETUID位或文件功能的二进制文件,除了newuidmapnewgidmap,它们是允许在用户名称空间中使用多个UID / GID所必需的。

先决条件

  • 您必须在主机上安装newuidmapnewgidmap。这些命令由uidmap大多数发行版的软件包提供。

  • /etc/subuid/etc/subgid应至少包含该用户的65,536个从属UID / GID。在以下示例中,用户testuser具有65,536个从属UID / GID(231072-296607)。

$ id -u
1001
$ whoami
testuser
$ grep ^$(whoami): /etc/subuid
testuser:231072:65536
$ grep ^$(whoami): /etc/subgid
testuser:231072:65536

特定于发行版的提示

注意:我们建议您使用Ubuntu内核。

  • 无需准备。

  • overlay2默认情况下启用存储驱动程序(特定于Ubuntu的内核补丁)。

  • 已知可在Ubuntu 16.04、18.04和20.04上运行。

  • 添加kernel.unprivileged_userns_clone=1/etc/sysctl.conf(或 /etc/sysctl.d)并运行sudo sysctl --system

  • 要使用overlay2存储驱动程序(推荐),请运行 sudo modprobe overlay permit_mounts_in_userns=1Debian 10中引入了Debian特定的内核补丁)。将配置添加到/etc/modprobe.d持久性。

  • fuse-overlayfs建议安装。运行sudo pacman -S fuse-overlayfs

  • 添加kernel.unprivileged_userns_clone=1/etc/sysctl.conf(或 /etc/sysctl.d)并运行sudo sysctl --system

  • fuse-overlayfs建议安装。运行sudo zypper install -y fuse-overlayfs

  • sudo modprobe ip_tables iptable_mangle iptable_nat iptable_filter是必须的。根据配置,其他发行版可能也需要这样做。

  • 已知可以在openSUSE 15上工作。

  • fuse-overlayfs建议安装。运行sudo dnf install -y fuse-overlayfs

  • 您可能需要sudo dnf install -y iptables

  • 启用S​​ELinux后,您可能会遇到can't open lock file /run/xtables.lock: Permission denied错误。解决此问题的方法是sudo dnf install -y policycoreutils-python-utils && sudo semanage permissive -a iptables_t。此问题已在moby / moby#41230中进行了跟踪。

  • 已知可在CentOS 8和Fedora 33上工作。

  • 添加user.max_user_namespaces=28633/etc/sysctl.conf(或 /etc/sysctl.d)并运行sudo sysctl --system

  • systemctl --user默认情况下不起作用。dockerd-rootless.sh不使用systemd直接运行。

已知限制

  • 仅支持以下存储驱动程序:
    • overlay2 (仅在以5.11或更高版本的内核,Ubuntu风格的内核或Debian风格的内核运行时)
    • fuse-overlayfs(仅在与内核4.18或更高版本一起运行且fuse-overlayfs已安装的情况下)
    • btrfs(仅在使用内核4.18或更高版本运行,或~/.local/share/docker通过user_subvol_rm_allowedmount选项安装时)
    • vfs
  • 仅当与cgroup v2和systemd一起运行时,才支持Cgroup。请参阅限制资源
  • 不支持以下功能:
    • AppArmor
    • 检查站
    • 叠加网络
    • 暴露SCTP端口
  • 要使用该ping命令,请参阅路由ping数据包
  • 要公开特权TCP / UDP端口(<1024),请参阅公开特权端口
  • IPAddress显示在中,docker inspect并在RootlessKit的网络名称空间中命名。这意味着如果不nsenter进入网络名称空间,则无法从主机访问IP地址。
  • 主机网络(docker run --net=host)也位于RootlessKit中。

安装

笔记

如果系统范围的Docker守护程序已在运行,请考虑将其禁用: $ sudo systemctl disable --now docker.service docker.socket

如果您安装了带有RPM / DEB软件包的Docker 20.10或更高版本,则应在dockerd-rootless-setuptool.sh/usr/bin

dockerd-rootless-setuptool.sh install以非root用户身份运行以设置守护程序:

$ dockerd-rootless-setuptool.sh install
[INFO] Creating /home/testuser/.config/systemd/user/docker.service
...
[INFO] Installed docker.service successfully.
[INFO] To control docker.service, run: `systemctl --user (start|stop|restart) docker.service`
[INFO] To run docker.service on system startup, run: `sudo loginctl enable-linger testuser`

[INFO] Make sure the following environment variables are set (or add them to ~/.bashrc):

export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix:///run/user/1000/docker.sock

如果dockerd-rootless-setuptool.sh不存在,则可能需要docker-ce-rootless-extras手动安装软件包,例如,

$ sudo apt-get install -y docker-ce-rootless-extras

如果你没有运行包管理器一样的权限apt-getdnf,考虑使用提供的安装脚本在https://get.docker.com/rootless

$ curl -fsSL https://get.docker.com/rootless | sh
...
[INFO] Creating /home/testuser/.config/systemd/user/docker.service
...
[INFO] Installed docker.service successfully.
[INFO] To control docker.service, run: `systemctl --user (start|stop|restart) docker.service`
[INFO] To run docker.service on system startup, run: `sudo loginctl enable-linger testuser`

[INFO] Make sure the following environment variables are set (or add them to ~/.bashrc):

export PATH=/home/testuser/bin:$PATH
export DOCKER_HOST=unix:///run/user/1000/docker.sock

二进制文件将安装在~/bin

如果遇到错误,请参阅故障排除

卸载

要删除Docker守护程序的systemd服务,请运行dockerd-rootless-setuptool.sh uninstall

$ dockerd-rootless-setuptool.sh uninstall
+ systemctl --user stop docker.service
+ systemctl --user disable docker.service
Removed /home/testuser/.config/systemd/user/default.target.wants/docker.service.
[INFO] Uninstalled docker.service
[INFO] This uninstallation tool does NOT remove Docker binaries and data.
[INFO] To remove data, run: `/usr/bin/rootlesskit rm -rf /home/testuser/.local/share/docker`

要删除数据目录,请运行rootlesskit rm -rf ~/.local/share/docker

要删除二进制文件,docker-ce-rootless-extras请在软件包管理器中安装了Docker的情况下删除软件包。如果您使用https://get.docker.com/rootless(不带软件包安装)安装了Docker,请删除以下二进制文件~/bin

$ cd ~/bin
$ rm -f containerd containerd-shim containerd-shim-runc-v2 ctr docker docker-init docker-proxy dockerd dockerd-rootless-setuptool.sh dockerd-rootless.sh rootlesskit rootlesskit-docker-proxy runc vpnkit

用法

守护进程

systemd单位文件安装为 ~/.config/systemd/user/docker.service

使用systemctl --user管理守护程序的生命周期:

$ systemctl --user start docker

要在系统启动时启动守护程序,请启用systemd服务并持续进行以下操作:

$ systemctl --user enable docker
$ sudo loginctl enable-linger $(whoami)

/etc/systemd/system/docker.service即使使用User=指令,也不支持将Rootless Docker作为全系统范围的服务()启动。

要在不使用systemd的情况下直接运行守护程序,您需要运行dockerd-rootless.sh而不是dockerd

必须设置以下环境变量:

  • $HOME:主目录
  • $XDG_RUNTIME_DIR:临时目录,只有预期的用户可以访问,例如~/.docker/run。该目录应在每次主机关闭时删除。该目录可以位于tmpfs上,但是不应位于之下/tmp。在此目录下/tmp可能容易受到TOCTOU攻击。

关于目录路径的说明:

  • 套接字路径$XDG_RUNTIME_DIR/docker.sock默认设置为。 $XDG_RUNTIME_DIR通常设置为/run/user/$UID
  • 数据目录~/.local/share/docker默认设置为。数据目录不应位于NFS上。
  • 守护程序配置目录~/.config/docker默认设置为。此目录~/.docker与客户端使用的目录不同。

客户

您需要明确指定套接字路径或CLI上下文。

要指定套接字路径,请使用$DOCKER_HOST

$ export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
$ docker run -d -p 8080:80 nginx

要指定CLI上下文,请使用docker context

$ docker context use rootless
rootless
Current context is now "rootless"
$ docker run -d -p 8080:80 nginx

最佳实务

Docker中的无根Docker

要在“根” Docker中运行无根Docker,请使用docker:<version>-dind-rootless 映像代替docker:<version>-dind

$ docker run -d --name dind-rootless --privileged docker:20.10-dind-rootless

docker:<version>-dind-rootless映像以非root用户身份运行(UID 1000)。但是,--privileged禁用seccomp,AppArmor和安装掩码是必需的。

通过TCP公开Docker API套接字

为了揭露通过TCP泊坞窗API插座,你需要推出dockerd-rootless.shDOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS="-p 0.0.0.0:2376:2376/tcp"

$ DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS="-p 0.0.0.0:2376:2376/tcp" \
  dockerd-rootless.sh \
  -H tcp://0.0.0.0:2376 \
  --tlsverify --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem

通过SSH公开Docker API套接字

要通过SSH公开Docker API套接字,您需要确保$DOCKER_HOST 已在远程主机上设置了该套接字。

$ ssh -l <REMOTEUSER> <REMOTEHOST> 'echo $DOCKER_HOST'
unix:///run/user/1001/docker.sock
$ docker -H ssh://<REMOTEUSER>@<REMOTEHOST> run ...

路由ping数据包

在某些发行版中,ping默认情况下不起作用。

添加net.ipv4.ping_group_range = 0 2147483647/etc/sysctl.conf(或 /etc/sysctl.d)并运行sudo sysctl --system以允许使用ping

公开特权端口

要公开特权端口(<1024),请设置CAP_NET_BIND_SERVICErootlesskit二进制。

$ sudo setcap cap_net_bind_service=ep $HOME/bin/rootlesskit

或添加net.ipv4.ip_unprivileged_port_start=0/etc/sysctl.conf(或 /etc/sysctl.d)并运行sudo sysctl --system

限制资源

仅当与cgroup v2和systemd一起运行时,才支持使用与cgroup相关的docker run标志(例如--cpus,)限制资源。请参阅更改cgroup版本以启用cgroup v2。--memory--pids-limit

如果docker info显示noneCgroup Driver,则不满足条件。当不满足这些条件时,无根模式将忽略与cgroup相关的docker run标志。有关变通办法,请参阅在不使用cgroup情况下限制资源

如果docker info显示systemdCgroup Driver,则满足条件。但是,通常,默认情况下,仅memorypids控制器被委派给非root用户。

$ cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/cgroup.controllers
memory pids

要允许委派所有控制器,您需要按以下方式更改systemd配置:

# mkdir -p /etc/systemd/system/user@.service.d
# cat > /etc/systemd/system/user@.service.d/delegate.conf << EOF
[Service]
Delegate=cpu cpuset io memory pids
EOF
# systemctl daemon-reload

笔记

委派cpuset需要systemd 244或更高版本。

在没有cgroup的情况下限制资源

即使cgroup不可用,您仍然可以使用传统的ulimitand cpulimit,尽管它们以进程粒度而不是容器粒度运行,并且可以被容器进程任意禁用。

例如:

  • 要将CPU使用率限制为0.5个内核(类似于docker run --cpus 0.5): docker run <IMAGE> cpulimit --limit=50 --include-children <COMMAND>
  • 要将最大VSZ限制为64MiB(类似于docker run --memory 64m): docker run <IMAGE> sh -c "ulimit -v 65536; <COMMAND>"

  • 要将每个命名空间UID 2000的最大进程数限制为100(类似于docker run --pids-limit=100),请执行以下操作: docker run --user 2000 --ulimit nproc=100 <IMAGE> <COMMAND>

故障排除

启动Docker守护程序时发生错误

[rootlesskit:parent]错误:无法启动子级:fork / exec / proc / self / exe:不允许操作

的错误主要是在的值/proc/sys/kernel/unprivileged_userns_clone 设置为0时发生的:

$ cat /proc/sys/kernel/unprivileged_userns_clone
0

要解决此问题,请添加 kernel.unprivileged_userns_clone=1/etc/sysctl.conf(或/etc/sysctl.d)并运行sudo sysctl --system

[rootlesskit:parent]错误:无法启动子级:fork / exec / proc / self / exe:设备上没有剩余空间

的错误主要是由于的值/proc/sys/user/max_user_namespaces太小:

$ cat /proc/sys/user/max_user_namespaces
0

要解决此问题,请添加 user.max_user_namespaces=28633/etc/sysctl.conf(或/etc/sysctl.d)并运行sudo sysctl --system

[rootlesskit:parent]错误:无法设置UID / GID映射:未能计算uid / gid映射:未找到用户1001(“ testuser”)的子uid范围

未配置/etc/subuid和时,/etc/subgid会发生此错误。请参阅先决条件

无法获得XDG_RUNTIME_DIR

$XDG_RUNTIME_DIR未设置时发生此错误。

在非系统主机上,您需要创建一个目录,然后设置路径:

$ export XDG_RUNTIME_DIR=$HOME/.docker/xrd
$ rm -rf $XDG_RUNTIME_DIR
$ mkdir -p $XDG_RUNTIME_DIR
$ dockerd-rootless.sh

注意:每次注销时必须删除目录。

在systemd主机上,使用登录到主机pam_systemd(请参见下文)。该值将自动设置为,/run/user/$UID并在每次注销时清除。

systemctl --user 失败并显示“无法连接到总线:没有这样的文件或目录”

当您使用以下命令从root用户切换到非root用户时,通常会发生此错误sudo

# sudo -iu testuser
$ systemctl --user start docker
Failed to connect to bus: No such file or directory

取而代之的是sudo -iu <USERNAME>,您需要使用登录pam_systemd。例如:

  • 通过图形控制台登录
  • ssh <USERNAME>@localhost
  • machinectl shell <USERNAME>@

守护程序不会自动启动

您需要sudo loginctl enable-linger $(whoami)启用守护程序以自动启动。请参阅用法

iptables失败:iptables -t nat -N DOCKER:致命:无法打开锁定文件/run/xtables.lock:权限被拒绝

在主机上启用SELinux时可能会发生此错误。

已知的解决方法是运行以下命令来禁用SELinux iptables

$ sudo dnf install -y policycoreutils-python-utils && sudo semanage permissive -a iptables_t

此问题已在moby / moby#41230中进行了跟踪。

docker pull 错误

泊坞窗:无法注册层:错误处理tar文件(退出状态1):lchown <FILE>:无效参数

当可用条目数不足/etc/subuid/etc/subgid不足时,会发生此错误。所需的条目数量因图像而异。但是,对于大多数图像来说,有65,536个条目就足够了。请参阅 先决条件

泊坞窗:无法注册层:ApplyLayer退出状态1 stdout:stderr:lchown <FILE>:不允许操作

大多数~/.local/share/docker位于NFS上时,会发生此错误。

一种解决方法是按以下方式指定非NFSdata-root目录~/.config/docker/daemon.json

{"data-root":"/somewhere-out-of-nfs"}

docker run 错误

--cpus--memory以及--pids-limit被忽略

这是cgroup v1模式下的预期行为。要使用这些标志,需要将主机配置为启用cgroup v2。有关更多信息,请参见限制资源

网路错误

docker run -p 失败于 cannot expose privileged port

docker run -p 当将特权端口(<1024)指定为主机端口时,此错误将失败,并显示此错误。

$ docker run -p 80:80 nginx:alpine
docker: Error response from daemon: driver failed programming external connectivity on endpoint focused_swanson (9e2e139a9d8fc92b37c36edfa6214a6e986fa2028c0cc359812f685173fa6df7): Error starting userland proxy: error while calling PortManager.AddPort(): cannot expose privileged port 80, you might need to add "net.ipv4.ip_unprivileged_port_start=0" (currently 1024) to /etc/sysctl.conf, or set CAP_NET_BIND_SERVICE on rootlesskit binary, or choose a larger port number (>= 1024): listen tcp 0.0.0.0:80: bind: permission denied.

遇到此错误时,请考虑使用非特权端口。例如,用8080代替80。

$ docker run -p 8080:80 nginx:alpine

要允许公开特权端口,请参阅公开特权端口

ping不起作用

当平不起作用/proc/sys/net/ipv4/ping_group_range设置为1 0

$ cat /proc/sys/net/ipv4/ping_group_range
1       0

有关详细信息,请参阅路由ping数据包

IPAddress中显示的docker inspect内容无法访问

这是预期的行为,因为该守护进程在RootlessKit的网络名称空间中被命名。使用docker run -p代替。

--net=host 不侦听主机网络名称空间上的端口

这是预期的行为,因为该守护进程在RootlessKit的网络名称空间中被命名。使用docker run -p代替。

网络速度慢

如果安装了slirp4netns v0.4.0或更高版本,则无根模式的Docker将slirp4netns用作默认网络堆栈。如果未安装slirp4netns,则Docker将退回到VPNKit

安装slirp4netns可以提高网络吞吐量。有关基准测试结果,请参见RootlessKit文档

同样,更改MTU值可以提高吞吐量。可以通过添加Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_MTU=<INTEGER>" 到MTU值~/.config/systemd/user/docker.service然后运行来指定MTU值systemctl --user daemon-reload

docker run -p 不传播源IP地址

这是因为默认情况下,具有无根模式的Docker使用RootlessKit的内置端口驱动程序。

可以通过添加Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=slirp4netns" 到源IP地址~/.config/systemd/user/docker.service然后运行来传播源IP地址systemctl --user daemon-reload

请注意,此配置会降低吞吐量。有关基准测试结果,请参见RootlessKit文档

调试技巧

进入dockerd名称空间

dockerd-rootless.sh脚本dockerd在其自己的用户,安装和网络名称空间中执行。

为了进行调试,您可以通过运行输入名称空间 nsenter -U --preserve-credentials -n -m -t $(cat $XDG_RUNTIME_DIR/docker.pid)

安全命名空间无根