はじめに

compute-server 側で rootless Podman を中心にサービスを動かしていると、いちばん怖いのは「壊れたときにどう戻すか」が曖昧なまま構成だけ増えていくことだった。今回はその状態を避けるために、OS 再インストールや NVMe 初期化を前提にしても、Podman 構成とデータを短時間で戻せるバックアップ/復旧方針を固めた。

狙いは、派手なストレージ機能を増やすことではなく、復旧手順を読み返したときに迷わないことだった。ZFS は使わず、ext4/XFS + tar.zst + rclone に寄せて、何を保存して何を再生成するかを明確にしている。

背景・動機

自分の compute-server では、Postgres、Trino、Dagster のような永続データを持つワークロードに加えて、rootless Podman の user systemd や namespace 設定まで含めて環境として成立している。普段は普通に動いていても、OS を入れ直す場面になると、単に /mnt/data を戻すだけでは足りない。

特に rootless Podman は、データ本体だけでなく、ユーザー環境、/etc/subuid/etc/subgidloginctl enable-linger のような前提が揃って初めて素直に復帰する。ここを感覚で復旧すると事故るので、「どこまでをバックアップし、どこから先は復元時に再実行するか」を先に固定した。

大きな blob 群は rclone copy で並列転送し、symlink を含む snapshotsrefsrsync -aH でそのまま持つ、という考え方を取っている。今回の compute-server バックアップでも、この役割分担がそのまま活きる。

目的

この設計で達成したいことは明確で、OS 再インストールや NVMe 初期化後に Podman(rootless) 構成とデータを短時間で完全再現することだった。

  • ZFS を使わず、ext4/XFS + rclone + tar.zst で再現性と単純さを両立する
  • 復旧時に必要な前提条件を、UID/GID、subuid/subgidlinger まで含めて固定する
  • バックアップ対象と非対象をはっきり分け、余計なものを抱え込まない

ディレクトリ構成

まず、どこに何があるかを復旧目線で整理した。

パス役割
/mnt/dataPodman各アプリの実体・永続データ(Postgres, Trino, Dagster, etc)
/opt/{app}構成・compose・systemd定義(bind mount先)
/usr/localTrino/dbt/Dagster設定などローカル構成
/etcシステム設定(ネットワーク、systemd、podman設定など)
/home/ksh3rootless Podmanユーザー環境、systemd-user設定
/srv/backupバックアップ生成先(zstd圧縮tar, pkglist等)

ここで重要なのは、データだけではなく「rootless Podman が成り立つ周辺設定」も復旧対象として見ていることだ。/home/ksh3/etc を外すと、コンテナデータは残っていても user systemd や namespace 側でつまずきやすい。

バックアップ方針

システム構成バックアップ(毎日)

毎日取るのは、OS 再構築後のベースに必要な設定群だ。対象は /usr/local, /etc, /home/ksh3, /srv/backup/pkglist.txt, /etc/apt/sources.list.d/ にしている。

  sudo dpkg --get-selections | awk '!/deinstall|purge/ {print $1}' \
  > /srv/backup/pkglist.txt

tar --use-compress-program="zstd -T0 -19" \
    -cf /srv/backup/system-$(date +%Y-%m-%d_%H-%M).tar.zst \
    /etc /usr/local /home/ksh3 \
    /srv/backup/pkglist.txt /etc/apt/sources.list.d \
    --exclude='/home/ksh3/.cache' --exclude='/home/ksh3/.local/share/Trash'
  

このバックアップでは、再インストール後にパッケージの足りないものを洗い出しやすいように pkglist.txt も含めている。逆に、/home/ksh3/.cache や Trash のような再生成可能で容量だけ食うものは除外した。

圧縮レベルを zstd -T0 -19 にしているのは、構成バックアップのサイズを抑えつつ CPU 並列を使えるからだ。毎日回す対象は比較的サイズが読めるので、このくらい重めでも扱いやすい。

データ領域バックアップ(週次または手動)

永続データは /mnt/data に集めていて、こちらは週次または必要時に取る。

  tar --use-compress-program="zstd -T0 -5" \
    -cf /srv/backup/mnt-data-$(date +%Y-%m-%d).tar.zst -C /mnt data
  

データ側は容量が大きくなりやすいので、圧縮レベルは -5 に落としている。ここで欲しいのは最高圧縮率ではなく、現実的な時間でアーカイブが終わることだ。

/mnt/data を独立しているのは復旧の都合でも意味がある。設定系とデータ系が別アーカイブになっていると、まず OS と設定を戻してからデータを流し込む順序を取りやすい。

転送先(storage-server)

