Skip to content

Latest commit

 

History

History
414 lines (302 loc) · 18.8 KB

File metadata and controls

414 lines (302 loc) · 18.8 KB

スナップショットガイド

devbase のスナップショット機能は、永続化ボリュームを増分バックアップし、世代管理と復元を提供します。 /work 配下のプロジェクト作業ファイルはバックアップ対象外なので、重要なファイルは Git に push するか別途バックアップを取ってください。

対象は次の 2 本です。

ボリューム コンテナ内 内容
devbase_home_ubuntu /persistent/ai 全コンテナ共通の AI 資産・共有ファイル
devbase_home_{group} /persistent/group アカウントグループ単位の認証・会話ログ・gcloud / gws の設定

{group} は実行時の DEVBASE_ACCOUNT_GROUP の解決結果です(未設定なら default)。 プロジェクトディレクトリで実行すればそのプロジェクトのグループが、devbase ルートで実行すれば グローバル env の値(無ければ default)が対象になります。詳細は コンテナ運用ガイド の「アカウントグループ」を参照してください。

仕組み

増分バックアップ

devbase のスナップショットは GNU tar の --listed-incremental オプションを使用した増分バックアップ方式を採用しています。

graph LR
    A[フルバックアップ<br/>full.tar.zst] --> B[差分 1<br/>incr-001.tar.zst]
    B --> C[差分 2<br/>incr-002.tar.zst]
    C --> D[差分 3<br/>incr-003.tar.zst]

    style A fill:#e8e8f4
    style B fill:#e8f4e8
    style C fill:#e8f4e8
    style D fill:#e8f4e8
Loading
  • フルバックアップ: 対象ボリューム 2 本の全体をアーカイブ(アーカイブ内では ai/group/ に分かれます)
  • 差分バックアップ: 前回からの変更分のみをアーカイブ
  • 圧縮: zstd -1 -T0(圧縮レベル 1、全 CPU コア使用)で高速圧縮

軽量専用イメージ

スナップショット操作には専用の軽量コンテナイメージ devbase-snapshot を使用します。

項目
イメージ名 devbase-snapshot
サイズ 約 80MB
含まれるツール zstd のみ
ビルドタイミング 初回のスナップショット操作時に自動ビルド

プロジェクトのコンテナを起動せずにバックアップ・復元を実行できるため、ダウンタイムが発生しません。

世代管理

設定パラメータ

パラメータ デフォルト値 説明
DEFAULT_MAX_INCREMENTALS 10 1 世代あたりの最大差分バックアップ数
DEFAULT_MAX_GENERATIONS 3 アカウントグループ(系列)ごとに保持する世代数
全体の上限 DEFAULT_MAX_GENERATIONS × 39 すべてのグループを合わせて保持する世代数の上限

デフォルト設定では、差分バックアップが 10 回溜まるとフルバックアップが新たに作成され、グループごとに最大 3 世代、全体で最大 9 世代が保持されます。

系列

世代は、控えるボリュームの組(共通ボリューム devbase_home_ubuntu とグループの ボリューム devbase_home_<group>)ごとの系列に分かれます。アカウントグループ 1 つにつき 系列が 1 つできます。アカウントグループ分離より前に作られた世代(共通ボリュームだけを控えたもの)は、 まとめて 1 つの系列になります。

  • 差分は、起動したグループの系列の最新の世代へ積みます。別のグループのプロジェクトを 起動した後に戻ってきても、フルバックアップを取り直しません
  • 保持の数は系列ごとに数えます。グループを切り替えても、他のグループの世代が保持の枠から押し出されません
  • 全体の上限を超えると、系列をまたいで最も古い世代から削除します
  • 各系列の最新の世代は自動では削除されません。 次の差分の積み先になるためです。使わなくなった グループや、アカウントグループ分離より前の系列の最新の世代も残ります。不要になったら devbase snapshot delete <名前> で削除してください。系列の数が全体の上限を超えているときは、 上限を超えたまま残し、警告を出します

