ある朝、K3sの本番ノードが壊れてわかったこと
本番のK3sクラスタで、Veleroによる日次バックアップを半年以上運用していたチームがありました。Slackに流れる通知は毎晩「Completed」。誰もバックアップの失敗を疑ったことはありませんでした。
ところがある朝、ワーカーノードの1台がディスク障害で起動しなくなり、そのノード上のPostgreSQLのPodも一緒に失われました。慌ててVeleroでリストアを実行すると、PodもPVCも問題なく作成されます。しかし中身を見ると、テーブルは空っぽ。データが1行も入っていませんでした。
K3sとVeleroの組み合わせでバックアップ/リストア運用を設計している方であれば、他人事ではないはずです。この記事では、K3sでVeleroのバックアップが「成功したのに復元できない」事態がなぜ起きるのか、その切り分け方と、本番環境で取るべき恒久対策を整理します。
なぜ"Completed"なのにデータが無かったのか — K3s標準ストレージという前提

原因は、K3sが標準で同梱しているストレージプロビジョナーにありました。
K3sにはRancher製の「Local Path Provisioner」がデフォルトで組み込まれており、PersistentVolumeClaimを作成すると、Podがスケジュールされたノード上の/var/lib/rancher/k3s/storage配下にディレクトリを掘って割り当てます(K3s公式ドキュメント)。Local Path ProvisionerのGitHubリポジトリでも、これは分散アプリケーション向けのボリューム移行・レプリケーション・スナップショットを想定した設計ではなく、開発環境や単一ノード構成での「ノード固有データの永続化で十分な場面」を想定したツールだと明記されています。
つまりLocal Path Provisionerは、Kubernetesの用語で言うCSIドライバとしてのスナップショット機能(CREATE_DELETE_SNAPSHOTケーパビリティ)を持っていません。VeleroがCSI連携モードでバックアップを取ろうとしても、スナップショットを作成する相手がそもそも存在しないのです。
それでもVeleroのバックアップジョブは「Completed」のステータスを返し続けます。Velero自身はKubernetesリソース定義(YAMLの中身)のバックアップには成功しているため、ジョブとしては正常終了だからです。ボリュームの実データが保護されているかどうかは、また別の話だということを、運用者が気づきにくい形で切り離されています。
序盤でこの前提に気づいていれば、KuboのようにLonghorn等のCSI対応ストレージを標準搭載したマネージドK3s基盤を選ぶという選択肢も検討できたはずです。
CSIスナップショットのサイレント失敗ポイントを切り分ける

Local Path Provisioner特有の話を抜きにしても、CSIスナップショットの仕組みそのものに、気づきにくい失敗ポイントが複数あります。
まず大前提として、VolumeSnapshot / VolumeSnapshotContent / VolumeSnapshotClass はKubernetesのコアAPIには含まれない、snapshot.storage.k8s.ioグループのCRDです。これらのCRDとsnapshot-controllerのインストールは、Kubernetesディストリビューション側の責任とされています(Kubernetes公式ドキュメント)。K3sのような軽量ディストリビューションでは、ここが未導入のまま運用されているケースも珍しくありません。
その上で、よくある失敗パターンとして以下が挙げられます(Netdataのガイド)。
- CRD・snapshot-controllerの未導入: VolumeSnapshotが永遠にVolumeSnapshotContentとバインドされない
- VolumeSnapshotClassのdriver名の不一致: イベントすら出ないまま、サイドカーが完全に沈黙する
- CSIドライバがスナップショット未対応: サイドカーのリクエストがドライバ側で「未実装」として拒否される
- リストア時のstorageClassName指定ミス: スナップショット復元ではなく、空のボリュームが新規プロビジョニングされる
最後の「storageClassName指定ミス」が特に厄介です。PVCもPodも正常に作成されるため見た目には問題がなく、実際にデータを確認するまで誰も気づけません。
Veleroを使う場合はさらに、対象のVolumeSnapshotClassにvelero.io/csi-volumesnapshot-class: "true"というラベルを付与しておく必要があります(Velero公式CSIドキュメント)。このラベルが抜けていると、「対象プロビジョナーのVolumeSnapshotClassが見つからない」というエラーでスナップショット作成自体が失敗します。
こうした構成の整合性チェックを毎回自分たちで維持し続けるのは、小さなチームには重い負担です。Kubo CloudはCSI対応ストレージとモニタリングを標準搭載しているため、この手の「ラベル1つの付け忘れ」に起因する事故のリスクをそもそも減らせます。
正しい代替策 — File System Backupとその限界

