参考:

一.Kubernetes DNS服务发展史


从Kubernetes 1.11开始,可使用CoreDNS作为Kubernetes的DNS插件进入GA状态,Kubernetes推荐使用CoreDNS作为集群内的DNS服务。 我们先看一下Kubernetes DNS服务的发展历程。

1.1 Kubernetes 1.3之前的版本 – skyDNS

Kubernetes 1.3之前的版本使用skyDNS作为DNS服务,这个有点久远了。Kubernetes的DNS服务由kube2sky、skyDNS、etcd组成。 kube2sky通过kube-apiserver监听集群中Service的变化,将生成的DNS记录信息更新到etcd中,而skyDNS将从etcd中获取数据对外提供DNS的查询服务。

1.2 Kubernetes 1.3版本开始 – kubeDNS

Kubernetes 1.3开始使用kubeDNS和dnsmasq替换了原来的kube2sky和skyDNS,不再使用etcd,而是将DNS记录直接存放在内存中,通过dnsmasq的缓存功能提高DNS的查询效率。下图是描述了Kubernetes使用kubeDNS实现服务发现的整体架构:

k8s dns域名 负载均衡 k8s内部dns_贪心算法

1.3 Kubernetes 1.11版本开始 – CoreDNS进入GA

从Kubernetes 1.11开始,可使用CoreDNS作为Kubernetes的DNS插件进入GA状态,Kubernetes推荐使用CoreDNS作为集群内的DNS服务。 CoreDNS从2017年初就成为了CNCF的的孵化项目,CoreDNS的特点就是十分灵活和可扩展的插件机制,

各种插件实现:

k8s dns域名 负载均衡 k8s内部dns_k8s dns域名 负载均衡_02

不同的功能,如重定向、定制DNS记录、记录日志等等。下图描述了CoreDNS的整体架构:

k8s dns域名 负载均衡 k8s内部dns_DNS_03

二、CoreDNS简介


     Kubernetes包括用于服务发现的DNS服务器Kube-DNS。 该DNS服务器利用SkyDNS的库来为Kubernetes pod和服务提供DNS请求。SkyDNS2的作者,Miek Gieben,创建了一个新的DNS服务器,CoreDNS,它采用更模块化,可扩展的框架构建。 Infoblox已经与Miek合作,将此DNS服务器作为Kube-DNS的替代品。

。虽然它一开始作为Web服务器,但是Caddy并不是专门针对HTTP协议的,而是构建了一个基于CoreDNS的理想框架。

      在这种灵活的模型中添加对Kubernetes的支持,相当于创建了一个Kubernetes中间件。该中间件使用Kubernetes API来满足针对特定Kubernetes pod或服务的DNS请求。而且由于Kube-DNS作为Kubernetes的另一项服务,kubelet和Kube-DNS之间没有紧密的绑定。您只需要将DNS服务的IP地址和域名传递给kubelet,而Kubernetes并不关心谁在实际处理该IP请求。

1、CoreDNS支持行为

1.0.0版本主要遵循Kube-DNS的当前行为。 CoreDNS的005及更高版本实现了完整的规范和更多功能。

  • A记录(正常的Service分配了一个名为my-svc.my-namespace.svc.cluster.local的DNS A记录。 这解决了服务的集群IP)
  • “headless”(没有集群IP)的Service也分配了一个名为my-svc.my-namespace.svc.cluster.local的DNS A记录。 与普通服务不同,这解决了Service选择了pods的一组IP。 客户预计将从这ip集合中消耗集合或使用标准循环选择。
  • 针对名为正常或无头服务的端口创建的SRV记录,对于每个命名的端口,SRV记录的格式为_my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster.local。对于常规服务,这将解析为端口号和CNAME:my-svc.my-namespace.svc.cluster.local;对于无头服务,这解决了多个答案,一个用于支持服务的每个pod,并包含端口号还有格式为auto-generated-name.my-svc.my-namespace.svc.cluster.local 的pod的CNAME 。SRV记录包含它们中的“svc”段,对于省略“svc”段的旧式CNAME不支持。
  • 作为Service一部分的endpoints的A记录(比如“pets”的记录)
  • pod的Spec中描述的A记录
  • 还有就是用来发现正在使用的DNS模式版本的TXT记录

      所有群集中不需要pod A记录支持,默认情况下禁用。 此外,CoreDNS对此用例的支持超出了在Kube-DNS中找到的标准行为。

     在Kube-DNS中,这些记录不反映集群的状态,例如,对w-x-y-z.namespace.pod.cluster.local的任何查询将返回带有w.x.y.z(ip)的A记录,即使该IP不属于指定的命名空间,甚至不属于集群地址空间。最初的想法是启用对* .namespace.pod.cluster.local这样的域使用通配符SSL证书。

    CoreDNS集成了提供pod验证的选项,验证返回的IP地址w.x.y.z实际上是指定命名空间中的pod的IP。他防止在命名空间中欺骗DNS名称。 然而,它确实会大大增加CoreDNS实例的内存占用,因为现在它需要观察所有的pod,而不仅仅是服务端点。

