Docker内のmysqlをバックアップしてリストア

2024/11/29に公開

はじめに

Dockerでmysqlを動かしていて、本番運用するにあたってバックアップとリストアの一連の手順をいろいろと勉強したので忘れないように残しておきます。

検証環境

ホストサーバーでバックアップを取って、バックアップサーバーからrsyncでとってくる…をやります。

  • ホストサーバー 192.168.2.21
  • バックアップサーバー 192.168.2.22

バージョンは以下のとおり

  • ubuntu 24.04.1
  • docker 27.3.1
  • mysql 8.4

物理バックアップと論理バックアップ

本題に入る前にちょっとお勉強。
mysql公式が丁寧にまとめておいてくれたので一部抜粋します。

物理バックアップ

  • 物理バックアップ方法は、変換なしでファイルのコピーのみを行うため、論理バックアップ方法よりも高速です。
  • 出力は論理バックアップの場合よりもコンパクトになります。
  • バックアップは、同一または類似のハードウェア特性を持つ他のマシンにのみ移植可能です。

InnoDBで動かしているのであれば.idbファイルのこと。

論理バックアップ

  • 論理バックアップは、MySQL サーバーにクエリを実行してデータベースの構造とコンテンツ情報を取得することによって実行されます。
  • 論理バックアップは、サーバーがデータベース情報にアクセスしてそれを論理形式に変換する必要があるため、物理バックアップよりも遅くなります。
  • 論理形式で保存されたバックアップはマシンに依存せず、移植性も高くなります。

mysqldumpなどで生成されるファイルのこと。

どっちもとっておけば安心でしょう。ということでどっちもとっていきます。

mysqldumpで論理バックアップを取得

mysqlpumpとかmysqlshとかもっと新しくてイケてるコマンドがあるようなんだけど、それらのコマンドを使用した記事は私の理解力を超えており扱い切れないと感じたので、mysqldumpコマンドを使用してバックアップを取っていきます。

my.cnfの編集

mysqldumpの実行に際して設定をいじくっておきます。

my.cnf
[mysqldump]
single-transaction # データの整合性を保つ
events # DBのイベントスケジューライベントを出力に含めます。
routines # DBのストアドルーチン (プロシージャーおよび関数) を出力に含めます。
triggers # DBの各テーブルのトリガーを出力に含めます。
skip-disable-keys # キーを無効にしない

mysqldumpを実行するシェルスクリプトを作成

/opt/scripts/mysqldump.sh
#!/bin/bash

# MYSQL_USERNAMEなどは別ファイルから取得
source .env

# 年、月、タイムスタンプをそれぞれ取得
YEAR=`date '+%Y'`
MONTH=`date '+%m'`
TIMESTAMP=`date '+%Y%m%d%H%M'`

# バックアップファイルの保存場所
BACKUP_DIR=/opt/backup/mysqldump

# バックアップファイルを保存するためのディレクトリを作成
mkdir -p ${BACKUP_DIR}/${YEAR}/${MONTH}

# コンテナID取得
CONTAINER_ID=$(docker ps -q --filter name=mysql)

# Docker内のmysqlに接続してバックアップをする部分(zstdでファイルの圧縮もしている
docker exec ${CONTAINER_ID} mysqldump -u${MYSQL_USERNAME} -p${MYSQL_PASSWORD} ${MYSQL_DATABASE} --all-databases | zstd > ${BACKUP_DIR}/${YEAR}/${MONTH}/${TIMESTAMP}.sql.zst

BACKUP_DIRなどはお好きな場所に変えてください。
バックアップファイルの保存場所やこのシェルスクリプトに実行ユーザーの権限を与えることも忘れずに。

mysqldumpのシェルスクリプトをcronで実行

crontab -u ubuntu -e
0,30 * * * * /bin/bash /opt/scripts/mysqldump.sh

0分と30分ごとに実行する場合は上記のとおり。

うまくいっている場合は以下のようなファイルが生成されています。

/home/ubuntu
$ ls /opt/backup/mysqldump/2024/11
202411291030.sql.zst # 10:30のデータ
202411291100.sql.zst # 11:00のデータ

rsyncを実行するシェルスクリプトを作成

rsyncを使ってバックアップサーバー(192.168.2.22)からホストサーバー(192.168.2.21)に接続⇒コピーします。
rsync実行時にホストサーバーのパスワードを入力しなければならないため、expectをインストールします。

ここからの作業はすべてバックアップサーバー(192.168.2.22)で行います。

/home/ubuntu
sudo apt install expect

インストールが完了したら、シェルスクリプトを作成しましょう。

/home/ubuntu/rsync.sh
#!/bin/bash

# PASSWORDは別ファイルから取得
source .env

expect -c "
  set timeout -1
  spawn rsync -av ubuntu@192.168.2.21:/opt/backup /home/ubuntu/
  expect \"password:\"
  sleep 1
  send \"${PASSWORD}\r\"
  expect eof
  exit
