问题排查

针对使用 Google Kubernetes Engine (GKE) 时您可能遇到的问题,我们提供了以下问题排查方法。

调试 Kubernetes 资源

如果您遇到与集群相关的问题,请参阅 Kubernetes 文档中的对集群进行问题排查

如果您的应用、其 Pod 或其控制器对象出现问题,请参阅对应用进行问题排查

找不到 kubectl 命令

  1. 通过运行以下命令,安装 kubectl 二进制文件:

    sudo gcloud components update kubectl
    
  2. 当安装程序弹出提示您修改 $PATH 环境变量时,请选择“是”。修改此变量可让您在使用 kubectl 命令时无需键入其完整文件路径。

    或者,将以下行添加到 ~/.bashrc(在 macOS 版中是 ~/.bash_profile,或者 shell 存储环境变量的任何位置):

    export PATH=$PATH:/usr/local/share/google/google-cloud-sdk/bin/
    
  3. 运行以下命令加载更新的 .bashrc(或 .bash_profile)文件:

    source ~/.bashrc
    

kubectl 命令返回“连接被拒绝”错误

使用以下命令设置集群上下文:

gcloud container clusters get-credentials cluster-name

如果您不确定要为 cluster-name 输入什么内容,请使用以下命令列出您的集群:

gcloud container clusters list

kubectl 命令返回“无法协商 api 版本”错误

确保 kubectl 已有身份验证凭据:

gcloud auth application-default login

kubectl logsattachexecport-forward 命令停止响应

集群的控制层面(主实例)能够与集群中的节点通信是运行这些命令的基础。但是,由于该控制层面与集群节点不在同一个 Compute Engine 网络中,因此我们依靠 SSH 隧道来实现安全通信。

GKE 会在您的 Compute Engine 项目元数据中保存一个 SSH 公钥文件。使用 Google 提供的映像的所有 Compute Engine 虚拟机会定期检查其项目的公共元数据及其实例的元数据,以使 SSH 公钥添加到虚拟机的授权用户列表中。GKE 还为您的 Compute Engine 网络添加了防火墙规则,以允许通过 SSH 实现从控制层面的 IP 地址到集群中每个节点的访问。

如果上述任何一个 kubectl 命令无法运行,则可能是 API 服务器无法打开到节点的 SSH 隧道。请从以下方面检查可能的原因:

  1. 集群没有任何节点。

    如果您已将集群中的节点数减少到零,则 SSH 隧道将无法运行。

    要解决此问题,请调整集群大小以至少拥有一个节点。

  2. 集群中的 Pod 已陷入终止状态,并阻止从集群中删除不再存在的节点。

    这个问题应该只影响 Kubernetes 1.1版,但重复调整集群大小也可能引起上述现象。

    如需解决此问题,请删除处于终止状态超过几分钟的 Pod。然后将旧节点从控制层面中移除,并替换为新节点。

  3. 您的网络的防火墙规则不允许通过 SSH 访问控制层面。

    所有 Compute Engine 网络都使用名为 default-allow-ssh 的防火墙规则创建,该规则允许从所有 IP 地址进行 SSH 访问(当然,需要使用有效的私钥)。GKE 还为每个公共集群插入一项 SSH 规则(格式为 gke-cluster-name-random-characters-ssh),该规则允许通过 SSH 实现专门从集群的控制层面到集群的节点的访问。如果这些规则都不存在,则控制层面将无法打开 SSH 隧道。

    如需解决此问题,请重新添加防火墙规则,以允许从控制层面的 IP 地址访问具有所有集群节点上的标记的虚拟机。

  4. 您的项目的“ssh-keys”的公共元数据条目已满。

    如果项目名为 “ssh-keys” 的元数据条目接近大小上限,则 GKE 无法添加自己的 SSH 密钥,因此无法打开 SSH 隧道。您可以通过运行以下命令来查看项目的元数据:

    gcloud compute project-info describe [--project=PROJECT]
    

    然后查看 ssh-keys 列表的长度。

    如需解决此问题,对不再需要的密钥,请删除一些 SSH 密钥

  5. 您已在集群中的虚拟机上使用密钥“ssh-keys”设置元数据字段。

    相比项目范围的 SSH 密钥,虚拟机上的节点代理会优先使用每个实例的 ssh-keys,因此如果您在集群的节点上专门设置了任何 SSH 密钥,则节点将不会遵循项目元数据中控制层面的 SSH 密钥。如需检查上述条目,请运行 gcloud compute instances describe <VM-name> 并在元数据中查找“ssh-keys”字段。

    如需解决此问题,请从实例元数据中删除每个实例的 SSH 密钥