2、架构

整个 CoreDNS 服务都建立在一个使用 Go 编写的 HTTP/2 Web 服务器 Caddy · GitHub 上,CoreDNS 整个项目可以作为一个 Caddy 的教科书用法。

k8s dns域名 负载均衡 k8s内部dns_kubernetes_04

CoreDNS 的大多数功能都是由插件来实现的,插件和服务本身都使用了 Caddy 提供的一些功能,所以项目本身也不是特别的复杂。

3、插件

作为基于 Caddy 的 Web 服务器,CoreDNS 实现了一个插件链的架构,将很多 DNS 相关的逻辑都抽象成了一层一层的插件,包括 Kubernetes 等功能,每一个插件都是一个遵循如下协议的结构体:

type (
	Plugin func(Handler) Handler

	Handler interface {
		ServeDNS(context.Context, dns.ResponseWriter, *dns.Msg) (int, error)
		Name() string
	}
)

所以只需要为插件实现 ServeDNS 以及 Name 这两个接口并且写一些用于配置的代码就可以将插件集成到 CoreDNS 中。

4、Corefile

另一个 CoreDNS 的特点就是它能够通过简单易懂的 DSL 定义 DNS 服务,在 Corefile 中就可以组合多个插件对外提供服务:

coredns.io:5300 {
    file db.coredns.io
}

example.io:53 {
    log
    errors
    file db.example.io
}

example.net:53 {
    file db.example.net
}

.:53 {
    kubernetes
    proxy . 8.8.8.8
    log
    errors
    cache
}

对于以上的配置文件,CoreDNS 会根据每一个代码块前面的区和端点对外暴露两个端点提供服务:

k8s dns域名 负载均衡 k8s内部dns_服务发现_05

该配置文件对外暴露了两个 DNS 服务,其中一个监听在 5300 端口,另一个在 53 端口,请求这两个服务时会根据不同的域名选择不同区中的插件进行处理。

原理

CoreDNS 可以通过四种方式对外直接提供 DNS 服务,分别是 UDP、gRPC、HTTPS 和 TLS:

k8s dns域名 负载均衡 k8s内部dns_贪心算法_06

但是无论哪种类型的 DNS 服务,最终队会调用以下的 ServeDNS 方法,为服务的调用者提供 DNS 服务:

func (s *Server) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) {
	m, _ := edns.Version(r)

	ctx, _ := incrementDepthAndCheck(ctx)

	b := r.Question[0].Name
	var off int
	var end bool

	var dshandler *Config

	w = request.NewScrubWriter(r, w)

	for {
		if h, ok := s.zones[string(b[:l])]; ok {
			ctx = context.WithValue(ctx, plugin.ServerCtx{}, s.Addr)
			if r.Question[0].Qtype != dns.TypeDS {
				rcode, _ := h.pluginChain.ServeDNS(ctx, w, r)
 			dshandler = h
		}
		off, end = dns.NextLabel(q, off)
		if end {
			break
		}
	}

	if r.Question[0].Qtype == dns.TypeDS && dshandler != nil && dshandler.pluginChain != nil {
		rcode, _ := dshandler.pluginChain.ServeDNS(ctx, w, r)
		plugin.ClientWrite(rcode)
		return
	}

	if h, ok := s.zones["."]; ok && h.pluginChain != nil {
		ctx = context.WithValue(ctx, plugin.ServerCtx{}, s.Addr)

		rcode, _ := h.pluginChain.ServeDNS(ctx, w, r)
		plugin.ClientWrite(rcode)
		return
	}
}

在上述这个已经被简化的复杂函数中,最重要的就是调用了『插件链』的 ServeDNS 方法,将来源的请求交给一系列插件进行处理,如果我们使用以下的文件作为 Corefile:

example.org {
    file /usr/local/etc/coredns/example.org
    prometheus     # enable metrics
    errors         # show errors
    log            # enable query logs
}

那么在 CoreDNS 服务启动时,对于当前的 example.org 这个组,它会依次加载 filelogerrors 和 prometheus 几个插件,这里的顺序是由 zdirectives.go 文件定义的,启动的顺序是从下到上:

var Directives = []string{
  // ...
	"prometheus",
	"errors",
	"log",
  // ...
	"file",
  // ...
	"whoami",
	"on",
}

因为启动的时候会按照从下到上的顺序依次『包装』每一个插件,所以在真正调用时就是从上到下执行的,这就是因为 NewServer 方法中对插件进行了组合:

func NewServer(addr string, group []*Config) (*Server, error) {
	s := &Server{
		Addr:        addr,
		zones:       make(map[string]*Config),
		connTimeout: 5 * time.Second,
	}

	for _, site := range group {
		s.zones[site.Zone] = site
		if site.registry != nil {
			for name := range enableChaos {
				if _, ok := site.registry[name]; ok {
					s.classChaos = true
					break
				}
			}
		}
		var stack plugin.Handler
		for i := len(site.Plugin) - 1; i >= 0; i-- {
			stack = site.Plugin[i](stack)
			site.registerHandler(stack)
		}
		site.pluginChain = stack
	}

	return s, nil
}

对于 Corefile 里面的每一个配置组,NewServer 都会讲配置组中提及的插件按照一定的顺序组合起来,原理跟 Rack Middleware 的机制非常相似,插件 Plugin 其实就是一个出入参数都是 Handler 的函数:

type (
	Plugin func(Handler) Handler

	Handler interface {
		ServeDNS(context.Context, dns.ResponseWriter, *dns.Msg) (int, error)
		Name() string
	}
)

所以我们可以将它们叠成堆栈的方式对它们进行操作,这样在最后就会形成一个插件的调用链,在每个插件执行方法时都可以通过 NextOrFailure 函数调用下一个插件的 ServerDNS 方法:

func NextOrFailure(name string, next Handler, ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
	if next != nil {
		if span := ot.SpanFromContext(ctx); span != nil {
			child := span.Tracer().StartSpan(next.Name(), ot.ChildOf(span.Context()))
			defer child.Finish()
			ctx = ot.ContextWithSpan(ctx, child)
		}
		return next.ServeDNS(ctx, w, r)
	}

	return dns.RcodeServerFailure, Error(name, errors.New("no next plugin found"))
}

除了通过 ServeDNS 调用下一个插件之外,我们也可以调用 WriteMsg 方法并结束整个调用链。

k8s dns域名 负载均衡 k8s内部dns_k8s dns域名 负载均衡_07

从插件的堆叠到顺序调用以及错误处理,我们对 CoreDNS 的工作原理已经非常清楚了,接下来我们可以简单介绍几个插件的作用。

三、在kubernetes中部署coredns


1、下载coredns部署包并说明

https://github.com/coredns/deployment/tree/master/kubernetes

注意和k8s的版本对应关系:https://github.com/coredns/deployment/blob/master/kubernetes/CoreDNS-k8s_version.md

主要有几个文件:

k8s dns域名 负载均衡 k8s内部dns_服务发现_08

      deploy.sh是一个便捷的脚本,用于生成用于在当前运行标准kube-dns的集群上运行CoreDNS的清单。使用coredns.yaml.sed文件作为模板,它创建一个ConfigMap和一个CoreDNS  deployment,然后更新 Kube-DNS service selector以使用CoreDNS deployment。 通过重新使用现有服务,服务请求不会中断。

    脚本不会删除kube-dns的deployment或replication controller - 您必须手动执行:

kubectl delete --namespace=kube-system deployment kube-dns

要使用它,只需将它们放在同一目录中,然后运行deploy.sh脚本,将其传递给您的服务CIDR(10.3.0.0/24)。 这将生成具有必要Corefile的ConfigMap。 它还将查找现有的kube-dns服务的集群IP。 

[root@k8s-master conf.d]# etcdctl ls /k8s/network/subnets
/k8s/network/subnets/10.0.24.0-24
/k8s/network/subnets/10.0.86.0-24
/k8s/network/subnets/10.0.35.0-24

(注意:以上原始脚本只适用于当前kubernetes集群含有kube-dns的情况,如果没有需要修改下脚本

#!/bin/bash

# Deploys CoreDNS to a cluster currently running Kube-DNS.

SERVICE_CIDR=$1
CLUSTER_DOMAIN=${2:-cluster.local}
YAML_TEMPLATE=${3:-`pwd`/coredns.yaml.sed}
YAML=${4:-`pwd`/coredns.yaml}

if [[ -z $SERVICE_CIDR ]]; then
echo "Usage: $0 SERVICE-CIDR [ CLUSTER-DOMAIN ] [ YAML-TEMPLATE ] [ YAML ]"
exit 1
fi

#CLUSTER_DNS_IP=$(kubectl get service --namespace kube-system kube-dns -o jsonpath="{.spec.clusterIP}")
CLUSTER_DNS_IP=10.0.24.200

默认情况下CLUSTER_DNS_IP是自动获取kube-dns的集群ip的,但是由于没有部署kube-dns所以只能手动指定一个集群ip了。

执行: ./deploy.sh 10.0.0.0/24 cluster.local 

以上脚本执行后可以看到预览的效果。

仔细观察上面的Corefile部分,这是一个在端口53上运行CoreDNS并为Kubernetes提供cluster.local域的示例

.:53 { errors log stdout health kubernetes cluster.local 10.3.0.0/24 proxy . /etc/resolv.conf cache 30 }

1)errors官方没有明确解释,后面研究

2)log stdout:日志中间件配置为将日志写入STDOUT

