Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Extension API Server による Kubernetes API の拡張 / K...

Preferred Networks
August 07, 2024
370

Extension API Server による Kubernetes API の拡張 / Kubernetes Meetup Tokyo #66

Kubernetes Meetup Tokyo #66 での発表資料です。
PFN では機械学習基盤に Kubernetes の Extension API Server を活用しており、その仕組みや具体的な利用方法についてご紹介します。

Preferred Networks

August 07, 2024
Tweet

More Decks by Preferred Networks

Transcript

  1. 3 PFNの事業: AI技術のバリューチェーンを垂直統合 ソリューション・製品 計算基盤 AIチップ PFNは、チップ、計算基盤、生成AI・基盤モデル、ソリューション・製品まで、AI技術のバリューチェーンを垂直統合し、 ソフトウェアとハードウェアを高度に融合することで、競争力の高い技術の開発および産業応用を進めています。 生成AI・基盤モデル 様々な産業・消費者向けのソリューション・製品群

    MN-Core™ MN-Core™ 2 GPUクラスタ MN-3 (MN-Core™ クラスタ) 大規模言語モデル マルチモーダル基盤モデル (2024年リリース予定) 次世代機 MN-4 (MN-Core™2 クラスタ MN-Core™ 2による 計算能力のクラウド提供 (2024年開始予定) 物質の電子状態・ エネルギー計算モデル PLaMo-13B Preferred Potential (PFP)
  2. 4 誰もが MN-Core™ シリーズを利用できる AI クラウドサービス Preferred Computing Platform Preferred

    Computing Platform(以下、PFCP)は株式会社 Preferred Networks(以下、PFN)が構築 運用する深層学習・AI ワークロード向けのクラウドサービスです。PFNが開発する独自アクセラレータであ るMN-Core™ シリーズを唯一利用でき、最先端の性能と効率性を備えています。 強力な計算ノード MN-Core 2 ボードを4基搭載した MN-Core 2 サー バを複数専有して利用できます。すべてのノードは 深層学習に最適化された高速なネットワークで相互 に接続されています。NVIDIA GPU を搭載したノー ドも順次提供予定です。 フルマネージドサービス 深層学習・AI ワークロード向けに拡張された Kubernetes クラスタをマルチテナントで利用でき ます。大規模分散学習から推論サーバの高可用な運 用まで幅広く行なえます。ワークロードの状況を観 測するためのマネージドなモニタリングサービスも 付随しています。 MN-Core 2 サーバの構成 アクセラレータ 8 MN-Core 2 boards x 8 CPU Intel Xeon SPR 8480+ 56C x 2 Memory DDR5 64GB x 16 (2TB) Interconnect NVIDIA ConnectX-6 (100GbE) × 2 OS コンテナ ワークロード ベアメタル
  3. 5 PFCP のクラスタにおける課題 クラスタ テナントB 専有ノード 専有ノード テナントA 専有ノード 専有ノード

    テナントC 専有ノード • クラスタがマルチテナントであり、テナントには専有ノードが割り当てられる • 専有ノードはテナント名を値にもつラベルと Toleration で制御されている • 利用者に自身のテナントに割り当てられた専有ノードだけをリストさせたい (他のテナントの専有ノードは参照できないようにしたい) • Node リソースを部分的に参照させる方法が Kubernetes にはない
  4. 6 PFCP のクラスタにおける課題 クラスタ テナントB 専有ノード 専有ノード テナントA 専有ノード 専有ノード

    テナントC 専有ノード • クラスタがマルチテナントであり、テナントには専有ノードが割り当てられる • 専有ノードはテナント名を値にもつラベルと Toleration で制御されている • 利用者に自身のテナントに割り当てられた専有ノードだけをリストさせたい (他のテナントの専有ノードは参照できないようにしたい) • Node リソースを部分的に参照させる方法が Kubernetes にはない kubectl get nodes で 見える範囲
  5. 7 PFCP のクラスタにおける課題 クラスタ テナントB 専有ノード 専有ノード テナントA 専有ノード 専有ノード

    テナントC 専有ノード • クラスタがマルチテナントであり、テナントには専有ノードが割り当てられる • 専有ノードはテナント名を値にもつラベルと Toleration で制御されている • 利用者に自身のテナントに割り当てられた専有ノードだけをリストさせたい (他のテナントの専有ノードは参照できないようにしたい) • Node リソースを部分的に参照させる方法が Kubernetes にはない テナントA の利用者には ここだけを参照させたい
  6. 8 ReservedNode リソースの導入 • Cluster-wide な ReservedNode というリソースで利用者が所属するテナント の専有ノードのみを取得できるようにする •

    ReservedNode リソースの内容は Node リソースと完全に同じであり、Node リソースのサブセットとして動作する。Node リソースと同様に変更も不可 • Node リソースの内容はそのままにサブセットの機能だけを提供すればよいた め、データの保持やリソース間の状態の同期が必要になる CRD と Controller の組み合わせではなく、より柔軟性が高い Extension API Server の仕組みを 採用 $ kubectl get reservednode NAME STATUS ROLES AGE VERSION node001 Ready <none> 27d v1.29.5 node002 Ready <none> 28h v1.29.5 node003 Ready <none> 58d v1.29.5
  7. 9 Extension API Server • Extension API Server とは、Kubernetes の

    Aggregation Layer と連携して 独自のリソースを提供する外部 API を指す • 独自のリソースだけを管理することに特化した kube-apiserver のようなも の、と考えると分かりやすい • いくつかの実装が公開されている ◦ https://github.com/kubernetes/sample-apiserver ▪ Extension API Server をこれから実装する人向けのサンプル実装 ▪ 実装に利用するパッケージの使い方などを知るのに便利 ◦ https://github.com/kubernetes-sigs/metrics-server ▪ Autoscaling 用途のためのメトリクスをリソースとして提供する ▪ sample-apiserver の次に読むとより実践的な実装方法が分かる
  8. 10 Aggregation Layer kube-apiserver Aggregation Layer Extension API Server Resource

    API Resource API kubectl APIService リソースの操作 リソースの操作 独自リソースと API エンドポイントの登録 • Extension API Server との連携は kube-apiserver の内部に実装された Aggregatgion Layer で処理される。これにより外部 API が提供する独自リ ソースを Kubernetes のリソースとして扱えるようになる • kube-apiserver が API Proxy のように振る舞うと考えると分かりやすい • 外部 API の情報は APIService と呼ばれるリソースを使用して登録する
  9. 11 APIService リソースによる独自リソースの登録 apiVersion: apiregistration.k8s.io/v1 kind: APIService metadata: name: v1.node.example.com

    spec: group: node.example.com version: v1 service: name: extension-apiserver namespace: extension-apiserver-system groupPriorityMinimum: 100 versionPriority: 100 APIService リソースには、登録する独自リソースの Group/Version および API エンドポイントとなる Service リソースの情報を定義する。 リソースの Group/Version API エンドポイントとなる Service リソースの情報
  10. 14 Aggregation Layer との連携フロー kube-apiserver (Aggregation Layer) Extension API Server

    kubectl リソースの操作を要求 認証と認可の処理
  11. 15 Aggregation Layer との連携フロー kube-apiserver (Aggregation Layer) Extension API Server

    kubectl リソースの操作を要求 認証と認可の処理 HTTP ヘッダに設定したユーザ情報と ともにリソースの操作を要求 ユーザ情報の取得と検証
  12. 16 Aggregation Layer との連携フロー kube-apiserver (Aggregation Layer) Extension API Server

    kubectl リソースの操作を要求 認証と認可の処理 HTTP ヘッダに設定したユーザ情報と ともにリソースの操作を要求 ユーザ情報の取得と検証 SubjectAccessReview API による権限の検証
  13. 17 Aggregation Layer との連携フロー kube-apiserver (Aggregation Layer) Extension API Server

    kubectl リソースの操作を要求 認証と認可の処理 HTTP ヘッダに設定したユーザ情報と ともにリソースの操作を要求 ユーザ情報の取得と検証 SubjectAccessReview API による権限の検証 リソースの操作を実行
  14. 18 Aggregation Layer との連携フロー kube-apiserver (Aggregation Layer) Extension API Server

    kubectl リソースの操作を要求 認証と認可の処理 HTTP ヘッダに設定したユーザ情報と ともにリソースの操作を要求 ユーザ情報の取得と検証 SubjectAccessReview API による権限の検証 リソースの操作を実行 操作の結果を応答 操作の結果を応答
  15. 19 Extension API Server の開発の流れ Extension API Server の開発はおおまかに次のような流れで行う。 1.

    Go で独自リソースの型を定義する 2. k8s.io/code-generator を使用して、型定義から Kubernetes リソースと しての処理に必要な関連コードを生成する 3. 定義した独自リソースの型定義と k8s.io/apiserver の構造体を利用して Extension API Server の処理を実装する
  16. 20 独自リソースの型定義 最初に pkg/apis/node/types.go のようなファイルパスに独自リソースの型を 定義する。このあたりは Custom Resource を使った時と同じ。 //

    +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // ReservedNode is an example type with a spec and a status. type ReservedNode struct { metav1.TypeMeta metav1.ObjectMeta Spec corev1.NodeSpec Status corev1.NodeStatus } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // ReservedNodeList is a list of ReservedNode objects. type ReservedNodeList struct { metav1.TypeMeta metav1.ListMeta Items []ReservedNode }
  17. 21 独自リソースの型定義 最初に pkg/apis/node/types.go のようなファイルパスに独自リソースの型を 定義する。このあたりは Custom Resource を使った時と同じ。 //

    +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // ReservedNode is an example type with a spec and a status. type ReservedNode struct { metav1.TypeMeta metav1.ObjectMeta Spec corev1.NodeSpec Status corev1.NodeStatus } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // ReservedNodeList is a list of ReservedNode objects. type ReservedNodeList struct { metav1.TypeMeta metav1.ListMeta Items []ReservedNode } リソースの型定義
  18. 22 独自リソースの型定義 最初に pkg/apis/node/types.go のようなファイルパスに独自リソースの型を 定義する。このあたりは Custom Resource を使った時と同じ。 //

    +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // ReservedNode is an example type with a spec and a status. type ReservedNode struct { metav1.TypeMeta metav1.ObjectMeta Spec corev1.NodeSpec Status corev1.NodeStatus } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // ReservedNodeList is a list of ReservedNode objects. type ReservedNodeList struct { metav1.TypeMeta metav1.ListMeta Items []ReservedNode } コード生成用タグ
  19. 23 関連コードの生成 • 定義した独自リソースの型に対して k8s.io/code-generator を実行し、 Kubernetes のリソースとして扱うために必要なファイルを生成する • code-generator

    には使い方の説明がないため、リポジトリにあるサンプ ルスクリプトの update-codegen.sh を参考にして実行する pkg/apis ├── generated │ └── openapi │ └── zz_generated.openapi.go └── node ├── doc.go ├── register.go ├── types.go └── zz_generated.deepcopy.go $ ./hack/update-codegen.sh code-generator により 生成されたファイル 型を定義したファイル
  20. 24 Extension API Server の機能の実装 • 生成したコードを利用して Extension API Server

    の機能部分を実装する • k8s.io/apiserver パッケージで提供されている構造体を使用すると便利 • パッケージに含まれるよく使う構造体として GenericAPIServer や Store などがある Extension API Server GenericAPIServer Store リソースの操作要求や ユーザー情報の連携 データストアの操作
  21. 25 • k8s.io/apiserver/pkg/server に用意されている kube-apiserver のような API サーバーを作るための構造体 • Extension

    API Server を実装する場合はこれをベースとして、独自リソー スの処理を追加していくような形になる • kube-apiserver の Aggregation Layer との連携処理やユーザ情報の取得 などもこの構造体の内部でうまく処理してくれる GenericAPIServer
  22. 26 GenericAPIServer の使用例 scheme = runtime.NewScheme() config := genericapiserver.NewConfig(serializer.NewCodecFactory(scheme)) informerFactory

    := informers.NewSharedInformerFactory(client, 300*time.Second) server, err := config.Complete(informerFactory).New("extension-apiserver", server.NewEmptyDelegate()) if err != nil { return nil, err } server.PrepareRun().Run(ctx.Done()) リソース管理のための Scheme と GenericAPIServer 用の設定を生成
  23. 27 GenericAPIServer の使用例 scheme = runtime.NewScheme() config := genericapiserver.NewConfig(serializer.NewCodecFactory(scheme)) informerFactory

    := informers.NewSharedInformerFactory(client, 300*time.Second) server, err := config.Complete(informerFactory).New("extension-apiserver", server.NewEmptyDelegate()) if err != nil { return nil, err } server.PrepareRun().Run(ctx.Done()) Informer を管理する Informer Factory を生成
  24. 28 GenericAPIServer の使用例 scheme = runtime.NewScheme() config := genericapiserver.NewConfig(serializer.NewCodecFactory(scheme)) informerFactory

    := informers.NewSharedInformerFactory(client, 300*time.Second) server, err := config.Complete(informerFactory).New("extension-apiserver", server.NewEmptyDelegate()) if err != nil { return nil, err } server.PrepareRun().Run(ctx.Done()) 設定と Informer Factory から GenericAPIServer を生成して起動
  25. 29 • k8s.io/apiserver/pkg/registry/generic/registry に用意されている独自 リソースの CRUD 操作を提供する構造体 • Create/Update/Delete の各種操作に対して

    Strategy と呼ばれる Interface を実装して任意の処理を実行したり、Hook を使用して前後処 理を追加したりできる • デフォルトのデータストアとして内部で etcd が使われる Store
  26. 30 Store の使用例 store := &genericregistry.Store{ NewFunc: func() runtime.Object {

    return &v1.ReservedNode{} }, NewListFunc: func() runtime.Object { return &v1.ReservedNodeList{} }, PredicateFunc: selectionPredicate, DefaultQualifiedResource: v1.Resource("reservednodes"), SingularQualifiedResource: v1.Resource("reservednode"), CreateStrategy: strategy, UpdateStrategy: strategy, DeleteStrategy: strategy, TableConvertor: rest.NewDefaultTableConvertor(v1.Resource("reservednodes")), } リソースの新しいインスタンスを返す関数
  27. 31 Store の使用例 store := &genericregistry.Store{ NewFunc: func() runtime.Object {

    return &v1.ReservedNode{} }, NewListFunc: func() runtime.Object { return &v1.ReservedNodeList{} }, PredicateFunc: selectionPredicate, DefaultQualifiedResource: v1.Resource("reservednodes"), SingularQualifiedResource: v1.Resource("reservednode"), CreateStrategy: strategy, UpdateStrategy: strategy, DeleteStrategy: strategy, TableConvertor: rest.NewDefaultTableConvertor(v1.Resource("reservednodes")), } Label Selector や Field Selector の条件を設定
  28. 32 Store の使用例 store := &genericregistry.Store{ NewFunc: func() runtime.Object {

    return &v1.ReservedNode{} }, NewListFunc: func() runtime.Object { return &v1.ReservedNodeList{} }, PredicateFunc: selectionPredicate, DefaultQualifiedResource: v1.Resource("reservednodes"), SingularQualifiedResource: v1.Resource("reservednode"), CreateStrategy: strategy, UpdateStrategy: strategy, DeleteStrategy: strategy, TableConvertor: rest.NewDefaultTableConvertor(v1.Resource("reservednodes")), } リソース名の設定 (複数形/単数形)
  29. 33 Store の使用例 store := &genericregistry.Store{ NewFunc: func() runtime.Object {

    return &v1.ReservedNode{} }, NewListFunc: func() runtime.Object { return &v1.ReservedNodeList{} }, PredicateFunc: selectionPredicate, DefaultQualifiedResource: v1.Resource("reservednodes"), SingularQualifiedResource: v1.Resource("reservednode"), CreateStrategy: strategy, UpdateStrategy: strategy, DeleteStrategy: strategy, TableConvertor: rest.NewDefaultTableConvertor(v1.Resource("reservednodes")), } Create/Update/Delete の処理に対する Strategy の設定
  30. 34 Store の使用例 store := &genericregistry.Store{ NewFunc: func() runtime.Object {

    return &v1.ReservedNode{} }, NewListFunc: func() runtime.Object { return &v1.ReservedNodeList{} }, PredicateFunc: selectionPredicate, DefaultQualifiedResource: v1.Resource("reservednodes"), SingularQualifiedResource: v1.Resource("reservednode"), CreateStrategy: strategy, UpdateStrategy: strategy, DeleteStrategy: strategy, TableConvertor: rest.NewDefaultTableConvertor(v1.Resource("reservednodes")), } テーブル出力時の変換用関数
  31. 35 GenericAPIServer と Store の紐付け apiGroup := genericapiserver.NewDefaultAPIGroupInfo(groupName, Scheme, metav1.ParameterCodec,

    Codecs) storage:= map[string]rest.Storage{} storage["reservednodes"] = store apiGroup.VersionedResourcesStorageMap["v1"] = storage if err := server.InstallAPIGroup(&apiGroup); err != nil { return nil, err } • GenericAPIServer と Store の用意ができたらそれらを紐づける • 最初にリソース名と Store を APIGroup として紐づける • 次に APIGroup を GenericAPIServer にインストールする
  32. 36 GenericAPIServer と Store の紐付け apiGroup := genericapiserver.NewDefaultAPIGroupInfo(groupName, Scheme, metav1.ParameterCodec,

    Codecs) storage:= map[string]rest.Storage{} storage["reservednodes"] = store apiGroup.VersionedResourcesStorageMap["v1"] = storage if err := server.InstallAPIGroup(&apiGroup); err != nil { return nil, err } • GenericAPIServer と Store の用意ができたらそれらを紐づける • 最初にリソース名と Store を APIGroup として紐づける • 次に APIGroup を GenericAPIServer にインストールする APIGroup でリソース名と Store を紐付け
  33. 37 GenericAPIServer と Store の紐付け apiGroup := genericapiserver.NewDefaultAPIGroupInfo(groupName, Scheme, metav1.ParameterCodec,

    Codecs) storage:= map[string]rest.Storage{} storage["reservednodes"] = store apiGroup.VersionedResourcesStorageMap["v1"] = storage if err := server.InstallAPIGroup(&apiGroup); err != nil { return nil, err } • GenericAPIServer と Store の用意ができたらそれらを紐づける • 最初にリソース名と Store を APIGroup として紐づける • 次に APIGroup を GenericAPIServer にインストールする APIGroup を GenericAPIServer にインストール
  34. 38 ReservedNode リソースの実装 • データストアは使用せず、Node リソースのサブセットだけを扱うため、 キャッシュを持つ独自の Store を実装 •

    Informer を使用して Node リソースの変更を検知し、ReservedNode リ ソースを生成して Store の内部にあるキャッシュに保存する • Aggregation Layer から連携されたユーザ情報をもとに専有ノードを特定 し、キャッシュに保存された ReservedNode リソースを返す Extension API Server Store Cache / Indexer Node Informer GenericAPIServer リソースの取得要求 ユーザー情報からテナントを特 定し、リソースを取得 ReservedNode リソースへの 変換と保存 Node リソースの変更検知
  35. 39 独自 Store の実装 • Store 用の構造体を定義し、k8s.io/apiserver/pkg/registry/rest に定義 されている各種 Interface

    を実装すれば etcd に依存しない独自の Store として、get や watch などの要求に応じた任意の処理ができる ◦ rest.Storage ◦ rest.Lister ◦ rest.Getter ◦ rest.Watcher • Store を独自に実装すると Label Selector や Watch などの処理も作り込 むことになるので注意が必要
  36. 40 Node リソースの変換とキャッシュ • Informer で Node リソースの変更を検知したら ReservedNode リソース

    を生成してキャッシュに保存する • 保存時に Node リソースに付与されているテナントを識別するラベルの値 をインデックスとして使用し、取得時の処理を効率化 • キャッシュには k8s.io/client-go/tools/cache の Indexer を利用 Store Cache Index: テナント A Cache Index: テナント B Cache Index: テナント B Node Informer Node リソースの変更を検知 ReservedNode ReservedNode リソースを生成 ReservedNode リソースを保存
  37. 41 ユーザ情報にもとづく専有ノードの特定 • Aggregation Layer を通じて連携される Kubernetes のユーザ情報は Store に渡される

    Context から取得できる • 取得したユーザー情報を元にテナントを特定し、インデックスを使用して キャッシュから ReservedNode リソースを取得する func (s *Store) Get(ctx context.Context, name string, opts *metav1.GetOptions) (runtime.Object, error) { // Context からユーザ情報を取得 user, ok := request.UserFrom(ctx) if !ok { return nil, errors.New("invalid user") } // 取得したユーザ情報に含まれるテナントに基づいて ReservedNode を取得 // … }
  38. 42 まとめ • PFCP では Extension API Server を使用して専有ノードのリソースを参 照できるようにしている

    • Extension API Server は Kubernetes の Aggregation Layer に API を登 録して使用する • Extension API Server は k8s.io/apiserver で提供されている構造体を使 うと簡単に実装できる • Extension API Server を利用すると様々なデータを Kubernetes のリソー スとして柔軟に扱えるようになって便利
  39. 43 • Preferred Networks ではエンジニアを採用中です! ◦ 機械学習プラットフォームエンジニア ◦ 大規模計算基盤エンジニア ◦

    ストレージエンジニア ◦ ネットワークエンジニア • カジュアル面談をご希望の方は以下のフォームからご連絡ください! We are hiring!