K8s服务发现与DNS解析全解析

一、概述

在 Kubernetes 中,服务发现有几种方式:

  • 基于环境变量的方式

  • 基于内部域名的方式

基本上,使用环境变量的方式很少,主要还是使用内部域名这种服务发现的方式。

其中,基于内部域名的方式,涉及到 Kubernetes 内部域名的解析,而 kubedns,是 Kubernetes 官方的 DNS 解析组件。从 1.11 版本开始,kubeadm 已经使用第三方的 CoreDNS 替换官方的 kubedns 作为 Kubernetes 集群的内部域名解析组件。

Kubernetes 中的域名是如何解析的?

在 Kubernetes 中,比如服务 a 访问服务 b,对于同一个 Namespace下,可以直接在 pod 中,通过 curl b 来访问。对于跨 Namespace 的情况,服务名后边对应 Namespace即可。比如 curl b.default。那么,使用者这里边会有几个问题:

  1. 服务名是什么?

  2. 为什么同一个 Namespace 下,直接访问服务名即可?不同 Namespace 下,需要带上 Namespace 才行?

  3. 为什么内部的域名可以做解析,原理是什么?

DNS 如何解析,依赖容器内 resolv 文件的配置

复制代码
cat /etc/resolv.conf
nameserver 10.10.0.3
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

这个文件中,配置的 DNS Server,一般就是 K8S 中,kubedns 的 Service 的 ClusterIP,这个IP是虚拟IP,无法ping,但可以访问。

service 是否可以ping取决于 svc使用iptables 以及ipvs的区别。

如果pod使用dns策略为clusterfirst的时候 ,要经过 kubedns 的虚拟IP 10.10.0.3 进行解析,不论是 Kubernetes 内部域名还是外部的域名。

二、pod DNS策略

Kubernetes 中 Pod 的 DNS 策略有四种类型。

  • Default:Pod 继承所在主机上的 DNS 配置;

  • ClusterFirst:K8s 的默认设置;先在 K8s 集群配置的 coreDNS 中查询,查不到的再去继承自主机的上游 nameserver 中查询;

复制代码
dnsPolicy: ClusterFirst  
  • ClusterFirstWithHostNet:对于网络配置为 hostNetwork 的 Pod 而言,其 DNS 配置规则与 ClusterFirst 一致;

  • None:忽略 K8s 环境的 DNS 配置,只认 Pod 的 dnsConfig 设置。

复制代码
dnsPolicy: "None"
  dnsConfig:
    nameservers:
      - 114.114.114.114
    searches:
      - default.svc.cluster.local
    options:
      - name: ndots
        value: "5"    ###默认为5

三、CoreDns解析规则

在部署 pod 的时候, kubelet 在起容器的时候,会将其 DNS 解析配置初始化成集群内的配置。因此在每个pod里面都会有/etc/resolv.conf文件,通过修改其中的配置可以更改DNS的查询规则

如下启动一个pod,查看/etc/resolv.conf配置

复制代码
[root@mypod /]# cat etc/resolv.conf 
search default.svc.test.com svc.test.com test.com
nameserver 169.254.25.10     ####我的环境中使用了nodelocaldns,这个地址是nodelocaldns的地址
options ndots:5
[root@mypod /]# 
​
#在集群中 pod 之间互相用 svc name 访问的时候,会根据 resolv.conf 文件的 DNS 配置来解析域名
  • nameserver:集群中的DNS服务器IP,一般来说就是CoreDNSClusterIP

  • search:需要搜索的域,默认情况下会从该pod所属的namespace开始逐级补充

复制代码
#解析域名的时候,将要访问的域名依次带入 search 域,进行 DNS 查询。
#例如在pod 中访问一个域名为 nginx 的服务,其进行的 DNS 域名查询的顺序是:
​
nginx.default.svc.test.com. -> nginx.svc.test.com. -> nginx.test.com.
​
#按照上述顺序直到查到为止
  • options ndots:触发上面的search的域名点数'.',在K8S中默认为5,上限15;例如test.com这个域名的ndots是1,test.com.这个域名的ndots才是2(需要注意所有域名其实都有一个根域.,因此test.com的全称应该是test.com.)。如果dnots 指定查询的域名包含的点 "." 小于 5,则先走 search 域,再用绝对域名;如果查询的域名包含点数大于或等于 5,则先用绝对域名,再走 search 域。
复制代码
#例如当ndots大于等于5时访问的是 a.b.c.e.f.g ,那么域名查找的顺序如下:
a.b.c.e.f.g. -> a.b.c.e.f.g.default.svc.test.com. -> a.b.c.e.f.g.svc.test.com. -> a.b.c.e.f.g.test.com.
​
#例如当ndots小于5时访问的是 a.b.c.e. ,那么域名查找的顺序如下:
a.b.c.e.default.svc.test.com. -> a.b.c.e.svc.test.com. -> a.b.c.e.test.com. -> a.b.c.e.
​

