Kubernetes 上的 TiDB 集群故障诊断

本文介绍了 Kubernetes 上 TiDB 集群的一些常见故障以及诊断解决方案。

诊断模式

当 Pod 处于 CrashLoopBackoff 状态时,Pod 内会容器不断退出,导致无法正常使用 kubectl exectkctl debug,给诊断带来不便。为了解决这个问题,TiDB in Kubernetes 提供了 PD/TiKV/TiDB Pod 诊断模式。在诊断模式下,Pod 内的容器启动后会直接挂起,不会再进入重复 Crash 的状态,此时,便可以通过 kubectl exectkctl debug 连接 Pod 内的容器进行诊断。

操作方式:

首先,为待诊断的 Pod 添加 Annotation:

  1. kubectl annotate pod ${pod_name} -n ${namespace} runmode=debug

在 Pod 内的容器下次重启时,会检测到该 Annotation,进入诊断模式。等待 Pod 进入 Running 状态即可开始诊断:

  1. watch kubectl get pod ${pod_name} -n ${namespace}

下面是使用 kubectl exec 进入容器进行诊断工作的例子:

  1. kubectl exec -it ${pod_name} -n ${namespace} -- /bin/sh

诊断完毕,修复问题后,删除 Pod:

  1. kubectl delete pod ${pod_name} -n ${namespace}

Pod 重建后会自动回到正常运行模式。

Helm 管理的集群意外删除后恢复

TiDB Operator 使用 PV (Persistent Volume)、PVC (Persistent Volume Claim) 来存储持久化的数据,如果不小心使用 helm delete 意外删除了集群,PV/PVC 对象以及数据都会保留下来,以最大程度保证数据安全。

此时集群恢复的办法就是使用 helm install 命令来创建一个同名的集群,之前保留下来未被删除的 PV/PVC 以及数据会被复用:

  1. helm install pingcap/tidb-cluster -n ${release_name} --namespace=${namespace} --version=${chart_version} -f values.yaml

Pod 未正常创建

通过 helm install 创建集群后,如果 Pod 没有创建,则可以通过以下方式进行诊断:

  1. kubectl get tidbclusters -n ${namespace}
  2. kubectl get statefulsets -n ${namespace}
  3. kubectl describe statefulsets -n ${namespace} ${release_name}-pd

Pod 之间网络不通

针对 TiDB 集群而言,绝大部分 Pod 间的访问均通过 Pod 的域名(使用 Headless Service 分配)进行,例外的情况是 TiDB Operator 在收集集群信息或下发控制指令时,会通过 PD Service 的 service-name 访问 PD 集群。

当通过日志或监控确认 Pod 间存在网络连通性问题,或根据故障情况推断出 Pod 间网络连接可能不正常时,可以按照下面的流程进行诊断,逐步缩小问题范围:

  1. 确认 Service 和 Headless Service 的 Endpoints 是否正常:

    1. kubectl -n ${namespace} get endpoints ${release_name}-pd
    2. kubectl -n ${namespace} get endpoints ${release_name}-tidb
    3. kubectl -n ${namespace} get endpoints ${release_name}-pd-peer
    4. kubectl -n ${namespace} get endpoints ${release_name}-tikv-peer
    5. kubectl -n ${namespace} get endpoints ${release_name}-tidb-peer

    以上命令展示的 ENDPOINTS 字段中,应当是由逗号分隔的 cluster_ip:port 列表。假如字段为空或不正确,请检查 Pod 的健康状态以及 kube-controller-manager 是否正常工作。

  2. 进入 Pod 的 Network Namespace 诊断网络问题:

    1. tkctl debug -n ${namespace} ${pod_name}

    远端 shell 启动后,使用 dig 命令诊断 DNS 解析,假如 DNS 解析异常,请参照诊断 Kubernetes DNS 解析进行故障排除:

    1. dig ${HOSTNAME}

    使用 ping 命令诊断到目的 IP 的三层网络是否连通(目的 IP 为使用 dig 解析出的 ClusterIP):

    1. ping ${TARGET_IP}

    假如 ping 检查失败,请参照诊断 Kubernetes 网络进行故障排除。

    假如 ping 检查正常,继续使用 telnet 检查目标端口是否打开:

    1. telnet ${TARGET_IP} ${TARGET_PORT}

    假如 telnet 检查失败,则需要验证 Pod 的对应端口是否正确暴露以及应用的端口是否配置正确:

    1. # 检查端口是否一致
    2. kubectl -n ${namespace} get po ${pod_name} -ojson | jq '.spec.containers[].ports[].containerPort'
    3. # 检查应用是否被正确配置服务于指定端口上
    4. # PD, 未配置时默认为 2379 端口
    5. kubectl -n ${namespace} -it exec ${pod_name} -- cat /etc/pd/pd.toml | grep client-urls
    6. # TiKV, 未配置时默认为 20160 端口
    7. kubectl -n ${namespace} -it exec ${pod_name} -- cat /etc/tikv/tikv.toml | grep addr
    8. # TiDB, 未配置时默认为 4000 端口
    9. kubectl -n ${namespace} -it exec ${pod_name} -- cat /etc/tidb/tidb.toml | grep port

