排查容器运行时问题

Google Kubernetes Engine (GKE) 节点上的容器运行时的问题可能会导致各种工作负载失败,从而阻止容器启动或可靠运行。这些问题通常是由网络配置错误、权限问题或容器运行时之间的差异等问题引起的。

您可以使用本页面来排查 GKE 节点池上容器运行时的常见问题。查找以下问题的解决方案:Windows 上装载路径错误、从私有注册表拉取映像失败、缺少文件系统指标、CNI 初始化错误以及在执行探测中出现行为差异。

对于在 GKE 集群上管理和部署工作负载的平台管理员和运维人员以及应用开发者来说,此信息非常重要。了解这些问题排查步骤有助于确保您的容器在不同的节点映像和配置中可靠运行。如需详细了解我们在 内容Google Cloud 中提及的常见角色和示例任务,请参阅常见的 GKE 用户角色和 任务

Windows Server 节点池上的问题

以下部分介绍了特定于运行 Windows Server 节点池的 GKE 集群的容器运行时问题。

包含裸盘符的装载路径失败

此问题已在 containerd 1.6.6 版及更高版本中得到解决。

运行使用低于 1.6.6 版的 containerd 运行时的 Windows Server 节点池的 GKE 集群可能会遇到类似于以下内容的错误:

failed to create containerd task : CreateComputeSystem : The parameter is incorrect : unknown

此错误是由指定不带尾部反斜杠的盘符(例如,D: 而不是 `D:`)引起的。如需了解详情,请参阅 GitHub 问题 #6589

解决方案

如需解决此问题,请将节点池升级到使用 containerd 运行时 1.6.6 版或更高版本的最新 GKE 版本。

在使用 Containerd 的 Windows 节点池上,具有非数组预转义 CMDENTRYPOINT 命令行的容器映像失败

此问题已在 containerd 1.6 版及更高版本中得到解决。

在启动容器时,运行使用 containerd 运行时 1.5.X 的 Windows Server 节点池的 GKE 集群可能会遇到以下错误:

failed to start containerd task : hcs::System::CreateProcess : The system cannot find the file specified.: unknown

如需了解详情,请参阅 GitHub 问题 #5067GitHub 问题 #6300

解决方案

如需解决此问题,请将节点池升级到使用 containerd 运行时 1.6.6 版或更高版本的最新 GKE 版本。

在使用 Containerd 的 Windows 节点池上,具有不存在的路径或 Linux 格式(正斜线)路径的容器映像卷失败

此问题已在 containerd 1.6 版及更高版本中得到解决。

在启动容器时,运行使用 containerd 运行时 1.5.X 的 Windows Server 节点池的 GKE 集群可能会遇到以下错误:

failed to generate spec: failed to stat "<volume_path>": CreateFile : The system cannot find the path specified.

如需了解详情,请参阅 GitHub 问题 #5671

解决方案

如需解决此问题,请将节点池升级到使用 containerd 运行时 1.6.x 版或更高版本的最新 GKE 版本。

在 GKE Windows Server 节点池上容器运行时重启后,基于挂接的操作将无法正常运行

受影响的 GKE 版本: 1.21 到 1.21.5-gke.1802、1.22 到 1.22.3-gke.700

如果 GKE 集群运行使用 Containerd 运行时(版本 1.5.4 和 1.5.7-gke.0)的 Windows Server 节点池,则在容器运行时被强制重启时,可能会遇到问题:对现有运行容器的挂接操作无法再次绑定 IO。此问题不会导致 API 调用失败,但系统不会发送或接收数据。这包括通过集群 API 服务器挂接和记录 CLI 与 API 的数据。

解决方案

如需解决此问题,请升级到具有更高 GKE 版本的修补后容器运行时版本 (1.5.7-gke.1)。

/etc/mtab:不存在此类文件或目录

Docker 容器运行时默认在容器内填充此符号链接,但 containerd 运行时不会填充。

如需了解详情,请参阅 GitHub 问题 #2419

解决方案

如需解决此问题,请在构建映像期间手动创建符号链接 /etc/mtab

