摘要
作为服务发现机制的基本功能,在集群内需要能够通过服务名对服务进行访问,这就需要一个集群范围内的DNS服务来完成从服务名到ClusterIP的解析。
本文介绍k8s集群中,默认的CoreDNS配置,域名解析过程分析,解释服务发现的机制。
内容
从Kubernetes 1.11版本开始,Kubernetes集群的DNS服务由CoreDNS提供。CoreDNS是CNCF基金会的一个项目,是用Go语言实现的高性能、插件式、易扩展的DNS服务端。CoreDNS解决了KubeDNS的一些问题,例如dnsmasq的安全漏洞、externalName不能使用stubDomains设置,等等。
CoreDNS支持自定义DNS记录及配置upstream DNS Server,可以统一管理Kubernetes基于服务的内部DNS和数据中心的物理DNS。
CoreDNS没有使用多个容器的架构,只用一个容器便实现了KubeDNS内3个容器的全部功能。
第9课 Kubernetes之服务发现,CoreDNS配置和域名解析过程
图4.5展现了CoreDNS的总体架构
(1)查看CoreDNS信息
k8s的v1.20.5版本在集群启动时,已经启动了coreDNS域名服务。
在部署CoreDNS应用前,至少需要创建一个ConfigMap、一个Deployment和一个Service共3个资源对象。ConfigMap“coredns”主要设置CoreDNS的主配置文件Corefile的内容,其中可以定义各种域名的解析方式和使用的插件。
相关的配置可以查看。
[1] configmap
代码语言:javascript复制# kubectl get configmap -n kube-system
NAME DATA AGE
coredns 1 23h
extension-apiserver-authentication 6 23h
kube-flannel-cfg 2 23h
kube-proxy 2 23h
kube-root-ca.crt 1 23h
kubeadm-config 2 23h
kubelet-config-1.20 1 23h
# kubectl edit configmap coredns -n kube-system
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
Corefile: |
.:53 {
log
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local 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:
creationTimestamp: "2021-08-21T02:41:04Z"
name: coredns
namespace: kube-system
resourceVersion: "18785"
uid: 37e1f743-aa3c-4a37-aa70-207bb8e6e377
[2] Deployment
Deployment“coredns”主要设置CoreDNS容器应用的内容,示例如下。
其中,replicas副本的数量通常应该根据集群的规模和服务数量确定,如果单个CoreDNS进程不足以支撑整个集群的DNS查询,则可以通过水平扩展提高查询能力。
由于DNS服务是Kubernetes集群的关键核心服务,所以建议为其Deployment设置自动扩缩容控制器,自动管理其副本数量。
另外,对资源限制部分(CPU限制和内存限制)的设置也应根据实际环境进行调整:
代码语言:javascript复制# kubectl get Deployment -n kube-system
NAME READY UP-TO-DATE AVAILABLE AGE
coredns 2/2 2 2 23h
# kubectl edit deployment coredns -n kube-system
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
creationTimestamp: "2021-08-21T02:41:04Z"
generation: 1
labels:
k8s-app: kube-dns
name: coredns
namespace: kube-system
resourceVersion: "886"
uid: acfab872-cf2f-450b-8929-40889e77d4b7
spec:
progressDeadlineSeconds: 600
replicas: 2
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: kube-dns
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
k8s-app: kube-dns
spec:
containers:
- args:
- -conf
- /etc/coredns/Corefile
image: k8s.gcr.io/coredns:1.7.0
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 5
Service“kube-dns”是DNS服务的配置,示例如下。
这个服务需要设置固定的ClusterIP,也需要将所有Node上的kubelet启动参数--cluster-dns设置为这个ClusterIP:
代码语言:javascript复制# kubectl get service -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 24h
# kubectl edit service kube-dns -n kube-system
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
annotations:
prometheus.io/port: "9153"
prometheus.io/scrape: "true"
creationTimestamp: "2021-08-21T02:41:04Z"
labels:
k8s-app: kube-dns
kubernetes.io/cluster-service: "true"
kubernetes.io/name: KubeDNS
name: kube-dns
namespace: kube-system
resourceVersion: "228"
uid: b188b415-d784-498e-bb44-9013074a038e
spec:
clusterIP: 10.96.0.10
clusterIPs:
- 10.96.0.10
ports:
- name: dns
port: 53
protocol: UDP
targetPort: 53
- name: dns-tcp
port: 53
protocol: TCP
targetPort: 53
- name: metrics
port: 9153
protocol: TCP
targetPort: 9153
selector:
k8s-app: kube-dns
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
(2)服务名的DNS解析举例
接下来使用一个带有nslookup工具的Pod来验证DNS服务能否正常工作:
dnsutils.yaml
代码语言:javascript复制apiVersion: v1
kind: Pod
metadata:
name: dnsutils
spec:
containers:
- name: dnsutils
image: mydlqclub/dnsutils:1.3
imagePullPolicy: IfNotPresent
command: ["sleep","3600"]
运行kubectl create -f dnsutils.yaml即可完成创建。
** [1] 查看该集群的服务情况。**
代码语言:javascript复制# kubectl get services --all-namespaces -o wide
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
default kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 24h <none>
default mysql ClusterIP 10.99.230.190 <none> 3306/TCP 24h app=mysql
default myweb NodePort 10.105.77.88 <none> 8080:31330/TCP 24h app=myweb
default myweb-headless ClusterIP None <none> 8080/TCP 12h app=myweb
kube-system kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 24h k8s-app=kube-dns
[2] 查找同命名空间的服务
在dnsutils成功启动后,通过nslookup进行测试。
查找defaul命名空间存在的mysql服务。
代码语言:javascript复制$ kubectl exec -it dnsutils sh
/ # nslookup mysql
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: mysql.default.svc.cluster.local
Address: 10.99.230.190
可以看到,通过DNS服务器169.169.0.100成功找到了mysql服务的IP地址:10.99.230.190。
[3] 查找不同命名空间的服务
如果某个Service属于不同的命名空间,那么在进行Service查找时,需要补充Namespace的名称,组合成完整的域名。下面以查找kube-dns服务为例,将其所在的Namespace“kube-system”补充在服务名之后,用“.”连接为“kube-dns.kube-system”,即可查询成功:
代码语言:javascript复制/ # nslookup kube-dns.kube-system
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: kube-dns.kube-system.svc.cluster.local
Address: 10.96.0.10
如果仅使用“kube-dns”进行查找,则会失败:
代码语言:javascript复制/ # nslookup kube-dns
Server: 10.96.0.10
Address: 10.96.0.10#53
** server can't find kube-dns: NXDOMAIN
查找公网域名百度,如下:
代码语言:javascript复制/ # nslookup www.baidu.com
Server: 10.96.0.10
Address: 10.96.0.10#53
Non-authoritative answer:
www.baidu.com canonical name = www.a.shifen.com.
Name: www.a.shifen.com
Address: 180.101.49.12
Name: www.a.shifen.com
Address: 180.101.49.11
www.a.shifen.com canonical name = www.wshifen.com.
(3)域名解析过程分析
[1] resolv.conf 文件分析
部署 pod 的时候,如果用的是 K8s 集群的 DNS,那么 kubelet 在起 pause 容器的时候,会将其 DNS 解析配置初始化成集群内的配置。
比如刚才创建了一个叫 dnsutils 的 pod,它的 resolv.conf 文件如下:
代码语言:javascript复制$ kubectl exec -it dnsutils sh
/ # cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
在集群中 pod 之间互相用 svc name 访问的时候,会根据 resolv.conf 文件的 DNS 配置来解析域名,下面来分析具体的过程。
pod 的 resolv.conf 文件主要有三个部分,分别为 nameserver、search 和 option。而这三个部分可以由 K8s 指定,也可以通过 pod.spec.dnsConfig 字段自定义。
nameserver
resolv.conf 文件的第一行 nameserver 指定的是 DNS 服务的 IP,这里就是 coreDNS 的 clusterIP:
代码语言:javascript复制# kubectl get services -n kube-system -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 24h k8s-app=kube-dns
也就是说所有域名的解析,都要经过 coreDNS 的虚拟 IP 10.96.0.10 进行解析,不论是 Kubernetes 内部域名还是外部的域名。
search 域
resolv.conf 文件的第二行指定的是 DNS search 域。解析域名的时候,将要访问的域名依次带入 search 域,进行 DNS 查询。
比如我要在刚才那个 pod 中访问一个域名为 dnsutils 的服务,其进行的 DNS 域名查询的顺序是:
代码语言:javascript复制dnsutils.default.svc.cluster.local -> dnsutils.svc.cluster.local -> dnsutils.cluster.local
直到查到为止。
options
resolv.conf 文件的第三行指定的是其他项,最常见的是 dnots。dnots 指的是如果查询的域名包含的点 “.” 小于 5,则先走 search 域,再用绝对域名;如果查询的域名包含点数大于或等于 5,则先用绝对域名,再走 search 域。K8s 中默认的配置是 5。
也就是说,如果我访问的是 a.b.c.e.f.g ,那么域名查找的顺序如下:
代码语言:javascript复制a.b.c.e.f.g. -> a.b.c.e.f.g.default.svc.cluster.local -> a.b.c.e.f.g.svc.cluster.local -> a.b.c.e.f.g.cluster.local
如果我访问的是 a.b.c.e,那么域名查找的顺序如下:
代码语言:javascript复制a.b.c.e.default.svc.cluster.local -> a.b.c.e.svc.cluster.local -> a.b.c.e.cluster.local -> a.b.c.e
[2] pod 之间的通信
通过 svc 访问
在 K8s 中,Pod 之间通过 svc 访问的时候,会经过 DNS 域名解析,再拿到 ip 通信。而 K8s 的域名全称为 "<service-name>.<namespace>.svc.cluster.local",而我们通常只需将 svc name 当成域名就能访问到 pod,这一点通过上面的域名解析过程并不难理解。
在dnsutils pod内,
代码语言:javascript复制/ # wget kube-dns
wget: bad address 'kube-dns'
/ # wget kube-dns.kube-system
Connecting to kube-dns.kube-system (10.96.0.10:80)
/ # wget myweb
Connecting to myweb (10.105.77.88:80)
可以看到,当直接用 kube-dns 去访问的时候,提示 bad address,说明域名错了,因为在不同的 namespace 下,所有的 search 域都找过了还是找不到;当用 kube-dns.kube-system 去访问的时候,会解析到 10.96.0.10 这个 IP,而这个 IP 正是 kube-dns 的 ClusterIP。
所以,在不同的 namespace 下的 pod 通过 svc 访问的时候,需要在 svc name 后面加上 .<namespace>。
(4)CoreDNS的配置说明
在命名空间 kube-system 下,集群有一个名为 coredns 的 configmap。其 Corefile 字段的文件配置内容如下
代码语言:javascript复制 Corefile: |
.:53 {
log
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local 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
}
其中,各插件说明:
- errors:错误信息到标准输出。
- health:CoreDNS自身健康状态报告,默认监听端口8080,一般用来做健康检查。您可以通过http://localhost:8080/health获取健康状态。
- ready:CoreDNS插件状态报告,默认监听端口8181,一般用来做可读性检查。可以通过http://localhost:8181/ready获取可读状态。当所有插件都运行后,ready状态为200。
- kubernetes:CoreDNS kubernetes插件,提供集群内服务解析能力。
- prometheus:CoreDNS自身metrics数据接口。可以通过http://localhost:9153/metrics获取prometheus格式的监控数据。
- forward(或proxy):将域名查询请求转到预定义的DNS服务器。默认配置中,当域名不在kubernetes域时,将请求转发到预定义的解析器(/etc/resolv.conf)中。默认使用宿主机的/etc/resolv.conf配置。
- cache:DNS缓存。
- loop:环路检测,如果检测到环路,则停止CoreDNS。
- reload:允许自动重新加载已更改的Corefile。编辑ConfigMap配置后,请等待两分钟以使更改生效。
- loadbalance:循环DNS负载均衡器,可以在答案中随机A、AAAA、MX记录的顺序。
在下面的示例中为域名“ cluster.local ”设置了一系列插件,包括errors 、 health、kubernetes、prometheus、forward、cache、loop、reload和loadbalance,在进行域名解析时,这些插件将以从上到下的顺序依次执行。
另外,etcd和hosts插件都可以用于用户自定义域名记录。
下面是使用etcd插件的配置示例,将以“.com”结尾的域名记录配置为从etcd中获取,并将域名记录保存在/skydns路径下:
第9课 Kubernetes之服务发现,CoreDNS配置和域名解析过程
如果用户在etcd中插入一条“10.1.1.1 mycompany.com”DNS记录:
代码语言:javascript复制etcdctl put /skydns/com/mycompany '{"host":"10.1.1.1","ttl":60}'
客户端应用就能访问域名“mycompany.com”了:
代码语言:javascript复制nslookup mycompany.com
forward和proxy插件都可以用于配置上游DNS服务器或其他DNS服务器,当在CoreDNS中查询不到域名时,会到其他DNS服务器上进行查询。在实际环境中,可以将Kubernetes集群外部的DNS纳入CoreDNS,进行统一的DNS管理。
(5)Pod级别的DNS配置说明
除了使用集群范围的DNS服务(如CoreDNS),在Pod级别也能设置DNS的相关策略和配置。在Pod的YAML配置文件中通过spec.dnsPolicy字段设置DNS策略,例如:
代码语言:javascript复制apiVersion: v1
kind: Pod
metadata:
namespace: default
name: dns-example
spec:
containers:
- name: test
image: nginx
dnsPolicy: "Default"
目前可以设置的DNS策略如下。
◎ Default:继承Pod所在宿主机的DNS设置。
◎ ClusterFirst:优先使用Kubernetes环境的DNS服务(如CoreDNS提供的域名解析服务),将无法解析的域名转发到从宿主机继承的DNS服务器。
◎ ClusterFirstWithHostNet:与ClusterFirst相同,对于以hostNetwork模式运行的Pod,应明确指定使用该策略。
◎ None:忽略Kubernetes环境的DNS配置,通过spec.dnsConfig自定义DNS配置。这个选项从Kubernetes 1.9版本开始引入,到Kubernetes 1.10版本升级为Beta版,到Kubernetes 1.14版本升级为稳定版。
以下面的dnsConfig为例:
代码语言:javascript复制apiVersion: v1
kind: Pod
metadata:
namespace: default
name: dns-example
spec:
containers:
- name: test
image: nginx
dnsPolicy: "None"
dnsConfig:
nameservers:
- 1.2.3.4
searches:
- ns1.svc.cluster.local
- my.dns.search.suffix
options:
- name: ndots
value: "2"
- name: edns0
该Pod被成功创建之后,容器内的DNS配置文件/etc/resolv.conf的内容将被系统设置为:
第9课 Kubernetes之服务发现,CoreDNS配置和域名解析过程
表示该Pod完全使用自定义的DNS配置,不再使用Kubernetes环境的DNS服务。
参考
(1)K8S落地实践 之 服务发现(CoreDNS) https://blog.51cto.com/u_12965094/2641238
(2)自定义 DNS 服务 https://kubernetes.io/zh/docs/tasks/administer-cluster/dns-custom-nameservers/
(3)Kubernetes 服务发现之 coreDNS https://juejin.cn/post/6844903965520297991
【说明】此篇文章讲解域名解析的过程。
(4)Kubernetes 集群 DNS 服务发现原理 https://developer.aliyun.com/article/779121
【说明】CoreDNS的配置说明
(5)Kubernetes 服务自动发现CoreDNS https://www.cnblogs.com/jasonminghao/p/12250965.html