Skip to content

feat(recovery): PV の戻しを pv.sh restore-all の 1 コマンドにする - #467

Merged
5ym merged 4 commits into
mainfrom
feat/restore-all
Oct 20, 2026
Merged

5ym merged 4 commits into
mainfrom
feat/restore-all

Conversation

@5ym

@5ym 5ym commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

変更

  • recovery/pv.sh に plan / restore(namespace 単位)と plan-all / restore-all(全部)を足した
    • 最新のスナップショットを経路ごとに引き、経路の形で操作を決める(/data/<pvc> → folder、/<ns>-<app>.db → sqlite、/<ns>-postgres.sql → pgload、netbird / matrix の tar)
    • namespace ごとに stop → folder → sqlite → tar → pgload → start。pgload のときは deploy/postgres を残し、ユーザーと DB は plan の時点で deploy/postgres の env から決める
    • restore-all は argo-pause → Forgejo → argo-resume して ApplicationSet のエラーが消えるのを待つ → argo-pause → 残り → argo-resume
    • 表に無い経路、namespace や PVC が無いものは plan の時点で止まる(黙って飛ばさない)。restore-all は戻し方の無い経路を Forgejo の前に、未作成の namespace / PVC は Forgejo の後に全部の plan が通るまで待って(10 分)、どこも上書きする前に止まる
  • recovery/README.md の 8 を plan-all → restore-all の 2 行にし、経路と操作の表を新しい判定に合わせた

建て直しの PV の戻しが、namespace ごとに 4〜6 コマンド × 14(約 60〜80)から 2 コマンドになる。

確認

  • pv.sh - plan-all を本番の restic とクラスタに対して流した(読むだけ)。forgejo と 13 の namespace の判定が README の表どおり(denpa は folder → sqlite の順、blog は yosegaki-db、netbird / matrix の tar も)
  • restore 自体は本番では流していない(上書きになるので)。次の建て直しで最初に確かめる、と README に書いてある

🤖 Generated with Claude Code

https://claude.ai/code/session_01NN4TmSXHrMP8dkTbtkjfz1

…V の戻しを 1 コマンドにする

スナップショットの経路の形(/data/<pvc>・/<ns>-<app>.db・/<ns>-postgres.sql・netbird / matrix の tar)で
操作を決め、namespace ごとに「止める → 戻す → 起こす」を通しでやる。restore-all は Forgejo を先に戻し、
ApplicationSet のエラーが消えるのを待ってから残りを回す(README の 8 そのもの)。表に無い経路は plan で止める。
plan-all は本番の restic と突き合わせ、全 namespace の判定が README の表どおりになるのを確かめた。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NN4TmSXHrMP8dkTbtkjfz1
@claude

claude Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Claude finished @5ym's task in 46s —— View job


レビュー結果(recovery/pv.sh / recovery/README.md)

全体の構成(経路の形で操作を決める、止める → folder → sqlite → tar → pgload → 起動の順、表に無い経路は ERROR で止める)は筋が通っています。set -eu 下でのパイプや $(plan) の exit の伝わり方も、読んだ範囲では問題ありません。以下は確認したい点です。restore は本番で未実行とのことなので、実行して確かめたわけではなく、コードを読んだ範囲での指摘です。

1. restore-all の 2 周目で PVC / namespace がまだ無く、黙って飛ばす可能性(要確認)

