使用节点自动预配功能优化多租户 GKE 集群中的资源用量


本教程介绍如何使用节点自动预配功能来扩缩多租户 Google Kubernetes Engine (GKE) 集群以及如何使用 Workload Identity 控制租户对 Cloud Storage 存储分区等资源的访问权限。本指南面向开发者和架构师;它假定您具备 Kubernetes 和 GKE 的基础知识。如果您需要简介,请参阅 GKE 概览

集群多租户通常用于降低费用或标准化跨租户的操作。要全面节省费用,则应调整集群大小,以便高效地使用集群资源。此外,您还应在集群自动扩缩时,确保所添加的集群节点大小合适,从而最大限度地减少资源浪费。

在本教程中,您将使用节点自动预配功能来扩缩集群。节点自动预配功能可以通过添加最适合待处理工作负载的集群节点来帮助优化集群资源用量,从而控制费用。

目标

  • 创建已启用节点自动预配功能和 Workload Identity 的 GKE 集群。
  • 为多租户设置集群。
  • 将作业提交到集群以展示节点自动预配功能如何创建和销毁优化大小的节点。
  • 使用污点和标签来指示节点自动预配功能为每个租户创建专用的节点池。
  • 使用 Workload Identity 控制对租户专属资源(如 Cloud Storage 存储分区)的访问权限。

费用

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

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

准备工作

  1. 登录您的 Google Cloud 账号。如果您是 Google Cloud 新手,请创建一个账号来评估我们的产品在实际场景中的表现。新客户还可获享 $300 赠金,用于运行、测试和部署工作负载。
  2. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  3. 确保您的 Google Cloud 项目已启用结算功能

  4. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  5. 确保您的 Google Cloud 项目已启用结算功能

  6. 在 Google Cloud 控制台中,激活 Cloud Shell。

    激活 Cloud Shell

    Cloud Shell 会话随即会在 Google Cloud 控制台的底部启动,并显示命令行提示符。Cloud Shell 是一个已安装 Google Cloud CLI 且已为当前项目设置值的 Shell 环境。该会话可能需要几秒钟时间来完成初始化。

  7. 在 Cloud Shell 中,为 GKE 和 Cloud Build API 启用 API:
    gcloud services enable container.googleapis.com \
        cloudbuild.googleapis.com
    

    此操作可能需要几分钟才能完成。

准备环境

在本部分中,您将获取本教程所需的代码,并使用整个教程中使用的值来设置您的环境。

  1. 在 Cloud Shell 中,定义本教程使用的环境变量:

    export PROJECT_ID=$(gcloud config get-value project)
    
  2. 克隆包含本教程代码的 GitHub 代码库:

    git clone https://github.com/GoogleCloudPlatform/solutions-gke-autoprovisioning
    
  3. 切换到代码库目录:

    cd solutions-gke-autoprovisioning
    
  4. 使用您的 Google 项目 ID 更新 Kubernetes YAML 作业配置文件:

    sed -i "s/MY_PROJECT/$PROJECT_ID/" manifests/bases/job/base-job.yaml
    
  5. 提交 Cloud Build 作业以构建容器映像:

    gcloud builds submit pi/ --tag gcr.io/$PROJECT_ID/generate-pi
    

    该映像是一个 Go 程序,可生成 pi 的近似值。您稍后使用此容器映像。

    Cloud Build 将映像导出到项目的 Container Registry 中。

创建 GKE 集群

在本部分中,您将创建一个启用了节点自动预配功能和 Workload Identity 的 GKE 集群。请注意以下关于集群创建过程的详细信息:

  • 您可以为集群指定 CPU 和内存限制。节点自动预配功能在从集群添加或移除节点时遵循这些限制。如需了解详情,请参阅 GKE 文档中的启用节点自动预配功能
  • 您可以指定自动预配节点池中的节点使用的默认服务账号和范围。使用这些设置,您可以控制预配节点的访问权限。如需了解详情,请参阅 GKE 文档中的为自动预配的节点设置身份默认
  • 您需要设置自动扩缩配置文件来优先处理利用率。此配置文件告知集群自动扩缩器,以快速缩小集群,从而最大限度地减少未使用的资源。这有助于提高批量或以作业为中心的工作负载的资源效率。该设置适用于集群中的所有节点池。
  • 您可以指定工作负载池来启用 Workload Identity。

