Wasabiの障害に悩まされている小規模Mastodonサーバのための機能拡張と運用の提案

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.productionS3_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