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

CRDとオペレータ

レプリカを増やしたのに何も起きなかった - scale サブリソースの契約

TT Labで続きを見る

目標

CRDのsubresources.scaleを3つのパスで正しく結び、セレクターのパスを抜かしたときと、パスにタイプミスがあるときに、それぞれどんな沈黙が生じるかを自分で作ってみたうえで、その沈黙を見つけ出すチェックスクリプトを作ります。

なぜ重要なのか

kubectl scaleとHPAは、対象の種類を知りません。この2つが知っているのは、ただscaleサブリソースという共通の窓1つだけで、CRDは、その窓を3つのJSONPathで開いてあげます。そのため、Operatorを作る人がこの3行を正しく書けば、自分の型がKubernetesの既存のオートスケーリングのツールすべてと無料でつながり、間違って書けば、それらのツールがエラーなしに何もしません。この沈黙が、このモジュールの主題です。パスにタイプミスがあると、kubectl scaleは成功したと答えながら値を変えず、セレクターのパスがないと、HPAは対象を読むことに成功したまま、永遠にスケーリングしません。どちらも、人が画面を覗き込むまでは表に出ないため、デプロイのパイプラインに組み込めるチェックの方法も、一緒に備えておく必要があります。

ステップ

  1. /root/op-scale/shard-crd.yamlにCRD shards.scale.labhub.ioを書いてください。グループscale.labhub.io、kind Shard、複数形shards、バージョンv1を1つ(servedもstorageもtrue)とし、spec.replicas(integer)・status.replicas(integer)・status.selector(string)をスキーマに置いて、subresourcesにstatus: {}とscaleを有効にします。scaleの3つのパスは、それぞれ.spec.replicas・.status.replicas・.status.selectorです。適用した後、クラスターに登録された3つのパスを、/root/op-scale/scale-paths.txtに<이름>=<값>(プレースホルダーは名前と値です)の3行で保存してください(specReplicasPath=...、statusReplicasPath=...、labelSelectorPath=...)。
  2. ネームスペースop-scaleを作成し、/root/op-scale/shard-a.yamlにShard aを書いてください。spec.replicasは2です。適用した後、kubectl -n op-scale get shard a --subresource=scale -o yamlの出力をそのまま/root/op-scale/scale-initial.yamlに保存してください。
  3. kubectl -n op-scale scale shard a --replicas=5で、Shard aの望ましい数を5に上げてください。そのあと、scaleサブリソースから2つの数値を取り出して、/root/op-scale/after-scale.txtにspec.replicas=5とstatus.replicas=0の2行で保存してください。
  4. コントローラーがするはずの仕事を代わりに行い、statusを書いてください。kubectl -n op-scale patch shard a --subresource=statusで、status.replicasを5に、status.selectorをapp=shard-aに埋めます。そのあと、scaleサブリソースの出力を/root/op-scale/scale-ready.yamlに保存してください。
  5. /root/op-scale/shard-hpa.yamlにautoscaling/v2のHPA shard-a-hpaを書いてください。scaleTargetRefはapiVersion scale.labhub.io/v1、kind Shard、name aで、minReplicasは2、maxReplicasは10、メトリクスはResource cpuのUtilization 70です。適用して、条件が埋まるまで待った後、/root/op-scale/hpa-status.txtに2行で保存してください。AbleToScale=<상태>/<이유>とREFERENCE=<kubectl get hpa 의 REFERENCE 칸>です(プレースホルダーは順に、状態と理由、そしてkubectl get hpaのREFERENCE列の値です)。
  6. /root/op-scale/block-crd.yamlにCRD blocks.scale.labhub.io(kind Block、複数形blocks)を書きますが、scaleにlabelSelectorPathは入れません。/root/op-scale/block-b.yamlでBlock b(spec.replicasは3)を、/root/op-scale/block-hpa.yamlでHPA block-b-hpa(minReplicasは1、maxReplicasは6、cpuは70)を作成して適用し、条件が埋まったら、/root/op-scale/noselector.txtにScalingActive=<상태>/<이유>とMESSAGE=<조건 메시지>の2行で保存してください(プレースホルダーは順に、状態と理由、そして条件のメッセージです)。
  7. /root/op-scale/relay-crd.yamlにCRD relays.scale.labhub.io(kind Relay、複数形relays)を書きますが、specReplicasPathをわざと.spec.replica(末尾のsを抜いたタイプミス)と書きます。/root/op-scale/relay-r.yamlでRelay r(spec.replicasは2)を作成して適用し、kubectl -n op-scale scale relay r --replicas=4とkubectl -n op-scale get relay r --subresource=scale -o yamlを順に実行して、その結果を/root/op-scale/typo-report.txtに集めてください。scale-rc=<종료 코드>、spec.replicas=<명령 뒤 실제 값>、そして2つ目のコマンドのエラーの文をそのまま含めた行が必要です(プレースホルダーは順に、終了コードと、コマンドの後の実際の値です)。
  8. /root/op-scale/scale-audit.tsvに、<CRD 이름><탭><기대 분류>(プレースホルダーはCRDの名前、タブ、期待する分類です)の3行を書いてください。shards.scale.labhub.ioはok、blocks.scale.labhub.ioはnosel、relays.scale.labhub.ioはokです。/root/op-scale/scale-audit.shは、この表を読んでCRDをok・nosel・brokenに分類し、合っていればOK …を、間違っていればMISMATCH …を標準出力にだけ出力し、1行でも間違っていれば、0ではないコードで終了する必要があります。まず直す前に一度実行して、/root/op-scale/scale-audit-before.txtに残し、そのあと、/root/op-scale/relay-crd-fixed.yamlでタイプミスを直して適用した後、kubectl -n op-scale scale relay r --replicas=4が今度は実際に値を変えるかを確認して、もう一度実行した出力を/root/op-scale/scale-audit.txtに保存してください。

