问题总结

在 Kubernetes 环境下,服务间访问遇到多个 DNS 和网络相关问题:

问题 1:Alpine 镜像 DNS 解析失败

服务使用 node:xxx-alpine 镜像,服务间访问报错:getaddrinfo EAI_AGAIN

问题 2:ClusterIP 访问超时

非 Alpine 镜像,使用 ClusterIP 访问频繁出现超时问题:connect ECONNRESETread ECONNRESET 以及 axios 的 timeout

问题 3:DNS 访问报错

非 Alpine 镜像,使用 DNS 访问报错:getaddrinfo ENOTFOUND

问题 4:CoreDNS I/O 超时

CoreDNS 报错:[ERROR] plugin/errors: 2 . NS: read udp 10.42.2.5:38764->183.60.82.98:53: i/o timeout

详细背景见:https://github.com/k3s-io/k3s/issues/5897

问题排查过程

问题 1:Alpine 镜像 DNS 解析问题

问题现象:

  1. 问题发生在流量高峰阶段
  2. 压测 TPS,200 线程仅 30 多每秒,吞吐量极差
  3. 是偶尔性的,200 线程 50 次仅发现十几条这样的报错日志

原因分析:

备注: 该原因分析,仅针对 node:alpine 镜像在 DNS 解析报错 EAI_AGAIN 的问题。在后文我发现换了 Debian 也有同样的错误,所以这个问题也不排除是 CoreDNS 出了问题

参考链接:https://github.com/nodejs/docker-node/issues/1030#issuecomment-956122581

我们所有的程序相互都是以 HTTP 协议进行接口交互,而 Kubernetes 提供的 Service 为每一组 Pod 提供了 DNS。

当 A 调用 B 时,调用方式如下:

1
http://B.default.svc.cluster.local:<port>

问题根源:

  1. 当 A 尝试与 B.default.svc.cluster.local 建立 HTTP 连接时,必须查找 IP
  2. Node 的 lib/dns.js 包装了 Node 的引擎函数,通过调用 getaddrinfo() 进行实际查找
  3. 后者取决于动态链接的 libc(Alpine 使用的并不是传统的 libc
  4. 关键问题:Alpine 的 musl libc 不完全支持 DNS 解析的所有功能,例如当使用多个 DNS 服务器或搜索命令时

解决方案:

参考链接中提到两种可能的解决方案:

  • 方案 A:不要使用不完全支持 libc 功能(例如 Alpine)的最小基础镜像,而是切换到 Debian、Ubuntu、Ubi 等 "slim" 变体
  • 方案 B:在构建 Node.js 引擎二进制文件时,将 libc 的 getaddrinfo() 静态链接到二进制文件中。这不会使用动态链接的 musl libc 代码

问题 2、3、4 排查与解决过程

0. 确认 DNS 策略

首先确认 Pod 的 dnsPolicy,为 ClusterFirst(默认)

1. 查看系统日志

发现有 error 提示,但是通过该 issue 来看,似乎问题不在这里:

1
2
Aug 15 21:51:12 k3s-prod-master3 k3s: W0815 21:51:12.632215    2021 watcher.go:220] watch chan error: etcdserver: mvcc: required revision has been compacted
Aug 15 21:51:18 k3s-prod-master3 k3s: W0815 21:51:18.566631 2021 watcher.go:220] watch chan error: etcdserver: mvcc: required revision has been compacted

2. 查看 CoreDNS 日志

通过日志,检查了 10.42.2.5 所在节点,服务器带宽较低:

1
2
3
4
kubectl logs coredns-7448499f4d-pswcf -n kube-system

# 输出
error: [ERROR] plugin/errors: 2 . NS: read udp 10.42.2.5:38764->183.60.82.98:53: i/o timeout

3. 手动测试连接

进入容器测试:

  • curl 集群内部接口 - 正常
  • curl 集群外部第三方服务接口 - 响应较慢

4. 查看 CoreDNS 监控

我们现在 CoreDNS 的资源及副本数如下,猜测是由于 CoreDNS 副本数不足、业务请求量高等情况导致的 CoreDNS 负载高才发生的报错(但 Prometheus 查并没啥异样...高峰期 0.38 左右的 CPU 和 20 多 MB 内存,并没到限制值):

1
2
3
4
5
6
7
8
9
10
11
12
spec:
replicas: 1
template:
spec:
containers:
- name: coredns
resources:
limits:
memory: 170Mi
requests:
cpu: 100m
memory: 70Mi

5. 进行 DNS 调试

参考:https://kubernetes.io/docs/tasks/administer-cluster/dns-debugging-resolution/

部署调试 Pod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Pod
metadata:
name: dnsutils
namespace: default
spec:
containers:
- name: dnsutils
image: e2eteam/jessie-dnsutils:1.0
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
restartPolicy: Always

测试 DNS 解析:

1
2
3
4
5
6
7
8
9
10
# 使用 dig 命令,status 为 NOERROR,循环看了 1000 次
kubectl exec -i -t dnsutils -- sh -c 'while true; do dig +all sapi-tenant-part.sopei-biz.svc.cluster.local google.com; sleep 1; done'

# 输出示例
; <<>> DiG 9.9.5-9+deb8u19-Debian <<>> +all sapi-tenant-part.sopei-biz.svc.cluster.local
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 36446
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 使用 nslookup 命令
kubectl exec -i -t dnsutils -- nslookup kubernetes.default

# 输出
Server: 10.96.0.10
Address: 10.96.0.10#53

Name: kubernetes.default.svc.cluster.local
Address: 10.96.0.1

# 测试业务服务
kubectl exec -it dnsutils -- nslookup sapi-part-category.sopei-biz.svc.cluster.local

