Slide 1

Slide 1 text

spanner-autoscalerに学ぶ CRD設計パターン 〜自動化と緊急時対応を両立する Kubernetesコントローラーの作り方〜 Platform Engineering Kaigi 2026 / tkuchiki

Slide 2

Slide 2 text

WHOAMI(1) @tkuchiki • Fintech SRE Tech Lead • DBRE •

Slide 3

Slide 3 text

今日話すこと • CRD設計パターン: 役割分割と協調 • オートスケールと緊急時対応の両立 •RBACの設計について • APIバージョニング • テスト戦略 • 入力検証

Slide 4

Slide 4 text

Cloud Spannerについて • Cloud SpannerはProcessing Units(PU)を無停止で増減できる • 公式のオートスケーラー • cloudspannerecosystem/autoscaler(OSS) • Google Cloudのマネージドオートスケーラー • 我々が開発しているオートスケーラー • https://github.com/mercari/spanner-autoscaler spanner-autoscaler

Slide 5

Slide 5 text

なぜKubernetesコントローラーとして実装したか • Kubernetes上でアプリを運用している • Microservice 200+、Spanner Instance 50+ • SpannerAutoscaler導入率: 90+% • Deployment などと同じような運用体験を提供 • YAML/CUE で管理できる • 既存のKubernetes用の仕組みを活用できる • Terraform module・CUEによる抽象化で、導入しやすい仕組みを提供 [1][2]

Slide 6

Slide 6 text

CRD設計パターン

Slide 7

Slide 7 text

Kubernetesコントローラーの基本的な仕組み • コントローラーは、クラスタの状態をapiserver経由でwatchし続ける control loop • Spec(望ましい状態)とStatus(現在の状態)を比較し、差があれば現在の状 態を望ましい状態に近づける • この差分検出(Reconcile)は、「何が起きたか」ではなく「今どうなって いるか」だけを見るので、イベントを取りこぼしても次のwatchで自然に 追従する • 実装には controller-runtime や kubebuilder が使われる

Slide 8

Slide 8 text

CRDとCR • CustomResourceDefinition(CRD) • Kubernetesに新しい種類のリソース(Kind)を追加する型定義 • 例: 「SpannerAutoscalerという種類のリソース」をapiserverに登録する • CustomResource(CR) • CRDで定義された型の実際のインスタンス • kubectl applyで作る対象そのもの • 標準のPod/Deploymentなどと同様、etcdに保存される

Slide 9

Slide 9 text

spanner-autoscalerが提供する基本機能 CPU使用率ベースのオートスケーリング • 平常時のメイン機能 • cronによるスケジュールドスケーリング • •あらかじめ分かっているスパイクに対応

Slide 10

Slide 10 text

2つのCRD/CRと役割分担 SpannerAutoscaleSchedule • schedule • PUをどのくらい増やすか • statusを持たない • spec.targetResourceで SpannerAutoscalerを指定 watch/reconcile SpannerAutoscaler • scaleConfig • minPU, maxPU, ... • scale up/downの間隔 • scale up/downで増減するPU数 ... targetResourceに自分を指定している watch/reconcile SpannerAutoscaleScheduleをwatch Controller • SpannerAutoscaleScheduleの 管理のみ • PUの計算をしない • 外部リソースにアクセスしない Controller • DesiredPUの計算 • 外部リソースへのアクセス • オートスケーリング • 根本的な機能を集約 CPU使用率 UpdateInstance

Slide 11

Slide 11 text

各CRDの役割 • SpannerAutoscaler • CPU使用率ベースの自動スケーリング本体 • Spannerの操作が責務 • SpannerAutoscaleSchedule • cronによるスケジュールドスケーリング • Spannerの状態は持たない • 役割ごとに分割し、リソース間参照(targetResource)で協調させる • SpannerAutoscaler -(watch)-> SpannerAutoscaleSchedule

Slide 12

Slide 12 text

責務を1つのコントローラーに集約する利点と欠点 • 利点 • Spannerへの書き込み経路が1つに集約される • PU計算ロジックが1箇所にまとまるので、テスト・デバッグ・変更がしやすい • SpannerAutoscaleScheduleは薄い実装のままでよく、単一責任が保たれる • 欠点 • SpannerAutoscalerコントローラーの責務が大きくなりがち • targetResourceでの参照は、参照先の存在チェックやwatchの実装が必要

