MastodonのPostgreSQLをpg_rmanを使ってバックアップする
2017-04-25 23:26 typo修正
しばらくブログを更新していなかったが、最近は「ぼくもあの日、有給を取ってインスタンスを立ててさえいれば今頃年収3億ぐらいでドワンゴに雇われていたのでは...。」と思う日々を過ごしている。
とはいえ、インスタンス運用しつつ運用ネタでも書けば、まだ年収1億ぐらいでなら雇われるチャンスがあると思うので、運用ネタでも書くことにする。バックアップだ。
ユーザが20人程度いる弱小インスタンスを回しているが、つまりデータを飛ばすと20人から怒られてしまう。小心者なのであまり怒られたくはない。ということでPostgresのバックアップを構成することにした。
本記事では、Dockerで運用しているMastodonのDBを、Dockerの恩恵を受けつつ比較的楽にバックアップする方法について言及する。内容自体はMastodonに限らず、Postgresを利用するシステム全般で参考になるだろう。
pg_rman
色々調べてみると、Postgresでオンラインバックアップを取るにはpg_rman
を使うのが便利そうであることが分かった[1] [2]。pg_rman
は 1)DBの実体ファイルの物理的なコピーと 2)コピー中のトランザクションログ(WAL)をマークするコマンドの発行をまとめてやってくれるユーティリティだ。加えて、実際にリストアをする際もよしなに面倒をみてくれるらしい。
導入
弱小インスタンスの良いところは、パフォーマンスを気にせずDockerコンテナから剥がさずほぼ本家そのままのdocker-compose.yml
で動かせるところで、ぼくももれなくコンテナで運用している。そういうわけで、pg_rman
もコンテナにしてしまって楽に動かしたい。Docker
Hubに誰か上げてくれてるやろって思いながら探したら意外と無かった。なんでや...。仕方ないので作った。
https://hub.docker.com/r/mecab/docker-pg_rman/
(なんか9.3と9.4版がビルド通らなくてイメージ作れてないんですがあとでどうにかします...。たぶん。)
postgres.conf
の編集
pg_rman
を使うためには、postgresql.conf
を編集してPostgres側でWALをアーカイブするように設定する必要がある。デフォルトでコメントアウトされている部分を編集し、以下のように有効化する。
#wal_level = minimal #minimal, replica, or logical
wal_level = replica
#archive_mode = off # enables archiving; off, on, or always
# (change requires restart)
archive_mode = on
#archive_command = '' # command to use to archive a logfile segment
# placeholders: %p = path of file to archive
# %f = file name only
# e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'
archive_command = 'test ! -f /archive/%f && cp %p /archive/%f'
wal_level=archive
に設定すると書かれているドキュメントもあるが、PostgreSQL 9.6からreplica
に変更されたらしい [3]。
archive_command
で書いたコピー先/archive
は実際はホスト側のストレージで、volumeとしてマウントする。このため、下で説明するようにdocker-compose.yml
にも設定が必要である。
docker-compose.yml
の編集
db:
restart: always
image: postgres:alpine
### Uncomment to enable DB persistance
volumes:
- ./postgres:/var/lib/postgresql/data
- /host/path/to/wal_archive:/archive
このような感じで、ホスト上の/host/path/to/wal_archive
をコンテナ内の/archive
に対応付ける。
また、pg_rman
のコンテナを追加する。
rman:
image: mecab/pg_rman
env_file: .env.production
user: "70:root"
volumes:
- ./postgres:/pg_data
- /host/path/to/backup:/backup
depends_on:
- db
このような感じで/pg_data
を実際のPosgtresのデータの実体の場所(=dbでマウントする場所)、/backup
をバックアップを置きたい場所に対応付ける。また、depends_on
を設定することで、ネットワーク的にrman
コンテナからdb
コンテナをdb
というホスト名で見えるようにする。user
を設定したのはデフォルトではposgtresのコンテナがUID:GID=70:0で動いてファイルを作るので、バックアップのそれに合わせておきたかったということで気分の問題。
pg_rman
はPostgresのバージョンに応じて別のビルドを使う必要があるので、実際はdb
とrman
のイメージをタグで固定したほうが良いと思う。
あとは、.env.production
に環境変数を設定して、db
のホスト名と必要な資格情報を与えてあげるだけだ。
.env.production
の編集
# Service dependencies
...
DB_HOST=db
DB_USER=postgres
DB_NAME=postgres
DB_PASS=
DB_PORT=5432
# For pg_rman
PGHOST=db
PGUSER=postgres
PGDATABASE=postgres
PGPASS=
PGPORT=5432
こんな感じで、PGHOST
にDB_HOST
を、PGUSER
にDB_USER
を...という感じで同じものを与える。
あとは、docker-compose down; docker-compose up -d;
でコンテナたちを作り直す。以下で書くようにpg_rman
はdocker-compose run
だけで使いたいのだけど、docker-compose.yml
に書いちゃうとdocker-compose up
の時に必ず無駄にコンテナが作られてしまうので悲しい。作ったpg_rman
のDockerfileは引数無しで実行されると正常終了するようにしているので、気になるなら直後にdocker-compose rm rman
とかしてしまっても良い。
バックアップ
ということで準備ができたので早速バックアップする。初回のみ
$ docker-compose run --rm rman init
で初期化して
$ docker-compose run --rm rman backup --backup-mode=full --compress-data --progress
でバックアップする。バックアップ後
$ docker-compose run --rm rman validate
で検証する。バックアップは
$ docker-compose run --rm rman show
で見える。オプションの詳細については公式ドキュメントや[1:1] [4] あたりを参照のこと。
とりあえず、
/usr/local/bin/docker-compose run --rm rman backup \
--backup-mode=full --compress-data --progress \
--keep-data-generations=3 --keep-data-days=14 --keep-arclog-days=14
/usr/local/bin/docker-compose run --rm rman validate
こんな感じのをcronに仕込んでみた。--backup-mode=incremental
で増分バックアップもできるみたいだけどなんか事故ったときにめんどくさそうなのでfull
にした。インスタンスが小さいことと、幸いにもストレージに余裕があること、またバックアップ先はzfsにしてdedupしているのでフルバックアップしてもあんまり容量は消費しないんじゃないかなーという楽観的な観測だ。辛くなってきたらそのうち考える。
とりあえず今日はここまで。リカバリの実験してないけどあとでやる(死亡フラグ)。