使用 DaemonSet 自动引导 GKE 节点


本教程介绍如何使用 DaemonSets 来自定义 Google Kubernetes Engine (GKE) 集群的节点。DaemonSet 可确保所有节点或所选节点运行的都是某个 Pod 的副本。 通过此方法,您可以使用修改 GKE 节点时所用的相同工具来编排工作负载。

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

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

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

在本教程中,您将学习如何使用 Kubernetes 标签和选择器根据节点的标签来选择要运行的初始化过程。在这些步骤中,您将部署一个仅在具有 default-init 标签的节点上运行的 DaemonSet。但是,为了证明这种机制的灵活性,您可以再创建一个节点池,并将 alternative-init 标签应用于该新池中的节点。然后,您可以在集群中再部署一个仅在具有 alternative-init 标签的节点上运行的 DaemonSet。

此外,您还可以在每个节点上运行多个初始化过程,而不仅仅只运行一个。您可以利用此机制更好地构建初始化过程,明确区分每个过程的关注点。

在本教程中,作为示例,初始化过程会对带有 default-init 标签的每个节点执行以下操作:

  1. 将额外的磁盘挂接到节点。
  2. 使用节点的操作系统软件包管理系统安装一组软件包和库。
  3. 加载一组 Linux 内核模块。

目标

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

  • 预配和配置 GKE 集群。
  • 准备一个 DaemonSet 描述符,用于初始化集群中的节点。
  • 在集群中部署 DaemonSet。
  • 验证集群节点是否已初始化。

费用

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

您可使用价格计算器根据您的预计使用情况来估算费用。 Google Cloud 新用户可能有资格申请免费试用

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

准备工作

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. Make sure that billing is enabled for your Google Cloud project.

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

    Go to project selector

  5. Make sure that billing is enabled for your Google Cloud project.

引导环境

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

  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. 启用 Google Kubernetes Engine API:

    gcloud services enable container.googleapis.com
    

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

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

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

  • Monitoring Viewer 角色 (roles/monitoring.viewer)。此角色授予对 Cloud Monitoring 控制台和 API 的只读权限。
  • Monitoring Metric Writer 角色 (roles/monitoring.metricWriter)。此角色允许写入监控数据。
  • Logs Writer 角色 (roles/logging.logWriter)。此角色仅授予足以写入日志的权限。
  • Service Account User 角色 (roles/iam.serviceAccountUser)。此角色授予访问项目中服务账号的权限。在本教程中,初始化过程将模拟服务账号来运行特权操作。
  • Compute Admin 角色 (roles/compute.admin)。此角色提供对所有 Compute Engine 资源的完全控制权限。在本教程中,服务账号将需要此角色才能将其他磁盘挂接到集群节点。

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

  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/compute.admin
    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
    gcloud projects add-iam-policy-binding \
        "$(gcloud config get-value project 2> /dev/null)" \
        --member serviceAccount:"$GKE_SERVICE_ACCOUNT_EMAIL" \
        --role roles/iam.serviceAccountUser
    

准备 GKE 集群

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

在本教程中,具有相对较少的小型通用节点的集群已足以演示本教程的概念。您将创建一个具有一个节点池(默认节点池)的集群。然后,您将为默认节点池中的所有节点添加 default-init 标签。

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

    gcloud container clusters create ds-init-tutorial \
        --enable-ip-alias \
        --image-type=ubuntu_containerd \
        --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 \
        --region us-central1 \
        --service-account="$GKE_SERVICE_ACCOUNT_EMAIL"
    

部署 DaemonSet

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

  1. 创建用于存储初始化过程的 ConfigMap
  2. 部署用于安排和执行初始化过程的 DaemonSet。

DaemonSet 将执行以下操作:

  1. 配置一个卷,它将 ConfigMap 的内容提供给 DaemonSet 处理的容器。
  2. 为底层集群节点的特权文件系统区域配置卷。这些区域可让 DaemonSet 安排的容器直接与运行它们的节点进行交互。
  3. 安排并运行一个 init 容器,该容器会执行初始化过程,然后在完成时终止。
  4. 安排并运行一个处于空闲状态且不消耗资源的容器。

空闲容器可确保节点仅初始化一次。DaemonSet 的设计方式应确保所有符合条件的节点都运行 Pod 的副本。 如果您使用常规容器,则该容器将运行初始化过程,然后在完成时终止。根据设计,DaemonSet 将重新安排 Pod。为避免“持续重新安排”,DaemonSet 将首先在 init 容器中执行初始化过程,然后使容器保持运行状态。

以下初始化过程包含特权和非特权操作。通过使用 chroot,您可以运行命令,就像您直接在节点上而不仅仅是在容器内执行它们一样。

# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: entrypoint
  labels:
    app: default-init
