TT Lab
はじめる
学ぶ 学習パス コース

CRDとオペレータ

古いバージョンを消したらオブジェクトが消えた - 保存バージョン移行の手順

TT Labで続きを見る

目標

2つのバージョンを持つCRDを作ってストレージバージョンを上げ、すべてのオブジェクトを書き直したうえでstatus.storedVersionsを整理し、古いバージョンを下げる移行の手順を、最初から最後まで歩いてみます。途中でAPIサーバーがどこで止めてくれるかも、自分で確認します。

なぜ重要なのか

CRDはコードではなくAPI契約です。フィールドを1つ変えるには、新しいバージョンを出し、古いバージョンを使っていた人たちが移行する時間を与えてはじめて、古いものを削除できます。このとき、人々が最もよく抜かすのが、すでに保存されたオブジェクトを書き直す作業です。ストレージバージョンを変えるのは「今後保存する表現」だけを変える作業なので、すでにetcdに入っているバイト列は、そのまま古い表現です。その状態で古いスキーマを削除すると、保存されたオブジェクトを読む方法がなくなります。status.storedVersionsは、まさにその事故を防ぐための記録であり、APIサーバーは、このリストに残っているバージョンを外そうとする試みを、実際に拒否します。このラボでは、その安全装置に一度ぶつかってみたうえで、正しい順序で通過します。

ステップ

  1. /root/crd-version/tunnel-crd.yamlにCRD tunnels.net.labhub.ioを書いてください。グループnet.labhub.io、kind Tunnel、複数形tunnels、バージョンは2つです。v1alpha1はserved: true・storage: trueで、deprecated: trueとdeprecationWarningを付け、スキーマにはspec.endpoint(string)・spec.port(integer)だけを置きます。v1beta1はserved: true・storage: falseで、この2つのフィールドに加えてspec.mtu(integer、default: 1400)を置きます。適用した後、2つのバージョンの状態を/root/crd-version/versions-initial.txtに、<버전> served=<참거짓> storage=<참거짓>(プレースホルダーはバージョンとtrueまたはfalseです)の2行で保存してください。
  2. ネームスペースcrd-versionを作成し、/root/crd-version/tunnel-east.yaml(名前t-east、endpoint 10.30.0.11、port 4789)と/root/crd-version/tunnel-west.yaml(名前t-west、endpoint 10.30.0.12、port 4789)をv1alpha1で適用してください。適用するときに返ってくる警告を標準エラー出力まで受け取って/root/crd-version/deprecation-warning.txtに保存し、その時点のstatus.storedVersionsを/root/crd-version/stored-initial.txtに1行で保存してください。
  3. 同じオブジェクトを、新しいバージョンの窓で読んでください。kubectl -n crd-version get tunnels.v1beta1.net.labhub.io t-east -o yamlの出力を、/root/crd-version/as-v1beta1.yamlに保存します。そして、/root/crd-version/conversion-note.txtに2行を書いてください。apiVersion=<읽은 apiVersion>とmtu=<spec.mtu 값, 없으면 none>です(プレースホルダーは順に、読み取ったapiVersionと、spec.mtuの値(なければnone)です)。
  4. /root/crd-version/tunnel-crd-beta-storage.yamlに、ステップ1のCRDをコピーして、v1alpha1のstorageをfalseに、v1beta1のstorageをtrueに変えて適用してください。適用した後、status.storedVersionsを/root/crd-version/stored-after-flip.txtに1行で保存してください。
  5. ストレージバージョンを実際に移してください。crd-versionのすべてのTunnelをv1beta1で読んで、そのまま書き直せばよいです(kubectl get … -o json | kubectl replace -f -)。2つのオブジェクトをどちらも書き直した後、kubectl -n crd-version get tunnels.v1beta1.net.labhub.io -o yamlの出力を/root/crd-version/after-rewrite.yamlに保存してください。
  6. 古いバージョンをspec.versionsから外してみてください。kubectl patch crd tunnels.net.labhub.io --type=json -p '[{"op":"remove","path":"/spec/versions/0"}]'です。コマンドの出力と終了コードを/root/crd-version/remove-blocked.txtに集めてください。1行目はremove-rc=<종료 코드>(プレースホルダーは終了コードです)で、その下にサーバーが出した文をそのまま貼り付けます。
  7. これですべてのオブジェクトがv1beta1で保存されているので、記録を整理してください。kubectl patch crd tunnels.net.labhub.io --subresource=status --type=mergeで、status.storedVersionsを["v1beta1"]の1つにします。整理した後の値を、/root/crd-version/stored-pruned.txtに1行で保存してください。
  8. /root/crd-version/tunnel-crd-retire.yamlに、ステップ4のCRDをコピーして、v1alpha1のservedをfalseに変えて適用してください。そのあと、kubectl -n crd-version get tunnels.v1alpha1.net.labhub.io t-eastを実行して、結果を/root/crd-version/retired.txtに集めてください。1行目はget-rc=<종료 코드>(プレースホルダーは終了コードです)で、その下にサーバーの文を貼り付けます。最後に、/root/crd-version/version-check.shを作成してください。このCRDのserved・storage・storedを1行ずつ<이름>=<값>(プレースホルダーは名前と値です)として出力し、storedVersionsにストレージバージョンではない値があればPENDING <버전>(プレースホルダーはバージョンです)を出力して0ではないコードで、なければMIGRATEDを出力して0で終了する必要があります。その出力を/root/crd-version/version-check.txtに保存してください。