使用以下命令创建集群:

  1. 创建服务账号:

    gcloud iam service-accounts create nap-sa
    

    此服务账号由自动预配的节点使用。

  2. 授予新的服务账号从 Container Registry 使用的 Cloud Storage 存储分区拉取映像的权限:

    gsutil iam ch \
        serviceAccount:nap-sa@$PROJECT_ID.iam.gserviceaccount.com:objectViewer \
        gs://artifacts.$PROJECT_ID.appspot.com
    
  3. 创建已启用节点自动预配功能和 Workload Identity 的 GKE 集群:

    gcloud container clusters create multitenant \
        --release-channel=regular \
        --zone=us-central1-c \
        --num-nodes=2 \
        --machine-type=n1-standard-2 \
        --workload-pool=${PROJECT_ID}.svc.id.goog \
        --autoscaling-profile=optimize-utilization \
        --enable-autoprovisioning \
        --autoprovisioning-service-account=nap-sa@${PROJECT_ID}.iam.gserviceaccount.com \
        --autoprovisioning-scopes=\
    https://www.googleapis.com/auth/devstorage.read_write,\
    https://www.googleapis.com/auth/cloud-platform \
        --min-cpu 1 \
        --min-memory 1 \
        --max-cpu 50 \
        --max-memory 256 \
        --enable-network-policy \
        --enable-ip-alias
    
  4. 设置默认集群名称和计算区域:

    gcloud config set container/cluster multitenant
    gcloud config set compute/zone us-central1-c
    

为多租户设置集群

当您运行多租户软件即服务 (SaaS) 应用时,通常应该分离您的租户。分离租户有助于最大限度地减少来自破解租户所造成的任何损害。此外,它还可以甚至跨租户分配集群资源,并跟踪每个租户消耗了多少资源。Kubernetes 不能保证租户之间完全安全地隔离,但是它确实提供了足以满足特定用例的功能。如需详细了解 GKE 多租户功能,请参阅 GKE 文档中的概览最佳做法指南部分。

在示例应用中,您将创建两个租户:tenant1tenant2。您可以将每个租户及其 Kubernetes 资源分隔到各自的命名空间中。您可以创建一个简单的网络政策,以通过防止来自其他命名空间的通信来强制执行租户隔离。稍后,您可以使用节点污点nodeSelector 字段来防止不同租户的 Pod 被调度到同一节点上。您可以通过在专用节点上运行租户工作负载来提供其他分离程度。

您可以使用 Kustomize 管理您提交到集群的 Kubernetes 清单。Kustomize 可让您组合和自定义 YAML 文件,以实现多种目的。

  1. tenant1 创建命名空间、服务账号和网络政策资源:

    kubectl apply -k manifests/setup/tenant1
    

    输出如下所示:

    namespace/tenant1-ns created
    serviceaccount/tenant1-ksa created
    networkpolicy.networking.k8s.io/tenant1-deny-from-other-namespaces created
    
  2. tenant2 创建集群资源:

    kubectl apply -k manifests/setup/tenant2
    

验证节点自动预配的行为

GKE 集群由一个或多个节点池组成。节点池中的所有节点具有相同的机器类型,这意味着它们拥有相同数量的 CPU 和内存。如果您的工作负载资源需求是可变的,则您可能会受益于拥有多个在集群中具有不同机器类型的节点池。通过这种方式,集群自动扩缩器可以添加最合适的类型的节点,从而提高资源效率并因此降低费用。但是,维护多个节点池会增加管理开销。如果要在专用节点池中执行租户工作负载,则在多租户集群中可能也不可行。

您可以改用节点自动预配来扩展集群自动扩缩器。启用节点自动预配功能后,集群自动扩缩器可以根据待处理 Pod 的规范自动创建新节点池。因此,集群自动扩缩器可以创建最合适的类型节点,但您不必自行创建或管理节点池。使用节点自动预配功能,您的集群可以在不超额预配的情况下高效自动扩缩,这有助于降低费用。

此外,如果待处理 Pod 具有工作负载分离限制,节点自动预配功能可以创建满足限制条件的节点。通过这种方式,您可以使用节点自动预配功能自动创建仅由单个租户使用的节点池。

在本部分中,您将向集群提交各种作业以验证节点自动预配的行为。这些作业使用您之前创建的 generate-pi 映像。

