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からコンテナを作成しようとすると、
- CLIはUNIXソケット(デフォルトでは/var/run/docker.sock)を通してDockerデーモンへと指示を送る
- DockerデーモンはCLIから指示を受け取り、containerdにUNIXソケット(デフォルトでは/run/containerd/containerd.sock)を通してコンテナ作成の指示を送る
- 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