参考

3つのパスで契約を結ぶ

/root/op-scale/shard-crd.yamlにCRD shards.scale.labhub.ioを書いてください。グループscale.labhub.io、kind Shard、複数形shards、バージョンv1を1つ(servedもstorageもtrue)とし、spec.replicas(integer)・status.replicas(integer)・status.selector(string)をスキーマに置いて、subresourcesにstatus: {}とscaleを有効にします。scaleの3つのパスは、それぞれ.spec.replicas・.status.replicas・.status.selectorです。適用した後、クラスターに登録された3つのパスを、/root/op-scale/scale-paths.txtに<이름>=<값>(プレースホルダーは名前と値です)の3行で保存してください(specReplicasPath=...、statusReplicasPath=...、labelSelectorPath=...)。

3つのパスは、APIサーバーに対して、「この型で、望ましい数はどこか、実際の数はどこか、Podを選ぶセレクターはどこか」を伝える契約です。パスはドットで始まるJSONPathで、ドット表記だけが許されます(配列表記は使えません)。登録された値は、kubectl get crd shards.scale.labhub.io -o jsonpathで取り出すと、手で書き写すミスを避けられます。

scaleサブリソースは何として見えるのか

ネームスペースop-scaleを作成し、/root/op-scale/shard-a.yamlにShard aを書いてください。spec.replicasは2です。適用した後、kubectl -n op-scale get shard a --subresource=scale -o yamlの出力をそのまま/root/op-scale/scale-initial.yamlに保存してください。

同じオブジェクトを別の窓から覗き込むのが、サブリソースです。返ってくるのはShardではなくautoscaling/v1のScaleオブジェクトで、spec.replicasが1つとstatus.replicasが1つだけ入っています。まだ誰もstatusを書いていないので、status側の数値が何になるか、予想してみてください。

kubectl scaleが何を変えるのか

kubectl -n op-scale scale shard a --replicas=5で、Shard aの望ましい数を5に上げてください。そのあと、scaleサブリソースから2つの数値を取り出して、/root/op-scale/after-scale.txtにspec.replicas=5とstatus.replicas=0の2行で保存してください。

kubectl scaleは、Deployment専用のコマンドではありません。scaleサブリソースが有効なすべての型で、そのまま動作します。2つの数値が異なる値になる理由を考えてみてください。specReplicasPathはユーザーが書く場所で、statusReplicasPathはコントローラーが書く場所ですが、このクラスターには、Shardを管理するコントローラーがありません。

コントローラーの場所を手で埋める

コントローラーがするはずの仕事を代わりに行い、statusを書いてください。kubectl -n op-scale patch shard a --subresource=statusで、status.replicasを5に、status.selectorをapp=shard-aに埋めます。そのあと、scaleサブリソースの出力を/root/op-scale/scale-ready.yamlに保存してください。

statusは別のエンドポイントなので、普通のpatchでは書けません。--subresource=statusを付けてはじめて、その窓に入れます。セレクターは文字列1行で、ラベルセレクターの構文(키=값(プレースホルダーはキーと値です))をそのまま使います。この値が、あとでHPAがPodを数える基準になります。

HPAをカスタムリソースにかける

