kubeadm 故障排查

与任何程序一样,你在安装或运行 kubeadm 时可能会遇到错误。本页面列出了一些常见的失败场景,并提供了帮助你理解和解决问题的步骤。

如果你的问题未在下方列出,请按照以下步骤操作

  • 如果你认为你的问题是 kubeadm 的一个 Bug

  • 如果你不确定 kubeadm 的工作原理,可以在 Slack#kubeadm 频道提问,或在 StackOverflow 上提出问题。请包含相关的标签,例如 #kubernetes#kubeadm,以便大家能帮助你。

由于缺少 RBAC,无法将 v1.18 节点加入到 v1.17 集群

在 v1.18 中,kubeadm 增加了防止同名节点加入集群的功能。这需要为 bootstrap-token 用户添加 RBAC 权限,以便能够 GET Node 对象。

然而,这导致了一个问题,即 v1.18 的 kubeadm join 无法加入由 kubeadm v1.17 创建的集群。

要解决此问题,你有两个选择

在一个控制平面节点上使用 kubeadm v1.18 执行 kubeadm init phase bootstrap-token。请注意,这也会启用其余的 bootstrap-token 权限。

或者

使用 kubectl apply -f ... 手动应用以下 RBAC

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: kubeadm:get-nodes
rules:
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: kubeadm:get-nodes
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: kubeadm:get-nodes
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind: Group
    name: system:bootstrappers:kubeadm:default-node-token

安装期间找不到 ebtables 或类似的可执行文件

如果在运行 kubeadm init 时看到以下警告

[preflight] WARNING: ebtables not found in system path
[preflight] WARNING: ethtool not found in system path

那么你的节点上可能缺少 ebtablesethtool 或类似的可执行文件。你可以使用以下命令安装它们

  • 对于 Ubuntu/Debian 用户,运行 apt install ebtables ethtool
  • 对于 CentOS/Fedora 用户,运行 yum install ebtables ethtool

安装期间 kubeadm 阻塞,等待控制平面就绪

如果注意到 kubeadm init 在打印出以下行后挂起

[apiclient] Created API client, waiting for the control plane to become ready

这可能是由多种问题引起的。最常见的原因是

  • 网络连接问题。继续之前,请检查你的机器是否具有完整的网络连接。
  • 容器运行时的 cgroup 驱动与 kubelet 的不同。要了解如何正确配置,请参见配置 cgroup 驱动
  • 控制平面容器处于 CrashLoopBackOff 或挂起状态。你可以通过运行 docker ps 并运行 docker logs 检查每个容器来查看。对于其他容器运行时,请参见使用 crictl 调试 Kubernetes 节点

移除托管容器时 kubeadm 阻塞

如果容器运行时停止并且不移除任何 Kubernetes 管理的容器,可能会发生以下情况

sudo kubeadm reset
[preflight] Running pre-flight checks
[reset] Stopping the kubelet service
[reset] Unmounting mounted directories in "/var/lib/kubelet"
[reset] Removing kubernetes-managed containers
(block)

一种可能的解决方案是重新启动容器运行时,然后重新运行 kubeadm reset。你还可以使用 crictl 调试容器运行时的状态。参见使用 crictl 调试 Kubernetes 节点

Pod 处于 RunContainerErrorCrashLoopBackOffError 状态

紧随 kubeadm init 之后,不应该有任何 Pod 处于这些状态。

  • 如果在 kubeadm init 之后立即 有 Pod 处于这些状态之一,请在 kubeadm 仓库中提交一个 Issue。coredns (或 kube-dns) 在你部署网络插件之前应该处于 Pending 状态。
  • 如果在部署网络插件后看到 Pod 处于 RunContainerErrorCrashLoopBackOffError 状态,并且 coredns (或 kube-dns) 没有变化,那么很可能你安装的 Pod Network 插件有问题。你可能需要为其授予更多的 RBAC 权限或使用更新的版本。请在 Pod Network 提供商的 Issue 跟踪器中提交一个 Issue 并在那里进行问题分类。

coredns 卡在 Pending 状态

