排查 GKE Standard 节点池问题


本页面介绍如何解决 GKE Standard 模式节点池的问题。

如果您需要其他帮助,请与 Cloud Customer Care 联系。

节点池创建问题

本部分列出了在 Standard 集群中创建新节点池时可能出现的问题,并提供了有关如何解决这些问题的建议。

问题:节点池创建失败,原因是资源不足

当您在某个 Google Cloud 可用区中创建使用特定硬件的节点池,并且该可用区没有足够的硬件来满足您的要求时,会发生以下问题。

如需验证节点池创建是否由于某个可用区没有足够的资源而失败,请查看日志中是否有相关错误消息。

  1. 在 Google Cloud 控制台中打开 Logs Explorer。

    转到日志浏览器

  2. 查询字段中,指定以下查询:

    log_id(cloudaudit.googleapis.com/activity)
    resource.labels.cluster_name="CLUSTER_NAME"
    protoPayload.status.message:("ZONE_RESOURCE_POOL_EXHAUSTED" OR "does not have enough resources available to fulfill the request" OR "resource pool exhausted" OR "does not exist in zone")
    

    CLUSTER_NAME 替换为 GKE 集群的名称。

  3. 点击运行查询

您可能会看到以下某条错误消息:

  • resource pool exhausted
  • The zone does not have enough resources available to fulfill the request. Try a different zone, or try again later.
  • ZONE_RESOURCE_POOL_EXHAUSTED
  • ZONE_RESOURCE_POOL_EXHAUSTED_WITH_DETAILS
  • Machine type with name 'MACHINE_NAME' does not exist in zone 'ZONE_NAME'

如需解决此问题,请尝试以下建议:

  • 确保所选 Google Cloud 区域或可用区具有您需要的特定硬件。使用 Compute Engine 可用性表检查特定可用区是否支持特定硬件。为节点选择满足您需要的硬件可用性的其他 Google Cloud 区域或可用区。
  • 创建具有较小机器类型的节点池。增加节点池中的节点数,以使总计算容量保持不变。
  • 使用 Compute Engine 容量预留提前预留资源。
  • 使用下文介绍的尽力而为的预配功能,只要 GKE 能够至少预配指定的最小节点数,即使未满足请求的数量,也能成功创建节点池。

尽力而为的预配

对于某些硬件,您可以使用“尽力而为的预配”,该功能指示 GKE 在至少能够预配指定的最小节点数的情况下即可成功创建节点池。在接下来的一段时间内,GKE 会继续尝试预配其余节点以满足原始请求。如需指示 GKE 使用尽力而为的预配,请使用以下命令:

gcloud container node-pools create NODE_POOL_NAME \
    --cluster=CLUSTER_NAME \
    --node-locations=ZONE1,ZONE2,... \
    --machine-type=MACHINE_TYPE
    --best-effort-provision \
    --min-provision-nodes=MINIMUM_NODES

请替换以下内容:

  • NODE_POOL_NAME:新节点池的名称。
  • ZONE1,ZONE2,...:节点的 Compute Engine 可用区。这些可用区必须支持所选的硬件。
  • MACHINE_TYPE:节点的 Compute Engine 机器类型。例如 a2-highgpu-1g
  • MINIMUM_NODES:GKE 预配的最小节点数,只要满足该数量即可成功创建节点池。如果省略,则默认为 1

例如,设想这样一个场景,您需要在 us-central1-c 中有 10 个挂接 NVIDIA A100 40GB GPU 的节点。根据 GPU 区域和可用区可用性表,此可用区支持 A100 GPU。为了避免在没有 10 个 GPU 机器可用时节点池创建失败,您可以使用尽力而为的预配。

gcloud container node-pools create a100-nodes \
    --cluster=ml-cluster \
    --node-locations=us-central1-c \
    --num-nodes=10 \
    --machine-type=a2-highgpu-1g \
    --accelerator=type=nvidia-tesla-a100,count=1 \
    --best-effort-provision \
    --min-provision-nodes=5

即使 us-central1-c 中只有 5 个 GPU 可用,GKE 也会创建节点池。在随后的一段时间内,GKE 会尝试预配更多节点,直到节点池中有 10 个节点为止。

错误:实例不包含“instance-template”元数据

如果节点池升级、扩缩或执行自动节点修复失败,您可能会看到以下错误:

Instance INSTANCE_NAME does not contain 'instance-template' metadata

此错误表示由 GKE 分配的虚拟机实例的元数据已损坏。这种情况通常发生在自定义编写的自动化脚本或脚本尝试添加新的实例元数据(例如 block-project-ssh-keys)时,不仅会添加或更新值,还会删除现有元数据。如需了解虚拟机实例元数据,请参阅设置自定义元数据