Pod 处于 Pending 状态

Pod 处于 Pending 状态,通常都是资源不满足导致的,比如:

  • 使用持久化存储的 PD、TiKV、Monitor Pod 使用的 PVC 的 StorageClass 不存在或 PV 不足
  • Kubernetes 集群中没有节点能满足 Pod 申请的 CPU 或内存
  • PD 或者 TiKV Replicas 数量和集群内节点数量不满足 tidb-scheduler 高可用调度策略

此时,可以通过 kubectl describe pod 命令查看 Pending 的具体原因:

  1. kubectl describe po -n ${namespace} ${pod_name}

如果是 CPU 或内存资源不足,可以通过降低对应组件的 CPU 或内存资源申请使其能够得到调度,或是增加新的 Kubernetes 节点。

如果是 PVC 的 StorageClass 找不到,需要在 values.yaml 里面将 storageClassName 修改为集群中可用的 StorageClass 名字,执行 helm upgrade,然后将 Statefulset 删除,并且将对应的 PVC 也都删除,可以通过以下命令获取集群中可用的 StorageClass:

  1. kubectl get storageclass

如果集群中有 StorageClass,但可用的 PV 不足,则需要添加对应的 PV 资源。对于 Local PV,可以参考本地 PV 配置进行扩充。

tidb-scheduler 针对 PD 和 TiKV 定制了高可用调度策略。对于同一个 TiDB 集群,假设 PD 或者 TiKV 的 Replicas 数量为 N,那么可以调度到每个节点的 PD Pod 数量最多为 M=(N-1)/2(如果 N<3,M=1),可以调度到每个节点的 TiKV Pod 数量最多为 M=ceil(N/3)(ceil 表示向上取整,如果 N<3,M=1)。如果 Pod 因为不满足高可用调度策略而导致状态为 Pending,需要往集群内添加节点。

Pod 处于 CrashLoopBackOff 状态

Pod 处于 CrashLoopBackOff 状态意味着 Pod 内的容器重复地异常退出(异常退出后,容器被 Kubelet 重启,重启后又异常退出,如此往复)。可能导致 CrashLoopBackOff 的原因有很多,此时,最有效的定位办法是查看 Pod 内容器的日志:

  1. kubectl -n ${namespace} logs -f ${pod_name}

假如本次日志没有能够帮助诊断的有效信息,可以添加 -p 参数输出容器上次启动时的日志信息:

  1. kubectl -n ${namespace} logs -p ${pod_name}

确认日志中的错误信息后,可以根据 tidb-server 启动报错tikv-server 启动报错pd-server 启动报错中的指引信息进行进一步排查解决。

若是 TiKV Pod 日志中出现 “cluster id mismatch” 信息,则 TiKV Pod 使用的数据可能是其他或之前的 TiKV Pod 的旧数据。在集群配置本地存储时未清除机器上本地磁盘上的数据,或者强制删除了 PV 导致数据并没有被 local volume provisioner 程序回收,可能导致 PV 遗留旧数据,导致错误。

在确认该 TiKV 应作为新节点加入集群、且 PV 上的数据应该删除后,可以删除该 TiKV Pod 和关联 PVC。TiKV Pod 将自动重建并绑定新的 PV 来使用。集群本地存储配置中,应对机器上的本地存储删除,避免 Kubernetes 使用机器上遗留的数据。集群运维中,不可强制删除 PV ,应由 local volume provisioner 程序管理。用户通过创建、删除 PVC 以及设置 PV 的 reclaimPolicy 来管理 PV 的生命周期。

另外,TiKV 在 ulimit 不足时也会发生启动失败的状况,对于这种情况,可以修改 Kubernetes 节点的 /etc/security/limits.conf 调大 ulimit:

  1. root soft nofile 1000000
  2. root hard nofile 1000000
  3. root soft core unlimited
  4. root soft stack 10240

假如通过日志无法确认失败原因,ulimit 也设置正常,那么可以通过诊断模式进行进一步排查。

无法访问 TiDB 服务

TiDB 服务访问不了时,首先确认 TiDB 服务是否部署成功,确认方法如下:

查看该集群的所有组件是否全部都启动了,状态是否为 Running。

  1. kubectl get po -n ${namespace}

检查 TiDB 组件的日志,看日志是否有报错。

  1. kubectl logs -f ${pod_name} -n ${namespace} -c tidb