这是预期的行为,也是设计的一部分。kubeadm 与网络提供商无关,因此管理员应该安装选择的 Pod 网络插件。在完全部署 CoreDNS 之前,你必须安装 Pod Network。因此,在网络设置好之前,它会处于 Pending 状态。

HostPort 服务不起作用

HostPortHostIP 功能是否可用取决于你的 Pod 网络提供商。请联系 Pod 网络插件的作者,了解 HostPortHostIP 功能是否可用。

Calico、Canal 和 Flannel CNI 提供商已验证支持 HostPort。

更多信息,请参见CNI portmap 文档

如果你的网络提供商不支持 portmap CNI 插件,你可能需要使用服务的 NodePort 特性或使用 HostNetwork=true

Pod 无法通过其 Service IP 访问

  • 许多网络插件尚未启用发夹模式 (hairpin mode),该模式允许 Pod 通过其 Service IP 访问自身。这是与CNI相关的问题。请联系网络插件提供商以获取其对发夹模式支持的最新状态。

  • 如果你使用 VirtualBox (直接或通过 Vagrant),需要确保 hostname -i 返回一个可路由的 IP 地址。默认情况下,第一个接口连接到一个不可路由的仅主机网络。一种解决方法是修改 /etc/hosts,参见此Vagrantfile 中的示例。

TLS 证书错误

以下错误表示可能存在证书不匹配。

# kubectl get pods
Unable to connect to the server: x509: certificate signed by unknown authority (possibly because of "crypto/rsa: verification error" while trying to verify candidate authority certificate "kubernetes")
  • 验证 $HOME/.kube/config 文件包含有效证书,必要时重新生成证书。kubeconfig 文件中的证书是 base64 编码的。可以使用 base64 --decode 命令解码证书,使用 openssl x509 -text -noout 命令查看证书信息。

  • 使用以下命令取消设置 KUBECONFIG 环境变量

    unset KUBECONFIG
    

    或者将其设置为默认的 KUBECONFIG 位置

    export KUBECONFIG=/etc/kubernetes/admin.conf
    
  • 另一种解决方法是覆盖现有的 "admin" 用户的 kubeconfig

    mv $HOME/.kube $HOME/.kube.bak
    mkdir $HOME/.kube
    sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
    sudo chown $(id -u):$(id -g) $HOME/.kube/config
    

Kubelet 客户端证书轮换失败

默认情况下,kubeadm 使用 /etc/kubernetes/kubelet.conf 中指定的 /var/lib/kubelet/pki/kubelet-client-current.pem 软链接配置 kubelet 自动轮换客户端证书。如果此轮换过程失败,你可能会在 kube-apiserver 日志中看到诸如 x509: certificate has expired or is not yet valid 的错误。要解决此问题,必须执行以下步骤

  1. 备份并删除失败节点上的 /etc/kubernetes/kubelet.conf/var/lib/kubelet/pki/kubelet-client*

  2. 在集群中一个拥有 /etc/kubernetes/pki/ca.key 的工作控制平面节点上,执行 kubeadm kubeconfig user --org system:nodes --client-name system:node:$NODE > kubelet.conf$NODE 必须设置为集群中现有失败节点的名称。手动修改生成的 kubelet.conf 以调整集群名称和服务器端点,或者传递 kubeconfig user --config(参见为其他用户生成 kubeconfig 文件)。如果你的集群没有 ca.key,你必须在外部对 kubelet.conf 中的嵌入证书进行签名。

  3. 将生成的 kubelet.conf 复制到失败节点上的 /etc/kubernetes/kubelet.conf

  4. 在失败节点上重新启动 kubelet (systemctl restart kubelet),并等待 /var/lib/kubelet/pki/kubelet-client-current.pem 被重新创建。

  5. 手动编辑 kubelet.conf 指向已轮换的 kubelet 客户端证书,将 client-certificate-dataclient-key-data 替换为

    client-certificate: /var/lib/kubelet/pki/kubelet-client-current.pem
    client-key: /var/lib/kubelet/pki/kubelet-client-current.pem
    
  6. 重新启动 kubelet。

  7. 确保节点变为 Ready 状态。

在 Vagrant 中使用 flannel 作为 Pod 网络时的默认 NIC

