在 GKE 集群中停用 kubelet 只读端口


本页面向您展示如何禁用 Google Kubernetes Engine (GKE) 集群中不安全的 kubelet 只读端口,以降低未经授权访问 kubelet 的风险,以及如何将应用迁移到更安全的端口。

在 Kubernetes 集群(包括 GKE)中,节点上运行的 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
  • 对于在节点上使用 kubelet API 获取 Kubernetes 信息(例如列出节点上的 Pod)的工作负载,请改用 Kubernetes API。

检查应用是否使用不安全的 kubelet 只读端口

本部分介绍如何检查集群中是否存在不安全的端口使用情况。

检查 Autopilot 模式下的端口使用情况

如需检查 Autopilot 集群中的端口使用情况,请确保至少有一个工作负载不是在集群中运行的 DaemonSet。如果您对空的 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. 创建命名空间并设置 RBAC 角色以允许读取节点指标。
    2. 部署一个 DaemonSet,用于针对不安全的只读端口检查 kubelet 指标。
  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 模式下的端口使用情况

在集群的每个节点池中的至少一个节点上运行以下命令:

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. 更新引用不安全的只读端口的网址或端点,以改用安全的只读端口。例如,将 http://203.0.113.104:10255 更改为 http://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 端点,您需要授予 nodes/proxy RBAC 权限以在安全的 kubelet 端口上访问相应端点。nodes/proxy 是一种强大的权限,您无法在 GKE Autopilot 集群中授予该权限,也不应在 GKE Standard 集群中授予。请改用将 fieldSelector 用于节点名称的 Kubernetes API。

如果您使用依赖于不安全的 kubelet 只读端口的第三方应用,请与应用供应商联系,以获取有关迁移到安全端口 10250 的说明。

示例迁移

假设一个 Pod 从不安全的 kubelet 只读端口查询指标。

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 命名空间中的 default ServiceAccount
  • 在节点上对 /metrics 端点运行 curl 命令。

如需更新此 Pod 以使用安全端口 10250,请执行以下步骤:

  1. 创建有权获取节点指标的 ClusterRole:

    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: curl-authenticated-role
    rules:
    - apiGroups:
      - ""
      resources:
      - nodes/metrics
      verbs:
      - get
    
  2. 将 ClusterRole 绑定到应用的身份:

    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 集群停用不安全的 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 集群上停用不安全的只读端口

您可以为整个 Standard 集群或单个节点池停用不安全的 kubelet 只读端口。我们建议您为整个集群停用相应端口。

如果使用节点自动预配功能,则自动预配的节点池会继承您在集群级别指定的端口设置。您可以选择为自动预配的节点池指定不同的设置,但我们建议您在集群的所有节点上停用相应端口。

您还可以使用节点系统配置文件以声明方式停用不安全的 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:新标准集群的名称。
  • LOCATION:新 Standard 集群的位置。

您可以选择添加 --no-autoprovisioning-enable-insure-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 集群中,您还会看到 nodeConfig 字段,该字段会设置 kubelet 只读端口状态的值。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