值得注意的是,集群的正确运行不需要这些功能。如果您希望集群的网络拒绝所有外部访问,请注意这些功能将停止工作。

节点版本与控制层面版本不兼容

请检查集群的控制层面正在运行的 Kubernetes 版本,然后检查集群的节点池正在运行的 Kubernetes 版本。如果集群的任一节点池比控制层面高两个次要版本,则可能会导致您的集群出现问题。

GKE 团队会代表您定期升级集群控制层面。控制层面将升级到较新的 Kubernetes 稳定版本。默认情况下,集群的节点已启用自动升级,我们建议您不要将其停用

如果集群的节点已停用自动升级,并且您未将节点池版本手动升级到与控制层面兼容的版本,则您的控制层面最终会与节点不兼容,因为控制层面会随着时间的推移自动升级。集群的控制层面与节点之间不兼容会导致出现意外问题。

Kubernetes 版本和版本倾斜支持政策可保证控制层面与最多比控制层面低两个次要版本的节点兼容。例如,Kubernetes 1.19 控制层面与 Kubernetes 1.19、1.18 和 1.17 节点兼容。要解决此问题,请手动将节点池版本升级到与控制层面兼容的版本。

如果您担心升级过程会导致受影响节点上运行的工作负载出现中断情况,请按照将工作负载迁移到不同机器类型教程的“迁移工作负载”部分中的步骤进行操作。通过这些步骤,您可以通过创建新的节点池,然后封锁并排空旧节点池,从而从容完成迁移。

集群中的指标未显示在 Cloud Monitoring 中

确保您已在项目中激活 Cloud Monitoring APICloud Logging API,并且您可以在 Cloud Monitoring 中查看项目。

如果问题仍然存在,请检查是否有以下现象:

  1. 确保已在集群上启用监控。

    对于从 Google Cloud Console 以及从 gcloud 命令行工具创建的集群,默认启用监控,但您可以运行以下命令或点击 Cloud Console 中的集群详细信息进行验证:

    gcloud container clusters describe cluster-name
    

    此命令的输出应声明:“monitoringService”为“monitoring.googleapis.com”,并且应在 Cloud Console 中启用 Cloud Monitoring。

    如果未启用监控,请运行以下命令启用:

    gcloud container clusters update cluster-name --monitoring-service=monitoring.googleapis.com
    
  2. 自创建集群或启用其监控功能以来已经有多长时间了?

    新集群的指标最多可能需要一个小时才能出现在 Cloud Monitoring 中。

  3. “kube-system”命名空间中的集群是否正在运行一个 heapstergke-metrics-agent(OpenTelemetry 收集器)?

    此 pod 可能无法调度工作负载,因为您的集群没有足够资源。通过调用 kubectl get pods --namespace=kube-system 并检查名称中带有 heapstergke-metrics-agent 的 pod,检查 Heapster 或 OpenTelemetry 是否正在运行。

  4. 集群的控制层面是否能够与节点通信?

    Cloud Monitoring 以此为基础。您可以通过运行以下命令来确认是否存在这种情况。

    kubectl logs pod-name
    

    如果此命令返回错误,则问题可能是 SSH 隧道导致的。如需了解详情,请参阅此部分

如果您遇到与 Cloud Logging 代理相关的问题,请参阅其问题排查文档

如需了解详情,请参阅 Logging 文档

错误 404:调用 gcloud container 命令时“找不到”资源

gcloud 命令行工具重新进行身份验证:

gcloud auth login

错误 400/403:缺少帐号的修改权限

您的 Compute Engine 默认服务帐号与 GKE 相关联的服务帐号已被手动删除或修改。

