1. 前言
本文记录使用K8S过程中遇到的问题、解决办法和一些原理。
问题排查方法参考《kubectl命令——故障排查篇》。
2. kubelet不停重启
2.1. 问题描述
K8S的一个worker节点磁盘不足,关机进行磁盘扩容,物理扩容后开机,执行esize2fs /dev/vdb
,扩容完成。
docker ps
,发现没有容器被启动。kubectl status kubelet
,发现kubelet不停进行重启,每次都启动失败。kubectl status docker
,正常docker正常running。
重启机器,问题依旧。
2.2. 排查解决
1 | journalctl -xeu kubelet -r |
kubelet日志没有报错,docker日志中报错:
1 | level=error msg="xxx cleanup: failed to delete container from containerd: no such container |
FROM ChatGPT:
该错误日志表示 Docker 清理容器时失败,原因是没有找到相应的容器。
可能原因及解决方法:
- 容器不存在:检查容器是否已被删除或者已经退出,如果是则不需要处理该错误。
- 容器正在运行:如果容器正在运行,可能是由于正在执行某些任务而无法清理。此时可以尝试停止容器后再进行清理。
- Docker daemon 出现故障:在某些情况下,Docker daemon 可能会出现故障导致无法清理容器。尝试重启 Docker daemon 可能会解决问题。
- 操作系统出现故障:在某些情况下,操作系统可能会出现故障导致无法清理容器。尝试重启操作系统可能会解决问题。
解决办法:手动清理容器,然后重启机器。
1 | docker container prune |
3. 新增节点flannel启动失败
3.1. 问题描述
K8S集群新增了一个节点,flannel pod自动调度上去了,但是并没有启动成功。
查看kubelet日志,报错为:
[failed to find plugin “flannel” in path [/opt/cni/bin]]
W0523 20:49:19.343813 12586 cni.go:239] Unable to update cni config: no valid networks found in /etc/cni/net.d
3.2. 解决办法
从其他正常节点拷贝一个flannel文件到这个问题节点上的 /opt/cni/bin 目录。
4. 节点上的pod被驱逐
4.1. 问题描述
因为磁盘压力,节点上的pod被驱逐了。但是,实际上节点还有很多磁盘空间。
4.2. 解决办法
1、查找kubelet配置文件路径
1 | systemctl status kubelet -l |
找到Drop-In配置文件路径,一般为:
1 | /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf |
2、修改kubelet配置文件
配置文件中添加:
1 | KUBELET_EXTRA_ARGS="--eviction-hard=memory.available<100Mi,nodefs.available<5%,imagefs.available<15%,nodefs.inodesFree<5%" |
3、重启kubelet
1 | systemctl daemon-reload |
4.3. 扩展阅读
节点压力驱逐是 kubelet 主动终止 Pod 以回收节点上资源的过程。
kubelet 监控集群节点的内存、磁盘空间和文件系统的 inode 等资源。 当这些资源中的一个或者多个达到特定的消耗水平, kubelet 可以主动地使节点上一个或者多个 Pod 失效,以回收资源防止饥饿。
在节点压力驱逐期间,kubelet 将所选 Pod 的 PodPhase 设置为 Failed。这将终止 Pod。
我们可以为 kubelet 指定自定义驱逐条件,以便在作出驱逐决定时使用。驱逐条件分为软驱逐条件和硬驱逐条件。
软驱逐条件将驱逐条件与管理员所必须指定的宽限期配对。 在超过宽限期之前,kubelet 不会驱逐 Pod。如果没有指定的宽限期,kubelet 会在启动时返回错误。
硬驱逐条件没有宽限期。当达到硬驱逐条件时, kubelet 会立即杀死 pod,而不会正常终止以回收紧缺的资源。
kubelet 具有以下默认硬驱逐条件:
- memory.available<100Mi
- nodefs.available<10%
- imagefs.available<15%
- nodefs.inodesFree<5%(Linux 节点)
参考文档:节点压力驱逐
5. 节点上的镜像被清理
5.1. 问题描述
节点上的镜像,被清理掉了,但是并没有人进行过手动清理操作,也没有定时任务。
手动拉取镜像,过一段时间还是会被清理掉。
5.2. 问题原因
参考文档:
Kubernetes 对节点上的所有镜像提供生命周期管理服务,这里的所有镜像是真正意义上的所有镜像,不仅仅是通过 Kubelet 拉取的镜像。当磁盘使用率超过设定上限 HighThresholdPercent 时,Kubelet 就会按照 LRU 清除策略逐个清理掉那些没有被任何 Pod 容器(包括已经死亡的容器)所使用的镜像,直到磁盘使用率降到设定下限 LowThresholdPercent 或没有空闲镜像可以清理。此外,在进行镜像清理时,会考虑镜像的生存年龄,对于年龄没有达到最短生存年龄 MinAge 要求的镜像,暂不予以清理。
在磁盘使用率超出设定上限后:首先,通过 CRI 容器运行时接口读取节点上的所有镜像以及 Pod 容器;然后,根据现有容器列表过滤出那些已经不被任何容器所使用的镜像;接着,按照镜像最近被使用时间排序,越久被用到的镜像越会被排在前面,优先清理;最后,就按照排好的顺序逐个清理镜像,直到磁盘使用率降到设定下限(或者已经没有空闲镜像可以清理)。
需要注意的是,Kubelet 读取到的镜像列表是节点镜像列表,而读取到的容器列表却仅包括由其管理的容器(即 Pod 容器,包括 Pod 内的死亡容器)。因此,那些用户手动 docker run 起来的容器,对于 Kubelet 垃圾回收来说就是不可见的,也就不能阻止对相关镜像的垃圾回收。当然,Kubelet 的镜像回收不是 force 类型的回收,虽然会对用户手动下载的镜像进行回收动作,但如果确实有运行的(或者停止的任何)容器与该镜像关联的话,删除操作就会失败(被底层容器运行时阻止删除)。
影响镜像垃圾回收的关键参数有:
1 | --image-gc-high-threshold:磁盘使用率上限,有效范围 [0-100],默认 85 |
需要注意的是,1.21 版本之后 image-gc-high-threshold 替换为了 eviction-hard,image-gc-low-threshold 替换为了 eviction-mininum-reclaim。
6. 业务服务响应很慢
6.1. 问题描述
某个业务服务的Pod响应很慢,发现它的requests资源配置很低,limits资源配置很高。
调大requests后响应速度明显变快了,是什么原理?
6.2. 原理解析
requests是长期允许,保证资源;limits是临时允许,并不保证资源。
上面的问题中,因为requests配置的很低,所以只能保证requests中配置的资源,并不能保证用到用到limits中配置的资源。
6.3. 扩展阅读
“requests”和”limits”在Kubernetes中的原理是通过Linux的cgroups(control groups)来实现资源管理和隔离。cgroups是Linux内核提供的一种机制,它允许对进程组进行资源限制、优先级调整和统计。Kubernetes利用cgroups将资源限制和隔离应用到容器级别。
“requests”定义了容器所需的最小资源数量。Kubernetes调度器使用这个值来决定在哪个节点上运行容器,并确保节点上有足够的资源满足容器的请求。”requests”的目的是为了确保容器能够正常运行而不会遇到资源不足的问题。
而”limits”定义了容器允许使用的资源的上限。Kubernetes使用这个值来监控容器的资源使用情况,并保护节点的稳定性。如果容器试图使用超过其限制的资源量,Kubernetes会采取相应的措施,如终止容器或重新调度到其他节点。”limits”的目的是为了防止容器使用过多的资源,从而保护整个集群的稳定性。
尽管可以在容器中设置超过”requests”的资源使用量,但这并不是一个推荐的做法。当容器超过其”requests”的资源使用量时,它可能会影响其他容器的性能,导致资源竞争和不稳定的情况。超过”requests”的使用量只是暂时允许,Kubernetes会尽力满足容器的需求,但不保证持续提供额外的资源。
7. 节点资源充足但是无法调度
7.1. 问题描述
某个节点CPU、内存资源充足,也没有污点和亲和性配置,但是Pod无法调度到上面。
7.2. 排查解决
除了检查资源之外,再检查下节点上Pod数量的限制。
1 | kubectl describe node node01 |
Non-terminated Pods
要小于 Allocatable.pods
,如果Pod数量已经达到了节点上Pod数量限制,那么需要调大这个上限。
1 | vim /var/lib/kubelet/config.yaml # maxPods默认110,调大它 |
8. kubectl top命令执行报错
8.1. 问题描述
执行kubectl top pod
,报错:
error: Metrics API not available
在Kubernetes Dashboard上,也发现Pod的CPU使用率和内存使用都无法显示。
8.2. 问题分析
参考kubectl top node error: metrics not available yet可知,之所以出现上面的问题,是因为K8S集群没有安装 metrics-server 。
8.3. 解决办法
解决办法:安装 metrics-server 。
通用安装方法:
1 | wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml |
如果是sealos安装的K8S集群,那么可以使用 metrics-server 集群镜像安装。
1 | sealos run labring/metrics-server:v0.6.2 --cmd="\ |
其中 /root/metrics-server/values.yaml 内容为:
1 | defaultArgs: |
9. kubectl get响应慢
9.1. 问题描述
执行 kubectl get xxx
,响应很慢,十几秒,总会几条出现提示:
1 | I0819 11:17:11.769943 2075052 request.go:668] Waited for 1.159893511s due to client-side throttling, not priority and fairness, request: GET:https://192.168.56.101:6443/apis/enterprisesearch.k8s.elastic.co/v1beta1?timeout=32s |
而且每次request后面的内容都不同。
9.2. 解决办法
解决办法:kube-apiserver 的启动参数中添加 --feature-gates=APIPriorityAndFairness=false
具体操作方法:
1 | # master节点 |
添加启动参数:
1 | spec: |
但是,上面的办法只能解决部分问题。kubectl get pod
正常了,但是kubectl get all
还是有问题。
终极解决办法:升级kubectl到v1.22版本以上。从提示其实也能看出是 client-side 侧的限流,但是 kubectl 并没有修改限流配置的方法,只能升级版本。
参考文档:
- Kubectl Error - Waited for … due to client-side throttling, not priority and fairness, request
- API Priority and Fairness
- Crossplane 支持的自定义资源数量突破了 Kubernetes 的限制
- How to avoid “Waited for Xs due to client-side throttling, not priority and fairness”
- kubectl get all - command return - Throttling request
- k8smaster-请求受到了节流
- kube-apiserver
10. 某个节点无法访问coredns
10.1. 问题描述
新增了一个节点X,处于Ready状态,但是发现节点X和coredns不通。
具体表现为 telnet 10.96.0.10 53
不通,nslookup www.baidu.com 10.96.0.10
也无法得到解析结果。
与此同时,节点A是正常的,telnet 10.96.0.10 53
通,nslookup www.baidu.com 10.96.0.10
可以得到解析结果。
10.2. 问题排查解决
1、检查节点状态
所有节点都处于Ready状态。
2、检查pod网络组件
查看节点X的flannel和kube-proxy,都运行正常。
3、确认问题边界
- 节点X ping 节点A和节点B,通
- 节点X telnet 另外的 service,不通
- 节点X ping coredns pod ip,不通
- 节点X ping 本机 pod ip,通
- 节点X ping 节点A pod ip,通
- 节点X ping 节点B pod ip,不通
这就有意思了,节点X和主机A pod通,和主机B pod不通,大概率是CNI的问题。
4、检查所有主机的cni插件
1 | kubectl get nodes |
10个节点,发现只有5个节点有flannel,但是这些没有flannel的节点也处于Ready状态,也是个bug。
继续排查发现没有flannel的节点存在污点,因此给flannel添加容忍,使每个节点都部署上CNI插件。
至此,问题解决。
10.3. 根因分析
CNI插件的根本作用是调整节点上网络的转发规则。
虽然节点X上的CNI插件正常,但是有5个节点缺少CNI插件,那么当节点X上调整网络转发规则时,会漏掉这5个节点上的pod,也就是说节点X上没有网络转发规则可以访问到它们。
11. 某个节点上的pod全部pending
11.1. 问题描述
节点X处于Ready状态,pod也可以调度到上面,但是所有调度到上面的pod全部处于pending状态,也没有任何events输出。
11.2. 解决办法
解决办法:重启节点X的kubelet。
12. read eth0/speed: invalid argument
12.1. 问题描述
个别GPU节点上,运行 gpu-feature-discovery 报错:
1 | E0927 10:43:40.329641 1 network.go:146] failed to read net iface attribute speed: read /host-sys/class/net/eth0/speed: invalid argument |
登录GPU节点,执行:
1 | cat /host-sys/class/net/eth0/speed |
同样会报错 invalid argument
12.2. 解决办法
升级内核。
参考文档:
13. 无法删除pod
13.1. 问题描述
一些Pod处于Terminating状态,但是一直无法被删除。
使用--force
和--grace-period
强制删除,会报出警告:
1 | warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely. |
13.2. 解决办法
1 | kubectl describe pod xxx -n yyy |
可以看到具体的pod无法被删除的原因,比如:
1 | Warning Unhealthy 12s (x3182 over 8h) kubelet Readiness probe errored: rpc error: code = Unknown desc = Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? |
根据提示检查docker状态,发现docker没有正常运行,启动它即可。
14. 日志不存在
14.1. 问题描述
systemctl status kubelet
,发现大量报错:
1 | 11月 28 15:48:31 node1 kubelet[1663]: E1122 15:48:31.502542 1663 cri_stats_provider.go:669] "Unable to fetch container log stats" err="failed to get fsstats for \"/var/log/pods/calico-system_csi-node-driver-jbf9l_969616b6-784a-4154-8161-24206f4501cc/calico-csi/21.log\": no such file or directory" containerName="calico-csi" |
14.2. 问题排查解决
查看报错中的日志路径:
1 | /var/log/pods/calico-system_csi-node-driver-jbf9l_969616b6-784a-4154-8161-24206f4501cc/calico-csi/21.log -> /var/lib/docker/containers/8b352f0c0582b0c048449fef8305fde15ce4017dbb37abfaedcd698e9e1d2ffb/8b352f0c0582b0c048449fef8305fde15ce4017dbb37abfaedcd698e9e1d2ffb-json.log |
日志路径是一个软链,而真实日志已经不存在了。
解决办法为删除所有失效软链:
1 | find /var/log/pods -type l ! -exec test -e {} \; -print |
然后重启kubelet
1 | systemctl restart kubelet |
15. 流量经过ingress偶发502问题
15.1. 问题描述
100个并发压测一个接口,直接压测service,全部200;通过域名压测时,无论是在内网压测还是外网压测,都会出现1%的502。
服务pod日志中,没有502。
ingress access.log 中,出现502。
ingress errer.log 中,出现104。
1 | 2023/12/09 02:35:50 [error] 7960#7960: *60866823 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 172.20.0.100, server: test.voidking.com, request: "POST /api/v1/user/login HTTP/1.1", upstream: "http://100.107.80.144:80/api/v1/user/login", host: "test.voidking.com" |
15.2. 问题排查解决
1、调整服务pod资源、ingress pod资源、coredns pod资源,都没有效果。
2、去掉tls证书,没有效果。
3、调整ingress内核参数,没有效果。
4、调整ingress upstream-keepalive-timeout
参数为 4 ,502数量降低到了 0.1% ,效果明显!调整 ingress upstream-keepalive-timeout
参数为 30,502数量降低到了 0.1% 。
注意:要在ingress controller的configMap中修改 upstream-keepalive-timeout
配置,而不是ingress的annotations(经验证无效)。
参考文档:
15.3. 扩展阅读
502错误是网关错误,504错误是网关超时。
有个比较好的比喻:502是电话打通没人接,504是电话打不通。
什么是keepalive?HTTP/1.1中的Keep-Alive是一种机制,它允许在单个TCP连接上发送多个HTTP请求和响应,而不需要为每个请求/响应对都建立一个新的连接。在HTTP/1.0中,每个HTTP请求都需要建立一个新的TCP连接,这会导致一些性能上的开销。HTTP/1.1引入了Keep-Alive以解决这个问题。
那么 keepalive_timout 和 502 错误之间有什么关系?因为网站的架构不是浏览器直接连接后端的应用服务器,而是中间有nginx服务器做反向代理,浏览器和nginx服务器之间建立keepalive连接,nginx再和后端的应用服务器建立keepalive连接,所以这是两种不同的keepalive连接。
我们把浏览器和nginx之间的keepalive连接叫做ka1,把nginx和应用服务器之间的keepalive连接叫做ka2。如果ka1的超时设置为100秒,也就是说如果100秒之内没有新内容要传输,就把nginx和浏览器之间的连接断掉。而同时,我们把ka2设置为50秒,也就是说如果nginx和应用服务器之间没有新内容要传输,那么就把应用服务器和nginx之间的连接断掉。那么这时候就会产生一个问题:前50秒没有传输内容,在第51秒的时候,浏览器向nginx发了一个请求,这时候ka1还没有断掉,因为没有到100秒的时间,所以这是没有问题的,但是当nginx试图向应用服务器发请求的时候就出问题了,ka2断了!因为ka2的超时设置是50秒,这时候已经超了,所以就断了,这时候nginx无法再从应用服务器获得正确响应,只好返回浏览器502错误!
因此,解决办法也就明确了:让ka1小于ka2。
16. udp包问题
16.1. 问题描述
1 | ffprobe rtsp://user:password@192.168.56.200:8080/api/to/stream |
在宿主机上执行上面这条命令,可以正常获取到视频流。
启动一个容器,执行上面这条命令,可以正常获取到视频流。
使用同一个镜像启动Pod,执行上面这条命令,获取到视频流时会卡住。
正常输出:
1 | ffprobe version 4.2.7-0ubuntu0.1 Copyright (c) 2007-2022 the FFmpeg developers |
卡住的输出:
1 | ffprobe version 4.2.7-0ubuntu0.1 Copyright (c) 2007-2022 the FFmpeg developers |
16.2. 解决办法
指定 tcp 可以正常获取到视频流。
1 | ffprobe rtsp://user:password@192.168.56.200:8080/api/to/stream -rtsp_transport tcp |
从问题表现,只能推测出calico网络插件对于udp包的处理存在问题,具体原因还需要探索。
17. 卸载istio后无法创建pod问题
17.1. 问题描述
卸载istio后,创建了一个deployment,但是k8s无法创建出这个deployment对应的pod,到不了调度阶段,看不到pod。
查看deployment的描述,发现报错:
1 | Warning FailedCreate 24m (x17 over 29m) deployment-controller Error creating: Internal error occurred: failed calling webhook "rev.object.sidecar-injector.istio.io": Post "https://istiod.istio-system.svc:443/inject?timeout=10s": no endpoints available for service "istiod" |
17.2. 原因分析
出现这个问题的原因是卸载 Istio 后,ValidatingWebhookConfiguration 还保留在 Kubernetes 系统中。当 Kubernetes API server 内部试图调用 webhook 但无法找到相应的 webhook 服务端点时,会出现此错误。
17.3. 解决办法
1、列出k8s集群中所有的 MutatingWebhookConfiguration 和 ValidatingWebhookConfiguration
1 | kubectl get mutatingwebhookconfigurations |
找到与 Istio 相关的 webhook 配置。
2、删除与 Istio 相关的 webhook 配置
1 | kubectl delete mutatingwebhookconfigurations <your-mutating-webhook-config-name> |
删除这些 webhook 配置之后,deployment 对应的 pods 就能够正常创建了。