Local Path Provisionerのようにスナップショット非対応のストレージでボリュームデータを守るには、Veleroの File System Backup(FSB)を使う必要があります。
FSBは、ノードごとに動くnode-agentのDaemonSetが、Pod volumeをhostPathマウント経由で読み取り、Kopia(旧Resticから移行)でオブジェクトストレージへアップロードする仕組みです。EFSやAzureFile、NFS、そしてLocal Path Provisionerのようなローカルストレージなど、CSIスナップショットに対応していないボリュームに対しては、FSBが現実的な選択肢になります(Velero公式File System Backupドキュメント)。
ただしFSBにも限界があります。FSBは稼働中のファイルシステムから直接ファイルを読み取るため、スナップショット方式のような「ある瞬間の状態」をそのまま切り取ることができません。公式ドキュメントでも、スナップショット方式に比べて一貫性が劣ることが明記されています。データベースのようにファイルが頻繁に書き換わるワークロードでは、バックアップの瞬間にクラッシュコンシステント(突然電源を切ったのと同程度の整合性)止まりになる可能性があります。
アプリケーション一貫性のあるバックアップが必要な場合は、Veleroのバックアップフック機能で、直前にファイルシステムをフリーズする、あるいはデータベース自身のダンプコマンドを実行するといった対応が必要です(Velero公式バックアップフックドキュメント)。fsfreezeのコマンドをPodアノテーションで指定するだけで実現できますが、裏を返せば「フックを自分で書いて動作確認までする」という追加の運用タスクが発生するということでもあります。
"バックアップ成功"ログを信じない運用 — リストアドリルを仕組み化する

ここまで見てきたように、Veleroの「Completed」というステータスは、あくまで「バックアップコントローラがワークフローを最後まで処理できた」ことの証明にすぎません。実際にそのバックアップでアプリケーションを復元できるかどうかは、別の軸で検証する必要があります(Velero公式CSI統合解説)。
よくある誤解として、「K3sのetcdスナップショットを取っているから、バックアップは万全」という思い込みがあります。etcdスナップショットが保護するのはKubernetesのリソース定義(PodやServiceのYAML相当の情報)であって、PersistentVolume上のアプリケーションデータそのものは含みません。データベースのPVがノード障害で失われれば、etcdスナップショットからクラスタ状態を復元しても、データベースの中身は空のまま起動します。
この落とし穴を避けるために有効なのが、定期的な「リストアドリル」です。本番とは隔離されたnamespaceやクラスタにVeleroで実際にリストアし、アプリケーションが正しく起動してデータが読み込めるかを確認します。これを月次、少なくとも四半期に1回は実施し、実測のRTO(目標復旧時間)・RPO(目標復旧時点)を記録しておくことで、「バックアップが取れている」という状態と「実際に復旧できる」という状態のギャップを事前に発見できます。
こうした検証サイクルを人手で回し続けるのは、ただでさえ手一杯なインフラチームには重荷です。Kuboなら、K3sベースの本格的なKubernetes環境にCSI対応ストレージと運用監視が組み込まれており、バックアップ設計そのものを自前でゼロから作り込む必要がなくなります。コスト面でも、Kubo Cloudは月額48,000円〜とEKSの約58%のコストで運用できるため、バックアップ基盤にかける検証工数を他の開発に振り向けやすくなります。
まとめ
K3s標準のLocal Path Provisionerは、開発・CI用途に最適化されたシンプルなストレージであり、CSIスナップショットには対応していません。これを本番環境でそのまま使い続けると、Veleroのバックアップジョブが「Completed」を返し続けていても、実際にはボリュームデータが保護されていないという状態に陥りがちです。
対策の要点は3つです。
- 本番環境ではLocal Path Provisionerをそのまま使わない: LonghornなどCSI対応ストレージへの移行を検討する
- CSIスナップショットが使えない場合はFile System Backupを使い、限界を理解する: クラッシュコンシステント止まりであることを前提に、必要ならバックアップフックを併用する
- バックアップは「取れているか」ではなく「戻せるか」で評価する: 定期的なリストアドリルをスケジュール化し、RTO/RPOを実測する
バックアップ・ディザスタリカバリの設計と検証を継続的に回し続ける負荷を減らしたい場合は、CSI対応ストレージを標準搭載したKuboや、データを完全に自社管理したい場合のKubo On-Premiseの構成も選択肢の一つです。まずはお問い合わせから、現在のバックアップ構成を相談してみてください。