一个计算机技术爱好者与学习者

0%

好好学K8S:K8S问题记录

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
2
journalctl -xeu kubelet -r
journalctl -xeu docker -r

kubelet日志没有报错,docker日志中报错:

1
level=error msg="xxx cleanup: failed to delete container from containerd: no such container

FROM ChatGPT:
该错误日志表示 Docker 清理容器时失败,原因是没有找到相应的容器。

可能原因及解决方法:

  1. 容器不存在:检查容器是否已被删除或者已经退出,如果是则不需要处理该错误。
  2. 容器正在运行:如果容器正在运行,可能是由于正在执行某些任务而无法清理。此时可以尝试停止容器后再进行清理。
  3. Docker daemon 出现故障:在某些情况下,Docker daemon 可能会出现故障导致无法清理容器。尝试重启 Docker daemon 可能会解决问题。
  4. 操作系统出现故障:在某些情况下,操作系统可能会出现故障导致无法清理容器。尝试重启操作系统可能会解决问题。

解决办法:手动清理容器,然后重启机器。

1
2
docker container prune
reboot

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
2
3
/usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf
或者:
/etc/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
2
systemctl daemon-reload
systemctl restart kubelet

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
2
3
--image-gc-high-threshold:磁盘使用率上限,有效范围 [0-100],默认 85
--image-gc-low-threshold: 磁盘使用率下限,有效范围 [0-100],默认 80
--minimum-image-ttl-duration:镜像最短应该生存的年龄,默认 2 分钟

需要注意的是,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
2
vim /var/lib/kubelet/config.yaml # maxPods默认110,调大它
systemctl restart kubelet

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
2
wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
kubectl apply -f components.yaml

如果是sealos安装的K8S集群,那么可以使用 metrics-server 集群镜像安装。

1
2
3
4
sealos run labring/metrics-server:v0.6.2 --cmd="\
helm upgrade -i metrics-server charts/metrics-server \
-n kube-system \
-f /root/metrics-server/values.yaml"

其中 /root/metrics-server/values.yaml 内容为:

1
2
3
4
5
6
defaultArgs:
- --cert-dir=/tmp
- --kubelet-preferred-address-types=InternalIP
- --kubelet-use-node-status-port
- --metric-resolution=15s
- --kubelet-insecure-tls

9. kubectl get响应慢

9.1. 问题描述

执行 kubectl get xxx ,响应很慢,十几秒,总会几条出现提示:

1
2
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
I0819 11:17:31.979933 2075052 request.go:668] Waited for 9.271822241s due to client-side throttling, not priority and fairness, request: GET:https://192.168.56.101:6443/apis/events.openfunction.io/v1alpha1?timeout=32s

而且每次request后面的内容都不同。

9.2. 解决办法

解决办法:kube-apiserver 的启动参数中添加 --feature-gates=APIPriorityAndFairness=false

具体操作方法:

1
2
3
# master节点
cd /etc/kubernetes/manifests
vim kube-apiserver.yaml

添加启动参数:

1
2
3
4
5
6
spec:
containers:
- command:
- kube-apiserver
- --feature-gates=APIPriorityAndFairness=false
- ...

但是,上面的办法只能解决部分问题。kubectl get pod正常了,但是kubectl get all还是有问题。

终极解决办法:升级kubectl到v1.22版本以上。从提示其实也能看出是 client-side 侧的限流,但是 kubectl 并没有修改限流配置的方法,只能升级版本。

参考文档:

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
2
kubectl get nodes
kubectl get pod -n kube-system -owide | grep flannel

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
2
3
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"
11月 28 15:48:31 node1 kubelet[1663]: E1122 15:48:31.502610 1663 cri_stats_provider.go:669] "Unable to fetch container log stats" err="failed to get fsstats for \"/var/log/pods/kube-system_kube-proxy-9vh2d_f52465f9-229f-4895-9c23-3366b9d71c11/kube-proxy/21.log\": no such file or directory" containerName="kube-proxy"
11月 28 15:48:31 node1 kubelet[1663]: E1122 15:48:31.502645 1663 cri_stats_provider.go:669] "Unable to fetch container log stats" err="failed to get fsstats for \"/var/log/pods/calico-system_calico-node-68w9g_d33768de-9b24-43f6-ba60-ec9917889847/calico-node/0.log\": no such file or directory" containerName="calico-node"

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
2
3
find /var/log/pods -type l ! -exec test -e {} \; -print

find /var/log/pods -type l ! -exec test -e {} \; -delete

然后重启kubelet

1
2
systemctl restart kubelet
systemctl status kubelet

15. 流量经过ingress偶发502问题

15.1. 问题描述

100个并发压测一个接口,直接压测service,全部200;通过域名压测时,无论是在内网压测还是外网压测,都会出现1%的502。

服务pod日志中,没有502。
ingress access.log 中,出现502。
ingress errer.log 中,出现104。

1
2
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"
2023/12/09 02:36:05 [error] 7373#7373: *60866826 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.82.122.191: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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ffprobe version 4.2.7-0ubuntu0.1 Copyright (c) 2007-2022 the FFmpeg developers
...
libavutil 56. 31.100 / 56. 31.100
libavcodec 58. 54.100 / 58. 54.100
libavformat 58. 29.100 / 58. 29.100
libavdevice 58. 8.100 / 58. 8.100
libavfilter 7. 57.100 / 7. 57.100
libavresample 4. 0. 0 / 4. 0. 0
libswscale 5. 5.100 / 5. 5.100
libswresample 3. 5.100 / 3. 5.100
libpostproc 55. 5.100 / 55. 5.100
Input #0, rtsp, from 'rtsp://user:password@192.168.56.200:8080/api/to/stream':
Metadata:
title : Media Presentation
Duration: N/A, start: 0.000000, bitrate: N/A
Stream #0:0: Video: hevc (Main), yuv420p(tv), 1280x720, 32 fps, 25 tbr, 90k tbn, 32 tbc
Stream #0:1: Audio: pcm_alaw, 8000 Hz, 1 channels, s16, 64 kb/s

卡住的输出:

1
2
3
4
5
6
ffprobe version 4.2.7-0ubuntu0.1 Copyright (c) 2007-2022 the FFmpeg developers
...
libavutil 56. 31.100 / 56. 31.100
libavcodec 58. 54.100 / 58. 54.100
libavformat 58. 29.100 / 58. 29.100
libavdevice 58. 8.100 / 58. 8.100

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
2
kubectl get mutatingwebhookconfigurations
kubectl get validatingwebhookconfigurations

找到与 Istio 相关的 webhook 配置。

2、删除与 Istio 相关的 webhook 配置

1
2
kubectl delete mutatingwebhookconfigurations <your-mutating-webhook-config-name>
kubectl delete validatingwebhookconfigurations <your-validating-webhook-config-name>

删除这些 webhook 配置之后,deployment 对应的 pods 就能够正常创建了。

  • 本文作者: 好好学习的郝
  • 原文链接: https://www.voidking.com/dev-k8s-problem/
  • 版权声明: 本文采用 BY-NC-SA 许可协议,转载请注明出处!源站会即时更新知识点并修正错误,欢迎访问~
  • 微信公众号同步更新,欢迎关注~