EKS Managed Node Group: 解决 PodEvictionFailure 错误

EKS Managed Node Group: 解决 PodEvictionFailure 错误

自上篇提及 2019 年 EKS 发布了新的 API 支持 Managed Node Groups (托管式工作节点) 1 和简介 Ec2SubnetInvalidConfiguration 的错误排查后,本篇内容将进一步探讨在执行 Managed Node Group 升级过程遭遇的 PodEvictionFailure 错误。

PodEvictionFailure 错误是在 EKS 进行节点组升级时会遇到的常见错误之一,通常是使用 AWS Console 界面、aws eks update-nodegroup-version 命令或是 UpdateNodegroupVersion API 调用时产生。本文将进一步分析这个错误的原因、常见情境以及解决方法。

如何确认该问题

要检查 EKS Managed Node Groups 是否存在 PodEvictionFailure 错误,可以通过 EKS Console 或是 AWS CLI 命令确认是否存在任何错误。例如,通过点击 Cluster 底下的 Compute 页签 > 点击 Node groups 进入到节点组的详细页面后,可以检查 Update history 页签相关的升级事件是否存在相关的错误信息:

再进一步点击个别的升级事件,便可以进一步看到详细信息和错误:

根据文件提到 EKS Managed Node Group 的升级流程 2,如果升级或是新建节点超过 15-20 分钟后卡住,很可能是某些原因使得工作节点在运作时存在一些问题,过一段时间后,通常有机会通过这些信息进一步排查可能的原因。以下是使用 AWS CLI 命令的范例:

$ aws eks list-updates --name <CLUSTER_NAME> --nodegroup-name <NODE_GROUP_NAME>
{
    "updateIds": [
        "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEEE"
    ]
}

$ aws eks describe-update --name <CLUSTER_NAME> --nodegroup-name <NODE_GROUP_NAME> --update-id AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEEE
{
    "update": {
        "id": "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEEE",
        "status": "Failed",
        "type": "VersionUpdate",
        "params": [
            {
                "type": "Version",
                "value": "1.26"
            },
            {
                "type": "ReleaseVersion",
                "value": "1.26.4-20230526"
            }
        ],
        "createdAt": "2023-06-03T17:19:38.068000+00:00",
        "errors": [
            {
                "errorCode": "PodEvictionFailure",
                "errorMessage": "Reached max retries while trying to evict pods from nodes in node group <NODE_GROUP_NAME>",
                "resourceIds": [
                    "ip-192-168-2-44.eu-west-1.compute.internal"
                ]
            }
        ]
    }
}

上述范例为在升级时尝试停用 Pod 超出重试的次数导致失败,并且该次升级为 Failed 状态。

常见情境和发生原因

Pod Disruption Budget (PDB)

Pod Disruption Budget (PDB) 是 Kubernetes 内的一个资源对象,用于确保在进行维护、升级、回滚等操作时,对于特定部署的可用性不会被破坏 3

PDB 通常用在设定 Pod 的最小 (可用) 运行数量,当进行维护操作时,Kubernetes 会确保在目前集群中 Pod 的运行数量不会低于这个数字。这样可以确保在进行维护等操作时,服务的可用性不会受到影响或是中断 (Pod)。

以下是一个 PDB 的范例:

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: my-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: my-app

在这个范例中,我们设定了一个 PDB,最小可用数量为 2 个,选择器为 app=my-app。这表示在进行维护等操作时,Kubernetes 会确保至少有 2 个符合选择器的 Pod 可用。

PDB 的设定可以帮助您在进行维护等操作时确保 Pod 的可用性,同时也可以减少可能出现的 PodEvictionFailure 错误。在设计应用程序时,建议您考虑使用 PDB 来确保应用程序的可用性。

PodEvictionFailure 和 PDB 之间的关系

PodEvictionFailure 错误通常是由于 EKS 无法在要更新的工作节点上节点上停用 Pod 引起。在进行 Managed Node Group 升级替换操作时,如果目前要被更新的节点上运行的目标 Pod 的运行数量低于 PDB 中设定的最小值,那么 Kubernetes 就会拒绝删除 Pod,在这种情况下,就可能会触发升级过程产生的 PodEvictionFailure 错误。例如,以下的环境中部署了一个 Kubernetes Deployment (nginx-deploymnet),并且在目前环境中的唯一节点 ip-192-168-2-44.eu-west-1.compute.internal 上运行:

NAMESPACE     NAME                                   READY   STATUS    RESTARTS      AGE   IP               NODE                                           NOMINATED NODE   READINESS GATES
default       pod/nginx-deployment-ff6774dc6-dntfm   1/1     Running   0             45m   192.168.7.16     ip-192-168-2-44.eu-west-1.compute.internal

