一、服务发现

Pod 提供的服务可以通过 Service 生成的 ClusterIP(VIP) 来访问。怎么知道某个应用的 VIP 呢?

k8s主要有两种 Service 发现机制:环境变量和DNS。没有DNS服务的时候,k8s会采用环境变量的形式,但一旦有多个service,环境变量会变复杂,为解决该问题,我们使用DNS服务。



环境变量

在之前的版本中,Kubernetes 采用了环境变量的方法,每个 Pod 启动的时候,会通过环境变量设置所有服务的 IP 和 port 信息,这样 Pod 中的应用可以通过读取环境变量来获取依赖服务的地址信息,这种方法使用起来相对简单,但是有一个很大的问题就是依赖的服务必须在 Pod 启动之前就存在,不然是不会被注入到环境变量中的。


shell到pod,查看pod的env信息:

$ sh -c env
CLOUD_EMQX_LATEST_PORT_14567_UDP_PORT=14567
KUBERNETES_SERVICE_PORT=443
EMQX_DASHBOARD_SERVICE_HOST=172.17.20.120
CLOUD_EMQX_LATEST_PORT_14567_UDP_PROTO=udp
EMQX_LISTENERS__QUIC__DEFAULT__initial_rtt_ms=150000
KUBERNETES_PORT=tcp://172.17.0.1:443
CLOUD_EMQX_LATEST_PORT_8883_TCP=tcp://172.17.110.54:8883
EMQX_PORT_8083_TCP_ADDR=172.17.30.239
EMQX_PORT_1883_TCP_ADDR=172.17.30.239
EMQX_PORT_8084_TCP_ADDR=172.17.30.239
HOSTNAME=cloud-emqx-0
EMQX_LISTENERS__QUIC__DEFAULT__keep_alive_interval_ms=150000
EMQX_SERVICE_PORT_QUIC_DEFAULT=14567
CLOUD_EMQX_LATEST_SERVICE_PORT_WS_DEFAULT=8083
SHLVL=1
EMQX_LOG__CONSOLE_HANDLER__LEVEL=warning
EMQX_PORT_8083_TCP_PORT=8083
EMQX_PORT_8084_TCP_PORT=8084
EMQX_PORT_8083_TCP_PROTO=tcp
HOME=/home/emqx
EMQX_PORT_1883_TCP_PORT=1883
EMQX_SERVICE_PORT=1883
EMQX_PORT=tcp://172.17.30.239:1883
EMQX_PORT_1883_TCP_PROTO=tcp
EMQX_PORT_8084_TCP_PROTO=tcp
CLOUD_EMQX_DASHBOARD_SERVICE_PORT_DASHBOARD_LISTENERS_HTTP_BIND=18083
EMQX_SERVICE_PORT_TCP_DEFAULT=1883
EMQX_DASHBOARD_SERVICE_PORT=18083
EMQX_DASHBOARD_PORT=tcp://172.17.20.120:18083
EMQX_LISTENERS__QUIC__DEFAULT__disconnect_timeout_ms=150000
CLOUD_EMQX_LATEST_PORT_14567_UDP=udp://172.17.110.54:14567
EMQX_PORT_8883_TCP_ADDR=172.17.30.239
CLOUD_EMQX_LATEST_SERVICE_PORT_WSS_DEFAULT=8084
CLOUD_EMQX_DASHBOARD_SERVICE_HOST=172.17.113.161
...


输出了很多环境变量信息,有 HOST、PORT、PROTO、ADDR 等,包括自己 Service 信息,也包括别的 Service 的信息,可以直接通过环境变量直接访问服务。这不是最优的方法,局限性太多了。



DNS

DNS 其实就是一个分布式的树状命名系统,它就像一个去中心化的分布式数据库,存储着从域名到 IP 地址的映射。


DNS 工作原理

当一台主机想要通过域名访问某个服务的内容时,需要先通过当前域名获取对应的 IP 地址。这时就需要通过一个 DNS 解析器负责域名的解析,下面的图片展示了 DNS 查询的执行过程:

Kubernetes 网络之 DNS 介绍_coredns

  1. 本地的 DNS 客户端向 DNS 解析器发出解析 draveness.me 域名的请求;
  2. DNS 解析器首先会向就近的根 DNS 服务器 . 请求顶级域名 DNS 服务的地址;
  3. 拿到顶级域名 DNS 服务 me. 的地址之后会向顶级域名服务请求负责 dravenss.me. 域名解析的命名服务;
  4. 得到授权的 DNS 命名服务时,就可以根据请求的具体的主机记录直接向该服务请求域名对应的 IP 地址;

DNS 客户端接受到 IP 地址之后,整个 DNS 解析的过程就结束了,客户端接下来就会通过当前的 IP 地址直接向服务器发送请求。


