セルフホストアプリのデプロイでDBデータが消えた(ように見えた)話
OSSのメモアプリ「memos」をOracle Cloudにセルフホストして運用しています。CI/CDパイプラインを構築する過程で、memosを起動したら全メモが消えていました。結論から言うとデータは無事だったのですが、原因と対処、そこから整えたバックアップ体制について書きます。
前回の記事はこちらです。
全メモが消えた
GitHub Actionsでmemosの自動デプロイ環境を構築していたときのことです。新しいdeploy.ymlでコンテナを起動したら、memosが初期セットアップ画面で立ち上がりました。今まで書いたメモが1件も表示されない状態です。
原因: ボリュームマウントのホスト側パスが違っていた
落ち着いて調べたら、データは消えていませんでした。見えなくなっていただけです。
原因はDockerのボリュームマウントパスの不一致でした。
手動でmemosをセットアップしたときのコマンド:
docker run -d \
-v /home/ubuntu/memos/data/:/var/opt/memos \
ghcr.io/usememos/memos:latest
CI/CDのdeploy.ymlで書いたコマンド:
docker run -d \
-v /var/opt/memos:/var/opt/memos \
ghcr.io/usememos/memos:latest
ホスト側のパスが /home/ubuntu/memos/data/ から /var/opt/memos に変わっています。memosのコンテナ内では同じ /var/opt/memos を参照しますが、ホスト側で別のディレクトリがマウントされたため、新しい空のディレクトリが使われました。
SQLiteのDBファイル(memos_prod.db)は旧パスの /home/ubuntu/memos/data/ にそのまま残っていました。
なぜこれが起きやすいか
Dockerの-vオプションは、ホスト側のパスが存在しなければ自動で空のディレクトリを作ります。エラーにならないのが厄介です。パスを間違えても、コンテナは何事もなかったように起動して、空のDBで動き始めます。
手動でセットアップした環境をCI/CDに移行するとき、この手のズレが起きやすいです。手動のときに使ったパスと、自動化スクリプトに書いたパスが一致しているか、意外と確認を忘れます。
復旧
最初は「旧パスのDBファイルをまるごとコピーすれば終わり」と思ったのですが、そう簡単にはいきませんでした。新しいパスで起動したmemosが既に空のDBを作っていて、単純にファイルを上書きするとCI/CD側で生成された初期データとの整合性が壊れる可能性がありました。
結局やったのは、ローカルに持っていたDBのバックアップとクラウド側のDBを比較して、欠落しているメモを特定し、直接SQLで挿入するという泥臭い作業でした。
# SSHでサーバーに接続してsqlite3をインストール
ssh ubuntu@<サーバーIP>
sudo apt install -y sqlite3
# DB権限を変更して書き込み可能にする
sudo chmod 666 /home/ubuntu/memos/data/memos_prod.db
# 欠落しているメモをINSERTで復元
sqlite3 /home/ubuntu/memos/data/memos_prod.db \
"INSERT INTO memo (id, uid, creator_id, ...) VALUES (...);"
# コンテナ再起動
docker restart memos
# 権限を戻す
sudo chmod 644 /home/ubuntu/memos/data/memos_prod.db
メモ数件の復元だったので何とかなりましたが、これが数百件だったらかなり厳しかったと思います。復旧できて心底安堵しました。
その後、ボリュームマウントのパスを統一して、deploy.ymlと実際のDBファイルの場所が一致するようにしました。
その後に整えたバックアップ体制
今回はデータが残っていたから良かったものの、本当に消えていたら終わりです。この経験を踏まえて、バックアップの仕組みを入れました。
memosはSQLiteなので、バックアップはDBファイル1つをコピーするだけで済みます。RDBMSのダンプと比べると手軽です。
CI/CDのデプロイフローに、デプロイ前のDBバックアップを組み込みました。GitHub Actionsのdeployジョブで、新しいコンテナを起動する前にDBファイルをコピーしておき、デプロイ失敗時にはリストアできるようにしています。
バックアップ時のポイントとして、GitHub ActionsのランナーにはSQLite CLIが入っていないため、alpine/sqliteのDockerイメージ経由でDB操作を実行しています。コンテナ内のファイル権限も合わせる必要があり、MEMOS_UID/GIDで実行ユーザーを統一して権限問題を解消しました。
まとめ
Dockerのボリュームマウントは、ホスト側パスが間違っていてもエラーが出ません。空のディレクトリが静かに作られて、既存のデータが見えなくなります。
手動デプロイからCI/CDに移行するとき、ボリュームマウントのパスが一致しているかは真っ先に確認すべきポイントです。自分の場合、データは消えていなかったので復旧できましたが、運が悪ければそのまま新しいDBで上書きされていた可能性もあります。
セルフホストでデータを守るなら、バックアップ体制はデプロイ自動化より先に整えるべきだと実感しました。SQLiteならファイル1つコピーするだけなので、仕組みを入れるハードルは低いです。
Discussion