背景:最近公司有个需求,要在POD应用容器里面能够访问到一些外部域名,这些域名都在一台自建的DNS服务器上做了解析绑定。如果直接在Pod容器里的/etc/hosts文件中设置域名解析,或修改/etc/resolv.conf中的nameserver指向这台DNS服务器,这两种方式都不太方便管理,因为每次重新创建Pod或后续还有新Pod要创建时,都需要重新设置一遍,这时就需要有一个统一的地方设置域名解析,不需要到每个Pod里设置,这个地方就是coredns。
CoreDNS是一个DNS解析的组件,作为集群内的DNS服务器,为集群内部提供域名解析服务。比如当集群内Pod要访问某个service name时,就会用到coredns的解析服务。\
DNS解析原理:
当部署好coredns后,集群中会有一个名为kube-dns的service,以及这个service下还有两个名为CoreDNS的Pod后端。
[root@localhost ~]# kubectl get svc kube-dns -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.43.0.10 <none> 53/UDP,53/TCP,9153/TCP 14d
[root@localhost ~]# kubectl get deployment coredns -n kube-system
NAME READY UP-TO-DATE AVAILABLE AGE
coredns 2/2 2 2 14d
当集群中的Pod,需要访问集群内的nginx服务,会经过以下两个步骤:
.------> Pod1(nginx1)
(2) /
.------------------> Service(nginx)---------> Pod2(nginx2)
|
Pod(Client)
|
'------------------> Service(kube-dns) ---------> Pod(coredns)
(1) UDP
(1)Pod Client要访问nginx服务时,会先请求本地DNS配置文件(/etc/resolv.conf)中指向的DNS服务器,然后DNS服务器再将nginx service解析后的IP返回给Pod。
(2)Pod Client获取到nginx服务的IP后,会再次直接向这个IP发起请求,请求最终会经过Nginx Service转发到后端POD容器上(nginx1和nginx2)
Pod的DNS配置策略:
每个Pod所使用的DNS策略,是通过pod.spec.dnsPolicy字段设置的,共有4种DNS策略:
- ClusterFirst:默认策略,表示使用集群内部的CoreDNS来做域名解析,Pod内/etc/resolv.conf文件中配置的nameserver是集群的DNS服务器,即kube-dns的地址。
- Default:Pod直接继承集群node节点的域名解析配置,也就是,Pod会直接使用宿主机上的/etc/resolv.conf文件内容。
- None:忽略k8s集群环境中的DNS设置,Pod会使用其dnsConfig字段所提供的DNS配置,dnsConfig字段的内容要在创建Pod时手动设置好。
- ClusterFirstWithHostNet:如果Pod的hostNetwork字段设置为true,则表示Pod与宿主机共用同一个网络命名空间,此时Pod的DNS策略默认使用的是Default,不能访问集群内的服务。若希望Pod在host网络模式下还能访问集群内的服务,可以将dnsPolicy设置成ClusterFirstWithHostNet
场景1:使用k8s集群提供的CoreDNS来做域名解析
将dnsPolicy字段设置为ClusterFirst,或者省略dnsPolicy字段,默认也是ClusterFirst。
nd: Pod
metadata:
name: nginx-demo
namespace: default
spec:
containers:
- image: nginx:1.7.9
name: nginx
ports:
- containerPort: 80
protocol: TCP
dnsPolicy: ClusterFirst #该字段要设置为ClusterFirst(若省略该字段默认也是ClusterFirst)
创建好Pod后,查看Pod内/etc/resolv.conf文件内容,nameserver指向的是kube-dns service地址,也就是使用CoreDNS来做域名解析
kubectl exec nginx-demo cat /etc/resolv.conf
nameserver 172.17.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
场景2:Pod层面自定义DNS配置
将dnsPolicy设置为None,同时添加dnsConfig字段手动配置DNS解析内容。
apiVersion: v1
kind: Pod
metadata:
name: nginx-demo
namespace: mytest
spec:
containers:
- image: nginx:1.7.9
name: nginx
ports:
- containerPort: 80
protocol: TCP
dnsPolicy: None
dnsConfig:
nameservers: ["192.168.1.1","192.168.2.2"] #最多可指定3个IP,当Pod的dnsPolicy设置为None时,列表必须至少包含一个IP地址
searches: #Pod中主机名查找的DNS搜索域列表
- default.svc.cluster.local
- svc.cluster.local
- cluster.local
options:
- name: ndots
value: "5"
创建好Pod后,查看Pod内/etc/resolv.conf文件内容,nameserver是dnsConfig字段中配置的IP。
kubectl exec nginx-demo cat /etc/resolv.conf
nameserver 192.168.1.1
nameserver 192.168.2.2
search mytest.svc.cluster.local svc.cluster.local cluster.local default.svc.cluster.local
options ndots:5
如果dnsPolicy字段不是设置为None,而是ClusterFirst,则coredns的地址也会加入到Pod的/etc/resolv.conf文件中,该文件内容会变成如下:
kubectl exec nginx-demo cat /etc/resolv.conf
nameserver 172.17.0.10
nameserver 192.168.1.1
nameserver 192.168.2.2
search mytest.svc.cluster.local svc.cluster.local cluster.local default.svc.cluster.local
options ndots:5
场景3:继承node节上的DNS配置
将dnsPolicy字段设置为Default。Pod会继承宿主机上的/etc/resolv.conf内容,如果宿主机上/etc/resolv.conf文件中没有定义指向coredns地址的nameserver,则Pod容器内是无法访问到service name的。
apiVersion: v1
kind: Pod
metadata:
name: nginx-demo
namespace: default
spec:
containers:
- image: nginx:1.7.9
name: nginx
ports:
- containerPort: 80
protocol: TCP
dnsPolicy: Default
但注意,这个要先在宿主机上配置好/etc/resolv.conf的内容,如下:
cat /etc/resolv.conf
options timeout:2 attempts:3 rotate single-request-reopen
; generated by /usr/sbin/dhclient-script
nameserver 192.168.10.156
nameserver 192.168.10.157
nameserver 10.100.1.1
创建好Pod后,查看Pod内/etc/resolv.conf文件内容,跟宿主机上的是一样的,说明从宿主机上继承过来了。
kubectl exec nginx-demo cat /etc/resolv.conf
nameserver 192.168.10.156
nameserver 192.168.10.157
nameserver 10.100.1.1
options timeout:2 attempts:3 rotate single-request-reopen
场景4:Pod在HostNetwork网络模式下访问集群内的服务
如果Pod的hostNetwork字段设置为true,也就是Pod与宿主机共享同一个网络命名空间,则此时Pod的dnsPolicy默认策略是Default,即Pod会继承宿主机上的resolv.conf文件内容。若宿主机上的resolv.conf文件中没有配置coredns地址作为域名解析服务器,则此时的Pod容器内是无法访问集群内部其他的service name的,所以要访问集群内的service name,就要将dnsPolicy字段设置为ClusterFirstWithHostNet。
apiVersion: v1
kind: Pod
metadata:
name: nginx-demo
namespace: default
spec:
containers:
- image: nginx:1.7.9
name: nginx
ports:
- containerPort: 80
protocol: TCP
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
创建好Pod后,查看Pod内/etc/resolv.conf文件内容,nameserver指向的还是coredns的地址
kubectl exec nginx-demo cat /etc/resolv.conf
nameserver 172.17.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
如果不设置dnsPolicy字段,默认使用的策略是Default,则resolv.conf里就是宿主机上的内容,这时Pod内无法解析service name的。
kubectl exec nginx-demo cat /etc/resolv.conf
nameserver 192.168.10.156
nameserver 192.168.10.157
nameserver 10.100.1.1
options timeout:2 attempts:3 rotate single-request-reopen
集群CoreDNS配置:
coredns是k8s集群的一个DNS解析组件,不仅为集群内部的Pod应用提供域名解析服务,还支持集群内部的自定义服务域名、集群外部域名等。
常见的用法是,可以在coredns配置文件中,添加一些自定义DNS解析配置,它会对集群内部的所有Pod生效(前提是这个Pod的nameserver指向的是coredns)。
比如说,集群内部有很多个Pod应用,这个集群中的所有Pod应用都要能够访问到一个集群外部的域名a.test.com,如果此时逐个登录到Pod容器里修改/etc/hosts文件,添加一条a.test.com域名解析记录,要是真的这样做,估计会累死,另外如果后面还有源源不断的Pod要创建,每个Pod创建时又要配置好这个域名的解析,这样做实在太麻烦了。这时我们就可以通过coredns,修改它的corefile配置文件,添加一条a.test.com域名解析记录,这样只要Pod的nameserver指向的是coredns,这个Pod就不用做任何修改,就能访问到这个a.test.com域名。
比如说,现在集群中的Pod要访问的不只是a.test.com这一个域名,还有b.test.com、c.test.com 等很多外部域名要访问,而这些域名都在10.10.10.1这台外部的nameserver上做了解析绑定。那为了让集群内的Pod能访问到这些域名,就算在corefile里逐个添加这些域名的解析记录,也会很麻烦。这时可以在corefile里面,直接配置成10.10.10.1这个nameserver地址,供集群中的所有Pod使用,只要Pod的nameserver指向的是coredns,就能通过这个nameserver访问到集群外部的这些域名。
详细用法可参考官方手册:CoreDNS: DNS and Service Discovery
CoreDNS的默认配置:
如果k8s集群安装了coredns插件,在命名空间kube-system下,会有一个CoreDNS配置项。
[root@localhost ~]# kubectl -n kube-system get configmaps coredns
NAME DATA AGE
coredns 1 14d
[root@localhost ~]# kubectl -n kube-system get configmaps coredns -o yaml
apiVersion: v1
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes jettech.com.cn in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . "/etc/resolv.conf"
cache 30
loop
reload
loadbalance
}
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"Corefile":".:53 {\n errors\n health {\n lameduck 5s\n }\n ready\n kubernetes jettech.com.cn in-addr.arpa ip6.arpa {\n pods insecure\n fallthrough in-addr.arpa ip6.arpa\n }\n prometheus :9153\n forward . \"/etc/resolv.conf\"\n cache 30\n loop\n reload\n loadbalance\n}\n"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"coredns","namespace":"kube-system"}}
creationTimestamp: "2022-10-25T02:08:19Z"
name: coredns
namespace: kube-system
resourceVersion: "409"
selfLink: /api/v1/namespaces/kube-system/configmaps/coredns
uid: a823fe6f-67aa-47c8-b8a5-fcc2a8185920
配置字段说明:
error:错误信息到标准输出。
health:CoreDNS自身健康状态报告,默认监听端口8080,一般用来做健康检查。可通过http://localhost:8080/health获取健康状态。
ready:CoreDNS插件状态报告,默认监听端口8181,一般用来做可读性检查。可通过http://localhost:8181/ready读取状态。
kubernetes:CoreDNS kubernetes插件,提供集群内服务解析能力。
prometheus:CoreDNS自身metrics数据接口。可过http://localhost:9153/metrics获取prometheus格式的监控数据。
forward(或proxy):将域名查询请求转到预定义的DNS服务器。
cache:DNS缓存。
loop:环路检测,如果检测到环路,则停止CoreDNS。
reload:允许自动重新加载已更改的Corefile。编辑ConfigMap配置后,最好等几分钟以使更改生效。
loadbalance:循环负载均衡器
CoreDNS的扩展配置:
场景1:特定域名使用指定的自定义DNS服务器
比如a.test.com域名需要经过自建DNS服务器(IP为10.10.10.1)进行解析,可为域名配置一个单独的服务块,示例配置如下:
a.test.com:53 {
errors
cache 30
forward . 10.10.10.1
}
也可以匹配指定后缀为test.com的域名,都经过自建DNS服务器进行解析,如下:
test.com:53 {
errors
cache 30
forward . 10.10.10.1
}
完整配置如下:
Corefile: |-
.:53 {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
proxy . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
test.com:53 {
errors
cache 30
forward . 10.101.1.3
}
场景2:所有外部域名都使用自建DNS服务器
如果要访问的外部域名没有统一后缀,也可以直接指定自建的DNS服务器(IP为10.10.10.1、10.10.10.2).
Corefile: |-
.:53 {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . 10.10.10.1 10.10.10.2 #直接指定DNS服务器IP,可以有多个
cache 30
loop
reload
loadbalance
}
场景3:自定义hosts
也可以在coredns上配置ip与域名的绑定关系,比如a.test.com指定IP为192.168.1.100。
Corefile: |-
.:53 {
errors
health
hosts { #设置hosts绑定
192.168.1.100 a.test.com
fallthrough
}
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
proxy . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
场景4:集群外部访问集群内服务
集群node节点上的进程,如果希望能够访问到集群内的服务,可以修改node节点上的/etc/resolv.conf文件中的nameserver配置,将nameserver指定为集群kube-dns的ClusterIP来达到目的。
K8s hosts 解析 HostAliases
在 k8s 中,容器内部的 hosts 解析是不会继承宿主机的 host 解析的(与宿主机不相干),常规情况我们在这个应用体系内都有公共的 DNS 服务,DNS 服务是跨主机和容器共享的。
只是有时候在没有公共DNS解析服务的情况下,而容器中的程序又需要通过本地域名的方式去请求接口。这就需要我们手工配置 hosts 映射,这个配置如图来看在宿主机上配置是不可行的,那么在 k8s 的应用场景中,它为我们提供了可以为 pod 配置 hosts 解析的方式,如题所述就是使用 HostAliases。
[root@localhost docker]# cat jettopro-ui-poc-env.yaml
apiVersion: v1
kind: Service
metadata:
labels: {name: jettopro-ui}
name: jettopro-ui
namespace: jettopro-poc
spec:
ports:
- {name: t30001, nodePort: 30001, port: 80, protocol: TCP, targetPort: t80}
#- {name: t80, port: 80, protocol: TCP, targetPort: t80}
selector: {name: jettopro-ui}
type: NodePort
#type: ClusterIP
#clusterIP: None
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: jettopro-poc
name: jettopro-ui
labels: {name: jettopro-ui}
spec:
replicas: 1
selector:
matchLabels: {name: jettopro-ui}
template:
metadata:
name: jettopro-ui
labels: {name: jettopro-ui}
spec:
hostAliases:
- ip: "172.16.10.4"
hostnames:
- "jettoui-end.jettech.com.cn"
- "jettoui-basic.jettech.com.cn"
- "jettoui-zuul.jettech.com.cn"
- ip: "172.16.10.87"
hostnames:
- "jettoui-end.jettech.com.cn"
- "jettoui-basic.jettech.com.cn"
- "jettoui-zuul.jettech.com.cn"
- ip: "172.16.10.21"
hostnames:
- "jettoui-end.jettech.com.cn"
- "jettoui-basic.jettech.com.cn"
- "jettoui-zuul.jettech.com.cn"
containers:
- name: jettopro-ui
image: harbor.jettech.com/jettomanager/jettopro-ui:v7.3.0
#command: ["/bin/sh","-c"," sh /opt/docker-entrypoint.sh&&while true;do sleep 1000;done"]
#command: ["/bin/sh","-c","while true;do sleep 1000;done"]
#command: ["/bin/sh","-c","sh /opt/docker-entrypoint.sh&&nginx -g 'daemon off';"]
env:
- {name: JETTOUIEND_IP, value: 'jettoui-end.jettech.com.cn'}
#- {name: JETTOUIEND_IP, value: 'jettopro-ui.jettopro-poc.svc.jettech.com.cn'}
- {name: JETTOUIEND_PORT, value: '80'}
- {name: JETTOWEED_IP, value: '172.16.10.4'}
- {name: JETTOWEED_PORT_10001, value: '10001'}
- {name: JETTOWEED_PORT_9333, value: '9333'}
- {name: JETTOFILE_IP, value: 'localhost'}
- {name: JETTOFILE_PORT, value: '9999'}
- {name: JETTOMANAGER_ZUUL_IP, value: 'jettoui-zuul.jettech.com.cn'}
- {name: JETTOMANAGER_ZUUL_PORT, value: '80'}
- {name: JETTOUIBE_IP, value: 'jettech.com.cn'}
- {name: JETTOUIBE_PORT, value: '80'}
#- {name: JETTOUIBE_PORT, value: '30001'}
- {name: JETTAPIIBE_IP, value: ''}
- {name: JETTAPIIBE_PORT, value: ''}
- {name: JETTDAIBE_IP, value: ''}
- {name: JETTDAIBE_PORT, value: ''}
- {name: JETTOMANAGER_ZUUL_IP, value: 'jettoui-zuul.jettech.com.cn'}
- {name: JETTOMANAGER_ZUUL_PORT, value: '80'}
#- {name: JETTOMANAGER_ZUUL_PORT, value: '30003'}
- {name: JETTOMANAGEREND_IP, value: ''}
- {name: JETTOMANAGEREND_PORT, value: ''}
- {name: JETTOAPIEND_IP, value: ''}
- {name: JETTOAPIEND_PORT, value: ''}
securityContext:
privileged: true
ports:
- {containerPort: 80, name: t80, protocol: TCP}
#hostNetwork: true
#dnsPolicy: ClusterFirstWithHostNet
#dnsPolicy: None #None ,ClusterFirst ,Default ,ClusterFirstWithHostNet
#dnsConfig:
# nameservers: ["172.16.10.4","172.16.10.87","172.16.10.21"]
# searches:
# - default.svc.jettech.com.cn
# - svc.jettech.com.cn
# - jettoui-end.jettech.com.cn
# - jettech.com.cn
# options:
# - name: ndots
# value: "5"
#hostNetwork: true
restartPolicy: Always #Never