对于 DNS 解析器,这里使用的 DNS 查询方式是迭代查询,每个 DNS 服务并不会直接返回 DNS 信息,而是会返回另一台 DNS 服务器的位置,由客户端依次询问不同级别的 DNS 服务直到查询得到了预期的结果;另一种查询方式叫做递归查询,也就是 DNS 服务器收到客户端的请求之后会直接返回准确的结果,如果当前服务器没有存储 DNS 信息,就会访问其他的服务器并将结果返回给客户端。


域名层级

域名层级是一个层级的树形结构,树的最顶层是根域名,一般使用 . 来表示,这篇文章所在的域名一般写作 draveness.me,但是这里的写法其实省略了最后的 .,也就是全称域名(FQDN)dravenss.me.

Kubernetes 网络之 DNS 介绍_coredns_02

根域名下面的就是 comnet 和 me 等顶级域名以及次级域名 draveness.me,我们一般在各个域名网站中购买和使用的都是次级域名、子域名和主机名了。


dig 域名

既然域名的命名空间是树形的,那么用于处理域名解析的 DNS 服务器也是树形的,只是在树的组织和每一层的职责上有一些不同。DNS 解析器从根域名服务器查找到顶级域名服务器的 IP 地址,又从顶级域名服务器查找到权威域名服务器的 IP 地址,最终从权威域名服务器查出了对应服务的 IP 地址。

% dig -t A draveness.me +trace

返回:


; <<>> DiG 9.10.6 <<>> -t A draveness.me +trace
;; global options: +cmd
.                       2       IN      NS      m.root-servers.net.
.                       2       IN      NS      d.root-servers.net.
.                       2       IN      NS      k.root-servers.net.
.                       2       IN      NS      j.root-servers.net.
.                       2       IN      NS      h.root-servers.net.
.                       2       IN      NS      a.root-servers.net.
.                       2       IN      NS      i.root-servers.net.
.                       2       IN      NS      l.root-servers.net.
.                       2       IN      NS      c.root-servers.net.
.                       2       IN      NS      g.root-servers.net.
.                       2       IN      NS      e.root-servers.net.
.                       2       IN      NS      b.root-servers.net.
.                       2       IN      NS      f.root-servers.net.
;; Received 239 bytes from 10.131.4.220#53(10.131.4.220) in 3 ms

me.                     172800  IN      NS      a0.nic.me.
me.                     172800  IN      NS      a2.nic.me.
me.                     172800  IN      NS      b0.nic.me.
me.                     172800  IN      NS      b2.nic.me.
me.                     172800  IN      NS      c0.nic.me.
me.                     86400   IN      DS      45352 8 2 7708C8A6D5D72B63214BBFF50CB54553F7E07A1FA5E9074BD8D63C43 102D8559
me.                     86400   IN      RRSIG   DS 8 1 86400 20231230050000 20231217040000 46780 . vULmJevs3K8UKt9yH8ZoA85CwF7ZrlVYrFV8pXDdoogXPQyw+4ZxtxIu T0NcaC/ZV8GTvfqtQAuxOqj85xPq8RB+40aI3omwY2rvWyijg3mqtP88 9Y4x8+PnhM5XqdTmwrdsV7WUtFZFYOqAWTQtP7wmIbG8xhRZq0UJNg2b OfIU8bMkn4AJWfBRKOg/Qv63qXgvLGi/dB72VANR75TgXAOVWV/qHymq 0hxJqmt4mckXFs+bZv+8yW6RVpeABqLWClqiHU5mIoDfT+dxNAwuRl+j ORRGoh+vxZXwXrq8pRSMZOkpdKazOFFXWjB3qJwVPDnNOOEK6PSpMFup zYvbcA==
;; Received 687 bytes from 202.12.27.33#53(m.root-servers.net) in 67 ms

draveness.me.           3600    IN      NS      ns4.dnsv2.com.
draveness.me.           3600    IN      NS      ns3.dnsv2.com.
c0aa63slu8ucpmm55hrueae8tj5iopm2.me. 3600 IN NSEC3 1 1 100 332539EE7F95C32A C0AJT0T91MSH2HQLHLMC6K9HT14H00T4  NS SOA RRSIG DNSKEY NSEC3PARAM
c0aa63slu8ucpmm55hrueae8tj5iopm2.me. 3600 IN RRSIG NSEC3 8 2 3600 20240107084911 20231217074911 12693 me. SlCrJFAW6wkIdqVsCrPl7ub7c2J9geCQRXUI+qc75UluKBEXMB09i2JT RWdCbpPNoOpIJIAAUjOStjDIGqTQK6eKyOnw1fKWVLAwKRLJnt0s5KTi SYAskFv9vd3bsKhGUBOEpdxloiMXe/bzG92v3pm7podfY2BJ3Ota5+Tf bxI=
utu6vkh4imogpsoglmuqj90a8f9m377u.me. 3600 IN NSEC3 1 1 100 332539EE7F95C32A UTVQH4ACRLEBR0LIOI6BKFJBJ61PCP52  NS DS RRSIG
utu6vkh4imogpsoglmuqj90a8f9m377u.me. 3600 IN RRSIG NSEC3 8 2 3600 20231230154517 20231209144517 12693 me. WUbzrcSNaBgDsIbIomedypjRjaqVo64z282lyC0UDPc6XlaGnvOMxFSS H3EsjR5YOrkmqxt9rUzXRH8qxgbJ6RfVIkW9TSQ6VCHqIL1FQ04nu7BV KiKArQM+niAtpoG3UrqVYHcEFugvsHt5eCR+e7o1ciE6/1MvGVvx/WPA y6I=
;; Received 585 bytes from 199.253.60.1#53(b0.nic.me) in 218 ms

