Docker デーモンをルート以外のユーザで実行(Rootless モード)

Rootless モード(Rootless mode)は Docker デーモンとコンテナを root 以外のユーザが実行できるようにするもので、デーモンやコンテナ・ランタイムにおける潜在的な脆弱性を回避します。

Rootless モードは Docker デーモンのインストールに root 権限を必要としないだけでなく、 事前準備 においても不要です。

Rootless モードは Docker Engine v19.03 から導入されました。

注釈

Rootless モードは実験的な機能であり、いくつかの制約があります。詳細は 既知の制約 をご覧ください。

どのように動作するか

Rootless モードは Docker デーモンとコンテナをユーザ名前空間(user namespace)の中で実行します。これは userns-remap モード と非常に似ていますが、こちらはデーモン自身は root 権限として動作しています。一方の Rootless モードでは、デーモンとコンテナの両方の実行に root 権限がありません。

Rootless モードは SETUID ビットやファイル・ケーパビリティを必要としません、しかし、 newuidmapnewgidmap は除外します。これらはユーザ名前空間内で複数の UID/GID を利用可能にするために必要です。

事前準備

  • ホスト上に newuidmapnewgidmap のインストールが必要です。各コマンドは多くのディストリビューションで uidmap パッケージとして提供されています。
  • /etc/subuid/etc/subgid はユーザに対して、少なくともサブオーディネイト UID/GID を 65,536 含むべきです。以下の例は、ユーザ testuser は 65,546 サブオーディネイト UID/GID (231072~296607)を持っています。
$ id -u
1001
$ whoami
testuser
$ grep ^$(whoami): /etc/subuid
testuser:231072:65536
$ grep ^$(whoami): /etc/subgid
testuser:231072:65536

ディストリビューション固有のヒント

注釈

私たちは Ubuntu kernel の利用を推奨します。

