riq0h.jp/content/post/かゆいところに手が届くインスタンス運用の初級テクニック集.md
2023-08-21 10:07:01 +09:00

13 KiB

title date draft tags
かゆいところに手が届くインスタンス運用の初級テクニック集 2023-07-22T20:47:25+09:00 false
tech

ソロインスタンスを建ててそろそろ2週間が経とうとしている。おかげさまで絶好調だ。本稿では僕がMastodonインスタンスの運用を改善していく上で、手頃ながら日本語情報が乏しかった情報について取り上げる。

SSLの対応をCloudflareに丸投げする

前提
・運用中のドメインをCloudflareで管理しているか、またはネームサーバを向けている。
・CloudflareのSSL/TLS設定で暗号化モードを フル(厳密) に設定している。

各種の文献ではインスタンスを建てる過程でLet's Encryptを紹介しているものが多い。確かにこの証明書は様々な用途において気安い選択肢だが、反面、3ヶ月に1回の更新作業が地味に面倒だったり、cronで自動化してもなぜかcertbotがコケていたりと微妙な使い勝手の悪さは否めない。

そこで、僕はSSLの対応をCloudflareに丸投げすることを提案したい。多少の面倒を押してひとたび設定してしまえば以降は二度と証明書の顔を見なくて済む最高の選択肢がここにある。まずはCloudflareのページから運用中のドメインを指定して、SSL/TLS → オリジンサーバへと進む。

次に「証明書を作成」をクリックして画面下の「作成」を押す。設定項目は特にいじらなくても差し支えない。証明書の有効期限はデフォルトで15年間となる。西暦2038年7月22日……。その頃には紙より薄いスマホの上にインスタンスが建っていそうだ。

暗号鍵が作成されると画面上に文字列が現れるので、速やかにコピペして指示通りの拡張子で保存する。この画面は再び開けないため、インスタンスを動かしているサーバ上だけでなく安全なローカル環境にも予備を保存しておくのが望ましい。サーバにそれぞれの鍵を保存したら、先にLet's Encryptで発行したSSL証明書を削除する。以降の操作はroot権限で行う。

$ certbot revoke --cert-path /etc/letsencrypt/live/あんたのドメイン名/cert.pem

実行確認を承諾すると証明書は直ちに失効される。cronを設定している人はcrontabを実行して自動更新も忘れずに解除しよう。続いて、nginxの設定に進む。手始めに必要なディレクトリを作成してCloudflareの暗号鍵を移動する。

$ mkdir /etc/ssl/certs
$ mkdir /etc/ssl/private
$ mv あんたのドメイン名.pem /etc/ssl/certs/あんたのドメイン名.pem
$ mv あんたのドメイン名.key /etc/ssl/private/あんたのドメイン名.key

移動したら任意のエディタでnginxの設定ファイルを編集する。

$ vim /etc/nginx/sites-available/あんたのドメイン名.conf

ssl_certificate     /etc/ssl/certs/あんたのドメイン名.pem;
ssl_certificate_key /etc/ssl/private/あんたのドメイン名.key;

編集後、念のためにnginx -tでエラーを確認して問題がなければsystemctl restart nginxでnginxを再起動する。Web UIに接続して証明書の有効性が確認できたら作業は完了だ。

データベースのバックアップをCloudflareに丸投げする

前提
・Docker環境でインスタンスを動かしている。

誉れ高き丸投げシリーズその2。どんどん丸投げしていこう。我々はすでに巨人の肩に乗っているし、どうせ今さら降りることなどできない。CloudflareのページでR2 → 概要と進んでバケットを作成する。バケットの名前はなんでも構わない。ついでに設定から自動削除をスケジュールすると容量の節約になる。

次にwranglerを導入する。wranglerはCloudflareのWorkerをCLIで動かすツールだが、ほとんどのインスタンス運営者が使用しているUbuntuやDebianでは簡単にインストールが行えない。 aptコマンドでインストールされるNode.jsのバージョンが古すぎるためにエラーを起こしてしまうのだ。したがって、wranglerを導入する前に最新のNode.jsをインストールしなければならない。