draveness.me.           600     IN      A       167.179.82.83
draveness.me.           86400   IN      NS      ns3.dnsv2.com.
draveness.me.           86400   IN      NS      ns4.dnsv2.com.
;; Received 111 bytes from 125.94.59.205#53(ns3.dnsv2.com) in 30 ms


我们可以使用 dig 命令追踪 draveness.me 域名对应 IP 地址是如何被解析出来的,首先会向预置的 13 组根域名服务器发出请求获取顶级域名的地址:

.                       2       IN      NS      m.root-servers.net.
.                       2       IN      NS      d.root-servers.net.
.                       2       IN      NS      k.root-servers.net.
.                       2       IN      NS      j.root-servers.net.
.                       2       IN      NS      h.root-servers.net.
.                       2       IN      NS      a.root-servers.net.
.                       2       IN      NS      i.root-servers.net.
.                       2       IN      NS      l.root-servers.net.
.                       2       IN      NS      c.root-servers.net.
.                       2       IN      NS      g.root-servers.net.
.                       2       IN      NS      e.root-servers.net.
.                       2       IN      NS      b.root-servers.net.
.                       2       IN      NS      f.root-servers.net.
;; Received 239 bytes from 10.131.4.220#53(10.131.4.220) in 3 ms

根域名服务器是 DNS 中最高级别的域名服务器,这些服务器负责返回顶级域的权威域名服务器地址,这些域名服务器的数量总共有 13 组,域名的格式从上面返回的结果可以看到是 .root-servers.net,每个根域名服务器中只存储了顶级域服务器的 IP 地址,大小其实也只有 2MB 左右,虽然域名服务器总共只有 13 组,但是每一组服务器都通过提供了镜像服务,全球大概也有几百台的根域名服务器在运行。

在这里,我们获取到了以下的 5 条 NS 记录,也就是 5 台 me. 定义域名 DNS 服务器:

me.                     172800  IN      NS      a0.nic.me.
me.                     172800  IN      NS      a2.nic.me.
me.                     172800  IN      NS      b0.nic.me.
me.                     172800  IN      NS      b2.nic.me.
me.                     172800  IN      NS      c0.nic.me.
me.                     86400   IN      DS      45352 8 2 7708C8A6D5D72B63214BBFF50CB54553F7E07A1FA5E9074BD8D63C43 102D8559
me.                     86400   IN      RRSIG   DS 8 1 86400 20231230050000 20231217040000 46780 . vULmJevs3K8UKt9yH8ZoA85CwF7ZrlVYrFV8pXDdoogXPQyw+4ZxtxIu T0NcaC/ZV8GTvfqtQAuxOqj85xPq8RB+40aI3omwY2rvWyijg3mqtP88 9Y4x8+PnhM5XqdTmwrdsV7WUtFZFYOqAWTQtP7wmIbG8xhRZq0UJNg2b OfIU8bMkn4AJWfBRKOg/Qv63qXgvLGi/dB72VANR75TgXAOVWV/qHymq 0hxJqmt4mckXFs+bZv+8yW6RVpeABqLWClqiHU5mIoDfT+dxNAwuRl+j ORRGoh+vxZXwXrq8pRSMZOkpdKazOFFXWjB3qJwVPDnNOOEK6PSpMFup zYvbcA==
;; Received 687 bytes from 202.12.27.33#53(m.root-servers.net) in 67 ms

当 DNS 解析器从根域名服务器中查询到了顶级域名 .me 服务器的地址之后,就可以访问这些顶级域名服务器其中的一台 b2.nic.me 获取权威 DNS 的服务器的地址了:

draveness.me.           3600    IN      NS      ns4.dnsv2.com.
draveness.me.           3600    IN      NS      ns3.dnsv2.com.
c0aa63slu8ucpmm55hrueae8tj5iopm2.me. 3600 IN NSEC3 1 1 100 332539EE7F95C32A C0AJT0T91MSH2HQLHLMC6K9HT14H00T4  NS SOA RRSIG DNSKEY NSEC3PARAM
c0aa63slu8ucpmm55hrueae8tj5iopm2.me. 3600 IN RRSIG NSEC3 8 2 3600 20240107084911 20231217074911 12693 me. SlCrJFAW6wkIdqVsCrPl7ub7c2J9geCQRXUI+qc75UluKBEXMB09i2JT RWdCbpPNoOpIJIAAUjOStjDIGqTQK6eKyOnw1fKWVLAwKRLJnt0s5KTi SYAskFv9vd3bsKhGUBOEpdxloiMXe/bzG92v3pm7podfY2BJ3Ota5+Tf bxI=
utu6vkh4imogpsoglmuqj90a8f9m377u.me. 3600 IN NSEC3 1 1 100 332539EE7F95C32A UTVQH4ACRLEBR0LIOI6BKFJBJ61PCP52  NS DS RRSIG
utu6vkh4imogpsoglmuqj90a8f9m377u.me. 3600 IN RRSIG NSEC3 8 2 3600 20231230154517 20231209144517 12693 me. WUbzrcSNaBgDsIbIomedypjRjaqVo64z282lyC0UDPc6XlaGnvOMxFSS H3EsjR5YOrkmqxt9rUzXRH8qxgbJ6RfVIkW9TSQ6VCHqIL1FQ04nu7BV KiKArQM+niAtpoG3UrqVYHcEFugvsHt5eCR+e7o1ciE6/1MvGVvx/WPA y6I=
;; Received 585 bytes from 199.253.60.1#53(b0.nic.me) in 218 ms

这里的权威 DNS 服务是作者在域名提供商进行配置的,当有客户端请求 draveness.me 域名对应的 IP 地址时,其实会从作者使用的 DNS 服务商 DNSPod 处请求服务的 IP 地址:

draveness.me.           600     IN      A       167.179.82.83
draveness.me.           86400   IN      NS      ns3.dnsv2.com.
draveness.me.           86400   IN      NS      ns4.dnsv2.com.
;; Received 111 bytes from 125.94.59.205#53(ns3.dnsv2.com) in 30 ms

最终,DNS 解析器从 f1g1ns1.dnspod.net 服务中获取了当前博客的 IP 地址 123.56.94.228,浏览器或者其他设备就能够通过 IP 向服务器获取请求的内容了。

从整个解析过程,我们可以看出 DNS 域名服务器大体分成三类,根域名服务、顶级域名服务以及权威域名服务三种,获取域名对应的 IP 地址时,也会像遍历一棵树一样按照从顶层到底层的顺序依次请求不同的服务器。



二、CoreDNS

DNS 服务不是一个独立的系统服务,而是作为一种 addon 插件而存在,现在比较推荐的两个插件:kube-dns 和 CoreDNS,实际上在比较新点的版本中已经默认是 CoreDNS 了。

$ kubectl get pod -n kube-system -l k8s-app=kube-dns 
NAME                       READY   STATUS    RESTARTS   AGE
coredns-68599f8c7f-4p4c9   1/1     Running   0          124d
coredns-68599f8c7f-7gt6q   1/1     Running   0          124d


DNS服务在 kubernetes 中经历了三个阶段(SkyDNS-> KubeDNS-> CoreDNS):

  • 【第一阶段】在kubernetes 1.2版本时,dns服务使用的是由SkyDNS提供的,由4个容器组成:kube2sky、skydns、etcd和healthz。etcd存储dns记录;kube2sky监控service变化,生成dns记录;skydns读取服务,提供查询服务;healthz提供健康检查。
  • 【第二阶段】在kubernetes 1.4版本开始使用KubeDNS,有3个容器组成:kubedns、dnsmasq和sidecar。kubedns监控service变化,并记录到内存(存到内存提高性能)中;dnsmasq获取dns记录,提供dns缓存,提供dns查询服务;sidecar提供健康检查。
  • 【第三阶段】从kubernetes >=1.11版本开始,dns服务有CoreDNS提供,coredns支持自定义dns记录及配置upstream dns server,可以统一管理内部dns和物理dns。coredns只有一个coredns容器。下面是coredns的架构。

Kubernetes 网络之 DNS 介绍_DNS_03




CoreDNS 配置

CoreDNS 内部采用插件机制,所有功能都是插件形式编写,用户也可以扩展自己的插件,以下是 Kubernetes 部署 CoreDNS 时的默认配置:

$ kubectl get cm coredns -n kube-system -o yaml
apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors              # 启动错误记录
        health              # 启用健康检查检查端点,8080:health
        template ANY AAAA {
          rcode NOERROR
        }
        kubernetes cluster.local in-addr.arpa ip6.arpa {    # 处理 k8s 域名解析
          pods insecure
          upstream
          fallthrough in-addr.arpa ip6.arpa
          ttl 30
        }
        prometheus :9153                 # 启用 metrics 指标,9153:metrics
        forward . /etc/resolv.conf {     # 通过 resolv.conf 内的 nameservers 解析
          policy sequential
        }
        cache 30      # 启用缓存,所有内容限制为 30s 的TTL
        loop          # 检查简单的转发循环并停止服务
        reload        # 运行自动重新加载 corefile,热更新
        loadbalance   # 负载均衡,默认 round_robin
    }
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
  ...
  • 每个 {} 代表一个 zone,格式是 “Zone:port{}”, 其中"."代表默认 zone
  • {} 内的每个名称代表插件的名称,只有配置的插件才会启用,当解析域名时,会先匹配 zone(都未匹配会执行默认 zone),然后 zone 内的插件从上到下依次执行(这个顺序并不是配置文件内谁在前面的顺序,而是core/dnsserver/zdirectives.go内的顺序),匹配后返回处理(执行过的插件从下到上依次处理返回逻辑),不再执行下一个插件

CoreDNS的主要功能是通过插件系统实现的。它实现了一种链式插件的结构,将dns的逻辑抽象成了一个个插件。常见的插件如下:

  • loadbalance:提供基于dns的负载均衡功能
  • loop:检测在dns解析过程中出现的简单循环问题
  • cache:提供前端缓存功能
  • health:对Endpoint进行健康检查
  • kubernetes:从kubernetes中读取zone数据
  • etcd:从etcd读取zone数据,可以用于自定义域名记录
  • file:从文件中读取zone数据
  • hosts:使用/etc/hosts文件或者其他文件读取zone数据,可以用于自定义域名记录
  • auto:从磁盘中自动加载区域文件
  • reload:定时自动重新加载Corefile配置文件的内容
  • forward:转发域名查询到上游dns服务器
  • proxy:转发特定的域名查询到多个其他dns服务器,同时提供到多个dns服务器的负载均衡功能
  • prometheus:为prometheus系统提供采集性能指标数据的URL
  • pprof:在URL路径/debug/pprof下提供运行是的西能数据
  • log:对dns查询进行日志记录
  • errors:对错误信息镜像日志记录

Kubernetes 网络之 DNS 介绍_coredns_04


CoreDNS 扩展配置

可以根据自己的实际需求,针对不同场景来扩展 CoreDNS 的配置。


开启日志服务

如果需将 CoreDNS 每次域名解析的日志打印出来,可以开启 Log 插件,只需要在 Corefile 里加上 log 即可,示例配置如下:

apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        log             # 启动 log 插件
        health
        template ANY AAAA {
          rcode NOERROR
        }
        kubernetes cluster.local in-addr.arpa ip6.arpa {
          pods insecure
          upstream
          fallthrough in-addr.arpa ip6.arpa
          ttl 30
        }
        prometheus :9153
        forward . /etc/resolv.conf {
          policy sequential
        }
        cache 30
        loop
        reload
        loadbalance
    }
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
  ...


特定域名使用自定义 DNS 服务器

如果 example.com 类型后缀的域名需要使用自建 DNS 服务器(比如 10.10.0.10)进行解析的话,我们可为域名配置一个单独的服务块,示例配置如下:

apiVersion: v1
data:
  Corefile: |
    .:53 {
				# 省略
    }
    example.com:53 {
      errors
      cache 30
      forward . 10.10.0.10
      prefer_udp
    }
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
  ...


自定义 Hosts

如果需要为特定域名指定 hosts 映射,如为 www.example.com 指定 IP 为 127.0.0.1,那么可以使用 Hosts 插件来配置,示例配置如下:

Corefile: |
  .:53 {
      errors
      health {
         lameduck 15s
      }
      ready
      
      hosts {  												# 使用 hosts 插件
        127.0.0.1 www.example.com
        fallthrough
      }
    
      kubernetes cluster.local in-addr.arpa ip6.arpa {
        pods insecure
        fallthrough in-addr.arpa ip6.arpa
        ttl 30
      }
      prometheus :9153
      forward . /etc/resolv.conf {
        prefer_udp
      }
      cache 30
      loop
      reload
      loadbalance
  }


集群外部访问集群内服务

如果你希望在集群外的进程能够访问到集群内的服务,可以可以通过将节点的 /etc/resolv.conf 文件内 nameserver 配置为集群 kube-dns 的 ClusterIP 地址来达到目的,但不推荐该方式,如果你使用的是云服务器,在内网场景下,可以将集群内的服务通过内网 SLB 进行暴露,然后通过云服务商提供的解析服务添加 A 记录到该 SLB 的内网 IP 进行解析,比如阿里云的 PrivateZone 服务。



统一域名访问

可以实现在公网、内网和集群内部通过统一域名 foo.example.com 访问你的服务,原理如下:


  • 集群内的服务 foo.default.svc.cluster.local 通过公网 SLB 进行了暴露,且有域名 foo.example.com 解析到该公网 SLB 的 IP。
  • 集群内服务 foo.default.svc.cluster.local 通过内网 SLB 进行了暴露,且通过云解析 PrivateZone(比如在阿里云)在 VPC 内网中将 foo.example.com 解析到该内网 SLB 的 IP。
  • 在集群内部,可以通过 Rewrite 插件将 foo.example.com CNAME 到 foo.default.svc.cluster.local