data:
  entrypoint.sh: |
    #!/usr/bin/env bash

    set -euo pipefail

    DEBIAN_FRONTEND=noninteractive
    ROOT_MOUNT_DIR="${ROOT_MOUNT_DIR:-/root}"

    echo "Installing dependencies"
    apt-get update
    apt-get install -y apt-transport-https curl gnupg lsb-release

    echo "Installing gcloud SDK"
    export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)"
    echo "deb https://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
    curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
    apt-get update
    apt-get install -y google-cloud-sdk

    echo "Getting node metadata"
    NODE_NAME="$(curl -sS http://metadata.google.internal/computeMetadata/v1/instance/name -H 'Metadata-Flavor: Google')"
    ZONE="$(curl -sS http://metadata.google.internal/computeMetadata/v1/instance/zone -H 'Metadata-Flavor: Google' | awk -F  "/" '{print $4}')"

    echo "Setting up disks"
    DISK_NAME="$NODE_NAME-additional"

    if ! gcloud compute disks list --filter="name:$DISK_NAME" | grep "$DISK_NAME" > /dev/null; then
        echo "Creating $DISK_NAME"
        gcloud compute disks create "$DISK_NAME" --size=1024 --zone="$ZONE"
    else
        echo "$DISK_NAME already exists"
    fi

    if ! gcloud compute instances describe "$NODE_NAME" --zone "$ZONE" --format '(disks[].source)' | grep "$DISK_NAME" > /dev/null; then
        echo "Attaching $DISK_NAME to $NODE_NAME"
        gcloud compute instances attach-disk "$NODE_NAME" --device-name=sdb --disk "$DISK_NAME" --zone "$ZONE"
    else
        echo "$DISK_NAME is already attached to $NODE_NAME"
    fi

    # We use chroot to run the following commands in the host root (mounted as the /root volume in the container)
    echo "Installing nano"
    chroot "${ROOT_MOUNT_DIR}" apt-get update
    chroot "${ROOT_MOUNT_DIR}" apt-get install -y nano

    echo "Loading Kernel modules"
    # Load the bridge kernel module as an example
    chroot "${ROOT_MOUNT_DIR}" modprobe bridge
...

建议您仔细检查每个初始化过程,因为这些过程可能会更改集群节点的状态。由于这些过程会极大地影响集群的可用性和安全性,因此应只有一小部分人员才有权修改这些过程。

要部署 ConfigMap 和 DaemonSet,请执行以下操作:

  1. 在 Cloud Shell 中,将工作目录更改为 $HOME 目录:

    cd "$HOME"
    
  2. 克隆包含脚本和清单文件的 Git 代码库,这些脚本和文件用于部署和配置初始化过程:

    git clone https://github.com/GoogleCloudPlatform/solutions-gke-init-daemonsets-tutorial
    
  3. 将工作目录更改为新克隆的代码库目录:

    cd "$HOME"/solutions-gke-init-daemonsets-tutorial
    
  4. 创建一个 ConfigMap 来保存节点初始化脚本:

    kubectl apply -f cm-entrypoint.yaml
    
  5. 部署 DaemonSet:

    kubectl apply -f daemon-set.yaml
    
  6. 验证节点初始化是否已完成:

    kubectl get ds --watch
    

    等待系统将 DaemonSet 的状态报告为已就绪且最新,如以下输出所示:

    NAME              DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
    node-initializer   3         3         3         3            3          <none>  2h
    

验证初始化过程

在标有 default-init 标签的集群的每个节点执行初始化过程后,您就可验证结果。

对于每个节点,验证过程将检查以下各项:

  1. 其他磁盘是否已挂接且可供使用。
  2. 节点的操作系统软件包管理系统是否已安装软件包和库。
  3. 系统是否已加载内核模块。

执行验证过程:

  • 在 Cloud Shell 中,运行验证脚本:

    kubectl get nodes -o=jsonpath='{range .items[?(@.metadata.labels.app=="default-init")]}{.metadata.name}{" "}{.metadata.labels.failure-domain\.beta\.kubernetes\.io/zone}{"\n"}{end}' | while IFS= read -r line ; do ./verify-init.sh $line < /dev/null; done
    

    等待脚本运行,并检查每个节点是否已正确初始化,如以下输出所示:

    Verifying gke-ds-init-tutorial-default-pool-5464b7e3-nzjm (us-central1-c) configuration
    Disk configured successfully on gke-ds-init-tutorial-default-pool-5464b7e3-nzjm (us-central1-c)
    Packages installed successfully in gke-ds-init-tutorial-default-pool-5464b7e3-nzjm (us-central1-c)
    Kernel modules loaded successfully on gke-ds-init-tutorial-default-pool-5464b7e3-nzjm (us-central1-c)
    Verifying gke-ds-init-tutorial-default-pool-65baf745-0gwt (us-central1-a) configuration
    Disk configured successfully on gke-ds-init-tutorial-default-pool-65baf745-0gwt (us-central1-a)
    Packages installed successfully in gke-ds-init-tutorial-default-pool-65baf745-0gwt (us-central1-a)
    Kernel modules loaded successfully on gke-ds-init-tutorial-default-pool-65baf745-0gwt (us-central1-a)
    Verifying gke-ds-init-tutorial-default-pool-6b125c50-3xvl (us-central1-b) configuration
    Disk configured successfully on gke-ds-init-tutorial-default-pool-6b125c50-3xvl (us-central1-b)
    Packages installed successfully in gke-ds-init-tutorial-default-pool-6b125c50-3xvl (us-central1-b)
    Kernel modules loaded successfully on gke-ds-init-tutorial-default-pool-6b125c50-3xvl (us-central1-b)
    

清理

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

清理项目

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

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

    gcloud container clusters delete ds-init-tutorial --quiet --region us-central1
    
  2. 删除您在此示例初始化过程中创建的其他磁盘:

    gcloud compute disks list --filter="name:additional" --format="csv[no-heading](name,zone)" | while IFS= read -r line ; do DISK_NAME="$(echo $line | cut -d',' -f1)"; ZONE="$(echo $line | cut -d',' -f2)"; gcloud compute disks delete "$DISK_NAME" --quiet --zone "$ZONE" < /dev/null; done
    
  3. 删除服务账号:

    gcloud iam service-accounts delete "$GKE_SERVICE_ACCOUNT_EMAIL" --quiet
    
  4. 删除克隆的代码库目录:

    rm -rf "$HOME"/solutions-gke-init-daemonsets-tutorial
    

删除项目

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

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

后续步骤