提交简单作业

首先,向集群提交一个简单的作业。作业未指定任何特定于租户的限制。集群中有足够的空闲容量来处理作业的 CPU 和内存请求。因此,您希望将作业安排到默认节点池中的现有节点之一。未预配其他节点。

  1. 列出集群中的节点池:

    gcloud container node-pools list
    

    您会看到一个默认池。

  2. 将作业的配置打印到控制台:

    kubectl kustomize manifests/jobs/simple-job/
    

    输出如下所示:

    apiVersion: batch/v1
    kind: Job
    metadata:
    name: pi-job
    spec:
    ...
    

    该配置未指定任何节点污点或选择器。

  3. 提交作业:

    kubectl apply -k manifests/jobs/simple-job/
    
  4. 监视集群中的节点池:

    watch -n 5 gcloud container node-pools list
    

    您仍会看到一个默认池。未创建新节点池。

  5. 大约 30 秒后,按 Control+C 停止监控节点池。

  6. 监视集群中的节点:

    kubectl get nodes -w
    

    您不会看到正在创建任何新节点。

  7. 观看 1 分钟后,按 Control+C 停止观看。

  8. 列出集群中的作业:

    kubectl get jobs --all-namespaces
    

    输出如下所示:

    NAMESPACE   NAME     COMPLETIONS   DURATION   AGE
    default     pi-job   1/1           14s        21m
    

    Completions 列中的 1/1 值表示总共 1 个作业中有 1 个作业已经完成。

提交具有租户专用限制条件的作业

在本部分中,您将提交另一个作业,以确认节点自动预配功能是否遵循工作负载分离限制。作业配置包括特定于租户的节点选择器和特定于租户的容忍设置。作业只能安排在具有与选择器的键值对匹配的标签的节点上。容忍设置与节点污点结合使用,这还可以限制可以将哪些作业安排到节点上。节点自动预配的最佳做法是包含节点选择器和工作负载分离容忍设置。

此作业无法安排到默认节点池,因为该池没有满足选择器限制条件的任何节点。因此,节点自动预配功能会创建具有满足选择器要求的节点标签的新节点池。节点自动预配功能还会向与作业配置中的容忍设置匹配的节点添加特定于租户的污点。只有具有相匹配的容忍设置的 Pod 可以被安排到池中的节点上,这样您就可以进一步拆分租户工作负载。

  1. 列出集群中的节点池:

    gcloud container node-pools list
    

    您会看到一个默认池。

  2. 将作业的配置打印到控制台:

    kubectl kustomize manifests/jobs/one-tenant/
    

    该配置包括特定于租户的节点选择器要求和容忍设置。输出如下所示:

    apiVersion: batch/v1
    kind: Job
    metadata:
    name: tenant1-pi-job
    spec:
    ...
    
  3. 提交作业:

    kubectl apply -k manifests/jobs/one-tenant/
    
  4. 监视集群中的节点池:

    watch -n 5 gcloud container node-pools list
    

    一段时间后,您将看到新的节点池。输出如下所示:

    NAME                            MACHINE_TYPE       DISK_SIZE_GB
    default-pool                    n1-standard-2      100
    nap-n1-standard-1-15jwludl      n1-standard-1      100
    

    节点池名称带有 nap- 前缀,这表明它是由节点自动预配功能创建的。节点池名称还包括池中节点的机器类型,例如 n1-standard-1

  5. 监视集群中的节点:

    kubectl get nodes -w
    

    大约一分钟后,您会在列表中看到一个新节点。节点名称包含 nap- 节点池的名称。新节点的初始状态为 Not Ready。一段时间后,新节点的状态会更改为 Ready,这表示节点现在可接受待处理工作。

  6. 如需停止监控节点,请按 Control+C

  7. 列出节点污点:

    kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
    

    您会看到,新节点有一个键值对 tenant: tenant1NoSchedule 污点。因此,只有具有与 tenant: tenant1 对应的容忍设置的 Pod 可以安排到节点上。

  8. 监控集群中的作业:

    kubectl get jobs -w --all-namespaces
    

    一段时间后,您会看到 tenant1-pi-job 已完成 1/1,这表示它成功完成。

  9. 如需停止监控作业,请按 Control+C

  10. 监视集群中的节点池:

    watch -n 5 gcloud container node-pools list
    

    一段时间后,您会看到 nap- 池已被删除,而且集群再次只有一个默认节点池。节点自动预配功能已删除 nap- 节点池,因为没有更多与池的限制条件匹配的待处理工作。

  11. 要停止监控节点池,请按 Control+C