Slide 13

Slide 13 text

OwnerReference方式とscaleTargetRef(targetResource)方式 • OwnerReference方式 • 親が消えたら子も自動でcascade deleteされる • 作成に親のUIDが必要となり、作成順序に制約 • scaleTargetRef(targetResource)方式 • 正式名称は不明だが HPAでは CrossVersionObjectReference と定義 • 独立して作成・削除することを許容 • 参照先が存在しない時はエラーを返す • 存在チェック・watch・参照先消失時のハンドリングを自分で実装する必要がある

Slide 14

Slide 14 text

緊急時対応

Slide 15

Slide 15 text

オートスケーラーにおける一般的な課題 • 観測してから動くため、急激な負荷上昇には反応が追いつかないことが ある • 追いつかない間は、オートスケーラー自身の設定を変えて強制的にス ケールさせる運用が必要になることがある • 例: HPAのminReplicasを引き上げる • この操作を常に自由に行えるようにしておくのは危険 • 誤操作や、戻し忘れによる過剰なリソース確保につながる

Slide 16

Slide 16 text

オートスケーラーと緊急時対応の両立 • 緊急時専用のCRD「SpannerManualScaling」で両立させている • SpannerAutoscalerを直接編集させない方針 • 現在の値が元の値なのか一時的な値なのかがわからない • kubectlで作成するだけでPUを一時的に固定できる • specはimmutable、誤操作でのすり替えを防ぐ • expiresAtで期限を設定し、期限切れで自動的にSpannerAutoscalerの自動制御へ 戻る • 戻し忘れが起きない

Slide 17

Slide 17 text

SpannerManualScalingの設定例

Slide 18

Slide 18 text

権限管理: 独自RBAC vs 標準RBAC • ArgoCDのような独自RBAC方式 • 細かい粒度で権限を制御できる • kubectl rollout restart deployment の実行だけ許可、といった制御も可能 • 独自の権限モデルを実装・運用する必要があり複雑 • spanner-autoscalerの方式(役割ごとにCRDを分割) • 標準のKubernetes RBAC・Audit Loggingがそのまま使える • 独自の権限モデルの実装・保守が不要 • 細かい粒度で権限を制御できない

Slide 19

Slide 19 text

より安全に運用するための工夫 • コントローラーに、SpannerManualScaling使用時に スケールダウンを拒否する起動オプションを実装 --reject-manual-scaledown=true • •常時権限を付与するリスクを低減 •緊急時の対応も迅速に行えるようにする

Slide 20

Slide 20 text

Manual Scalingの動作例 PU: 1000 Manual Scaling Controller PU: 4000 Manual Scaling Controller Create CR1(minPU:2000) Create CR3 minPU:3000 OK Reject Delete CR2 PU: 2000 OK Create CR2(minPU:4000) PU: 2000 OK PU: 4000 Create CR3(minPU:3000) OK Manual Scaling Controller PU: 3000 Manual Scaling Controller

Slide 21

Slide 21 text

AIによるオペレーションの可能性 • AIにはcreateのみ許可という制御が、標準のRBACだけで実現可能 • 例: 夜間にスケールアウトイベントが複数回発生、かつ、CPU使用率90% がN分継続している、を条件にアラートをトリガー • アラートのトリガーを条件にAIにスケールアウトさせる • オートスケーラーのスケールアウトが間に合っていないと判断 • 実行内容は標準の監査ログに記録されるため追跡が容易 • より堅牢にしたい場合は専用CRDの実装+独自RBACが必要になるかも

Slide 22

Slide 22 text

上書き処理の実装Tips • ManualScalingのような「手動上書き」を実装するときの一般化できる 手法 • 2つの望ましい値を比較して大きい方を採用するロジックにしない • 計算が複雑になりがち & テストも増えがち • Reconcileの中で有効な上書きがあるかを先に判定し、あればその場で 処理を終了(早期リターン) • 上書きが無い場合だけ、通常のロジックに進む

Slide 23

Slide 23 text

APIバージョニング

Slide 24

Slide 24 text