3)health:健康检查,提供了指定端口(默认为8080)上的HTTP端点,如果实例是健康的,则返回“OK”。

4)cluster.local:CoreDNS为kubernetes提供的域,10.3.0.0/24这告诉Kubernetes中间件它负责为反向区域提供PTR请求0.0.3.10.in-addr.arpa ..换句话说,这是允许反向DNS解析服务(我们经常使用到得DNS服务器里面有两个区域,即“正向查找区域”和“反向查找区域”,正向查找区域就是我们通常所说的域名解析,反向查找区域即是这里所说的IP反向解析,它的作用就是通过查询IP地址的PTR记录来得到该IP地址指向的域名,当然,要成功得到域名就必需要有该IP地址的PTR记录。PTR记录是邮件交换记录的一种,邮件交换记录中有A记录和PTR记录,A记录解析名字到地址,而PTR记录解析地址到名字。地址是指一个客户端的IP地址,名字是指一个客户的完全合格域名。通过对PTR记录的查询,达到反查的目的。)

5)proxy:这可以配置多个upstream 域名服务器,也可以用于延迟查找 /etc/resolv.conf 中定义的域名服务器

6)cache:这允许缓存两个响应结果,一个是肯定结果(即,查询返回一个结果)和否定结果(查询返回“没有这样的域”),具有单独的高速缓存大小和TTLs。

2、现在安装coredns到kubernetes中

#    ./deploy.sh -r 10.0.0.0/16 -i 192.168.10.90  -d cluster.local -t coredns.yaml.sed -s  > coredns.yaml

#   .kubectl apply -f coredns.yaml

或者直接执行:

# ./deploy.sh -r 10.0.0.0/16 -i 192.168.10.90  -d cluster.local -t coredns.yaml.sed -s  | kubectl apply -f -

查看service

[root@k8s-master coredns]# kubectl get service -l k8s-app=kube-dns --namespace=kube-system
NAME       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   192.168.10.90   <none>        53/UDP,53/TCP,9153/TCP   15d

