wasabiと上手に付き合おう
安くて嬉しい我らがオブジェクトストレージwasabiですが、たびたび不安定になってツライという悩みどころがあります。そこで、Mastodonにちょっとだけ手を加え、運用を工夫して、上手に付き合っていこうという提案です。(どのオブジェクトストレージを使う場合でも、同様の運用が可能です)
この運用の特徴
- wasabiが死んでいても、sidekiqのキューが詰まって遅延しない
- 設定の切替で、いつでもこの運用をやめられるし、いつでも再開できる
- wasabiが調子の悪い時だけこの運用に切り替えるバックアッププランとしても有効
- 複数のオブジェクトストレージにデータ保存する運用も可能
- ローカルファイルシステムを使うので、webとsidekiqが同一サーバの必要あり
- Mastodon本体にちょっとだけ改造が必要
どんな運用を行うか
メディアを最初はローカルファイルシステムに保存し、あとからwasabiに同期する戦略です。
wasabiに直接保存したり、wasabiから直接取得しないことで、wasabiの障害の影響を回避します。
外部からのアクセスはnginxが引き受け、ローカルにある場合はローカルから、なかったらwasabiからデータを取得・送信するように設定します。
ローカルは一時保存ですので、通常はwasabiに同期できたら消してしまいます。しかし、発想を変えて、7日など長めに保持して、メディアがよく参照される期間はすべてローカルで対応することも可能です。アカウントのアバターやカスタム絵文字は、ローカルから消さずに保持し続けるなど、内容による柔軟な運用も可能です。いずれの場合も、ローカルに保存可能な空き容量と相談して、余裕を持って削除します。
wasabi側は全てのデータを保持し、ローカルに障害が発生したり、この運用を中止した場合でも、従来通り単独で機能するように維持します。wasabiへの同期は、wasabiが正常動作している場合は早めに実行しましょう。ファイル変更を検知してリアルタイムに行っても良いし、cronで適当な間隔で定期実行でもOKです。wasabiに障害が発生している間は同期を中止すれば良いので、回復するまで心穏やかに過ごすことが出来ます。
メディアファイルが削除された場合、単純にローカルからファイル削除すると、wasabi側のファイルが参照されて見えてしまったり、削除を同期するのが難しくなるので、MastodonのPaperclipの動作に手を入れて、0バイトの公開アクセス権のないファイルに置き換えるようにします。
また、この運用ではnginxを用いるため、nginxのキャッシュ機能を活用した高速化が図れます。manaelを組み合わせてwebpで配送することも可能です。さらに外側にcloudflareなどを置くと、負荷が軽減されます。色々工夫の余地があって楽しいですね?
nginxの設定
最初に、nginxの設定を済ませてしまいます。 自身の環境に合わせて、読み替えて必要なところだけ拾ってください。
メディアファイルのドメイン名について
ところで、メディアファイルのドメインはどうしていますか?
wasabiを既に利用している場合は、S3_ALIAS_HOSTに何かドメインを設定して、DNSにCNAMEを設定してwasabiにつながるようにしているのではないかと思います。あるいは、本提案と同様、nginxをリバースプロキシにしていますか?
してない場合は、wasabiのURLのまま参照されているかもしれません。
メディアのドメイン名は、連合先に配信された投稿に埋め込まれているので、変更すると連合先からは参照できなくなります。今回設定する場合は、ずっと変更せずに使い続けられるドメインを設定しましょう。メインのMastodonのドメインがexample.com
だったら、media.example.com
とかfiles.example.com
という感じ。
設定ファイルを書く
media.example.com
ということにして、ざっくりこんな感じに設定します。
proxy_cache_path /var/cache/nginx/cache levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g; server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name media.example.com; ssl_certificate /etc/letsencrypt/live/media.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/media.example.com/privkey.pem; ssl_protocols TLSv1.2; ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; root /home/mastodon/live/public/system; add_header Strict-Transport-Security "max-age=31536000"; add_header Content-Security-Policy "style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'self'; img-src data: https:; media-src data: https:; upgrade-insecure-requests"; location / { limit_except GET { deny all; } try_files $uri @proxy; } location @proxy { limit_except GET { deny all; } proxy_ignore_headers set-cookie; proxy_hide_header set-cookie; proxy_set_header cookie ""; proxy_hide_header x-amz-delete-marker; proxy_hide_header x-amz-id-2; proxy_hide_header x-amz-request-id; proxy_hide_header x-amz-version-id; proxy_hide_header etag; proxy_cache CACHE; proxy_cache_valid 200 28d; proxy_intercept_errors on; resolver 8.8.8.8 valid=100s; proxy_pass https://s3.wasabisys.com/media.example.com$request_uri; expires max; } error_page 403 404 =404 /404.html; location = /404.html { return 404 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n<html><head>\n<title>404 Not Found</title>\n</head><body>\n<h1>Not Found</h1>\n<p>The requested URL $request_uri was not found on this server.</p>\n<hr>\n<address>Apache/2.2.31 Server at $host Port $server_port</address>\n</body></html>"; internal; } }
証明書を設定する
certbot等で取得して設定してください。Mastodon本体の証明書に一緒にしてもいいと思います。ここは説明を省略します。
ローカルファイルシステムをみて、見つからなければwasabiを参照する
try_files $uri @proxy;
これです。$uri
をみて、見つからなければ@proxy
で別途記述したwasabiへのproxy接続を行います。
wasabiからのレスポンスをキャッシュする
キャッシュのパス設定。Mastodon本体にもキャッシュパスが設定されてたりするので、nginx.confなど一箇所に書いて共有するか、それぞれ別の名前とパスで設定すること。
proxy_cache_path /var/cache/nginx/cache levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g;
で、ここが実際に使う設定。
proxy_cache CACHE; proxy_cache_valid 200 28d;
ファイルが見つからない時の設定
ちょっとした小細工の類です。
- 403 Forbiddenを404 Not Foundとして返す(後述の削除したファイルへの対応)
- ローカルとwasabiが返してくる404を、Apache風の表示にして返す
error_page 403 404 =404 /404.html; location = /404.html { return 404 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n<html><head>\n<title>404 Not Found</title>\n</head><body>\n<h1>Not Found</h1>\n<p>The requested URL $request_uri was not found on this server.</p>\n<hr>\n<address>Apache/2.2.31 Server at $host Port $server_port</address>\n</body></html>"; internal; }
設定をチェックして切り替える
nginx -t
でエラーがないか確かめてsystemctl reload nginx
する。
DNSの設定をかえる
まだそうなっていない場合は、media.example.com
がnginxを指すように変更する。
S3_ALIAS_HOSTの設定をかえる
まだそうなっていない場合は、.env.production
にS3_ALIAS_HOST=media.example.com
を設定して、Mastodonを再起動する。
ここまでのまとめ
ローカルのnginxリバースプロキシ経由でwasabiに接続するように設定されます。 また、ローカルにファイルが存在すれば、そちらを優先して表示するようになります。
これが問題無く動くことが確認できたら、次のステップに進みます。
Mastodonに改造を施す
cherry-pickする
どちらかというとRailsでよく使われているPaperclipというgemの改造になるので、Mastodon本体に与える影響は軽微です。
このコミットをcherry-pickしてください。v2.9.3用にまとめてありますが、v3.0.0rc1に適用しても大丈夫です。 Add 'NonDeleteFilesystem' storage to paperclip
ブランチを切ってなければ、適用前にブランチを作成
git checkout -b v2.9.3-ndfs
必要なコードを取得してきて、cherry-pick
git fetch https://github.com/fedibird/mastodon.git feature-non-delete-filesystem-v2.9.3 git cherry-pick 7f68672e0
なお、無改造の状態に戻したい場合は、git checkout v2.9.3
などタグを添えてcheckoutすればOKです。
cherry-pickが下記のようなメッセージを出してきた場合、うまくソースコードを取り込めずにエラーが発生しています。
error: could not apply 7f68672e0... Add 'NonDeleteFilesystem' storage to paperclip hint: after resolving the conflicts, mark the corrected paths hint: with 'git add <paths>' or 'git rm <paths>' hint: and commit the result with 'git commit'
この場合、うまくいかなかった部分を修正してgit add
してgit cherry-pick --continue
すればいいんですが、よくわからないから元に戻したい、キャンセルしたいということもあるかと思います。
その場合は、git cherry-pick --abort
とコマンドを入力してください。失敗したので取り消し、となります。
.env.productionで設定を変更する
NDFS_ENABLED=true
とすることで、ローカルファイルシステムにファイルを保存し、削除の代わりに0バイトにして、nginx経由での読み出しを禁止(chmod 0640)するようになります。nginxの設定で、アクセス権が無い場合に404を返すようにしてあるので、実質的に削除したのと同等の振る舞いをするようになります。
wasabiなどS3互換のオブジェクトストレージを有効にするS3_ENABLED=true
の方が優先されるので、これをコメントアウト(行頭に # をつける)すれば、設定完了です。mastodonを再起動すれば、動作が切り替わります。
#S3_ENABLED=true NDFS_ENABLED=true
この運用形態をやめて、以前のwasabiのみの状態に戻す場合は、この設定を戻すだけでOKです。
S3_ENABLED=true #NDFS_ENABLED=true
wasabiにローカルのファイルを転送する
ここからは、様々な運用方法が考えられるため、紹介するやり方は一つの例です。
aws-cliを使用してsyncする
あらかじめaws-cliをインストールして、aws configure
でwasabiにアクセスできるようにセットアップしておきます。
$ aws configure AWS Access Key ID [None]: XXXXXXXXXXXXXXXXXXXX AWS Secret Access Key [None]: xxxxXxxxxXXxXXXxxxXxxxxxXxxXxXXXXxxXxxXx Default region name [None]: us-east-1 Default output format [None]:
wasabiのus-east-1を利用している場合はこちらを実行します。media.example.com
は、実際のバケット名に読み替えてください。
aws s3 sync /home/mastodon/live/public/system s3://media.example.com --endpoint-url=https://s3.wasabisys.com --quiet
wasabiのus-west-1を利用している場合はこちらを実行します。media.example.com
は、実際のバケット名に読み替えてください。
aws s3 sync /home/mastodon/live/public/system s3://media.example.com --endpoint-url=https://s3.us-west-1.wasabisys.com --quiet
これを定期実行すればwasabiに同期されます。不安定な時は、手動で実行した方がいいかもしれません。
syncは結構重いので、ファイルを絞り込んでcpやmvで済ませたり、max_concurrent_requestsやmax_queue_sizeを引き上げたり、深夜に実行するようにしたり、工夫が要るかと思います。
無理に定期実行せずに、wasabiの調子が良い時はS3_ENABLED=true
で運用して、ダメな時だけこの運用に切り替えるのがベストかもしれません。
ローカルの不要なファイルを削除する
保持期間は任意ですが、wasabiに同期が済んでいるものだけを削除します。
7日以上経過したファイルを削除
find /home/mastodon/live/public/system/ -mtime +7|xargs rm -f
12時間以上経過したファイルを削除
find /home/mastodon/live/public/system/ -mmin +720|xargs rm -f