ln -sf /proc/mounts /etc/mtab

映像拉取错误:不是目录

受影响的 GKE 版本:全部

使用 kaniko 构建映像时,可能无法使用 containerd 拉取该映像,并显示错误消息“不是目录”。如果映像以特殊方式构建,则以下情况下会发生此错误:当上一个命令删除一个目录,而下一个命令在该目录中重新创建相同的文件。

以下是带有 npm 的 Dockerfile 示例,说明了此问题。

RUN npm cache clean --force
RUN npm install

如需了解详情,请参阅 GitHub 问题 #4659

解决方案

如需解决此问题,请使用 docker build 构建映像,此问题不会影响该映像。

如果 docker build 不适合您,请将多个命令组合成一个命令。以下 Dockerfile 示例将 RUN npm cache clean --forceRUN npm install 组合:

RUN npm cache clean --force && npm install

某些文件系统指标缺失且指标格式不同

受影响的 GKE 版本:全部

Kubelet /metrics/cadvisor 端点提供 Prometheus 指标,如 Kubernetes 系统组件的指标中所述。如果您安装依赖于该端点的指标收集器,可能会看到以下问题:

  • Docker 节点上的指标格式为 k8s_<container-name>_<pod-name>_<namespace>_<pod-uid>_<restart-count>,但 containerd 节点上的指标格式为 <container-id>
  • containerd 节点上缺少一些文件系统指标,如下所示:

    container_fs_inodes_free
    container_fs_inodes_total
    container_fs_io_current
    container_fs_io_time_seconds_total
    container_fs_io_time_weighted_seconds_total
    container_fs_limit_bytes
    container_fs_read_seconds_total
    container_fs_reads_merged_total
    container_fs_sector_reads_total
    container_fs_sector_writes_total
    container_fs_usage_bytes
    container_fs_write_seconds_total
    container_fs_writes_merged_total
    

解决方案

您可以使用 cAdvisor 作为独立守护进程集来缓解此问题。

  1. 查找名称模式为 vX.Y.Z-containerd-cri 的最新 cAdvisor 版本(例如 v0.42.0-containerd-cri)。
  2. 按照 cAdvisor Kubernetes Daemonset 中的步骤创建守护进程集。
  3. 让已安装的指标收集器指向使用 cAdvisor /metrics 端点,该端点提供全套 Prometheus 容器指标

替代方案

  1. 将监控解决方案迁移到 Cloud Monitoring,后者提供了一整套容器指标。
  2. 使用端点 /stats/summaryKubelet 摘要 API 收集指标。

探测超出超时后的执行探测行为差异

受影响的 GKE 版本:全部

containerd 映像上的执行探测行为与 dockershim 映像上的行为不同。如果为 Pod 定义的 exec 探测超过 dockershim 映像上声明的 Kubernetes timeoutSeconds 阈值,则该探测会被视为失败。在 containerd 映像上,系统会忽略声明的 timeoutSeconds 阈值后返回的探测结果。

解决方案

在 GKE 中,特性门控 ExecProbeTimeout 设置为 false,并且无法更改。如需解决此问题,请提高所有受影响的执行探测的 timeoutSeconds 阈值,或在探测逻辑中实现超时功能。

排查私有注册表问题

本部分提供了 containerd 中的私有注册表配置的问题排查信息。

映像拉取失败并显示 x509 错误:证书由未知授权机构签名

如果 GKE 找不到特定私有注册表网域的证书,则会出现此问题。您可以使用以下查询在 Cloud Logging 中检查此错误:

  1. 前往 Google Cloud 控制台中的 Logs Explorer 页面:

    转到 Logs Explorer

  2. 请运行以下查询:

    ("Internal error pulling certificate" OR
    "Failed to get credentials from metadata server" OR
    "Failed to install certificate")
    

如需解决此问题,请尝试执行以下操作:

  1. 在 GKE Standard 中,打开位于以下路径的配置文件:

    /etc/containerd/hosts.d/DOMAIN/config.toml
    

    DOMAIN 替换为注册表的 FQDN。

  2. 验证您的配置文件是否包含正确的 FQDN。

  3. 验证配置文件的 secretURI 字段中的证书路径是否正确。

  4. 验证 Secret Manager 中是否存在证书。

