去哪儿Kube DNS架构优化
1. 公司服务器节点(物理机或KVM等),在装机时均有安装q-dnsmasq,是在容器化之前就存在、用于缓解Localdns压力。
2. dnsmasq支持将所有 DNS 查询发送给所有配置的DNS服务器(--all-servers),可以很大提高 DNS 查询的性能以及可靠性。
3. 相比较更符合业务需求,能够确保快速且可靠地得到响应。
三、测试记录
配置展示
示例信息注明,如下信息均为虚拟
Cluster : abc
Cluster Domain:abc.k8s.xxx.qunar.com
1. q-dnsmasq
[user@node1 ~]$ cat /usr/lib/systemd/system/q-dnsmasq.service
...
ExecStart=/usr/local/sbin/dnsmasq -k -h -C /etc/q-dnsmasq.conf --dns-forward-max=3000 # dnsmasq默认值较小,这里做了调整
...
[user@node1 ~]$ cat /etc/q-dnsmasq.conf
...
all-servers
resolv-file=/etc/dnsmasq.d/q-ns.conf
servers-file=/etc/q-kubedns.servers
...
[user@node1 ~]$ cat /etc/q-kubedns.servers
# servers for abc
# 1. node coredns pod
server=/abc.k8s.xxx/${local_Node_IP}#53
server=/abc.k8s.xxx.qunar.com/${local_Node_IP}#53
# 2. kube-dns svc
server=/abc.k8s.xxx/${kube-dns_IP}#53
server=/abc.k8s.xxx.qunar.com/${kube-dns_IP}#53
# 3. svc 反解析配置
server=/xx.xx.10.in-addr.arpa/$(hostname -I | sed 's/ //g')#xx53
server=/xx.xx.10.in-addr.arpa/${kube-dns_IP}#53
server=/xx.xx.10.in-addr.arpa/$(hostname -I | sed 's/ //g')#xx53
server=/xx.xx.10.in-addr.arpa/${kube-dns_IP}#53
...... # 按实际环境配置全部添加
[user@node1 ~]$ cat /etc/dnsmasq.d/q-ns.conf
nameserver ${localdns-1_IP} # xxx localdns-1
nameserver ${localdns-2_IP} # xxx localdns-2
2.Node resolv.conf
[user@node1 ~]$ cat /etc/resolv.conf
search qunar.com
nameserver ${local_Node_IP} # node ip
nameserver ${localdns-1_IP} # xxx localdns-1
nameserver ${localdns-2_IP} # xxx localdns-2
3.Kubelet config
[user@node1 ~]$ cat /var/lib/kubelet/config.yaml
...
clusterDNS:
- ${local_Node_IP} # node ip
- ${kube-dns_IP} # kube-dns (coredns svc)
clusterDomain: abc.k8s.xxx.qunar.com
...
Pod 使用测试
测试场景
1. 将测试用pod调度至调整后的节点,并确认Dns policy: ClusterFirst。
2. 进入pod内,查看其/etc/resolv.conf是否符合预期(kubelet clusterDNS指定的2个dns地址)。
3. pod内部,使用dig工具进行测试,并在宿主节点查看q-dnsmasq解析日志,同时开始抓包查看走向,确保解析链路符合预期。
集群外部域名: www.qunar.com
集群内部域名:
kubernetes.default.svc.abc.k8s.xxx.qunar.com
集群内部域名(search domain):
kubernetes.default
kubernetes.default.svc
${svc} # 当前namespace下
4. 宿主节点,使用dig工具进行测试。
集群外部域名: www.qunar.com
集群内部域名:
kubernetes.default.svc.abc.k8s.xxx.qunar.com
测试过程
因信息敏感性与篇幅限制,实测记录相关信息不再贴出,简述整体过程;测试场景要覆盖如下:
Pod - 集群外部域名
Pod - 集群内部域名
Pod - 内外域名+search
Dnsmasq 缓存有效性
1. 配置确认
检查改造节点的如下配置文件是否修改符合预期。
/etc/q-dnsmasq.conf
/etc/dnsmasq.d/q-ns.conf
/etc/q-kubedns.server
2. Pod调度
确认测试的Pod通过nodeName或affinity等方式定向调度至了"改造后的目标节点"。
3. Pod内环境确认
确认/etc/resolv.conf配置文件是否符合预期。
4. 集群外部域名测试
4.1 域名解析
Pod内dig www.qunar.com确认是否可正常解析。
4.2 抓包记录
在"改造后的目标节点"通过抓包观察,确认链路为如下走向:
pod ip → Node q-dnsmasq → localdns allserver
4.3 解析日志
如果开启了dnsmasq的log-queries,也可以通过其日志,确认链路为如下走向:
pod → Node q-dnsmasq → localdns allserver
5. 集群内部域名测试
参照[ 4. 集群外部域名测试 ]过程,正常解析链路表现应当如下:
pod → Node q-dnsmasq → Coredns:xx53 & kube-dns:53
6. 缓存测试
30s内连续dig两次,关注第二次是否直接由q-dnsmasq返回结果即可。
7. 宿主节点测试
K8S内部域名
非完整fqdn - UnknownHost(宿主resolv.conf中,不存在K8S相关search domain)
完整fqdn - 正常解析(node → node q-dnsmasq → coredns:xx53 & kube-dns:53)
K8S外部域名
正常解析(node → Node q-dnsmasq → localdns allserver)
8. q-dnsmasq/coredns pod 故障测试
pod ip → node q-dnsmasq → 异常,转而请求 → kube-dns svc
node → node q-dnsmasq → 异常,转而请求localdns-1
在这里我们环境有些特殊,之前为了方便可直接使用svc(Qunar的容器网络是打通的,pod ip/svc均可直接通信)访问相关应用,我们在公司localdns服务器上做了forward,它会将*.abc.k8s.xxx.qunar.com全部转发至对应集群的kube-dns。
# pod resolv.conf
[root@testpod ~]# cat /etc/resolv.conf
search ops-test.svc.abc.k8s.xxx.qunar.com svc.abc.k8s.xxx.qunar.com abc.k8s.xxx.qunar.com qunar.com
nameserver ${local_Node_IP} # node ip
nameserver ${kube-dns_IP} # kube-dns (coredns svc)
options ndots:2 (可选优化,按实际场景需要调整)
# node resolv.conf
[root@l-k8snode ~]# cat /etc/resolv.conf
search qunar.com
nameserver ${local_Node_IP} # node ip
nameserver ${localdns-1_IP} # xxx localdns-1
nameserver ${localdns-2_IP} # xxx localdns-2
8.1 停止q-dnsmasq
8.2 pod内分别测试内、外部域名
四、上线方案
配置q-dnsmasq
# 删除原配置文件
sudo mv /etc/q-dnsmasq.conf /etc/q-kubedns.servers /tmp/
# 修改q-dnsmasq配置文件
sudo cat >> /etc/q-dnsmasq.conf << EOF
...
all-servers
resolv-file=/etc/dnsmasq.d/q-ns.conf
servers-file=/etc/q-kubedns.servers
...
EOF
# 修改q-dnsmasq server-file文件
sudo cat >> /etc/q-kubedns.servers << EOF
# servers for abc
# 1. coredns pod on node
server=/abc.k8s.xxx/$(hostname -I | sed 's/ //g')#xx53
server=/abc.k8s.xxx.qunar.com/$(hostname -I | sed 's/ //g')#xx53
# 2. kube-dns svc
server=/abc.k8s.xxx/${kube-dns_IP}#53
server=/abc.k8s.xxx.qunar.com/${kube-dns_IP}#53
# 3. svc 反解析配置
server=/xx.xx.10.in-addr.arpa/$(hostname -I | sed 's/ //g')#xx53
server=/xx.xx.10.in-addr.arpa/${kube-dns_IP}#53
server=/xx.xx.10.in-addr.arpa/$(hostname -I | sed 's/ //g')#xx53
server=/xx.xx.10.in-addr.arpa/${kube-dns_IP}#53
...... # 按实际环境配置全部添加
EOF
# 修改resolv.conf
sudo sed -i '/search .*/a\nameserver '"$(hostname -I | sed 's/ //g')"'' /etc/resolv.conf
# 重启q-dnsmasq
sudo systemctl restart q-dnsmasq
配置Kubelet
# 驱逐节点
sudo kubectl drain ${node} --ignore-daemonsets
# 修改kubelet config
sudo sed -i 's/- ${kube-dns_IP}/- '"$(hostname -I | sed 's/ //g')"'\n- ${kube-dns_IP}/' /var/lib/kubelet/config.yaml
# 重启kubelet服务
sudo systemctl restart kubelet
# 放开调度
sudo kubectl uncordon ${node}
截止此处,Pod的首选DNS已经指向为q-dnsmasq,调度到该节点上的Pod不会再去访问kube-svc进行解析。
配置kube-dns
1. 准备Coredns Daemonset
1.1 创建新的Coredns ConfigMap - coredns-2,端口配置为xx53
apiVersion: v1
data:
Corefile: |
.:xx53 {
errors
health {
lameduck 15s
}
ready
kubernetes abc.k8s.xxx.qunar.com in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}
kind: ConfigMap
metadata:
name: coredns-2
namespace: kube-system
1.2 创建Coredns DaemonSet
确认引用的configmap为coredns-2(改配置coredns启动为xx53端口)
确认启用了hostNetwork: true
也可以保守些,通过nodeName: xxx-node 指定仅部署1个进行测试,确认无问题再放开调度
apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
k8s-app: kube-dns
name: coredns
namespace: kube-system
spec:
...
hostNetwork: true
...
volumes:
- configMap:
defaultMode: 420
items:
- key: Corefile
path: Corefile
name: coredns-2
name: config-volume
...
2. 测试加入Coredns Daemonset实例后的kube-dns svc是否正常
# 确保coredns daemonset加进来后不会影响当前解析
1. 循环、持续探测关注解析结果
# 测试首选q-dnsmasq的新架构使用是否正常
1. 调度Pod至存在coredns daemonset的节点
2. 进入pod内进行dig、curl等测试K8S内SVC是否可正常解析(pod → q-dnsmasq → coredns daemonset pod / kube-dns svc)
2.1 小结-观察确认
变更过程中,中间态的必要理解:
1. kube-dns svc下的endpoints列表,会新增coredns daemonset的实例(为hostip)。
2. kube-dns svc的port为53,转发endpoints的目标端口也为53(这里理解下,新增进去的coredns daemonset端口为xx53,理论上来讲其实转发到coredns daemosnet 53端口时,因coredns daemonset pod未监听53端口,应当是要出问题的;但是并不会。因coredns daemonset为hostip,虽然coredns daemonset pod未监听53端口,但所在节点上的q-dnsmasq是监听着53端口的)。
所以,这里的新增coredns daemonset pod加入kube-dns svc的endpoints列表后不受影响的详细,如下图所示:
kube-dns svc: ${kube-dns-svc_IP]
kube-dns endpoints: pod-1_ip:53,pod-2_ip:53,...,host-1_ip:53,host-2_ip:53,...
3. 将Coredns Daemonset投入使用
缩容cordons deployment
下线coredns deployment
缩容deployment creodns 副本数为0,将其进行下线;
缩容时,kube-dns svc会自动将deployment corends实例从endpoints移除,不会再去请求到这些实例上,只会请求daemonset coredns:53(实际上为q-dnsmasq),如此完成Coredns Deployment → Coredns DaemonSet的转换。
$ sudo kubectl scale --replicas=0 deployment -n kube-system coredns
修改kube-svc service
kube-dns svc tcp port存在不可修改情况,需要recreate才能成功修改生效。
在kube-dns svc修改完成后,此时,直接访问kube-dns svc的链路,将会由变更时中间态(pod → kube-dns svc:53 → q-dnsmasq:53)修正为 pod → kube-dns svc:53 → coredns daemonset pod:xx53(不再是q-dnsmasq)。
至此,coredns deployment将彻底完成下线,整个架构转换彻底完成。
$ sudo kubectl edit svc -n kube-system kube-dns
# 修改service的tartPort端口为xx53
ports:
- name: dns
port: 53
protocol: UDP
targetPort: xx53
- name: dns-tcp
port: 53
protocol: TCP
targetPort: xx53
- name: metrics
port: 9153
protocol: TCP
targetPort: 9153
4. 调整Coredns配置
在如上已经完成了变更。该步骤可忽略,此处为为了方便统一进行的额外调整。
#修改configmap coredns,将其端口配置由53更改为xx53
$ kubectl edit configmap coredns -n kube-system
apiVersion: v1
data:
Corefile: |
.:xx53 {
...
#修改daemosnet coredns,将其引用的configmap由coredns-2更改为coredns
$ kubectl edit ds -n kube-system coredns
...
volumes:
- configMap:
defaultMode: 420
items:
- key: Corefile
path: Corefile
name: coredns
name: config-volume
...
#逐个delete daemonset coredns pod完成手动滚动更新
$ kubectl delete pod -n kube-system ${coredns_pod}
#全部完成更新后,删除configmap coredns-2
$ kubectl delete configmap -n kube-system coredns-2
五、总结与后续规划
文章介绍了在Qunar的业务需求场景下,kube-dns调整方案的优势以及原生k8s dns方案的不足,并提供可实践、可复刻的方案,以及测试验收、无损上线方式。
希望对在大规模场景下kube-dns,对解析有较大依赖性的团队能够提供一种优化思路或优化方案候选项。
另外,Qunar是有使用到公有云作为弹性资源的,DNS解析相关问题在云上也会存在,因公有云virtual-kubelet并不允许Daemonset调度,所以文中方案对公有云并不适用,但我们对云上环境也已有相关解决思路,在Q3会逐步验证和实施:
封装q-dnsmasq镜像,由初始化脚本读取Pod Annotation获取自己所在机房,来完成各机房弹云后的转发差异化配置。
在调度至云上时,通过对调度至公有云节点的Pod进行Sidecar注入dnsmasq容器,以及dnsConfig配置改写云上Pod的首选DNS为127.0.0.1、次选对应集群的kube-dns svc来实现。