以下错误可能表明 Pod 网络存在问题

Error from server (NotFound): the server could not find the requested resource
  • 如果你在 Vagrant 内部使用 flannel 作为 Pod 网络,则需要为 flannel 指定默认接口名称。

    Vagrant 通常为所有 VM 分配两个接口。第一个接口,所有主机都被分配 IP 地址 10.0.2.15,用于进行 NAT 的外部流量。

    这可能会导致 flannel 的问题,flannel 默认使用主机的第一个接口。这会导致所有主机都认为它们具有相同的公共 IP 地址。为防止这种情况,向 flannel 传递 --iface eth1 标志,以便选择第二个接口。

容器使用非公共 IP

在一个正常工作的集群中,某些情况下 kubectl logskubectl run 命令可能会返回以下错误

Error from server: Get https://10.19.0.41:10250/containerLogs/default/mysql-ddc65b868-glc5m/mysql: dial tcp 10.19.0.41:10250: getsockopt: no route to host
  • 这可能是由于 Kubernetes 使用的 IP 无法与同一子网上的其他 IP 通信,这可能是由机器提供商的策略导致的。

  • DigitalOcean 为 eth0 分配一个公共 IP,同时分配一个用于内部使用的私有 IP 作为其浮动 IP 特性的锚点,但 kubelet 会选择后者作为节点的 InternalIP,而不是公共 IP。

    使用 ip addr show 而非 ifconfig 检查此情况,因为 ifconfig 不会显示有问题的别名 IP 地址。或者,DigitalOcean 特定的 API 端点允许从 droplet 查询锚点 IP。

    curl http://169.254.169.254/metadata/v1/interfaces/public/0/anchor_ipv4/address
    

    解决方法是使用 --node-ip 告诉 kubelet 使用哪个 IP。在使用 DigitalOcean 时,如果你想使用可选的私有网络,可以使用公共 IP (分配给 eth0) 或私有 IP (分配给 eth1)。kubeadm NodeRegistrationOptions 结构体kubeletExtraArgs 部分可用于此目的。

    然后重新启动 kubelet

    systemctl daemon-reload
    systemctl restart kubelet
    

coredns Pod 处于 CrashLoopBackOffError 状态

如果你的节点正在运行 SELinux 且 Docker 版本较旧,可能会遇到 coredns Pod 无法启动的情况。为解决此问题,你可以尝试以下选项之一

kubectl -n kube-system get deployment coredns -o yaml | \
  sed 's/allowPrivilegeEscalation: false/allowPrivilegeEscalation: true/g' | \
  kubectl apply -f -

CoreDNS 出现 CrashLoopBackOff 的另一个原因是部署在 Kubernetes 中的 CoreDNS Pod 检测到循环。有一些解决方法可供使用,以避免 Kubernetes 在 CoreDNS 检测到循环并退出时尝试每次都重新启动 CoreDNS Pod。

etcd Pod 持续重启

如果遇到以下错误

rpc error: code = 2 desc = oci runtime error: exec failed: container_linux.go:247: starting container process caused "process_linux.go:110: decoding init error from pipe caused \"read parent: connection reset by peer\""

如果你在 CentOS 7 上运行 Docker 1.13.1.84,则会出现此问题。此版本的 Docker 会阻止 kubelet 执行进入 etcd 容器的操作。

要解决此问题,请选择以下选项之一

  • 回滚到较早的 Docker 版本,例如 1.13.1-75

    yum downgrade docker-1.13.1-75.git8633870.el7.centos.x86_64 docker-client-1.13.1-75.git8633870.el7.centos.x86_64 docker-common-1.13.1-75.git8633870.el7.centos.x86_64
    
  • 安装更近期的推荐版本之一,例如 18.06

    sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    yum install docker-ce-18.06.1.ce-3.el7.x86_64
    

无法将逗号分隔的值列表传递给 --component-extra-args 标志内的参数

kubeadm init 的标志,例如 --component-extra-args,允许你向控制平面组件(如 kube-apiserver)传递自定义参数。但是,由于用于解析值的基础类型 (mapStringString) 的限制,此机制受到限制。

