GKE クラスタで kubelet 読み取り専用ポートを無効にする


このページでは、Google Kubernetes Engine(GKE)クラスタで、安全でない kubelet 読み取り専用ポートを無効にして、kubelet への不正アクセスのリスクを軽減する方法と、より安全なポートにアプリケーションを移行する方法について説明します。

GKE などの Kubernetes クラスタでは、ノードで実行されている kubelet プロセスが、安全でないポート 10255 を使用する読み取り専用 API を備えています。Kubernetes では、このポートに対して認証や認可のチェックが行われません。kubelet は、より安全な認証済みのポート 10250 で同じエンドポイントにサービスを提供します。

kubelet 読み取り専用ポートを無効にし、ポート 10255 を使用するワークロードがより安全なポート 10250 を使用するように切り替えます。

始める前に

始める前に、次の作業が完了していることを確認してください。

  • Google Kubernetes Engine API を有効にする。
  • Google Kubernetes Engine API の有効化
  • このタスクに Google Cloud CLI を使用する場合は、gcloud CLI をインストールして初期化する。すでに gcloud CLI をインストールしている場合は、gcloud components update を実行して最新のバージョンを取得する。

要件

  • GKE バージョン 1.26.4-gke.500 以降でのみ安全でない kubelet 読み取り専用ポートを無効にできます。

安全でないポートの使用状況を確認してアプリケーションを移行する

安全でない読み取り専用ポートを無効にする前に、対象のポートを使用する実行中のアプリケーションをより安全な読み取り専用ポートに移行します。移行が必要となる可能性のあるワークロードには、カスタム指標パイプラインと、kubelet エンドポイントにアクセスするワークロードが含まれます。

  • 指標など、ノード上の kubelet API によって提供される情報にアクセスする必要があるワークロードには、ポート 10250 を使用します。
  • ノード上の Pod のリストなど、ノード上の kubelet API を使用して Kubernetes 情報を取得するワークロードの場合は、代わりに Kubernetes API を使用します。

アプリケーションが安全でない kubelet 読み取り専用ポートを使用しているかどうかを確認する

このセクションでは、クラスタで安全でないポートの使用を確認する方法について説明します。

Autopilot モードでポートの使用状況を確認する

Autopilot クラスタでポートの使用状況を確認するには、クラスタで実行されている DaemonSet 以外のワークロードが少なくとも 1 つあることを確認します。空の Autopilot クラスタで次の手順を行うと、結果が無効になる可能性があります。

  1. 次のマニフェストを read-only-port-metrics.yaml として保存します。

    apiVersion: v1
    kind: Namespace
    metadata:
      name: node-metrics-printer-namespace
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: node-metrics-printer-role
    rules:
    - apiGroups:
      - ""
      resources:
      - nodes/metrics
      verbs:
      - get
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: node-metrics-printer-binding
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: node-metrics-printer-role
    subjects:
    - kind: ServiceAccount
      name: node-metrics-printer-sa
      namespace: node-metrics-printer-namespace
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: node-metrics-printer-sa
      namespace: node-metrics-printer-namespace
    ---
    apiVersion: apps/v1
    kind: DaemonSet
    metadata:
      name: node-metrics-printer
      namespace: node-metrics-printer-namespace
    spec:
      selector:
        matchLabels:
          app: node-metrics-printer
      template:
        metadata:
          labels:
            app: node-metrics-printer
        spec:
          serviceAccountName: node-metrics-printer-sa
          containers:
          - name: metrics-printer
            image: us-docker.pkg.dev/cloud-builders/ga/v1/curl:latest
            command: ["sh", "-c"]
            args:
            - 'while true; do curl -s --cacert "${CA_CERT}" -H "Authorization: Bearer $(cat ${TOKEN_FILE})" "https://${NODE_ADDRESS}:10250/metrics"|grep kubelet_http_requests_total; sleep 20; done'
            env:
            - name: CA_CERT
              value: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
            - name: TOKEN_FILE
              value: /var/run/secrets/kubernetes.io/serviceaccount/token
            - name: NODE_ADDRESS
              valueFrom:
                fieldRef:
                  fieldPath: status.hostIP
    

    このマニフェストの内容は次のとおりです。

    1. Namespace を作成し、ノードの指標を読み取ることができるように RBAC ロールを設定します。
    2. 安全でない読み取り専用ポートの kubelet 指標を確認する DaemonSet をデプロイします。
  2. マニフェストをデプロイします。

    kubectl create -f read-only-port-metrics.yaml
    
  3. DaemonSet のログを確認します。

    kubectl logs --namespace=node-metrics-printer-namespace \
        --all-containers --prefix \
        --selector=app=node-metrics-printer
    

    出力に「server_type=readonly」という文字列を含む結果がある場合、アプリは安全でない読み取り専用ポートを使用しています。

