注意
- ここで紹介する手順は行き当たりばったりです。最短手順ではありません
- とりあえず使えるインスタンスが立てられますがこのまま本運用しない方がいいと思います
この記事を書いた人の知識
- 現職Webエンジニア
- Docker Composeを使ったことはない
- Kubernetesを使ったことはない
- 他のコンテナオーケストレーションツールを使ったことはある
- GKEも使ったことはない
- GCP自体は使ったことがある
- RoRを使ったことはない
対象とする読者
- Mastodonのインスタンスを立ててみたいと思っている
- Docker,Docker Compose,Kubernetes,GKEがそれぞれなんなのか知っていて,必要ならば公式ドキュメントを読んで理解することができる
まえおき
「なんかMastodonのインスタンス立てるの流行ってるっぽいな〜」
「みんなVPS上にいろいろインストールしてるけど公式でDockerイメージ用意してあるしKubernetes使ったら簡単にデプロイできんじゃね?」
と考えてKubernetes使ったことない人がやってみたら意外と手こずった話です
準備
以下GKEを使うので,GKEのQuickstartドキュメントの Creating GKE Cluster までを済ませてGKEクラスタを作成しておきます。クラスタの名前はなんでもいいのですがmstdn
としておきます
Mastodonのインスタンスを立ててみよう
まずは公式ドキュメントを見に行く
とりあえずMastodonのリポジトリを見に行くと,Dockerを使ってデプロイする方法が書かれたドキュメントがあります
👉https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Docker-Guide.md
Docker ComposeのComposeファイルが提供されているので,そのまま乗っかってKomposeを使ってGKEクラスタにデプロイを試みてみることにします
Composeファイルを修正する
既に用意されているDockerイメージを使いたいので,公式ドキュメントの Getting the Mastodon image / Using a prebuilt image に従って作業していきます
まず docker-compose.ymlをダウンロードして,作業ディレクトリに保存します。ドキュメントに従ってbuild .
をコメントアウトします。Dockerイメージのバージョンはデフォルトで大丈夫でしょう。コンテナのビルドも必要無いのでdocker-compose build
は実行しません。手許のpublic
ディレクトリも使わないので作成する必要も権限を変える必要もありません
Mastodonの設定ファイルを生成する
ドキュメントを見ると設定ファイルの生成のために
$ docker-compose run --rm web bundle exec rake mastodon:setup
を実行しろと書いてあるのですが,手許のコンテナ上で設定ファイルを生成してもこれがクラスタの上に載るわけではないので,生成された設定ファイルを手に入れて他の方法でなんとかすることにします。そこで,設定ファイルを生成するコマンドを
$ docker-compose run web bundle exec rake mastodon:setup
としてファイルの生成後にファイルシステムを残すことにし,docker cp
コマンドを使ってファイルをコンテナ内からホストに取ってくることにします
というわけで
$ docker-compose run web bundle exec rake mastodon:setup
を実行するといろいろと聞かれます。
Domain name:
には適当なドメインを入れ,
Do you want to enable single user mode?
はたぶんYesでもNoでもどっちでもよさそう?とりあえずNoで
Are you using Docker to run Mastodon?
にはYes
PostgreSQLの設定はとりあえずデフォルトのまま
Redisの設定もデフォルトのまま
Do you want to store uploaded files on the cloud?
は設定めんどくさいのでNo
Do you want to send e-mails from localhost?
もNo
Eメールの設定はあとで書き換えるのでここもとりあえずデフォルトのまま
Save configuration?
にYesと答え,データベースのセットアップやらアセットのコンパイルやら管理者ユーザの作成やらは後回しにします。設定ファイルが生成されたら
$ docker container ls
でwebコンテナの名前を確かめて
$ docker cp <container name>:/mastodon/.env.production ./
として.env.production
ファイルをコピーしてきます。ここで
$ kompose up
とするとGKEクラスタ上にアプリケーションがデプロイされます👏
ヘッドレスサービスの罠
ここでGCPコンソールからGKEクラスタのワークロードを見に行くと,sidekiqデプロイのステータスが Does not have minimum availability になっています。デプロイの詳細を開き,“Container logs”からログを見ると,getaddrinfo: Name does not resolve
というメッセージが見えるので,何かのアドレスの解決に失敗しているようです。前後を見るとRedisのモジュールを呼び出しているようなので,Redisのアドレス解決に失敗している?
ここで結構詰まったのですが,実はKubernetes DNSはふつうのサービスについてはアドレス解決ができるようになっているのですが,ヘッドレスサービスについてはアドレス解決ができません(当たり前)。RedisとPostgreSQLはdocker-compose.ymlでportsが指定されていないので,Komposeがヘッドレスサービスと解釈してしまったためアドレス解決ができなかったというわけです
なのでdocker-compose.ymlの各サービスでports
を指定します
dbサービスは
ports:
- "127.0.0.1:5432:5432"
を,redisサービスは
ports:
- "127.0.0.1:6379:6379"
を指定して,$ kompose down && kompose up
を実行してデプロイをやり直します。これでステータスがOKになりました👏
ここで早速Mastodonにアクセスしてみたいのですが,どうやら外部からアクセスできるIPアドレスが割り当てられていないようです。というわけで外部からアクセスできるようにしたいと思います
外部からアクセス可能なIPアドレスを割り当てる
Kubernetesのサービスに外部からアクセスするためにはサービスタイプをLoadBalancer
としなければいけないようです。この辺からだんだんKubernetesのいろいろの機能を使うので,docker-compose.ymlファイルをKubernetesの設定ファイルにいったん変換しておきます
$ kompose convert
を実行するとdocker-compose.ymlが変換され,Kubernetes用のYAMLファイルが大量に生成されます。この中からweb-service.yamlファイルを開き,Pod specに次のようにしてサービスタイプを追加します
spec:
type: LoadBalancer
...
$ kompose down
を実行して先ほどのデプロイの後片付けをして,新しいKubernetes設定ファイルを使ってデプロイをしてみましょう
$ kubectl create -f <configuration file name>
を実行すると,渡したファイルの中に書かれたKubernetesオブジェクトが生成されます。一連のYAMLファイル全てをこのコマンドに渡して全てのKubernetesオブジェクトを生成するとデプロイ完了です
ここでもう一度GCPコンソールからGKEのサービスを見に行くと,「サービスのタイプ」が「ロードバランサ」となっていて,「エンドポイント」のところに外部からアクセス可能なアドレスが書かれていることがわかります。早速このアドレスにアクセスしてみますが,いつまで経ってもロードされません。webサービスのログを見に行くと,どうやらHTTPSに転送されているらしいのですが,いまはまだ証明書がないのでHTTPSでアクセスしても何も返ってこないというわけです
どうやらいまのMastodonはproduction環境でHTTPで接続することはできないらしいですね。他の記事を見ると環境変数にLOCAL_HTTPS=false
としているものがありますが今のバージョンでは無効のようです。ここで取り得る方法は,
- 素直にドメインと証明書を取ってHTTPSで接続する
- local環境のイメージをビルドし直してデプロイする
の二つだと思うのですが,ここではドメインと証明書を取ってHTTPSで接続する設定をやることにします。まず,ドメインは適当なところで取ってきてください。以下mastodon.carbontwelve
を取得したとします
静的IPアドレスを使う
この先は静的IPアドレスがないといろいろと困るので,静的IPアドレスを取得します。GCPのドキュメントのReserving a new static external IP addressに従って静的IPアドレスを予約します。IPアドレスが予約できたらDNSの設定でmastodon.carbontwelveをそのアドレスに対応させるレコードを作成してください。数時間ぐらいするとドメイン名で予約したIPアドレスにアクセスできるようになります。web-service.yamlのPod specに
spec:
type: LoadBalancer
loadBalancerIP: "<your static IP address>"
...
を追加してwebサービスを生成し直します
$ kubectl replace --force -f web-service.yaml
を実行すると強制的にオブジェクトが生成し直されます。ここでコンソールを見に行くと,webサービスに指定した静的IPアドレスが割り当てられているのがわかります
https-portalを使って証明書を取得する
ここでLet's Encryptで証明書を取るのですが,使い方を読むとcertbotというツールを使えと書いてあります。おそらく
- certbotで証明書を取得し,GCPのロードバランサに登録して,ロードバランサにSSL終端をさせる
- certbotとHTTPサーバを一緒にしたコンテナを作成して証明書の取得とSSL終端をさせる
の二つの選択肢があると思うのですが,ここはいったんhttps-portalというnginxとcertbotを一緒にしたDockerイメージを使わせてもらうことにします。まず,このコンテナ用のKubernetesデプロイ,サービス,Persistent Volume Claimの設定ファイルを書きます
nginx-deployment.yaml
デプロイの設定ファイルに必要なことは
- imageにsteveltn/https-portal:1を指定する
- portsに80(HTTP)と443(HTTPS)を指定する
- envにDOMAINSとSTAGE,WEBSOCKETを指定する
- /var/lib/https-portalにPersistent Volumeをマウントする(一度取得した証明書を保存しておくため)
- /var/lib/nginx-conf/mastodon.carbontwelve.ssl.conf.erbにカスタムのnginx設定ファイルを置く(ここではconfigmapをvolumeとしてマウントすることでファイルを置いています)
自分が用意した設定ファイルは以下の通りです
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: steveltn/https-portal:1
ports:
- containerPort: 80
- containerPort: 443
env:
- name: DOMAINS
value: mastodon.carbontwelve
- name: STAGE
value: production
- name: WEBSOCKET
value: "true"
volumeMounts:
- name: "nginx-config"
mountPath: "/var/lib/nginx-conf/mastodon.carbontwelve.ssl.conf.erb"
subPath: "mastodon.carbontwelve.ssl.conf.erb"
- mountPath: /var/lib/https-portal
name: nginx-pvc
volumes:
- name: "nginx-config"
configMap:
name: "nginx-config"
- name: nginx-pvc
persistentVolumeClaim:
claimName: nginx-pvc
mastodon.carbontwelve.ssl.conf.erb
基本的には公式ドキュメントに書いてあるnginx設定ファイルなのですが,https-portalに合わせて以下の点を変更してあります
-
ssl_certificate
とssl_certificate_key
,ssl_dhparam
をhttps-portalの設定ファイル生成ツールが置き換えるマクロに置き換えてある -
proxy_cache CACHE;
を消してある(アドホックな修正) -
proxy_pass
をhttp://web:3000
とhttp://streaming:4000
にそれぞれ置き換えてある
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name mastodon.carbontwelve;
(omitted)
# Uncomment these lines once you acquire a certificate:
ssl_certificate <%= domain.chained_cert_path %>;
ssl_certificate_key <%= domain.key_path %>;
ssl_dhparam <%= dhparam_path %>;
(omitted)
location @proxy {
(omitted)
proxy_pass http://web:3000;
proxy_buffering on;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_cache_valid 200 7d;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
add_header X-Cached $upstream_cache_status;
add_header Strict-Transport-Security "max-age=31536000";
tcp_nodelay on;
}
location /api/v1/streaming {
(omitted)
proxy_pass http://streaming:4000;
proxy_buffering off;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
tcp_nodelay on;
}
error_page 500 501 502 503 504 /500.html;
}
nginx-service.yaml
サービスの設定ファイルで必要なことは
- 80番と443番ポートを開ける
-
loadBalancerIP
に取得した静的IPアドレスを指定する
kind: Service
apiVersion: v1
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- protocol: TCP
name: "https"
port: 443
targetPort: 443
- protocol: TCP
name: "http"
port: 80
targetPort: 80
type: LoadBalancer
loadBalancerIP: "your static IP address"
nginx-pvc.yaml
これは適当です
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
$ kubectl create configmap nginx-config --from-file=mastodon.carbontwelve.ssl.conf.erb
を実行してconfigmapを追加し,いま作成した各Kubernetesオブジェクトを実際に生成します。ここでnginxワークロードのログを見に行くと証明書が取得できていると思います👏
ただここでmastodon.carbontwelveにアクセスするとエラーが出ます
ERROR: relation "session_activations" does not exist
データベースの初期化忘れてました。というわけで
$ kubectl get pod
でwebポッドのPod IDを確認し,
$ kubectl exec -it <pod ID> /bin/bash
でポッド上でbashを起動して
$ SAFTY_ASSURED=1 bundle exec rake db:setup
でDBを初期化します。もう一度アクセスしてみるとトップページが表示されますおめでとう!🎉🎉🎉
やり残したこと
- nginxのconfigurationをまともにする
- いろいろアドホックなことをしまくったのでまともにする必要がある気がする?
- メールサーバの設定をする
- ここではまだメールサーバの設定をしてないので,ユーザを作成しても確認メールが来ないので使えません
- RedisにPersistent Volumeをくっつける
- いまのままだと再起動するとRedisのデータが消えます
- PostgreSQLをCloud SQLのインスタンスに変更する
- コンテナ上にPostgreSQLを立てるよりもCloud SQL使ったほうがパフォーマンスいいはず
- ファイアウォールなどの設定をする
- SSL終端をLBにする
- L7のLBが使えたほうがいろいろたぶん便利
感想
Kubernetes使った意味なんだったんだろう
質問などについて
この記事は筆者がやった方法をまとめたという位置づけなので,この通りにやってデプロイできることを保証しません。デプロイできなかったとしても質問は遠慮願います
記事の誤りについての質問・指摘は歓迎しています