如果删除任何关键元数据值(以及其他值:instance-templatekube-labelskubelet-configkubeconfigcluster-nameconfigure-shcluster-uid),则节点或整个节点池可能会自行呈现不稳定状态,因为这些值对于 GKE 操作至关重要。

如果实例元数据已损坏,我们建议您通过重新创建包含损坏的虚拟机实例的节点池来恢复元数据。您需要向集群添加节点池,并增加新节点池的节点数,同时隔离并移除另一个节点池中的节点。请参阅在节点池之间迁移工作负载的说明。

如需了解谁在何时修改了实例元数据,您可以查看 Compute Engine 审核日志记录信息,也可以使用日志浏览器并输入类似于以下内容的搜索查询来查找日志:

resource.type="gce_instance_group_manager"
protoPayload.methodName="v1.compute.instanceGroupManagers.setInstanceTemplate"

您可以在日志中找到请求发起者 IP 地址和用户代理。例如:

requestMetadata: {
  callerIp: "REDACTED"
  callerSuppliedUserAgent: "google-api-go-client/0.5 GoogleContainerEngine/v1"
}

在节点池之间迁移工作负载

请按照以下说明将工作负载从一个节点池迁移到另一个节点池。如果要更改节点池中节点的机器属性,请参阅通过更改节点机器属性进行纵向扩缩

了解如何将 Pod 迁移到新节点池

如需将 Pod 迁移到新节点池,您必须执行以下操作:

  1. 封锁现有节点池中的节点:此操作会将现有节点池中的节点标记为不可调度。将它们标记为无法安排后,Kubernetes 会停止将新 Pod 安排到这些节点。

  2. 排空现有节点池中的节点:此操作会正常逐出正在现有节点池的节点上运行的工作负载。

这些步骤会导致在现有节点池中运行的 Pod 正常终止,您需要针对每个节点单独执行这些步骤。Kubernetes 会将它们重新安排到其他可用的节点上。

为了确保 Kubernetes 正常终止您的应用,容器应该处理 SIGTERM 信号。使用此方法可以关闭与客户端的活跃连接,并干净地提交或回滚数据库事务。在 Pod 清单中,您可以使用 spec.terminationGracePeriodSeconds 字段指定 Kubernetes 必须等待多长时间才能停止 Pod 中的容器。默认值为 30 秒。 您可以在 Kubernetes 文档中详细了解终止 Pod

您可以使用 kubectl cordonkubectl drain 命令封锁和排空节点。

创建节点池并迁移工作负载

如需将工作负载迁移到新节点池,请创建新的节点池,然后封锁并排空现有节点池中的节点:

  1. 向集群添加节点池

    通过运行以下命令来验证新节点池已创建:

    gcloud container node-pools list --cluster CLUSTER_NAME
    
  2. 运行以下命令以查看 Pod 正在哪个节点上运行(请参阅 NODE 列):

    kubectl get pods -o=wide
    
  3. 获取现有节点池中的节点列表,并将 EXISTING_NODE_POOL_NAME 替换为名称:

    kubectl get nodes -l cloud.google.com/gke-nodepool=EXISTING_NODE_POOL_NAME
    
  4. 运行 kubectl cordon NODE 命令(将 NODE 替换为上一个命令中的名称)。以下 shell 命令将遍历现有节点池中的每个节点,并将它们标记为无法安排:

    for node in $(kubectl get nodes -l cloud.google.com/gke-nodepool=EXISTING_NODE_POOL_NAME -o=name); do
      kubectl cordon "$node";
    done
    
  5. (可选)更新现有节点池上运行的工作负载,以为标签 cloud.google.com/gke-nodepool:NEW_NODE_POOL_NAME 添加 nodeSelector,其中 NEW_NODE_POOL_NAME 是新节点池的名称。这可以确保 GKE 将这些工作负载放置在新节点池中的节点上。

  6. 通过在分配的正常终止时间段(10 秒)内逐出 Pod,排空每个节点:

    for node in $(kubectl get nodes -l cloud.google.com/gke-nodepool=EXISTING_NODE_POOL_NAME -o=name); do
      kubectl drain --force --ignore-daemonsets --delete-emptydir-data --grace-period=GRACEFUL_TERMINATION_SECONDS  "$node";
    done
    

    GRACEFUL_TERMINATION_PERIOD_SECONDS 替换为正常终止所需的时间。

  7. 运行以下命令,查看现有节点池中的节点在节点列表中是否具有 SchedulingDisabled 状态:

    kubectl get nodes
    

    此外,您应该会看到 Pod 现正在新节点池中的节点上运行:

    kubectl get pods -o=wide
    
  8. 如果您不再需要现有节点池,请将其删除:

    gcloud container node-pools delete default-pool --cluster CLUSTER_NAME
    

后续步骤

如果您需要其他帮助,请与 Cloud Customer Care 联系。