证书不存在

如果 GKE 无法从 Secret Manager 拉取证书以在您的节点上配置 containerd,则会出现此问题。 如果 GKE 无法访问证书,则节点会在没有证书的情况下启动。在该节点上运行的工作负载无法访问您的私有注册表。

如需解决此问题,请尝试执行以下操作:

  1. 确保受影响的节点在运行 Container-Optimized OS。不支持 Ubuntu 和 Windows 节点。
  2. 在配置文件中,确保 secretURI 字段中的 Secret 路径正确无误。
  3. 检查集群的 IAM 服务账号是否具有访问 Secret 的正确权限
  4. 检查集群是否具有 cloud-platform 访问权限范围。如需了解相关说明, 请参阅检查访问权限范围
  5. 使用以下方法之一重新创建节点:

没有为本地网络 (10.0.0.0/8) 配置不安全的注册表选项

受影响的 GKE 版本:全部

在 containerd 映像上,没有为本地网络 10.0.0.0/8 配置不安全的注册表选项。如果使用不安全的私有仓库,您可能会看到以下错误:

pulling image: rpc error: code = Unknown desc = failed to pull and unpack image "IMAGE_NAME": failed to do request: Head "IMAGE_NAME": http: server gave HTTP response to HTTPS client

如需解决此问题,请尝试执行以下操作:

  • 使用 Artifact Registry
  • 如果您的使用场景支持此选项,请在私有注册表上配置 TLS。您可以使用 containerd 配置文件指示 GKE 使用存储在 Secret Manager 中的证书来访问私有注册表。如需查看相关说明,请参阅使用私有 CA 证书访问私有注册表

配置特权 DaemonSet 以修改 containerd 配置

对于 Standard 集群,请尝试以下步骤。此解决方法不适用于 Autopilot,因为特权容器存在安全风险。如果您的环境向互联网公开,则在部署此解决方案之前,请考虑您的风险容忍度。 在所有情况下,我们都强烈建议您为私有注册表配置 TLS,并改用 Secret Manager 选项。

  1. 请查看以下清单:

    apiVersion: apps/v1
    kind: DaemonSet
    metadata:
      name: insecure-registries
      namespace: default
      labels:
        k8s-app: insecure-registries
    spec:
      selector:
        matchLabels:
          name: insecure-registries
      updateStrategy:
        type: RollingUpdate
      template:
        metadata:
          labels:
            name: insecure-registries
        spec:
          nodeSelector:
            cloud.google.com/gke-container-runtime: "containerd"
          hostPID: true
          containers:
            - name: startup-script
              image: gke.gcr.io/startup-script:v2
              imagePullPolicy: Always
              securityContext:
                privileged: true
              env:
              - name: ADDRESS
                value: "REGISTRY_ADDRESS"
              - name: STARTUP_SCRIPT
                value: |
                  set -o errexit
                  set -o pipefail
                  set -o nounset
    
                  if [[ -z "$ADDRESS" || "$ADDRESS" == "REGISTRY_ADDRESS" ]]; then
                    echo "Error: Environment variable ADDRESS is not set in containers.spec.env"
                    exit 1
                  fi
    
                  echo "Allowlisting insecure registries..."
                  containerd_config="/etc/containerd/config.toml"
                  hostpath=$(sed -nr 's;  config_path = "([-/a-z0-9_.]+)";\1;p' "$containerd_config")
                  if [[ -z "$hostpath" ]]; then
                    echo "Node uses CRI config model V1 (deprecated), adding mirror under $containerd_config..."
                    grep -qxF '[plugins."io.containerd.grpc.v1.cri".registry.mirrors."'$ADDRESS'"]' "$containerd_config" || \
                      echo -e '[plugins."io.containerd.grpc.v1.cri".registry.mirrors."'$ADDRESS'"]\n  endpoint = ["http://'$ADDRESS'"]' >> "$containerd_config"
                  else
                    host_config_dir="$hostpath/$ADDRESS"
                    host_config_file="$host_config_dir/hosts.toml"
                    echo "Node uses CRI config model V2, adding mirror under $host_config_file..."
                    if [[ ! -e "$host_config_file" ]]; then
                      mkdir -p "$host_config_dir"
                      echo -e "server = \"https://$ADDRESS\"\n" > "$host_config_file"
                    fi
                    echo -e "[host.\"http://$ADDRESS\"]\n  capabilities = [\"pull\", \"resolve\"]\n" >> "$host_config_file"
                  fi
                  echo "Reloading systemd management configuration"
                  systemctl daemon-reload
                  echo "Restarting containerd..."
                  systemctl restart containerd

    .spec.containers.env 字段中,将 ADDRESS 变量的 REGISTRY_ADDRESS 值替换为本地 HTTP 仓库的地址,格式为 DOMAIN_NAME:PORT。例如,

    containers:
    - name: startup-script
      ...
      env:
      - name: ADDRESS
        value: "example.com:5000"
    
  2. 部署 DaemonSet:

    kubectl apply -f insecure-registry-ds.yaml
    

