kubernetes服务访问流程(第9课Kubernetes之服务发现)

摘要

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

kubernetes服务访问流程(第9课Kubernetes之服务发现)(1)

图4.5展现了CoreDNS的总体架构

(1)查看CoreDNS信息

k8s的v1.20.5版本在集群启动时,已经启动了coreDNS域名服务。

在部署CoreDNS应用前,至少需要创建一个ConfigMap、一个Deployment和一个Service共3个资源对象。ConfigMap“coredns”主要设置Coredns的主配置文件Corefile的内容,其中可以定义各种域名的解析方式和使用的插件。

相关的配置可以查看。

[1] configmap

# 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限制和内存限制)的设置也应根据实际环境进行调整:

# 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:

# 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

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] 查看该集群的服务情况。**

# 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服务。

$ 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”,即可查询成功:

/ # 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”进行查找,则会失败:

/ # nslookup kube-dns Server: 10.96.0.10 Address: 10.96.0.10#53 ** server can't find kube-dns: NXDOMAIN

查找公网域名百度,如下:

/ # 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 文件如下:

$ 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:

# 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 域名查询的顺序是:

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 ,那么域名查找的顺序如下:

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,那么域名查找的顺序如下:

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内,

/ # 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 字段的文件配置内容如下

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路径下:

kubernetes服务访问流程(第9课Kubernetes之服务发现)(2)

如果用户在etcd中插入一条“10.1.1.1 mycompany.com”DNS记录:

etcdctl put /skydns/com/mycompany '{"host":"10.1.1.1","ttl":60}'

客户端应用就能访问域名“mycompany.com”了:

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策略,例如:

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为例:

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的内容将被系统设置为:

kubernetes服务访问流程(第9课Kubernetes之服务发现)(3)

表示该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

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页