APIバージョニング(v1alpha1 -> v1beta1) • 既存フィールドの型・意味を変えるなど、後方互換性を保てない変更を行 うときにバージョンを変更する • Hub-and-Spokeモデル • v1beta1をHubに、v1alpha1はHubとの変換だけ持つ(組合せ爆発しない) • 新しいバージョンから古いバージョンに戻す変換では、一部の情報が失わ れることを意図的に許容している • 例: パーセント指定のスケールダウンは、旧バージョンでは固定のノード 数に置き換わる

Slide 25

Slide 25 text

Hub-and-Spokeモデル [3]

Slide 26

Slide 26 text

テスト戦略

Slide 27

Slide 27 text

カスタムコントローラーを作る際のテストの選択肢 • client/fakeという「インメモリの疑似クライアント」がある • OpenAPI validationは実行されず、webhookも呼ばれない • Generation/ResourceVersionも正しく振る舞わない • 「When in doubt, it's almost always better not to use this package and instead use envtest.」 [4] • もう少し本物に近い検証がしたい場合、kindなどの実クラスタでのe2e testと、envtestがある [4]

Slide 28

Slide 28 text

envtestとは • controller-runtimeが提供する、テスト用に本物のetcd+kube-apiserverを起動する Goライブラリ • kubelet・controller-manager・kube-schedulerは含まれない • Podリソースは作れるが、実際にコンテナとして実行はされない • バイナリはsetup-envtestという別のCLIツールで取得する(Kubernetesバージョ ンごとに固定) • 本物のapiserverが動くため、CRDのvalidation/defaulting/admission webhook・ status subresourceなど、実際のAPI層の振る舞いを検証できる [5]

Slide 29

Slide 29 text

外部システム依存コントローラーのテスト • Spanner/Cloud MonitoringなどのKubernetes外のリソースのテストは自 分で用意する必要がある • 実リソース、エミュレータ、モック • 厳密さ:実リソース > エミュレータ > モック • コスト(利用料金・実行速度・計算リソースも含む):実リソース > エミュ レータ > モック • spanner-autoscalerはエミュレータ+モック

Slide 30

Slide 30 text

なぜ自作エミュレータが必要だったか • 以前は機能追加やバグ修正の度に実リソースを用意していた • 動作検証のためにSpannerに負荷をかける必要があり大変 • 公式SpannerエミュレータはPU変更(UpdateInstance)をサポートしてい ない • Cloud Monitoringは公式エミュレータ自体が存在しない • ユースケースにあったエミュレータを実装することで問題を解決

Slide 31

Slide 31 text

自作エミュレータの機能 現在のPUを保持 固定のCPU使用率を返す UpdateInstance Spanner Controller エミュレータ CPU使用率を5,10,40,... のように PUを取得 PUを考慮した 返す値を問い合わせごとに変える CPU使用率を返す ↓ Spannerに負荷をかけている状況を再現 Workloadモード シナリオモード Monitoringエミュレータ Staticモード

Slide 32

Slide 32 text

実例: defaulting webhookのバグ

Slide 33

Slide 33 text

AIエージェントの動作検証基盤としての活用 • 自作エミュレータを、AIエージェントによる操作の検証基盤としても活用 できる • defaulting webhookのバグの再現と修正で実際に利用 • 本物のGoogle Cloudのリソースに影響を与えず、状態もリセットしやすい • 本物のSpannerやCloud Monitoringを使うよりもテストシナリオを用意し やすい • AIエージェントに検証も含めた実装を任せやすい

Slide 34

Slide 34 text

入力検証: Webhook vs ValidatingAdmissionPolicy

Slide 35

Slide 35 text

ValidatingAdmissionPolicy(VAP) • Kubernetes組み込みの検証機構(1.30 GA) • Admission Webhookに対する宣言的・in-processな代替として位置づけられて いる • CELで条件式を書くだけでバリデーションのルールを宣言的に定義できる • apiserver内で直接評価されるため、Webhookのような外部プロセス呼び出し が不要で、apiserverが動いていれば実行し続けられる • CELはhost application(apiserver)が渡したデータにしかアクセスできない設計 (non-Turing complete)なので、できることに制約がある

Slide 36

Slide 36 text

VAPとWebhookの使い分け • VAP • object/oldObjectの比較(Update前後の自分自身、immutable化など) • params/namespaceObject(paramRefで事前に固定したリソース)を使った 形式チェック • Webhook • フィールド値に応じて動的に別リソースを参照する(paramRefは静的固定 のため不可) • 現在時刻を参照する(CELの変数に時刻を返すものがない)