四、pod之间通信

  • 通过svc的方式通信

在 K8s 中,Pod 之间通过 svc 访问的时候,会经过 DNS 域名解析,再拿到 ip 通信。而 K8s 的域名全称为 "<service-name>.<namespace>.svc.test.com",通常只需将 svc name 当成域名就能访问到 pod。

复制代码
1:使用deploy启动一个nginx的pod,svc名称为nginx-svc,如下:
---                                                                                                                                                           
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: docker.io/library/nginx:latest
        imagePullPolicy: IfNotPresent
        ports:
          - containerPort: 80
 
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
spec:
  selector:
    app: nginx
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  type: ClusterIP
  
查看pod中/etc/resolv.conf配置如下:
root@nginx-5977dc5756-lcmwq:/# cat etc/resolv.conf 
search default.svc.test.com svc.test.com test.com
nameserver 169.254.25.10
options ndots:5
root@nginx-5977dc5756-lcmwq:/# 
​
2:使用另外一个pod访问nginx-svc这个域名,如下:
[root@master yaml]# kubectl exec -it  mypod bash 
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
[root@mypod /]# ping nginx-svc
PING nginx-svc.default.svc.test.com (10.10.23.221) 56(84) bytes of data.
64 bytes from nginx-svc.default.svc.test.com (10.10.23.221): icmp_seq=1 ttl=64 time=0.152 ms
64 bytes from nginx-svc.default.svc.test.com (10.10.23.221): icmp_seq=2 ttl=64 time=0.143 ms
64 bytes from nginx-svc.default.svc.test.com (10.10.23.221): icmp_seq=3 ttl=64 time=0.114 ms
^C
--- nginx-svc.default.svc.test.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 0.114/0.136/0.152/0.018 ms
[root@mypod /]# 
#############################
返回的nginx-svc的地址为10.10.23.221,此地址为nginx-svc的地址,如下:
[root@master yaml]# kubectl get svc 
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
nginx-svc    ClusterIP   10.10.23.221   <none>        80/TCP    2d17h
[root@master yaml]# 
​
#############################
如果要访问其他ns的svc,需要带上ns name即可,如下:
[root@master yaml]# kubectl exec -it  mypod bash 
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
[root@mypod /]# ping harbor-core
ping: harbor-core: Name or service not known    ###不添加ns的话,无法解析habor-core
​
[root@mypod /]# ping harbor-core.harbor
PING harbor-core.harbor.svc.test.com (10.10.30.184) 56(84) bytes of data.
64 bytes from harbor-core.harbor.svc.test.com (10.10.30.184): icmp_seq=1 ttl=64 time=0.095 ms
64 bytes from harbor-core.harbor.svc.test.com (10.10.30.184): icmp_seq=2 ttl=64 time=0.130 ms
64 bytes from harbor-core.harbor.svc.test.com (10.10.30.184): icmp_seq=3 ttl=64 time=0.165 ms
^C
--- harbor-core.harbor.svc.test.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 0.095/0.130/0.165/0.028 ms
[root@mypod /]# 
  • 通过hostname和subdomain通信

在 K8s 中,如果不指定 pod 的 hostname,其默认为 pod.metadata.name,通过 spec.hostname 字段可以自定义;另外还可以给 pod 设置 subdomain,通过 spec.subdomain 字段。如下:

复制代码
#启动pod,如下:
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: web
spec:
  hostname: nginx      ####设置hostnam为nginx
  subdomain: subdomain-test       ####
  containers:
  - name: nginx
    image: docker.io/library/nginx:latest
    imagePullPolicy: IfNotPresent
  dnsPolicy: "None"
  dnsConfig:
    nameservers:
     - 114.114.114.114
    searches:
     - default.svc.test.com
    options:
     - name: ndots
       value: "5"    ###默认为5
 
---
apiVersion: v1
kind: Service
metadata:
  name: subdomain-test
spec:
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
    