启用 Compute Engine 或 Kubernetes Engine API 时,将创建一个服务帐号并获得您的项目的修改权限。如果您在任何时候修改了权限、移除了“Kubernetes Engine Service Agent”角色、完全移除了帐号或停用了 API,则集群的创建将失败,且所有管理功能都将失效。

Google Kubernetes Engine 服务帐号的名称如下所示,其中 project-number 是您的项目编号

service-project-number@container-engine-robot.iam.gserviceaccount.com

要解决此问题,如果您已从 Google Kubernetes Engine 服务帐号中移除了 Kubernetes Engine Service Agent 角色,请重新添加。否则,您必须重新启用 Kubernetes Engine API 才能正确恢复您的服务帐号和权限。您可以在 gcloud 工具或 Cloud Console 中执行此操作。

控制台

  1. 访问 Cloud Console 中的 API 和服务

    “API 和服务”页面

  2. 选择您的项目。

  3. 点击启用 API 和服务

  4. 搜索 Kubernetes,然后从搜索结果中选择 API。

  5. 点击启用。如果您之前已启用 API,则必须先停用,然后再次启用。API 和相关服务的启用可能需要几分钟才能完成。

gcloud

gcloud 工具中运行以下命令:

gcloud services enable container.googleapis.com

在 1.9.x 及更高版本上复制 1.8.x(及更早版本)自动防火墙规则

如果您的集群正在运行 Kubernetes 1.9.x 版本,则自动防火墙规则会更改为禁止 GKE 集群中的工作负载与其他 Compute Engine 虚拟机(在集群外但位于同一网络上)通信。

您可以执行以下步骤来复制 1.8.x(及更早版本)集群的自动防火墙规则行为:

  1. 找到集群的网络:

    gcloud container clusters describe cluster-name --format=get"(network)"
    
  2. 获取用于容器的集群的 IPv4 CIDR:

    gcloud container clusters describe cluster-name --format=get"(clusterIpv4Cidr)"
    
  3. 为网络创建防火墙规则,以 CIDR 作为源范围,并允许所有协议:

    gcloud compute firewall-rules create "cluster-name-to-all-vms-on-network" \
      --network="network" --source-ranges="cluster-ipv4-cidr" \
      --allow=tcp,udp,icmp,esp,ah,sctp
    

将默认服务帐号恢复到您的 GCP 项目

GKE 的默认服务帐号 container-engine-robot 可能会意外地从项目中解除绑定。GKE 服务代理是一种 Identity and Access Management (IAM) 角色,它授予服务帐号管理集群资源的权限。如果从服务帐号中移除此角色绑定,则默认服务帐号将从项目中解除绑定,这可能会阻止您部署应用和执行其他集群操作。

您可以使用 gcloud 工具或 Cloud Console 检查服务帐号是否已从项目中移除。

gcloud

运行以下命令:

gcloud projects get-iam-policy project-id

project-id 替换为您的项目 ID。

控制台

访问 Cloud Console 中的 IAM 和管理页面。

如果命令或信息中心未针对服务帐号显示 container-engine-robot,则该服务帐号已解除绑定。

如果移除了 GKE 服务代理角色绑定,请运行以下命令以恢复角色绑定:

PROJECT_ID=$(gcloud config get-value project)
PROJECT_NUMBER=$(gcloud projects describe "${PROJECT_ID}" --format "value(projectNumber)")
gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
  --member "serviceAccount:service-${PROJECT_NUMBER}@container-engine-robot.iam.gserviceaccount.com" \
  --role roles/container.serviceAgent

如需确认已授予角色绑定:

gcloud projects get-iam-policy $PROJECT_ID

如果您看到服务帐号名称和 container.serviceAgent 角色,则已授予角色绑定。例如:

- members:
  - serviceAccount:service-1234567890@container-engine-robot.iam.gserviceaccount.com
  role: roles/container.serviceAgent

Cloud KMS 密钥已停用。

GKE 的默认服务帐号不能将已停用的 Cloud KMS 密钥用于应用级 Secret 加密

如需重新启用已停用的密钥,请参阅启用已停用的密钥版本

