スナップショットではできない復旧
一言でいうと
etcdスナップショットは、クラスター全体を1つの時点へ戻すための道具なので、「このネームスペースだけ」や「別のクラスターへ」には使えません。その役目をオブジェクトレベルのバックアップが埋めますが、エクスポートしたものをそのまま戻してはいけません。特にownerReferencesを残すと、リストアしたオブジェクトが静かに消えます。
なぜスナップショットだけでは足りないのか
運用で実際に来る依頼は、たいていこのような形です。「昨日の午後に誤って削除したshopネームスペースだけを元に戻してください」、または「ステージングクラスターに、本番と同じ構成を立ててください」。どちらの依頼も、etcdスナップショットでは応えられません。スナップショットの復旧は、クラスター全体をその時点に戻す作業なので、その間に起きたほかのすべての変更まで一緒に消えます。削除したネームスペース1つのために、その日1日分のデプロイをすべて元に戻すことはできません。さらに、スナップショットはそのクラスターのアイデンティティと結び付いているので、別のクラスターへ移せません。
2つの方式は、役割が異なると考えるほうが正確です。
| etcdスナップショット | オブジェクトバックアップ | |
|---|---|---|
| 単位 | クラスター全体 | 選んだオブジェクト |
| 時点 | 丸ごと1つの時点 | オブジェクトごとにエクスポートした時点 |
| 別のクラスターへ | できません | できます |
| 部分的な復旧 | できません | できます |
| コントロールプレーンの設定 | 一緒に復旧されます | 対象ではありません |
どう動くのか
オブジェクトバックアップの核心は、エクスポートではなくクリーンアップです。kubectl get -o yamlは、ユーザーが書いたものとサーバーが埋めたものを区別せず、すべてエクスポートします。その中に、戻してはいけないものがあります。
| 削除するもの | 理由 |
|---|---|
metadata.uid |
オブジェクトごとに新しく発行されます。以前の値は入れられません |
metadata.resourceVersion |
楽観的ロック用の値なので、リストア時に衝突を生むだけです |
metadata.creationTimestamp・generation |
サーバーが決めます |
metadata.managedFields |
フィールド所有権の履歴なので、別のクラスターでは意味がありません |
metadata.ownerReferences |
リストア先のクラスターに、そのuidを持つ所有者がいません |
status |
コントローラーが観測して書き直します |
Serviceのspec.clusterIP・nodePort |
そのクラスターが割り当てたアドレスです |
これに加えて、metadata.namespaceも一緒に削除する習慣が役に立ちます。リストア先をマニフェストではなくコマンドで決められるようになり、そうすれば、同じバックアップで検証用ネームスペースに先に立ててみる手順を作れます。
最も危険なのはownerReferencesです。Kubernetesはこの参照で依存関係を管理し、ガベージコレクターは所有者がいなくなったオブジェクトを削除します。ところが、参照は名前だけでなくuidまで含みます。リストアしたクラスターでそのuidを持つオブジェクトが見つからなければ、ガベージコレクターは所有者が消えたと判断して、リストアしたオブジェクトを削除します。エラーも、通知も、ほとんどの場合イベントもありません。ReplicaSetをまるごとバックアップしてリストアした数秒後に静かに消える事故は、ここから生じます。
リストアには順序もあります。ネームスペースが先にあって初めて、その中にオブジェクトを入れられ、CRDが先に登録されて初めて、カスタムリソースを受け付けます。順序を間違えると、APIサーバーは、その種類をそもそも知らないと拒否します。マニフェストが間違っているのではなく、その種類がまだないのです。
最後に、PVCはデータではありません。PersistentVolumeClaimをバックアップしたなら、リストアしたのは「これだけのボリュームをください」という申請書だけで、その中にあったファイルはどこにもありません。データのバックアップは、ボリュームスナップショットやアプリケーションレベルのバックアップという、別の問題です。この区別を曖昧にすると、復旧訓練に合格しても、実際の事故でデータを失います。
現場での姿
最もよくある事故は、リストアしたのに数秒後に消えることです。上のownerReferencesが原因ですが、症状が「リストアが失敗した」ではなく「リストアは成功したのに存在しない」なので、原因を見つけるのに時間がかかります。
2つ目は、リストアの順序を書き留めていないことです。普段はCRDがすでにあるので何も問題がなく、新しいクラスターにまるごとリストアする日に初めて遭遇します。その日は時間がありません。
3つ目は、検証を人が目で行うことです。オブジェクトがあるかどうかだけを数えると、レプリカが2から1に減ったリストアも成功に見えます。そのため、リストア手順の最後のステップは、常に機械が値を照合するものであるべきです。
このラボ環境の限界
このクラスターにはPersistentVolumeもCSIドライバーもないので、PVCとデータの違いを実物で見せることはできません。その部分は、上の説明で代えます。逆に、ガベージコレクターは本当に動くので、古い所有者参照を残したままリストアすると、そのオブジェクトが実際に消えるのを目で確認できます。ラボで計測したところ、数秒で消えました。また、このラボはetcdctlを一度も使いません。同じコースのetcdのラボと重ならないようにするためでもあり、オブジェクトバックアップが実際にetcdに触れない作業だからでもあります。
次のラボですること
CRD1つとネームスペース1セット(Deployment・Service・ConfigMap・カスタムリソース)を作成し、加工していない状態のままエクスポートして、何が含まれているかを確認します。次に、削除すべきフィールドを取り除くスクリプトを作成し、ネームスペースをまるごと削除したあと、別のネームスペースへリストアします。続いて、古い所有者参照を残したオブジェクトが実際に消えることを確認し、CRDなしでカスタムリソースを先に入れたときのエラーを読んで、リストアの順序を書き留めます。最後に、リストア一覧と実際のクラスターを照合する検証スクリプトを作成します。
参考ドキュメント:
- https://kubernetes.io/docs/concepts/architecture/garbage-collection/
- https://kubernetes.io/docs/concepts/overview/working-with-objects/