マストドンの一人用サーバーをAWS上に建てる

23 min read読了の目安(約20900字

はじめに

世間でマストドンが流行ったのは2017年のことのようなので、それから数えて4年の周回遅れとなりますが、このたび私もマストドンをはじめてみることにしました。
今回は個人のドメインで個人のサーバーを動かしたかったので、既存のサーバーに入らず自鯖を作成することにします。
今回はクラウドサービスの勉強も兼ねてAWS上に自鯖を構築することにしました。

以下は個人の備忘録です。
私はサーバーサイド、インフラ周りには詳しくなく、試行錯誤で作業をしたものの記録となっております。
この記事のとおりに作業してサーバーを建て損害を被ったとしても責任は取れません。ご了承ください。
なにかこの記事の内容についてお気づきの点があればコメントなり連絡をしていただけると助かります。

使ったAWSのコンポーネントなど

  • VPC
    EC2とRDSを入れとくためのネットワーク。

  • EC2 (t2.micro)
    マストドンのサーバー本体を動かすため。

  • S3
    メディアを格納するストレージとして。

  • CloudFront
    S3のメディアへのアクセスの前に配置。

  • RDS (db.t2.micro)
    お金がかかるのでAZは一つ。

新規アカウントの登録はさせないので、メール送信に必要なものは今回は用意しません。

作業工程

ドメインの準備

XServer Domainで以前に購入していたドメイン名(orito-itsuki.net)を利用することにしました。
サブドメインのsocial.orito-itsuki.netをマストドン用に利用します。

AWSのアカウント作成とIAMユーザの作成

AWSのアカウントを作り、マストドンのサーバーを建てるのに必要な権限を付けたIAMユーザを作ります。
また、ルートアカウントで使用料金の通知設定などを済ませました。

ここらへんの入門的なAWSの使い方は次の本を参考にしました。

VPCを準備する

VPCを作成する

VPCを作成します。
サブネットはEC2のマストドンWebサーバーを入れておくパブリックのものと、RDSを入れておくプライベートのものを用意します。

まずはVPCのダッシュボードからVPCの管理画面に移動します。

リージョンを東京にしてVPCを作成を選択します。

VPCに適当な名前をつけて、CIRDは10.0.0.0/16とします。
IPv6 CIDRブロックは無し、テナンシーはデフォルトのままとしました。

作成を実行すると、作成されたVPCが表示されます。

サブネットの作成

左のメニューからサブネットを選択してサブネットの管理画面を開きます。
ここでEC2インスタンス用とRDSインスタンス用に2つのサブネットを作成します。

VPCには先程作ったVPCを、そしてサブネット名、アベイラビリティーゾーン、IPv4 CIDRブロックは次の表のように設定します。

サブネット名 アベイラビリティーゾーン IPv4 CIDR ブロック
public subnet: social-orito-itsuki-net: web-server ap-northeast-1a 10.0.0.0/24
private subnet: social-orito-itsuki-net: db ap-northeast-1a 10.0.1.0/24
private subnet: social-orito-itsuki-net: db-2 ap-northeast-1c 10.0.2.0/24

作成を実行すると、作成されたサブネットが表示されます。

インターネットゲートウェイの作成

VPC内部のサーバがインターネットと接続するためにインターネットゲートウェイを設置します。

左のメニューからインターネットゲートウェイを選択し、インターネットゲートウェイの作成をします。

名前タグを適当に付けて作成をします。

次に先程作ったVPCへアタッチをします。

アタッチされました。

ルートテーブルの確認と作成

現状でルートテーブルがデフォルトで作成され割り当てられています。

サブネットで作成したサブネットリストからサブネットを確認してルートテーブルを確認するとルートテーブルが作成され割り当てられていることがわかります。
このデフォルトで作成されたルートテーブルはそのままprivateのサブネット用とします。

新たにpublic用のルートテーブルを作成します。

左のメニューからルートテーブルを選択し、ルートテーブルの作成をします。

名前タグを適当につけて、さきほど作ったVPCを選択して作成します。

新しく作成したルートテーブルを選択し、ルートのタブを開きます。

ルートの編集をクリックし、ルートの追加で0.0.0.0/0に先程作成したインターネットゲートウェイを選択します。

いま作成したルートテーブルをpublicのサブネットに設定します。
左のタブからサブネットを選択し、サブネット一覧からpublicのサブネットを選択します。

ルートテーブルタグからルートテーブルの関連付けを編集をして、先程作ったルートテーブルに関連付けます。

セキュリティグループの作成

次の2つのセキュリティグループを作成します。

  • EC2用
    • インターネットからの接続(80,、443)
    • SSHからの接続(22)
  • DB用
    • EC2からの接続(5432)

左のメニューからセキュリティーグループを選択します。

最初にEC2用のセキュリティグループを作成します。
セキュリティグループ名、VPC、インバウンドルールを設定します。
インバウンドルールではHTTP、HTTPSを任意の場所から、SSHをマイIPからとします。

作成できました。

次に同様にしてDB用のセキュリティグループを作成します。
インバウンドルールでPostgreSQLについてソースを先程のセキュリティグループのIDとします。

DBサブネットグループの作成

RDSのコンソールを開き、左のメニューからサブネットグループを選択します。

DBサブネットグループを作成します。

作成できました。

RDSを準備する

RDSのコンソールからデータベースの作成をします。

PostgreSQLを無料枠利用します。
接続に関してVPCやサブネットグループ、セキュリティグループに先程まで作成したものを選択します。
ここでデータベースのユーザ名とパスワードは控えておきます。



しばらくするとステータスが作成中から状態が変わります。


RDSのエンドポイントを控えておきます。

S3を準備する

S3のバケットを準備します。
S3のコンソールからバケットを作成を選択します。

S3のバケット名にドットを含むと悲しい感じになるのでドットを含まない名前をつけると良いです。

CloudFrontを準備する

S3の手前に配置するCloudFrontを用意します。

ACMで証明書の作成

CloudFront用に証明書を作ります。

AWS Certificate Managerを開きます。
CloudFrontでは特定のリージョンの証明書しか使えないので、リージョンをバージニア北部に変更します。

新しく証明書をリクエストします。
パブリック証明書のリクエストをします。

ドメイン名をmedia.social.orito-itsuki.netとします。

DNSの検証でドメイン名の検証を行います。

確認とリクエストをします。

ドメインのCNAMEに特定の値を設定するように指示されます。

XServer Domainでドメインを管理しているので、XServer Domainの管理ページからDNSを設定します。
DNSの設定によってはアンダースコアを値として設定できない場合がありますが、その場合アンダースコアを取り除いた値を設定するとうまく動くようです。

これで数十分放置すると検証保留中から状態が変わります。

CloudFrontの作成

CloudFrontを用意します。
Create Distributionします。

Origin Domain Nameをちょっと工夫します。
デフォルトではバケットを選択するとバケット名.s3.amazonaws.comとなります。
これをバケット名.s3-ap-northeast-1.amazonaws.comとします。
S3のリージョンが東京なのですが、CloudFrontのデフォルトとリージョンが違うためそのままでは24時間アクセスができなくなるらしいです。

Restrict Bucket AcessはYesにします。
Create New Identityにチェックを入れ、Yes,Update Bucket Policyにチェックを入れることでS3のバケットの設定もしてもらいます。

Viewer Protocol PolicyはRedirect HTTP to HTTPSとします。

SSL Certificateで先程作成したSSL証明書を設定します。


作成したCloudFrontからドメイン名d**********.cloudfront.netを確認します。

DNSの設定

d**********.cloudfront.netに対してCNAMEを設定します。
media.social.orito-itsuki.netのCNAMEとしてそのd**********.cloudfront.netを設定します。

S3のブロックパブリックアクセスの設定を変更する

S3のブロックパブリックアクセスの設定をオフにします。
先程のCloudFrontの設定などによって、CloudFrontからしかアクセスされない様になっているため、パブリックアクセスをブロックする設定を解除します。



IAMユーザの作成

EC2がS3のバケットにアクセスするためのIAMユーザを作成します。
IAMのコンソールに移動します。

IAMのコンソールから先程のS3バケットへのフルアクセスを持ったグループを作成します。
まずは左側のメニューからポリシーを選択し、ポリシーの作成を行います。

S3のフルアクセスで先程のバケットへのリソースのアクセスを指定します。

左側のメニューからグループを選択し、新しいグループの作成を行います。

先ほど作成したポリシーを設定します。

グループの作成をします。

次にユーザの作成をします。
左側のメニューからユーザーを選んで、ユーザーを追加を選択します。

ユーザ名を適当に決めて、プログラムによるアクセスを有効にします。

ユーザを先ほど作成したグループに追加します。

これでユーザーを作成します。

作成したらアクセスキーIDとシークレットアクセスキーを控えておきます。

EC2を準備する

マストドンのウェブサービスを実行するEC2インスタンスを作成します。

EC2の作成

EC2のコンソールの左メニューからインスタンスを選びます。

インスタンスを起動します。
マストドンの公式サイトの推奨環境に従い、Ubuntu 18.04のインスタンスを建てます。

t2.microを選択しインスタンスの詳細の設定を行います。

ネットワークには作成したVPCを、サブネットにはpublicなサブネットを選択します。

ストレージの追加でストレージサイズを10GiBにしておきます。
デフォルトの8GiBでは大きめのスワップファイルを作る関係上、若干足りなかったので。

セキュリティグループを先ほど作成したwebサーバー用のものをセットします。

起動し、キーペアを新しく作成してダウンロードしておきます。


ElasticIPの設定

インスタンスが作成されたらElastic IPの作成とインスタンスへの割り当てを行います。
左のメニューからElastic IPを選択します。

Elastic IPアドレスの割当を行います。

そのままこのElastic IPアドレスを関連付けるを行います。

先ほど作成したインスタンスを選び、プライベートIPアドレスを設定し関連付けます。

ドメインのDNSの設定

Elastic IPのIPv4アドレスに対するsocial.orito-itsuki.netのAレコードを作成します。
DNSの設定を行います。

EC2インスタンスに接続する

ダウンロードした.pemファイルを~/.ssh/以下に配置します。
ダウンロードした.pemファイルの権限を変更し、sshのキーとして接続に使えるようにします。

次のコマンドでEC2インスタンスに接続します。

$ ssh -i .ssh/social-orito-itsuki-net.pem ubuntu@social.orito-itsuki.net

EC2に接続できることが確認できました。

EC2内にマストドンサーバーを建てる

EC2に接続した状態で、最初に4Gのスワップファイルを作成します。

$ sudo fallocate -l 4G /swapfile
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile
$ echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

その後は公式サイトの案内に従い作業を続けます。

公式の案内はrootユーザーの前提なのでルートユーザーに移行します。

$ sudo su

Node.jsのリポジトリを追加します。

$ curl -sL https://deb.nodesource.com/setup_12.x | bash -

Yarnのリポジトリを追加します。

$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
$ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list

パッケージをインストールします。

$ apt update
$ apt install -y \
  imagemagick ffmpeg libpq-dev libxml2-dev libxslt1-dev file git-core \
  g++ libprotobuf-dev protobuf-compiler pkg-config nodejs gcc autoconf \
  bison build-essential libssl-dev libyaml-dev libreadline6-dev \
  zlib1g-dev libncurses5-dev libffi-dev libgdbm-dev \
  nginx redis-server redis-tools postgresql postgresql-contrib \
  certbot python-certbot-nginx yarn libidn11-dev libicu-dev libjemalloc-dev

次にmastodonユーザーを作成します。
いくつか設定を効かれますがEnterですべてデフォルトとします。

$ adduser --disabled-login mastodon

ユーザーを切り替えます。

$ su - mastodon

rbenvのインストールをします。

$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
$ cd ~/.rbenv && src/configure && make -C src
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
$ echo 'eval "$(rbenv init -)"' >> ~/.bashrc
$ exec bash
$ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

Rubyをインストールします。
Installing ruby-2.7.2...でしばらく固まりますが放置しておくとなんとかなります。

$ RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 2.7.2
$ rbenv global 2.7.2

bundlerもインストールします。

$ gem install bundler --no-document

exitします。

$ exit

PostgreSQLの設定をします。
まずはRDSのPostgreSQLに接続します。
先ほど控えたRDSのエンドポイントとユーザ名、パスワードを使います。

$ psql -h RDSのエンドポイント -p 5432 -U ユーザ名

PostgreSQLに接続できました。

次のコマンドを実行します。

CREATE USER mastodon CREATEDB;
\q

再びmastodonユーザーに切り替えます。

$ su - mastodon

gitからmastodonをクローンしてきます。

$ git clone https://github.com/tootsuite/mastodon.git live && cd live
$ git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)