启用“节点可分配”后,Pod 陷入待处理状态

如果在启用节点可分配后陷入“待处理”状态的 Pod 遇到问题,请注意以下事项:

从 1.7.6 版开始,GKE 会为 Kubernetes 开销(包括 Docker 和操作系统)保留 CPU 和内存。如需了解 Pod 可以安排的每种机器类型的数量,请参阅集群架构

如果 Pod 在升级后处于待处理状态,我们建议执行以下操作:

  1. 确保 Pod 的 CPU 和内存请求没有超出峰值使用量。如果 GKE 为开销预留了 CPU 和内存,则 Pod 无法请求这些资源。如果 Pod 请求的 CPU 或内存超出其使用量,则会阻止其他 Pod 请求这些资源,而且可能会导致集群利用率过低。如需了解详情,请参阅如何安排具有资源请求的 Pod

  2. 请考虑调整集群大小。如需查看相关说明,请参阅调整集群大小

  3. 通过将集群降级来还原此更改。如需查看相关说明,请参阅手动升级集群或节点池

已创建专用集群节点,但未加入集群

通常,在专用集群所用的 VPC 上使用自定义路由和第三方网络设备时,默认路由 (0.0.0.0/0) 会重定向到设备,而不是默认互联网网关。除了控制层面连接外,您还需要确保可以到达以下目的地:

  • *.googleapis.com
  • *.gcr.io
  • gcr.io

为所有三个网域配置专用 Google 访问通道。利用此最佳做法,新节点可以启动并加入集群,同时使互联网出站流量受到限制。

已部署工作负载的问题排查

如果工作负载的 Pod 存在问题,GKE 将返回错误。 您可以使用 kubectl 命令行工具或 Cloud Console 检查 Pod 的状态。

kubectl

如需查看集群中正在运行的所有 Pod,请运行以下命令:

kubectl get pods

输出:

NAME       READY  STATUS             RESTARTS  AGE
pod-name   0/1    CrashLoopBackOff   23        8d

如需获取有关特定 Pod 的更多详细信息,请运行以下命令:

kubectl describe pod pod-name

pod-name 替换为所需 Pod 的名称。

控制台

请执行以下步骤:

  1. 访问 Cloud Console 中的 GKE Workloads 信息中心。

    访问 GKE Workloads 信息中心

  2. 选择所需的工作负载。概览标签页显示工作负载的状态。

  3. 托管 Pod 部分,点击错误状态消息。

以下部分介绍了工作负载返回的一些常见错误以及解决方法。

CrashLoopBackOff

CrashLoopBackOff 表示容器在重新启动后反复崩溃。容器可能由于多种原因而崩溃,通常检查 Pod 的日志即可排除根本原因。

默认情况下,崩溃的容器重新启动,指数延迟限制为五分钟。您可以通过在 spec: restartPolicy 下设置 restartPolicy 字段 Deployment 的 Pod 规范来更改此行为。该字段的默认值为 Always

您可以使用 kubectl 命令行工具或 Cloud Console 找出 Pod 容器崩溃的原因。

kubectl

如需查看集群中正在运行的所有 Pod,请运行以下命令:

kubectl get pods

查找具有 CrashLoopBackOff 错误的 Pod。

如需获取 Pod 的日志,请运行以下命令:

kubectl logs pod-name

pod-name 替换为有问题的 Pod 的名称。

您还可以传递 -p 标志,以获取 Pod 容器的上一个实例的日志(如果存在)。

控制台

请执行以下步骤:

  1. 访问 Cloud Console 中的 GKE Workloads 信息中心。

    访问 GKE Workloads 信息中心

  2. 选择所需的工作负载。概览标签页显示工作负载的状态。

  3. 托管 Pod 部分中,点击有问题的 Pod。

  4. 在 Pod 的菜单中,点击日志标签页。

检查崩溃容器的“退出代码”

您可以通过执行以下任务找到退出代码:

  1. 运行以下命令:

    kubectl describe pod pod-name
    

    pod-name 替换为 Pod 的名称。

  2. 查看 containers: container-name: last state: exit code 字段中的值:

    • 如果退出代码为 1,表示容器因为应用崩溃而崩溃。
    • 如果退出代码为 0,请验证您的应用运行了多长时间。

    当应用的主进程退出时,容器将退出。如果您的应用很快完成执行,容器可能仍会继续重新启动。