Standard モードでポートの使用状況を確認する

クラスタ内のすべてのノードプールの少なくとも 1 つのノードで、次のコマンドを実行します。

kubectl get --raw /api/v1/nodes/NODE_NAME/proxy/metrics | grep http_requests_total | grep readonly

NODE_NAME は、ノードの名前で置き換えます。

ノードのワークロードが安全でない kubelet の読み取り専用ポートを使用する場合、出力には次の例のように、文字列 server_type="readonly" を含むエントリが含まれます。

kubelet_http_requests_total{long_running="false",method="GET",path="healthz",server_type="readonly"} 3
kubelet_http_requests_total{long_running="false",method="GET",path="metrics",server_type="readonly"} 2549
kubelet_http_requests_total{long_running="false",method="GET",path="metrics/probes",server_type="readonly"} 2546
kubelet_http_requests_total{long_running="false",method="GET",path="other",server_type="readonly"} 2
kubelet_http_requests_total{long_running="false",method="GET",path="pods",server_type="readonly"} 1
kubelet_http_requests_total{long_running="false",method="GET",path="stats",server_type="readonly"} 2549

出力が空の場合、対象のノード上のアプリケーションは安全でない読み取り専用ポートを使用していません。

安全でない kubelet 読み取り専用ポートから移行する

通常、アプリケーションをセキュアポートに移行するには、次の手順を行います。

  1. 安全でない読み取り専用ポートを参照する URL またはエンドポイントを更新して、安全な読み取り専用ポートを使用するようにします。たとえば、http://203.0.113.104:10255http://203.0.113.104:10250 に変更します。

  2. HTTP クライアントの認証局(CA)証明書をクラスタ CA 証明書に設定します。この証明書を確認するには、次のコマンドを実行します。

    gcloud container clusters describe CLUSTER_NAME \
        --location=LOCATION \
        --format="value(masterAuth.clusterCaCertificate)"
    

    次のように置き換えます。

    • CLUSTER_NAME: クラスタの名前。
    • LOCATION: クラスタのロケーション。

認証済みポート 10250 では、特定のリソースにアクセスするために、サブジェクトに適切な RBAC ロールを付与する必要があります。詳細については、Kubernetes ドキュメントの kubelet の認可をご覧ください。

ワークロードが安全でない kubelet 読み取り専用ポートで /pods エンドポイントを使用する場合は、安全な kubelet ポートのエンドポイントにアクセスするための nodes/proxy RBAC 権限を付与する必要があります。nodes/proxy は強力な権限であり、GKE Autopilot クラスタでは付与できません。また、GKE Standard クラスタでは付与しないでください。代わりに、ノード名に fieldSelector を指定して Kubernetes API を使用します。

安全でない kubelet 読み取り専用ポートに依存するサードパーティ アプリケーションを使用している場合は、安全なポート 10250 に移行する手順についてアプリケーション ベンダーに確認してください。

移行の例

安全でない kubelet 読み取り専用ポートから指標をクエリする Pod について考えてみましょう。

apiVersion: v1
kind: Pod
metadata:
  name: kubelet-readonly-example
spec:
  restartPolicy: Never
  containers:
  - name: kubelet-readonly-example
    image: us-docker.pkg.dev/cloud-builders/ga/v1/curl:latest
    command:
      - curl
      - http://$(NODE_ADDRESS):10255/metrics
    env:
    - name: NODE_ADDRESS
      valueFrom:
        fieldRef:
          fieldPath: status.hostIP

このアプリケーションは次の処理を行います。

  • default Namespace で default ServiceAccount を使用します。
  • ノードの /metrics エンドポイントに対して curl コマンドを実行します。

