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

CNPA — クラウドネイティブプラットフォームエンジニアリングアソシエイト

KubernetesのAPIはなぜプラットフォームの共通語になったのか

TT Labで続きを見る

一言でいうと

Kubernetesが勝ったのは、コンテナオーケストレーションの競争ではなく、API仕様の競争でした。宣言的なリソースと調整コントローラーという一対のパターンがコンテナの外にまで広がり、そのため、プラットフォームAPIを新しく作るときはCRDが標準の選択肢になりました。

なぜ必要なのか

プラットフォームチームが「サービスを1つ立ち上げる」ことを簡単にしようとすると、たいてい次のような道をたどります。

  1. Wikiに手順を書きます → 誰も最新の状態に保ちません。
  2. シェルスクリプトを作ります → 実行環境ごとに結果が違い、失敗すると途中の状態が残ります。
  3. 社内のWebアプリを作ります → 状態の保存、認証、監査、リトライ、並行制御をすべて自前で実装する必要があります。しかも、そのアプリが新たなSPOFになります。

3つ目を最後まで作ってみると、結局何を作り直しているのかに気づきます。状態ストア、楽観的同時実行制御、認証・認可、監査ログ、監視(watch)、そして調整ループ。どれもKubernetes APIサーバーがすでに持っているものばかりです。

そこで方向が逆になります。プラットフォームAPIを新しく作るのではなく、Kubernetes APIを拡張します。CRDを登録した瞬間に無料でついてくるものは、これだけあります。

これが「Kubernetes APIがプラットフォームの共通言語だ」という言葉の実質です。新しい語彙(CRD)を定義しつつ、文法(API規約)は誰もがすでに知っているものを使うわけです。

どう動くのか

CRD+コントローラー=プラットフォームAPI

2つの部品が必要です。

CRDは語彙を定義します。group、version、kind、スコープ(NamespacedかClusterか)、そしてOpenAPI v3スキーマです。スキーマの役割は、思ったより大きいです。

spec:
  versions:
    - name: v1alpha1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              required: [image]
              properties:
                image:    { type: string }
                replicas: { type: integer, default: 2, minimum: 1, maximum: 10 }
                public:   { type: boolean, default: false }

コントローラーは、その語彙に意味を与えます。WebServiceというCRを見て、Deployment、Service、HPA、NetworkPolicyを作ってくれる調整ループです。CRDだけあってコントローラーがなければ、そのCRは「構造が検証される設定ファイル」にとどまります。それはそれで役に立ちますが、プラットフォームAPIではありません。

スコープの選択も試験の常連です。Namespacedはテナントの境界の中に収まり、ネームスペースのRBACがそのまま効きます。Clusterスコープは名前が全体で共有されるため、テナント同士で名前が衝突し、RoleではなくClusterRoleでしか権限を与えられません。テナントが作るリソースは、ほぼ常にNamespacedであるべきです。

抽象化が漏れる瞬間

優れたプラットフォームAPIは「必要なことだけを尋ねます」。WebServiceは、イメージと、必要ならreplicas程度だけを尋ね、残り(ラベル規約、セキュリティコンテキスト、リソースのデフォルト値、オブザーバビリティ用のアノテーション、ネットワークポリシー)はコントローラーが埋めます。

ところが、必ず漏れる日が来ます。

このとき「それはサポートしていません」と答えると、そのチームはプラットフォームを捨てて、生のYAMLに戻ります。一度出ていくと戻ってきません。だから、エスケープハッチ(escape hatch)を設計に最初から組み込んでおく必要があります。

エスケープハッチ 形態 リスク
部分オーバーライド spec.podOverridesのような自由フィールド 何でも入れられると、抽象化が無意味になります
拡張ポイント extraEnv、extraVolumes、nodeSelectorだけを許可 リストの管理コスト
レンダリング後の離脱 生成されたマニフェストをコピーして直接管理 その後のプラットフォーム改善を受けられません

バランスの取りどころは次のとおりです。エスケープハッチは必要ですが、それを使ったことが目に見える必要があります。オーバーライドを使ったサービスにラベルやステータス条件を残しておけば、プラットフォームチームは「この機能は5つのチームがオーバーライドで回避している」というシグナルを受け取り、正式な機能へとプロモーションできます。これが、プラットフォームを製品として回すフィードバックループです。

現場での姿

筆者のホームラボで、この問題がまさにそのままの形で現れました。GPU Feature Discoveryがノードにカード情報を自動でラベル付けします。RTX 3090(24576MB、ampere)、5090(32607MB、blackwell)、4070 Laptop(8188MB、ada-lovelace)は2枚です。ところが、Podがnvidia.com/gpu: 1だけを要求すると、32GBが必要な学習が8GBのノートPC用GPUに載ってしまうことがあります。Kubernetesにとっては、どちらも「GPU 1つ」だからです。

GFDが付けたgpu.memoryラベルは文字列なので、「24GB以上」のような比較セレクターは使えません。そこで、意味ベースのラベルを自分で付けました。gpu.homelab/tier=xlarge|large|smallとgpu.homelab/vram=32g|24g|8gです。これでワークロードは、nodeSelector: gpu.homelab/tier: xlargeで自分に合う等級を選べます。

この1行が、プラットフォームAPI設計の典型です。下層の物理的な事実(カードのモデル名、メモリのバイト数)をそのまま公開せず、ユーザーが判断できる語彙(tier)に翻訳しました。同時にエスケープハッチも残っています。本当に特定のカードが必要なら、GFDの元のラベルで直接セレクトできます。優れた抽象化とは、下の層を隠すものではなく、覆いつつ開けておくものです。

次のラボですること

CRDwebservices.platform.labhub.ioを実際のクラスターに作成し、スキーマ違反が即座に拒否されることと、デフォルト値がサーバーで補われることを自分の目で確認します。続いて、ネームスペースとResourceQuotaとLimitRangeでテナントの境界を引き、RBACでセルフサービスの権限を与えたうえで、ほかのテナントには手を出せないことをkubectl auth can-iで証明します。