连接到正在运行的容器

打开一个连接至 Pod 的 shell:

kubectl exec -it pod-name -- /bin/bash

如果您的 Pod 中有多个容器,请添加 -c container-name

现在,您可以从容器运行 bash 命令:您可以测试网络或检查您是否可以访问应用使用的文件或数据库。

ImagePullBackOff 和 ErrImagePull

ImagePullBackOffErrImagePull 指示无法从映像注册表加载容器使用的映像。

您可以使用 Cloud Console 或 kubectl 命令行工具验证此问题。

kubectl

要获取有关 Pod 容器映像的更多信息,请运行以下命令:

kubectl describe pod pod-name

控制台

请执行以下步骤:

  1. 访问 Cloud Console 中的 GKE Workloads 信息中心。

    访问 GKE Workloads 信息中心

  2. 选择所需的工作负载。概览标签页显示工作负载的状态。

  3. 托管 Pod 部分中,点击有问题的 Pod。

  4. 在 Pod 的菜单中,点击事件标签页。

如果找不到映像

如果找不到您的映像:

  1. 验证映像的名称是否正确。
  2. 验证映像的标记正确。(尝试使用 :latest 命令或不添加标记来获取最新映像)。
  3. 如果映像具有完整的注册表路径,请验证它是否存在于您正在使用的 Docker 注册表中。如果仅提供映像名称,请检查 Docker Hub 注册表。
  4. 尝试手动拉取 docker 映像:

    • 使用 SSH 连接到节点:

      例如,如需使用 SSH 连接到 us-central1-a 地区中的 example-instance,请运行以下命令:

      gcloud compute ssh example-instance --zone us-central1-a
      
    • 运行 docker pull image-name

    如果此选项有效,则可能需要在 Pod 上指定 ImagePullSecrets。Pod 只能在自己的命名空间中引用映像拉取密钥,因此每个命名空间需要执行一次此过程。

如果您遇到“权限被拒绝”或“无拉取路径”错误,请确认您已登录和/或有权访问该映像。

如果您使用的是私有注册表,则可能需要密钥才能读取映像。

如果您的映像托管在 Container Registry 中,与您的节点池关联的服务帐号需要具备包含该映像的 Cloud Storage 存储分区的读取访问权限。如需了解详情,请参阅 Container Registry 文档

Pod 无法调度

PodUnschedulable 表示由于资源不足或某些配置错误而无法调度 Pod。

资源不足

您可能会遇到指示缺少 CPU、内存或其他资源的错误。例如:“没有可用的节点匹配所有谓词:cpu(2)不足”,表明在两个节点上没有足够的 CPU 可用于满足 Pod 的请求。

默认 CPU 请求是 100M 或 CPU(或一个核心)的 10%。 如果您想请求更多或更少的资源,请在 spec: containers: resources: requests 下的 Pod 规范中指定该值

MatchNodeSelector

MatchNodeSelector 指示没有与 Pod 的标签选择器匹配的节点。

如需验证这一点,请在 spec: nodeSelector 下,检查 Pod 规范中的 nodeSelector 字段指定的标签。

如需查看集群中节点的标签,请运行以下命令:

kubectl get nodes --show-labels

如需将标签附加到节点,请运行以下命令:

kubectl label nodes node-name label-key=label-value

请替换以下内容:

  • node-name 替换为所需的节点。
  • label-key 替换为标签的键。
  • label-value 替换为标签的值。

如需了解详情,请参阅将 Pod 分配给节点

PodToleratesNodeTaints

PodToleratesNodeTaints 指示无法安排任何节点运行 Pod,因为当前没有节点可承受其节点污点

如需验证属于这种情况,请运行以下命令:

kubectl describe nodes node-name

在输出中,选中 Taints 字段,该字段列出了键值对和调度效果。

如果列出的效果是 NoSchedule,则在该节点上没有 Pod 可以计划,除非它有一个匹配的负荷

