Slide 1

Slide 1 text

Extension API Server による Kubernetes API の拡張 Moto Ishizawa Preferred Networks, Inc.

Slide 2

Slide 2 text

2 自己紹介 Moto Ishizawa Software Engineer, PFN

Slide 3

Slide 3 text

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)

Slide 4

Slide 4 text

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 コンテナ ワークロード ベアメタル

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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 27d v1.29.5 node002 Ready 28h v1.29.5 node003 Ready 58d v1.29.5

Slide 9

Slide 9 text

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 の次に読むとより実践的な実装方法が分かる

Slide 10

Slide 10 text

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 と呼ばれるリソースを使用して登録する

Slide 11

Slide 11 text

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 リソースの情報

Slide 12

Slide 12 text

12 Aggregation Layer との連携フロー kube-apiserver (Aggregation Layer) Extension API Server kubectl

Slide 13

Slide 13 text

13 Aggregation Layer との連携フロー kube-apiserver (Aggregation Layer) Extension API Server kubectl リソースの操作を要求

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

19 Extension API Server の開発の流れ Extension API Server の開発はおおまかに次のような流れで行う。 1. Go で独自リソースの型を定義する 2. k8s.io/code-generator を使用して、型定義から Kubernetes リソースと しての処理に必要な関連コードを生成する 3. 定義した独自リソースの型定義と k8s.io/apiserver の構造体を利用して Extension API Server の処理を実装する

Slide 20

Slide 20 text

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 }

Slide 21

Slide 21 text

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 } リソースの型定義

Slide 22

Slide 22 text

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 } コード生成用タグ

Slide 23

Slide 23 text

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 により 生成されたファイル 型を定義したファイル

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

25 ● k8s.io/apiserver/pkg/server に用意されている kube-apiserver のような API サーバーを作るための構造体 ● Extension API Server を実装する場合はこれをベースとして、独自リソー スの処理を追加していくような形になる ● kube-apiserver の Aggregation Layer との連携処理やユーザ情報の取得 などもこの構造体の内部でうまく処理してくれる GenericAPIServer

Slide 26

Slide 26 text

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 用の設定を生成

Slide 27

Slide 27 text

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 を生成

Slide 28

Slide 28 text

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 を生成して起動

Slide 29

Slide 29 text

29 ● k8s.io/apiserver/pkg/registry/generic/registry に用意されている独自 リソースの CRUD 操作を提供する構造体 ● Create/Update/Delete の各種操作に対して Strategy と呼ばれる Interface を実装して任意の処理を実行したり、Hook を使用して前後処 理を追加したりできる ● デフォルトのデータストアとして内部で etcd が使われる Store

Slide 30

Slide 30 text

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")), } リソースの新しいインスタンスを返す関数

Slide 31

Slide 31 text

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 の条件を設定

Slide 32

Slide 32 text

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")), } リソース名の設定 (複数形/単数形)

Slide 33

Slide 33 text

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 の設定

Slide 34

Slide 34 text

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")), } テーブル出力時の変換用関数

Slide 35

Slide 35 text

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 にインストールする

Slide 36

Slide 36 text

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 を紐付け

Slide 37

Slide 37 text

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 にインストール

Slide 38

Slide 38 text

38 ReservedNode リソースの実装 ● データストアは使用せず、Node リソースのサブセットだけを扱うため、 キャッシュを持つ独自の Store を実装 ● Informer を使用して Node リソースの変更を検知し、ReservedNode リ ソースを生成して Store の内部にあるキャッシュに保存する ● Aggregation Layer から連携されたユーザ情報をもとに専有ノードを特定 し、キャッシュに保存された ReservedNode リソースを返す Extension API Server Store Cache / Indexer Node Informer GenericAPIServer リソースの取得要求 ユーザー情報からテナントを特 定し、リソースを取得 ReservedNode リソースへの 変換と保存 Node リソースの変更検知

Slide 39

Slide 39 text

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 などの処理も作り込 むことになるので注意が必要

Slide 40

Slide 40 text

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 リソースを保存

Slide 41

Slide 41 text

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 を取得 // … }

Slide 42

Slide 42 text

42 まとめ ● PFCP では Extension API Server を使用して専有ノードのリソースを参 照できるようにしている ● Extension API Server は Kubernetes の Aggregation Layer に API を登 録して使用する ● Extension API Server は k8s.io/apiserver で提供されている構造体を使 うと簡単に実装できる ● Extension API Server を利用すると様々なデータを Kubernetes のリソー スとして柔軟に扱えるようになって便利

Slide 43

Slide 43 text

43 ● Preferred Networks ではエンジニアを採用中です! ○ 機械学習プラットフォームエンジニア ○ 大規模計算基盤エンジニア ○ ストレージエンジニア ○ ネットワークエンジニア ● カジュアル面談をご希望の方は以下のフォームからご連絡ください! We are hiring!

Slide 44

Slide 44 text

Making the real world computable