Docker の Rootlessモードを試してみた


# 前書き

ユーザが、Dockerを使用する際に、/var/run/docker.sock へのアクセス権限が問題になることがありました。

    $ sudo ls -l /var/run/docker.sock
    srw-rw---- 1 root docker 0 12月 15 19:07 /var/run/docker.sock

対処方法として、sudoを使う方法やユーザをdocker グループに入れる方法がありました。しかし、いずれもひと手間が面倒であったり、セキュリティ面に問題ありで、良い方法ではありませんでした。

1つの解決策として、Docker 19.03から下記のRootlessモードが行えるようになりました。

[Docker 19.03新機能 (root権限不要化、GPU対応強化、CLIプラグイン…)](https://medium.com/nttlabs/docker-1903-5155754ff8ac)


簡単に説明すると、各ユーザ用にDockerの環境を作成します。そのためDockerを使用するユーザ毎に、RootlessモードのDockerのインストールが必要です。

RootlessモードのDockerを実際にインストールした時に気が付いたことを書いています。
以下では、「Rootless Docker」と記述しています。


# 試した環境

- CentOS Linux release 8.0.1905 (Core)

※CentOSは、「最小限のインストール」でインストールした直後の状態です。

この記事の中でインストールするRootless Dockerのバージョン

- Client: Docker Engine - Community             master-dockerproject-2019-12-11
- Server: Docker Engine - Community  Engine     master-dockerproject-2019-12-11

# 前準備

テスト用にアカウントを2つ作成しています。

    $ sudo useradd user01
    $ sudo useradd user02
    $ sudo passwd user01
    $ sudo passwd user02


# Rootless Dockerのインストール手順

インストールは、下記のコマンドで完了します。
ユーザ権限で実行できます。

    $ curl -fsSL https://get.docker.com/rootless | sh
    $ echo 'export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock' >> ~/.bash_profile
    $ source ~/.bash_profile
    $ docker container run hello-world
    Hello from Docker!
    This message shows that your installation appears to be working correctly.

    To generate this message, Docker took the following steps:
     1. The Docker client contacted the Docker daemon.
     2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
        (amd64)
     3. The Docker daemon created a new container from that image which runs the
        executable that produces the output you are currently reading.
     4. The Docker daemon streamed that output to the Docker client, which sent it
        to your terminal.

    To try something more ambitious, you can run an Ubuntu container with:
     $ docker run -it ubuntu bash

    Share images, automate workflows, and more with a free Docker ID:
     https://hub.docker.com/

    For more examples and ideas, visit:
     https://docs.docker.com/get-started/

「Hello from Docker!」が表示されたら成功です。
次に、OSを再起動したとき自動起動するようにします。

    $ vi .config/systemd/user/docker.service

docker.serviceファイルの最後の行を変更します。
multi-user.target のままだと、自動起動しなかったです。

    [Install]
    WantedBy=multi-user.target

        ↓

    [Install]
    WantedBy=default.target

自動起動の設定を続けます。

    $ systemctl --user daemon-reload
    $ systemctl --user enable docker
    Created symlink /home/user01/.config/systemd/user/multi-user.target.wants/docker.service → /home/user01/.config/systemd/user/docker.service.
    $ sudo loginctl enable-linger user01

これで、OS再起動時にRootless Dockerが起動するようになります。

# インストール後の確認

### ディレクトリ・ファイルを確認

インストールを完了すると、以下のディレクトリやファイルが作成されています。

    ~/.config
    ~/.local
    ~/bin
    /var/run/user/1000/docker
    /var/run/user/1000/docker.pid
    /var/run/user/1000/docker.sock
    /var/run/user/1000/runc

※1000の部分は、UIDです。環境によって変わります。
※/var/run/user/1000/に追加されたディレクトリやファイルは、OS再起動時に消えますが、rootless dockerd が起動すると作成されます。


## パスの確認

homeディレクトリ下にインストールされています。

    $ which docker
    ~/bin/docker

## バージョンの確認

Versionの表記が少し気になりますが、Engine Versionも表示されています。
通常のDockerだと、Engine Versionは「dial unix /var/run/docker.sock: connect: permission denied」となって表示されません。

    $ docker version
    Client: Docker Engine - Community
     Version:           master-dockerproject-2019-12-11
     API version:       1.41
     Go version:        go1.12.12
     Git commit:        08eaead2
     Built:             Wed Dec 11 23:52:32 2019
     OS/Arch:           linux/amd64
     Experimental:      false

    Server: Docker Engine - Community
     Engine:
      Version:          master-dockerproject-2019-12-11
      API version:      1.41 (minimum version 1.12)
      Go version:       go1.13.4
      Git commit:       1347481
      Built:            Wed Dec 11 23:58:15 2019
      OS/Arch:          linux/amd64
      Experimental:     true
     containerd:
      Version:          v1.3.2
      GitCommit:        ff48f57fc83a8c44cf4ad5d672424a98ba37ded6
     runc:
      Version:          1.0.0-rc9
      GitCommit:        d736ef14f0288d6993a1845745d6756cfc9ddd5a
     docker-init:
      Version:          0.18.0
      GitCommit:        fec3683


## プロセスを確認

4つのプロセスが動作しています。
全てのプロセスがユーザ権限で動作しています。

    $ ps xo user,cmd | grep docker
    user01   rootlesskit --net=vpnkit --mtu=1500 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run /home/user01/bin/dockerd-rootless.sh --experimental --storage-driver=vfs
    user01   /proc/self/exe --net=vpnkit --mtu=1500 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run /home/user01/bin/dockerd-rootless.sh --experimental --storage-driver=vfs
    user01   dockerd --experimental --storage-driver=vfs
    user01   containerd --config /run/user/1000/docker/containerd/containerd.toml --log-level info

# ユーザ毎の違いを比べてみる

user02でRootless Dockerをインストールする前にバージョン確認してみました。
当然、見つかりません。

    [user02@localhost ~]$ docker version
    -bash: docker: コマンドが見つかりません

user02でも、同様にRootless Dockerをインストールして、確認を続けます。


### パスの確認

各ユーザのhomeの下を参照しているので、別ファイルです。

    [user01@localhost ~]$ which docker
    ~/bin/docker

    [user02@localhost ~]$ which docker
    ~/bin/docker


i-node番号を確認すると、別ファイルだとわかります。

    [user01@localhost ~]$ ls -i ~/bin/docker
    33554563 /home/user01/bin/docker

    [user02@localhost ~]$ ls -i ~/bin/docker
    33554616 /home/user02/bin/docker


### バージョンの確認

実行ファイルは別々でも、同じバージョンなので違いがわからないです。

    $ docker version
    Client: Docker Engine - Community
     Version:           master-dockerproject-2019-12-11
     API version:       1.41
     Go version:        go1.12.12
     Git commit:        08eaead2
     Built:             Wed Dec 11 23:52:32 2019
     OS/Arch:           linux/amd64
     Experimental:      false

    Server: Docker Engine - Community
     Engine:
      Version:          master-dockerproject-2019-12-11
      API version:      1.41 (minimum version 1.12)
      Go version:       go1.13.4
      Git commit:       1347481
      Built:            Wed Dec 11 23:58:15 2019
      OS/Arch:          linux/amd64
      Experimental:     true
     containerd:
      Version:          v1.3.2
      GitCommit:        ff48f57fc83a8c44cf4ad5d672424a98ba37ded6
     runc:
      Version:          1.0.0-rc9
      GitCommit:        d736ef14f0288d6993a1845745d6756cfc9ddd5a
     docker-init:
      Version:          0.18.0
      GitCommit:        fec3683

    [user02@localhost ~]$ docker version
    Client: Docker Engine - Community
     Version:           master-dockerproject-2019-12-11
     API version:       1.41
     Go version:        go1.12.12
     Git commit:        08eaead2
     Built:             Wed Dec 11 23:52:32 2019
     OS/Arch:           linux/amd64
     Experimental:      false

    Server: Docker Engine - Community
     Engine:
      Version:          master-dockerproject-2019-12-11
      API version:      1.41 (minimum version 1.12)
      Go version:       go1.13.4
      Git commit:       1347481
      Built:            Wed Dec 11 23:58:15 2019
      OS/Arch:          linux/amd64
      Experimental:     true
     containerd:
      Version:          v1.3.2
      GitCommit:        ff48f57fc83a8c44cf4ad5d672424a98ba37ded6
     runc:
      Version:          1.0.0-rc9
      GitCommit:        d736ef14f0288d6993a1845745d6756cfc9ddd5a
     docker-init:
      Version:          0.18.0
      GitCommit:        fec3683


### プロセスを確認

4つのプロセスで、PIDが違っています。

    [user01@localhost ~]$ ps xo pid,user,cmd | grep docker
     1225 user01   rootlesskit --net=vpnkit --mtu=1500 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run /home/user01/bin/dockerd-rootless.sh --experimental --storage-driver=vfs
     1233 user01   /proc/self/exe --net=vpnkit --mtu=1500 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run /home/user01/bin/dockerd-rootless.sh --experimental --storage-driver=vfs
     1272 user01   dockerd --experimental --storage-driver=vfs
     2866 user01   containerd --config /run/user/1000/docker/containerd/containerd.toml --log-level info

    [user02@localhost ~]$ ps xo pid,user,cmd | grep docker
     8013 user02   rootlesskit --net=vpnkit --mtu=1500 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run /home/user02/bin/dockerd-rootless.sh --experimental --storage-driver=vfs
     8022 user02   /proc/self/exe --net=vpnkit --mtu=1500 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run /home/user02/bin/dockerd-rootless.sh --experimental --storage-driver=vfs
     8074 user02   dockerd --experimental --storage-driver=vfs
     8091 user02   containerd --config /run/user/1001/docker/containerd/containerd.toml --log-level info


### /ver/run の下のファイルの確認

そもそもパスが違うので別物です。

    [user01@localhost ~]$ ls -l /var/run/user/1000
    合計 4
    srw-rw-rw-. 1 user01 user01   0 12月 22 21:54 bus
    drwx-----T. 7 user01 user01 180 12月 22 21:58 docker
    -rw-r--r-T. 1 user01 user01   4 12月 22 21:58 docker.pid
    srw-rw---T. 1 user01 user01   0 12月 22 21:58 docker.sock
    drwx-----T. 2 user01 user01  40 12月 22 21:58 runc
    drwxr-xr-x. 2 user01 user01  80 12月 22 22:00 systemd

    [user02@localhost ~]$ ls -l /var/run/user/1001
    合計 4
    srw-rw-rw-. 1 user02 user02   0 12月 22 22:01 bus
    drwx-----T. 5 user02 user02 140 12月 22 22:05 docker
    -rw-r--r-T. 1 user02 user02   4 12月 22 22:05 docker.pid
    srw-rw---T. 1 user02 user02   0 12月 22 22:05 docker.sock
    drwx-----T. 2 user02 user02  40 12月 22 22:05 runc
    drwxr-xr-x. 2 user02 user02  80 12月 22 22:05 systemd


# Dockerコマンドでの違い

### コンテナの確認

hello-worldコンテナを起動したので、停止状態のコンテナが残っています。
CONTAINER ID が違っており別々のコンテナだとわかります。

    [user01@localhost ~]$ docker container ls -a
    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
    a9fa819c358e        hello-world         "/hello"            9 minutes ago       Exited (0) 9 minutes ago                       crazy_lederberg

    [user02@localhost ~]$ docker container ls -a
    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
    236dbc03fa53        hello-world         "/hello"            13 seconds ago      Exited (0) 12 seconds ago                       heuristic_hypatia


### イメージの確認

IMAGE ID が違がって・・・ないです。同じです。
※先に結論だけ書いておきますが、IMAGE IDは同じですが、実体は別物です。

    [user01@localhost ~]$ docker image ls
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    hello-world         latest              fce289e99eb9        11 months ago       1.84kB

    [user02@localhost ~]$ docker image ls
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    hello-world         latest              fce289e99eb9        11 months ago       1.84kB


user01で、nginx イメージをpullします。

    [user01@localhost ~]$ docker image pull nginx
    Using default tag: latest
    latest: Pulling from library/nginx
    000eee12ec04: Pull complete
    eb22865337de: Pull complete
    bee5d581ef8b: Pull complete
    Digest: sha256:50cf965a6e08ec5784009d0fccb380fc479826b6e0e65684d9879170a9df8566
    Status: Downloaded newer image for nginx:latest
    docker.io/library/nginx:latest

ダウンロードが終わるとイメージ一覧に追加されています。

    [user01@localhost ~]$ docker image ls
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    nginx               latest              231d40e811cd        4 weeks ago         126MB
    hello-world         latest              fce289e99eb9        11 months ago       1.84kB


user02は、ダウンロードしていないので、nginxイメージを確認できません。

    [user02@localhost ~]$ docker image ls
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    hello-world         latest              fce289e99eb9        11 months ago       1.84kB

user02でnginxコンテナを起動すると、「Unable to find image」と表示されダウンロードが開始しました。

    [user02@localhost ~]$ docker container run nginx
    Unable to find image 'nginx:latest' locally
    latest: Pulling from library/nginx
    000eee12ec04: Pull complete
    eb22865337de: Pull complete
    bee5d581ef8b: Pull complete
    Digest: sha256:50cf965a6e08ec5784009d0fccb380fc479826b6e0e65684d9879170a9df8566
    Status: Downloaded newer image for nginx:latest
    ^C                        <-- nginxコンテナを停止
    [user02@localhost ~]$

nginxイメージのIMAGE IDも、user01、user02ともに同じになっています。

    [user01@localhost ~]$ docker image ls
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    nginx               latest              231d40e811cd        4 weeks ago         126MB
    hello-world         latest              fce289e99eb9        11 months ago       1.84kB

    [user02@localhost ~]$ docker image ls
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    nginx               latest              231d40e811cd        4 weeks ago         126MB
    hello-world         latest              fce289e99eb9        11 months ago       1.84kB

イメージは、各ユーザでダウンロードしないとダメなようです。
一見、IMAGE IDが同じなので、同じイメージを見ているように勘違いしそうです。

### イメージの実体を確認

hello-worldイメージを少し追ってみました。
jsonファイルの処理のために、pythonをインストールしています。

    $ sudo dnf install python3

管理しているファイルを確認しました。

    [user01@localhost ~]$ cat /home/user01/.local/share/docker/image/vfs/repositories.json | python3 -m json.tool
    {
        "Repositories": {
            "hello-world": {
                "hello-world:latest": "sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e",
                "hello-world@sha256:4fe721ccc2e8dc7362278a29dc660d833570ec2682f4e4194f4ee23e415e1064": "sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e"
            },
            "nginx": {
                "nginx:latest": "sha256:231d40e811cd970168fb0c4770f2161aa30b9ba6fe8e68527504df69643aa145",
                "nginx@sha256:50cf965a6e08ec5784009d0fccb380fc479826b6e0e65684d9879170a9df8566": "sha256:231d40e811cd970168fb0c4770f2161aa30b9ba6fe8e68527504df69643aa145"
            }
        }
    }

このファイルは各ユーザのhomeディレクトリ下にあります。
パスが違うので当然、別物です。
また下記のように、i-node番号も違っています。（桁が違いすぎる）

    [user01@localhost ~]$ ls -li /home/user01/.local/share/docker/image/vfs/repositories.json
    152 -rw-------. 1 user01 user01 542 12月 22 22:12 /home/user01/.local/share/docker/image/vfs/repositories.json

    [user02@localhost ~]$ ls -li /home/user02/.local/share/docker/image/vfs/repositories.json
    100663444 -rw-------. 1 user02 user02 542 12月 22 22:21 /home/user02/.local/share/docker/image/vfs/repositories.json

UUID（で合ってる？）をたどっていくと最終的に、helloコマンドにたどり着きます。

    [user01@localhost ~]$ ls -li /home/user01/.local/share/docker/vfs/dir/e1b5ab5b419c6229106017654150711e2717ae81f3c8002bedb936f0f2786b4b/hello
    33554585 -rwxrwxr-x. 1 user01 user01 1840  1月  1  2019 /home/user01/.local/share/docker/vfs/dir/e1b5ab5b419c6229106017654150711e2717ae81f3c8002bedb936f0f2786b4b/hello

同じように、user02でも確認します。
「dir」の下のディレクトリ名がuser01と違っています。

    [user02@localhost ~]$ ls -li /home/user02/.local/share/docker/vfs/dir/f56bbc69cac68b78ef386f01643acbdaa89929e4f8f57bd2e8c8039e5dfa17c6/hello
    67215817 -rwxrwxr-x. 1 user02 user02 1840  1月  1  2019 /home/user02/.local/share/docker/vfs/dir/f56bbc69cac68b78ef386f01643acbdaa89929e4f8f57bd2e8c8039e5dfa17c6/hello

helloコマンドのi-node番号が違っています。
dockerコマンド上は、同じIDに見えますが、イメージは別物だと言えます。

### イメージの削除

user01、user02ともに、停止コンテナを削除した後、user02でイメージの削除を行います。

    [user02@localhost ~]$ docker image rm hello-world
    Untagged: hello-world:latest
    Untagged: hello-world@sha256:4fe721ccc2e8dc7362278a29dc660d833570ec2682f4e4194f4ee23e415e1064
    Deleted: sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e
    Deleted: sha256:af0b15c8625bb1938f1d7b17081031f649fd14e6b233688eea3c5483994a66a3
    [user02@localhost ~]$ docker image ls
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    nginx               latest              231d40e811cd        3 weeks ago         126MB

user02では、hello-worldイメージが消えています。
user01では、残っています。

    [user01@localhost ~]$ docker image ls
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    nginx               latest              231d40e811cd        3 weeks ago         126MB
    hello-world         latest              fce289e99eb9        11 months ago       1.84kB

# docker-compose の場合

docker-commposeを実行したときの動作を確認してみました。

    $ sudo pip3 install docker-compose
    $ docker-compose version
    docker-compose version 1.25.0, build b42d419
    docker-py version: 4.1.0
    CPython version: 3.6.8
    OpenSSL version: OpenSSL 1.1.1 FIPS  11 Sep 2018

簡単なdocker-compose.ymlを作成します。

    $ vi docker-compose.yml
    version: '3'
    services:

      wordpress:
        image: wordpress
        container_name: wordpress
        restart: always
        ports:
          - 80:80
        environment:
          WORDPRESS_DB_PASSWORD: wp-password

      mysql:
        image: mysql:5.7
        container_name: mysql
        restart: always
        environment:
          MYSQL_ROOT_PASSWORD: mysql-password

実行します。
wordpressとmysqlのコンテナのダウンロードが始まるため、時間がかかります。

    $ docker-compose up -d
    Starting wordpress ... done
    Starting mysql     ... done
    $ docker container ls
    CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                 NAMES
    32a9bd917995        mysql:5.7           "docker-entrypoint.s…"   2 minutes ago       Up 33 seconds       3306/tcp, 33060/tcp   mysql
    cfdd93b5f5ff        wordpress           "docker-entrypoint.s…"   2 minutes ago       Up 33 seconds       0.0.0.0:80->80/tcp    wordpress

docker-composeからの起動もできました。
確認できたので、終了させておきます。

    $ docker-compose down
    Stopping mysql     ... done
    Stopping wordpress ... done
    Removing mysql     ... done
    Removing wordpress ... done
    Removing network user01_default

# 作業中に解決したエラー等

### バージョン確認時のメッセージ

バージョン確認時等に、下記のようなメッセージが表示される。

    Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

環境変数の「DOCKER_HOST」が正しく設定されているか確認してください。

### systemctlのメッセージ

systemctl --user status docker コマンドを実行した時に下記のようなエラーが出る。

    level=warning msg="Running modprobe bridge br_netfilter failed with message: modprobe: ERROR: could not insert 'br_netfilter': Operation not permitted\ninsmod /lib/modules/4.18.0-80.el8.x86_64/kernel/net/bridge/br_netfilter.ko.xz \n, error: exit status 1"

原因は、br_netfilterモジュールがロードされていないこと。
なので、OS起動時にロードするようにします。

    $ lsmod | grep br_netfilter
    br_netfilter           24576  0
    bridge                188416  1 br_netfilter

    $ echo "br_netfilter" > /etc/modules-load.d/br_netfilter.conf

# 最後に

ユーザごとに別の環境でDockerを使えるようになります。
当然、本来のDocker（/usr/bin/docker）と、別なものになります。
イメージが別になるなど注意が必要そうです。
気になるところはありますが、root権限が不要なるメリットがあるのでしばらく使ってみようと思います。