1.长连接服务:滚动更新导致负载不均
建议: client 侧设置连接过期时间或者达到一定请求数就重连
2.高并发服务:
ipvs 下,高并发 client 导致 “no route to host”
- client 高并发导致源端口重用,ipvs 模块匹配到旧连接(weight为0,但没真正剔除,目的是ipvs为了支持graceful termination)
- 旧连接的后端 pod 已经销毁,导致连接异常
建议:暂时没有完美解决方案,可通过 Pod 反亲和打散 client 避免流量集中规避
大流量的边缘节点源端口耗尽
- 边缘节点通过 NodePort 接收外界流量,发生大量 SNAT,导致源端口耗尽
连接队列溢出问题
syn 队列保存半连接状态的连接, accpet 队列保存已建立但还没有被应用处理的连接。
- ss –lnt 查看连接队列情况, Send-Q 表示 accept 队列大小, Recv-Q 表示 accept 队列中连接数量,通常为0,比 Send-Q 大于1表示溢出;
- 通常 somaxconn 决定 accpet 队列大小,默认为 128;
$ netstat -s | grep -E 'overflow|drop'
- 流量过大或高负载易导致应用处理不过来(accept过慢),从而队列溢出丢包;
- 使用 K8S sysctls 特性设置 somaxconn (此特性在 k8s v1.12 beta,默认开启);
- 使用 initContainers 设置 somaxconn (特权容器);
- nginx 在 listen() 时并没有指定 somaxconn 的值为队列大小,而是有自己的默认值:511
listen 80 default backlog=1024
- 通过在 nginx.conf 配置 backlog 可调整队列大小
慎用alpine
- alpine 比较精简,很多依赖库没有,或者跟其它镜像不一样,一些依赖动态链接库的程序容易报错,比如 go 的 cgo 调用;
- 底层库是 musl libc,域名解析行为跟 glibc 有差异,在 resolv.conf 配置的很多选项不支持;
- 没有内置 bash,运行 bash 脚本不兼容;
- 使用 lxcfs 提升容器资源可见性对 alpine 不生效(容器内看到的还是宿主机的cpu, memory使用情况),因为 alpine 内置 busybox 用系统调用获取信息而非读取 /proc;
DNS优化
search 与 ndots:
- 若域名中的 “.” 数量大于等于 ndots 数量,直接向 dns server 查询此域名;
- 否则就先依次遍历 search 并拼接到域名后缀查询;
- 默认 ndots 为 5,即便把 service 名称拼全(比如 test.default.svc.cluster.local,4个 “.”),也还是会有 4 查询,前 3 次是多余的;
优化:
- Pod dnsConfig 配置较小的 ndots;
- 集群内部通过 service 访问时拼全后缀(namespaces.svc.cluster.local)
DNS cache
优化:
- 改造业务,避免每次请求都查dns,比如 java 设置 networkaddress.cache.ttl ;
- 部署 NodeLocal DNS 作为本地 DNS 缓存
常见故障定位
1、查看状态码与详情;
$ kubectl describe pod {xxx} -n {namespaces}
使用 kubectl describe pod 查看容器上次退出状态码:
分析退出状态码:
- 1-128 表示进程主动退出 (只是约定),具体状态码含义取决于应用程序逻辑;有时主动退出也会是 255 状态码: 代码里使用类似 exit(-1) 时,-1 被自动转成 255,通常状态码为 1 和 255 是一般性错误,看不出具体含义,需要结合日志分析;
- 129-255 表示进程因外界中断信号退出,最常见的是 137,表示被 SIGKILL 杀死,可能是 Cgroup OOM,系统 OOM,存活检查失败或者被其它进程杀死导致。
2、查看日志;
使用 kubectl logs 查看容器日志 (-p 可看上次退出日志):
$ kubectl logs {podname} -n {namespaces}
3、查看资源配置;
使用 -o yaml 查看资源 yaml:
# pod
$ kubectl get pod {podname} -n {namespaces} -o yaml
# deploy
$ kubectl get deploy {deplyname} -n {namespaces} -o yaml
# svc
$ kubectl get svc {svcname} -n {namespaces} -o yaml
# daemonset
$ kubectl get daemonset {daemonsetname} -n {namespaces} -o yaml
4、进入容器抓包关键思路;
- kubectl describe pod 拿到容器 id;
- 通过 docker 或 crictl 查看容器 id 对应的进程号;
- 使用 nsenter –n --target 进入容器进程的 netns;
- 使用宿主机上的抓包工具进行抓包 (tcpdump)
# 拿到 pod 中 nginx 的容器 id
$ kubectl describe pod tcpbench-6484d4b457-847gl | grep -A10 "^Containers:" | grep -Eo 'docker://.*$' | head -n 1 | sed 's/docker:\/\/\(.*\)$/\1/'
49b4135534dae77ce5151c6c7db4d528f05b69b0c6f8b9dd037ec4e7043c113e
# 通过容器 id 拿到 nginx 进程 pid
$ docker inspect -f {{.State.Pid}} 49b4135534dae77ce5151c6c7db4d528f05b69b0c6f8b9dd037ec4e7043c113e
3985
# 进入 nginx 进程所在的 netns
$ nsenter -n -t 3985
$ tcpdump -i eth0 -nnnn -ttt port 24568
5、Pod 无法调度;
可能原因:
- 节点资源不够;
- 不满足 nodeSelector 与 affinity;
- Node 存在 Pod 没有容忍的污点;
- 有状态应用漂移找不到符合条件的同可用区节点;
- scheduler 不正常 (概率低)
6、Pod无法启动;
可能原因:
拉取镜像失败;
- 镜像仓库认证失败 (imagePullSecret 没有配置或配置有误);
- 镜像仓库协议与证书问题 (http 没有加 insecure-registry / https 自签没有加 CA) ;
- 镜像不存在 (镜像名配置有误);
- 镜像文件损坏;
- 镜像拉取超时 (镜像太大或在国内拉不到国外镜像);
- 程序启动配置不当导致进程主动退出 ;
- limit 没配置单位,内存默认用 byte,导致一直被 kill ;
- 依赖的 ConfigMap、Secret 或者 PVC 等不存在;
- 被管理员配置的 LimitRange, PodSecurityPolicy 限制住了;
挂载 Volume 失败;
- 命中 bug #68211;
- Pod 漂移没有正常解挂之前的磁盘;
- 磁盘爆满;
- 节点内存碎片化 ;
- controller-manager 异常;
- CNI 网络错误;
- 程序启动慢被存活检查 kill;
7、Pod 发生 Crash;
可能原因:
- cgroup OOM / 系统 OOM ;
- DNS 故障导致解析失败,业务进程报错退出;
- 高负载导致网络不通,业务进程报错退出;
- 存活检查探测失败,容器被 kill;
- 业务本身 bug;
- 容器进程被木马进程杀死
8、Pod 无法删除;
可能原因:
- 磁盘爆满;
- 存在 Finalizers;
- 资源被其它进程占用;
- 存在 “i” 文件属性;
- 运行时 bug;
9、Namespace 无法删除;
可能原因:
- Namespace 上存在 Finalizers (比如安装过 KubeSphere 或 Rancher,卸载后没清理 Finalizers);
- 注册过的 apiservice 状态异常 (比如安装过 prometheus-adapter,卸载后没清理 apiservice)
10、Service/Ingress 创建失败;
可能原因:
- LB 数量达到最大配额;
- 复用已有 LB 但上面存在规则或正在被其它 Service/Ingress 使用 ;
- 复用已有 LB 但 ID 写错;
- Secret 不正确;
- LB 内部错误 (概率低);
11、网络不通;
可能原因:
- 节点安全组没有放通容器网段、节点网段或者 NodePort 端口区间 (30000-32768);
- 外部服务防火墙没有放开容器网段 (如 CDB、自建 DNS);
- DNS 异常;
- 高负载;
- 进程没有监听端口;
12、节点状态异常;
可能原因:
- 节点高负载;
- 磁盘故障;
- 踩内核 bug;
- 运行时 hang 住或挂掉;
- 系统 OOM ;
- 节点 DNS 被修改;
案例: Pod 访问另一个集群的 apiserver 有延时
真相大白:
- kubectl 没有使用底层库查询域名,在 alpine 下底层库是 musl libc,但 kubectl 用的却是类似 glibc 的查询逻辑;
- 由于 alpine 没有 /etc/nsswitch.conf 文件,所以 kubectl 先查 dns,再查 hosts;
- 虽然 dns 解析没有用,但始终会先尝试解析 dns,当踩上内核 conntrack bug 时导致部分请求丢包,等待 5s 超时后 kubectl 继续重 试,造成 5s 延时现象;
解决方案:
- 换基础镜像,不用 alpine;
- 挂载 nsswitch.conf 文件 (hostPath或configMap);