查看coredns的Pod,确认所有Pod都处于Running状态:

kubectl get pods -n kube-system -l k8s-app=kube-dns
查看日志:# kubectl logs -f coredns-6cc7bf59f4-4rfq8 --namespace=kube-system

3、修改cluster-dns

方式一kubelet统一修改:

kubelet的启动参数添加--cluster-dns配置:修改master节点和所有node节点--cluster-dns配置,修改内容如红色所注,与上面的Corefile中的值对应。

k8s dns域名 负载均衡 k8s内部dns_贪心算法_09

方式二:在pod启动添加

apiVersion: v1
kind: Pod
metadata:
  name: springbootweb
spec:
  containers:
    - name: springbootweb
      image: registry.xxxxx.com/springboot:latest
      ports:
      - containerPort: 9081
  imagePullSecrets:
    - name: registry-key-secret
  dnsConfig:
    nameservers:
      - 172.16.1.21
    searches:
      - ns1.svc.cluster.local
      - my.dns.search.suffix
    options:
      - name: ndots
        value: "2"
      - name: edns0

通过上述配置创建 Pod 之后,执行 kubectl exec dockerId cat /etc/resolv.conf 命令即可看到额外的配置项目

即在 nameservers 中多了 172.16.1.21、 search 中多了 ns1.svc.cluster.localmy.dns.search.suffix 两项值,及多了 options ndots:2 edns0 配置。

因此,直接通过 dnsConfig 进行配置后,默认是追加到当前默认配置中。

4、测试CoreDNS

现在我们来创建一个wepapp的pod和service,测试一下coredns是否起作用

[root@k8s-master conf.d]# kubectl get pods  -o wide                           
NAME           READY     STATUS    RESTARTS   AGE       IP          NODE
webapp-nrz4t   1/1       Running   0          1d        10.0.35.3   192.168.10.39
webapp-zp69q   1/1       Running   0          1d        10.0.24.4   192.168.10.50