mimemagicのバージョンが低くてインストールに失敗するので、Gemfile.lockのmimemagicのバージョンを次のようにmasterのものに書き換えます。

$ vi Gemfile.lock
    mimemagic (0.3.10)
      nokogiri (~> 1)
      rake

インストール作業をします。

$ bundle config deployment 'true'
$ bundle config without 'development test'
$ bundle install -j$(getconf _NPROCESSORS_ONLN)
$ yarn install --pure-lockfile

マストドンのセットアップを行います。

$ RAILS_ENV=production bundle exec rake mastodon:setup

いろいろと質問されるので一つずつ答えていきます。

mastodon@ip-10-0-0-163:~/live$ RAILS_ENV=production bundle exec rake mastodon:setup
Your instance is identified by its domain name. Changing it afterward will break things.
Domain name: social.orito-itsuki.net

Single user mode disables registrations and redirects the landing page to your public profile.
Do you want to enable single user mode? yes

Are you using Docker to run Mastodon? no
PostgreSQL host: RDSのエンドポイント
PostgreSQL port: 5432
Name of PostgreSQL database: mastodon_production
Name of PostgreSQL user: postgres
Password of PostgreSQL user:
Database configuration works! 🎆

Redis host: localhost
Redis port: 6379
Redis password:
Redis configuration works! 🎆

