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

Docker 19.03で入った新機能であるDockerのRootlessモードを試してみました。

## Rootlessモードとは

従来はroot権限でdockerデーモンを実行していたために、脆弱性やdockerソケットを渡すなどの設定ミスをしているとホストのroot権限を奪われてしまう可能性がありました。

Rootlessモードでは各ユーザでdockerデーモンを実行するため、脆弱性などがあった場合でもそのユーザの権限内でしか影響がなくなります（まあ、各ユーザから権限昇格されたら別でしょうが）。

## RootlessモードのDockerをインストールする

実際にRootlessモードのDockerをインストールしてみます。

試した環境は以下のようになっています。

OS : Ubuntu 18.04(Hyper-V上)
Memory : 2GB
CPU : Intel Core i9
docker-composeインストール済み

1. インストールコマンドを実行する

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

2. .bashrcに追加を行う

    インストールしたRootlesモードDockerバイナリとソケットのパスを.bashrcに記述します。インストールしたときにコンソール上に記述されている内容をコピペすればOKです。

    ```bash
    export PATH=$HOME/bin:$PATH
    export DOCKER_HOST=unix:///run/user/1000/docker.sock
    ```

3. 自動起動の設定を行う

    最後にRootlessモードのDockerの起動設定を行います。これはsystemctlで行います。

    ```bash
    $ systemctl --user enable docker
    $ systemctl --user start docker
    ```

## 実際にコマンドを実行してみる

### 単一コンテナの実行

まずは順当にhello-worldのコンテナを実行してみましょう。

```bash
$ docker run -it --rm hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
0e03bdcc26d7: Pull complete
Digest: sha256:8e3114318a995a1ee497790535e7b88365222a21771ae7e53687ad76563e8e76
Status: Downloaded newer image for hello-world:latest

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

おお、sudoコマンドを使わずに行けました。

### 単一コンテナでポートを公開する

では、WEBサーバとして外部にポートを公開するコンテナを作ってみましょう。

昔、Dockerのポート公開はiptablesを使用してforwardしていると聞いたことがあるので、ここはさすがにいけないでしょうと思っていますが。

```bash
$ docker run -d --rm -p 80:80 nginx:1.17-alpine
Unable to find image 'nginx:1.17-alpine' locally
1.17-alpine: Pulling from library/nginx
cbdbe7a5bc2a: Pull complete
c554c602ff32: Pull complete
Digest: sha256:763e7f0188e378fef0c761854552c70bbd817555dc4de029681a2e972e25e30e
Status: Downloaded newer image for nginx:1.17-alpine
45bc78be4cc1d2f35321ae80ea3d9fe82cfc3b545a17f16b2eb1c3ea81e5a535
docker: Error response from daemon: driver failed programming external connectivity on endpoint youthful_einstein (9fddff03eda43e5fc3d29328cc20040d5a1a57168bd2fcd7d08473f2a2af6e9d): Error starting userland proxy:.
```

やっぱり駄目でした。。。

エラーメッセージからするとポート公開を設定しようとして、エラーになったようですね。

### docker-composeを使ってみる

では、docker-composeで複数のコンテナを起動してみましょう。

以下の内容でdocker-compose.ymlを書きました。今回はとりあえず、giteaが動けばいいな～と思っています。

```docker-compose.yml
version: "3.7"
services:
  server:
    image: gitea/gitea:latest
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - DB_TYPE=postgres
      - DB_HOST=db:5432
      - DB_NAME=gitea
      - DB_USER=gitea
      - DB_PASSWORD=gitea
      - DISABLE_SSH=false
    restart: always
    networks:
      - gitea
    volumes:
      - ./gitea:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - 3000:3000
    depends_on:
      - db
  db:
    image: postgres:11-alpine
    restart: always
    environment:
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=gitea
      - POSTGRES_DB=gitea
    networks:
      - gitea
    volumes:
      - ./postgres:/var/lib/postgresql/data
networks:
  gitea:
    external: false
```
先ほどの例からするとポート公開で失敗しそうですが、とりあえず実行してみます。。。

```bash
$ docker-compose up -d
Creating network "foo_gitea" with the default driver
Creating foo_db_1 ... done
Creating foo_server_1 ... done
$ docker-compose ps
      Name                     Command               State               Ports
-------------------------------------------------------------------------------------------
foo_db_1       docker-entrypoint.sh postgres    Up      5432/tcp
foo_server_1   /usr/bin/entrypoint /bin/s ...   Up      22/tcp, 0.0.0.0:3000->3000/tcp
```

どうも、うまく動いていそうですね。

では、ブラウザから見てみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26703/3a0e814b-48f2-64b8-70f8-9b11031544e1.png)

出ましたね。

## まとめ

RootlessモードのDockerは単一で実行するようなもの（バッチ処理とか）には向いていそうです。
docker-composeでのポート公開とdockerコマンドでのポート公開で挙動が異なるのがかなり気になりますが、docker-composeを使えばWebアプリの開発環境でも使えそうです。

それにしても、docker-composeのポート公開はどのようにして実現しているのでしょう？？不思議でたまりません。どなたかご存じないでしょうか？