参考

1つの型に2つのバージョンを同時に提供する

/root/crd-version/tunnel-crd.yamlにCRD tunnels.net.labhub.ioを書いてください。グループnet.labhub.io、kind Tunnel、複数形tunnels、バージョンは2つです。v1alpha1はserved: true・storage: trueで、deprecated: trueとdeprecationWarningを付け、スキーマにはspec.endpoint(string)・spec.port(integer)だけを置きます。v1beta1はserved: true・storage: falseで、この2つのフィールドに加えてspec.mtu(integer、default: 1400)を置きます。適用した後、2つのバージョンの状態を/root/crd-version/versions-initial.txtに、<버전> served=<참거짓> storage=<참거짓>(プレースホルダーはバージョンとtrueまたはfalseです)の2行で保存してください。

1つのCRDの中のバージョンは、同じオブジェクトを別の窓で見ているものなので、保存はたった1つの表現でしか行われません。そのため、storageがtrueのバージョンはちょうど1つでなければならず、残りはservedだけを有効にして、読み書きの窓として残します。deprecationWarningは、そのバージョンでリクエストしたときに、クライアントに返る文です。

非推奨のバージョンで作ると、警告が返ってくる

ネームスペースcrd-versionを作成し、/root/crd-version/tunnel-east.yaml(名前t-east、endpoint 10.30.0.11、port 4789)と/root/crd-version/tunnel-west.yaml(名前t-west、endpoint 10.30.0.12、port 4789)をv1alpha1で適用してください。適用するときに返ってくる警告を標準エラー出力まで受け取って/root/crd-version/deprecation-warning.txtに保存し、その時点のstatus.storedVersionsを/root/crd-version/stored-initial.txtに1行で保存してください。

非推奨の警告は、標準出力ではなく標準エラー出力に出ます。storedVersionsはspecではなくstatusにある記録で、「このCRDでこれまでに実際に保存されたことのあるバージョン」を意味します。まだ1つのバージョンでしか保存していないなら、何が入っているか予想してみてください。

新しいバージョンで読むと、何が変わるのか

同じオブジェクトを、新しいバージョンの窓で読んでください。kubectl -n crd-version get tunnels.v1beta1.net.labhub.io t-east -o yamlの出力を、/root/crd-version/as-v1beta1.yamlに保存します。そして、/root/crd-version/conversion-note.txtに2行を書いてください。apiVersion=<읽은 apiVersion>とmtu=<spec.mtu 값, 없으면 none>です(プレースホルダーは順に、読み取ったapiVersionと、spec.mtuの値(なければnone)です)。

conversion.strategyを別に書かなければNoneで、Noneは保存されたバイト列をそのままにして、apiVersionの表記だけを書き換えて見せます。v1beta1のスキーマにはデフォルト値があるフィールドがありますが、デフォルト値は「保存するとき」に埋められます。このオブジェクトが、まだどのバージョンで保存されているか、考えてみてください。