Do you want to store uploaded files on the cloud? yes
Provider Amazon S3
S3 bucket name: media-social-orito-itsuki-net
S3 region: ap-northeast-1
S3 hostname: s3-ap-northeast-1.amazonaws.com
S3 access key: S3のアクセスキー
S3 secret key: S3のシークレットキー
Do you want to access the uploaded files from your own domain? Yes
Domain for uploaded files: media.social.orito-itsuki.net

Do you want to send e-mails from localhost? No
SMTP server: smtp.mailgun.org
SMTP port: 587
SMTP username:
SMTP password:
SMTP authentication: plain
E-mail address to send e-mails "from": Mastodon <notifications@social.orito-itsuki.net>
Send a test e-mail with this configuration right now? no

This configuration will be written to .env.production
Save configuration? Yes

Now that configuration is saved, the database schema must be loaded.
If the database already exists, this will erase its contents.
Prepare the database now? Yes
Running `RAILS_ENV=production rails db:setup` ...

...

The final step is compiling CSS/JS assets.
This may take a while and consume a lot of RAM.
Compile the assets now? Yes
Running `RAILS_ENV=production rails assets:precompile` ...

...

Done!

All done! You can now power on the Mastodon server 🐘

Do you want to create an admin user straight away? Yes
Username: ユーザ名
E-mail: ログイン用のメールアドレス
You can login with the password: 初期パスワード
You can change your password once you login.