#启动pod,查看/etc/hosts文件
[root@master yaml]# kubectl get po -owide 
NAME                                      READY   STATUS    RESTARTS   AGE     IP             NODE    NOMINATED NODE   READINESS GATES
nginx                                     1/1     Running   0          6s      10.10.92.25   node3   <none>           <none>
[root@master yaml]# kubectl get svc
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
subdomain-test   ClusterIP   10.10.7.174    <none>        80/TCP    15s
[root@k8s-master ~]# dig @10.96.0.10 nginx.subdomain-test.default.svc.cluster.local
​
; <<>> DiG 9.11.36-RedHat-9.11.36-14.el8_10 <<>> @10.96.0.10 nginx.subdomain-test.default.svc.cluster.local
; (1 server found)
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50420
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
​
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 793a61ce1b4aae71 (echoed)
;; QUESTION SECTION:
;nginx.subdomain-test.default.svc.cluster.local.    IN A
​
;; ANSWER SECTION:
nginx.subdomain-test.default.svc.cluster.local. 30 IN A 10.244.169.187
​
;; Query time: 1 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: 五 11月 29 03:51:09 EST 2024
;; MSG SIZE  rcvd: 149
​
​
#通过其他pod 访问nginx.subdomain,如下:
[root@master yaml]# kubectl exec -it  mypod bash 
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
[root@mypod /]# 
[root@mypod /]# 
[root@mypod /]# ping nginx.subdomain-test
PING nginx.subdomain-test.default.svc.test.com (10.10.92.25) 56(84) bytes of data.
64 bytes from nginx.subdomain-test.default.svc.test.com (10.10.92.25): icmp_seq=1 ttl=62 time=1.91 ms
64 bytes from nginx.subdomain-test.default.svc.test.com (10.10.92.25): icmp_seq=2 ttl=62 time=0.902 ms
64 bytes from nginx.subdomain-test.default.svc.test.com (10.10.92.25): icmp_seq=3 ttl=62 time=1.01 ms
^C
--- nginx.subdomain-test.default.svc.test.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 0.902/1.278/1.914/0.452 ms
​
#如上返回的地址为pod本身的ip地址
​

五、CoreDns Corefile 文件

CoreDNS 实现了应用的插件化,用户可以选择所需的插件编译到可执行文件中;CoreDNS 的配置文件是 Corefile 形式的,以下是CoreDns的configmap的配置:

复制代码
[root@master yaml]# kubectl -n kube-system get cm coredns -o yaml
apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        health {
           lameduck 5s
        }
        ready
        kubernetes test.com 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: "2023-12-08T17:18:27Z"
  name: coredns
  namespace: kube-system
  resourceVersion: "224"
  uid: ccf7598d-8b5c-48db-9230-7a539d6c7e98
[root@master yaml]# 

配置文件分析

复制代码
 #第一部分
 errors
        health {
           lameduck 5s
        }
 
 coredns内部插件,错误日志以及健康监测等,其他插件可以参考https://coredns.io/plugins/kubernetes/
#第二部分
 kubernetes test.com in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
#指明 test.com 后缀的域名,都是 kubernetes 内部域名,coredns 会监听 service 的变化来维护域名关系,test.com 相关域名都在这里解析。

#ttl 30:设置标准的DNS域名TTL,默认值为 5 秒。允许的最小 TTL 为 0 秒,最大值为 3600 秒。将 TTL 设置为 0 将防止记录被缓存。

#pods insecure:总是从请求中返回带有 IP 的 A 记录(不检查 k8s),即查询域名1-2-3-4.ns.pod.cluster.local.的时候,不论是否存在一个IP地址为1.2.3.4的pod,都返回这个结果给客户端。如果与通配符 SSL 证书一起被恶意使用,此选项很容易被滥用。提供此选项是为了向后兼容 kube-dns。

#fallthrough in-addr.arpa ip6.arpa:正常情况下一个客户端对CoreDNS发起了一个DNS查询,如果该记录不存在,那么就会直接返回一个NXDOMAIN的响应。

#第三部分
forward . /etc/resolv.conf {
           max_concurrent 1000
        }

#指 coredns 中没有找到记录,则去 /etc/resolv.conf 中的 nameserver 请求解析,而 coredns 容器中的 /etc/resolv.conf 是继承自宿主机的。实际是如果不是 k8s 内部域名,就会去默认的 dns 服务器请求解析,并返回给 coredns 的请求者。

#第四部分
       prometheus :9153
        cache 30
        loop
        reload
        loadbalance
#prometheus:CoreDNS 的监控地址为: http://localhost:9153/metrics ,满足 Prometheus 的格式。
#cache:允许缓存
#loop:如果找到循环,则检测简单的转发循环并停止 CoreDNS 进程。
#reload:允许 Corefile 的配置自动更新。在更改 ConfigMap 后两分钟,修改生效
#loadbalance:这是一个循环 DNS 负载均衡器,可以在答案中随机化 A,AAAA 和 MX 记录的顺序。

#第五部分
#当某个域名服务不在集群内部时,为了让pod可以访问,可以在corefile中添加host选项如下:
hosts {
    192.168.10.10 edu.com
    fallthrough
}

案例:添加外部解析