セキュアポート 10250 を使用するようにこの Pod を更新する手順は次のとおりです。

  1. ノード指標を取得するアクセス権を持つ ClusterRole を作成します。

    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: curl-authenticated-role
    rules:
    - apiGroups:
      - ""
      resources:
      - nodes/metrics
      verbs:
      - get
    
  2. ClusterRole をアプリケーションの ID にバインドします。

    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: curl-authenticated-role-binding
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: curl-authenticated-role
    subjects:
    - kind: ServiceAccount
      name: default
      namespace: default
    
  3. セキュアポート エンドポイントと対応する認可ヘッダーを使用するように curl コマンドを更新します。

    apiVersion: v1
    kind: Pod
    metadata:
      name: kubelet-authenticated-example
    spec:
      restartPolicy: Never
      containers:
      - name: kubelet-readonly-example
        image: us-docker.pkg.dev/cloud-builders/ga/v1/curl:latest
        env:
        - name: NODE_ADDRESS
          valueFrom:
            fieldRef:
              fieldPath: status.hostIP
        command:
        - sh
        - -c
        - 'curl -s --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -H "Authorization:
          Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" https://${NODE_ADDRESS}:10250/metrics'
    

VPC ファイアウォール ルールを変更する

ポート 10250 を使用するようにワークロードを更新する場合は、クラスタ内の Pod がノードの IP アドレス範囲内のポートに到達できるようにファイアウォール ルールを作成します。ファイアウォール ルールは、次の処理を行います。

  • 内部 Pod IP アドレス範囲からノード IP アドレス範囲の TCP ポート 10250 への受信トラフィックを許可します。
  • パブリック インターネットからノード IP アドレス範囲の TCP ポート 10250 への受信トラフィックを拒否します。

新しいルールで指定するパラメータのテンプレートとして、次のデフォルトの GKE ファイアウォール ルールを使用できます。

  • gke-[cluster-name]-[cluster-hash]-inkubelet
  • gke-[cluster-name]-[cluster-hash]-exkubelet

Autopilot クラスタで安全でない読み取り専用ポートを無効にする

新しい Autopilot クラスタと既存の Autopilot クラスタで、安全でない kubelet 読み取り専用ポートを無効にできます。

新しい Autopilot クラスタで安全でない読み取り専用ポートを無効にする

新しい Autopilot クラスタを作成するときに安全でない kubelet 読み取り専用ポートを無効にするには、次のコマンドのように --no-autoprovisioning-enable-insecure-kubelet-readonly-port フラグを使用します。

gcloud container clusters create-auto CLUSTER_NAME \
    --location=LOCATION \
    --no-autoprovisioning-enable-insecure-kubelet-readonly-port

次のように置き換えます。

  • CLUSTER_NAME: 新しい Autopilot クラスタの名前。
  • LOCATION: 新しい Autopilot クラスタのロケーション。

既存の Autopilot クラスタで安全でない読み取り専用ポートを無効にする

既存の Autopilot クラスタで安全でない kubelet 読み取り専用ポートを無効にするには、次のコマンドのように --no-autoprovisioning-enable-insecure-kubelet-readonly-port フラグを使用します。クラスタ内のすべての新規ノードと既存ノードが、ポートの使用を停止します。

gcloud container clusters update CLUSTER_NAME \
    --location=LOCATION \
    --no-autoprovisioning-enable-insecure-kubelet-readonly-port

次のように置き換えます。

  • CLUSTER_NAME: 既存のクラスタの名前。
  • LOCATION: 既存のクラスタのロケーション。

Standard クラスタで安全でない読み取り専用ポートを無効にする

安全でない kubelet 読み取り専用ポートは、Standard クラスタ全体または個別のノードプールで無効にできます。クラスタ全体でポートを無効にすることをおすすめします。

ノード自動プロビジョニングを使用する場合、自動プロビジョニングされたノードプールは、クラスタレベルで指定されたポート設定を継承します。必要に応じて、自動プロビジョニングされたノードプールに別の設定を指定できますが、クラスタ内のすべてのノードでポートを無効にすることをおすすめします。

ノードシステム構成ファイルを使用して、安全でない kubelet 読み取り専用ポートを宣言的に無効にすることもできます。このファイルを使用すると、次のセクションのコマンドを使用して kubelet の設定を制御できなくなります。

新しい Standard クラスタで安全でない読み取り専用ポートを無効にする

新しい Standard クラスタで安全でない kubelet 読み取り専用ポートを無効にするには、次のコマンドのように --no-enable-insecure-kubelet-readonly-port フラグを使用します。