積み先・最小間隔・ローテーションの規則と世代の場所の検証の仕様は スナップショットの世代の系列 にあります。

世代の概念

graph TD
    subgraph 系列 default
        subgraph 世代 1(最古)
            A1[full.tar.zst]
            A2[incr-001.tar.zst]
            A3[incr-002.tar.zst]
        end
        subgraph 世代 2(系列の最新)
            B1[full.tar.zst]
            B2[incr-001.tar.zst]
        end
    end
    subgraph 系列 with
        subgraph 世代 3(系列の最新)
            C1[full.tar.zst]
        end
    end
Loading
  • 1 つの世代は 1 つのフルバックアップと 0 個以上の差分バックアップで構成される
  • 系列の最新の世代の差分バックアップが DEFAULT_MAX_INCREMENTALS 回に達すると、その系列に新しい世代が開始される
  • 系列ごとに DEFAULT_MAX_GENERATIONS を超えた古い世代と、全体の上限を超えた古い世代は自動的に削除される

自動実行

スナップショットはコンテナのライフサイクルに連動して自動実行されます。

devbase up 時の動作

flowchart TD
    A[devbase up 実行] --> S{起動するグループの系列に<br/>世代がある?}
    S -->|いいえ| C[新世代のフルバックアップを作成]
    S -->|はい| B{系列の最新の世代の差分バックアップが<br/>DEFAULT_MAX_INCREMENTALS 回以上?}
    B -->|はい| C
    B -->|いいえ| D[系列の最新の世代に差分バックアップを追加]
    C --> R[ローテーション]
    D --> R
    R --> E[コンテナを起動]
Loading

起動時の出力には、扱った系列のグループ名が出ます。新しい世代を作るときは、その理由 (系列に世代が無い / 差分が上限に達した)を 1 行出します。

[0/6] スナップショットを差分更新中: 20260920-212546 (グループ with)

直近のスナップショットから DEVBASE_SNAPSHOT_MIN_INTERVAL_MINUTES(既定 60 分)以内なら スナップショットを飛ばします。この間隔も系列ごとに判定するため、default のプロジェクトを 起動した直後に with のプロジェクトを起動しても、with の系列は控えます。

devbase down 時の動作

flowchart TD
    A[devbase down 実行] --> B[コンテナを停止・削除]
    B --> C[系列ごとに DEFAULT_MAX_GENERATIONS を<br/>超えた古い世代を削除]
    C --> T{全体の世代数 > 全体の上限?}
    T -->|はい| D[系列の最新ではない世代のうち<br/>最も古いものを削除]
    D --> T
    T -->|いいえ| E[完了]
Loading

バックアップデータ構造

スナップショットは ${DEVBASE_ROOT}/backups/ ディレクトリ(devbase ルート直下)に保存され、全プロジェクトで共通の場所に集約されます。

backups/
├── snapshot.yml                    # スナップショット全体のメタデータ
├── 20260220-103000/                # タイムスタンプ名の世代
│   ├── meta.yml                    # 世代のメタデータ
│   ├── full.tar.zst               # フルバックアップ
│   ├── incr-001.tar.zst           # 差分バックアップ 1
│   └── incr-002.tar.zst           # 差分バックアップ 2
└── before-upgrade/                 # 名前付きスナップショット
    ├── meta.yml
    └── full.tar.zst

ファイルの説明

ファイル 内容
snapshot.yml 全世代のインデックス情報(対象ボリューム名を含む)
meta.yml 世代ごとの作成日時、バックアップポイント数、サイズ、対象ボリューム
full.tar.zst フルバックアップアーカイブ
incr-NNN.tar.zst 差分バックアップアーカイブ(NNN は連番)

コマンド詳細

スナップショットの作成

自動命名(タイムスタンプ)

devbase snapshot create

現在の世代に差分バックアップを追加します。世代が存在しない場合はフルバックアップを作成します。

名前付きスナップショット

devbase snapshot create --name before-upgrade

指定した名前でスナップショットを作成します。重要な変更の前に手動で作成する場合に便利です。