如果你决定传递一个支持多个逗号分隔值的参数,例如 --apiserver-extra-args "enable-admission-plugins=LimitRanger,NamespaceExists",此标志将失败并显示 flag: malformed pair, expect string=string。这是因为 --apiserver-extra-args 的参数列表期望 key=value 对,在这种情况下,NamespacesExists 被认为是一个缺少值的键。

另一种方法是尝试像这样分隔 key=value 对:--apiserver-extra-args "enable-admission-plugins=LimitRanger,enable-admission-plugins=NamespaceExists",但这将导致键 enable-admission-plugins 只有 NamespaceExists 的值。

一个已知的解决方法是使用 kubeadm 配置文件

节点尚未被 cloud-controller-manager 初始化时 kube-proxy 已被调度

在云提供商场景中,kube-proxy 最终可能会在 cloud-controller-manager 初始化节点地址之前就被调度到新的工作节点上。这导致 kube-proxy 无法正确获取节点的 IP 地址,并对管理负载均衡器的代理功能产生连锁影响。

在 kube-proxy Pods 中可以看到以下错误

server.go:610] Failed to retrieve node IP: host IP unknown; known addresses: []
proxier.go:340] invalid nodeIP, initializing kube-proxy with 127.0.0.1 as nodeIP

一个已知的解决方案是修补 kube-proxy DaemonSet,允许其调度到控制平面节点上,无论其条件如何,并使其不被调度到其他节点上,直到这些节点的初始保护条件消退。

kubectl -n kube-system patch ds kube-proxy -p='{
  "spec": {
    "template": {
      "spec": {
        "tolerations": [
          {
            "key": "CriticalAddonsOnly",
            "operator": "Exists"
          },
          {
            "effect": "NoSchedule",
            "key": "node-role.kubernetes.io/control-plane"
          }
        ]
      }
    }
  }
}'

此问题的跟踪 Issue 在这里

节点上的 /usr 目录被挂载为只读

在 Fedora CoreOS 或 Flatcar Container Linux 等 Linux 发行版上,目录 /usr 被挂载为只读文件系统。对于flex-volume 支持,kubelet 和 kube-controller-manager 等 Kubernetes 组件使用默认路径 /usr/libexec/kubernetes/kubelet-plugins/volume/exec/,但 flex-volume 目录必须是可写的该功能才能工作。

要解决此问题,可以使用 kubeadm 配置文件配置 flex-volume 目录。

在主控制平面节点(使用 kubeadm init 创建)上,使用 --config 传递以下文件

apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
nodeRegistration:
  kubeletExtraArgs:
  - name: "volume-plugin-dir"
    value: "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
controllerManager:
  extraArgs:
  - name: "flex-volume-plugin-dir"
    value: "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"

在加入的节点上

apiVersion: kubeadm.k8s.io/v1beta4
kind: JoinConfiguration
nodeRegistration:
  kubeletExtraArgs:
  - name: "volume-plugin-dir"
    value: "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"

另一种方法是修改 /etc/fstab 使 /usr 挂载可写,但请注意,这改变了 Linux 发行版的设计原则。

kubeadm upgrade plan 打印出 context deadline exceeded 错误消息

在使用外部 etcd 的情况下,使用 kubeadm 升级 Kubernetes 集群时会显示此错误消息。这不是一个严重错误,发生的原因是旧版本的 kubeadm 会对外部 etcd 集群执行版本检查。你可以继续执行 kubeadm upgrade apply ...

此问题在 1.19 版本中已修复。

kubeadm reset 会卸载 /var/lib/kubelet

如果 /var/lib/kubelet 被挂载,执行 kubeadm reset 将有效地将其卸载。

要解决此问题,在执行 kubeadm reset 操作后重新挂载 /var/lib/kubelet 目录。

这是在 kubeadm 1.15 中引入的回归问题。该问题在 1.20 版本中已修复。

无法在 kubeadm 集群中安全地使用 metrics-server

在 kubeadm 集群中,可以通过向其传递 --kubelet-insecure-tls 来不安全地使用 metrics-server。对于生产集群,不推荐这样做。