提交两个具有租户限制的较大作业

在本部分中,您将提交两个具有租户专用限制的作业,同时还会增加每个作业的资源请求。同样,由于节点选择器的限制条件,这些作业不能安排到默认节点池中。由于每个作业都有自己的选择器限制条件,因此节点自动预配功能会创建两个新的节点池。通过这种方式,您可以使用节点自动预配功能将租户作业分开。由于作业与前一个作业相比具有更多的资源请求,因此节点自动预配功能创建的节点池会具有比上一次更大的机器类型。

  1. 列出集群中的节点池:

    gcloud container node-pools list
    

    您会看到一个默认池。

  2. 打印合并的配置:

    kubectl kustomize manifests/jobs/two-tenants/
    

    配置包括两个单独的作业,每个作业都有一个特定于租户的节点选择器和容忍设置,以及增加的资源请求。

    输出如下所示:

    apiVersion: batch/v1
    kind: Job
    metadata:
    name: tenant1-larger-pi-job
    spec:
    ...
    
  3. 提交作业:

    kubectl apply -k manifests/jobs/two-tenants/
    
  4. 监视集群中的节点池:

    watch -n 5 gcloud container node-pools list
    

    一段时间后,您会看到另外两个节点池。输出如下所示:

    NAME                            MACHINE_TYPE       DISK_SIZE_GB
    default-pool                    n1-standard-2      100
    nap-n1-standard-2-6jxjqobt      n1-standard-2      100
    nap-n1-standard-2-z3s06luj      n1-standard-2      100
    

    节点池名称带有 nap- 前缀,这表明它们是由节点自动预配功能创建的。节点池名称还包括池中节点的机器类型,例如 n1-standard-2

  5. 如需停止监控节点,请按 Control+C

  6. 监视集群中的节点:

    kubectl get nodes -w
    

    大约一分钟后,您会在列表中看到两个新节点。节点名称包含其关联的 nap- 节点池的名称。新节点最初处于 Not Ready 状态。一段时间后,新节点的状态会更改为 Ready,这表示节点现在可接受待处理工作。

  7. 如需停止监控节点,请按 Control+C

  8. 列出节点污点:

    kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
    

    您会看到新节点有 NoSchedule 污点,一个有键值对 tenant: tenant1,另一个有 tenant: tenant2。只有具有相应租户容忍设置的 Pod 可以被安排到节点上。

  9. 监控集群中的作业:

    kubectl get jobs -w --all-namespaces
    

    一段时间后,您发现 tenant1-larger-pi-jobtenant2-larger-pi-job 发生了变化,以分别完成 1/1,这表示作业已成功完成。

  10. 如需停止监控作业,请按 Control+C

  11. 监视集群中的节点池:

    watch -n 5 gcloud container node-pools list
    

    一段时间后,您会看到两个 nap- 池均已被删除,而且集群再次只有一个默认节点池。节点自动预配功能已删除 nap- 节点池,因为没有更多与池限制条件匹配的待处理工作。

  12. 要停止监控节点池,请按 Control+C

控制对 Google Cloud 资源的访问权限

除了维护集群内租户的分离之外,您通常还希望控制租户对 Google Cloud 资源(例如 Cloud Storage 存储分区或 Pub/Sub 主题)的访问权限。例如,每个租户可能需要一个不应被其他租户访问的 Cloud Storage 存储分区。

使用 Workload Identity,您可以在 Kubernetes 服务账号与 Google Cloud 服务账号之间创建映射。然后,您可以为 Google Cloud 服务账号分配适当的 Identity and Access Management (IAM) 角色。通过这种方式,您可以强制执行最小特权原则,以便租户作业可以访问其分配的资源,但不能访问其他租户拥有的资源。

设置 GKE Workload Identity

