本記事は、[2024年夏季インターンシッププログラム](https://www.preferred.jp/ja/news/internship2024/)で勤務された大川快さんによる寄稿です。 ## はじめに 東京工業大学情報理工学院情報工学系D2の大川快と申します。PFN2024 夏季国内インターンシップに参加し、Cluster ServicesチームでKubernetesで今後導入予定の新機能であるMutating Admission Policyの調査、検証を行いました。 Mutating Admission Policyはユーザー定義のルールによるKubernetes APIへのリクエストの書き換えをWebhookを用いずに実現する機能です。本記事ではMutating Admission Policyの説明や Admission Webhook からの置き換え例などを解説します。なおMutating Admission Policyは現時点(2024年9月)では、Pull Request([https://github.com/kubernetes/kubernetes/pull/126497](https://github.com/kubernetes/kubernetes/pull/126497))がUpstreamにマージされていないため仕様が変更される可能性があります。 ## Admission WebhookによるValidationおよびMutation  kube-apiserverのAdmission ControllerはKubernetes APIへのリクエストによってオブジェクトが永続化される前に、リクエストの内容を検証(Validation)したり、内容の変更(Mutation)したりします\[1,2\]。  kube-apiserverは事前にコンパイルされるため、Kubernetesオブジェクトによって設定できるValidationやMutationのルールには限りがあります。複雑なルールを適用したい場合はAdmission Webhookを用いて外部のサーバーで処理を行います。Admission Webhookを用いることでValidationやMutationの柔軟性を高めることができますが、WebhookによるオーバーヘッドやWebhookを処理するサーバーの開発や運用、保守のコストが発生するという問題があります。 ![](https://tech.preferred.jp/wp-content/uploads/2024/10/image2-2.png) ## Validating Admission PolicyとMutating Admission Policy 上記の問題を解決するため、Validating Admission PolicyとMutating Admission Policyが提案されました\[3\]。これらはCEL(Common Expression Language)と呼ばれる式言語を用いて、ValidationやMutationのルールを定義できます。この機能によりWebhookサーバーなしに複雑なルールを適用できます。Validating Admission PolicyはKubernetes v1.30でGAしましたが、Mutating Admission Policyについてはまだ実装されておらず、v1.32でアルファでの追加が目指されています。インターン開始時点で、MutatingAdmissionPolicyを利用可能にしたPull Request(#126497)が出されており、このPull RequestからKubernetesをビルドすることで先んじてMutating Admission Policyを試用できます。本記事ではこのPull Requestの使用してAdmission Webhookを用いて行っているMutationをMutating Admission Policyで置き換えられるか以下の3つのケースについて調査、検証した結果を紹介します。 - 環境変数 `NVIDIA_VISIBLE_DEVICES` に `none` に強制 - RDMAジョブの設定の自動化 - 拡張リソースをlabelsに付与 補足: インターン後に本Pull RequestはマージされずにCloseされており、Pull Request #127134 でMutating Admission Policyの実装作業が行われています。 ## 環境変数 `NVIDIA_VISIBLE_DEVICES` に `none` に強制  PFNではオンプレミスの計算機クラスタを運用しており、NVIDIA製GPUを搭載したNodeが存在します。NVIDIA GPUを利用する場合には、以下のように設定することでGPUのリソースを要求できます。 | 1 2 3 4 5 6 7 8 9 | `apiVersion:` `v1` `kind:` `Pod` `...` `spec:` `containers:` `-` `resources``:` `limits:` `nvidia.com/gpu``:` `1` `...` | | --- | --- |  しかし、NVIDIAのdevice pluginの仕様により、`nvidia.com/gpu: 0` を設定したりGPUをリクエストしない場合に、コンテナの中からNodeに搭載されているGPUが意図せずすべて見えてしまう問題があります\[7\]。PFNでは、この問題に対処するため、GPUがリクエストされていない場合には強制的に環境変数 `NVIDIA_VISIBLE_DEVICES` を` none `に設定することで、コンテナからGPUが見えないようにしています\[8\]。 これを実現するためにAdmission Webhookを用いて、envの設定を行っています。例えば、以下のようなマニフェストがあった場合 | 1 2 3 4 5 6 7 8 | `apiVersion:` `v1` `kind:` `Pod` `...` `spec:` `containers:` `-` `resources``:` `limits:` `nvidia.com/gpu``:` `0` | | --- | --- | 次のように変更することでコンテナからGPUが見えないようにします。 | 1 2 3 4 5 6 7 8 9 10 11 | `apiVersion:` `v1` `kind:` `Pod` `...` `spec:` `containers:` `-` `resources``:` `limits:` `nvidia.com/gpu``:` `0` `env:` `-` `name``:` `NVIDIA_VISIBLE_DEVICES` `value:` `none` | | --- | --- | また `.spec.containers[x].resources.limits.['nvidia.com/gpu']` が設定されていなかった場合も同様です。 ### Mutating Admission Policy   マニフェストを自動で書き換えるためにMutating Admission Policyを用いた設定を行います。Mutating Admission Policyを適用するにはMutatingAdmissionPolicyオブジェクトとMutatingAdmissionPolicyBindingオブジェクトの二つを作成する必要があります。MutatingAdmissionPolicyオブジェクトはCEL(Common Expression Language)と呼ばれるGoogleが開発した式言語\[9\]を用いて定義されたMutationのルールを持ちます。このCELで定義された内容に基づいてMutationが行われます。この処理はkube-apiserver内で行われるため、Webhookが不要になります。 ![](https://tech.preferred.jp/wp-content/uploads/2024/10/image3-2.png) ### MutatingAdmissionPolicyオブジェクトのマニフェストファイル  マニフェストファイルの例を使って、MutatingAdmissionPolicyオブジェクトとMutatingAdmissionPolicyBindingオブジェクトをどのように設定するのかを説明します。MutatingAdmissionPolicyオブジェクト定義するマニフェストファイルは以下のようになります。 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | `apiVersion:` `admissionregistration.k8s.io/v1alpha1` `kind:` `MutatingAdmissionPolicy` `metadata:` `name:` `nvidia-visible-devices-policy.example.com` `spec:` `matchConstraints:` `resourceRules:` `-` `apiGroups``:`   `[``""``]` `apiVersions:` `[``"v1"``]` `operations:`  `[``"CREATE"``]` `resources:`   `[``"pods"``]` `failurePolicy:` `Fail` `mutations:` `-` `patchType``:` `ApplyConfiguration` `reinvocationPolicy:` `IfNeeded` `expression:` `>-` `Object ``{` `spec:` `Object.spec``{` `containers:` `object.spec.containers.filter(` `ct``,` `quantity(ct.resources.?limits``[``'nvidia.com/gpu'``]``.orValue(``"0"``)).asInteger() ==` `0` `).map(` `ct``,` `Object.spec.containers.item ``{` `name:` `ct.name``,` `env:` `[` `Object.spec.containers.item.env.item ``{` `name:` `'NVIDIA_VISIBLE_DEVICES'``,` `value:` `'none'``,` `}` `]` `}` `)``,` `initContainers:` `object.spec.?initContainers.orValue(``[``]``).filter(` `ct``,` `quantity(ct.resources.?limits``[``'nvidia.com/gpu'``]``.orValue(``"0"``)).asInteger() ==` `0` `).map(` `ct``,` `Object.spec.containers.item ``{` `name:` `ct.name``,` `env:` `[` `Object.spec.containers.item.env.item ``{` `name:` `'NVIDIA_VISIBLE_DEVICES'``,` `value:` `'none'``,` `}` `]` `}` `)` `}` `}` | | --- | --- |  このマニフェストファイルについて説明します。  `.spec.matchConstraints` によりPolicyを適用する範囲を設定できます。この例ではPodを作成する際にPolicyが適用されます。   `.spec.mutations` ではMutationのルールを定義します。`.spec.mutations.patchType` には、CELで定義されたオブジェクトとオリジナルのオブジェクトをどのようにマージするかを設定します。 また `.spec.mutations.expression` にMutationを定義するCELを記述します。 ### CELで利用可能な変数  KubernetesのCEL内では `object` や  `params` 、 `variables` 等の変数を利用できます。  objectにはMutationされる前のオブジェクトの値が格納されています。例えばオブジェクトの`name`を取得したい場合は、`object.metadata.name` で参照できます。 `params` はMutatingAdmissionPolicyBindingで設定したパラメータの値を参照できます。こちらについては次の例で説明します。`variables` は MutatingAdmissionPolicyの `.spec.variables` で設定したCELの評価値を参照することできます。ただし variables については 2024年9月時点では正常に動作せず、実際に利用できませんでした。 ### `nvidia.com/gpu: 0` もしくは `nvidia.com/gpu` が設定されていないコンテナの判定  `NVIDIA_VISIBLE_DEVICES` を `none` に設定すべきコンテナは次のように表現できます。 | 1 2 3 4 | `object.spec.containers.filter(` `ct,` `quantity(ct.resources.?limits['nvidia.com/gpu'].orValue("0")).asInteger() == 0` `)` | | --- | --- | ここで `filter()` はCELのlistやmapに対し、条件に一致する要素だけ取り出す関数です。第一引数が、走査する要素を表し、第二引数が条件式を表しています。また、`?limits` はOptionalであることを表し、入力されたオブジェクトの `limits` が設定されていない場合は `orValue()` に渡された値で評価されるようになります。また `xxx.?limits['nvidia.com/gpu']` は `xxx.?limits[?'nvidia.com/gpu']`と等価なので limits は設定されているが `nvidia.com/gpu` は設定されていないというケースでも `orValue()`  に渡された値が評価されます。`quantity()` は `.spec.containers[x].resources.limits.xxx` に指定される値を数値に変換する関数です。このようにして`nvidia.com/gpu: 0` もしくは `nvidia.com/gpu` が設定されていないコンテナを取り出すことができます。 ### `NVIDIA_VISIBLE_DEVICES` に `none` を設定 次のようにして`NVIDIA_VISIBLE_DEVICES` を強制的に `none` にします。 | 1 2 3 4 5 6 7 8 9 10 11 12 | `.map(` `ct,` `Object.spec.containers.item {` `name: ct.name,` `env: [` `Object.spec.containers.item.env.item {` `name: 'NVIDIA_VISIBLE_DEVICES',` `value: 'none',` `}` `]` `}` `)` | | --- | --- | ここで `map()` はCELのlistやmapの各要素を変換する関数です。この例ではコンテナを表すオブジェクトに `env` を指定します。ここで注意が必要なのは、`env` の他に`name`も指定する必要があります。これは`name`が一致する`Object.spec.containers`の要素にCELで定義したオブジェクトをマージするためです。 ### MutatingAdmissionPolicyBindingオブジェクトのマニフェストファイル  MutatingAdmissionPolicyオブジェクトに加え、MutatingAdmissionPolicyBindingオブジェクトを作成する必要があります。 MutatingAdmissionPolicyBindingを用いることでMutatingAdmissionPolicyの範囲を制限したり、パラメーターを与えたりします。MutatingAdmissionPolicyBindingオブジェクトがあることにより、適用条件やパラメータの異なるMutatingAdmissionPolicyオブジェクトを複数作成することなく定義できるようになります。  以下が、MutatingAdmissionPolicyBindingオブジェクトのマニフェストファイルです。 | 1 2 3 4 5 6 7 8 9 10 11 12 | `apiVersion:` `admissionregistration.k8s.io/v1alpha1` `kind:` `MutatingAdmissionPolicyBinding` `metadata:` `name:` `nvidia-visible-devices-binding.example.com` `spec:` `policyName:` `"nvidia-visible-devices-policy.example.com"` `matchResources:` `namespaceSelector:` `matchExpressions:` `-` `key``:` `kubernetes.io/metadata.name` `operator:` `NotIn` `values:` `[` `"nvidia-gpu"``]` | | --- | --- |  このマニフェストファイルについて説明します。  `.spec.policyName` はBindするMutatingAdmissionPolicyオブジェクトの`name`を指定します。  `.spec.matchResources` はPolicyを適用する範囲を指定します。この例ではKubernetesの namespaceSelectorを用いて namespaceが `'nvidia-gpu'` でないときにPolicyを適用するように設定しています。ほかの指定方法は次のコマンドで調べることができます。 | 1 | `kubectl explain mutatingadmissionpolicybinding.spec.matchResources` | | --- | --- | ### Mutating Admission Policyの適用および動作確認  上で示したマニフェストでMutaingが行われるか動作確認を行います。MutatingAdmissionPolicyオブジェクトのマニフェストファイルをpolicy.yaml、MutatingAdmissionPolicyBindingオブジェクトのマニフェストファイルをbinding.yamlとします。 Mutating Admission Policyを適用するには以下のコマンドを実行します。 | 1 | `kubectl apply -f policy.yaml -f binding.yaml` | | --- | --- |  続いて、Podを作成します。以下のマニフェストファイルを作成します。(ファイル名はpod.yamlとします。) | 1 2 3 4 5 6 7 8 9 10 11 | `apiVersion:` `v1` `kind:` `Pod` `metadata:` `name:` `gpu-zero` `spec:` `containers:` `-` `name``:` `main` `image:` `busybox` `resources:` `limits:` `nvidia.com/gpu``:` `0` | | --- | --- |  以下のコマンドでPodを作成します。 | 1 | `kubectl apply -f pod.yaml` | | --- | --- |  このPodに対して正しくMutationが行われたかを以下のコマンドで確認します。 | 1 | `kubectl get pod gpu-zero -o "jsonpath={.spec.containers[0].env}" \| jq -r .` | | --- | --- | --- | 実行結果は以下のようになり、`NVIDIA_VISIBLE_DEVICES` を `none` に設定できていることがわかります。 | 1 2 3 4 5 6 | `[` `{` `"name": "NVIDIA_VISIBLE_DEVICES",` `"value": "none"` `}` `]` | | --- | --- | ## RDMAジョブの設定の自動化   PFNのクラスタで RDMA で使った複数ノードでのジョブを実行するときには SR-IOV を使ったネットワークを Pod で使えるようにするために複数のリソースやアノテーションをまとめてつける必要がありますが、現状ではユーザーがマニフェストに決まった記述をマニュアルで書いてます。この設定を自動化し、クラスタに詳しくない人でも扱えるようにするため、自動でリソースの設定やアノテーションをMutating Admission Policyを用いて行います。 ### 実現したいこと  以下のように `preferred.jp/rdma: 1` が設定されていた場合に | 1 2 3 4 5 6 7 8 9 | `apiVersion:` `v1` `kind:` `Pod` `...` `spec:` `containers:` `-` `resources``:` `limits:` `preferred.jp/rdma``:` `1` `...` | | --- | --- | 該当するコンテナに `preferred.jp/net[1-4]: 1` がというリソースのリミットを追加し、`.metadata.annotations['k8s.v1.cni.cncf.io/networks']` に  `networking-rdma/net1, networking-rdma/net2, networking-rdma/net3, networking-rdma/net4` を設定します。期待されるMutationの結果は以下の通りです。 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | `apiVersion:` `v1` `kind:` `Pod` `metadata:` `annotations:` `k8s.v1.cni.cncf.io/networks``:` `"networking-rdma/net1, networking-rdma/net2, networking-rdma/net3, networking-rdma/net4"` `...` `spec:` `containers:` `-` `resources``:` `limits:` `preferred.jp/rdma``:` `1` `preferred.jp/net1``:` `1` `preferred.jp/net2``:` `1` `preferred.jp/net3``:` `1` `preferred.jp/net4``:` `1` `...` | | --- | --- | ### マニフェストファイルの例 MutatingAdmissionPolicyオブジェクトとMutatingAdmissionPolicyBindingオブジェクトの例を以下に示します。 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | `apiVersion:` `admissionregistration.k8s.io/v1alpha1` `kind:` `MutatingAdmissionPolicy` `metadata:` `name:` `rdma-resources-and-annotations-inserting.example.com` `spec:` `paramKind:` `apiVersion:` `mutations.example.com/v1` `kind:` `RdmaResourcesAndAnnotationsInsertingParam ` `matchConstraints:` `resourceRules:` `-` `apiGroups``:`   `[``""``]` `apiVersions:` `[``"v1"``]` `operations:`  `[``"CREATE"``]` `resources:`   `[``"pods"``]` `failurePolicy:` `Fail` `mutations:` `-` `patchType``:` `ApplyConfiguration` `reinvocationPolicy:` `IfNeeded` `expression:` `>-` `Object ``{` `metadata:` `Object.metadata``{` `annotations:` `object.spec.containers.exists(` `ct``,` `quantity(` `ct.resources.?limits``[``"preferred.jp/rdma"``]``.orValue(``"0"``)` `).asInteger() ==` `1` `) ` `? ``{` `"k8s.v1.cni.cncf.io/networks"``:` `object.metadata.?annotations``[``"k8s.v1.cni.cncf.io/networks"``]``.orValue(params.annotations) ` `}` `:` `{``}` `}``,` `spec:` `Object.spec``{` `containers:` `object.spec.containers.filter(` `ct``,` `quantity(` `ct.resources.?limits``[``"preferred.jp/rdma"``]``.orValue(``"0"``)` `).asInteger() ==` `1` `).map(` `ct``,` `Object.spec.containers.item ``{` `name:` `ct.name``,` `resources:` `{` `"limits"``:` `params.nicLimits` `}` `}` `)` `}` `}` | | --- | --- | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | `apiVersion:` `apiextensions.k8s.io/v1` `kind:` `CustomResourceDefinition` `metadata:` `name:` `rdma-resources-and-annotations-inserting-params.mutations.example.com` `spec:` `group:` `mutations.example.com` `names:` `kind:` `RdmaResourcesAndAnnotationsInsertingParam ` `listKind:` `RdmaResourcesAndAnnotationsInsertingParamList` `plural:` `rdma-resources-and-annotations-inserting-params` `singular:` `rdma-resources-and-annotations-inserting-param` `scope:` `Namespaced` `versions:` `-` `name``:` `v1` `served:` `true` `storage:` `true` `schema:` `openAPIV3Schema:` `type:` `object` `properties:` `nicLimits:` `type:` `object` `properties:` `preferred.jp/net1``:` `type:` `integer` `preferred.jp/net2``:` `type:` `integer` `preferred.jp/net3``:` `type:` `integer` `preferred.jp/net4``:` `type:` `integer` `annotations:` `type:` `string` `---` `apiVersion:` `mutations.example.com/v1` `kind:` `RdmaResourcesAndAnnotationsInsertingParam ` `metadata:` `name:` `rdma-resources-and-annotations-inserting-param.example.com` `namespace:` `default` `nicLimits:` `preferred.jp/net1``:` `1` `preferred.jp/net2``:` `1` `preferred.jp/net3``:` `1` `preferred.jp/net4``:` `1` `annotations:` `"networking-rdma/net1 networking-rdma/net2 networking-rdma/net3 networking-rdma/net4"` `---` `apiVersion:` `admissionregistration.k8s.io/v1alpha1` `kind:` `MutatingAdmissionPolicyBinding` `metadata:` `name:` `rdma-resources-and-annotations-inserting-binding.example.com` `spec:` `policyName:` `rdma-resources-and-annotations-inserting.example.com` `paramRef:` `name:` `rdma-resources-and-annotations-inserting-param.example.com` `namespace:` `default` | | --- | --- |  この例の特徴はMutatingAdmissionPolicyBindingオブジェクトを用いてパラメータを設定している点です。 MutatingAdmissionPolicyBinding `spec.paramRef` でMutatingAdmissionPolicyに渡すパラメータを設定しています。この例ではCRDを用いてRdmaResourcesAndAnnotationsInsertingParamオブジェクトを作成し、それをパラメータとしています。MutatingAdmissionPolicyBinding `.spec.paramRef.name` にパラメータのオブジェクトの`name`を指定しています。  続いてMutatingAdmissionPolicyオブジェクトの設定を見てみます。 MutatingAdmissionPolicy `.spec.paramKind` で受け取るパラメータの`apiVersion`と`kind`を設定しています。受け取ったパラメータは以下のようにCEL内の変数 `params` で用いて参照できます。 | 1 2 3 4 5 6 | `Object.spec.containers.item {` `name: ct.name,` `resources: {` `"limits": params.nicLimits` `}` `}` | | --- | --- |  また、この例では`exist()`と`filter()`に同じ条件を渡しているので、`variables`を使うことで記述量を削減できます。 ## 拡張リソースをlabelsに付与   PFNのクラスタでは、GPUやMN-Coreを拡張リソースとして扱っており、Validationや管理を行う上でPodが拡張リソースを用いているかの判定が必要な場合があります。その際、Podが利用している拡張リソースを `.metadata.labels` に持たせておくと、KubernetesのlabelSelector等を用いて容易に判定ができます。具体的に以下のようなPodのマニフェストファイルがあった場合、 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | `apiversion:` `v1` `kind:` `pod` `...` `spec:` `initcontainers:` `-` `resources``:` `limits:` `nvidia.com/gpu``:` `1` `...` `containers:` `-` `resources``:` `limits:` `cpu:` `200m` `preferred.jp/mncore``:` `1` `...` `-` `resources``:` `limits:` `preferred.jp/mncore``:` `1` `preferred.jp/with-binarysi``:` `1mi` `...` | | --- | --- | 以下のようにMutationしたいです。 | 1 2 3 4 5 6 7 8 | `apiversion:` `v1` `kind:` `pod` `metadata:` `labels:` `pod-resource.preferred.jp/preferred` `""` `pod-resource.preferred.jp/preferred` `""` `pod-resource.preferred.jp/nvidia` `""` `...` | | --- | --- | ### CELの限界  しかし次のような理由から、Mutating Admission Policyを用いてこのMutationを実現することは難しいのではないかという結論に至りました。 - list から mapを作成できない。  - `.spec.containers`はlist型なのに対し、`.metadata.labels`はmap型 - listの要素をkeyとしてmapを構築する方法がない。 - 配列のconcatenateができない - `.spec.containers` は各要素が複数の拡張リソースを持つことがあるので二重の配列になる。 - これらをまとめる concatenate の処理が必要だが、そのような機能がない ### CELのKubernetes拡張に自前の実装を追加   KubernetesではCEL上で使える拡張ライブラリがあります。このライブラリに上記の処理を行う関数を追加しました。具体的には - `listToMap(x,y)` - list `x` の要素をkey, list y の要素をvalueとしたmapを返す - `e.sum()` - listを要素に持つlist `e` をconcatenateして平滑化したlistを返す の二つを追加しました。 ![](https://tech.preferred.jp/wp-content/uploads/2024/10/image1-2.png)  また、ExtendedResourseかどうかの判定はv1.31から導入されるformatというライブラリを用いれば可能ですが、CELが複雑になるため直接判定できる関数を追加しました。  CELの拡張ライブラリはKubernetesのソースコードの /staging/src/k8s.io/apiserver/pkg/cel/library/ 以下で実装されています([https://github.com/kubernetes/kubernetes/tree/v1.31.0/staging/src/k8s.io/apiserver/pkg/cel/library/](https://github.com/kubernetes/kubernetes/tree/v1.31.0/staging/src/k8s.io/apiserver/pkg/cel/library/))。ここに拡張する関数を実装、追加しました。拡張した関数の実装例は以下の通りです。 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | `func` `listToMap(keyList ref.Val, valueList ref.Val) ref.Val {` `goMap := ``make``(``map``[``string``]any)` `keyLister, keyOk := keyList.(traits.Lister)` `valueLister, valueOk := valueList.(traits.Lister)` `if` `!keyOk {` `return` `types.MaybeNoSuchOverloadErr(keyList)` `}` `if` `!valueOk {` `return` `types.MaybeNoSuchOverloadErr(valueList)` `}` `keySz := keyLister.Size().(types.Int)` `valueSz := valueLister.Size().(types.Int)` `var` `sz = keySz` `if` `sz > valueSz {` `sz = valueSz` `}` `for` `i := types.Int(``0``); i < sz; i++ {` `key := keyLister.Get(types.Int(i)).Value()` `value := valueLister.Get(types.Int(i)).Value()` `keyStr, keyStrOk := key.(``string``)` `if` `!keyStrOk {` `return` `types.MaybeNoSuchOverloadErr(keyList)` `}` `goMap[keyStr] = value` `}` `return` `types.NewStringInterfaceMap(types.DefaultTypeAdapter, goMap)` `}` | | --- | --- | 手元のKubernetesクラスタに新たに追加した関数についてはPull Requestを出したいと考えています。 ### 自前実装を用いた例 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | `apiVersion:` `admissionregistration.k8s.io/v1alpha1` `kind:` `MutatingAdmissionPolicy` `metadata:` `name:` `extended-resource-labeling.example.com` `spec:` `matchConstraints:` `resourceRules:` `-` `apiGroups``:`   `[``""``]` `apiVersions:` `[``"v1"``]` `operations:`  `[``"CREATE"``]` `resources:`   `[``"pods"``]` `failurePolicy:` `Fail` `mutations:` `-` `patchType``:` `ApplyConfiguration` `reinvocationPolicy:` `IfNeeded` `expression:` `>-` `Object ``{` `metadata:` `Object.metadata ``{` `labels:` `listToMap(` `(object.spec.containers + object.spec.?initContainers.orValue(``[``]``)).map(` `ct``,` `ct.resources.?limits.orValue(``{``}``).filter( ` `k``,` `resourceName(k).isExtended()` `).filter(` `k``,` `quantity(ct.resources.limits``[``k``]``).compareTo(quantity(``"0"``)) !=` `0` `).map(` `k``,` `k.replace(``'/'``,` `'--'``,` `-1)` `)` `).sum()``,` `(object.spec.containers + object.spec.?initContainers.orValue(``[``]``)).map(` `ct``,` `ct.resources.?limits.orValue(``{``}``).filter(` `k``,` `resourceName(k).isExtended()` `).filter(` `k``,` `quantity(ct.resources.limits``[``k``]``).compareTo(quantity(``"0"``)) !=` `0` `).map(` `k``,` `""` `)` `).sum()` `)` `}` `}` | | --- | --- | ## Mutating Admission Policyの所感  今回、Mutating Admission Policyを試してみましたが、Webhookを利用せずにユーザー定義のMutationが行えることがわかりました。 サーバーの実装コストやCI/CDの環境構築などの運用コストが無くなることは大きなメリットです。  一方でCELの機能が限定的であるということがわかりました。CELでは停止性を保証するなどの理由から非チューリング完全になっており、機能が少ない印象を受けました(「拡張リソースをlabelsに付与」の例)。また、関数定義ができないため、記述が冗長になりがちです。そのため、数行では書けないような、複雑な処理は従来通りWebhookを用いた方が良いケースもありそうです。 ## インターンの感想  本インターンに参加するまでKubernetesをほとんど触ったことがなかったので、Kubernetesの機能や思想について知ることができ、大変勉強になりました。また普段研究室で管理しているGPUクラスタとPFNの計算基盤の違いを理解できました。特にSlurm等のJobスケジューラーとKubernetesの違いについて知ることができ大変貴重な経験ができました。 メンターの須田さん、清水さん、 クラスターサービスの皆さん、大変お世話になりました!ありがとうございました! ## メンターより 大川さんのメンターを担当しました Cluster Services チームの清水、須田です。 Cluster Services チームのインターンは昨年までは7週間の期間で行っていましたが、今年は期間を短くし2週間で実施しました。この2週間の中にオリエンテーション、クラスタを設置しているデータセンターへの見学、最終発表などがありインターンのテーマに取り組むことができる実施的な日数がさらに少ない状況でもしっかりと成果を出してくれました。 インターンに参加するまでは Kubernetes を触った経験はほぼなかったとのことだったので、短い期間の中で Kubernetes そのものを理解するところから始まり、インターンのテーマであった Mutatating Admission Policy という現在進行形で開発が進んでいる新機能の仕様や実装を調査するのは大変だったと思いますがスムーズに作業を進めていました。既存の Admission Webhook で行われている mutation を置き換えた時にどのようになるかの検討からは、 Mutating Admission Policy の使いやすいところや逆にまだ使いにくいところを明らかにしてくれ、今後の機能の置き換えを行う際に参考になる知見が得られました。ありがとうございます。 ## 付録: Kindを用いた検証環境の構築 今回の検証はPull Request(#126497)のKubernetesをソースからビルドし、Kind上で検証を行いました。 Mutating Admission Policyを有効にするには以下のような設定(cluster.yaml)を加えてclusterを作成します。 | 1 2 3 4 5 6 7 8 9 | `apiVersion:` `kind.x-k8s.io/v1alpha4` `kind:` `Cluster` `nodes:` `- role``:` `control-plane` `image:` `kindest/node``:``latest` `featureGates:` `MutatingAdmissionPolicy:` `true` `runtimeConfig:` `api/alpha``:` `"true"` | | --- | --- | 以下のコマンドでクラスター作成できます。 | 1 | `kind create cluster --config cluster.yaml` | | --- | --- | 以下のコマンドでAPIが有効になっているか確認できます。 | 1 | `kubectl api-resources \| grep mutatingadmission` | | --- | --- | --- | ## 付録: CELのTips ### Optional  Kubernetes v1.29からCELのOptionalが利用できます。従来はmapやlistの要素が存在するかわからない場合に `has()` を多用する必要がありました。 例えば  `.spec.containers[x].resources.limits.['nvidia.com/gpu']` が設定されていない、もしくは `"0"` と判定する場合次のような長い評価式が必要です。 | 1 | `!(has(ct.resources.limits) && "nvidia.com/gpu" in ct.resources.limits) \|\| quantity(ct.resources.limits['nvidia.com/gpu']).asInteger() == 0` | | --- | --- | --- | --- | `limits`や `'nvidia.com/gpu'` が存在するかわからないため、`has()`や `in` を用いて何度も評価を行う必要があります。これをOptionalを用いると以下のように短く書くことができます。 | 1 | `quantity(ct.resources.?limits['nvidia.com/gpu'].orValue("0")).asInteger() == 0` | | --- | --- | `?`はOptionalであることを表します。`ct.resources`が`limits`を持つ場合は `optional.of(ct.resources.limits)`がそうでない場合は`optional.none()`と評価されます。また一度?をつけた場合、下位の要素もOptionalとして評価されるため、`limits`はあるが`'nvidia.com/gpu'` はないという状況でも、エラーは発生せずOptional型として評価されます。 Optionalのメンバ関数`.orValue()`はOptional型の変数が値を持つ場合はその値を、`optional.none()`の場合は渡された引数を返します。このようにすることで冗長な記述をさけることができます。 ## 付録: variables, matchCondtionsを用いた場合の例 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | `apiVersion:` `admissionregistration.k8s.io/v1alpha1` `kind:` `MutatingAdmissionPolicy` `metadata:` `name:` `rdma-resources-and-annotations-inserting.example.com` `spec:` `paramKind:` `apiVersion:` `mutations.example.com/v1` `kind:` `RdmaResourcesAndAnnotationsInsertingParam ` `matchConstraints:` `resourceRules:` `-` `apiGroups``:`   `[``""``]` `apiVersions:` `[``"v1"``]` `operations:`  `[``"CREATE"``]` `resources:`   `[``"pods"``]` `variables:` `-` `name``:` `containersWithLimits` `expression:` `>-` `object.spec.containers.filter(` `ct``,` `quantity(ct.resources.?limits``[``"preferred.jp/rdma"``]``.orValue(``"0"``)).asInteger() ==` `1` `)` `-` `name``:` `isContainerWithLimitsExists` `expression:` `>-` `variables.containersWithLimits.size() >` `0` `matchConditions:` `-` `name``:` `has-rdma-resources-limits` `expression:` `>-` `variables.isContainerWithLimitsExists` `failurePolicy:` `Fail` `mutations:` `-` `patchType``:` `ApplyConfiguration` `reinvocationPolicy:` `IfNeeded` `expression:` `>-` `Object ``{` `metadata:` `Object.metadata``{` `annotations:` `{` `"k8s.v1.cni.cncf.io/networks"``:` `object.metadata.?annotations``[``"k8s.v1.cni.cncf.io/networks"``]``.orValue(params.annotations) ` `}` `}``,` `spec:` `Object.spec``{` `containers:` `variables.containersWithLimits.map(` `ct``,` `Object.spec.containers.item ``{` `name:` `ct.name``,` `resources:` `{` `"limits"``:` `params.nicLimits` `}` `}` `)` `}` `}` | | --- | --- |  未検証ではありますが、variablesとmatchCondtionsを用いてRDMAジョブの設定の自動化を行う例です。RDMAに関する`limits`を持つコンテナを`variables`で持つことで判定処理の記述を減らしたり、`matchConditions`でそのようなコンテナが存在するかを条件にすることで、ロジックを簡略化できます。 ## 参考文献 - \[1\] [https://sysdig.com/blog/kubernetes-admission-controllers/](https://sysdig.com/blog/kubernetes-admission-controllers/) - \[2\] [https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/) - \[3\] [https://github.com/kubernetes/enhancements/issues/3962](https://github.com/kubernetes/enhancements/issues/3962) - \[4\] [https://v1-27.docs.kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/](https://v1-27.docs.kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/) - \[5\] [https://v1-28.docs.kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/](https://v1-28.docs.kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/) - \[6\] [https://v1-30.docs.kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/](https://v1-30.docs.kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/) - \[7\] [https://github.com/NVIDIA/k8s-device-plugin?tab=readme-ov-file#running-gpu-jobs](https://github.com/NVIDIA/k8s-device-plugin?tab=readme-ov-file#running-gpu-jobs) - \[8\] [https://www.slideshare.net/slideshow/tech-inmlcluster20180729-julytechfesta2018/107950030](https://www.slideshare.net/slideshow/tech-inmlcluster20180729-julytechfesta2018/107950030) - \[9\] [https://github.com/google/cel-spec](https://github.com/google/cel-spec)