如果你希望在 metrics-server 和 kubelet 之间使用 TLS,则存在问题,因为 kubeadm 为 kubelet 部署了一个自签名的服务证书。这可能导致 metrics-server 端出现以下错误

x509: certificate signed by unknown authority
x509: certificate is valid for IP-foo not IP-bar

参见启用签名的 kubelet 服务证书,了解如何在 kubeadm 集群中配置 kubelet 以拥有正确签名的服务证书。

另请参见如何安全地运行 metrics-server

由于 etcd hash 未更改导致升级失败

仅适用于使用 kubeadm 二进制文件 v1.28.3 或更高版本升级控制平面节点的情况,其中该节点当前由 kubeadm v1.28.0、v1.28.1 或 v1.28.2 版本管理。

你可能会遇到以下错误消息

[upgrade/etcd] Failed to upgrade etcd: couldn't upgrade control plane. kubeadm has tried to recover everything into the earlier state. Errors faced: static Pod hash for component etcd on Node kinder-upgrade-control-plane-1 did not change after 5m0s: timed out waiting for the condition
[upgrade/etcd] Waiting for previous etcd to become available
I0907 10:10:09.109104    3704 etcd.go:588] [etcd] attempting to see if all cluster endpoints ([https://172.17.0.6:2379/ https://172.17.0.4:2379/ https://172.17.0.3:2379/]) are available 1/10
[upgrade/etcd] Etcd was rolled back and is now available
static Pod hash for component etcd on Node kinder-upgrade-control-plane-1 did not change after 5m0s: timed out waiting for the condition
couldn't upgrade control plane. kubeadm has tried to recover everything into the earlier state. Errors faced
k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade.rollbackOldManifests
	cmd/kubeadm/app/phases/upgrade/staticpods.go:525
k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade.upgradeComponent
	cmd/kubeadm/app/phases/upgrade/staticpods.go:254
k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade.performEtcdStaticPodUpgrade
	cmd/kubeadm/app/phases/upgrade/staticpods.go:338
...

此失败的原因是受影响的版本在 PodSpec 中生成了一个包含不需要的默认值的 etcd 清单文件。这将导致与清单比较时出现差异,kubeadm 将期望 Pod hash 发生变化,但 kubelet 永远不会更新 hash。

如果在集群中看到此问题,有两种解决方法

  • 通过使用以下命令,可以在受影响的版本和 v1.28.3(或更高版本)之间跳过 etcd 升级

    kubeadm upgrade {apply|node} [version] --etcd-upgrade=false
    

    如果后续的 v1.28 补丁版本引入了新的 etcd 版本,不建议这样做。

  • 在升级之前,修补 etcd 静态 Pod 的清单,移除有问题的默认属性

    diff --git a/etc/kubernetes/manifests/etcd_defaults.yaml b/etc/kubernetes/manifests/etcd_origin.yaml
    index d807ccbe0aa..46b35f00e15 100644
    --- a/etc/kubernetes/manifests/etcd_defaults.yaml
    +++ b/etc/kubernetes/manifests/etcd_origin.yaml
    @@ -43,7 +43,6 @@ spec:
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
    -      successThreshold: 1
          timeoutSeconds: 15
        name: etcd
        resources:
    @@ -59,26 +58,18 @@ spec:
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
    -      successThreshold: 1
          timeoutSeconds: 15
    -    terminationMessagePath: /dev/termination-log
    -    terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /var/lib/etcd
          name: etcd-data
        - mountPath: /etc/kubernetes/pki/etcd
          name: etcd-certs
    -  dnsPolicy: ClusterFirst
    -  enableServiceLinks: true
      hostNetwork: true
      priority: 2000001000
      priorityClassName: system-node-critical
    -  restartPolicy: Always
    -  schedulerName: default-scheduler
      securityContext:
        seccompProfile:
          type: RuntimeDefault
    -  terminationGracePeriodSeconds: 30
      volumes:
      - hostPath:
          path: /etc/kubernetes/pki/etcd
    

有关此 Bug 的更多信息,请参见跟踪 Issue

最后修改于 2024 年 7 月 5 日 下午 4:06 PST:kubeadm: 在所有文档示例中使用 v1beta4 (efc1133fa4)