解决此问题的一种方法是移除污点。例如,如需移除 NoSchedule 污点,请运行以下命令:

kubectl taint nodes node-name key:NoSchedule-

PodFitsHostPorts

PodFitsHostPorts 指示节点正在尝试使用的端口已在使用中。

如需解决此问题,请在 spec: containers: ports: hostPort 下查看 Pod 规范的 hostPort 值。您可能需要将此值更改为另一个端口。

无最低可用性

如果节点有足够的资源但仍然显示 Does not have minimum availability 消息,请查看 Pod 的状态。如果状态为 SchedulingDisabledCordoned 状态,则该节点无法调度新的 Pod。您可以使用 Cloud Console 或 kubectl 命令行工具检查节点的状态。

kubectl

如需获取节点的状态,请运行以下命令:

kubectl get nodes

如需在节点上启用调度,请运行:

kubectl uncordon node-name

控制台

请执行以下步骤:

  1. 访问 Cloud Console 中的 GKE Workloads 信息中心。

    访问 GKE Clusters 信息中心

  2. 选择集群。节点标签页显示节点及其状态。

如需在节点上启用调度,请执行以下步骤:

  1. 从列表中,点击所需的节点。

  2. 从“节点详细信息”中,点击取消封锁按钮。

Unbound PersistentVolumeClaims

Unbound PersistentVolumeClaims 指示 Pod 引用了未绑定的 PersistentVolumeClaim。如果您的 PersistentVolume 预配失败,可能会发生此错误。您可以通过获取 PersistentVolumeClaim 事件并检查其是否失败来验证预配是否失败。

如需获取事件,请运行以下命令:

kubectl describe pvc statefulset-name-pvc-name-0

请替换以下内容:

  • statefulset-name 替换为 StatefulSet 对象的名称。
  • pvc-name 替换为 PersistentVolumeClaim 对象的名称。

如果您在手动预配 PersistentVolume 并将其绑定到 PersistentVolumeClaim 期间遇到配置错误,也可能会出现此问题。您可以尝试再次预配卷。

连接问题

网络概览讨论中所述,为了有效地进行问题排查,了解 Pod 如何从其网络命名空间连接到节点上的根命名空间非常重要。对于以下讨论,除非另有说明,否则假设集群使用 GKE(而非 Calico)的原生 CNI。也就是说,没有应用任何网络政策

选定节点上的 Pod 无可用性

如果选定节点上的 Pod 没有网络连接,请确保 Linux 网桥已启动:

ip address show cbr0

如果 Linux 网桥已关闭,请启动:

sudo ip link set cbr0 up

确保节点正在获取连接到 cbr0 的 Pod MAC 地址:

arp -an

选定节点上的 Pod 具有最小连接性

如果选定节点上的 Pod 具有最小连接性,则应首先通过在工具箱容器中运行 tcpdump 来确认是否丢失了任何数据包:

sudo toolbox bash

如果您尚未在工具箱中安装 tcpdump,请进行安装:

apt install -y tcpdump

针对 cbr0 运行 tcpdump

tcpdump -ni cbr0 host hostname and port port-number and [tcp|udp|icmp]

如果出现大型数据包从网桥下游丢弃的情况(例如,TCP 握手完成,但未收到 SSL hello),请确保 Linux 网桥 MTU 已正确设置为集群的 VPC 网络的 MTU。

ip address show cbr0

使用叠加层时(例如,Weave 或 Flannel),必须进一步减小该 MTU 以适应叠加层上的封装开销。

间歇性连接失败

与 Pod 的连接由 iptables 转发。流作为 conntrack 表中的条目被跟踪,当每个节点有许多工作负载时,conntrack 表可能会耗尽并表现为失败。这些可以记录在节点的串行控制台中,例如:

nf_conntrack: table full, dropping packet

如果您能够确定间歇性问题是由 conntrack 耗尽引起的,则可以增加集群的大小(从而减少每个节点的工作负载和流),或者增加 nf_conntrack_max

