k8s系列 – DNS关键问题
一篇调研,关于平滑迁入k8s时要考虑的DNS问题,点击下载。
正文
以下内容由word文档直接导入,虽然排版差劲一点,但是可以方便大家可以在线查阅。
K8s-DNS关键问题
背景
当前服务间调用是按域名实现的,每个节点基于/etc/hosts解析到云厂商的load balanace,进而转发到目标服务的具体节点,即没有内网DNS存在。
上k8s之后,肯定要面临部分服务在k8s集群内in-clsuter,部分服务在集群外out-cluster,面临几种情况:
1,out-cluster访问in-cluter:保持/etc/hosts走load balance代理到k8s ingress的nodePort,经过ingress转发到具体backend POD。
2,in-cluster访问in-cluster:显然k8s的service domain和原本服务的domain是不一样的,前者长这样my-nginx.default.svc.cluster.local,后者长这样nginx.company.com,所以如何保持原有domain方式访问是一个问题。
3,in-cluster访问out-cluster:需要让in-cluster的container拥有和out-cluster一样的/etc/hosts配置,而且还不能覆盖POD自带的hosts配置,才能保持按domain走到云上的load balance进入Out-cluster服务。
可见,问题就出在in-cluster上。
一旦业务搬到k8s上,
1,in-cluster到out-cluter的情况:如果继续采用传统配置/etc/hosts的方式将很难用,虽然可以挂ConfigMap到container的/etc/hosts,configmap更新可以实时反馈到container中,但这种做法也把container自身的hosts文件覆盖掉了,显然挂载hosts不是可行方案。
2,in-cluster到in-cluster的情况:目标service domain是k8s风格的,让业务改调用域名显然不合适,所以得让原本nginx.company.com这样的域名可以解析到目标service上。因为目标service以及POD都在k8s集群内动态分配,所以coredns就最适合做这个解析工作,它动态维护了service的IP地址,我们如果可以让coredns把nginx.company.com做cname到my-nginx.default.svc.cluster.local,那么coredn就可以最终返回my-nginx.default.svc.cluster.local这个service的IP,就可以用nginx.company.com域名访问到目标service了。
既然决定用Coredns作in-cluster的domain解析,那么也可以把out-cluster的domain直接配到coredns里,这样无论是in-cluster->in-cluster,还是in-cluster->out-cluster,都可以找coredns解析到IP,这个IP可能是外部的loadbalance,也可能是内部的service ip,做到了业务透明。
实战
一共要做2个事情。
- 把out-cluster的domain,配置到coredns直接解析为load balance IP,这样POD可以直接找coredns解析到外部IP。
- 把in-cluster的domain,配置到coredns做cname到service domain,这样POD可以直接找coredns解析到service IP。
创建测试service
先来创建一个in-cluster的service:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
app: my-nginx
replicas: 2
template:
metadata:
labels:
app: my-nginx
spec:
dnsPolicy: ClusterFirst
containers:
– name: my-nginx
image: nginx
ports:
– containerPort: 80
—
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
app: my-nginx
spec:
type: ClusterIP
clusterIP: None
ports:
– port: 80
protocol: TCP
selector:
app: my-nginx
注意,podTemplate里有一个dnsPolicy配置,其实POD默认就是clusterFirst,意思就是让container里的/etc/resolv.conf配置指向Coredns,即所有的dns请求发给coredns,这是k8s的默认行为。
Service域名解析
可以kubectl exec -it my-nginx-7448cc6b89-kqxx2 bash登录到my-nginx的一个POD里,看一下resolv.conf:
nameserver就是coredns的service Cluter IP,可以查看一下:
上述service叫做my-nginx,coredns会创建对应的k8s domain,叫什么呢?
my-nginx.default.svc.cluster.local,就是 服务名.命名空间.svc.cluster.local,其中svc就是资源类型是service的意思。
解析域名可以得到什么呢?先apt-get update && apt-get install dnsutils安装一下网络工具。
竟然可以解析service的名字,其实这就与resolv.conf有关了。
如果你请求的domain中.少于5个(my-nginx一个.也没有),那么就会逐个把search中的后缀拼到my-nginx上,然后去nameserver的地址请求解析IP。所以最终其实请求解析的域名是my-nginx.default.svc.cluster.local,如果拼接后都请求不到A记录,最后还会直接请求解析my-nginx。
如果domain包含的.大于等于5个,那么会先请求原始my-nginx这个domain,如果没有得到IP则会拼接server后缀再发起请求。
你也可以直接请求完整域名:my-nginx.default.svc.cluster.local:
同时,我们要注意到,k8s宿主机的resolv.conf是物理网络分配的,与container内是两码事:
所以,在k8s宿主机上,虽然可以与容器IP互通,但是解析k8s内的域名是不可能的,因为宿主机指定的nameserver是物理网络的:
当然可以指定去coredns的clusterip进行解析:
此时,因为宿主机的resolv.conf没有server后缀配置,所以需要指定完整的k8s domain,coredns那边保存只有完整域名的IP解析。
简称my-nginx只有在resolv.conf中有server后缀配置的情况下由请求方补全,再请求coredns才有效。
Service的headless模式
上述service指定了clusterIp为None,这叫headless service。
coredns解析这种service的时候,返回的是POD的IP列表,而不是service的cluster ip。
如果我们删掉service的clusterIP: None配置,那么coredns就只会返回cluster ip了,这里就不演示了(需要先删掉现在的service,再重新建)。
Headless service的好处,就是可以coredns可以直接返回pod的ip列表,不需要经过cluster ip的中转,性能更好,另外coredns每次返回的ip顺序不同,即支持round robin,后续会看到coredns的相关配置。
Resolve.conf解释
/etc/resolv.conf大家可以学习一下,这是linux基础:https://www.ichenfu.com/2018/10/09/resolv-conf-desc/
实际上,当我们解析少于5个.的domain时候,会经历多次server拼接,每次拼接后会去nameserver问一下:
所以请求量还是挺大的,网上有关于如何解决option ndots性能浪费问题的方法,思路是把ndots改成1,这样就总是首先按原始查询发起,之后再追加后缀:
配置out-cluster的domain
如果我们有一个some-api.company.com的服务在k8s集群外,然后in-cluster想按域名访问它,该如何实现呢?
公司目前没有内网dns,都是/etc/hosts解析,容器的hosts不方便配置。
解决思路就是在coredns里配置hosts解析,作为一个dns server,它支持这个功能。
首先,把coredns的configmap拖下来,coredns的配置文件是通过configmap配置的:
kubectl get configmap coredns -n kube-system -o yaml > coredns-configmap.yaml
保存到文件后,我们增加这么一段:
然后apply提交configmap:
kubectl apply -f coredns-configmap.yaml
k8s会重新mount configmap到各个容器中去,因为coredns的配置中有一个reload选项,意思就是coredns会定时重新加载corefile配置文件,所以稍微等一会解析就生效了:
解析得到了IP地址,这个IP是out-cluster的云load balance地址。
Corefile配置简介
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
这一段配置,就是说cluster.local后缀的domain,都是kubernetes内部域名,coredns会监听service的变化维护域名关系,所以cluster.local相关域名都在这里解析。
proxy . /etc/resolv.conf
这个proxy意思是如果在coredns中没有找到A记录,则去/etc/resolv.conf中的nameserver请求解析,而coredns容器中的/etc/resolv.conf是继承自宿主机的,所以实际效果就是如果不是k8s内部域名,就会去宿主机的dns服务器请求解析,并返回给coredns的请求者。
这是coredns deployment的截图,可见coredns的dnsPolicy是Default而不是clusterFirst,此时coredns container的resolv.conf将直接继承自宿主机。
Loop没具体看。
Reload是定期重新加载coredns配置的意思,我们更新configmap后k8s会立即重新挂载,配合coredns定期check配置文件的变化,就可以实现热加载了。
Loadbalance是随机打乱A记录的顺序,因为headlesss service会返回POD列表的IP,如果每次都同样的顺序,客户端可能就都访问第一个IP了,这就是DNS负载均衡的功能。
hosts {
123.59.59.33 some-api.company.com
fallthrough
}
这段就是指定hosts文件的作用,如果请求的是some-api.company.com的域名,那么直接返回IP。如果没有hosts命中的话,则继续执行其他的解析插件,所以hosts其实是一种插件: https://coredns.io/plugins/hosts/ 。
配置in-cluster的domain
之前说过,如果希望保持按原有域名nginx.company.com访问my-nginx这个service,显然就要提供nginx.company.com的解析能力。
有一种思路就是,请求coredns完成解析,这是POD的默认dnspolicy。
可以给coredns配置从nginx.company.com CNAME 到my-nginx.default.svc.cluster.local,这样就相当于请求的my-nginx的A记录。
所以,我们可以再编辑configmap:
新增一个配置文件company.db,相当于配置了company.com的权威DNS,目前提供了一个nginx.company.com到my-nginx.default.svc.cluster.local的CNAME。
File插件如果遇到company.com的域名后缀,就会用company.db作解析。
经过解析后得到了cname my-nginx.default.svc.cluster.local,并不是最终A记录,此时需要让coredns做递归解析my-nginx.default.svc.cluster.local,此时会命中kubernetes插件得到POD IP列表:
。
操作系统的DNS客户端,是不会自己递归解析cname的,必须由递归DNS sever去完成,我们生活中都是用运营商DNS,它们都是递归DNS,你问他domain,它直接给你A记录。
而自上而下的DNS拓扑,通过NS记录逐级向下查找:
最终走到权威DNS,得到A记录。
local dns就是递归DNS,它和权威DNS的关系:
我们这里coredns是个k8s内部的local dns,既能基于宿主机的ns做递归解析外部域名,也能本地解析内部domain或者hosts或者权威记录。
DNS配置的一些说明:
https://klionsec.github.io/2017/12/11/Dns-tips/
https://coredns.io/2017/05/08/custom-dns-entries-for-kubernetes/
其他
既然coredns承载了集群所有的dns解析工作,那么一定要给它足够多的实例和资源,可以修改coredns的deployment的replicas,resource limit/request。
Coredns默认是配置定时加载的,如果配置有错误是不影响服务的。 但是一旦操作deployment触发重新部署,就需要确保配置文件没有问题,否则coredns拉不起来,集群通讯就挂了。这和传统dns操作风险是一样的,例如百度前一阵的1小时故障。
另外,上述in-cluter直接将nginx.company.com解析到my-nginx甚至headless的my-nginx在性能上存在优势,避免了经过ingress,避免了流量先离开集群走到云load balance再走nodePort回到k8s集群的浪费,但也导致无法方便的监控nginx日志,incluter -> incluter成为了点对点通讯,这是需要考虑的问题。 也许可以让nginx.company.com CNAME到ingress上,或者干脆hosts到集群外的loadbalance,对运维方式改变更小,以牺牲性能为代价。
参考资料
Corefile配置:https://coredns.io/2017/07/23/corefile-explained/
DNS配置概念:https://ephen.me/2016/dns-rr/
Coredns file插件:https://github.com/coredns/coredns/tree/master/plugin/file
Coredns host插件:
https://coredns.io/plugins/hosts/
coredns loadbalance插件:
https://github.com/coredns/coredns/tree/master/plugin/loadbalance
coredns 定制dns entry:
https://coredns.io/2017/05/08/custom-dns-entries-for-kubernetes/
如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~

集群内部服务之间通过域名进行调用,除了修改coredns的cm,好像还需要把company.db文件挂上去
您好,我ingress controller使用的是traefik;
这个时候ingress配置的域名是无法在coredns中解析得到的;
我猜想是不是需要在coredns中把这类域名解析使用ingress controller 来处理?
请问我需要怎么配置coredns呢?
coredns中配置:应用域名 cname -> service name。
1
1
1