gcloud container clusters create CLUSTER_NAME \
    --location=LOCATION \
    --no-enable-insecure-kubelet-readonly-port

次のように置き換えます。

  • CLUSTER_NAME: 新しい Standard クラスタの名前。
  • LOCATION: 新しい Standard クラスタのロケーション。

必要に応じて --no-autoprovisioning-enable-insecure-kubelet-readonly-port フラグを追加して、ノード自動プロビジョニング設定を個別に制御することもできますが、この方法はおすすめしません。このフラグは、自動プロビジョニングされたノードプールのローリング アップデートを開始します。これにより、実行中のワークロードが中断する可能性があります。

既存の Standard クラスタで安全でない読み取り専用ポートを無効にする

既存の Standard クラスタで安全でない kubelet 読み取り専用ポートを無効にするには、次のコマンドのように --no-enable-insecure-kubelet-readonly-port フラグを使用します。新しいノードプールでは、安全でないポートは使用されなくなります。GKE は、既存のノードプールを自動的に更新しません。

gcloud container clusters update CLUSTER_NAME \
    --location=LOCATION \
    --no-enable-insecure-kubelet-readonly-port

次のように置き換えます。

  • CLUSTER_NAME: 既存の Standard クラスタの名前。
  • LOCATION: 既存の Standard クラスタのロケーション。

Standard ノードプールで安全でない読み取り専用ポートを無効にする

すべてのケースで、クラスタレベルで読み取り専用ポートの設定を行うことをおすすめします。すでに実行中のノードプールが存在する既存のクラスタで読み取り専用ポートを無効にした場合は、次のコマンドを使用して、それらのノードプールのポートを無効にします。

gcloud container node-pools update NODE_POOL_NAME \
    --cluster=CLUSTER_NAME \
    --location=LOCATION \
    --no-enable-insecure-kubelet-readonly-port

次のように置き換えます。

  • NODE_POOL_NAME: ノードプールの名前。
  • CLUSTER_NAME: クラスタの名前。
  • LOCATION: クラスタのロケーション。

ポートが無効になっていることを確認する

安全でない kubelet 読み取り専用ポートが無効になっていることを確認するには、GKE リソースを記述します。

Autopilot クラスタでポートのステータスを確認する

次のコマンドを実行します。

gcloud container clusters describe CLUSTER_NAME \
    --location=LOCATION \
    --flatten=nodePoolAutoConfig \
    --format="value(nodeKubeletConfig)"

次のように置き換えます。

  • CLUSTER_NAME: Autopilot クラスタの名前。
  • LOCATION: Autopilot クラスタのロケーション。

ポートが無効になっている場合、出力は次のようになります。

insecureKubeletReadonlyPortEnabled: false

Standard クラスタでポートのステータスを確認する

GKE API を使用してクラスタを記述すると、nodePoolDefaults.nodeConfigDefaults.nodeKubeletConfig フィールドにポートのステータスが表示されます。

Standard クラスタには、kubelet の読み取り専用ポートのステータスの値を設定する nodeConfig フィールドも表示されます。nodeConfig フィールドは非推奨であり、新しい Standard モードクラスタを作成するときに GKE によって作成されるデフォルトのノードプールにのみ適用されます。非推奨の nodeConfig フィールドのポートのステータスは、クラスタ内の他のノードプールに適用されません。

次のコマンドを実行します。

gcloud container clusters describe CLUSTER_NAME \
    --location=LOCATION \
    --flatten=nodePoolDefaults.nodeConfigDefaults \
    --format="value(nodeKubeletConfig)"

次のように置き換えます。

  • CLUSTER_NAME: Standard クラスタの名前。
  • LOCATION: Standard クラスタのロケーション。

ポートが無効になっている場合、出力は次のようになります。

insecureKubeletReadonlyPortEnabled: false

Standard ノードプールでポートのステータスを確認する

次のコマンドを実行します。

gcloud container node-pools describe NODE_POOL_NAME \
    --cluster=CLUSTER_NAME \
    --location=LOCATION \
    --flatten=config \
    --format="value(kubeletConfig)"

次のように置き換えます。

  • NODE_POOL_NAME: ノードプールの名前。
  • CLUSTER_NAME: クラスタの名前。
  • LOCATION: クラスタのロケーション。

ポートが無効になっている場合、出力は次のようになります。

insecureKubeletReadonlyPortEnabled: false