new_ct_max=$(awk '$1 == "MemTotal:" { printf "%d\n", $2/32; exit; }' /proc/meminfo)
sysctl -w net.netfilter.nf_conntrack_max="${new_ct_max:?}" \
  && echo "net.netfilter.nf_conntrack_max=${new_ct_max:?}" >> /etc/sysctl.conf

针对容器报告“bind: Address already in use”

Pod 中的容器无法启动,因为根据容器日志,应用尝试绑定到的端口已被预留。该容器会进行崩溃循环。例如,在 Cloud Logging 中:

resource.type="container"
textPayload:"bind: Address already in use"
resource.labels.container_name="redis"

2018-10-16 07:06:47.000 CEST 16 Oct 05:06:47.533 # Creating Server TCP listening socket *:60250: bind: Address already in use
2018-10-16 07:07:35.000 CEST 16 Oct 05:07:35.753 # Creating Server TCP listening socket *:60250: bind: Address already in use

当 Docker 崩溃时,有时一个正在运行的容器会被遗忘并过时。该进程仍在分配给该 Pod 的网络命名空间中运行,并在其端口上进行侦听。因为 Docker 和 kubelet 不知道容器已过时,所以它们尝试使用新进程启动新容器,但该进程无法在端口上绑定,因为它被添加到已与该 Pod 关联的网络命名空间中。