Slide 37

Slide 37 text

spanner-autoscalerでのVAP使用例

Slide 38

Slide 38 text

まとめ

Slide 39

Slide 39 text

spanner-autoscalerを提供することで得られている恩恵 • Platform Engineering/SREチームが介在せず運用できている • コントローラー、Terraform module、CUE の開発・運用に集中すれば 良い • プロダクトチームへのセルフサービス化が容易 • 導入・設定がプロダクトチーム内で完結するのでオーナーシップ を損なわない • YAML/CUE を書くだけで良い

Slide 40

Slide 40 text

今日話したこと • 責務を1つのコントローラーに集約するかは利点(ロジックの集約)と欠点 (責務の肥大化)のトレードオフなので、要件に合わせて選ぶ • 権限を分けたい単位でCRDを分割することで、独自の権限管理を不要に する事例を紹介 • 外部システムに依存するコントローラーは、ロジックのみの高速なテス トと、実際の外部呼び出しまで検証する厳密なテストを分けると、速さ と厳密さを両立できる

Slide 41

Slide 41 text

Appendix: 前提知識

Slide 42

Slide 42 text

Kubernetesコントローラーの基本的な仕組み(client-go内部) [6]

Slide 43

Slide 43 text

kubebuilderとは • CRD・コントローラー・Admission Webhookのひな形を生成するフレームワーク • controller-runtime/controller-toolsの上に構築されている • kubebuilder init でプロジェクト、kubebuilder create api でAPIを生成 •api/v1/_types.go(Spec/Status)とinternal/controller/ _controller.goが生成される • コード中の+kubebuilderマーカーをcontroller-genが処理し、CRD manifestや RBACを自動生成 [7]

Slide 44

Slide 44 text

controller-runtimeとは • コントローラーを実装するためのGoライブラリ群(kubebuilder/Operator SDKが利用) • Manager: Client・Cache・Schemeなど、コントローラーが共有する依存関 係を提供 • Reconciler: 実際の同期ロジック本体。対象オブジェクトの名前を受け取 り、都度最新状態を取りに行く • Client/Cache: apiserverへの読み書き(Client)とローカルキャッシュからの読 み取り(Cache) [8]

Slide 45

Slide 45 text

kubebuilderとcontroller-runtimeの関係 •kubebuilderが生成するコードは、内部で controller-runtimeに依存している •kubebuilder = ひな形を生成するツール、 controller-runtime = 生成されたコードが実際 に使うライブラリ、という上下関係 [9][10]

Slide 46

Slide 46 text

kubebuilderとcontroller-runtimeの関係 Controller (*_controller.go) 依存 controller-runtime • Manager • Reconciler • Client Watch/Reconcile プロジェクト生成 kubebuilder CRD (*_types.go) リソース定義を登録 kube-apiserver

Slide 47

Slide 47 text

参考文献 (1/3) • [1] Terraformモジュールを使ったCloud Spannerの設定標準化の取り組み • https://engineering.mercari.com/blog/entry/20230615-cloudspanner-configurationstandardization/ • [2] CUEを使用したKubernetesマニフェスト管理 • https://engineering.mercari.com/blog/entry/20220127-kubernetes-configurationmanagement-with-cue/ • [3] Kubebuilder Book: Hubs, spokes, and other wheel metaphors • https://book.kubebuilder.io/multiversion-tutorial/conversion-concepts.html • [4] controller-runtime: fake client package docs • https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/client/fake

Slide 48

Slide 48 text

参考文献 (2/3) • [5] Kubebuilder Book: Configuring envtest for integration tests • https://book.kubebuilder.io/reference/envtest.html • [6] sample-controller: client-go controller interaction diagram • https://github.com/kubernetes/sample-controller/blob/master/docs/ controller-client-go.md • [7] Kubebuilder Book: Quick Start • https://book.kubebuilder.io/quick-start.html • [8] Kubebuilder Book: Controller Overview • https://book.kubebuilder.io/cronjob-tutorial/controller-overview.html

Slide 49

Slide 49 text

参考文献 (3/3) • [9] kubebuilder: GitHub README •https://github.com/kubernetes-sigs/kubebuilder • [10] controller-runtime: GitHub README •https://github.com/kubernetes-sigs/controller-runtime