"

rsync -av ubuntu@192.168.2.21:/opt/backup /home/ubuntu/をご自身の値に変えてください。

なお、上記コマンドだと/opt/backup以下の作成と更新は反映されますが、削除は反映されません。

また、rsyncは873番ポートを使用するため開けておいてください。

rsyncのシェルスクリプトをcronで実行

バックアップサーバーに以下のcronを登録します。

crontab -u ubuntu -e
5,35 * * * * /bin/bash /home/ubuntu/rsync.sh

ホストサーバで0分と30分に実行されるので、バックアップサーバーでは5分遅れで実行するようにします。

正常に作動している場合、バックアップサーバーに以下のファイルがコピーされています。

/home/ubuntu
$ ls backup/mysqldump/2024/11
202411291030.sql.zst # 10:30のデータ
202411291100.sql.zst # 11:00のデータ

mysqldumpからリストア

生成されたmysqldumpからDBリストアするコマンドは以下のとおり。

/home/ubuntu
zstdcat <ファイルパス>/<ファイル名>.sql.zst | docker exec -i コンテナ名 mysql -uDBユーザ名 -pDBパスワード

.idbで物理バックアップを取得

リストアにあたって必要になるのはコンテナ内の以下のファイルです。

  • /var/lib/mysql/some_database/ 復元対象のデータベース
  • /var/lib/mysql/ibdata1

/var/lib/mysql以下をボリュームとしてホストサーバーに保存します。

compose.yaml
services:
  ...
  mysql:
    ...
    volumes:
      - mysql_data:/var/lib/mysql
    ...
volumes:
  mysql_data:

mysql_dataがホストサーバーのどこにあるかはdocker volume inspectコマンドでわかります。

docker volume inspect someapp_mysql_data
[
    {
        "CreatedAt": "2024-11-28T10:06:00+09:00",
        "Driver": "local",
        "Labels": {
            "com.docker.stack.namespace": "someapp"
        },
        "Mountpoint": "/var/lib/docker/volumes/someapp_mysql_data/_data", # ここにある
        "Name": "someapp_mysql_data",
        "Options": null,
        "Scope": "local"
    }
]

mysqlデータの圧縮を実行するシェルスクリプトを作成

/opt/scripts/mysqldata.sh
#!/bin/bash

# ROOT_PASSWORDなどは別ファイルから取得
source .env

# Mountpointを指定
VOLUME_PATH=/var/lib/docker/volumes/someapp_mysql_data/_data

# 年、月、タイムスタンプをそれぞれ取得
YEAR=`date '+%Y'`
MONTH=`date '+%m'`
TIMESTAMP=`date '+%Y%m%d%H%M'`

# バックアップファイルの保存場所
BACKUP_DIR=/opt/backup/mysqldata

# バックアップファイルを保存するためのディレクトリを作成
mkdir -p ${BACKUP_DIR}/${YEAR}/${MONTH}

# 圧縮して保存
echo ${ROOT_PASSWORD} | sudo -S tar -acf ${BACKUP_DIR}/${YEAR}/${MONTH}/${TIMESTAMP}.tar.zst ${VOLUME_PATH}

なお実行時にroot権限が必要であるため標準入力からパスワードを渡しています。

以降は論理バックアップと同様の流れになります。

シェルスクリプトをcronで実行

crontab -u ubuntu -e
0,30 * * * * /bin/bash /opt/scripts/mysqldata.sh

0分と30分ごとに実行する場合は上記のとおり。

うまくいっている場合は以下のようなファイルが生成されています。

/home/ubuntu
$ ls /opt/backup/mysqldata/2024/11
202411291030.sql.zst # 10:30のデータ
202411291100.sql.zst # 11:00のデータ

rsyncを実行するシェルスクリプトを作成

/opt/backup配下に置いたため、自動でバックアップが取得されます。
正常に作動している場合、バックアップサーバーに以下のファイルがコピーされています。

/home/ubuntu
$ ls backup/mysqldata/2024/11
202411291030.sql.zst # 10:30のデータ
202411291100.sql.zst # 11:00のデータ

.idbからリストア

前述の/var/lib/docker/volumes/someapp_mysql_data/_dataにバックアップで保存したデータを展開する

参考

参考にさせていただきました。ありがとうございました。
https://zenn.dev/oco/articles/8d280b157bfebf
https://www.itmedia.co.jp/enterprise/articles/1511/11/news017_4.html
https://zenn.dev/takaha4k/articles/3460fe12bb4d5d
https://zenn.dev/tkr923/articles/b07db0211b81e6
https://www.netassist.ne.jp/techblog/31826/
https://webprog-man.com/post-20/
https://qiita.com/TakSus/items/118cd29a85fd4447e8d3

Discussion