/root/op-scale/shard-hpa.yamlにautoscaling/v2のHPA shard-a-hpaを書いてください。scaleTargetRefはapiVersion scale.labhub.io/v1、kind Shard、name aで、minReplicasは2、maxReplicasは10、メトリクスはResource cpuのUtilization 70です。適用して、条件が埋まるまで待った後、/root/op-scale/hpa-status.txtに2行で保存してください。AbleToScale=<상태>/<이유>とREFERENCE=<kubectl get hpa 의 REFERENCE 칸>です(プレースホルダーは順に、状態と理由、そしてkubectl get hpaのREFERENCE列の値です)。

HPAコントローラーは、対象の種類を知らなくてもかまいません。scaleサブリソースさえ読めれば、付けられます。条件はkubectl -n <ns> get hpa <이름> -o jsonpath(プレースホルダーはネームスペースと名前です)で取り出せます。メトリクスサーバーがないこの環境では、TARGETSはずっと不明のままです。それと「対象を読めるか」は、別の話です。

セレクターのパスを抜くと、HPAが静かに止まる

/root/op-scale/block-crd.yamlにCRD blocks.scale.labhub.io(kind Block、複数形blocks)を書きますが、scaleにlabelSelectorPathは入れません。/root/op-scale/block-b.yamlでBlock b(spec.replicasは3)を、/root/op-scale/block-hpa.yamlでHPA block-b-hpa(minReplicasは1、maxReplicasは6、cpuは70)を作成して適用し、条件が埋まったら、/root/op-scale/noselector.txtにScalingActive=<상태>/<이유>とMESSAGE=<조건 메시지>の2行で保存してください(プレースホルダーは順に、状態と理由、そして条件のメッセージです)。

前のステップと何が変わるかが核心です。対象を読むこと(AbleToScale)は成功するのに、スケーリングが始まりません。HPAは目標の使用率を計算するために、「このワークロードのPodがどれなのか」を知る必要があり、その答えを与える場所が、まさに抜かしたそのパスです。条件のメッセージを、そのまま読んでみてください。

成功したと答えながら、何もしない

/root/op-scale/relay-crd.yamlにCRD relays.scale.labhub.io(kind Relay、複数形relays)を書きますが、specReplicasPathをわざと.spec.replica(末尾のsを抜いたタイプミス)と書きます。/root/op-scale/relay-r.yamlでRelay r(spec.replicasは2)を作成して適用し、kubectl -n op-scale scale relay r --replicas=4とkubectl -n op-scale get relay r --subresource=scale -o yamlを順に実行して、その結果を/root/op-scale/typo-report.txtに集めてください。scale-rc=<종료 코드>、spec.replicas=<명령 뒤 실제 값>、そして2つ目のコマンドのエラーの文をそのまま含めた行が必要です(プレースホルダーは順に、終了コードと、コマンドの後の実際の値です)。

このステップは、「何が起きたのか」ではなく、「何も起きていないのに、なぜ成功したように見えるのか」を見る場所です。終了コードと実際の値を別々に書いておくと、両者が食い違っていることが一目でわかります。2つ目のコマンドのエラーは標準エラー出力に出るので、2>&1で一緒に受け取ってください。

静かな故障を見つけ出すチェックリストを作る

/root/op-scale/scale-audit.tsvに、<CRD 이름><탭><기대 분류>(プレースホルダーはCRDの名前、タブ、期待する分類です)の3行を書いてください。shards.scale.labhub.ioはok、blocks.scale.labhub.ioはnosel、relays.scale.labhub.ioはokです。/root/op-scale/scale-audit.shは、この表を読んでCRDをok・nosel・brokenに分類し、合っていればOK …を、間違っていればMISMATCH …を標準出力にだけ出力し、1行でも間違っていれば、0ではないコードで終了する必要があります。まず直す前に一度実行して、/root/op-scale/scale-audit-before.txtに残し、そのあと、/root/op-scale/relay-crd-fixed.yamlでタイプミスを直して適用した後、kubectl -n op-scale scale relay r --replicas=4が今度は実際に値を変えるかを確認して、もう一度実行した出力を/root/op-scale/scale-audit.txtに保存してください。

チェックリストの価値は、「直す前」の出力が証明します。直した後にすべてOKのものだけを残しておくと、この検査が何を見つけられるのか、誰にもわかりません。分類は、CRDの定義を見るだけでは足りません。パスが実際のオブジェクトに届くかどうかは、scaleサブリソースを一度読んでみないとわかりません。スクリプトがファイルを直接書くと、再実行するときに成果物を上書きしてしまうので、標準出力にだけ出力してください。