# 输出
Server: 10.96.0.10
Address: 10.96.0.10#53

Name: sapi-part-category.sopei-biz.svc.cluster.local
Address: 10.43.xxx.xxx

解决方案

补充 1:临时切换到 ClusterIP

暂时将所有的 DNS 地址换成了 Service 的 ClusterIP,并且发现吞吐量是使用 DNS 时候的 5 倍之多,感觉就是 DNS 解析这里出了问题。

补充 2:资源调整无效

在将内存最小最大都调至 210 MB,再进行压测还是会有 getaddrinfo ENOTFOUND 这种问题,且内存占用资源还不到限制额度的七分之一。而且使用 ClusterIP 访问方式压测第 2 轮之后,发现流量过大会有如下问题:

服务间接性 timeout:connect ECONNRESETread ECONNRESET 以及服务本身 axios 报的 timeout

以下问题是因为重启了 CoreDNS 导致,忽略

发现了 CoreDNS 的 error 日志

1
2
3
4
5
6
.:53
[INFO] plugin/reload: Running configuration MD5 = 442b35f70385f5c97f2491a0ce8a27f6
CoreDNS-1.8.3
linux/amd64, go1.16, 4293992
[ERROR] plugin/errors: 2 4227623083530362207.3296446569099129492. HINFO: read udp 10.42.2.219:48721->xxx:53: i/o timeout
[ERROR] plugin/errors: 2 4227623083530362207.3296446569099129492. HINFO: read udp 10.42.2.219:58867->xxx:53: i/o timeout

补充 3:网络问题排查

按照官方的建议将版本升至 1.24.3,并加 egress-selector-mode: false 参数,再次进行压测,还是会有超时问题,所以想着是不是节点之间的网络有问题。

这里将所有的 agent 节点 Pod 迁移到 master,并停止调度,再次进行压测:

1
2
3
4
5
6
7
8
9
10
11
# 配置不可调度
echo k3s-prod-node1 k3s-prod-node2 k3s-prod-node3 | xargs -n 1 kubectl cordon

# 取消不可调度
echo k3s-prod-node1 k3s-prod-node2 k3s-prod-node3 | xargs -n 1 kubectl uncordon

# 驱逐已经运行的业务容器(排除 DaemonSet)
echo k3s-prod-node1 k3s-prod-node2 k3s-prod-node3 | xargs -n 1 kubectl drain --ignore-daemonsets

# 如果想删除 node 节点,则进行这个步骤
kubectl delete node k3s-prod-node1

测试结果:

  1. 使用 ClusterIP 的方式压测,timeout 问题消失,大胆断定之前服务间接性 timeout 问题是因为 node 节点和 master 节点之间 connect 存在互通问题(目前是 node 和 master 之间使用 nginx 进行负载,而 nginx 所在服务器资源占用比较严重)
  2. 使用 DNS 的方式进行压测,服务出现间接性的 getaddrinfo ENOTFOUND 错误

补充 4:NodeLocalCache 解决 DNS 解析问题

问题: 如果使用 DNS 访问,服务将偶现 getaddrinfo ENOTFOUND

肯定还是 DNS 解析问题,然后顺着 这个 StackOverflow 问题 去了解 NodeLocalCache,安装后并进行压测,此问题消失。

关于 NodeLocalCache,站内查看 NodeLocalCache 篇。

补充 5:开启 CoreDNS 日志调试

可以使用 log 插件将请求详情记录下日志,来分析异常的原因。但需要注意的是开启 log 插件调试可能会消耗 10% 左右的 CPU 资源,建议调试完之后关闭。

开启日志功能:

1
2
# 通过修改 kubernetes 的 configmap 中的 coredns 配置来开启日志功能,无需重启 coredns
kubectl edit configmaps -n kube-system coredns

配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 使用 log 插件,默认记录所有类型的日志,指定只记录错误相关的信息
log {
class denial error
}

# 只打印拒绝了的,并且是 example.org 下的请求
log example.org {
class denial
}

# forward . /etc/resolv.conf: 代表当访问外部域名时,
# 域名查询请求转移到预定义 DNS 服务器(/etc/resolv.conf 中指定的 nameserver),
# 请求在这几个 nameserver 间随机分发

查看上游 DNS 服务器:

1
2
3
cat /etc/resolv.conf
# nameserver 211.136.17.107
# nameserver 211.136.20.203

问题总结

问题 1:上游 DNS 服务器性能问题

通过错误日志 [ERROR] plugin/errors: 2 . NS: read udp 10.42.2.5:38764->183.60.82.98:53: i/o timeout,检查 10.42.2.5 所在节点,发现出网带宽较低。SSH 进入当前节点,进行 curl 外部第三方服务接口时发现响应也较慢。

问题 2:DNS 无意义请求

发现些外部域名末尾加了本不该加的 svc.cluster.local,如下:

1
2
[INFO] 10.42.3.0:41390 - 37462 "AAAA IN cmq-gz.public.tencenttdmq.com.cluster.local. tcp 61 false 65535" NXDOMAIN qr,aa,rd 154 0.000066155s "0"
[INFO] 10.42.1.0:37098 - 9090 "A IN api.weixin.qq.com.xxx.svc.cluster.local. tcp 63 false 65535" NXDOMAIN qr,aa,rd 156 0.000095249s "0"

原因:DNS 的无意义请求(其实这个本质上不是问题,但是产生了资源的浪费)

具体原因和解决方案见本站 关于 DNS 解析的一些认识

参考资料

  1. https://kubernetes.io/docs/tasks/administer-cluster/nodelocaldns/
  2. https://www.suse.com/support/kb/doc/?id=000020174
  3. 使用镜像(1.22.8):https://hub.docker.com/r/dyrnq/k8s-dns-node-cache/tags