DaemonSet 会将不安全的仓库添加到每个节点上的 containerd 配置中。

containerd 忽略特权 Pod 的任何设备映射

受影响的 GKE 版本:全部

对于特权 Kubernetes Pod,容器运行时会忽略 volumeDevices.devicePath 传递给它的所有设备映射,改为让主机上的每台设备都可供 /dev 下的容器使用。

当节点面临 I/O 压力时,containerd 泄露 shim 进程

受影响的 GKE 版本:1.25.0 至 1.25.15-gke.1040000、1.26.0 至 1.26.10-gke.1030000、1.27.0 至 1.27.6-gke.1513000,以及 1.28.0 至 1.28.3-gke.1061000

当 GKE 节点面临 I/O 压力时,containerd 在删除 Pod 后可能无法删除 containerd-shim-runc-v2 进程,从而导致进程泄露。当节点上发生泄露时,您会看到节点上的 containerd-shim-runc-v2 进程数大于该节点上的 Pod 数。您可能还会看到内存和 CPU 用量增加以及额外的 PID。如需了解详情,请参阅 GitHub 问题修复由高 IO 压力导致的 shim 泄露

如需解决此问题,请将节点升级到以下版本或更高版本:

  • 1.25.15-gke.1040000
  • 1.26.10-gke.1030000
  • 1.27.6-gke.1513000
  • 1.28.3-gke.1061000

在运行 containerd 的 Pod 上启用 IPv6 地址系列

受影响的 GKE 版本:1.18、1.19、1.20.0 至 1.20.9

为使用 containerd 运行的 Pod 启用了 IPv6 映像系列。dockershim 映像会在所有 Pod 上停用 IPv6,而 containerd 映像不会。例如,localhost 会先解析为 IPv6 地址 ::1。这通常不是问题,但在某些情况下可能会导致意外行为。

解决方案

如需解决此问题,请明确使用 IPv4 地址(如 127.0.0.1),或将 Pod 中运行的应用配置为同时用于两个地址系列。

172.17/16 IP 地址范围冲突

受影响的 GKE 版本:1.18.0 至 1.18.14

172.17/16 IP 地址范围会被启用了 containerd 的节点虚拟机上的 docker0 接口占用。向该范围发送或来自该范围的流量可能无法正确路由(例如,Pod 可能无法连接到 IP 地址在 172.17/16 中的 VPN 连接主机)。

未收集的 GPU 指标

受影响的 GKE 版本:1.18.0 至 1.18.18

在 GKE 1.18.18 版之前,如果将 containerd 用作运行时,系统不会收集 GPU 用量指标

解决方案

如需解决此问题,请将集群升级到 GKE 1.18.18 版或更高版本。

不能在 containerd 中使用 config.mediaType 设置为 application/octet-stream 的映像

受影响的 GKE 版本:全部

