Docker応用チュートリアル：Mastodon

## 概要

Docker および Docker Compose を使い、Mastodon および関連サービス（Nginx）、SSL 証明書（certbot + Let's Encrypt）動作環境をセットアップします。LinuxやDockerに慣れている方であれば、所要時間は10分程度です。

作成するサービス（コンテナ）、ボリューム、ネットワークの関係は、下図の通りです。

![docker-compose-mastdon-stack.png](https://qiita-image-store.s3.amazonaws.com/0/13002/ea2611f8-044e-d5d3-1d1a-58613b6d0247.png)


## 前提知識

Docker Compose の基本操作（[応用チュートリアル：WordPress](http://qiita.com/zembutsu/items/26cb3a3d138d26c42bab)）の理解が望ましいです（必須ではありません）。

## 6/28(水)ハンズオン用の補足情報

* 希望者に「 `任意の名称`.sakuradon.jp 」の DNS 設定を発行します（当日のみ）
 * サーバの IP アドレスを講師までお知らせください 

## 環境構築(CentOS 7.3)

Docker のセットアップ

```console
# curl -sSL https://get.docker.com/ | sh
# systemctl enable docker
# systemctl start docker
```

Docker Compose のセットアップ

```console
# curl -L https://github.com/docker/compose/releases/download/1.14.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
# chmod +x /usr/local/bin/docker-compose
```

## チュートリアル内容について

GitHub の Mastodon リポジトリで docker-compose.yml が提供されています。しかし、こちらは Mastodon 単体であり、Nginx フロントエンドやメールサーバ等の設定は含まれていません。また最新版（latest）ではイメージをビルドできなかったり、Docker Hub上のlatestタグのイメージに不具合があったりします。

そのため、公式ドキュメントの手順に準じながら、一通り Mastodon を動かすために必要なサービスを、Docker を通して管理できるようにします。

本チュートリアル（ハンズオン）では、Docker Hub 上から最新安定版（今日現在v1.4.6）のDockerイメージを取得し、実行します。

## 1. 作業用アカウントの作成

root 権限で `mastodon` アカウントを作成します。また、このアカウントは `docker` グループにも所属します（`docker` グループに所属しているユーザは `root` 権限としてコンテナを実行可能です。つまり、サーバ上で事実上の root 権限を持っているのと同じ状態のため、セキュリティ上の取り扱いにご注意ください）。

```console
# /usr/sbin/groupadd -g 991 mastodon
# /usr/sbin/adduser -g mastodon -u 991 mastodon
# usermod -aG docker mastodon
```

ユーザを作成した `mastodon` 切り替えます。

```console
# su - mastodon
```

それから `docker version` コマンドを実行し、バージョン情報を確認します。「Server:」 のバージョン情報が表示されていれば正常です。表示されない場合は Docker Engine が起動しているかどうかや、mastodon アカウントが docker グループに所属しているかどうか、確認します。

```console
$ docker version
Client:
 Version:      17.05.0-ce
 API version:  1.29
 Go version:   go1.7.5
 Git commit:   89658be
 Built:        Thu May  4 22:06:25 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.05.0-ce
 API version:  1.29 (minimum version 1.12)
 Go version:   go1.7.5
 Git commit:   89658be
 Built:        Thu May  4 22:06:25 2017
 OS/Arch:      linux/amd64
 Experimental: false
```

## 2. Let's Encrypt 証明書の作成

Mastodon のフロントエンドには SSL 通信に対応した Nginx を設定します。そこで使うための SSL 証明書を作成します。作成には `certbot` と呼ばれるツールを作成します。

ここでは `certbot` プロジェクトの公式 Docker イメージを使います。また `certbot` という名称の Docker ボリュームを作成し、そのボリューム内の `/etc/letsencrypt` ディレクトリ内に証明書を保存します。

まずはじめに証明書を作成するためのテスト（ドライ・ラン）を実行します。次のコマンドの実行にあたり、 `<ホスト名>` には Mastdon にアクセスする URL で使いたいホスト名（例：`social.example.jp`）を、`<メールアドレス>` には Let's Encrypt の通知用に使う自分のメールアドレスを入力します。

```console
$ docker container run -it --rm -v certbot:/etc/letsencrypt \
    -v /var/log/letsencrypt:/var/log/letsencrypt \
    -p 443:443 \
    certbot/certbot \
     certonly --standalone --agree-tos --dry-run -n \
    -d <ホスト名> --email <メールアドレス>
```

実行後、次のように `The dry run was successful.` (ドライ・ランの成功)と表示されれば正常です。

```
IMPORTANT NOTES:
 - The dry run was successful.
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.

```

つぎは、先ほどのコマンドから `---dry-run` を削除して実行します。

```console
$ docker container run -it --rm -v certbot:/etc/letsencrypt \
    -v /var/log/letsencrypt:/var/log/letsencrypt \
    -p 443:443 \
    certbot/certbot \
     certonly --standalone --agree-tos -n \
    -d <ホスト名> --email <メールアドレス>
```

実行すると、次のようにメールアドレスを EFF で共有するかどうか訊ねてくる場合があります。その場合は `Yes` または `No` を入力します。

```
-------------------------------------------------------------------------------
Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about EFF and
our work to encrypt the web, protect its users and defend digital rights.
-------------------------------------------------------------------------------
(Y)es/(N)o:
```

最終的に、次のように表示されていれば、作成完了です。

```実行例：
IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/social.zem.jp/fullchain.pem. Your cert will
   expire on 2017-08-21. To obtain a new or tweaked version of this
   certificate in the future, simply run certbot again. To
   non-interactively renew *all* of your certificates, run "certbot
   renew"
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

```

これでボリューム `certbot` の中に証明書が格納されました。

`docker volume ls` コマンドで確認しましょう。

```console
$ docker volume ls
DRIVER              VOLUME NAME
local               certbot
```

また、次のコマンドを実行することで、証明書関連ファイルを確認できます。

```console:実行例
$ docker run -it -v certbot:/cert/  --rm  alpine ls -al /cert/archive/ここにホスト名/
total 24
drwxr-xr-x    2 root     root          4096 Jun 28 01:30 .
drwx------    3 root     root          4096 Jun 28 01:30 ..
-rw-r--r--    1 root     root          1805 Jun 28 01:30 cert1.pem
-rw-r--r--    1 root     root          1647 Jun 28 01:30 chain1.pem
-rw-r--r--    1 root     root          3452 Jun 28 01:30 fullchain1.pem
-rw-r--r--    1 root     root          1708 Jun 28 01:30 privkey1.pem
```


## 3. SSL 用設定ファイルの作成

SSL通信で使う鍵交換用パラメータ・ファイルを作成するために、openssl コマンドを実行可能なイメージを作成します。

```console
$ cd
$ git clone https://github.com/zembutsu/mastodon-docker.git
$ cd ~/mastodon-docker/myopenssl
$ docker image build -t myopenssl - < ./Dockerfile
```

次に、鍵交換用パラメータ・ファイルを作成します。コマンドの実行後、１～２分ほど待ちます。

```console
$ docker container run -it --rm -v certbot:/certbot myopenssl openssl dhparam -out  /certbot/dhparam.pem 2048
```

## 4. Mastodon GitHub リポジトリの取得と設定ファイルの設置

GitHub リポジトリから、Mastodon 関連ファイルを取得します。

```console
$ cd
$ git clone https://github.com/tootsuite/mastodon.git live
$ cd live
```

それから、設定ファイルを複製、または Docker 関連設定ファイルを上書きします。

```console
$ cp .env.production.sample .env.production
$ cp ~/mastodon-docker/docker-compose.yml .
$ cp ~/mastodon-docker/Dockerfile .
$ cp -r ~/mastodon-docker/frontend .
$ cp ~/mastodon-docker/.env.smtp .
```

次に、設定ファイルを開きます。

```console
$ vi .env.production
```

ファイルを開き、15行目付近の `LOCAL_DOMAIN` を編集し、今回使う「ホスト名」にします。これは Mastodon のアクセスに使うための URL です。また、50行目付近のメールサーバ設定も次のように書き換えます。

```ini:.env.production
# Federation
LOCAL_DOMAIN=<ホスト名 mastodon.example.jp など>
LOCAL_HTTPS=true
（省略）
SMTP_SERVER=mta
SMTP_PORT=25
SMTP_LOGIN=
SMTP_PASSWORD=
SMTP_FROM_ADDRESS=who@example.com ←※要編集（メールの From: アドレスです）
SMTP_DOMAIN=example.com ←※要編集（メールサーバ自身のホスト名、ここでは Mastodon を動かすホスト名を指定）
```

なお、この .env ファイルでは、 `#` （コメント）の位置に注意が必要です。値の後に `#` を使うと、 `#` 込みの値として処理されますので、値の後には書かないようにします。

```ini:望ましい書き方
# 作業用アドレスを使用
SMTP_FROM_ADDRESS=hoe@example.com
```

```ini:誤った書き方
SMTP_FROM_ADDRESS=hoe@example.com # 作業用アドレスを使用
```

次に、メールサーバ用(exim4)設定ファイルを編集します。

```console
$ vi .env.smtp
```

ファイル内容を編集します。

```ini:.env.smtp
MAILNAME=example.com	←ここのホスト名を変更します
RELAY_NETWORKS=192.168.0.0/24
PORT=25
```

Nginx 用設定ファイルを編集します。

```console
$ vi frontend/nginx-default.conf
```

ファイルを開いたら `example.com` の箇所を、今回使うホスト名に変更します（5箇所）。vim の場合は一括置換（`ESC` -> `%s /example.com/自分のホスト名/g`）が便利です。


## 5. イメージの構築と取得

関連イメージの構築と取得を行います。build には数秒、pull はファイル容量が大きいため約1分半程かかります。

```console
$ docker-compose build
$ docker-compose pull
```

※ `docker-compose pull` 実行時、最後に `mastodon_frontend` イメージが pull できないというエラーが表示されますが、問題ありません（Docker Hub上に mastodon_frontend イメージは存在しないからです）。

## 6. 内部通信用ネットワーク作成

`docker network create` コマンドで、Mastodon 内部通信用のネットワークを作成します。これは内部での名前解決を行うためと、データのやりとりや SMTP サーバを外部のパブリックなネットワークから切り離し、安全性を高めるためです。

```console
$ docker network create --subnet=192.168.0.0/16 mastodon
```

ネットワーク `mastodon` が作成されているか `docker network ls` コマンドで確認します。

```console
$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
7d9b04de513f        bridge              bridge              local
babeb8b623d2        host                host                local
949a22119e80        mastodon            bridge              local
e49989f1bbd0        none                null                local
```

## 7. シークレットの作成と設定反映

シークレット（APIで言う所のトークンに相当）を３つ必要なため、次のコマンドを3回実行します。

```console
$ docker-compose run --rm web rake secret
```

実行後、文字列が表示されますので、エディタ等に控えておきます。

そして `.env.production` ファイルを開き、以下の行の `=` のあとに追加します。

```ini:.env.production
PAPERCLIP_SECRET=8979dee487...
SECRET_KEY_BASE=74a137afd0b0...
OTP_SECRET=aae3d46591fb057...
```

## 8. データベースの初期化とアセットのプリコンパイル

初期設定用のコマンドを実行します。

```console
$ docker-compose run --rm web rails db:migrate
$ docker-compose run --rm web rails assets:precompile
```

これで特にエラーが出ていなければ、準備完了です。

## 9. Mastodon 起動と管理用設定

```console
$ docker-compose up -d
```

実行後は `docker-compose ps` で各サービスが起動しているかどうか、確認できます。各サービス（コンテナ）の状態（State）が起動中（`Up`）なのを確認します。

```console
$ docker-compose ps
      Name                    Command               State                    Ports
----------------------------------------------------------------------------------------------------
live_db_1          docker-entrypoint.sh postgres    Up      5432/tcp
live_frontend_1    nginx -g daemon off;             Up      0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
live_mta_1         /bin/entrypoint.sh exim -b ...   Up      25/tcp
live_redis_1       docker-entrypoint.sh redis ...   Up      6379/tcp
live_sidekiq_1     bundle exec sidekiq -q def ...   Up      3000/tcp, 4000/tcp
live_streaming_1   npm run start                    Up      3000/tcp, 0.0.0.0:4000->4000/tcp
live_web_1         bundle exec rails s -p 300 ...   Up      3000/tcp, 4000/tcp
```

また、起動まで時間がかかるので、ログを取得し、画面のスクロールが止まるまで待ちます。

```console
$ docker-compose logs -f
```

次のようなログが出れば問題ありません。

```
Attaching to live_web_1
web_1        | => Booting Puma
web_1        | => Rails 5.0.2 application starting in production on http://0.0.0.0:3000
web_1        | => Run `rails server -h` for more startup options
web_1        | Creating scope :cache_ids. Overwriting existing method Notification.cache_ids.
web_1        | [1] Puma starting in cluster mode...
web_1        | [1] * Version 3.8.2 (ruby 2.4.1-p111), codename: Sassy Salamander
web_1        | [1] * Min threads: 5, max threads: 5
web_1        | [1] * Environment: production
web_1        | [1] * Process workers: 2
web_1        | [1] * Preloading application
web_1        | [1] * Listening on tcp://0.0.0.0:3000
web_1        | [1] Use Ctrl-C to stop
web_1        | [1] - Worker 0 (pid: 14) booted, phase: 0
web_1        | [1] - Worker 1 (pid: 16) booted, phase: 0
```

ログの表示は `Ctrl+C` で中断できます。

あとは、ブラウザから `https://<ホスト名/` にアクセスし、アカウント登録を済ませます。

アカウント登録を済ませたら、対象アカウントを管理者として設定します。

```console
$ docker-compose run --rm web rails mastodon:make_admin USERNAME=<ここにアカウント名>
```

コマンド実行後の URL から、サイトに関する管理情報を編集できるようになります。

最後に cron の設定を済ませます。`crontab -e` を実行し、次の内容を追加します。

```
@daily cd /home/mastodon/live && /usr/local/bin/docker-compose run --rm web rake mastodon:daily > /dev/null
@weekly cd /home/mastodon/live && docker container run -it --rm -v certbot:/etc/letsencrypt -v /var/log/letsencrypt:/var/log/letsencrypt certbot/certbot renew  && /usr/local/bin/docker-compose restart frontend > /dev/null
```

以上で設定完了です。

## 補足：手動でメールアドレスの許可

何らかの理由によりメールが送信できない場合、次のコマンドで手動許可できます（送信先メールの本文に含まれるURLをクリックした状態にします）。

```sh
docker-compose run --rm web rake mastodon:confirm_email USER_EMAIL=<ここにメールアドレス>
```

## 後片付け

次のコマンドを実行すると、**Mastodon に限らず** システム上の全てのockerコンテナ・イメージ・ネットワーク・ボリューム領域を **強制破棄** します。

```console
$ cd ~/live/
$ docker-compose down
$ docker system prune
```