CoreDNS 作为 Kubernetes 集群的域名解析组件,它可以用来为集群内部提供域名解析服务,在 Kubernetes 1.11 中,CoreDNS 已经达到基于 DNS 服务发现的 General Availability (GA)。
在本文中,我将分享一些我经常用来排查 K8s CoreDNS 问题的技巧。
root@node1:~# kubectl get svc -n kube-system -l k8s-app=kube-dns
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
coredns ClusterIP 10.233.0.3 <none> 53/UDP,53/TCP,9153/TCP 81d
kube-dns ClusterIP 10.233.0.10 <none> 53/UDP,53/TCP,9153/TCP 76d
# kube-dns 和 coredns 使用不同svc 但是指向后端相同的pod和endpoint
root@node1:~# nc -vz 10.233.0.3 53
coredns.kube-system.svc.cluster.local [10.233.0.3] 53 (domain) open
root@node1:~# nc -vz 10.233.0.10 53
kube-dns.kube-system.svc.cluster.local [10.233.0.10] 53 (domain) open
如果您无法连接到DNS集群 ip,请检查您的iptables
或者ipvs
规则,这个取决于你kube-proxy
您应该会看到类似的规则:
#kube-proxy use iptables
-A KUBE-SERVICES -d 10.233.0.3/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-SVC-JD5MR3NA4I4DYORP
-A KUBE-SERVICES -d 10.233.0.3/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -m udp --dport 53 -j KUBE-SVC-TCOU7JCQXEZGVUNU
-A KUBE-SERVICES -d 10.233.0.3/32 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp cluster IP" -m tcp --dport 53 -j KUBE-SVC-ERIFXISQEP7F7OF4
#kube-proxy use ipvs
root@node1:~# ipvsadm -Ln | grep -A2 -E "10.233.0.3|10.233.0.10"
TCP 10.233.0.3:53 rr
-> 10.233.64.148:53 Masq 1 0 0
-> 10.233.65.103:53 Masq 1 0 0
TCP 10.233.0.3:9153 rr
-> 10.233.64.148:9153 Masq 1 0 0
-> 10.233.65.103:9153 Masq 1 0 0
TCP 10.233.0.10:53 rr
-> 10.233.64.148:53 Masq 1 0 0
-> 10.233.65.103:53 Masq 1 0 0
TCP 10.233.0.10:9153 rr
-> 10.233.64.148:9153 Masq 1 0 0
-> 10.233.65.103:9153 Masq 1 0 0
--
UDP 10.233.0.3:53 rr
-> 10.233.64.148:53 Masq 1 0 0
-> 10.233.65.103:53 Masq 1 0 0
UDP 10.233.0.10:53 rr
-> 10.233.64.148:53 Masq 1 0 0
-> 10.233.65.103:53 Masq 1 0 0
apiVersion: v1
kind: Pod
metadata:
name: busybox
namespace: default
spec:
containers:
- image: busybox:1.28
name: busybox
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
restartPolicy: Always
root@node1:~/ephemeral-containers# kubectl apply -f pod.yaml
root@node1:~/ephemeral-containers# kubectl get pod -n default
NAME READY STATUS RESTARTS AGE
busybox 0/1 ContainerCreating 0 7s
root@node1:~/ephemeral-containers#
root@node1:~/ephemeral-containers# kubectl get pod -n default
NAME READY STATUS RESTARTS AGE
busybox 1/1 Running 0 62s
root@node1:~/ephemeral-containers#
root@node1:~/ephemeral-containers# kubectl exec -it busybox -n default -- /bin/sh
/ #
/ # nslookup kubernetes.default
Server: 169.254.25.10
Address 1: 169.254.25.10
Name: kubernetes.default
Address 1: 10.233.0.1 kubernetes.default.svc.cluster.local
/ # nslookup google.com
Server: 169.254.25.10
Address 1: 169.254.25.10
Name: google.com
Address 1: 142.251.42.238 tsa01s11-in-f14.1e100.net
/ # cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 169.254.25.10
options ndots:5
您还可以尝试在control-plane
节点上运行这个 busybox
pod,以检查 DNS 服务是否在主节点上正常工作:
这个是时候需要添加tolerations
配置
...
tolerations:
- key: "node-role.kubernetes.io/control-plane"
effect: "NoSchedule"
nodeSelector:
kubernetes.io/hostname: <node_name>
您可以使用kubectl get pods命令来验证 DNS pod 是否正在运行:
root@node1:~/ephemeral-containers# kubectl get po -n kube-system -l k8s-app=kube-dns
NAME READY STATUS RESTARTS AGE
coredns-74d6c5659f-24q2g 1/1 Running 6 (8d ago) 76d
coredns-74d6c5659f-9vh7w 1/1 Running 3 (8d ago) 37d
如果所有CoreDNSPod 都运行良好,您可以使用kubectl logs
命令查看来自 DNS 容器的日志:
root@node1:~/ephemeral-containers# kubectl logs coredns-74d6c5659f-24q2g -n kube-system
.:53
[INFO] plugin/reload: Running configuration MD5 = 438180e1b4b5c9e20262965fba0c62ae
CoreDNS-1.8.6
linux/amd64, go1.17.1, 13a9191
注意log日志中的报错信息。
如果所有CoreDNSPod 都运行良好并且日志中没有错误,下一步就是检查 DNS 服务。
# 使用kubectl get svc 验证 DNS 服务是否启动正常
root@node1:~/ephemeral-containers# kubectl get svc -A -l k8s-app=kube-dns
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-system coredns ClusterIP 10.233.0.3 <none> 53/UDP,53/TCP,9153/TCP 81d
kube-system kube-dns ClusterIP 10.233.0.10 <none> 53/UDP,53/TCP,9153/TCP 76d
#检查 DNS 服务endpoints是否正常暴露
root@node1:~/ephemeral-containers# kubectl get ep -A -l k8s-app=kube-dns
NAMESPACE NAME ENDPOINTS AGE
kube-system coredns 10.233.64.148:53,10.233.65.103:53,10.233.64.148:53 + 3 more... 81d
kube-system kube-dns 10.233.64.148:53,10.233.65.103:53,10.233.64.148:53 + 3 more... 76d
CoreDNS RBAC的角色必须能List service
和endpoints
或者endpointslices
相关资源的权限。
要检查权限,您可以获得当前的 ClusterRole system:coredns
:
root@node1:~/ephemeral-containers# kubectl describe clusterrole system:coredns -n kube-system
Name: system:coredns
Labels: addonmanager.kubernetes.io/mode=Reconcile
kubernetes.io/bootstrapping=rbac-defaults
Annotations: <none>
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
nodes [] [] [get]
endpoints [] [] [list watch]
namespaces [] [] [list watch]
pods [] [] [list watch]
services [] [] [list watch]
endpointslices.discovery.k8s.io [] [] [list watch]
检查以上角色的权限,如果缺少任何权限,您可以编辑ClusterRole
以添加缺少的权限:
kubectl edit clusterrole system:coredns -n kube-system
考虑以下几种方式:
deployment
的副本数:
kubectl -n kube-system scale --replicas=10 deployment/coredns
cluster-proportional-autoscaler
以实现更精确的扩缩容(推荐)。如果 K8S 节点没有禁用 IPV6 的话,容器内进程请求 coredns 时的默认行为是同时发起双栈 IPV4 和 IPV6 解析,而通常我们只需要用到 IPV4,当容器请求某个域名时,coredns 解析不到 IPV6 记录,就会 forward 到 upstream 去解析,如果到 upstream 需要经过较长时间(比如跨公网,跨机房专线),就会拖慢整个解析流程的速度,业务层面就会感知 DNS 解析慢。
CoreDNS 有一个 template 的插件,可以用它来禁用 IPV6 的解析,只需要给 CoreDNS 加上如下的配置:
template ANY AAAA {
rcode NXDOMAIN
}
这个配置的含义是:给所有 IPV6 的解析请求都响应空记录,即无此域名的 IPV6 记录。
默认情况下,Kubernetes 集群中的域名解析往往需要经过多次请求才能解析到。查看 pod 内 的 /etc/resolv.conf 可以知道 ndots 选项默认为 5:
/ # cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 169.254.25.10
options ndots:5
/etc/resolv.conf
包含3条配置信息
什么是FQDN(完全限定名称)
FQDN(完全限定名称)是不会对其在本地搜索域进行解析的名称,并且在解析期间该名称将被视为绝对名称。按照惯例,如果以句号 (.)
结尾,则 CoreDNS认为名称是FQDN(完全限定名称)的,否则是非完全限定的。
比如:google.com.是FQDN(完全限定名称)的绝对名称,google.com不是绝对名称。
非FQDN(完全限定名称)解析是如何执行的
当应用程序连接到远程主机时需要解析主机名称,通常会通过系统调用执行 DNS 解析,例如getaddrinfo(). 如果名称不是完全限定的(不以.
结尾.),CoreDNS会首先尝试将名称解析为绝对名称,还是首先通过本地搜索域?这取决于ndots选项。
From the resolv.conf man:
ndots:n
sets a threshold for the number of dots which must appear in a name before an initial absolute query will be made. The default for n is 1, meaning that if there are any dots in a name, the name will be tried first as an absolute name before any search list elements are appended to it.
这意味着如果ndots设置为5并且主机名称中包含少于 5 个点.
,则系统调用将尝试首先依次遍历所有本地搜索域来解析它,如果没有成功 最后只会将其解析为绝对名称.
举个示例:
举个例子,在default命名空间
查询 kubernetes.default
这个 service:
kubernetes.default.default.svc.cluster.local
,查不到该域名。kubernetes.default.svc.cluster.local
,查不到该域名。kubernetes.default.cluster.local
,查询成功,返回响应的 ClusterIP。
可以看到一个简单的 service 域名解析需要经过 3 轮解析才能成功,集群中充斥着大量无用的 DNS 请求。
鉴于以上情况:业务发请求时尽量将 service 域名拼完整,这样就不会经过 search 拼接造成大量多余的 DNS 请求。
我们可以设置较小的 ndots,在 Pod 的 dnsConfig
中可以设置:
允许通过dnsConfigpod 属性对 Pod 的 DNS 设置进行更多控制。
除其他外,它允许自定义ndots特定 pod 的值,即:
apiVersion: v1
kind: Pod
metadata:
namespace: default
name: dns-example
spec:
containers:
- name: test
image: nginx
dnsConfig:
options:
- name: ndots
value: "2"
启用 CoreDNS 的 autopath 插件可以避免每次域名解析经过多次请求才能解析到,原理是 CoreDNS 智能识别拼接过 search 的 DNS 解析,直接响应 CNAME 并附上相应的 ClusterIP,一步到位,可以极大减少集群内 DNS 请求数量。 启用方法是修改 CoreDNS 配置:
kubectl -n kube-system edit configmap coredns
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods verified
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . /etc/resolv.conf {
prefer_udp
}
cache 30
autopath @kubernetes
loop
reload
loadbalance
}
pods verified
autopath @kubernetes
需要注意的是,启用 autopath 后,由于 coredns 需要 watch 所有的 pod,会增加 coredns 的内存消耗,根据情况适当调节 coredns 的 memory request 和 limit。
参考 k8s 官方文档 Using NodeLocal DNSCache in Kubernetes clusters
建议升级至Kernnel 5.9 以上,内核netfilter模块nf_conntrack用一个哈希表记录已建立的连接,包括其他机器到本机、本机到其他机器、本机内部的连接,如果连接进来比释放得快,把这个哈希表塞满了,新连接的数据包会被drop掉,确认这部分的配置不会成为瓶颈,如果云服务厂商有提供安全组、防火墙等功能,可以直接使用外部防火墙,此时可以直接撤掉机器内部的防火墙,也就没有这个问题了。亦或者如果不需要使用到涉及到状态链路相关或者NAT相关的应用,也可以直接设置不加载这个模块的。
net.ipv4.vs.conn_reuse_mode = 1
net.ipv4.vs.conntrack = 1
net.ipv4.vs.expire_nodest_conn = 1
sys.net.ipv4.vs.conn_reuse_mode值为 0,ipvs 中,系统使用conntrack跟踪连接,conntrack使用三元组(即源IP、源端口、协议)定位具体连接,当客户端产生大量短连接时,其本身可能与之前的端口复用,而 TCP 在conntrack中的超时时间达 900s,也就是说conntrack中记录的连接可能存在很长时间,客户端过来的新连接极有可能复用老连接,此时已经 TIME_WAIT 的老连接无法响应请求,从而导致超时。可通过设置sys.net.ipv4.vs.conn_reuse_mode的值为 1 来解决
内核参数net.ipv4.vs.expire_nodest_conn,用于控制连接的rs不可用时的处理。在开启时,如果后端rs不可用,会立即结束掉该连接,使客户端重新发起新的连接请求;否则将数据包silently drop,也就是DROP掉数据包但不结束连接,等待客户端的重试