wait_appsets が待つのは ApplicationSet の ErrorOccurred が消えるまでです。その直後に argo-pause するので、Application が実際に sync されて namespace と PVC ができているかは保証されません。

  • hosts() は namespace が無い host を # 飛ばす で黙って落とします。
  • plan() は /data/* の PVC が無いと同じく飛ばします。これは「やめたアプリの残り」と、「まだ PVC ができていないだけ」を区別できません。

sqlite は PVC が無いと ERROR で止まるので、folder だけが黙って飛ばす非対称になっています。この場合は PV が空のままアプリが起動します。

対策案は次のどちらかです。

  • wait_appsets のあとに、restic に host がある namespace と、そこで期待する PVC が揃うまで待つ。
  • 飛ばした項目を restore-all の最後にまとめて出す。

2. restore-all が事前に全体の plan を通さない

plan が表に無い経路で止まるのは restore の中です。そのため途中の namespace で止まったとき、それまでの namespace は上書き済みで、Argo は pause のままになります。restore-all の先頭(restic_env の直後)で、全 host の plan を先に流して失敗したら何も触らずに止める方が安全です。

また、plan-all は sh "$0" "$h" plan | sed ... なのでパイプの終了ステータスが sed のものになります。ERROR が出ても plan-all 自体は 0 で終わり、次の host に進みます。上の事前検査に使うなら、この終了ステータスも拾えるようにする必要があります。

3. restore のループが標準入力を共有している(要確認)

echo "$steps" | while read -r _ op a b c d; do sh "$0" ... done の中で呼ぶ子プロセスは、steps を流しているパイプを標準入力として継承します。今の kubectl exec(-i 無し)や restic は標準入力を読まないはずです。ただ、将来 -i や kubectl ... -f - 系の呼び出しが入ると、残りの行を黙って食います。done <<EOF 形式にするか、各呼び出しに </dev/null を付けておくと安全です。

4. 細かい点

  • plan() の exit 1 では $_l / $_o の一時ファイルが残ります。trap 'rm -f "$_l" "$_o"' EXIT で足ります。
  • restic_env は RESTIC_REPOSITORY があれば restic.env を読みません。R2 の認証情報や RESTIC_PASSWORD だけが欠けている環境ではそのまま失敗します。親が一度 . restic.env した後の子プロセスのための早期 return なら、その旨がコメントにあると誤読されにくいです。
  • テストはありません。シェルスクリプトなので仕方ないですが、latest の出力をモックして経路の判定だけを検証できる分岐があると、README の表との食い違いを防げます。今回は「plan-all を本番で読み取りだけ流した」という確認で、現実的な範囲です。

セキュリティ

シークレットの混入はありません。$NS / 経路は restic のメタデータとクラスタ由来で、sh -c に渡す文字列($f, $2 など)は既存の sqlite / untar と同じ前提です。新しい攻撃面は増えていません。

総評

大きなロジックの誤りは見つかりませんでした。実害になり得るのは 1 と 2(黙って飛ばす、途中で止まると中途半端な状態になる)で、次の建て直しで restore を試す前に手当てしておく価値があります。

… が通ってから上書き

- plan は namespace や PVC が無ければ飛ばさずに止める(未作成とやめたアプリの残りを区別できない)
- restore-all は Forgejo の後、残り全部の plan が通るまで Argo CD を動かしたまま待ち(10 分)、
  通らなければどこも上書きせずに止める。plan-all は 1 つでも決められなければ 1 で終わる
- restore のループは fd 3 で読む(中で呼ぶコマンドが残りの行を食わない)。plan の一時ファイルは trap で消す

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NN4TmSXHrMP8dkTbtkjfz1
@claude

claude Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Claude finished @5ym's task in 41s —— View job


レビュー結果(recovery/pv.sh / recovery/README.md)

前回のレビュー(namespace / PVC が無いものを黙って飛ばす点、restore-all が事前に全体の plan を通さない点、stdin 共有、trap)は 96437ed で直っています。plan_all の事前検査、fd 3 での読み、trap ... EXIT を読んで確認しました。sh -n は未実行で、restore は本番でも流していないため、以下はコードを読んだ範囲での指摘です。

1. PR 本文と実装が食い違っている

本文に「PVC が無い /data/* は飛ばす(やめたアプリの残り)」とあります。実装(pv.sh:147)と README は、PVC が無ければ ERROR で止めます。実装の方が安全で、README とも合っています。本文だけ古いので直してください。

2. plan は deploy/postgres とその env を確認しない(要確認)

pgload の POSTGRES_USER / POSTGRES_DB は restore の中で kubectl exec deploy/postgres -- printenv で取ります(pv.sh:176-177)。この時点で stop と、folder による他の PVC の上書きは済んでいます。

  • deploy/postgres が無い、または env が無いと、そこで止まり、中途半端な状態で Argo CD が pause のままになります。
  • plan の段階で deploy/postgres の存在と env を確認しておくと、「どこも触る前に止まる」という方針に揃います。
  • 前の手順書は rybbit analytics と決め打ちでした。Pod の env がそれと一致するかも、次の建て直しで最初に確かめる対象です。

3. 「経路が表に無い」エラーも forgejo を戻した後に分かる

restore-all の待ちループ(pv.sh:245-249)は、namespace が未作成による失敗と、表に無い経路による失敗を区別しません。表に無い経路が混じっていると、forgejo を上書きしてから 10 分待って初めて止まります。

対策は、待ちに入る前に一度 plan-all を流し、ERROR: ... 戻し方が決まっていない だけを即時に失敗させることです。例えば plan の終了コードを分ける方法があります。致命的ではなく、forgejo だけ戻った状態で止まるのは設計どおりです。

4. 細かい点

  • H=$(hosts)(pv.sh:244)は、hosts の最後が grep -vxE です。除外後に 1 行も残らないと、終了ステータス 1 で set -e が静かにスクリプトを終わらせます。restic が空のときだけの稀な経路です。
  • README の旧手順にあった「戻した namespace ごとに rollout restart deploy」が消えました。stop → start で Pod は作り直されるため、実害は無いと思います。ただし pgload 時に残す postgres は再起動されません(psql で流すだけなので問題無いはず)。要確認です。

セキュリティ

シークレットの混入はありません。$NS / 経路は restic のメタデータとクラスタ由来で、sh -c へ渡す文字列は既存の sqlite / untar と同じ前提です。新しい攻撃面は増えていません。

テスト

シェルスクリプトで、restore は未実行です。PR 本文と README に「次の建て直しで最初に確かめる」と明記されており、plan-all を本番に対して読み取りで流した確認も妥当です。

総評

致命的な不具合は見つかりませんでした。直す価値があるのは 1(本文)と 2(plan で deploy/postgres を確認する)です。3 は任意です。

@claude

claude Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Claude finished @5ym's task in 43s —— View job


レビュー結果(recovery/pv.sh / recovery/README.md)

過去2回のレビューで挙がった点は、今の HEAD では直っています。

  • plan が deploy/postgres の POSTGRES_USER / POSTGRES_DB を触る前に決める。
  • 戻し方の無い経路は exit 2 にして、Forgejo を戻す前に止める。
  • restore は fd 3 で読む。
  • trap で一時ファイルを消す。

plan が出す行の列と、restore の read / sh "$0" ... の引数の対応(sqlite / untar / pgload / matrix)も確認しました。ずれはありません。sh -n と restore は実行しておらず、以下はコードを読んだ範囲での指摘です。

1. 途中で失敗すると Argo CD が pause のまま残る(要確認)

restore-all は set -e なので、sh "$0" forgejo restore や 2 周目の restore が落ちると、argo-resume に届かずに終わります。この場合、application-controller は 0 のままです。

  • 止めた namespace の控え(~/.cache/pv-replicas-*)は残るので、start で戻せます。
  • 一方で、Argo CD を戻す手順はエラーメッセージに出ません。
  • 失敗時に「Argo CD は止まったまま。sh recovery/pv.sh - argo-resume で戻す」と出す trap を、2 回目の argo-pause の直前に入れると親切です。
  • restore-all を流し直すと Forgejo をもう一度上書きします。README に「途中から再開するなら <ns> restore を個別に」と書いておくと迷いません。

2. wait_appsets は kubectl の失敗を「エラーなし」と読む(軽微)

kubectl ... | grep -c True || true では、kubectl が失敗(API 到達不可、権限)しても e=0 になり、「エラーなし」で抜けます。後段の plan 待ち(10 分)で拾えるので実害は小さいです。気になるなら、kubectl の出力を一度変数に取ってから grep してください。

3. 細かい点

  • hosts() は restic snapshots の失敗を || true で隠します。ただし plan_all forgejo が先に落ちるので、restore-all では問題になりません。plan-all も同様です。
  • plan_all は成功時にだけ出力を表示します。失敗した host の stderr は端末に出ますが、成功した host の行と混ざります。読みづらければ、失敗時にも out を表示してください。

セキュリティ

シークレットの混入はありません。$NS や経路は restic のメタデータとクラスタ由来で、sh -c に渡す文字列は既存の sqlite / untar と同じ前提です。新しい攻撃面は増えていません。

テスト

シェルスクリプトで、restore は未実行です。PR 本文と README に「次の建て直しで最初に確かめる」と明記されており、plan-all を本番に読み取りで流した確認も妥当です。

総評

致命的な不具合は見つかりませんでした。1 は、次の建て直しで restore を試す前に手当てしておく価値があります。2 と 3 は任意です。

@claude

claude Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Claude finished @5ym's task in 32s —— View job


レビュー結果(recovery/pv.sh / recovery/README.md)

これまでのレビューで挙がった点は、HEAD(9cede28)で直っています。

  • plan が postgres のユーザーと DB を触る前に決める。
  • 戻し方の無い経路は exit 2 にして、Forgejo の前に止める。
  • restore は fd 3 で読む。
  • trap で一時ファイルを消す。
  • restore-all が途中で止まったときに、Argo CD の戻し方と続け方を出す。

plan が出す行の列と、restore の read -r _ op a b c d の対応も確認しました(folder / sqlite / untar / pgload / matrix)。ずれはありません。sh -n はこの環境で実行を許可されておらず、restore も実行していません。以下はコードを読んだ範囲での指摘です。

1. 待ちに失敗したときの trap のメッセージが実態と合わない(軽微)

pv.sh の restore-all では、trap ... EXIT を最初の argo-pause の前に入れています。そのため、10 分待っても plan が通らずに止まる経路(「forgejo だけ戻した状態で止めた(Argo CD は動いている)」)でも、続けて「Argo CD は止まったままかもしれない」と出ます。

  • 2 つのメッセージが矛盾して見えます。
  • 実害は無く、「かもしれない」とも書いてあります。
  • 気になるなら、2 回目の argo-pause の直前まで trap を張らない形か、フェーズごとに文言を分けてください。
  • ただし、Forgejo の restore 中の失敗は trap が必要です。

2. wait_appsets は kubectl の失敗を「エラーなし」と読む(軽微・既出)

kubectl … | grep -c True || true なので、kubectl が失敗すると e=0 で抜けます。後段の plan 待ち(10 分)が受けるので、実害は小さいです。

3. 要確認(本番での最初の restore で見るもの)

  • latest の --latest 1 は host と paths の組ごとに最新を返します。経路ごとに最新が違う host で、古い経路が混ざらないかは、plan-all の出力で確認済みとのことなので問題なさそうです。
  • deploy/postgres の env は、値で書いてあるものだけを jsonpath で読みます。valueFrom(Secret 参照)の場合は空になり、plan が ERROR で止まります。この場合は、止まるので安全側です。README にも書いてあると親切です。

セキュリティ

シークレットの混入はありません。$NS や経路は restic のメタデータとクラスタ由来で、sh -c に渡す文字列の前提は既存の sqlite / untar と同じです。新しい攻撃面は増えていません。

テスト

シェルスクリプトです。plan-all を本番に読み取りで流した確認と、restore を次の建て直しで最初に確かめるという README の記載は、現実的な範囲です。

総評

致命的な不具合は見つかりませんでした。1 と 2 は任意です。マージして問題ないと思います。

@5ym
5ym merged commit b654fd3 into main Oct 20, 2026
1 check passed
@5ym
5ym deleted the feat/restore-all branch October 20, 2026 05:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant