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 は、ノードの名前で置き換えます。

出力に server_type="readonly" 文字列を含むエントリが含まれている場合は、次のシナリオが考えられます。

  • ノードのワークロードが安全でない kubelet 読み取り専用ポートを使用している。
  • 安全でないポートを無効にしても、コマンドは server_type="readonly" 文字列を返します。これは、kubelet_http_requests_total 指標が、kubelet サーバーが最後に再起動してから受信した HTTP リクエストの累積数を表すためです。安全でないポートが無効になっても、この番号はリセットされません。この数値は、GKE が kubelet サーバーを再起動した後(ノード アップグレード中など)にリセットされます。詳細については、Kubernetes 指標リファレンスをご覧ください。

出力が空の場合、対象のノード上のワークロードは安全でない読み取り専用ポートを使用していません。

安全でない kubelet 読み取り専用ポートを使用しているワークロードを特定する

安全でないポートを使用しているワークロードを特定するには、ConfigMap や Pod などのワークロードの構成ファイルを確認します。

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

    kubectl get pods --all-namespaces -o yaml | grep 10255
    kubectl get configmaps --all-namespaces -o yaml | grep 10255

コマンドの出力が空でない場合、次のスクリプトを使用して、安全でないポートを使用している ConfigMap または Pod の名前を特定します。

# This function checks if a Kubernetes resource is using the insecure port 10255.
#
# Arguments:
#  $1 - Resource type (e.g., pod, configmap, )
#  $2 - Resource name
#  $3 - Namespace
#
# Output:
#  Prints a message indicating whether the resource is using the insecure port.
isUsingInsecurePort() {
  resource_type=$1
  resource_name=$2
  namespace=$3

  config=$(kubectl get $resource_type $resource_name -n $namespace -o yaml)

  # Check if kubectl output is empty
  if [[ -z "$config" ]]; then
    echo "No configuration file detected for $resource_type: $resource_name (Namespace: $namespace)"
    return
  fi

  if echo "$config" | grep -q "10255"; then
    echo "Warning: The configuration file ($resource_type: $namespace/$resource_name) is using insecure port 10255. It is recommended to migrate to port 10250 for enhanced security."
  else
    echo "Info: The configuration file ($resource_type: $namespace/$resource_name) is not using insecure port 10255."
  fi
}

# Get the list of ConfigMaps with their namespaces
configmaps=$(kubectl get configmaps -A -o custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name | tail -n +2 | awk '{print $1"/"$2}')

# Iterate over each ConfigMap
for configmap in $configmaps; do
  namespace=$(echo $configmap | cut -d/ -f1)
  configmap_name=$(echo $configmap | cut -d/ -f2)
  isUsingInsecurePort "configmap" "$configmap_name" "$namespace"
done

# Get the list of Pods with their namespaces
pods=$(kubectl get pods -A -o custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name | tail -n +2 | awk '{print $1"/"$2}')

# Iterate over each Pod
for pod in $pods; do
  namespace=$(echo $pod | cut -d/ -f1)
  pod_name=$(echo $pod | cut -d/ -f2)
  isUsingInsecurePort "pod" "$pod_name" "$namespace"
done

関連するワークロードを特定したら、次のセクションの手順に沿って、セキュアポート 10250 を使用するように移行します。

安全でない 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