NAMESPACE     NAME                               READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS
default       deployment.apps/nginx-deployment   1/1     1            1           49m   nginx

同时,该环境中设定了以下 PodDisruptionBudget 资源 (这个 nginx-deployment 所有 Pod 存在 app=nginx 标签):

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: nginx-pdb
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: nginx

通常在执行更新或是替换节点过程时,我们通常会需要将节点标记为不可调度以及停用在上面运行的应用,在 Kubernetes 中,提供了 kubectl drain 这类方法支持这个操作 4。一旦执行 kubectl drain 将节点设定为维护状态时,它会触发 Kubernetes Scheduler 将节点上的 Pod 进行重新调度。

此外,在重新调度之前,kubectl drain 命令会将节点标记为不可调度,这样新的 Pod 就不会被调度到该节点上。同时,已在节点上运行的 Pod 会被逐一停用。但在违反 PodDisruptionBudget 情况下,这样的操作很可能会失败,例如,以下就是一个在存在 PDB 时无法正确停用的范例:

$ kubectl drain ip-192-168-2-44.eu-west-1.compute.internal --ignore-daemonsets --delete-emptydir-data
node/ip-192-168-2-44.eu-west-1.compute.internal already cordoned
WARNING: ignoring DaemonSet-managed Pods: kube-system/aws-node-n9lc5, kube-system/kube-proxy-lwxcb
evicting pod kube-system/coredns-6866f5c8b4-r96z8
evicting pod default/nginx-deployment-ff6774dc6-dntfm
evicting pod kube-system/coredns-6866f5c8b4-h296r

pod/coredns-6866f5c8b4-r96z8 evicted
pod/coredns-6866f5c8b4-h296r evicted

evicting pod default/nginx-deployment-ff6774dc6-dntfm
error when evicting pods/"nginx-deployment-ff6774dc6-dntfm" -n "default" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.

evicting pod default/nginx-deployment-ff6774dc6-dntfm
error when evicting pods/"nginx-deployment-ff6774dc6-dntfm" -n "default" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.

evicting pod default/nginx-deployment-ff6774dc6-dntfm
error when evicting pods/"nginx-deployment-ff6774dc6-dntfm" -n "default" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
...

Deployment 允许 (容忍) 在存在污点 (Taint) 的节点上部署

在 Kubernetes 中,Taints(污点)和 Tolerations(容忍)是用于控制 Pod 能否被调度到特定节点的机制。Taints 是对节点的标记,表示节点具有某些特定的限制或要求。而 Tolerations 则是由 Pod 定义,用于告诉 Kubernetes 这个 Pod 可以容忍哪些节点的 Taints,进而允许或阻止 Pod 被调度到符合条件的节点上。5

然而,不正确的 tolerations 设定很可能使得在预期进行替换的节点中,仍持续调度应用程序到节点上,例如,以下范例直接忽略了在 Node 关联的 Taint 设定:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  ...
  template:
    ...
    spec:
      containers:
      - name: nginx
        image: nginx
      tolerations:
        - operator: "Exists"
    ...

如同前面提到,在重新调度之前,kubectl drain 命令会将节点标记为不可调度,这样新的 Pod 就不会被调度到该节点上。执行这项操作时,Kubernetes 也会替节点设定 node.kubernetes.io/unschedulable:NoSchedule 的污点:

$ kubectl get nodes
NAME                                         STATUS                     ROLES    AGE    VERSION
ip-192-168-2-44.eu-west-1.compute.internal   Ready,SchedulingDisabled   <none>   126m   v1.25.9-eks-0a21954

$ kubectl describe node
Name:               ip-192-168-2-44.eu-west-1.compute.internal
...
Taints:             node.kubernetes.io/unschedulable:NoSchedule

但在使用上述 tolerations 设定时,对应应用程序的调度行为会直接忽略这项设定,例如,以下就是在 Node 执行 kubectl drain 仍持续执行 Pod 调度的过程:

NAME                                READY   STATUS    RESTARTS   AGE   IP              NODE
nginx-deployment-6876484bcc-h28sn   1/1     Running   0          25s   192.168.9.175   ip-192-168-2-44.eu-west-1.compute.internal

nginx-deployment-6876484bcc-h28sn   1/1     Terminating   0          38s   192.168.9.175   ip-192-168-2-44.eu-west-1.compute.internal

nginx-deployment-6876484bcc-kbgw6   0/1     Pending       0          0s    <none>          ip-192-168-2-44.eu-west-1.compute.internal

nginx-deployment-6876484bcc-kbgw6   0/1     ContainerCreating   0          0s    <none>          ip-192-168-2-44.eu-west-1.compute.internal

