Docker ComposeでMySQLの定期バックアップ

Docker Composeで構成したMySQLコンテナのデータベースの内容を定期的にバックアップする方法。

[databack/mysql-backup](https://hub.docker.com/r/databack/mysql-backup)というDockerイメージがあったのでこれを使う。

# 定期バックアップ
```yaml:docker-compose.yaml
services:
  db:
    image: mysql:8.0.27
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
    volumes:
      - ./db/storage:/var/lib/mysql
  
  dbbackup:
    image: databack/mysql-backup
    depends_on:
      - db
    environment:
      - DB_SERVER=db
      - DB_USER=root
      - DB_PASS=${MYSQL_ROOT_PASSWORD}
      - DB_DUMP_FREQ=1440
      - DB_DUMP_TARGET=/db
    volumes:
      - ./dbbackup/storage:/db
```

サービス`db`がMySQLコンテナ、サービス`dbbackup`がバックアップを行うコンテナ。

環境変数について、`DB_SERVER`、`DB_USER`、`DB_PASS`はそれぞれデータベースのホスト名、ユーザー名、パスワード。`DB_DUMP_FREQ`はバックアップの間隔で単位は分。ここでは $1440=60\times24$ より1日間隔。`DB_DUMP_TARGET`はバックアップファイルを出力するディレクトリで、`volumes`でホストのディレクトリをマウントして使う。ここではホストの`./dbbackup/storage`ディレクトリにバックアップされた圧縮ファイルが置かれる。

その他の設定については[databack/mysql-backup](https://hub.docker.com/r/databack/mysql-backup)に詳しい解説がある。バックアップファイルをSMBのネットワークドライブ上に置いたりAWS上に置いたりもできる模様。

## バックアップファイルを置くディレクトリのパーミッションについて
このDockerイメージ内のプロセスはデフォルトでは`root`ではなく`appuser`という名前の一般ユーザで動く。`appuser`のUIDおよびGIDは共に`1005`になっており、このユーザがマウントしたバックアップの出力ディレクトリに書き込めるようにディレクトリのパーミッションを設定しておかねばならない。

# 古いバックアップファイルの自動削除
このままだと古いバックアップファイルが無限に溜まっていくので、規定時間以上前のバックアップを自動で削除するようにする。このイメージにはバックアップ実行前もしくは後に実行するスクリプトを設定することができるので、その機能を活用する。

以下のシェルスクリプトを作成。

```sh:dbbackup/remove-old-backups.sh
#!/bin/bash
# Expect to be run as a pre-backup script of databack/mysql-backup docker image.
# Remove old backup files.

find $DB_DUMP_TARGET -type f -name "db_backup_*" -mtime +14 -delete
```

`find`コマンドで作成から（正確には最終変更から）15日=360時間 以上経ったバックアップファイルを削除している（何故か+1される）。

忘れずに実行権限を付与。

```sh:コンソール
$ chmod a+x dbbackup/remove-old-backups.sh
```

これをコンテナ内の`/scripts.d/pre-backup/`ディレクトリ内に配置することにより、バックアップ実行前に毎回実行させる。

```yaml:docker-compose.yaml
services:
  db: 同上
  
  dbbackup:
    image: databack/mysql-backup
    depends_on:
      - db
    environment:
      - DB_SERVER=db
      - DB_USER=root
      - DB_PASS=${MYSQL_ROOT_PASSWORD}
      - DB_DUMP_FREQ=1440
      - DB_DUMP_TARGET=/db
    volumes:
      - ./dbbackup/storage:/db
      - ./dbbackup/remove-old-backups.sh:/scripts.d/pre-backup/remove-old-backups.sh:ro
```

最終行でスクリプトを読み込み専用でマウントすることで配置している。

# リストア

バックアップしたファイルからリストアしたい場合の手順を説明する。

MySQLコンテナはDocker Composeで作られたネットワーク内にあるので、リストアする時も同じネットワークに入らなければならない。まずはそのネットワーク名を`docker network ls`で調べる。

```sh:コンソール
$ docker network ls
NETWORK ID     NAME                       DRIVER    SCOPE
a9a22d4ca2c4   bridge                     bridge    local
1616ec62a69d   host                       host      local
fe23f5a77f5a   none                       null      local
4823fecb1757   myproject_default          bridge    local
```

`NAME`列がネットワーク名であり、特に指定しなければプロジェクト名（`docker-compose.yaml`が配置されたの直上のディレクトリ名）の後ろに`_default`が付いた名前になる模様。ここでは最終行の`myproject_default`がネットワーク名。

リストアを`docker run`コマンドで実行する。調べたネットワーク名を`--network`オプションで指定し、環境変数`DB_RESTORE_TARGET`にバックアップファイルを指定して実行するとそのファイルからリストアされる。

```sh:コンソール
$ docker run \
  --network myproject_default \
  -e DB_SERVER=db -e DB_USER=root -e DB_PASS=${MYSQL_ROOT_PASSWORD} \
  -e DB_RESTORE_TARGET=/backup/db_backup_2022-03-30T07:25:21Z.tgz \
  -v $PWD/dbbackup/storage:/backup \
  databack/mysql-backup
```

5行目で`$PWD`を使用してカレントディレクトリに展開しているのは`-v`オプションでのマウント元のディレクトリは絶対パスでしか指定できないから。変数`${MYSQL_ROOT_PASSWORD}`はどこかで定義しておく。