生成したアーカイブは storage-server へ送る。

  rclone copy /srv/backup/*.tar.zst storage:/srv/backups/compute/ --progress
  

ここは単純な rclone copy にしている。理由は、送りたいものが圧縮済みアーカイブで、symlink の構造を保存したいわけではないからだ。

ログは Prometheus / Loki に集中管理している前提なので、root 側のログはバックアップから外している。障害調査に必要なログが別系統で取れているなら、バックアップ対象を絞った方が復旧系は読みやすくなる。

復旧手順(再インストール後)

最小環境整備

復旧直後にまず必要なのは、アーカイブを展開し、リモートから取ってきて、Podman を起動するための最小ツール群だ。

  sudo apt update
sudo apt install -y zstd rclone podman podman-compose
  

これで zstd 展開、rclone 転送、podman 起動の基本が揃う。

バックアップ取得

次に storage-server からローカルへアーカイブを戻す。

  rclone copy storage:/srv/backups/compute/latest/ /tmp/restore/
sudo tar -I zstd -xf /tmp/restore/system-YYYY-MM-DD.tar.zst -C /
sudo tar -I zstd -xf /tmp/restore/mnt-data-YYYY-MM-DD.tar.zst -C /
  

latest というパスを切っているので、今後はこの別名をどう更新するかまで運用に落とす必要がある。ただ、復旧フローとしては「最新版をまず取る」入口があるだけでかなり迷いにくくなる。

ユーザーと権限復旧

rootless Podman で最重要なのはここだ。ユーザー、所有権、namespace 設定、linger を戻さないと、データがあっても起動系が噛み合わない。

  sudo useradd -m -u 1000 -s /bin/bash ksh3
sudo chown -R ksh3:ksh3 /home/ksh3 /mnt/data /opt /usr/local
echo "ksh3:100000:65536" | sudo tee /etc/subuid /etc/subgid
loginctl enable-linger ksh3
  

UID を 1000 に固定しているのは、rootless Podman の永続データ整合性を崩さないためだ。ここがずれると、権限や namespace の見え方がずれて復旧時間が一気に伸びる。

Podman環境再構築

最後に Podman 側を再起動できる状態まで戻す。

  sudo -u ksh3 podman system migrate
sudo -u ksh3 systemctl --user daemon-reload
sudo -u ksh3 systemctl --user enable --now pod-*.service
# または:
cd /opt/containers/compose
for d in *; do [ -d "$d" ] && cd "$d" && podman-compose up -d && cd ..; done
  

この手順を分けて書いておくと、user systemd で戻すのか、podman-compose up -d を使うのかを状況に応じて選べる。どちらにしても、前段の UID/GID と subuid/subgid が揃っていることが前提になる。

補足ポイント

復旧手順の中で、あとから忘れやすいポイントも先に固定しておいた。

項目内容
UID/GID固定rootless Podmanの永続データ整合性のため、同一UID(1000)必須
/etc/subuid / /etc/subgidrootless namespaceに必須。バックアップ+復元要
loginctl enable-lingeruser systemdの自動起動を再設定
ネットワークPodman再構築時に自動生成されるのでバックアップ不要
SSH鍵セキュリティ上除外(再生成)

特に「ネットワークはバックアップ不要」と明示したのは大きい。全部を保存しようとすると設計が重くなるが、再生成されるものは再生成で割り切った方が、復旧設計としては強い。

SSH 鍵も同じで、便利だから一緒に固めるのではなく、セキュリティ上は切り離した方がよい。ここを除外対象として最初から書いておくと、あとで迷わない。

結果

この方針で残したかった結論は次の通りだ。

  • バックアップ対象は /usr/local, /etc, /home/ksh3, /srv/backup/pkglist.txt, /etc/apt/sources.list.d, /mnt/data
  • 形式は tar.zst + rclone
  • 鍵類は除外して安全性を確保する
  • 再現条件は同一 UID/GID、subuid/subgid の復元、linger の有効化
  • 復旧後は /opt/containers/compose から podman-compose up、または user systemd で再稼働できる

この構成なら、OS を入れ直しても 20-30 分で完全復旧を狙える。重要なのは、速さそのものよりも「何を戻せば起動するか」が明文化されていることだ。

今後の改善

まだ詰める余地はある。

  • storage:/srv/backups/compute/latest/ をどう更新するかを決めて、世代管理と保持期間を明文化する
  • symlink を含む資産やモデル群では rclonersync をどう使い分けるかを、backup 系メモとも接続して整理する
  • 復旧後に実行する smoke test を用意し、Podman サービス群が正しく戻ったかを機械的に確認できるようにする

いまの設計は、まず壊れても戻せることを優先した最小構成としては十分に実用的だ。ここから先は、復旧速度そのものより、運用の自動化と世代管理の明確化を積み増していく段階だと思っている。