# すでに古いNode.jsが入っている場合はアンインストールする。
$ apt purge nodejs

$ curl -fsSL https://deb.nodesource.com/setup_current.x | bash -
$ apt install nodejs

dpkgに怒られが発生した時はsudo dpkg -i --force-overwrite /var/cache/apt/archives/nodejs_20.5.0-deb-1nodesource1_amd64.debで強制的に上書きするとうまくいく。バージョン部分の20.5.0は本稿執筆時点での最新の数字なので適宜書き換えられたし。

最新のNode.jsを手に入れたところでようやくwranglerのインストールに入る。npmを導入していなければこれもapt install npmで予めインストールする。

$ npm create cloudflare@latest

# この後、色々訊かれるが指定すべき選択肢は以下の通り。
・Ok to proceed?
→ Yes

・In which directory do you want to create your application? 
→ プロジェクトディレクトリの命名を行う。適当な名前でいい。

・What type of application do you want to create?
→ type Scheduled Worker (Cron Trigger)

・Do you want to use TypeScript?
→ no

・Do you want to deploy your application?
→ no

作業が完了したら命名したディレクトリに移り、npx wrangler loginを実行する。URLが表示されるのでコピペしてブラウザに貼り付けるとCloudflareの認証画面が現れる。しかし、SSH越しに実行しているかぎりこの認証は絶対に失敗する。 認証情報をlocalhostに渡しているせいで照合が成立しないからだ。今回はちょっとした荒業でこいつをくぐり抜けたい。

一旦、愚直にコピペしたURLで認証を行なって失敗してみると、ブラウザのアドレス欄からlocalhostの8976番ポートと通信を試みていた形跡がうかがえる。つまり、このURLをサーバの固定IPアドレスに書き換えれば期待通りの挙動に変化すると考えられる。そこでまずはサーバ側の8976番ポートを開けて準備を整える。ufwはとても簡便なファイアウォールフロントエンドなので、知らなくともぜひ導入してみてほしい。

$ ufw allow 8976
$ ufw reload

ポートを開いた状態で例のURLのlocalhost:8976の部分をあんたのサーバのIPアドレス:8976に書き換えてエンターを押す。うまくいけばたちまち認証が完了してサーバ上でwranglerが使えるようになる。開いたポートはもう使わないので確実に閉じておく。

$ ufw deny 8976
$ ufw reload

以降はインスタンスを動かしているユーザに切り替えて非root環境で作業を行う。ここまで来たところで、試しにバックアップ作業を手動で実行する。

# pg_dumpでバックアップを取得してgzipで固める。userとdbの部分は各自の環境に合わせて変更すること。
$ sudo docker exec mastodon-db-1 pg_dump -Fc -U user db | gzip -c >> backup.gz
# バックアップファイルに権限を与える。
$ chmod 774 ./backup.gz
# wranglerのプロジェクトディレクトリに移動する。
$ cd /home/ユーザ/プロジェクトディレクトリ
# バックアップファイルをCloudflare R2にアップロードする。この際、ファイル名を現在時刻に書き換える。
$ sudo npx wrangler r2 object put "あんたのバケット名/$(date +\%Y\%m\%d_\%H-\%M-\%S).gz" --file=/home/ユーザ/インスタンスのディレクトリ/backup.gz
# 元のバックアップファイルを削除する。
$ rm /home/ユーザ/インスタンスのディレクトリ/backup.gz

一連の動作が間違いなく完了するのを確認した上で、同様の処理内容をシェルスクリプトにしたためる。これでいつでもワンタッチでバックアップを巨人の口にねじ込めるという寸法だ。作成したスクリプトはchmod +x ファイル名.shで実行権限を与えてからsudo ./ファイル名.shで発動する。

#!/bin/bash

