GKE Autopilotによるresourcesの書き換え

に公開

Google Kubernetes EngineのAutopilotモードはワークロードリソースのspec.containers[].resourcesの値を書き換えることがあるのですが、そのルールが初見だと少しわかりづらかったので簡単な入門記事を書いてみました。

Autopilotによるresourcesの書き換えのルール概要

どのようにresourcesが制御されるかについては、Autopilotでのリソースリクエストに詳細に書いてありますが、ざっくり説明すると以下のようになっています。

  1. requestsを指定していないと、limitsが設定されていればその値が、されてなければデフォルト値が設定される
  2. クラスタがバースト機能をサポートしていない場合は、requests=limitsとなるように上書きされる
  3. 何かしらの値を指定した場合でも最小値と最大値、CPUとメモリの比率の3つの制約を満たすように上書きされる
  4. デフォルト値、最大・最小値、CPUとメモリの比率は指定するコンピューティングクラス、リソースタイプによって決定される

コンピューティングクラスはPodをどのようなノードで実行するかを指定するためのもので、何も指定しない場合は汎用になります。詳細はこちら

クラスタがバースト機能をサポートしているかどうかは、こちらに条件が記載されています。クラスタの要件に加えて、特定のコンピューティングクラスを指定する必要があり、リソース管理も難しくなるので利用するケースは少ないと思います。

書き換えの確認

ルール概要を踏まえた上で実際に書き換えられる様子を確認してみます。なお、動作環境はバースト非対応のクラスタとなっています。

以下のDeploymentマニフェストではrequests.cpu125m, requests.memory256Miを指定、requests.ephemeral-storageを未指定、limitsは全て未指定としています。

sample-deployment-ng.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
  namespace: default
  labels:
    app: sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sample
  template:
    metadata:
      labels:
        app: sample
    spec:
      containers:
        - name: ubuntu
          image: ubuntu:24.04
          command: ["sleep"]
          args: ["infinity"]
          resources:
            requests:
              cpu: "125m"
              memory: "256Mi"

コンピューティングクラスは指定していないため汎用になりますが、このクラスの制約を見てみると以下のようになっており、比率は問題ないですが、CPUとメモリが最小値を下回っています。また、エフェメラルストレージのデフォルト値は1GiBです。

制約対象 値の範囲
CPU 250m ~ 30
メモリ 512 MiB ~ 110 GiB
CPUとメモリの比率 1:1 ~ 1:6.5

次に、これをAutopilotが制約を満たすように書き換える様子を見ていきますが、実際にデプロイせずとも --dry-run=server を利用することで確認できます。

実行すると出力の一行目に以下のようにWarningが出てAutopilotの制約を満たしていないためresourcesが書き換えられたという警告がでます。