ドメイン名にはsocial.orito-itsuki.netを指定。
新しくユーザーの追加をネット上から行える必要はないのでsingle user modeにします。
Dockerでmastodonは動かしていません。
PostgreSQLのホストにはRDSのエンドポイントを。
ユーザーとパスワードは設定したものとします。
Redisはデフォルトで。
メディアについてはcloudに保存する設定でS3を選択し、バケットネーム、リージョン、hostname、アクセスキーシークレットキーをそれぞれ設定します。
CloudFrontで設定しているmedia.social.orito-itsuki.netを設定します。
メールは送らないので適当にデフォルト値でメール送信の確認はパスします。
その後は一通りYesでDB設定やアセットの生成などをしていきます。
最後にadminユーザーを追加することになります。
初期パスワードはメモっておきましょう。

rootユーザーに戻ります。

$ exit

nginxのセットアップをします。

$ cp /home/mastodon/live/dist/nginx.conf /etc/nginx/sites-available/mastodon
$ ln -s /etc/nginx/sites-available/mastodon /etc/nginx/sites-enabled/mastodon

次のファイルのexample.comとなっている部分をsocial.orito-itsuki.netに変更します。

$ vi /etc/nginx/sites-available/mastodon

/etc/nginx/nginx.confのユーザーを書き換えます。

$ vi /etc/nginx/nginx.conf

ユーザーをmastodonとしました。

SSLの設定をします。
メールアドレスを登録し、規約に同意するかはAgree、メールを送るかどうかについてはNo、1か2を選ぶところでは2を選択しました。

$ certbot --nginx -d social.orito-itsuki.net

サービスのセットアップをします。

$ cp /home/mastodon/live/dist/mastodon-*.service /etc/systemd/system/

次を実行してサービスを起動します。

$ systemctl daemon-reload
$ systemctl enable --now mastodon-web mastodon-sidekiq mastodon-streaming

