使用 DaemonSet 自动引导 GKE 节点

本教程介绍如何使用 DaemonSets 来自定义 Google Kubernetes Engine (GKE) 集群的节点。DaemonSet 可确保所有节点或所选节点运行的都是某个 Pod 的副本。 当新节点添加到集群时,它们也会运行 DaemonSet 中的 Pod。

如果您用于初始化集群的工具和系统与用于运行工作负载的工具和系统不同,则管理环境所需的工作量将会增加。例如,如果您使用某种配置管理工具来初始化集群节点,则会对其余工作负载所在的运行时环境之外的过程构成依赖。 使用 DaemonSet 可让您使用修改 GKE 节点时所用的相同工具来编排工作负载。

本教程旨在帮助系统管理员、系统工程师或基础架构运营人员简化 Kubernetes 集群的初始化过程。

在阅读本页面之前,请确保您熟悉以下内容:

在本教程中,您将学习如何使用 Kubernetes 污点和容忍,以帮助确保节点由 DaemonSet 配置,然后才能在这些节点上调度应用工作负载。

目标

在本教程中,您将执行以下操作:

  • 预配 GKE 集群。
  • 对节点池设置污点,以防止在应用节点配置之前调度工作负载。
  • 部署一个用于配置节点并移除污点的 DaemonSet。
  • 验证集群节点是否已配置,并且污点已移除。

费用

在本文档中,您将使用 Google Cloud的以下收费组件:

如需根据您的预计使用情况来估算费用,请使用价格计算器

新 Google Cloud 用户可能有资格申请免费试用

完成本文档中描述的任务后,您可以通过删除所创建的资源来避免继续计费。如需了解详情,请参阅清理

准备工作

  1. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  2. Verify that billing is enabled for your Google Cloud project.

特权 DaemonSet 的安全注意事项

在 DaemonSet(或任何 Pod)中使用 securityContext: privileged: true 设置非常强大,但会带来严重的安全隐患,因为该设置会针对相应 Pod 停用大多数容器隔离边界。您应注意以下安全限制以及它带来的风险:

  • 容器逃逸或主机遭入侵:特权容器应用或映像中的漏洞可直接导致对主机节点的 root 访问。
  • 违反最小权限原则:特权模式授予所有功能,这可能远远超出特定任务所需的功能。如果容器遭到入侵,这种广泛的访问权限会增加潜在的损害。
  • 节点不稳定:特权容器中可能会运行意外或恶意命令,例如不正确的 sysctl 值或 rm -rf /host/boot 等命令。这些类型的命令可能会导致宿主节点操作系统崩溃或损坏。
  • 横向移动:通过特权 DaemonSet 攻陷一个节点后,攻击者便可站稳脚跟,进而攻击其他节点、Kubernetes 控制平面或连接的系统。
  • 数据暴露:对主机文件系统 (/) 的无限制访问可能会暴露节点上存储的敏感数据,包括凭据、密钥或其他 Pod 的数据(如果它们使用 hostPath 卷)。
  • 增加了攻击面:特权模式会向容器内的潜在漏洞暴露更多主机内核系统调用和功能。

为避免安全风险,您应了解以下最佳实践和缓解措施:

  • 避免使用特权模式:最安全的方法是完全避免使用 privileged: true 设置。
  • 使用 Linux 功能:如果需要提升权限,您可以在 securityContext.capabilities.add 字段中授予特定的 Linux 功能(例如 NET_ADMINSYS_ADMINSYS_MODULE),而不是授予完整特权。此方法遵循最小权限原则,我们建议您采用此方法,而不是授予广泛的权限。
  • 限制范围:仅在专用(可能已污染)的节点池上运行特权 DaemonSet,以限制容器遭到入侵时可能造成的影响。
  • 强制执行政策:使用 Policy Controller 或 Gatekeeper 等工具创建政策,以限制、审核或要求提供部署特权容器的理由。
  • 扫描并使用受信任的映像:使用 Binary Authorization 和严格的映像扫描来帮助确保只有经过审查的受信任容器映像以提升的权限运行。
  • 尽量减少主机挂载:仅挂载所需的特定主机路径,并尽可能使用 readOnly: true。避免装载整个根文件系统 (/)。
  • 定期执行审核:定期检查所有以 privileged: true 设置运行的工作负载。

引导环境

在本部分中,您将执行以下操作:

  1. 启用必要的 Cloud API
  2. 为 GKE 集群中的节点预配一个具有有限特权的服务账号
  3. 准备 GKE 集群。
  4. 向用户授予集群管理权限。

启用 Cloud API

  1. 打开 Cloud Shell。

    打开 Cloud Shell

  2. 选择 Google Cloud 项目:

    gcloud config set project project-id
    

    project-id 替换为您为本教程创建或选择的Google Cloud 项目的 ID。

  3. 启用 Kubernetes Engine API:

    gcloud services enable container.googleapis.com
    