$ kubectl apply -f sample-deployment-ng.yaml --dry-run=server -o yaml
Warning: autopilot-default-resources-mutator:Autopilot updated Deployment default/sample-deployment: adjusted 'cpu' resource to meet requirements for containers [ubuntu] (see http://g.co/gke/autopilot-defaults).

出力されたyamlを見てみると、annotationsautopilot.gke.io/resource-adjustmentにこの書き換え処理のinputとoutputが記載されており、どのように書き変わったかが確認できます。また、spec.containers[].resourcesをみると、実際にCPUとメモリが最小値まで増加されており、エフェメラルストレージはデフォルト値に設定され、設定していなかったlimitsrequests=limitsとなるように設定されてることが確認できます。

dry-runの出力
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    autopilot.gke.io/resource-adjustment: '{"input":{"containers":[{"requests":{"cpu":"125m","memory":"256Mi"},"name":"ubuntu"}]},"output":{"containers":[{"limits":{"cpu":"250m","ephemeral-storage":"1Gi","memory":"512Mi"},"requests":{"cpu":"250m","ephemeral-storage":"1Gi","memory":"512Mi"},"name":"ubuntu"}]},"computeClassAtAdmission":"Default","modified":true}'
    autopilot.gke.io/warden-version: xx.yy.zz-gke.1
    ...
spec:
  ...
  template:
    ...
    spec:
      containers:
      - args:
        - infinity
        command:
        - sleep
        image: ubuntu:24.04
        imagePullPolicy: IfNotPresent
        name: ubuntu
        resources:
          limits:
            cpu: 250m
            ephemeral-storage: 1Gi
            memory: 512Mi
          requests:
            cpu: 250m
            ephemeral-storage: 1Gi
            memory: 512Mi
...

書き換え対象と注意事項

次に、この書き換えが何によって行われ、書き換え対象となるリソースは何かを見ていきます。

まず、書き換えが何によって行われているかですが、先ほどのdry-runで出力されたyamlに autopilot.gke.io/warden-version: xx.yy.zz-gke.1 というアノテーションがついていましたが、GKEのAutopilotモードにはGKE Wardenというカスタムアドミッションコントローラが存在し、これはそのバージョンです(値はマスクしています)。

そして、このGKE Wardenは公式ドキュメントではセキュリティの文脈(例: Kubernetes Pod セキュリティ標準の適用)で出てきますが、resourcesの書き換えも担っています。

このことを確認するために、先ほどdry-runしたコマンドを実際にapplyします。

$ kubectl apply -f sample-deployment-ng.yaml
Warning: autopilot-default-resources-mutator:Autopilot updated Deployment default/sample-deployment: adjusted 'cpu' resource to meet requirements for containers [ubuntu] (see http://g.co/gke/autopilot-defaults).
deployment.apps/sample-deployment created

この操作の監査ログをCloudLoggingで以下のクエリを用いて検索すると、

protoPayload.@type="type.googleapis.com/google.cloud.audit.AuditLog"
protoPayload.methodName="io.k8s.apps.v1.deployments.create"

以下の監査ログがヒットします。

  {
    "protoPayload": {
      "@type": "type.googleapis.com/google.cloud.audit.AuditLog",
      "methodName": "io.k8s.apps.v1.deployments.create",
    "labels": {
      ...,
      "patch.webhook.admission.k8s.io/round_0_index_19": "...後述...",
      ...
    },

このログのlabelの"patch.webhook.admission.k8s.io/round_0_index_19"の値は以下のようになっており、リソースを書き換えているのは warden-mutating.common-webhooks.networking.gke.io、つまりGKE Wardenであることが確認できます。

"patch.webhook.admission.k8s.io/round_0_index_19"の値
{
  "configuration": "warden-mutating.config.common-webhooks.networking.gke.io",
  "webhook": "warden-mutating.common-webhooks.networking.gke.io",
  "patch": [
    {
      "op": "add",
      "path": "/metadata/annotations/autopilot.gke.io~1resource-adjustment",
      "value": "{\"input\":{\"containers\":[{\"requests\":{\"cpu\":\"125m\",\"memory\":\"256Mi\"},\"name\":\"ubuntu\"}]},\"output\":{\"containers\":[{\"limits\":{\"cpu\":\"250m\",\"ephemeral-storage\":\"1Gi\",\"memory\":\"512Mi\"},\"requests\":{\"cpu\":\"250m\",\"ephemeral-storage\":\"1Gi\",\"memory\":\"512Mi\"},\"name\":\"ubuntu\"}]},\"computeClassAtAdmission\":\"Default\",\"modified\":true}"
    },
    {
      "op": "add",
      "path": "/metadata/annotations/autopilot.gke.io~1warden-version",
      "value": "xx.yy.zz-gke.1"
    },
    {
      "op": "add",
      "path": "/spec/template/spec/securityContext/seccompProfile",
      "value": {
        "type": "RuntimeDefault"
      }
    },
    {
      "op": "add",
      "path": "/spec/template/spec/tolerations",
      "value": [
        {
          "effect": "NoSchedule",
          "key": "kubernetes.io/arch",
          "operator": "Equal",
          "value": "amd64"
        }
      ]
    },
    {
      "op": "add",
      "path": "/spec/template/spec/containers/0/resources/limits",
      "value": {
        "cpu": "250m",
        "ephemeral-storage": "1Gi",
        "memory": "512Mi"
      }
    },
    {
      "op": "replace",
      "path": "/spec/template/spec/containers/0/resources/requests/memory",
      "value": "512Mi"
    },
    {
      "op": "replace",
      "path": "/spec/template/spec/containers/0/resources/requests/cpu",
      "value": "250m"
    },
    {
      "op": "add",
      "path": "/spec/template/spec/containers/0/resources/requests/ephemeral-storage",
      "value": "1Gi"
    },
    {
      "op": "add",
      "path": "/spec/template/spec/containers/0/securityContext",
      "value": {
        "capabilities": {
          "drop": [
            "NET_RAW"
          ]
        }
      }
    }
  ],
  "patchType": "JSONPatch"
}

このwebhookの設定は以下のコマンドで確認できます。

$ kubectl mutatingwebhookconfiguration warden-mutating.config.common-webhooks.networking.gke.io -o yaml
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  ...
  labels:
    networking.gke.io/common-webhooks: "true"
  name: warden-mutating.config.common-webhooks.networking.gke.io
  ...
webhooks:
  ...
  - apiGroups:
    - apps
    apiVersions:
    - v1
    operations:
    - CREATE
    - UPDATE
    resources:
    - daemonsets
    - deployments
    - replicasets
    - statefulsets
    scope: '*'
   ...

これだけでは具体的にどの操作に何がどう適用されているかまではわかりませんが、あくまで標準のリソースのみが対象となっており、CustomResourceDefinitionは書き換えの対象になっていないことがわかります。

例えば、Deploymentの代わりにArgo RolloutsのRolloutsリソースを使っている場合は書き換えられません。ただし、Rolloutsが生成するReplicasetリソースは書き換えの対象となります。この挙動の影響を受ける例としてArgoCDがあります。Rolloutsが書き換えられないことによりOutOfSync判定されない一方で、Podは書き換えられているといった不一致が発生し得る点に注意が必要です。

Discussion