Ubuntu

  • 何も準備する必要がありません
  • overlay2 ストレージ・ドライバがデフォルトで有効です( Ubuntu 固有のカーネル・パッチ
  • 動作するのが分かっているのは Ubuntu 16.04、18.04、20.04 です。

Debian GNU/Linux

  • /etc/sysctl.conf (または /etc/sysctl.d )に kernel.unprivileged_userns_clone=1 を追加し、 sudo sysctl --system を実行。
  • overlay2 ストレージ・ドライバ(推奨)を使うために、 sudo modprobe overlay permit_mounts_in_userns=1 を実行( Debian 10 で導入された、Debian 独自のカーネル・パッチ 。 )。設定を残し続けるためには、 /etc/modprobe.d に設定を追加。
  • Debian 9 と 10 での動作が分かっています。 overlay2 をサポートしているのは Debian 10 からで、前述の modprobe 設定が必要。

Arch Linux

  • /etc/sysctl.conf (または /etc/sysctl.d )に kernel.unprivileged_userns_clone=1 を追加し、 sudo sysctl --system を実行。

openSUSE

  • sudo modprobe ip_tables iptable_mangle iptable_nat iptable_filte が必要。設定状況によっては、他のディストリビューションのような依存関係が必要な場合があります。
  • openSUSE 15 での動作が分かっています。

Fedora 31 以降

  • Fedora 31 では cgroup v2 の利用がデフォルトですが、containerd ランタイムでは未サポートです。 sudo grubby --update-kernel=ALL --args="systemd.unified_cgroup_h を実行し、 cgroup v1 を使います。
  • sudo dnf install -y iptables が必要になる場合もあります。

CentOS 8

  • sudo dnf install -y iptables が必要になる場合があります。

CentOS 7

  • /etc/sysctl.conf (または /etc/sysctl.d )に user.max_user_namespaces=28633 を追加し、 sudo sysctl --system を実行。
  • デフォルトでは systemctl --user が動作しません。systemd を使わずに直接デーモンを実行します: dockerd-rootless.sh --experimental --storage-driver vfs
  • CentOS 7.7 での動作が分かっています。以前のリリースでは、追加の設定手順が必要です。
  • CentOS 7.6 以前のリリースでは COPR パッケージ vbatts/shadow-utils-newxidmap をインストールします。
  • CentOS 7.5 以前のリリースでは、 sudo grubby --update-kernel=ALL --args="user_namespace.enable=1" の実行と、有効化のために再起動が必要です。

既知の制限

  • vfs グラフドライバをサポートしています。しかしながら、 Ubuntu と Debian 10 では overlay2overlay もサポートしています。

  • 以下の機能は非サポートです。

    • cgroups(cgroups に依存する docker top を含みます)
    • AppArmor
    • Checkpoint
    • オーバレイ・ネットワーク
    • SCTP ポートの公開(exposing)
  • ping コマンドを使うには、 routing-ping-packets をご覧ください。

  • 特権 TCP/UDP ポート(ポート 1024 以下)を公開するには、 exposing-privileged-ports をご覧ください。

  • docker inspect で表示する IPAddress とは、RootlessKit のネットワーク名前空間内で名前区間化されたものです。つまり、 nsenter 化しなければ、そのネットワーク名前空間にはホスト側から到達できない IP アドレスです。

  • また、ホスト・ネットワーク( docker run --net=host )も RootlessKit 内に名前空間化されています。

インストール

インストール用のスクリプトは https://get.docker.com/rootless にあります。

$ curl -fsSL https://get.docker.com/rootless | sh

スクリプトを root 以外のユーザで実行します。root ユーザとして Rootless Dockre をインストールするには、 手動インストール の手順をご覧ください。

スクリプトによって表示される環境変数が必要になります:

$ curl -fsSL https://get.docker.com/rootless | sh
...
# Docker binaries are installed in /home/testuser/bin
# WARN: dockerd is not in your current PATH or pointing to /home/testuser/bin/dockerd
# Make sure the following environment variables are set (or add them to ~/.bashrc):

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

#
# To control docker service run:
# systemctl --user (start|stop|restart) docker
#

手動インストール

インストーラを使わず手動でバイナリをインストールするには、 https://download.docker.com/linux/static/stable/x86_64/ から docker-<version>.tar.gz と一緒に docker-rootless-extras-<version>.tar.gz を展開します。

既に Docker を root として実行中の場合は、 docker-rootless-extras-<version>.tar.gz のみを展開します。アーカイブは $PATH 以下の相対ディレクトリに展開されます。たとえば、 /usr/local/bin$HOME/bin です。

nightly チャネル

nightly バージョンの Rootless Docker をインストールするには、インストール・スクリプトで CHANNEL="nightly" を使って実行します。

$ curl -fsSL https://get.docker.com/rootless | CHANNEL="nightly" sh

単体のバイナリは、こちらからダウンロードできます。

使い方

デーモン

systemctl --user を使い、デーモンのライフサイクルを管理します。

$ systemctl --user start docker

システム起動時にデーモンを起動するには、 systemd サービスを有効化し、実行し続ける設定にします。

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

systemd を使わずにデーモンを直接起動するには、 dockerd の代わりに dockerd-rootless.sh の実行が必要です。

$ dockerd-rootless.sh --experimental --storage-driver vfs

Rootless モードは実験的なため、 docker-rootless.sh には --experimental が必要です。

また、 Ubuntu や Debian 10 カーネルを使っていない場合は、 --storage-driver vfs の指定も必要です。systemd を使ってデーモンを管理している場合、これらのフラグについて考慮する必要はありません。systemd ユニットファイル内で各フラグが自動的に追加されるためです。

ディレクトリのパスについて説明します:

  • ソケットのパスは、デフォルトで $XDG_RUNTIME_DIR/docker.sock に設定されます。 $XDG_RUNTIME_DIR は通常 /run/user/$UID に設定されます。
  • データ・ディレクトリは、デフォルトで ~/.local/share/docker に設定されます。
  • 実行ディレクトリは、デフォルトで $XDG_RUNTIME_DIR/docker に設定されます。

その他の説明です:

  • dockerd-rootless.sh スクリプトの dockerd 実行は、自分自身のユーザ、マウント、ネットワークの各名前空間を使います。名前空間に入る場合は、 nsenter -U --preserve-credentials -n -m -t $(cat $XDG_RUNTIME_DI を実行します。
  • docker info を実行すると、 SecutiryOptionsrootless と表示します。
  • docker info を実行すると、 Cgroup Drivernone と表示します。

クライアント

ソケットのパスを明確に指定する必要があります。

$DOCKER_HOST` でソケットのパスを指定するには:

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

docker context でソケットのパスを指定するには:

$ docker context create rootless --description "for rootless mode" --docker "host=unix://$XDG_RUNTIME_DIR/docker.sock"
rootless
Successfully created context "rootless"
$ docker context use rootless
rootless
Current context is now "rootless"
$ docker run -d -p 8080:80 nginx

ベストプラクティス

Rootless Docker in Docker

Rootless Docker の中で "rootful" (root風に) Docker を実行するには、 docker:<version>-dind イメージの変わりに docker:<version>-dind-rootless イメージを使います。

$ docker run -d --name dind-rootless --privileged docker:19.03-dind-rootless --experimental

docker:<version>-dind-rootless イメージは非 root ユーザ(UID 1000)として実行します。しかしながら、 seccomp、AppArmor、mount mask を無効化するために --privileged が必要です。

Docker API ソケットを TCP を通して公開

Docker API ソケットを TCP を通して公開するには、 dockerd-rootless.sh の起動に DOCKERD_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 --experimental \
  -H tcp://0.0.0.0:2376 \
  --tlsverify --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem

Docker API ソケットを SSH を通して公開

Docker API ソケットを SSH を通して公開するには、リモートホスト上で $DOCKER_HOST` の指定が必要です。

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

ping パケットのルーティング

いくつかのディストリビューションで、デフォルトでは ping が動作しません。

/etc/sysctl.conf (または /etc/sysctl.d )に net.ipv4.ping_group_range = 0 2147483647 を追加し、 ping の使用を許可するために sudo sysctl --system を実行します。

特権ポートの公開

特権ポート(1024 以下)を公開するには、 rootlesskit バイナリに対して CAP_NET_BIND_SERVICE を設定します。

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

/etc/sysctl.conf (または /etc/sysctl.d )に net.ipv4.ip_unprivileged_port_start=0 を追加し、 sudo sysctl --system を実行します。

リソースの制限

Docker 19.03 では、rootless モードでは cgroups に関連する docker run のフラグ、 --cpu--memory-pids-limit を無視します。

しかしながら、伝統的な ulimitcpulimit は利用できます。これはコンテナ粒度というよりはプロセス粒度に対して機能するからです。また、コンテナのプロセスからの任意の割り当て(arbitrarily)は無効化されます。

例:

  • CPU の使用を 0.5 コアに制限するには( docker run --cpus 0.5 のように): docker run --cpus 0.5): docker run <イメージ> cpulimit --limit=50 --include-children <コマンド>
  • 最大 VSZ を 64MiB に制限するには( docker run --memory 64m のように): docker run <イメージ> sh -c "ulimit -v 65536; <コマンド>"
  • UID 2000 の名前空間ごとに最大 100 プロセスに制限する( docker run --pids-limit=100 のように): docker run --user 2000 --ulimit nproc=100 <IMAGE> <コマンド>

ネットワーク・スタックの変更

デフォルトでは、 dockerd-rootless.shslirp4netns (インストール済みであれば)または VPNKit をネットワーク・スタックとして使います。

これらのネットワーク・スタックはユーザ空間内で実行されるため、パフォーマンスのオーバヘッドを招く場合があります。詳しい情報は RootlessKit ドキュメント(英語) をご覧ください。

オプションで、ベストなパフォーマンスの代わりに lxc-user-nic を利用できます。 lxc-user-nic を使うには、 /etc/lxc/lxc-usernet を編集し、 $DOCKERD_ROOTLESS_ROOTLESSKIT_NET=lxc-user-nic を指定します。

トラブルシューティング

Docker デーモン起動時のエラー

[rootlesskit:parent] error: failed to start the child: fork/exec /proc/self/exe: operation not permitted

このエラーが発生するのは、ほとんどが /proc/sys/kernel/unprivileged_userns_clone の値を 0 に設定している時です。

$ cat /proc/sys/kernel/unprivileged_userns_clone
0

この問題を解決するには、 /etc/sysctl.conf (あるいは /etc/sysctl.d )に kernel.unprivileged_userns_clone=1 を追加し、 sudo sysctl --system を実行します。

[rootlesskit:parent] error: failed to start the child: fork/exec /proc/self/exe: no space left on device

このエラーが発生するのは、ほとんどが /proc/sys/user/max_user_namespaces が小さすぎる時です。

$ cat /proc/sys/user/max_user_namespaces
0

この問題を解決するには、 /etc/sysctl.conf (あるいは /etc/sysctl.d )に user.max_user_namespaces=28633 を追加し、 sudo sysctl --system を実行します。

[rootlesskit:parent] error: failed to setup UID/GID map: failed to compute uid/gid map: No subuid ranges found for user 1001 (“testuser”)

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 --experimental

注釈

このディレクトリは、ログアウト時に毎回削除する必要があります。

systemd ホスト上では、ホストへのログインに pam_systemd を使います(以下をご覧ください)。この値は自動的に /run/user/$UID に指定され、ログアウト後にクリーンアップされます。

systemctl --user がエラー「Failed to connect to bus: No such file or directory」で失敗

このエラーが発生するのは、ほとんどが root ユーザから非 root ユーザに sudo で切り替える時です。

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

sudo -iu <ユーザ名> の代わりに、 pam_systemd を使ってログインする必要があります。たとえば、

  • グラフィック・コンロールを通してログイン
  • ssh <ユーザ名>@localhost
  • machinectl shell <ユーザ名>@

デーモンが自動的に起動しない

デーモンを自動的に起動するには sudo loginctl enable-linger $(whoami) を実行する必要があります。 使い方 をご覧ください。

dockerd がエラー「rootless mode is supported only when running in experimental mode」

このエラーが発生するのは、 --experimental を付けずにデーモンを起動した時です。 使い方 をご覧ください。

docker pull errors

docker: failed to register layer: Error processing tar file(exit status 1): lchown <FILE>: invalid argument

このエラーが発生するのは、 /etc/subuid/etc/subgid で利用可能なエントリ数が十分ではない時です。エントリに必要な数は、イメージによって様々です。しかしながら、ほとんどのイメージでは 65,536 で十分です。 事前準備 をご覧ください。

docker run errors

--cpu、 --memory 、 --pids-limit が無視される

この挙動は Docker 19.03 で想定された挙動です。詳しい情報は リソースの制限 をご覧ください。

デーモンからのエラー応答「groups: cgroup mountpoint does not exist: unknown.」

このエラーが発生するのは、ほとんどが cgroup v2 がホスト上で動作している時です。ホストが cgroup v1 を使うように切り替えるための情報は、 Fedora 31 以降 のセクションをご覧ください。

ネットワークのエラー

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.

このエラーに遭遇したら、代わりに特権のないポートの利用を検討ください。たとえば 80 の代わりに 8080 を使います。

$ docker run -p 8080:80 nginx:alpine

特権ポートの公開を許可するには、 rootless-exposing-privileged-ports をご覧ください。

ping できません

/proc/sys/net/ipv4/ping_group_range1 0 の設定であれば、 ping は機能しません。

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

詳細は ping パケットのルーティング をご覧ください。

docker inspect で表示された IPAddress に到達できません(unreachable)

これは予想されうる挙動で、デーモンは RootlessKit のネットワーク名前空間内にいるからです。かわりに docker run -p を使います。

--net=host がホスト・ネットワーク名前空間上でポートをリッスンしません

これは予想されうる挙動で、デーモンは RootlessKit のネットワーク名前空間内にいるからです。かわりに docker run -p を使います。

参考

Run the Docker daemon as a non-root user (Rootless mode)
https://docs.docker.com/engine/security/rootless/