预配服务账号以管理 GKE 集群

在本部分,您将创建一个与集群中的节点关联的服务账号。在本教程中,GKE 节点将使用此服务账号而不是默认服务账号。最佳实践是仅授予服务账号运行应用所需的角色和访问权限

服务账号所需的角色如下所示:

  • Monitoring Viewer 角色 (roles/monitoring.viewer)。此角色授予对监控数据的只读权限。
  • Monitoring Metric Writer 角色 (roles/monitoring.metricWriter)。此角色允许写入监控数据。
  • Logs Writer 角色 (roles/logging.logWriter)。此角色授予写入日志的权限。

要预配服务账号,请按照下列步骤操作:

  1. 在 Cloud Shell 中,将一个环境变量初始化以存储服务账号名称:

    GKE_SERVICE_ACCOUNT_NAME=ds-init-tutorial-gke
    
  2. 创建服务账号:

    gcloud iam service-accounts create "$GKE_SERVICE_ACCOUNT_NAME" \
      --display-name="$GKE_SERVICE_ACCOUNT_NAME"
    
  3. 将一个环境变量初始化以存储服务账号的电子邮件账号名称:

    GKE_SERVICE_ACCOUNT_EMAIL="$(gcloud iam service-accounts list \
        --format='value(email)' \
        --filter=displayName:"$GKE_SERVICE_ACCOUNT_NAME")"
    
  4. 将 Identity and Access Management (IAM) 角色绑定到服务账号:

    gcloud projects add-iam-policy-binding \
        "$(gcloud config get-value project 2> /dev/null)" \
        --member serviceAccount:"$GKE_SERVICE_ACCOUNT_EMAIL" \
        --role roles/monitoring.viewer
    gcloud projects add-iam-policy-binding \
        "$(gcloud config get-value project 2> /dev/null)" \
        --member serviceAccount:"$GKE_SERVICE_ACCOUNT_EMAIL" \
        --role roles/monitoring.metricWriter
    gcloud projects add-iam-policy-binding \
        "$(gcloud config get-value project 2> /dev/null)" \
        --member serviceAccount:"$GKE_SERVICE_ACCOUNT_EMAIL" \
        --role roles/logging.logWriter
    

准备 GKE 集群

在本节中,您将启动 GKE 集群、授予权限并完成集群配置。

在本教程中,具有相对较少的小型通用节点的集群已足以演示本教程的概念。您将创建一个具有一个节点池(默认节点池)的集群。

  • 在 Cloud Shell 中,创建并启动区域级 GKE 集群:

    gcloud container clusters create ds-init-tutorial \
        --enable-ip-alias \
        --machine-type=n1-standard-2 \
        --metadata disable-legacy-endpoints=true \
        --node-labels=app=default-init \
        --node-locations us-central1-a,us-central1-b,us-central1-c \
        --no-enable-basic-auth \
        --no-issue-client-certificate \
        --num-nodes=1 \
        --location us-central1 \
        --service-account="$GKE_SERVICE_ACCOUNT_EMAIL"
    

使用 DaemonSet 应用节点配置

在本部分中,您将通过向节点池应用污点来防止工作负载在配置完成之前在节点上运行。然后,您部署一个执行以下操作的 DaemonSet:

  1. 通过使用对污点的容忍设置,在污点节点上调度 Pod。
  2. 运行一个特权 init 容器,该容器先使用 sysctl 应用节点配置,然后使用 kubectl 从节点中移除污点。移除污点后,节点可供工作负载调度。
  3. 安排并运行一个处于空闲状态且不消耗资源的暂停容器,以防止 DaemonSet 重新安排用于配置的 Pod。

