0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

MastodonインスタンスをGKEクラスタにデプロイする

Last updated at Posted at 2018-10-16

注意

  • ここで紹介する手順は行き当たりばったりです。最短手順ではありません
  • とりあえず使えるインスタンスが立てられますがこのまま本運用しない方がいいと思います

この記事を書いた人の知識

  • 現職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としているものがありますが今のバージョンでは無効のようです。ここで取り得る方法は,

  1. 素直にドメインと証明書を取ってHTTPSで接続する
  2. 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に

web-service.yaml
spec:
  type: LoadBalancer
  loadBalancerIP: "<your static IP address>"
  ...

を追加してwebサービスを生成し直します
$ kubectl replace --force -f web-service.yaml
を実行すると強制的にオブジェクトが生成し直されます。ここでコンソールを見に行くと,webサービスに指定した静的IPアドレスが割り当てられているのがわかります

https-portalを使って証明書を取得する

ここでLet's Encryptで証明書を取るのですが,使い方を読むとcertbotというツールを使えと書いてあります。おそらく

  1. certbotで証明書を取得し,GCPのロードバランサに登録して,ロードバランサにSSL終端をさせる
  2. 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としてマウントすることでファイルを置いています)

自分が用意した設定ファイルは以下の通りです

nginx-deployment.yaml
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_certificatessl_certificate_key, ssl_dhparamをhttps-portalの設定ファイル生成ツールが置き換えるマクロに置き換えてある
  • proxy_cache CACHE;を消してある(アドホックな修正)
  • proxy_passhttp://web:3000http://streaming:4000にそれぞれ置き換えてある
mastodon.carbontwelve.ssl.conf.erb
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アドレスを指定する
nginx-service.yaml
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

これは適当です

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使った意味なんだったんだろう

質問などについて

この記事は筆者がやった方法をまとめたという位置づけなので,この通りにやってデプロイできることを保証しません。デプロイできなかったとしても質問は遠慮願います
記事の誤りについての質問・指摘は歓迎しています

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?