🐳

Docker Rootless モードについて

に公開

恥の多い生涯を送って来ました。
日々Dockerにお世話になっているというのに、Rootlessモードというものが見当つかないのです。
そんな自分に嫌気が差しました。私はRootlessモードに向き合わなければならない。

おことわり

LinuxでDockerを実行していることを前提とします。
MacやWindowsでDockerを実行している方は、VMのLinuxレイヤー以上の話とお考えください。

この記事ではDocker Rootlessモードの内部動作についてまとめます。
Rootlessモードのセットアップ方法などは公式ドキュメントをご参照ください。

一般ユーザでのDocker利用、いままでどうしてた?

Rootlessモードが登場する以前、root以外のユーザはDockerを利用するのに

  • sudoをつけてdockerコマンド実行
  • 一般ユーザをdockerアクセス可能なグループに追加
  • /var/run/docker.sockの権限を変更

などいずれかの対応が必要でした。

これらに対し、ユーザごとにDockerデーモン実行しちゃおうぜ!というのがRootlessモードです。

Dockerコンテナ実行の挙動

Rootlessモードの解説の前に、Dockerの内部的アーキテクチャについて簡単におさらいしましょう。

主な登場人物は Docker CLI(Client)、Dockerデーモン、containerdです。

Docker CLIは我々が日常的に使用しているコマンドです。コマンドを解釈し、Dockerデーモンへ指示を送ります。

DockerデーモンはDockerリソースを管理するコンポーネントです。クライアントからREST APIで指示を受け付け、それらを処理します。

containerdは実際にコンテナを作成、管理するコンポーネントです。Linuxリソース確保などOSとのやり取りも、Dockerデーモン(には限らないですが)からgRPCで指示を受け付け、実行します。

例えばDocker CLIからコンテナを作成しようとすると、

  1. CLIはUNIXソケット(デフォルトでは/var/run/docker.sock)を通してDockerデーモンへと指示を送る
  2. DockerデーモンはCLIから指示を受け取り、containerdにUNIXソケット(デフォルトでは/run/containerd/containerd.sock)を通してコンテナ作成の指示を送る
  3. containerdはOSとやり取りを行い、コンテナを作成する

といった動作を行います。

なお、Dockerデーモンとcontainerdは通常rootユーザのプロセスとして実行されています。

Rootlessモードの場合

それに対し、RootlessモードではユーザごとにDockerデーモンとcontainerdを実行します。

RootlessモードがONになっている環境でプロセスツリーを見てみると、rootユーザ以外に一般ユーザ(下記例ではlima)でもDockerデーモン(dockerd)とcontainerdが実行されていることがわかります。

$ pstree -au
systemd
  ├─agetty -o -p -- \\u --keep-baud 115200,57600,38400,9600 hvc0 vt220
  ├─agetty -o -p -- \\u --keep-baud 115200,57600,38400,9600 ttyS0 vt220
  ├─agetty -o -p -- \\u --noclear tty1 linux
  ├─containerd # ←rootユーザのcontainerd
  │   └─9*[{containerd}]
  ├─cron -f -P
  ├─dbus-daemon,messagebus --system --address=systemd: --nofork --nopidfile --systemd-activation ...
  ├─dockerd -H fd:// --containerd=/run/containerd/containerd.sock # ←rootユーザのdockerデーモン
  │   └─10*[{dockerd}]
~~~
  ├─systemd,lima --user
  │   ├─(sd-pam)
  │   ├─dbus-daemon --session --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
  │   └─rootlesskit --state-dir=/run/user/502/dockerd-rootless --net=slirp4netns --mtu=65520 ...
  │       ├─exe --state-dir=/run/user/502/dockerd-rootless --net=slirp4netns --mtu=65520...
  │       │   ├─dockerd # ←一般ユーザのdockerデーモン
  │       │   │   ├─containerd --config /run/user/502/docker/containerd/containerd.toml # ←一般ユーザのcontainerd
  │       │   │   │   └─9*[{containerd}]
  │       │   │   └─13*[{dockerd}]
  │       │   └─9*[{exe}]
  │       ├─slirp4netns --mtu 65520 -r 3 --disable-host-loopback --enable-sandbox --enable-seccomp 1485 tap0
  │       └─9*[{rootlesskit}]
~~~

デフォルトでは、rootユーザのDockerデーモンソケットは var/run/docker.sock 、containerdソケットは /run/containerd/containerd.sock 、一般ユーザのソケットは $XDG_RUNTIME_DIR/docker.sock 、containerdソケットは $XDG_RUNTIME_DIR/docker/containerd/containerd.sock となります。ファイル所有者もそれぞれのユーザとなっていますね。