cronでcertbotの更新処理を動かすよう設定します。

$ vi /etc/crontab

次を追加します。

/etc/crontab
0 6 * * 6 root systemctl stop nginx && certbot renew && systemctl start nginx

毎週土曜日の6時にnginxを止めてLet's encryptを更新してnginxを起動し直しています。

マストドンにアクセスする

https://social.orito-itsuki.netにアクセスするとマストドンの初期ユーザのプロフィールページに飛びます。
ログインから先ほど作成したユーザと初期パスワードでログインできることを確認します。
ログインしたら初期パスワードの変更を行います。

PgBouncerを追加する

リレーなどを追加してしばらく動かしたところPostgreSQLのコネクションが辛そうに見えるので、PgBouncerでPostgresqlへのコネクションをプールしてみます。

まずはPgBouncerをインストールします。

$ sudo apt install pgbouncer
$ sudo service pgbouncer restart

次にuserlist.txtにユーザを追加します。
ユーザ名とパスワードをmd5ハッシュに掛ける必要があります。
password + usernameをハッシュにかけ、先頭にmd5を追加したものを利用します。

$ echo -n "passwordmastodon" | md5sum
$ sudo vi /etc/pgbouncer/userlist.txt
/etc/pgbouncer/userlist.txt
"mastodon" "md5d75bb2be2d7086c6148944261a00f605"

pgbouncer.iniの設定をします。

$ sudo vi /etc/pgbouncer/pgbouncer.ini
/etc/pgbouncer/pgbouncer.ini
[databases]

mastodon_production = host=social-orito-itsuki-net-db.cifulqn7t6cc.ap-northeast-1.rds.amazonaws.com port=5432 dbname=mastodon_production user=postgres password=password

[pgbouncer]
logfile = /var/log/postgresql/pgbouncer.log
pidfile = /var/run/postgresql/pgbouncer.pid

listen_addr = 127.0.0.1
listen_port = 6432

unix_socket_dir = /var/run/postgresql

auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt

pool_mode = transaction

server_reset_query = DISCARD ALL

max_client_conn = 100

default_pool_size = 20

接続先のhostやuser、passwordはそれぞれRDSのエンドポイントとユーザー、パスワードとします。

動作確認をします。

$ psql -p 6432 -U mastodon mastodon_production

これでパスワードを入力して問題なく接続できることを確認します。

次に.env.productionのDB周りの設定を編集します。

.env.production
DB_HOST=localhost
DB_PORT=6432
DB_NAME=mastodon_production
DB_USER=mastodon
DB_PASS=password

トランザクションベースのプールを使うため、PREPARED_STATEMENTSは利用できないので、.env.productionに次の行を追加します。

.env.production
PREPARED_STATEMENTS=false

設定を変えたらmastodonを再起動します。

$ sudo systemctl restart mastodon-*

おわりに

マストドンの一人用サーバーをAWS上に建てることができました。
建ててみてから知ったのですが、MisskeyやPleromaのほうが必要なリソースが少なくお財布に優しいかもしれません。
今はAWSの無料枠をふんだんに利用していますが、1年後の無料枠が切れたときにどのくらいの金額がかかるようになるのか気になっています。


そんなわけでよければこちらの私のアカウントをフォローしてみてください。

追記

一人で数人をフォローして運用している分には問題がなかったのですが、2本で一番大きそうな雪餅連合リレーを追加してみたところ負荷がかかりすぎるためか1-2日に一度サーバーのmastodonプログラムが504を返すようになってしましました。
sudo systemctl restart mastodon-*でmastodonを再起動すると直るのですが、毎日のように再起動が必要な状況はあまり良いとは言えませんね。

対策ができ次第、対策の内容を追記しようと考えています。

追記その2

いろいろなパラメータを最適化したりとかを試していたのですが、埒が明かないのでインスタンスタイプをt2.microからt3.smallに変更したら諸々の問題が解決しました。
単純にサーバーのスペック不足だったようですかね。お一人様サーバーなのでt2.microで行けるかと思っていましたが、連合リレーにつなぐとなるとそれなりの数の投稿を処理する必要があるので、それを考えるとt2.microには厳しかったのかもという印象です。
連合リレーに繋がなければt2.microでも動いてはいたので、そのくらいの感覚ということで。
mastodonはやはりリソースを割と食うので他のFediverseも試してみたくなりますね。