不能在 containerd 中使用 config.mediaType 设置为 "application/octet-stream" 的映像。如需了解详情,请参阅 GitHub 问题 #4756。这些映像与 Open Container Initiative 规范不兼容,被视为不正确。这些映像可与 Docker 结合使用来提供向后兼容性,而在 containerd 中,这些映像不受支持。

症状和诊断

节点日志中的错误示例:

Error syncing pod <pod-uid> ("<pod-name>_<namespace>(<pod-uid>)"), skipping: failed to "StartContainer" for "<container-name>" with CreateContainerError: "failed to create containerd container: error unpacking image: failed to extract layer sha256:<some id>: failed to get reader from content store: content digest sha256:<some id>: not found"

映像清单通常可在托管它的注册表中找到。 获得清单后,请检查 config.mediaType 以确定您是否遇到了此问题:

"mediaType": "application/octet-stream",

解决方案

由于 containerd 社区决定不支持此类映像,因此所有 containerd 版本都会受到影响,并且没有修复。您必须使用 Docker 1.11 或更高版本重新构建容器映像,并且必须确保 config.mediaType 字段未设置为 "application/octet-stream"

CNI 未初始化

受影响的 GKE 版本:全部

如果您看到类似以下内容的错误,则表示容器网络接口 (CNI) 配置尚未就绪:

Error: "network is not ready: container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized".

出现此错误的主要原因有两个:

  • CNI 尚未完成安装
  • webhook 配置错误

确保 CNI 已完成安装

当 GKE 安装 CNI 配置时,您可能会在节点引导期间的日志文件中看到此错误。如果您看到此错误,但 GKE 正在正确创建所有节点,则可以放心地忽略此错误。

之所以会出现这种情况,是因为 CNI 为 Pod 提供网络连接,因此 Pod 需要 CNI 才能正常运行。不过,Kubernetes 会使用污点来标记未就绪的节点,而系统 Pod 可以容忍这些污点。这意味着,系统 Pod 可以在网络就绪之前在新节点上启动,从而导致此错误。

如需解决此问题,请等待 GKE 完成 CNI 配置的安装。CNI 完成网络配置后,系统 Pod 会成功启动,无需任何干预。

修正配置错误的 webhook

如果“CNI 未初始化”错误仍然存在,并且您注意到 GKE 在升级、调整大小或其他操作期间无法创建节点,则可能是 webhook 配置错误。

如果您有一个拦截创建 Pod 的 DaemonSet 控制器命令的自定义 webhook,并且该 webhook 配置错误,那么您可能会在 Google Cloud 控制台中看到错误,即节点错误状态。此配置错误会阻止 GKE 创建 netdcalico-node Pod。如果当错误仍然存在时 netdcalico-node Pod 成功启动,请与 Customer Care 团队联系

如需修正任何配置错误的 webhook,请完成以下步骤:

  1. 识别配置错误的 webhook

    如果您使用的是启用了 Dataplane V1 网络政策强制执行的集群,还可以检查 calico-typha Pod 的状态,了解哪些 webhook 导致了此错误:

    kubectl describe pod -n kube-system -l k8s-app=calico-typha
    

    如果 Pod 存在错误,则输出类似于以下内容:

    Events:
    Type     Reason        Age                     From                   Message
    ----     ------        ----                    ----                   -------
    Warning  FailedCreate  9m15s (x303 over 3d7h)  replicaset-controller  Error creating: admission webhook WEBHOOK_NAME denied the request [...]
    

    在此输出中,WEBHOOK_NAME 是失败的 Webhook 的名称。您的输出可能包含有关其他类型错误的信息。

  2. 如果您想保留配置错误的 webhook,请进行问题排查。如果不需要这些文件,请运行以下命令将其删除:

    kubectl delete mutatingwebhookconfigurations WEBHOOK_NAME
    kubectl delete validatingwebhookconfigurations WEBHOOK_NAME
    

    WEBHOOK_NAME 替换为您要移除的配置错误的 webhook 的名称。

  3. 配置 webhook 以忽略系统 Pod

后续步骤