無根模式

無根模式允許以非 root 使用者身分執行 Docker Daemon 和容器,以減輕 Daemon 和容器執行階段中潛在的弱點。

只要滿足先決條件,無根模式即使在安裝 Docker Daemon 期間也不需要 root 權限。

運作方式

無根模式在使用者命名空間內執行 Docker Daemon 和容器。這與userns-remap 模式非常相似,不同之處在於使用 userns-remap 模式時,Daemon 本身是以 root 權限執行的,而在無根模式下,Daemon 和容器都以非 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 核心。


  • 重新啟動 AppArmor。

    • 建議安裝 `fuse-overlayfs`。執行 `sudo pacman -S fuse-overlayfs` 指令。

    • 將 `kernel.unprivileged_userns_clone=1` 加入到 `/etc/sysctl.conf`(或 `/etc/sysctl.d`)檔案中,並執行 `sudo sysctl --system` 指令。

    • 對於 openSUSE 15 和 SLES 15,建議安裝 `fuse-overlayfs`。執行 `sudo zypper install -y fuse-overlayfs` 指令。openSUSE Tumbleweed 不需要此步驟。

    • 需要執行 `sudo modprobe ip_tables iptable_mangle iptable_nat iptable_filter` 指令。根據配置,其他發行版可能也需要執行此指令。

    • 已知可在 openSUSE 15 和 SLES 15 上運作。

    • 對於 RHEL 8 和類似的發行版,建議安裝 `fuse-overlayfs`。執行 `sudo dnf install -y fuse-overlayfs` 指令。RHEL 9 和類似的發行版不需要此步驟。

    • 您可能需要執行 `sudo dnf install -y iptables` 指令。


    已知限制

    安裝

    **注意**

    如果系統級 Docker 守护程序已在運行,請考慮將其停用。

    $ sudo systemctl disable --now docker.service docker.socket
    $ sudo rm /var/run/docker.sock
    

    如果您選擇不安裝關閉 `docker` 服務和通訊端,則需要在下一節中使用 `--force` 參數。目前沒有已知問題,但在您關閉並停用之前,您仍在運行 rootful Docker。


    如果您使用RPM/DEB 套件安裝了 Docker 20.10 或更高版本,則 `/usr/bin` 中應該會有 `dockerd-rootless-setuptool.sh`。

    以非 root 使用者身分執行 `dockerd-rootless-setuptool.sh install` 來設定守护程序。

    $ 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-get` 和 `dnf` 等套件管理程式,請考慮使用位於 https://get.docker.com/rootless 的安裝腳本。由於 `s390x` 沒有靜態套件,因此不支援 `s390x`。

    $ 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`
    

    如果您已將環境變數 PATH 和 DOCKER_HOST 加入到 `~/.bashrc` 中,請取消設定它們。

    要移除資料目錄,請執行 `rootlesskit rm -rf ~/.local/share/docker` 指令。

    要移除二進制檔案,如果您使用套件管理程式安裝了 Docker,請移除 `docker-ce-rootless-extras` 套件。如果您使用 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 服務和 lingering。

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

    即使使用 `User=` 指令,也不支援將 Rootless Docker 作為系統級服務(`/etc/systemd/system/docker.service`)啟動。

    要在沒有 systemd 的情況下直接運行守护程序,您需要運行 `dockerd-rootless.sh` 而不是 `dockerd`。

    必須設定以下環境變數:

    • `$HOME`:家目錄
    • `$XDG_RUNTIME_DIR`:一個只有預期使用者可以訪問的臨時目錄,例如 `~/.docker/run`。該目錄應在每次主機關閉時移除。該目錄可以在 tmpfs 上,但不應位於 `/tmp` 下。將此目錄放在 `/tmp` 下可能容易受到 TOCTOU 攻擊。

    關於目錄路徑的備註

    用戶端

    您需要明確指定通訊端路徑或 CLI 上下文。

    使用 `$DOCKER_HOST` 指定通訊端路徑

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

    使用 `docker context` 指定 CLI 上下文

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

    最佳實務

    Docker in Docker 無根模式

    要在「rootful」Docker 內運行 Rootless Docker,請使用 `docker:<version>-dind-rootless` 鏡像而不是 `docker:<version>-dind`。

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

    `docker:<version>-dind-rootless` 鏡像以非 root 使用者(UID 1000)身分運行。但是,需要 `--privileged` 來停用 seccomp、AppArmor 和掛載遮罩。

    透過 TCP 公開 Docker API Socket

    要透過 TCP 公開 Docker API 通訊端,您需要使用 `DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS="-p 0.0.0.0:2376:2376/tcp"` 啟動 `dockerd-rootless.sh`。

    $ 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 Socket

    要透過 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),請在 `rootlesskit` 二進制檔案上設定 `CAP_NET_BIND_SERVICE` 並重新啟動守护程序。

    $ sudo setcap cap_net_bind_service=ep $(which rootlesskit)
    $ systemctl --user restart docker
    

    或者將 `net.ipv4.ip_unprivileged_port_start=0` 加入到 `/etc/sysctl.conf`(或 `/etc/sysctl.d`)檔案中,並執行 `sudo sysctl --system` 指令。

    限制資源

    僅在使用 cgroup v2 和 systemd 運行時,才支援使用與 cgroup 相關的 `docker run` 旗標(例如 `--cpus`、`--memory`、`--pids-limit`)來限制資源。請參閱更改 cgroup 版本以啟用 cgroup v2。

    如果 `docker info` 將 `Cgroup Driver` 顯示為 `none`,則表示不滿足條件。當不滿足這些條件時,rootless 模式會忽略與 cgroup 相關的 `docker run` 旗標。請參閱在沒有 cgroup 的情況下限制資源以了解解決方法。

    如果 `docker info` 將 `Cgroup Driver` 顯示為 `systemd`,則表示滿足條件。但是,通常預設情況下只有 `memory` 和 `pids` 控制器會委派給非 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 不可用,您仍然可以使用傳統的 `ulimit` 和 `cpulimit` ,但它們以程序粒度而不是容器粒度工作,並且可以被容器程序任意停用。

    例如:

    疑難排解

    當系統上存在 systemd 時,無法使用 systemd 安裝

    $ dockerd-rootless-setuptool.sh install
    [INFO] systemd not detected, dockerd-rootless.sh needs to be started manually:
    ...
    

    如果您透過 `sudo su` 切換到您的使用者,`rootlesskit` 將無法正確檢測 systemd。對於無法登入的使用者,您必須使用 `machinectl` 指令,它是 `systemd-container` 套件的一部分。安裝 `systemd-container` 後,使用以下指令切換到 `myuser`:

    $ sudo machinectl shell myuser@
    

    其中 `myuser@` 是您想要的使用者名稱,`@` 表示此機器。

    啟動 Docker Daemon 時發生錯誤

    [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") 的 subuid 範圍

    此錯誤發生在未配置 `/etc/subuid` 和 `/etc/subgid` 時。請參閱先決條件

    無法取得 XDG_RUNTIME_DIR

    $XDG_RUNTIME_DIR 未設定時,會發生此錯誤。

    在非 systemd 主機上,您需要建立一個目錄,然後設定路徑

    $ 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 失敗,並顯示「Failed to connect to bus: No such file or directory」(無法連線到匯流排:找不到檔案或目錄)

    當您使用 sudo 從 root 使用者切換到非 root 使用者時,通常會發生此錯誤

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

    您需要使用 pam_systemd 登入,而不是使用 sudo -iu <USERNAME>。例如

    守護行程不會自動啟動

    您需要使用 sudo loginctl enable-linger $(whoami) 來啟用守護行程自動啟動。請參閱用法

    iptables 失敗:iptables -t nat -N DOCKER: Fatal: can't open lock file /run/xtables.lock: Permission denied(iptables 失敗:iptables -t nat -N DOCKER:致命錯誤:無法開啟鎖定檔案 /run/xtables.lock:權限被拒)

    當主機上啟用 SELinux 時,舊版 Docker 可能會發生此錯誤。

    此問題已在 Docker 20.10.8 中修復。舊版 Docker 的已知解決方法是執行下列指令,為 iptables 關閉 SELinux

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

    docker pull 錯誤

    docker: failed to register layer: Error processing tar file(exit status 1): lchown <FILE>: invalid argument(docker:無法註冊層:處理 tar 檔案時發生錯誤(結束狀態 1):lchown <檔案>:無效的引數)

    /etc/subuid/etc/subgid 中可用的項目數量不足時,會發生此錯誤。所需的項目數量因映像檔而異。但是,對於大多數映像檔來說,65,536 個項目就足夠了。請參閱先決條件

    docker: failed to register layer: ApplyLayer exit status 1 stdout: stderr: lchown <FILE>: operation not permitted(docker:無法註冊層:ApplyLayer 結束狀態 1 標準輸出:標準錯誤:lchown <檔案>:不允許的操作)

    ~/.local/share/docker 位於 NFS 上時,通常會發生此錯誤。

    解決方法是在 ~/.config/docker/daemon.json 中指定非 NFS 的 data-root 目錄,如下所示

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

    docker run 錯誤

    docker: Error response from daemon: OCI runtime create failed: ...: read unix @->/run/systemd/private: read: connection reset by peer: unknown.(docker:來自守護行程的錯誤回應:OCI 執行階段建立失敗:...:讀取 unix @->/run/systemd/private:讀取:對等端重設連線:未知。)

    當使用者的 dbus 守護行程未執行時,cgroup v2 主機上通常會發生此錯誤。

    $ systemctl --user is-active dbus
    inactive
    
    $ docker run hello-world
    docker: Error response from daemon: OCI runtime create failed: container_linux.go:380: starting container process caused: process_linux.go:385: applying cgroup configuration for process caused: error while starting unit "docker
    -931c15729b5a968ce803784d04c7421f791d87e5ca1891f34387bb9f694c488e.scope" with properties [{Name:Description Value:"libcontainer container 931c15729b5a968ce803784d04c7421f791d87e5ca1891f34387bb9f694c488e"} {Name:Slice Value:"use
    r.slice"} {Name:PIDs Value:@au [4529]} {Name:Delegate Value:true} {Name:MemoryAccounting Value:true} {Name:CPUAccounting Value:true} {Name:IOAccounting Value:true} {Name:TasksAccounting Value:true} {Name:DefaultDependencies Val
    ue:false}]: read unix @->/run/systemd/private: read: connection reset by peer: unknown.
    

    要解決此問題,請執行 sudo apt-get install -y dbus-user-sessionsudo dnf install -y dbus-daemon,然後重新登入。

    如果錯誤仍然發生,請嘗試執行 systemctl --user enable --now dbus(不使用 sudo)。

    --cpus--memory--pids-limit 會被忽略

    這是 cgroup v1 模式下的預期行為。要使用這些旗標,主機需要設定為啟用 cgroup v2。如需更多資訊,請參閱限制資源

    網路錯誤

    本節提供無 root 模式下網路的疑難排解技巧。

    RootlessKit 中的網路和連接埠驅動程式支援無 root 模式的網路。網路效能和特性取決於您使用的網路和連接埠驅動程式的組合。如果您遇到與網路相關的非預期行為或效能問題,請查看下表,其中顯示了 RootlessKit 支援的組態,以及它們之間的比較

    網路驅動程式連接埠驅動程式網路吞吐量連接埠吞吐量來源 IP 傳播無 SUID備註
    slirp4netns內建快 ✅典型設定中的預設值
    vpnkit內建快 ✅未安裝 slirp4netns 時的預設值
    slirp4netnsslirp4netns
    pasta隱式快 ✅實驗性;需要 pasta 版本 2023_12_04 或更高版本
    lxc-user-nic內建快 ✅快 ✅實驗性
    bypass4netnsbypass4netns快 ✅快 ✅**注意:**未整合到 RootlessKit,因為它需要自訂 seccomp 設定檔

    有關特定網路問題的疑難排解資訊,請參閱

    docker run -p 失敗,並顯示 cannot expose privileged port(無法公開特權連接埠)

    當特權連接埠(< 1024)被指定為主機連接埠時,docker run -p 會發生此錯誤而失敗。

    $ 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 時,Ping 無法運作

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

    如需詳細資訊,請參閱路由 Ping 封包

    docker inspect 中顯示的 IPAddress 無法連線

    這是預期行為,因為守護行程位於 RootlessKit 網路命名空間內。請改用 docker run -p

    --net=host 未在主機網路命名空間上監聽連接埠

    這是預期行為,因為守護行程位於 RootlessKit 網路命名空間內。請改用 docker run -p

    網路速度慢

    如果已安裝 slirp4netns v0.4.0 或更高版本,則無 root 模式的 Docker 會使用slirp4netns 作為預設網路堆疊。如果未安裝 slirp4netns,Docker 會改用VPNKit。安裝 slirp4netns 可以提高網路吞吐量。

    有關 RootlessKit 網路驅動程式的更多資訊,請參閱RootlessKit 文件

    此外,更改 MTU 值可以提高吞吐量。可以透過使用下列內容建立 ~/.config/systemd/user/docker.service.d/override.conf 來指定 MTU 值

    [Service]
    Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_MTU=<INTEGER>"

    然後重新啟動守護行程

    $ systemctl --user daemon-reload
    $ systemctl --user restart docker
    

    docker run -p 不會傳播來源 IP 位址

    這是因為無 root 模式的 Docker 預設使用 RootlessKit 的 內建 連接埠驅動程式,該驅動程式不支援來源 IP 傳播。要啟用來源 IP 傳播,您可以

    pasta 網路驅動程式是實驗性的,但與 slirp4netns 連接埠驅動程式相比,它提供了更高的吞吐量效能。 pasta 驅動程式需要 Docker Engine 版本 25.0 或更高版本。

    要變更 RootlessKit 網路組態

    1. ~/.config/systemd/user/docker.service.d/override.conf 建立檔案。

    2. 根據您想要使用的組態,新增下列內容

      • slirp4netns

        [Service]
        Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_NET=slirp4netns"
        Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=slirp4netns"
      • pasta 網路驅動程式和 隱式 連接埠驅動程式

        [Service]
        Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_NET=pasta"
        Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=implicit"
    3. 重新啟動守護行程

      $ systemctl --user daemon-reload
      $ systemctl --user restart docker
      

    有關 RootlessKit 網路選項的更多資訊,請參閱

    除錯技巧

    進入 dockerd 命名空間

    dockerd-rootless.sh 指令碼會在其自己的使用者、掛載和網路命名空間中執行 dockerd

    為了進行除錯,您可以透過執行 nsenter -U --preserve-credentials -n -m -t $(cat $XDG_RUNTIME_DIR/docker.pid) 進入命名空間。