フルバックアップの強制作成

devbase snapshot create --full

差分ではなく、強制的にフルバックアップを作成します。

# 名前付きフルバックアップ
devbase snapshot create --name before-migration --full

スナップショットの一覧

devbase snapshot list

出力例:

名前                     作成日時                    差分数        サイズ  対象ボリューム
------------------------------------------------------------------------------------------
20260218-080000          2026-02-18 08:00:00           3       1.2GB  devbase_home_ubuntu
20260220-103000          2026-02-20 10:30:00           2     850.0MB  devbase_home_ubuntu, devbase_home_default
before-upgrade           2026-02-21 14:00:00           1       2.1GB  devbase_home_ubuntu, devbase_home_kkg

「対象ボリューム」が devbase_home_ubuntu だけの世代は、アカウントグループ分離よりに 作られた世代です。そのまま共通ボリュームへ復元できます。

対象ボリュームが変わったとき

アカウントグループを切り替えたり、分離前の環境から更新したりすると、対象ボリュームの構成が 変わります。devbase は、切り替えた先のグループの系列(系列)の最新の世代へ差分を 積みます。新しい世代を作るのは、その系列にまだ世代が無いときだけです。 構成の違う世代へは 差分を積みません。差分状態ファイル(snapshot.snar)は別のレイアウトを記録しているため、 そこへ差分を積むと全ファイルが移動したものとして扱われ、差分が壊れるからです。 系列を分けることで、どの世代もそのまま復元できます。

構成の違う世代を明示的に指定して差分を作ろうとした場合は、理由を示して中断します。

$ devbase snapshot create --name 20260218-080000
スナップショット操作に失敗: スナップショット '20260218-080000' は別のボリューム構成
(devbase_home_ubuntu) で作られています。現在の対象は devbase_home_ubuntu,
devbase_home_default です。新しい世代を作成してください (devbase snapshot create)

スナップショットからの復元

最新の状態に復元

devbase snapshot restore 20260220-103000

指定した世代のフルバックアップと全差分バックアップを順に適用し、最新の状態に復元します。

特定の時点まで復元

devbase snapshot restore 20260220-103000 --point 1

フルバックアップ(ポイント 0)と差分バックアップ 1(ポイント 1)まで適用します。ポイント 2 以降の変更は適用されません。

graph LR
    A["ポイント 0<br/>full.tar.zst<br/>(適用)"] --> B["ポイント 1<br/>incr-001.tar.zst<br/>(適用)"]
    B --> C["ポイント 2<br/>incr-002.tar.zst<br/>(スキップ)"]

    style A fill:#e8f4e8
    style B fill:#e8f4e8
    style C fill:#f4e8e8
Loading

復元の安全性

復元を実行する前に、現在の対象ボリュームの状態が pre-restore-<timestamp> という名前で自動バックアップされます。

# 復元前に自動作成されるバックアップ
# backups/pre-restore-20260221-150000/
#   ├── meta.yml
#   └── full.tar.zst

Note: 復元を元に戻したい場合は、この自動バックアップから再度復元できます。

# 復元を元に戻す
devbase snapshot restore pre-restore-20260221-150000

復元中に出る rename の警告

差分の適用中に、次のような警告が出ることがあります。復元は続行され、内容も正しく復元されます。

WARNING incr-002.tar.zst の展開で tar が rename に失敗しました。GNU tar の incremental が
inode 番号の再利用でディレクトリの rename を誤検出したものとみなし、復元を続けます:
tar: Cannot rename './ai/.claude/plugins/cache/foo' to './ai/.claude/plugins/cache/bar': Directory not empty

これは GNU tar の増分バックアップの仕組みに由来します。tar はディレクトリを inode 番号で追跡して「名前の変更」を検出しますが、~/.claude/plugins/cache/ のように ディレクトリごと作り直される場所では、削除されたディレクトリの inode 番号が新しい ディレクトリに再利用されます。すると tar は無関係なディレクトリを「名前が変わった」と 誤検出し、復元時にその名前変更を実行しようとして失敗します。