在 Kubernetes 1.28 版本中,如果您想要通过 CoreDNS 的 ConfigMap 添加外部指定主机名的解析,可以使用 CoreDNS 的 hosts 插件。以下是如何配置 CoreDNS 的 ConfigMap 以添加 hosts 字段的示例:

  1. 获取当前 CoreDNS ConfigMap: 您可以通过以下命令获取当前的 CoreDNS ConfigMap 配置:

    复制代码
    kubectl get cm coredns -n kube-system -o yaml > coredns-configmap.yaml
  2. 编辑 CoreDNS ConfigMap : 使用编辑器打开 coredns-configmap.yaml 文件,您将看到 CoreDNS 的配置文件 Corefile。在这个文件中,您可以添加 hosts 插件的配置。

  3. 添加 hosts 插件配置 : 在 Corefile 中添加 hosts 部分,例如,如果您想要将域名 git.k8s.local 解析到 IP 地址 10.151.30.11,可以添加如下配置:

    复制代码
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: coredns
      namespace: kube-system
    data:
      Corefile: |
        .:53 {
            errors
            health {
                lameduck 5s
            }
            ready
            kubernetes cluster.local in-addr.arpa ip6.arpa {
                pods insecure
                fallthrough in-addr.arpa ip6.arpa
            }
            hosts {
                10.151.30.11 git.k8s.local
                fallthrough
            }
            prometheus :9153
            forward . /etc/resolv.conf {
                max_concurrent 1000
            }
            cache 30
            loop
            reload
            loadbalance
        }

    在这个配置中,hosts 插件被用来指定特定的 IP 地址和主机名映射。fallthrough 指令表示如果请求的域名不在 hosts 部分定义,则请求将被转发到下一个插件处理。

  4. 应用更改 : 保存更改后的 coredns-configmap.yaml 文件,并使用以下命令应用更改:

    复制代码
    kubectl apply -f coredns-configmap.yaml
  5. 重启 CoreDNS Pod: 更改 ConfigMap 后,您需要重启 CoreDNS Pod 以使更改生效。可以通过以下命令删除现有的 CoreDNS Pod:

    复制代码
    kubectl delete pod -n kube-system -l k8s-app=kube-dns

    这将触发 CoreDNS Pod 的重启,新的 Pod 将使用更新后的 ConfigMap 配置。

通过以上步骤,您可以在 Kubernetes 1.28 版本中通过 CoreDNS 的 ConfigMap 添加外部指定主机名的解析。这种方法允许您自定义 DNS 解析规则,以满足集群内外的域名解析需求。

  1. 验证

    复制代码
    [root@k8s-master ~]# dig @10.96.0.10 git.k8s.local
    
    ; <<>> DiG 9.11.36-RedHat-9.11.36-14.el8_10 <<>> @10.96.0.10 git.k8s.local
    ; (1 server found)
    ;; global options: +cmd
    ;; Got answer:
    ;; WARNING: .local is reserved for Multicast DNS
    ;; You are currently testing what happens when an mDNS query is leaked to DNS
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 51965
    ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
    ;; WARNING: recursion requested but not available
    
    ;; OPT PSEUDOSECTION:
    ; EDNS: version: 0, flags:; udp: 4096
    ; COOKIE: e37b3a494b869230 (echoed)
    ;; QUESTION SECTION:
    ;git.k8s.local.			IN	A
    
    ;; ANSWER SECTION:
    git.k8s.local.		30	IN	A	10.151.30.11
    
    ;; Query time: 1 msec
    ;; SERVER: 10.96.0.10#53(10.96.0.10)
    ;; WHEN: 五 11月 29 04:04:09 EST 2024
    ;; MSG SIZE  rcvd: 83
相关推荐
FuckPatience2 小时前
C# 项目调试的时候进不去断点
开发语言·c#
元亓亓亓2 小时前
考研408--组成原理--day8--汇编指令&不同语句的机器级表示
开发语言·汇编·c#
g***B7384 小时前
Java 工程复杂性的真正来源:从语言设计到现代架构的全链路解析
java·人工智能·架构
期待のcode6 小时前
MyBatisX插件
java·数据库·后端·mybatis·springboot
醇氧8 小时前
【Windows】优雅启动:解析一个 Java 服务的后台启动脚本
java·开发语言·windows
sunxunyong8 小时前
doris运维命令
java·运维·数据库
菜鸟起航ing8 小时前
Spring AI 全方位指南:从基础入门到高级实战
java·人工智能·spring
古城小栈9 小时前
Docker 多阶段构建:Go_Java 镜像瘦身运动
java·docker·golang
MapGIS技术支持9 小时前
MapGIS Objects Java计算一个三维点到平面的距离
java·开发语言·平面·制图·mapgis