配置 Kubernetes 服务账号与您创建的 Google Cloud 服务账号之间的映射。

  1. tenant1 创建 Google Cloud 服务账号:

    gcloud iam service-accounts create tenant1-gsa
    
  2. 向 Kubernetes 服务账号授予 tenant1 IAM 权限,以便使用 tenant1 的相应 Google Cloud 服务账号:

    gcloud iam service-accounts add-iam-policy-binding \
        tenant1-gsa@${PROJECT_ID}.iam.gserviceaccount.com \
        --role roles/iam.workloadIdentityUser \
        --member "serviceAccount:${PROJECT_ID}.svc.id.goog[tenant1-ns/tenant1-ksa]"
    
  3. 通过 Google Cloud 服务账号注释 Kubernetes 服务账号,完成服务账号之间的映射:

    kubectl annotate serviceaccount tenant1-ksa -n tenant1-ns \
        iam.gke.io/gcp-service-account=tenant1-gsa@${PROJECT_ID}.iam.gserviceaccount.com
    

提交写入 Cloud Storage 存储分区的作业

在本部分中,您将确认作为特定 Kubernetes 服务账号执行的作业可以使用其映射的 Google Cloud 服务账号的 IAM 权限。

  1. tenant1 创建一个新的 Cloud Storage 存储分区:

    export BUCKET=tenant1-$PROJECT_ID
    gsutil mb -b on -l us-central1 gs://$BUCKET
    

    您可以使用项目 ID 作为存储分区名称的后缀,以使名称具有惟一性。

  2. 更新作业的配置文件以使用 Cloud Storage 存储分区:

    sed -i "s/MY_BUCKET/$BUCKET/" \
        manifests/jobs/write-gcs/bucket-write.yaml
    
  3. tenant1 服务账号授予读写存储分区中的对象的权限:

    gsutil iam ch \
        serviceAccount:tenant1-gsa@$PROJECT_ID.iam.gserviceaccount.com:objectAdmin \
        gs://$BUCKET
    
  4. 打印作业配置:

    kubectl kustomize manifests/jobs/write-gcs/
    

    输出如下所示:

    apiVersion: batch/v1
    kind: Job
    metadata:
    name: tenant1-pi-job-gcs
    spec:
    ...
    

    新存储分区名称将作为参数传递给 generate-pi 容器,并且作业指定相应的 tenant1-ksa Kubernetes 服务账号。

  5. 提交作业:

    kubectl apply -k manifests/jobs/write-gcs/
    

    如上一部分中所述,节点自动预配功能会创建新节点池和新节点来执行作业。

  6. 监控作业的 Pod:

    kubectl get pods -n tenant1-ns -w
    

    在这种情况下,您要监控 Pod 而不是监控节点池。您会看到 Pod 在不同状态下的转换几分钟后,状态会更改为 Completed。此状态表示作业已成功完成。

  7. 要停止观察,请按 Control+C

  8. 确认文件已被写入 Cloud Storage 存储分区:

    gsutil ls -l gs://$BUCKET
    

    您看到一个文件。

  9. 要进行清理,删除作业:

    kubectl delete job tenant1-pi-job-gcs -n tenant1-ns
    

    您将在下一部分中重新提交此作业。

撤消 IAM 权限

最后,您将确认撤消 Google Cloud 服务账号的 IAM 权限,阻止映射的 Kubernetes 服务账号访问 Cloud Storage 存储分区。

  1. 撤消 Google Cloud 服务账号对 Cloud Storage 存储分区的写入权限:

    gsutil iam ch -d \
        serviceAccount:tenant1-gsa@$PROJECT_ID.iam.gserviceaccount.com:objectAdmin \
        gs://$BUCKET
    
  2. 提交与上一步相同的作业:

    kubectl apply -k manifests/jobs/write-gcs/
    
  3. 再次查看该作业的 Pod 状态:

    kubectl get pods -n tenant1-ns -w
    

    几分钟后,状态会更改为 Error,这表示作业失败。此错误是正常的,因为此作业将作为 Kubernetes 服务账号执行,而该服务账号会映射到 Google Cloud 服务账号,而该账号反过来不再拥有 Cloud Storage 存储分区的写入权限。

  4. 如需停止监控 Pod,请按 Control+C

  5. 列出存储分区中的文件:

    gsutil ls -l gs://$BUCKET
    

    您会在存储分区中看到一个文件;新文件尚未写入。

清理

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

删除项目

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

    转到“管理资源”

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

删除 GKE 集群

如果您不想删除项目,请删除 GKE 集群:

gcloud container clusters delete multitenant

后续步骤