さいきん fly.io というサービスが面白くて触っている。 これはHerokuやCloud Runに近いPaas (CaaS?) で、Dockerfileとちょっとしたconfig fileを用意するとアプリをインターネット上にデプロイできるサービスだ。
個人的にすごい(面白い)と思った特徴として次のようなものがある。
Dockerfile
が使えるので特定の言語やランタイムに依存しない- デプロイしたアプリは各地のEdge Serverで実行され、高速なレスポンスが期待できる
- 東京Regionがある
- 特別な設定なしで自動的にHTTPSなURLを提供してくれる
- マネージドなPostgreSQLがある(無料枠付き!)
- 同じ組織からデプロイしたアプリはWireGuardのVPNを共有し、
<app-name>.internal
アドレスでお互いアクセスできる
特にPostgresの無料枠があるというのは個人で色々試すにはちょうど良さそうだと考え、fly.io が実際どれくらい使えるのか試したくなり、Pleroma の設置を試みた。
Pleroma とは、一時期話題になったMastodonと近い分散SNSで、Erlang/OTPで動作し、ストレージにPostgresを採用している。 Mastodonと比べると軽量で必要な周辺コンポーネントも少なめなので軽くデプロイするには丁度いいと感じて選択した。
PleromaをDocker化する
fly.io にデプロイするためにはまずアプリをコンテナで動くよう Dockerfile を記述する。 PleromaのREADMEに有志が作ったDockerfileへのリンクがあった ので、そのまま使わせてもらった。
GitHub - angristan/docker-pleroma: Docker image for the Pleroma federated social network
Pleromaの config.exs
もこのリポジトリのものをそのままコピーしてきた。
fly.io の設定を作成する
fly.ioのドキュメント に従ってCLIツール flyctl
を導入する。
そして flyctl launch
で設定ファイル (fly.toml
) を作る。
最後に「今すぐデプロイする?」と聞かれるが、まだPostgresなどを立てていないのでここはNoにする。
生成された fly.toml の設定はほぼデフォルトのままでOKだが、用意されたDockerfileでは公開するポート番号が4000なので、internal_port
の部分を 4000 に変える。
# fly.toml [[services]] http_checks = [] internal_port = 4000
Postgres Databaseを作成する
fly.ioのPostgresドキュメント の通りにPostgres Databaseを作成する。
ドキュメントにもある通り、Postgresのインスタンスも fly.io のアプリのひとつとして扱われる。(よって同じ組織内から <app-name>.internal
でアクセスできる。 )
作成するとPostgresのユーザーやパスワードが表示されるので、これをメモしておく。
環境変数を Pleroma アプリに設定する
今回使う config.exs では一部の情報を環境変数から取得する。 fly.io には環境変数を設定ファイルに含めずに秘密情報として差し込む機能 Secrets があるので、それを利用してDBのパスワードやドメイン情報などを入れる。
export DOMAIN=$(flyctl info --host) export DB_APP_NAME=<FLY_DB_APP> # Postgres instanceのアプリ名 export DB_PASSWORD=<FLY_DB_PASSWORD> # Postgres instanceのパスワード flyctl secrets set \ DOMAIN=${DOMAIN} \ DB_HOST=${DB_APP_NAME}.internal \ DB_USER=postgres \ DB_PASS=${DB_PASSWORD} \ DB_NAME=pleroma \ ADMIN_EMAIL=example@example.com \ NOTIFY_EMAIL=example@example.com
DBの初期化をする
作成したPostgresにローカル環境から直接入るのにflyctl proxy コマンドが便利である。
次のようにすると localhost:5432
に好きなPostgres clientから接続ができてDBが自由に触れる。
flyctl proxy --app <FLY_DB_APP> 5432:5432
このproxyを通したままの状態で、 postgres://postgres:{password}@localhost:5432/
に対して mix ecto.migrate
を実行してDBの初期化を行う。せっかくアプリをDocker化したのに mix ecto.migrate
はローカルから直接叩くのはイケてない感じがして、本当はこれもDockerの中でできれば良かったのだが、面倒でローカルにElixirを入れてやってしまった。。
Postgresに対して IPv6 接続を強制する
冒頭に書いたとおり、同じ組織からデプロイしたアプリはWireGuardのVPNを共有し、<app-name>.internal
アドレスでお互いアクセスできる。しかしこのDNS名によるアクセスはIPv6が必要らしい1。
よって、Ecto.Adapters.Postgresの設定 にIPv6接続のオプションを付けておく。
# config.exs config :pleroma, Pleroma.Repo, adapter: Ecto.Adapters.Postgres, username: System.get_env("DB_USER", "pleroma"), password: System.fetch_env!("DB_PASS"), database: System.get_env("DB_NAME", "pleroma"), hostname: System.get_env("DB_HOST", "db"), pool_size: 10, + socket_options: [:inet6]
Pleromaをデプロイする
ここまでできたらやっと最後のデプロイ。次のコマンドを打つだけでOK.
flyctl deploy
デプロイが完了したらWebブラウザで開いてみる。
flyctl open
Pleromaの画面が表示された! 🎉
ユーザー登録、テキストの投稿、そしてリモートサーバーからのフォローなどを少々試してみたが問題なさそうだった。
メモリ上限を上げておく
デフォルトだとfly.ioのVMはメモリ256MBで動いているが、Pleromaはさすがに256MBでは厳しいようで ひとりユーザー登録するだけでOOM Killが発生してしまった。
なので、fly.ioのアプリ管理画面からVMのメモリを512MBに変更したら安定して動作するようになった。
ただ、Metricsをみる感じ512MBでもギリギリな気もする。自分一人だけが登録してどこのサーバーもフォローしてない状態だから保てているが、フォローフォロワーが増えて処理量が多くなるとメモリ1GB以上が必要かもしれない。
(補足)画像のアップロード先をS3 + Cloudfrontにする
コピーしてきた config.exs の初期設定では画像などのアップロード先はローカルのファイルシステム上になっている。 しかし、fly.ioのVMのストレージは永続化されておらず、永続化されたvolume mountも設定可能だが無料枠では3GBまでなので、このまま画像アップロードを多用するのは厳しい。
そこで、画像のアップロード先をS3 (+CloudFront) に変えておくとよい。 基本的な設定は公式ドキュメントの通り だが、いくつか注意点がある。
S3 Bucketの権限設定を ACL enabled にする
- S3はACLを無効かしてpolicyのみで権限を管理することを推奨しているようだが、PleromaのS3 UploaderはACLを指定したアップロードをしている2 ため
- また、Block public access の解除も必要
- 本当はPleroma側を修正したほうがいいんだろうな…
AWSの
region:
の指定を忘れずに- これは上のドキュメントには書かれていないが、
region:
を指定しないと自動でus-east-1
をリクエストしてしまうため
- これは上のドキュメントには書かれていないが、
- CloudFrontを使う場合は
truncated_namespace: ""
を忘れずに- デフォルトではアップロードされた画像は
<base_url>/<s3_bucket_name>/xxxx.jpg
のようなURLになるが、CloudFrontでは<bucket_name>
の部分は不要なので、truncated_namespace で明示的にbucket_nameを削る指示が必要3。
- デフォルトではアップロードされた画像は
以上の注意点を考慮して最終的に config.exs は次のようになった。
# config.exs config :pleroma, Pleroma.Upload, uploader: Pleroma.Uploaders.S3, base_url: "https://xxx.cloudfront.net" config :pleroma, Pleroma.Uploaders.S3, bucket: System.get_env("S3_BUCKET"), truncated_namespace: "" config :ex_aws, :s3, access_key_id: System.get_env("AWS_ACCESS_KEY_ID"), secret_access_key: System.get_env("AWS_SECRET_ACCESS_KEY"), host: "s3.ap-northeast-1.amazonaws.com", region: "ap-northeast-1"