[root@k8s-master ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
kubernetes   ClusterIP   192.168.0.1      <none>        443/TCP    28d
webapp       ClusterIP   192.168.14.242   <none>        9081/TCP   17d
webapp2      ClusterIP   192.168.22.2     <none>        9082/TCP   17d

[root@k8s-master ~]# curl 192.168.22.2:9082
Hello world

k8s dns域名 负载均衡 k8s内部dns_服务发现_10

1、检查集群的pod /etc/resolv.conf是否生效:

首先进入这个集群内的另一个pod

kubectl exec -it webapp-nrz4t /bin/sh 或者 docker exec -it 0d0874df9e15 /bin/sh

$ cat /etc/resolv.conf

sh-4.2#  cat /etc/resolv.conf
nameserver 192.168.10.90
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

2、curl测试服务:

curl webapp.default.svc.cluster.local:9081

3、在master的主机上修改 /etc/resolv.conf,增加一行:nameserver 192.168.10.90后执行

curl webapp.default.svc.cluster.local:9081

k8s dns域名 负载均衡 k8s内部dns_kubernetes_11

通过域名访问成功!

三、DNS 策略


我们在kubelet的启动参数添加--cluster-dns配置--cluster-dns=172.16.10.254。在Kubelet配置中通过使用–cluster-dns=10.10.10.2参数指定的coredns地址会被写入容器的/etc/resolv.conf配置中。

k8s dns域名 负载均衡 k8s内部dns_DNS_12

在 kubernetes 中还提供了 dnsPolicy 决定 Pod 内预设4种DNS策略:

None,无任何策略:表示空的DNS设置,这种方式一般用于想要自定义 DNS 配置的场景,往往需要和 dnsConfig 配合一起使用达到自定义 DNS 的目的。

Default,默认: 此种方式是让kubelet来决定使用何种DNS策略。而kubelet默认的方式,就是使用宿主机的/etc/resolv.conf文件。同时,kubelet也可以配置指定的DNS策略文件,使用kubelet参数即可,如:–resolv-conf=/etc/resolv.conf

ClusterFirst, 集群 DNS 优先:此种方式是使用kubernets集群内部中的kubedns或coredns服务进行域名解析。若解析不成功,才会使用宿主机的DNS配置来进行解析。

ClusterFistWithHostNet,集群 DNS 优先,并伴随着使用宿主机网络:在某些场景下,我们的 POD 是用 HOST 模式启动的(HOST模式,是共享宿主机网络的),一旦用 HOST 模式,表示这个 POD 中的所有容器,都要使用宿主机的 /etc/resolv.conf 配置进行DNS查询,但如果你想使用了 HOST 模式,还继续使用 Kubernetes 的DNS服务,那就将 dnsPolicy 设置为 ClusterFirstWithHostNet。

1、无策略 (None)

清除 Pod 预设 DNS 配置,当 dnsPolicy 设置成为这个值之后, kubernetes 不会为 Pod 预先加载任何逻辑用于判定得到 DNS 的配置。因此若将 dnsPolicy 设置为 None , 为了避免 Pod 里面没有 DNS 配置,最好通过 dnsConfig 来描述自定义的 DNS 参数。如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: springbootweb
spec:
  containers:
    - name: springbootweb
      image: registry.xxxx.com/springboot:latest
      ports:
      - containerPort: 9081
  imagePullSecrets:
    - name: registry-key-secret
  dnsConfig:
    nameservers:
      - 172.16.1.21
    searches:
      - ns1.svc.cluster.local
      - my.dns.search.suffix
    options:
      - name: ndots
        value: "2"
      - name: edns0

通过上述配置创建 Pod 之后,执行 kubectl exec demo cat /etc/resolv.conf 命令即可看到额外的配置项目,如下:

k8s dns域名 负载均衡 k8s内部dns_kubernetes_13

2、默认预设 (Default)

Pod 里面的 DNS 配置继承了宿主机上的 DNS 配置。即,该 Pod 的 DNS 配置与宿主机完全一致。

apiVersion: v1
kind: Pod
metadata:
  name: springbootweb
spec:
  containers:
    - name: springbootweb
      image: registry.xxxx.com/springboot:latest
      ports:
      - containerPort: 9081
  imagePullSecrets:
    - name: registry-key-secret
  dnsPolicy: Default

查看到宿主机上的配置如下: cat /etc/resolv.conf

k8s dns域名 负载均衡 k8s内部dns_服务发现_14

通过上述配置创建 Pod 之后即可看到额外的配置项目,如下:

k8s dns域名 负载均衡 k8s内部dns_服务发现_15

注意:这个会覆盖--cluster-dns配置

3、集群优先 (ClusterFirst)

与 Default 相反,会预先使用 kube-dns (或 CoreDNS ) 的信息当预设置参数写入到该 Pod 内的DNS配置。

apiVersion: v1
kind: Pod
metadata:
  name: springbootweb
spec:
  containers:
    - name: springbootweb
      image: registry.xxxxx.com/springboot:latest
      ports:
      - containerPort: 9081
  imagePullSecrets:
    - name: registry-key-secret
  dnsPolicy: ClusterFirst

通过上述配置创建 Pod 之后,执行 kubectl exec springbootweb cat /etc/resolv.conf 命令即可看到额外的配置项目,如下:

nameserver 172.16.10.254
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

如设置了 hostNetwork = true 时,ClusterFirst 会被强制转化为 Default 。如下:

apiVersion: v1
kind: Pod
metadata:
  name: springbootweb
spec:
  containers:
    - name: springbootweb
      image: registry.xxxxx.com/springboot:latest
      ports:
      - containerPort: 9081
  imagePullSecrets:
    - name: registry-key-secret
  hostNetwork: true
  dnsPolicy: ClusterFirst

通过上述配置创建 Pod 之后,执行 kubectl exec springbootweb cat /etc/resolv.conf 命令即可看到额外的配置项目,如下:

nameserver 172.16.1.21
nameserver 119.29.29.29

设置 hostNetwork = true 之后,会让 Pod 与该节点公用相同的网络空间(网卡/路由等)

4、宿主机与 Kubernetes 共存 ( ClusterFirstWithHostNet )

同时使用 hostNetworkkube-dns 作为 Pod 预设 DNS 配置。

apiVersion: v1
kind: Pod
metadata:
  name: springbootweb
spec:
  containers:
    - name: springbootweb
      image: registry.xxxx.com/springboot:latest
      ports:
      - containerPort: 9081
  imagePullSecrets:
    - name: registry-key-secret
  hostNetwork: true
  dnsPolicy: ClusterFirstWithHostNet

通过上述配置创建 Pod 之后,执行 kubectl exec demo cat /etc/resolv.conf 命令即可看到额外的配置项目,如下:

k8s dns域名 负载均衡 k8s内部dns_kubernetes_16

四、coredns 自定义解析域名


1、CoreDNS ConfigMap选项

CoreDNS是一个模块化和可插拔的DNS服务器,每个插件都为CoreDNS添加了新功能。这可以通过维护Corefile来配置,Corefile是CoreDNS配置文件。集群管理员可以修改CoreDNS Corefile的ConfigMap以更改服务发现的工作方式。

在Kubernetes中,CoreDNS安装了以下默认的Corefile配置。先来看看默认的CoreDns的配置文件:

kubectl edit configmap coredns -n kube-system, 红框部分是新加的:

k8s dns域名 负载均衡 k8s内部dns_k8s dns域名 负载均衡_17

  • error: 错误记录到stdout
  • health:CoreDNS的运行状况报告为http:// localhost:8080 / health
  • kubernetes:CoreDNS将根据Kubernetes服务和pod的IP回复DNS查询
  • prometheus:CoreDNS的度量标准可以在http://localhost:9153/Prometheus格式的指标中找到
  • forward:任何不在Kubernetes集群域内的查询都将转发到预定义的解析器(/etc/resolv.conf)
  • cache:启用前端缓存
  • loop:检测简单的转发循环,如果找到循环则停止CoreDNS进程
  • reload:允许自动重新加载已更改的Corefile。编辑ConfigMap配置后,请等待两分钟以使更改生效
  • loadbalance:这是一个循环DNS负载均衡器,可以在答案中随机化A,AAAA和MX记录的顺序

注意:CoreDNS 1.5.0以后不在使用proxy关键字,而是使用forward。

我们强制所有非集群 DNS 查找通过特定的域名服务器(位于172.16.1.21)来解析,将forward 指向域名服务器,而不是 /etc/resolv.conf。

如果域名后缀都为.xxxapi.com,并通过172.16.1.21域名服务器来解析,

在Corefile配置如下:

   xxxapi.com:53 {
        errors
        cache 30
        forward . 172.16.1.21
 }

即上面截图。

然后可以访问进入容器:docker exec -it 7d9ba3b6acdf /bin/sh 可以ping通了:

k8s dns域名 负载均衡 k8s内部dns_kubernetes_18

问题排查技巧


如果执行 nslookup 命令失败,检查如下内容:

1、先检查本地 DNS 配置

查看配置文件 resolv.conf。

k8s dns域名 负载均衡 k8s内部dns_kubernetes_19

按照如下方法(注意搜索路径可能会因为云提供商不同而变化)验证搜索路径和 Name Server 的建立:

nameserver 192.168.10.90
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

2、快速诊断

出现类似如下指示的错误,说明 kube-dns 插件或相关 Service 存在问题:

k8s dns域名 负载均衡 k8s内部dns_kubernetes_20

检查service是否正常运行:

kubectl get svc --namespace=kube-system

k8s dns域名 负载均衡 k8s内部dns_kubernetes_21

或者检查是否 DNS Pod 正在运行

使用 kubectl get pods 命令验证 DNS Pod 正在运行:

kubectl get pods --namespace=kube-system -l k8s-app=kube-dns

应该能够看到类似如下信息:

k8s dns域名 负载均衡 k8s内部dns_kubernetes_22

如果看到没有 Pod 运行,或 Pod 失败/结束,DNS 插件不能默认部署到当前的环境,必须手动部署。

或者查看service的Endpoint是否正常:

kubectl get ep kube-dns --namespace=kube-system

k8s dns域名 负载均衡 k8s内部dns_DNS_23

如果没有看到 Endpoint,查看 调试 Service 文档 中的 Endpoint 段内容。

3、检查 DNS Pod 中的错误信息

使用 kubectl logs  命令查看 DNS 后台进程的日志:

kubectl logs coredns-6cc7bf59f4-vj7cc -n kube-system

看到错误信息:

eflector.go:134] pkg/mod/k8s.io/client-go@v10.0.0+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Namespace: Get https://192.168.0.1:443/api/v1/namespace