Corefile: |
  .:53 {
      errors
      health {
         lameduck 15s
      }
      ready
      
      rewrite stop {
        name regex foo.example.com foo.default.svc.cluster.local
        answer name foo.default.svc.cluster.local foo.example.com 
      }

      kubernetes cluster.local in-addr.arpa ip6.arpa {
        pods insecure
        fallthrough in-addr.arpa ip6.arpa
        ttl 30
      }
      prometheus :9153
      forward . /etc/resolv.conf {
        prefer_udp
      }
      cache 30
      loop
      reload
      loadbalance
  }


禁止对 ipv6 类型的 AAAA 记录查询返回

如果 K8s 节点没有禁用 IPV6 的话,容器内进程请求 CoreDNS 时的默认行为是同时发起 IPV4 和 IPV6 解析,而通常我们只需要用到 IPV4,当容器请求某个域名时,CoreDNS 解析不到 IPV6 记录,就会 forward 到 upstream 去解析,如果到 upstream 需要经过较长时间(比如跨公网,跨机房专线),就会拖慢整个解析流程的速度,业务层面就会感知 DNS 解析慢。


CoreDNS 有一个 template 的插件,可以用它来禁用 IPV6 的解析,只需要给 CoreDNS 加上如下的配置:

Corefile: |
  .:53 {
      errors
      health {
        lameduck 15s
      }
      # 新增以下一行Template插件,其它数据请保持不变。
      template IN AAAA .
      # ......
  }

该配置的含义是在 CoreDNS 中将 AAAA 记录类型拦截,返回域名不存在,以减少不必要的网络通信。



CoreDNS 使用

CoreDNS 的 Service 地址一般情况下是固定的,类似于 kubernetes 这个 Service 地址一般就是第一个 IP 地址 10.96.0.1,CoreDNS 的 Service 地址就是 10.96.0.10,该 IP 被分配后,kubelet 会将使用 --cluster-dns=<dns-service-ip> 参数配置的 DNS 传递给每个容器。DNS 名称也需要域名,本地域可以使用参数--cluster-domain = <default-local-domain> 在 kubelet 中配置:

☸ ➜ cat /var/lib/kubelet/config.yaml
......
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local
......


我们前面说了如果我们建立的 Service 如果支持域名形式进行解析,就可以解决我们的服务发现的功能,那么利用 CoreDNS 可以将 Service 生成怎样的 DNS 记录呢?


  • 普通的 Service:会生成 servicename.namespace.svc.cluster.local 的域名,会解析到 Service 对应的 ClusterIP 上,在 Pod 之间的调用可以简写成 servicename.namespace,如果处于同一个命名空间下面,甚至可以只写成 servicename 即可访问
  • Headless Service:无头服务,就是把 clusterIP 设置为 None 的,会被解析为指定 Pod 的 IP 列表,同样还可以通过 podname.servicename.namespace.svc.cluster.local 访问到具体的某一个 Pod。


验证 CoreDNS 使用

  1. shell 到 Pod,先检查下本地的 DNS 配置,查看
~ # cat /etc/resolv.conf 
nameserver 172.17.0.2
search testing-mingzhu-1.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
~ # 


  1. 在 Pod 里执行 nslookup命令
~ # nslookup kubernetes.default
Server:         172.17.0.2
Address:        172.17.0.2:53


** server can't find kubernetes.default: NXDOMAIN


  1. 查看 CoreDNS
 % kubectl get svc  kube-dns -n kube-system 
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   172.17.0.2   <none>        53/UDP,53/TCP,9153/TCP   2y255d


  1. 访问别的namespace的服务
# wget -q -O- emqx-dashboard.wsj:18083

可以返回内容。



比如当我们执行 curl s 的时候,必须就有一个名为 s 的 Service 对象,在容器内部,会根据 /etc/resolve.conf 进行解析,使用 nameserver 10.96.0.10 进行解析,然后用字符串 s 依次带入 search 域进行查找,分别是:s.default.svc.cluster.local -> s.svc.cluster.local -> s.cluster.local,直到找到为止。


所以,我们执行 curl s,或者执行 curl s.default,都可以完成 DNS 请求,这 2 个不同的操作,会分别进行不同的 DNS 查找步骤:

// curl s,可以一次性找到(s + default.svc.cluster.local)
s.default.svc.cluster.local

// curl s.default,第一次找不到(s.default + default.svc.cluster.local)
s.default.default.svc.cluster.local
// 第二次查找可以找到(s.default + svc.cluster.local)
s.default.svc.cluster.local

那么问题来了,curl s 要比 curl s.default 效率高吗?

答案是肯定的,因为 curl s.default 多经过了一次 DNS 查询,当执行 curl s.default,也就使用了带有命名空间的内部域名时,容器的第一个 DNS 请求会将 s.default 当作服务的名字,第二个 DNS 请求会将 s.default 当作服务的名字.命名空间。