echo "Backup begin..."
cd /home/ユーザ/インスタンスのディレクトリ/
docker exec mastodon-db-1 pg_dump -Fc -U user db | gzip -c >> backup.gz
chmod 744 ./backup.gz
echo "Success!"

su - ユーザ << bash
 echo "Uploading to Cloudflare R2..." 
 cd /home/ユーザ/プロジェクトディレクトリ/
 npx wrangler r2 object put "あんたのバケット名/$(date +\%Y\%m\%d_\%H-\%M-\%S).gz" --file=/home/ユーザ/インスタンスのディレクトリ/backup.gz
 rm /home/ユーザ/インスタンスのディレクトリ/backup.gz
bash

上記のスクリプトをcronに登録するとバックアップ作業の自動化が達成できる。

# rootで実行する。
sudo crontab -u root -e

# 毎日午前5時に指定された場所のスクリプトを実行する。別に好きな時間でいい。
0 5 * * * sh /home/ユーザ/ファイル.sh

最高だね。面倒なことは全部機械にやらせよう。ただし、cronのやつは油断すると裏切るのでたまにCloudflareのバケットを見に行った方がいいかもしれない。

Mastodonのリモートメディアを確認して削除する

前提
・Docker環境でMastodonインスタンスを動かしている。

Web UIのサーバ設定でもリモートメディアを自動削除するようにできるが、具体的に何GBのキャッシュが存在していて何GBぶん減らせたのか判らないところがちょっと物足りない。下記の平易なスクリプトでそれを補える。

#!/bin/bash

cd /home/ユーザ/インスタンスのディレクトリ/
echo "Check media usage..."
docker-compose run web bundle exec bin/tootctl media usage

read -p "Enter to proceed..."

echo "Removing..."
docker-compose run web bundle exec bin/tootctl media remove -d 1
echo "Done."

この記述例ではメディアの使用量を照会した後に処理の続行を確認して、Enterキーを押すと24時間以前のリモートメディアが削除される。予期せぬ請求やストレージの圧迫を避けるためにもそれなりの頻度で実施しておきたい。

Mastodonの投稿読み込み数上限を破壊する

前提
・Docker環境でMastodonインスタンスを動かしている。

Mastodonは投稿の読み込み数に制限がある。おそらく負荷対策だろう。過去の投稿は最大で800までしか読み込めない。いちユーザの立場では変えられないゆえ不便を被っている者も少なくないと思われるが、我々は圧倒的権力を誇る鯖缶だ。いくらでも好きな数字に書き換えられる。

# mastodon/app/lib/feed_manager.rb

MAX_ITEMS = 2000

さしあたり僕は2000にした。編集後はsudo docker-compose buildで再ビルドしなければ反映されない。これで深夜帯に蓄積された投稿の一部しか読めないなどという理不尽から解き放たれる。

Mastodonの文字数上限を破壊する

前提
・Docker環境でMastodonインスタンスを動かしている。

500文字もあれば十分と思いきや、ここ一番の時に足りない場合が意外とあったりする。実装系にもよるがだいたいどれも8000文字くらいは受け取れるらしいので不要は制限は予め取り払っておいた方が楽だ。さしあたり僕は9999文字に設定した。ここでは2つのファイルを編集するが、当該のファイル内を「500」で検索すれば容易に修正箇所を見つけることができる。

# mastodon/app/javascript/mastodon/features/compose/components/compose_from.javascript

return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > 9999 || (isOnlyWhitespace && !anyMedia));
};

<CharacterCounter max={9999} text={this.getFulltextForCharacterCounting()} />
# mastodon/app/validators/status_length_validator.rb

class StatusLengthValidator < ActiveModel::Validator
  MAX_CHARS = 9999

こっちでも再ビルドを忘れてはならない。余談だが、最近華々しいリニューアルを果たしたMisskeyフォークのFirefishは一瞬だけ最大文字数を2億5000万文字に設定できたらしい。いい心意気だ。