2.168.0.1:443/api/v1/endpoints?limit=500&resourceVersion=0: x509: certificate is valid for 192.168.10.50, 10.0.0.1, not 192.168.0.1
E0710 16:00:58.986103       1 reflector.go:134] pkg/mod/k8s.io/client-go@v10.0.0+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Namespace: Get https://192.168.0.1:443/api/v1/namespaces?limit=500&resourceVersion=0: x509: certificate is valid for 192.168.10.50, 10.0.0.1, not 192.168.0.1
E0710 16:00:58.989633       1 reflector.go:134] pkg/mod/k8s.io/client-go@v10.0.0+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Service: Get https://192.168.0.1:443/api/v1/services?limit=500&resourceVersion=0: x509: certificate is valid for 192.168.10.50, 10.0.0.1, not 192.168.0.1
E0710 16:00:58.991655       1 reflector.go:134] pkg/mod/k8s.io/client-go@v10.0.0+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Endpoints: Get https://192.168.0.1:443/api/v1/endpoints?limit=500&resourceVersion=0: x509: certificate is valid for 192.168.10.50, 10.0.0.1, not 192.168.0.1
E0710 16:00:59.990732       1 reflector.go:134] pkg/mod/k8s.io/client-go@v10.0.0+inco

原因:192.168.0.1不在证书里面