如需诊断此问题:

  1. 您需要在 .metadata.uuid 字段中找到 Pod 的 UUID:

    kubectl get pod -o custom-columns="name:.metadata.name,UUID:.metadata.uid" ubuntu-6948dd5657-4gsgg
    
    name                      UUID
    ubuntu-6948dd5657-4gsgg   db9ed086-edba-11e8-bdd6-42010a800164
    
  2. 从节点获取以下命令的输出:

    docker ps -a
    ps -eo pid,ppid,stat,wchan:20,netns,comm,args:50,cgroup --cumulative -H | grep [Pod UUID]
    
  3. 检查此 Pod 中运行的进程。因为 cgroup 命名空间的 UUID 包含 Pod 的 UUID,所以可以在 ps 输出中通过 grep 命令查找 Pod UUID。还可以通过 grep 查找前一行,以便在 docker-containerd-shim 进程的参数中也包含容器 ID。删除 cgroup 列的其余部分,以获得更为简单的输出:

    # ps -eo pid,ppid,stat,wchan:20,netns,comm,args:50,cgroup --cumulative -H | grep -B 1 db9ed086-edba-11e8-bdd6-42010a800164 | sed s/'blkio:.*'/''/
    1283089     959 Sl   futex_wait_queue_me  4026531993       docker-co       docker-containerd-shim 276e173b0846e24b704d4 12:
    1283107 1283089 Ss   sys_pause            4026532393         pause           /pause                                     12:
    1283150     959 Sl   futex_wait_queue_me  4026531993       docker-co       docker-containerd-shim ab4c7762f5abf40951770 12:
    1283169 1283150 Ss   do_wait              4026532393         sh              /bin/sh -c echo hello && sleep 6000000     12:
    1283185 1283169 S    hrtimer_nanosleep    4026532393           sleep           sleep 6000000                            12:
    1283244     959 Sl   futex_wait_queue_me  4026531993       docker-co       docker-containerd-shim 44e76e50e5ef4156fd5d3 12:
    1283263 1283244 Ss   sigsuspend           4026532393         nginx           nginx: master process nginx -g daemon off; 12:
    1283282 1283263 S    ep_poll              4026532393           nginx           nginx: worker process
    
  4. 在此列表中,您可以看到容器 ID,它们在 docker ps 中也应该可见。

    在此示例中:

    • docker-containerd-shim 276e173b0846e24b704d4 为 pause
    • docker-containerd-shim ab4c7762f5abf40951770 为 sh 和 sleep (sleep-ctr)
    • docker-containerd-shim 44e76e50e5ef4156fd5d3 为 nginx (echoserver-ctr)
  5. 请查看 docker ps 输出中的相应内容:

    # docker ps --no-trunc | egrep '276e173b0846e24b704d4|ab4c7762f5abf40951770|44e76e50e5ef4156fd5d3'
    44e76e50e5ef4156fd5d383744fa6a5f14460582d0b16855177cbed89a3cbd1f   gcr.io/google_containers/echoserver@sha256:3e7b182372b398d97b747bbe6cb7595e5ffaaae9a62506c725656966d36643cc                   "nginx -g 'daemon off;'"                                                                                                                                                                                                                                                                                                                                                                     14 hours ago        Up 14 hours                             k8s_echoserver-cnt_ubuntu-6948dd5657-4gsgg_default_db9ed086-edba-11e8-bdd6-42010a800164_0
    ab4c7762f5abf40951770d3e247fa2559a2d1f8c8834e5412bdcec7df37f8475   ubuntu@sha256:acd85db6e4b18aafa7fcde5480872909bd8e6d5fbd4e5e790ecc09acc06a8b78                                                "/bin/sh -c 'echo hello && sleep 6000000'"                                                                                                                                                                                                                                                                                                                                                   14 hours ago        Up 14 hours                             k8s_sleep-cnt_ubuntu-6948dd5657-4gsgg_default_db9ed086-edba-11e8-bdd6-42010a800164_0
    276e173b0846e24b704d41cf4fbb950bfa5d0f59c304827349f4cf5091be3327   k8s.gcr.io/pause-amd64:3.1
    

    在正常情况下,您会在 docker ps 中显示的 ps 中看到所有容器 ID。如果有一个看不到,则该容器为过时容器,并且您可能会看到在报告为已在使用中的 TCP 端口上侦听的 docker-containerd-shim process 的子进程。

    为验证这一点,请在该容器的网络命名空间中执行 netstat。获取 Pod 的任何容器进程(不是 docker-containerd-shim)的 pid。

    由以上示例得出:

    • 1283107 - pause
    • 1283169 - sh
    • 1283185 - sleep
    • 1283263 - nginx master
    • 1283282 - nginx worker
    # nsenter -t 1283107 --net netstat -anp
    Active Internet connections (servers and established)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
    tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      1283263/nginx: mast
    Active UNIX domain sockets (servers and established)
    Proto RefCnt Flags       Type       State         I-Node   PID/Program name     Path
    unix  3      [ ]         STREAM     CONNECTED     3097406  1283263/nginx: mast
    unix  3      [ ]         STREAM     CONNECTED     3097405  1283263/nginx: mast
    
    gke-zonal-110-default-pool-fe00befa-n2hx ~ # nsenter -t 1283169 --net netstat -anp
    Active Internet connections (servers and established)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
    tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      1283263/nginx: mast
    Active UNIX domain sockets (servers and established)
    Proto RefCnt Flags       Type       State         I-Node   PID/Program name     Path
    unix  3      [ ]         STREAM     CONNECTED     3097406  1283263/nginx: mast
    unix  3      [ ]         STREAM     CONNECTED     3097405  1283263/nginx: mast
    

    您还可以使用 ip netns 执行 netstat,但需要手动链接进程的网络命名空间,因为 Docker 不会执行此链接:

    # ln -s /proc/1283169/ns/net /var/run/netns/1283169
    gke-zonal-110-default-pool-fe00befa-n2hx ~ # ip netns list
    1283169 (id: 2)
    gke-zonal-110-default-pool-fe00befa-n2hx ~ # ip netns exec 1283169 netstat -anp
    Active Internet connections (servers and established)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
    tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      1283263/nginx: mast
    Active UNIX domain sockets (servers and established)
    Proto RefCnt Flags       Type       State         I-Node   PID/Program name     Path
    unix  3      [ ]         STREAM     CONNECTED     3097406  1283263/nginx: mast
    unix  3      [ ]         STREAM     CONNECTED     3097405  1283263/nginx: mast
    gke-zonal-110-default-pool-fe00befa-n2hx ~ # rm /var/run/netns/1283169
    

缓解措施:

短期缓解措施是通过上面概述的方法识别过时进程,并使用 kill [PID] 命令终止这些进程。

长期缓解措施包括确定 Docker 崩溃的原因并进行修复。 可能的原因包括:

  • 僵尸进程堆积,从而耗尽了 PID 命名空间
  • docker 中存在错误
  • 资源压力/OOM