第9课 Kubernetes之服务发现和域名解析过程分析

2021-11-24 13:35:49 浏览数 (1)

摘要

作为服务发现机制的基本功能,在集群内需要能够通过服务名对服务进行访问,这就需要一个集群范围内的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
    }

其中,各插件说明:

  1. errors:错误信息到标准输出。
  2. health:CoreDNS自身健康状态报告,默认监听端口8080,一般用来做健康检查。您可以通过http://localhost:8080/health获取健康状态。
  3. ready:CoreDNS插件状态报告,默认监听端口8181,一般用来做可读性检查。可以通过http://localhost:8181/ready获取可读状态。当所有插件都运行后,ready状态为200。
  4. kubernetes:CoreDNS kubernetes插件,提供集群内服务解析能力。
  5. prometheus:CoreDNS自身metrics数据接口。可以通过http://localhost:9153/metrics获取prometheus格式的监控数据。
  6. forward(或proxy):将域名查询请求转到预定义的DNS服务器。默认配置中,当域名不在kubernetes域时,将请求转发到预定义的解析器(/etc/resolv.conf)中。默认使用宿主机的/etc/resolv.conf配置。
  7. cache:DNS缓存。
  8. loop:环路检测,如果检测到环路,则停止CoreDNS。
  9. reload:允许自动重新加载已更改的Corefile。编辑ConfigMap配置后,请等待两分钟以使更改生效。
  10. 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

0 人点赞