解决:需要重新设置:apiserver证书

masterssl.cnf文件的示例如下:IP.3 = 192.168.0.1

[req]
 req_extensions = v3_req
 distinguished_name = req_distinguished_name
 [req_distinguished_name]
 [ v3_req ]
 basicConstraints = CA:FALSE
 keyUsage = nonRepudiation, digitalSignature, keyEncipherment
 subjectAltName = @alt_names
 [alt_names]
 DNS.1 = kubernetes
 DNS.2 = kubernetes.default
 DNS.3 = kubernetes.default.svc
 DNS.4 = kubernetes.default.svc.cluster.local
 IP.1 = ${K8S_SERVICE_IP}
 IP.2 = ${MASTER_IPV4}IP.3 = 192.168.0.1

错误信息:
 

E0712 02:25:45.326123       1 reflector.go:134] pkg/mod/k8s.io/client-go@v10.0.0+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Service: Unauthorized
 E0712 02:25:45.327038       1 reflector.go:134] pkg/mod/k8s.io/client-go@v10.0.0+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Endpoints: Unauthorized
 E0712 02:25:45.328029       1 reflector.go:134] pkg/mod/k8s.io/client-go@v10.0.0+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Namespace: Unauthorized
 E0712 02:25:46.327153       1 reflector.go:134] pkg/mod/k8s.io/client-go@v10.0.0+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Service: Unauthorized
 E0712 02:25:46.328210       1 reflector.go:134] pkg/mod/k8s.io/client-go@v10.0.0+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Endpoints: Unauthorized
 E0712 02:25:46.329160       1 reflector.go:134] pkg/mod/k8s.io/client-go@v10.0.0+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Namespace: Unauthorized
 E0712 02:25:47.328243       1 reflector.go:134] pkg/mod/k8s.io/client-go@v10.0.0+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Service: Unauthorized
 E0712 02:25:47.329143       1 reflector.go:134] pkg/mod/k8s.io/client-go@v10.0.0+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Endpoints: Unauthorized
 E0712 02:25:47.330086       1 reflector.go:134] pkg/mod/k8s.io/client-go@v10.0.0+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Namespace: Unauthorized

原因:service account 的token认知不通过,是因为重新生成证书后,需要删除旧的token。

解决:

kubectl get secret -n kube-system                     
 NAME                  TYPE                                  DATA      AGE
 coredns-token-cdn9x   kubernetes.io/service-account-token   3         3d
 default-token-lht2v   kubernetes.io/service-account-token   3         3d kubectl delete secret coredns-token-cdn9x  -n kube-system
  secret "coredns-token-cdn9x" deleted