nginx-deployment-6876484bcc-kbgw6   1/1     Running             0          2s    192.168.18.140   ip-192-168-2-44.eu-west-1.compute.internal

对于 EKS Managed Node Group 升级操作的行为来说,该节点并未完全停用 Pod 且属于未清空的状态,使得更新过程发生 PodEvictionFailure 错误。

Pod 本身存在错误

如果并非上述问题导致,则可能指出 Pod 基于某些原因无法正常关闭,例如:应用程序与 NFS 互动但执行操作卡在 I/O 行为、网络问题或资源不足 (像是 CPU 负载过高使得系统无法反应) 等原因导致。

要先确定出错 Pod 的状态和原因,您可以使用以下命令来查看详细的 Pod 状态或是相关的记录日志来确定问题的原因:

$ kubectl describe pod
$ kubectl logs <POD_NAME>

解决方法和相关步骤

Pod Disruption Budget (PDB)

要确认是否因为 Pod Disruption Budget (PDB) 影响升级,可以通过以下命令确认目前有的 PDB 设定:

kubectl get pdb --all-namespaces

如果有开启 EKS Control Plane Logging (审计记录),也可以通过 CloudWatch Log Insight 功能筛选并检查是否存在任何相关的失败事件,

  1. Cluster > Logging (记录) 标签下,选择 Audit (审计) 可以直接开启 Amazon CloudWatch Console。
  2. 在 Amazon CloudWatch 控制台中,选择 Logs (日志)。然后,选择 Log Insights (日志洞察) 对 EKS 产生的审计记录文件进行筛选

以下为相关查询范例:

fields @timestamp, @message
| filter @logStream like "kube-apiserver-audit"
| filter ispresent(requestURI)
| filter objectRef.subresource = "eviction"
| display @logStream, requestURI, responseObject.message
| stats count(*) as retry by requestURI, responseObject.message

通过上述检视审计记录,可以进一步确认在停用 Pod 事件中是否存在任何因为 Pod Disruption Budget 影响的具体信息:

如果是因为 PDB 导致升级失败,可以修改或是移除 PDB 后,再次尝试执行升级:

# Edit
$ kubectl edit pdb <PDB_NAME>

# Delete
$ kubectl delete pdb <PDB_NAME>

错误的 Tolerations 设定

前面提到不正确的 tolerations 设定,很可能使得在预期进行替换的节点中,仍持续调度应用程序到节点上。可以通过修正对应部署的设定解决这项问题:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  ...
  template:
    ...
    spec:
      containers:
      - name: nginx
        image: nginx
      tolerations: <---- 修正设定
        - operator: "Exists"
    ...

采用强制更新 (Force update)

默认情况下,EKS Managed Node Group 的升级会采用 Rolling update (滚动更新) 方法,这个选项在升级过程会遵守 Pod Disruption Budget (PDB) 设定。若因为 PDB 导致无法正确停用,升级过程将会失败。

如果因为前述 PDB 或其他原因无法正确升级,也可以通过在升级过程选择 Force update (强制更新) 进行升级,这个选项会在升级过程忽略 PDB 设定。无论 PDB 问题是否出现,都会强制节点重新启动以进行更新。

以下为使用 AWS CLI 的范例:

$ aws eks update-nodegroup-version --cluster-name <CLUSTER_NAME> --nodegroup-name <NODE_GROUP_NAME> --force

以下为使用 eksctl 的范例:

$ eksctl upgrade nodegroup --cluster <CLUSTER_NAME> --name <NODE_GROUP_NAME> --force-upgrade

总结

在这篇内容中,我们提到了升级 EKS Managed Node Group 时遇到 PodEvictionFailure 错误的常见情境和发生原因,并且提出相关的解决方法,例如:

  • 检查 PodDisruptionBudget (PDB) 设定是否有误,如果是 PDB 导致升级失败,可以修改或是移除 PDB 后再次执行升级。
  • 检查 Tolerations 设定是否有误。错误的 Tolerations 设定很可能使得在预期进行替换的节点中,仍持续调度应用程序到节点上,需修正对应部署的设定。

最后,如果以上方法仍无法解决问题,可以采用强制更新 (Force update) 进行升级。此选项会在升级过程中忽略 PDB 设定,并强制节点重新启动以进行更新6 7

通过遵循上述的解决方法和相关步骤,希望能帮助在阅读这篇内容的你更有方向的排查并且解决这个错误。

参考资源

Eason Cao
Eason Cao Eason is an engineer working at FANNG and living in Europe. He was accredited as AWS Professional Solution Architect, AWS Professional DevOps Engineer and CNCF Certified Kubernetes Administrator. He started his Kubernetes journey in 2017 and enjoys solving real-world business problems.
comments powered by Disqus