ndots

其实 /etc/resolv.conf 这个文件,并不止包含 nameserversearch 域,还包含了非常重要的一项:ndots

/ # cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
/ #

ndots:5 表示如果查询的域名包含的点 . 不到 5 个,那么进行 DNS 查找,将使用非完全限定名称(或者叫非绝对域名),如果你查询的域名包含点数大于等于 5,那么 DNS 查询,默认会使用绝对域名进行查询。

比如我们请求的域名是 a.b.c.d.e,这个域名中有 4 个点,那么容器中进行 DNS 请求时,会使用非绝对域名进行查找,使用非绝对域名,会按照 /etc/resolv.conf 中的 search 域,走一遍追加匹配:

a.b.c.d.e.default.svc.cluster.local.
a.b.c.d.e.svc.cluster.local.
a.b.c.d.e.cluster.local.

直到找到为止,如果走完了 search 域还找不到,则使用 a.b.c.d.e. 作为绝对域名进行 DNS 查找。


我们通过抓包分析一个具体案例,由于 CoreDNS 容器往往不具备 bash,所以无法通过 kubectl exec 的方式进入容器内抓包,我们可以给 CoreDNS 添加上 log 插件来记录解析日志。


域名中的点数对 DNS 解析的影响验证
  1. 查看 dns pod 的IP。

由于 CoreDNS 的 Pod 存在多个,那么 DNS 请求,可能会均分到所有 DNS 服务的容器上,我们如果只查看单个 DNS 服务容器抓到的包,可能就不全了,所以我们可以指定特定的一个 Pod 来进行 DNS 解析:

% kubectl get pod -n kube-system -l k8s-app=kube-dns -o wide
NAME                       READY   STATUS    RESTARTS   AGE    IP             NODE            NOMINATED NODE   READINESS GATES
coredns-68599f8c7f-4p4c9   1/1     Running   0          124d   10.23.173.48   10.23.111.202   <none>           <none>
coredns-68599f8c7f-7gt6q   1/1     Running   0          124d   10.23.177.37   10.23.157.219   <none>           <none>


  1. 域名点数 >5 的情况,进行 DNS 解析请求:
root@dnsutils:/# nslookup  a.b.c.d.ccccc 10.23.173.48
Server:         10.23.173.48
Address:        10.23.173.48#53

** server can't find a.b.c.d.ccccc: NXDOMAIN


  1. 查看对应 Pod 的日志
[INFO] 10.244.1.8:36066 - 32561 "A IN a.b.c.d.ccccc.default.svc.cluster.local. udp 57 false 512" NXDOMAIN qr,aa,rd 150 0.000878375s
[INFO] 10.244.1.8:46624 - 28834 "A IN a.b.c.d.ccccc.svc.cluster.local. udp 49 false 512" NXDOMAIN qr,aa,rd 142 0.000083375s
[INFO] 10.244.1.8:42002 - 46475 "A IN a.b.c.d.ccccc.cluster.local. udp 45 false 512" NXDOMAIN qr,aa,rd 138 0.000119708s
[INFO] 10.244.1.8:58041 - 58796 "A IN a.b.c.d.ccccc. udp 31 false 512" NXDOMAIN qr,rd,ra 31 0.269390917s

从上面的日志可以看出当点数少于 5 个的时候,先加上搜索域去进行解析的,解析失败后切换到下一个搜索域继续,最后再加一个 . 进行解析 a.b.c.d.ccccc.


  1. 我们可以再请求一个正常的域名验证下:
root@dnsutils:/# nslookup abc.com 10.23.173.48
Server:         10.23.173.48
Address:        10.23.173.48#53

Name:   abc.com
Address: 39.106.22.102

返回

[INFO] 10.244.1.8:42909 - 62036 "A IN abc.com.default.svc.cluster.local. udp 61 false 512" NXDOMAIN qr,aa,rd 154 0.000994042s
[INFO] 10.244.1.8:37866 - 24597 "A IN abc.com.svc.cluster.local. udp 53 false 512" NXDOMAIN qr,aa,rd 146 0.000103s
[INFO] 10.244.1.8:57768 - 45976 "A IN abc.com.cluster.local. udp 49 false 512" NXDOMAIN qr,aa,rd 142 0.000111083s
[INFO] 10.244.1.8:58678 - 3976 "A IN abc.com. udp 35 false 512" NOERROR qr,rd,ra 68 0.019599292s


可以看到仍然是先走 search 域最后再添加的一个 . 进行解析,也就是绝对域名,这样来看肯定就存在浪费请求的情况,当然我们在请求的时候直接手动在域名后面加上一个 . 直接变成绝对域名解析也是可以避免走 search 域的:

nslookup abc.com. 10.23.173.48

对应 dns 日志