ストレージバージョンを上げる

/root/crd-version/tunnel-crd-beta-storage.yamlに、ステップ1のCRDをコピーして、v1alpha1のstorageをfalseに、v1beta1のstorageをtrueに変えて適用してください。適用した後、status.storedVersionsを/root/crd-version/stored-after-flip.txtに1行で保存してください。

この1行を変えると、「今後保存するときに使う表現」だけが変わります。すでにetcdに入っているオブジェクトのバイト列は、そのままです。そのため、storedVersionsは減らず、むしろ増えます。そのリストが何を意味するのか、もう一度読んでみてください。

すべてのオブジェクトを1回ずつ書き直す

ストレージバージョンを実際に移してください。crd-versionのすべてのTunnelをv1beta1で読んで、そのまま書き直せばよいです(kubectl get … -o json | kubectl replace -f -)。2つのオブジェクトをどちらも書き直した後、kubectl -n crd-version get tunnels.v1beta1.net.labhub.io -o yamlの出力を/root/crd-version/after-rewrite.yamlに保存してください。

書き直しが本当に起きたかどうかは、新しいバージョンにだけあるデフォルト値が証拠になります。ストレージバージョンで書く瞬間に、APIサーバーがそのフィールドを埋めるからです。オブジェクトが数千個ある実際の運用では、この作業を一度に回さず、分けて回します。毎回etcdへの書き込みが発生するからです。

まだ削除できないと、APIサーバーが止める

古いバージョンをspec.versionsから外してみてください。kubectl patch crd tunnels.net.labhub.io --type=json -p '[{"op":"remove","path":"/spec/versions/0"}]'です。コマンドの出力と終了コードを/root/crd-version/remove-blocked.txtに集めてください。1行目はremove-rc=<종료 코드>(プレースホルダーは終了コードです)で、その下にサーバーが出した文をそのまま貼り付けます。

この拒否は小言ではなく、安全装置です。リストに残っているバージョンのスキーマを削除すると、その表現で保存されたオブジェクトを読む方法がなくなります。エラーの文がどのフィールドを指しているか、正確に読んでみてください。直すべき場所が、そこに書かれています。

記録を整理してはじめて、扉が開く

これですべてのオブジェクトがv1beta1で保存されているので、記録を整理してください。kubectl patch crd tunnels.net.labhub.io --subresource=status --type=mergeで、status.storedVersionsを["v1beta1"]の1つにします。整理した後の値を、/root/crd-version/stored-pruned.txtに1行で保存してください。

この整理は、人が「書き直しを終えた」と宣言する行為です。APIサーバーは、書き直しを代わりに数えてはくれません。そのため、ステップ5を飛ばしてここから始めると、そのまま事故になります。statusは別の窓なので、普通のpatchでは届きません。

古い窓を閉じて、移行の完了を証明する

/root/crd-version/tunnel-crd-retire.yamlに、ステップ4のCRDをコピーして、v1alpha1のservedをfalseに変えて適用してください。そのあと、kubectl -n crd-version get tunnels.v1alpha1.net.labhub.io t-eastを実行して、結果を/root/crd-version/retired.txtに集めてください。1行目はget-rc=<종료 코드>(プレースホルダーは終了コードです)で、その下にサーバーの文を貼り付けます。最後に、/root/crd-version/version-check.shを作成してください。このCRDのserved・storage・storedを1行ずつ<이름>=<값>(プレースホルダーは名前と値です)として出力し、storedVersionsにストレージバージョンではない値があればPENDING <버전>(プレースホルダーはバージョンです)を出力して0ではないコードで、なければMIGRATEDを出力して0で終了する必要があります。その出力を/root/crd-version/version-check.txtに保存してください。

servedをオフにすると、そのバージョンのエンドポイント自体が消えるため、古いapiVersionで送るリクエストは、リソースが見つからないという応答を受け取ります。保存されたオブジェクトは何事もなくあるのに、その窓からだけ見えないのです。チェックスクリプトは標準出力にだけ書き、ファイルを直接触らないようにしてください。そうすれば、何回実行しても同じ答えが出ます。