本教程应用 vm.max_map_count=262144 内核参数作为配置示例。

  1. 将污点应用于默认节点池:

    gcloud container node-pools update default-pool \
      --cluster=ds-init-tutorial \
      --node-taints=node.config.status/stage=configuring:NoSchedule \
      --region=us-central1
    

    有了此污点,只有能容忍它的 Pod(例如 DaemonSet Pod)才能调度到此节点池中。

  2. 验证污点是否已应用:

    kubectl describe nodes -l cloud.google.com/gke-nodepool=default-pool | grep Taints
    

    节点状态应显示 node.config.status/stage=configuring:NoSchedule

  3. 将以下清单保存为 auto-untaint-daemonset.yaml

    # WARNING: This DaemonSet runs as privileged, which has significant
    # security implications. Only use this on clusters where you have
    # strict controls over what is deployed.
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: node-config-sa
      namespace: default
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: node-patcher-role
    rules:
    - apiGroups: [""]
      resources: ["nodes"]
      # Permissions needed to read and remove a taint from the node.
      verbs: ["get", "patch", "update"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: node-config-binding
    subjects:
    - kind: ServiceAccount
      name: node-config-sa
      namespace: default
    roleRef:
      kind: ClusterRole
      name: node-patcher-role
      apiGroup: rbac.authorization.k8s.io
    ---
    apiVersion: apps/v1
    kind: DaemonSet
    metadata:
      name: auto-untaint-daemonset
      labels:
        app: auto-untaint-configurator
    spec:
      selector:
        matchLabels:
          app: auto-untaint-configurator
      updateStrategy:
        type: RollingUpdate
      template:
        metadata:
          labels:
            app: auto-untaint-configurator
        spec:
          serviceAccountName: node-config-sa
          hostPID: true
          # Toleration now matches the taint on your node.
          tolerations:
          - key: "node.config.status/stage"
            operator: "Equal"
            value: "configuring"
            effect: "NoSchedule"
          volumes:
          - name: host-root-fs
            hostPath:
              path: /
          initContainers:
          - name: configure-and-untaint
            image: ubuntu:22.04 # Using a standard container image.
            securityContext:
              privileged: true # Required for chroot and sysctl.
            env:
            - name: NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            volumeMounts:
            - name: host-root-fs
              mountPath: /host
            command: ["/bin/bash", "-c"]
            args:
            - |
              # Using explicit error checking for each critical command.
    
              # Define the configuration and taint details.
              SYSCTL_PARAM="vm.max_map_count"
              SYSCTL_VALUE="262144"
              TAINT_KEY="node.config.status/stage"
    
              echo "Running configuration on node: ${NODE_NAME}"
    
              # 1. APPLY CONFIGURATION
              echo "--> Applying ${SYSCTL_PARAM}=${SYSCTL_VALUE}..."
              if ! chroot /host sysctl -w "${SYSCTL_PARAM}=${SYSCTL_VALUE}"; then
                echo "ERROR: Failed to apply sysctl parameter." >&2
                exit 1
              fi
              echo "--> Configuration applied successfully."
    
              # 2. UNTAINT THE NODE
              # This command removes the taint from the node this Pod is running on.
              echo "--> Untainting node ${NODE_NAME} by removing taint ${TAINT_KEY}..."
              if ! /host/home/kubernetes/bin/kubectl taint node "${NODE_NAME}" "${TAINT_KEY}:NoSchedule-"; then
                echo "ERROR: Failed to untaint the node." >&2
                exit 1
              fi
              echo "--> Node has been untainted and is now schedulable."
          # The main container is minimal; it just keeps the Pod running.
          containers:
          - name: pause-container
            image: registry.k8s.io/pause:3.9
    

    此清单会创建 ServiceAccount、ClusterRole 和 ClusterRoleBinding,以授予 DaemonSet 从节点中移除污点的权限。DaemonSet 会将 Pod 部署到容忍 configuring:NoSchedule 污点的每个节点。此 Pod 运行一个特权 init 容器,该容器会应用 sysctl 配置 (vm.max_map_count=262144) 并移除节点污点,从而使节点可调度。然后,暂停容器开始运行,以保持 Pod 运行。

    init 容器在特权模式下运行,这会带来安全隐患。如需了解详情,请参阅特权 DaemonSet 的权衡取舍和安全限制

  4. 应用清单:

    kubectl apply -f auto-untaint-daemonset.yaml
    
  5. 验证 DaemonSet Pod 是否已创建,并等待它们达到 Running 状态:

    kubectl get pods -l app=auto-untaint-configurator -o wide
    

    Running 状态表示 init 容器已成功完成。 记下 Pod 名称,以便在下一部分中使用它来验证初始化。

验证初始化过程

节点配置完成后,您可以通过检查日志来验证结果。

  1. 检查其中一个 pod 的 init 容器日志,查看其输出:

    kubectl logs POD_NAME -c configure-and-untaint
    

    POD_NAME 替换为您的 Pod 名称。

    您应该会看到输出,指示配置成功且节点已取消污点。

  2. 验证污点是否已移除:

    kubectl describe nodes -l cloud.google.com/gke-nodepool=default-pool | grep Taints
    

    节点状态应显示 Taints: <none>,或显示具有键 node.config.status/stage 的污点。

清理

为避免系统因本教程中使用的资源向您的 Google Cloud 账号收取费用,您可以删除为本教程创建的项目。如果您创建了一个专用于本教程的项目,可以将其完全删除。 如果您使用现有项目且不想删除该项目,请按以下步骤清理该项目

清理项目

要在不删除项目的情况下对其进行清理,您需要移除在本教程中创建的资源。

  1. 在 Cloud Shell 中,删除 GKE 集群:

    gcloud container clusters delete ds-init-tutorial --quiet --region us-central1
    
  2. 删除服务账号:

    gcloud iam service-accounts delete "$GKE_SERVICE_ACCOUNT_EMAIL" --quiet
    

删除项目

为避免支付费用,最简单的方法是删除您为本教程创建的项目。

  1. 在 Google Cloud 控制台中,前往管理资源页面。

    转到“管理资源”

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。

后续步骤