[INFO] 10.244.1.8:55386 - 7685 "A IN abc.com. udp 35 false 512" NOERROR qr,rd,ra 68 0.022736709s


  1. 域名中点数>=5 个的情况,比如对域名 a.b.c.d.e.ccccc 进行 DNS 解析请求:
root@dnsutils:/# nslookup  a.b.c.d.e.ccccc 10.23.173.48
Server:         10.23.173.48
Address:        10.23.173.48#53

** server can't find a.b.c.d.e.ccccc: NXDOMAIN

root@dnsutils:/#

对应 CoreDNS 的日志如下所示:

[INFO] 10.244.1.8:44788 - 6603 "A IN a.b.c.d.e.ccccc. udp 33 false 512" NXDOMAIN qr,rd,ra 33 0.221383584s

可以看到现在没有进行 search 域匹配了。



Pod 的 DNS 策略

DNS 策略可以单独对 Pod 进行设定,目前 Kubernetes 支持以下特定 Pod 的 DNS 策略。这些策略可以在 Pod 规范中的 dnsPolicy 字段设置:


  • Default: 有人说 Default 的方式,是使用宿主机的方式,这种说法并不准确。这种方式其实是让 kubelet 来决定使用何种 DNS 策略。而 kubelet 默认的方式,就是使用宿主机的 /etc/resolv.conf(可能这就是有人说使用宿主机的 DNS 策略的方式吧),但是,kubelet 是可以灵活来配置使用什么文件来进行 DNS 策略的,我们完全可以使用 kubelet 的参数 –resolv-conf=/etc/resolv.conf 来决定你的 DNS 解析文件地址。
  • ClusterFirst: 这种方式,表示 Pod 内的 DNS 使用集群中配置的 DNS 服务,简单来说,就是使用 Kubernetes 中 kubedns 或 coredns 服务进行域名解析。如果解析不成功,才会使用宿主机的 DNS 配置进行解析。
  • ClusterFirstWithHostNet:在某些场景下,我们的 Pod 是用 HostNetwork 模式启动的,一旦用 HostNetwork 模式,表示这个 Pod 中的所有容器,都要使用宿主机的 /etc/resolv.conf 配置进行 DNS 查询,但如果你还想继续使用 Kubernetes 的 DNS 服务,那就将 dnsPolicy 设置为 ClusterFirstWithHostNet
  • None: 表示空的 DNS 设置,这种方式一般用于想要自定义 DNS 配置的场景,往往需要和 dnsConfig 配合一起使用达到自定义 DNS 的目的。

需要注意的是 Default 并不是默认的 DNS 策略,如果未明确指定 dnsPolicy,则使用 ClusterFirst


下面的示例显示了一个 Pod,其 DNS 策略设置为 ClusterFirstWithHostNet,因为它已将 hostNetwork 设置为 true。

apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
    - image: busybox:1.28
      command:
        - sleep
        - "3600"
      imagePullPolicy: IfNotPresent
      name: busybox
  restartPolicy: Always
  hostNetwork: true
  dnsPolicy: ClusterFirstWithHostNet


Pod 的 DNS 配置

Pod 的 DNS 配置可让用户对 Pod 的 DNS 设置进行更多控制。dnsConfig 字段是可选的,它可以与任何 dnsPolicy 设置一起使用。 但是,当 Pod 的 dnsPolicy 设置为 "None" 时,必须指定 dnsConfig 字段


用户可以在 dnsConfig 字段中指定以下属性:

  • nameservers:将用作于 Pod 的 DNS 服务器的 IP 地址列表,最多可以指定 3 个 IP 地址。当 Pod 的 dnsPolicy 设置为 "None" 时,列表必须至少包含一个 IP 地址,否则此属性是可选的。所列出的服务器将合并到从指定的 DNS 策略生成的基本名称服务器,并删除重复的地址。
  • searches:用于在 Pod 中查找主机名的 DNS 搜索域的列表。此属性是可选的。 指定此属性时,所提供的列表将合并到根据所选 DNS 策略生成的基本搜索域名中。重复的域名将被删除,Kubernetes 最多允许 6 个搜索域。
  • options:可选的对象列表,其中每个对象可能具有 name 属性(必需)和 value 属性(可选)。此属性中的内容将合并到从指定的 DNS 策略生成的选项。重复的条目将被删除。


以下是具有自定义 DNS 设置的 Pod 示例:

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-domain.example
      - my.dns.search.suffix
    options:
      - name: ndots
        value: "2"
      - name: edns0

创建上面的 Pod 后,容器 test 会在其 /etc/resolv.conf 文件中获取以下内容:

nameserver 1.2.3.4
search ns1.svc.cluster-domain.example my.dns.search.suffix
options ndots:2 edns0



参考:

知乎 - kubernetes 服务发现

Core-DNS

Kubernetes(k8s)DNS(CoreDNS)介绍

详解 DNS 与 CoreDNS 的实现原理