使用 NodeLocal DNSCache提高clusterDNS性能和可靠性
由于一次权威DNS故障
,导致的DNS链式故障
, 从中发现的各类使用问题 以及 后续可能的影响!
结论:
权威DNS是所有内外域名解析依赖,是组织内核心服务; 权威DNS故障导致的依赖故障不能避免,但可以**
减少爆炸半径
**,减少故障影响面
- DNSCache本地缓存:提升dns解析性能,避免不必要forward, 减少权威DNS、CoreDNS请求压力(
常见:解决 CoreDNS 的5秒超时问题
) - kubernetes
搜索域Search
和搜索半径Ndots
优化,减少发起不必要的请求
复现步骤
整个Kubernetes
集群使用CoreDNS
, CoreDNS
通过ConfigMap
定义Corefile
和 /etc/resolv.conf
, 在权威DNS
故障发生时,整体表象不正常。
不正常现象:
- nsenter 发包只有A记录,未有AAAA记录
dongjiangdeMacBook-Pro:kubernetes $ kubectl exec -it busybox "/bin/sh"
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # ping google.com
PING google.com (142.251.43.14): 56 data bytes
64 bytes from 142.251.43.14: seq=0 ttl=37 time=9.450 ms
64 bytes from 142.251.43.14: seq=1 ttl=37 time=7.857 ms
^C
--- google.com ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 7.857/8.653/9.450 ms
dongjiangdeMacBook-Pro:kubernetes $ nsenter -t 3885 -n tcpdump -i eth0 udp port 53
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
10:09:11.918847 IP 10.96.0.10.domain > 10.244.2.202.38697: 11858 NXDomain*- 0/1/0 (147)
10:09:11.922468 IP 10.244.2.202.38697 > 10.96.0.10.domain: 15573+ AAAA? google.com.default.svc.cluster.local. (54) //线上无此条记录
10:09:11.923001 IP 10.96.0.10.domain > 10.244.2.202.38697: 15573 NXDomain*- 0/1/0 (147)
10:09:11.923248 IP 10.244.2.202.43230 > 10.96.0.10.domain: 62042+ A? google.com.svc.cluster.local. (46)
- A记录中带有权威DNS不可识别的搜索域
10:09:11.922468 IP 10.244.2.202.38697 > 10.96.0.10.domain: 15573+ AAAA? google.com.default.svc.cluster.local.**unkownlan** //出现自定义domain
原因分析
kubernetes的容器域名解析
先从kubernetes的容器内域名解析
开始分析:
dongjiangdeMacBook-Pro:kubernetes $ kubectl get cm coredns -n kube-system -o yaml
apiVersion: v1
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
hosts /etc/add-hosts/customer-hosts . {
reload 5s
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}
kind: ConfigMap
CoreDNS/kube-dns
的Corefile
使用正常!
dongjiangdeMacBook-Pro:kubernetes $ cat /etc/resolv.conf
....
nameserver 10.xxx.xxx.xxx
Node节点上/etc/resolv.conf
也正常!
dongjiangdeMacBook-Pro:kubernetes $ kubectl exec -it nginx-deployment-xxxx-xxxx "/bin/sh"
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
# cat /etc/resolv.conf
nameserver 10.xxx.xxx.xxx
//'这才是问题关键'
search default.svc.cluster.local svc.cluster.local cluster.local unkownlan
options ndots:5
Pod中的/etc/resolv.conf
才是问题关键!
POD
在集群中使用了默认的dnspolicy: ClusterFirst
, 全部会将A
和AAAA
记录forward
到上游DNS
.
并且coredns ConfigMap
中配置了hosts域名本地解析
,因此default.svc.cluster.local
的AAAA记录
,本地host命中,因此只有A记录
会forward
到上游.
然而,使用了ndots:5
,就会拼接google.com.default.svc.cluster.local.**unkownlan**
请求上游DNS
, 也解释了unkownlan
段的纯在
解决方案
集群DaemonSet添加NodeLocal DNSCache
NodeLocal DNSCache
通过在集群节点上运行一个 DaemonSet 来提高 clusterDNS 性能和可靠性。处于 ClusterFirst
的 DNS 模式下的 Pod 可以连接到 CoreDNS
的 serviceIP 进行 DNS 查询。通过 kube-proxy
组件添加的 iptables
规则将其转换为 CoreDNS
端点。通过在每个集群节点上运行 DNS 缓存
,NodeLocal DNSCache
可以缩短 DNS 查找的延迟时间、使 DNS 查找时间更加一致,以及减少发送到 CoreDNS
的 DNS 查询次数。
安装 NodeLocal DNSCache 也非常简单,直接获取官方的资源清单即可:
dongjiangdeMacBook-Pro:kubernetes $ wget https://github.com/kubernetes/kubernetes/raw/master/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml
该资源清单文件中包含几个变量,其中:
__PILLAR__DNS__SERVER__
:表示kube-dns
这个 Service 的 ClusterIP,可以通过命令kubectl get svc -n kube-system | grep kube-dns | awk '{ print $3 }'
获取__PILLAR__LOCAL__DNS__
:表示DNSCache
本地的 IP,默认为169.254.20.10
__PILLAR__DNS__DOMAIN__
:表示集群域,默认就是cluster.local
__PILLAR__UPSTREAM__SERVERS__
: 定制的Upstream Server
配置__PILLAR__CLUSTER__DNS__
: 值来源于kube-dns
的ConfigMap
dongjiangdeMacBook-Pro:kubernetes $ kubectl get svc -n kube-system | grep kube-dns | awk '{ print $3 }'
10.96.0.10
dongjiangdeMacBook-Pro:kubernetes $ sed 's/__PILLAR__DNS__SERVER__/10.96.0.10/g
s/__PILLAR__LOCAL__DNS__/169.254.20.10/g
s/__PILLAR__DNS__DOMAIN__/cluster.local/g' nodelocaldns.yaml |
kubectl apply -f -
serviceaccount/node-local-dns created
service/kube-dns-upstream created
configmap/node-local-dns created
daemonset.apps/node-local-dns created
service/node-local-dns created
dongjiangdeMacBook-Pro:kubernetes $ kubectl get pods -n kube-system | grep node-local-dns
node-local-dns-4n7fj 1/1 Running 0 1m54s
使用 DaemonSet 部署 node-local-dns 使用了 hostNetwork=true,会占用宿主机的 8080 端口,所以需要保证该端口未被占用。
如果 kube-proxy
组件使用的是 ipvs模式
的话我们还需要修改 kubelet 的 --cluster-dns
参数,将其指向 169.254.20.10
,Daemonset 会在每个节点创建一个网卡来绑这个 IP,Pod 向本节点这个 IP 发 DNS 请求,缓存没有命中
的时候才会再代理到上游集群 DNS 进行查询。
iptables模式
下 Pod 还是向原来的集群 DNS 请求,节点上有这个 IP 监听,会被本机拦截,再请求集群上游 DNS,所以不需要更改 --cluster-dns
参数。
最后修改 kubelet
的 cluster-dns
参数进行修改
dongjiangdeMacBook-Pro:kubernetes $ kubectl exec -it busybox "/bin/sh"
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
dongjiangdeMacBook-Pro:kubernetes $ sed -i 's/10.96.0.10/169.254.20.10/g' /var/lib/kubelet/config.yaml
dongjiangdeMacBook-Pro:kubernetes $ systemctl daemon-reload && systemctl restart kubelet
dongjiangdeMacBook-Pro:kubernetes $ kubectl exec -it busybox "/bin/sh"
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # cat /etc/resolv.conf
nameserver 169.254.20.10 #转移到本地DNS Cache上
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
在集群中运行 NodeLocal DNSCache 有如下几个好处:
- 如果本地没有
CoreDNS
实例,则具有最高 DNS QPS 的 Pod 可能必须到另一个节点进行解析,使用 NodeLocal DNSCache 后,拥有本地缓存将有助于改善延迟 - 跳过
iptables DNAT
和连接跟踪将有助于减少 conntrack 竞争并避免 UDP DNS 条目填满conntrack
表(常见的5s超时问题
就是这个原因造成的) - 从本地缓存代理到 kube-dns 服务的连接可以升级到 TCP,
TCP conntrack
条目将在连接关闭时被删除,而 UDP 条目必须超时(默认 nf_conntrack_udp_timeout 是 30 秒) - 将 DNS 查询从
UDP 升级到 TCP
将减少归因于丢弃的 UDP 数据包和 DNS 超时的尾部等待时间,通常长达 30 秒(3 次重试+ 10 秒超时)
优化搜索域Search
和 搜索半径Ndots
先了解一个概念:FQDN(Fully qualified domain name)
FQDN
是完整域名,一般来说,域名最终以.
结束表示是FQDN,例如google.com.
是FQDN
,但google.com
不是。对FQDN,操作系统会直接查询DNS server, 就要用到search和ndots了。
ndots
表示的是域名中必须出现的.
的个数 (需要1-15个
)。 如果域名中的.
的个数>=
ndots数,则该域名为一个FQDN,操作系统会直接查询
;如果域名中的.
的个数<
ndots数据,操作系统会在search搜索域
中进行查询。
举例:
# cat /etc/resolv.conf
nameserver 10.xxx.xxx.xxx
search default.svc.cluster.local svc.cluster.local cluster.local unkownlan
options ndots:5
其中前面3个搜索域
是由kubernetes注入
的,最后的unkownlan
是操作系统默认
的搜索域。
kubernetes搜索域
kubernetes搜索域 源码研习
var (
// The default dns opt strings.
defaultDNSOptions = []string{"ndots:5"} //默认就是5个.
)
//.....
func (c *Configurer) generateSearchesForDNSClusterFirst(hostSearch []string, pod *v1.Pod) []string {
if c.ClusterDomain == "" {
return hostSearch
}
// kubernetes默认注入 3个搜索域
nsSvcDomain := fmt.Sprintf("%s.svc.%s", pod.Namespace, c.ClusterDomain)
svcDomain := fmt.Sprintf("svc.%s", c.ClusterDomain)
clusterSearch := []string{nsSvcDomain, svcDomain, c.ClusterDomain}
return omitDuplicates(append(clusterSearch, hostSearch...))
}
func (c *Configurer) GetPodDNS(pod *v1.Pod) (*runtimeapi.DNSConfig, error) {
//...
case podDNSCluster:
if len(c.clusterDNS) != 0 {
dnsConfig.Servers = []string{}
for _, ip := range c.clusterDNS {
dnsConfig.Servers = append(dnsConfig.Servers, ip.String())
}
dnsConfig.Searches = c.generateSearchesForDNSClusterFirst(dnsConfig.Searches, pod)
dnsConfig.Options = defaultDNSOptions
break
}
//...
// 是否有自定义dnsConfig 和 CustomPodDNS,走自定义
if utilfeature.DefaultFeatureGate.Enabled(features.CustomPodDNS) && pod.Spec.DNSConfig != nil {
dnsConfig = appendDNSConfig(dnsConfig, pod.Spec.DNSConfig)
}
}
ndots修改
ndots是可以被修改的,可以通过pod.Spec.DNSConfig改写
apiVersion: v1
kind: Pod
metadata:
namespace: default
name: dns-example
spec:
containers:
- name: test
image: nginx
dnsConfig:
options:
- name: ndots
value: "2"
node节点resolv.conf 挂载到pod中
apiVersion: v1
kind: Pod
metadata:
namespace: default
name: dns-example
spec:
containers:
- name: test
image: nginx
volumeMounts:
- name: resolv
mountPath: /etc/resolv.conf
readOnly: true
volumes:
- name: resolv
hostPath:
path: /etc/resolv.conf #去掉`操作系统默认`的搜索域
「如果这篇文章对你有用,请随意打赏」
如果这篇文章对你有用,请随意打赏
使用微信扫描二维码完成支付