$ whoami
lima
$ ls -l /var/run/docker.sock
srw-rw-r-- 1 root docker 0 May 24 00:01 /var/run/docker.sock
$ ls -l /run/containerd/containerd.sock
srw-rw---- 1 root root 0 Jun 23 10:18 /run/containerd/containerd.sock
$ ls -l "$XDG_RUNTIME_DIR/docker.sock"
srw-rw---T 1 lima 100998 0 May 24 00:01 /run/user/502/docker.sock
$ ls -l "$XDG_RUNTIME_DIR/docker/containerd/containerd.sock"
srw-rw---- 1 lima lima 0 Jun 23 10:18 /run/user/502/docker/containerd/containerd.sock

Docker CLIからのアクセス先ソケットはユーザごと自動選択されるため、コマンド実行時に特に意識することはありません。

ユーザごとにコマンド結果が異なることから、接続先のDockerデーモン(とその先のcontainerd)が別であることも確認できます。

$ whoami
lima
$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
alpine       latest    1d34ffeaf190   46 hours ago   7.79MB
docker       latest    d14813e41c93   8 days ago     360MB
$ sudo su
# whoami
root
# docker image ls
REPOSITORY            TAG       IMAGE ID       CREATED        SIZE
docker                latest    1eeb10c9d6c5   6 weeks ago    362MB
ubuntu                latest    7af9ba4f0a47   6 weeks ago    77.9MB
curlimages/curl       latest    79009b90fb07   8 weeks ago    17.3MB
hashicorp/http-echo   latest    04fa556e62bd   7 months ago   8.79MB

なお、docker cli実行時に明示的にDockerデーモンのソケットファイルを指定することで、別ユーザのDockerデーモンにアクセスすることもできます。

$ whoami
lima
$ ls -l /var/run/docker.sock
srw-rw---- 1 root docker 0 May 24 00:01 /var/run/docker.sock
$ sudo chmod 666 /var/run/docker.sock
$ docker --host unix:///var/run/docker.sock image ls
REPOSITORY            TAG       IMAGE ID       CREATED        SIZE
docker                latest    1eeb10c9d6c5   6 weeks ago    362MB
ubuntu                latest    7af9ba4f0a47   6 weeks ago    77.9MB
curlimages/curl       latest    79009b90fb07   8 weeks ago    17.3MB
hashicorp/http-echo   latest    04fa556e62bd   7 months ago   8.79MB

一般ユーザで実行したDocker CLIからrootのDockerデーモンにアクセスできたことがわかります。
なお、この例ではアクセスのために/var/run/docker.sockの権限を変更していますが、一般ユーザへの/var/run/docker.sock権限開放は極力避けるようにしてください。
一般ユーザがDockerのマウントを通して、rootユーザのファイルシステムにアクセスできるようになってしまうなどの問題が発生します。

【おまけ】一般ユーザからDockerを経由したrootユーザファイルシステムへのアクセス

1つ前の例で「一般ユーザへの/var/run/docker.sock権限開放は避けるべき」という話しをしました。
実際に一般ユーザからrootユーザファイルシステムへのアクセスを試してみましょう。

まず、一般ユーザからは権限エラーで/rootにアクセスできないことを確認します。

$ touch /root/sample.txt
touch: cannot touch '/root/sample.txt': Permission denied

では、rootユーザのDockerデーモンから/rootをマウントしたコンテナを立てて、書き込みを行ってみましょう。

$ docker --host unix:///var/run/docker.sock run --rm -it -v /root:/host_root ubuntu sh
# touch /host_root/sample_from_docker.txt
# exit
$ sudo ls /root
sample_from_docker.txt	snap

/rootへの書き込み権限がないにも関わらず、Dockerを経由すると書き込みができてしまいました。
これはrootユーザのDockerデーモンと、そこから起動されるコンテナはrootユーザのプロセスとして実行されるため、/rootへの書き込み権限を持っているためです。

当然、一般ユーザのDockerデーモンから立てたコンテナは/rootへのマウントはできません。マウントしたディレクトリ所有者がnobodyとなっています。

$ docker run --rm -it -v /root:/host_root ubuntu sh
# touch /host_root/sample_from_docker_2.txt
touch: cannot touch '/host_root/sample_from_docker_2.txt': Permission denied
# ls -l / | grep host_root
drwx------   6 nobody nogroup 4096 May 24 17:11 host_root

以上のように、sudoなどを許可していなくても、/var/run/docker.sockを許可していると、一般ユーザがrootユーザとしてプロセスを実行できてしまうため、権限は絞っておくようにしましょう。

Discussion