如果确定集群部署成功,则进行网络检查:

  1. 如果你是通过 NodePort 方式访问不了 TiDB 服务,请在 node 上尝试使用 service domain 或 clusterIP 访问 TiDB 服务,假如 serviceName 或 clusterIP 的方式能访问,基本判断 Kubernetes 集群内的网络是正常的,问题可能出在下面两个方面:

    • 客户端到 node 节点的网络不通。
    • 查看 TiDB service 的 externalTrafficPolicy 属性是否为 Local。如果是 Local 则客户端必须通过 TiDB Pod 所在 node 的 IP 来访问。
  2. 如果 service domain 或 clusterIP 方式也访问不了 TiDB 服务,尝试用 TiDB服务后端的 <PodIP>:4000 连接看是否可以访问,如果通过 PodIP 可以访问 TiDB 服务,可以确认问题出在 service domain 或 clusterIP 到 PodIP 之间的连接上,排查项如下:

    • 检查 DNS 服务是否正常:

      1. kubectl get po -n kube-system -l k8s-app=kube-dns
      2. dig ${tidb_service_domain}
    • 检查各个 node 上的 kube-proxy 是否正常运行:

      1. kubectl get po -n kube-system -l k8s-app=kube-proxy
    • 检查 node 上的 iptables 规则中 TiDB 服务的规则是否正确

      1. iptables-save -t nat |grep ${clusterIP}
    • 检查对应的 endpoint 是否正确

  3. 如果通过 PodIP 访问不了 TiDB 服务,问题出在 Pod 层面的网络上,排查项如下:

    • 检查 node 上的相关 route 规则是否正确
    • 检查网络插件服务是否正常
    • 参考上面的 Pod 之间网络不通章节

TiKV Store 异常进入 Tombstone 状态

正常情况下,当 TiKV Pod 处于健康状态时(Pod 状态为 Running),对应的 TiKV Store 状态也是健康的(Store 状态为 UP)。但并发进行 TiKV 组件的扩容和缩容可能会导致部分 TiKV Store 异常并进入 Tombstone 状态。此时,可以按照以下步骤进行修复:

  1. 查看 TiKV Store 状态:

    1. kubectl get -n ${namespace} tidbcluster ${release_name} -ojson | jq '.status.tikv.stores'
  2. 查看 TiKV Pod 运行状态:

    1. kubectl get -n ${namespace} po -l app.kubernetes.io/component=tikv
  3. 对比 Store 状态与 Pod 运行状态。假如某个 TiKV Pod 所对应的 Store 处于 Offline 状态,则表明该 Pod 的 Store 正在异常下线中。此时,可以通过下面的命令取消下线进程,进行恢复:

    1. 打开到 PD 服务的连接:

      1. kubectl port-forward -n ${namespace} svc/${cluster_name}-pd ${local_port}:2379 &>/tmp/portforward-pd.log &
    2. 上线对应 Store:

      1. curl -X POST http://127.0.0.1:2379/pd/api/v1/store/${store_id}/state?state=Up
  4. 假如某个 TiKV Pod 所对应的 lastHeartbeatTime 最新的 Store 处于 Tombstone 状态 ,则表明异常下线已经完成。此时,需要重建 Pod 并绑定新的 PV 进行恢复:

    1. 将该 Store 对应 PV 的 reclaimPolicy 调整为 Delete

      1. kubectl patch $(kubectl get pv -l app.kubernetes.io/instance=${release_name},tidb.pingcap.com/store-id=${store_id} -o name) -p '{"spec":{"persistentVolumeReclaimPolicy":"Delete"}}
    2. 删除 Pod 使用的 PVC:

      1. kubectl delete -n ${namespace} pvc tikv-${pod_name} --wait=false
    3. 删除 Pod,等待 Pod 重建:

      1. kubectl delete -n ${namespace} pod ${pod_name}

      Pod 重建后,会以在集群中注册一个新的 Store,恢复完成。

TiDB 长连接被异常中断

许多负载均衡器 (Load Balancer) 会设置连接空闲超时时间。当连接上没有数据传输的时间超过设定值,负载均衡器会主动将连接中断。若发现 TiDB 使用过程中,长查询会被异常中断,可检查客户端与 TiDB 服务端之间的中间件程序。若其连接空闲超时时间较短,可尝试增大该超时时间。若不可修改,可打开 TiDB tcp-keep-alive 选项,启用 TCP keepalive 特性。

默认情况下,Linux 发送 keepalive 探测包的等待时间为 7200 秒。若需减少该时间,可通过 podSecurityContext 字段配置 sysctls

  • 如果 Kubernetes 集群内的 kubelet 允许配置 --allowed-unsafe-sysctls=net.*,请为 kubelet 配置该参数,并按如下方式配置 TiDB:

    1. tidb:
    2. ...
    3. podSecurityContext:
    4. sysctls:
    5. - name: net.ipv4.tcp_keepalive_time
    6. value: "300"
  • 如果 Kubernetes 集群内的 kubelet 不允许配置 --allowed-unsafe-sysctls=net.*,请按如下方式配置 TiDB:

    1. tidb:
    2. annotations:
    3. tidb.pingcap.com/sysctl-init: "true"
    4. podSecurityContext:
    5. sysctls:
    6. - name: net.ipv4.tcp_keepalive_time
    7. value: "300"
    8. ...

注意:

进行以上配置要求 TiDB Operator 1.1 及以上版本。