tar は名前変更に失敗しても展開そのものは最後まで行うため、devbase はこの失敗だけを 警告として扱い、次の差分へ進みます。警告が出ても対応は不要です。

中身の欠落を検知したとき

上の警告とは別に、次の警告が出た場合は対応が必要です

WARNING 復元後、次のディレクトリが空のままです。GNU tar が記録した rename を適用できなかった
ため、中身が復元されていない可能性があります。利用者が実際に mv したディレクトリであれば、
そのスナップショットからは中身を復元できません:
./ai/.claude/some-renamed-dir

devbase は復元の最後に、飲み込んだ rename の宛先を検査します。判定は次のとおりです。

宛先の状態 意味 対応
存在しない / 中身がある 偽の rename。欠落なし 不要
存在するが空のまま 正当な rename を適用できなかった疑い 内容を確認する

偽の rename の宛先は、そのディレクトリ自身が新しく作られたものなので、アーカイブから中身が 展開されて空にはなりません。一方、利用者が実際に mv したディレクトリは、差分アーカイブに ディレクトリのエントリしか入らない(中身は rename でしか移動しない)ため、rename を 適用できないと空のまま残ります。

このディレクトリの中身は、そのスナップショットからは復元できません。より古い世代の スナップショット(mv する前のもの)から取り出してください。

復元が失敗したとき

rename 以外の理由で失敗した場合、復元はその場で止まります。このとき 対象ボリュームは途中まで書き換わっている可能性があります。エラーには、どのアーカイブの 展開中に失敗したかと、元に戻す手順が出ます。

復元に失敗しました (incr-002.tar.zst の展開中)。対象ボリュームは途中まで
書き換わっている可能性があります。復元前の状態は 'pre-restore-20260221-150000' に退避してあります。
元に戻すには devbase snapshot restore pre-restore-20260221-150000 を実行してください。

案内のとおり pre-restore-<timestamp> から復元すれば、復元を始める前の状態に戻せます。

スナップショットのコピー

devbase snapshot copy 20260220-103000 important-milestone

既存のスナップショットを別名でコピーします。ローテーションから保護したい重要なスナップショットに使用します。

スナップショットの削除

devbase snapshot delete 20260218-080000

指定したスナップショットを削除します。

Warning: 削除は取り消せません。重要なスナップショットは事前に snapshot copy でバックアップしてください。

手動ローテーション

# デフォルトの保持数で実行
devbase snapshot rotate

# グループごとに保持する世代数を指定
devbase snapshot rotate --keep 5

# 全体の上限も指定
devbase snapshot rotate --keep 5 --max-total 10

--keep N はアカウントグループ(系列)ごとに残す世代数です。各系列で古い世代から削除し、 残りの合計が --max-total M(省略時は N × 3)を超えていれば、系列をまたいで古い世代から 削除します。各系列の最新の世代は削除しません。名前付きスナップショット(--name で作成したもの)や copy で作った世代も、対象ボリュームの組でいずれかの系列に入り、同じ規則で削除の対象になります。

--keep--max-total は、その 1 回の実行だけに効きます。 値は保存されず、 devbase up / devbase down の自動ローテーションは既定(グループごとに 3・全体で 9)で動きます。

運用のベストプラクティス

  1. 重要な変更の前には名前付きスナップショットを作成する

    devbase snapshot create --name before-db-migration --full
  2. 名前付きスナップショットはローテーションから保護される -- 自動削除されないため、不要になったら手動で削除する

  3. 復元は --point N で段階的に確認する -- 全差分適用の前に特定時点を確認

  4. バックアップ容量を定期的に確認する

    devbase snapshot list
    du -sh projects/<project>/backups/
  5. 長期保持したい世代は backups/ の外へ複製する

    devbase snapshot rotate --keep の指定はその 1 回だけに効き、次の devbase up / devbase down の自動ローテーションで既定の数まで削除されます。snapshot copy で作った世代もローテーションの 対象です。長く残したい世代は、世代のディレクトリを backups/ の外へ複製してください。