CoreDNS篇9-kubernetes插件

本文最后更新于:November 7, 2022 am

CoreDNS作为现阶段k8s的默认DNS服务以及服务发现的重要一环,其内置的kubernetes插件可谓是举足轻重。本文主要讲解介绍CoreDNS内置的核心插件kubernetes的使用方式和适用场景。

CoreDNS的kubernetes插件的具体实现遵循k8s官方提供的标准指南Kubernetes DNS-Based Service Discovery Specification,这也是它能够替代kube-dns成为kubebernetes中默认的DNS的重要原因。

虽然 Kubernetes 中的服务发现可以通过其他协议和机制提供(如consul等服务注册发现中心),但DNS是非常常用的一种协议,同时考虑到K8S中的东西流量互访主要也是通过域名实现,因此K8S官方非常推荐使用DNS插件来实现K8S中的服务发现。

This document is a specification for DNS-based Kubernetes service discovery. While service discovery in Kubernetes may be provided via other protocols and mechanisms, DNS is very commonly used and is a highly recommended add-on. The actual DNS service itself need not be provided by the default Kube-DNS implementation. This document is intended to provide a baseline for commonality between implementations.

在开始介绍kubernetes插件之前,我们需要先了解一些K8S中的基础DNS知识。

1、K8S中的DNS服务

众所周知,在K8S中,IP是随时会发生变化的,变化最频繁的就是Pod IPCluster IP也并不是一定不会发生变化,EXTERNAL-IP虽然可以手动指定静态IP保持不变,但是主要面向的是集群外部的服务;因此在K8S集群中,最好的服务之间相互访问的方式就是通过域名。

1.1 DNS创建规则

在K8S集群中,Kubernetes 为 Service 和 Pod 创建 DNS 记录。

前面我们介绍了K8S中的每个SVC都会有一个对应的域名,域名的组成格式为$service_name.$namespace_name.svc.$cluster_name,同时也会给这个SVC下的所有Pod都创建一个$pod_name.$service_name.$namespace_name.svc.$cluster_name的这种域名,这个域名的解析结果就是Pod IP。

Pod域名有两个比较明显的特征:

  • 一是域名的组成比较特殊,因为域名中使用了Pod的名称,而pod名称在K8S中是会发生变化的(例如在服务更新或者滚动重启时),同时由于默认情况下Pod的命名是没有太明显的规律(大部分名字中会包含一串随机UUID)
  • 二是域名的解析结果特殊,相较于集群内的其他类型域名,Pod域名的解析是可以精确到特定的某个Pod,因此一些特殊的需要点对点通信的服务可以使用这类Pod域名

1.2 DNS策略配置

DNS 策略可以逐个 Pod 来设定。目前 Kubernetes 支持以下特定 Pod 的 DNS 策略。 这些策略可以在 Pod 规约中的 dnsPolicy 字段设置:

  • Default: Pod 从运行所在的K8S宿主机节点继承域名解析配置;
  • ClusterFirst: 不指定任何dnsPolicy配置情况下的默认选项,所有查询的域名都会根据生成的集群的K8S域名等信息生成的 /etc/resolv.conf 配置进行解析和转发到集群内部的DNS服务进行解析;
  • ClusterFirstWithHostNet:主要用于以 hostNetwork 方式运行的 Pod,如果这些pod想要使用K8S集群内的DNS服务,则可以配置为这个字段;
  • None: 此设置允许 Pod 忽略 Kubernetes 环境中的 DNS 设置,Pod 会使用其 dnsConfig 字段 所配置的 DNS 设置;

说明: 下面主要介绍ClusterFirst模式

1.3 DNS解析规则

DNS 查询参照 Pod 中的 /etc/resolv.conf 配置,kubelet 会为每个 Pod 生成此文件。因此在每个pod里面都有一个类似下面这样的 /etc/resolv.conf文件,通过修改其中的配置可以更改DNS的查询规则:

1
2
3
nameserver 10.32.0.10
search <namespace>.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

这里的配置有几个需要注意的点:

  • nameserver:集群中的DNS服务器IP,一般来说就是CoreDNSClusterIP
  • search:需要搜索的域,默认情况下会从该pod所属的namespace开始逐级补充
  • options ndots:触发上面的search的域名点数,默认为1,上限15,在K8S中一般为5;例如在Linux中tinychen.com这个域名的ndots是1,tinychen.com.这个域名的ndots才是2(需要注意所有域名其实都有一个根域.,因此tinychen.com的全称应该是tinychen.com.

这是一个比较通用的案例,我们再来看一个比较特殊的配置

1
2
3
4
5
6
7
8
# 首先进入一个pod查看里面的DNS解析配置
[root@tiny-calico-master-88-1 tiny-calico]# kubectl exec -it -n ngx-system ngx-ex-deploy-6bf6c99d95-5qh2w /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
[root@ngx-ex-deploy-6bf6c99d95-5qh2w /]# cat /etc/resolv.conf
nameserver 10.88.0.10
search ngx-system.svc.cali-cluster.tclocal svc.cali-cluster.tclocal cali-cluster.tclocal k8s.tcinternal
options ndots:5
[root@ngx-ex-deploy-6bf6c99d95-5qh2w /]# exit

这个pod里面的/etc/resolv.conf配置文件有两个和前面不同的地方:

  • cluster.local变成了cali-cluster.tclocal

    这里我们可以看到coredns的配置中就是配置的cali-cluster.tclocal,也就是说/etc/resolv.conf中的配置其实是和coredns中的配置一样,更准确的说是和该K8S集群初始化时配置的集群名一样

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    # 再查看K8S集群中的coredns的configmap	
    [root@tiny-calico-master-88-1 tiny-calico]# kubectl get configmaps -n kube-system coredns -oyaml
    apiVersion: v1
    data:
    Corefile: |
    .:53 {
    errors
    health {
    lameduck 5s
    }
    ready
    kubernetes cali-cluster.tclocal in-addr.arpa ip6.arpa {
    pods insecure
    fallthrough in-addr.arpa ip6.arpa
    ttl 30
    }
    prometheus :9153
    forward . 10.31.100.100 {
    max_concurrent 1000
    }
    cache 30
    loop
    reload
    loadbalance
    }
    kind: ConfigMap
    metadata:
    creationTimestamp: "2022-05-06T05:19:08Z"
    name: coredns
    namespace: kube-system
    resourceVersion: "3986029"
    uid: 54f5f803-a5ab-4c77-b149-f02229bcad0a
  • search新增了一个k8s.tcinternal

    实际上我们再查看K8S的宿主机节点的DNS配置规则时会发现这个k8s.tcinternal是从宿主机上面继承而来的

    1
    2
    3
    4
    5
    # 最后查看宿主机节点上面的DNS解析配置
    [root@tiny-calico-master-88-1 tiny-calico]# cat /etc/resolv.conf
    # Generated by NetworkManager
    search k8s.tcinternal
    nameserver 10.31.254.253

1.4 DNS解析流程

温馨提示:阅读这部分内容的时候要特别注意域名结尾是否有一个点号.

当ndots小于options ndots

前面我们说过options ndots的值默认情况下是1,在K8S中为5,为了效果明显,我们这里使用K8S中的5作为示例:

这里同样是在一个命名空间demo-ns中有两个SVC,分别为demo-svc1demo-svc2,那么他们的/etc/resolv.conf应该是下面这样的:

1
2
3
nameserver 10.32.0.10
search demo-ns.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

我们在demo-svc1中直接请求域名demo-svc2,此时ndots为1,小于配置中的5,因此会触发上面的search规则,这时第一个解析的域名就是demo-svc2.demo-ns.svc.cluster.local,当解析不出来的时候继续下面的demo-svc2.svc.cluster.localdemo-svc2.cluster.local,最后才是直接去解析demo-svc2.

注意上面的规则适用于任何一个域名,也就是当我们试图在pod中去访问一个外部域名如tinychen.com的时候也会依次进行上述查询。

当ndots大于等于options ndots

我们在demo-svc1中直接请求域名demo-svc2.demo-ns.svc.cluster.local,此时的ndots为4,还是会触发上面的search规则。

而请求域名demo-svc2.demo-ns.svc.cluster.local.,ndots为5,等于配置中的5,因此不会触发上面的search规则,直接去解析demo-svc2.demo-ns.svc.cluster.local.这个域名并返回结果

如果我们请求更长的域名如POD域名pod-1.demo-svc2.demo-ns.svc.cluster.local.,此时的ndots为6,大于配置中的5,因此也不会触发上面的search规则,会直接查询域名并返回解析

小结

通过上面的分析我们不难得出下面几点结论:

  • 同命名空间(namespace)内的服务直接通过$service_name进行互相访问而不需要使用全域名(FQDN),此时DNS解析速度最快;
  • 跨命名空间(namespace)的服务,可以通过$service_name.$namespace_name进行互相访问,此时DNS解析第一次查询失败,第二次才会匹配到正确的域名
  • 所有的服务之间通过全域名(FQDN)$service_name.$namespace_name.svc.$cluster_name.访问的时候DNS解析的速度最快
  • 在K8S集群内访问大部分的常见外网域名(ndots小于5)都会触发search规则,因此在访问外部域名的时候可以使用FQDN,即在域名的结尾配置一个点号.

2、kubernetes插件

kubernetes插件的主要作用就是用来连接k8s集群的apiserver并对外提供符合规范的域名解析服务,该插件在每个配置块中仅能使用一次,但在一个coredns实例中可以存在多个配置块,也就意味着一个coredns实例实际上是可以连接多个k8s集群并对外提供域名解析的。

接下来我们详细看一下kubernetes插件的各种具体配置,下面的这个是官方给出的一个配置文件示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kubernetes [ZONES...] {
endpoint URL
tls CERT KEY CACERT
kubeconfig KUBECONFIG [CONTEXT]
namespaces NAMESPACE...
namespace_labels EXPRESSION
labels EXPRESSION
pods POD-MODE
endpoint_pod_names
ttl TTL
noendpoints
fallthrough [ZONES...]
ignore empty_service
}
  • endpoint

    用来指定k8s集群的apiserver地址,如https://10.31.88.1:6443,当然也可以是域名等其他形式,如果不配置,那么默认情况下会使用对应的`service account`去连接当前k8s集群内的apiserver,如果不是在k8s集群中部署,那么就会连接失败。

  • tls

    CERT KEY CACERT是远程 k8s 连接的 TLS 证书、密钥和 CA 证书文件名。如果前面的endpoint没有配置,那么这个配置项会被忽略。

  • kubeconfig

    KUBECONFIG [CONTEXT]使用 kubeconfig 文件验证与远程 k8s 集群的连接。 [CONTEXT]是可选配置的,如果未设置,则将使用 kubeconfig中默认的[CONTEXT]。它支持 TLS、用户名和密码或基于令牌的身份验证。

    如果前面的endpoint没有配置,那么这个配置项会被忽略。

  • namespaces

    NAMESPACE [NAMESPACE…] 用来限制对外暴露的命名空间,多个命名空间之间使用空格间隔。如果不配置的话,则会暴露所有的命名空间。

  • namespace_labels

    namespace_labels EXPRESSION可以限定DNS的查询范围,仅有匹配labels命名空间才能被查询到。

  • labels

    labels EXPRESSION可以限定DNS的查询范围,仅有匹配lalelsservice才能被查询到。

注意这里的labels匹配的是service中的labels,而前面的labels匹配的是namespace中的labels。这两个labels的配置写法可以和使用kubectl命令中的-l参数完全一致。

如果要使用多个labels匹配规则,注意不要使用空格,而是对应的表达式进行匹配:

-l, –selector=’’: Selector (label query) to filter on, supports ‘=’, ‘==’, and ‘!=’.(e.g. -l key1=value1,key2=value2)

  • pods

    pods POD-MODE设置处理基于 IP 的 pod A 记录的模式,例如客户端向coredns查询域名1-2-3-4.ns.pod.cluster.local.,该参数用于控制响应的结果,提供此选项是为了方便在直接连接到 pod 时使用 SSL 证书。

    **POD-MODE **有效值:

    • disabled: 默认。不处理 pod 请求,总是返回NXDOMAIN
    • insecure:总是从请求中返回带有 IP 的 A 记录(不检查 k8s),即查询域名1-2-3-4.ns.pod.cluster.local.的时候,不论是否存在一个IP地址为1.2.3.4的pod,都返回这个结果给客户端。如果与通配符 SSL 证书一起被恶意使用,此选项很容易被滥用。提供此选项是为了向后兼容 kube-dns。
    • verified: 如果在同一个命名空间中存在匹配 IP 的 Pod,则返回 A 记录,即查询域名1-2-3-4.ns.pod.cluster.local.的时候,只有当该ns中确实存在一个IP地址为1.2.3.4的pod,才返回这个结果给客户端,否则返回NXDOMAIN。与insecure模式相比,此选项需要更多的内存,因为它需要监控所有的pods。
  • endpoint_pod_names

    使用endpoints所对应的pod名称作为A记录中的端点名称,例如, endpoint-name.my-service.namespace.svc.cluster.local. in A 1.2.3.4

    在没有配置该参数的情况下,endpoints名称选择如下:优先使用endpoints的hostname,如果endpoints没有配置hostname,则使用 IP 地址的虚线形式(例如,1-2-3-4.my-service.namespace.svc.cluster.local.

    如果配置了该参数,则endpoints名称选择如下:优先使用endpoints的hostname,如果endpoints没有配置hostname,则使用endpoints对应的pod名称,如果pod名称不存在或者长度超过63,则使用 IP 地址的虚线形式。

  • ttl

    设置标准的DNS域名TTL,默认值为 5 秒。允许的最小 TTL 为 0 秒,最大值为 3600 秒。将 TTL 设置为 0 将防止记录被缓存(如果查询的客户端遵循DNS规范)。

  • noendpoints

    配置该参数将禁用对K8S集群中的endpoints记录功能,因此所有endpoints查询和headless服务查询都将返回 NXDOMAIN。

  • fallthrough [ZONES…]

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

    但是我们可以通过配置fallthrough参数来将这些NXDOMAIN的域名转发到配置块中的下一个插件。

    例如在fallthrough插件后面还使用了诸如file插件之类的配置了DNS解析,那么这个请求就会转发到file插件进行查询并响应

    zones参数可以用来控制哪些域的域名会被fallthrough插件转发,留空的情况下是所有的域名都会被转发,当然也可以指定部分域名如(for example in-addr.arpa and ip6.arpa),此时就只有in-addr.arpaip6.arpa的查询出现NXDOMAIN才会被转发到下一个插件进行查询

  • ignore empty_service

    如果一个service当中没有任何可用的endpoints(即关联的所有pods都不是ready状态),那么会返回一个NXDOMAIN。

    这个配置项的主要作用就是让这类不正常的服务域名查询的时候能够返回NXDOMAIN响应码,从而触发配置的其他插件(如上面提到的fallthrough)进行组合操作。

3、一些其他问题

3.1 延迟启动

当CoreDNS启用了kubernetes插件之后,CoreDNS实例在启动的时候会延迟5s的时间再对外提供服务,这5s内CoreDNS会尝试和K8S的apiserver建立连接并同步信息。

如果5s内CoreDNS还是无法和k8s的apiserver完成信息同步工作,那么会开始对外提供服务,并且继续尝试同步信息,但是在成功和apiserver建立连接并同步信息之前,所有k8s相关的域名查询都会返回SERVFAIL

3.2 连接中断

如果在CoreDNS实例正常运行的时候,突然和k8s的apiserver断开连接,并且一直没有恢复,那么此时的CoreDNS实例是依旧正常运行的,对应的K8S集群域名也是能够正常解析的,但是解析出来的endpoint信息就有可能不是最新的。

如果此时再对CoreDNS实例进行重启操作,那么具体的过程就和上面讲述的延迟启动一样,最后会导致所有k8s相关的域名查询都会返回SERVFAIL

3.3 配置检查

kubernetes的健康状态会暴露在ready插件中,如果出现配置错误可以通过请求ready插件暴露的接口发现,但是如果出现连接异常这种情况,ready接口是无法探测出来的。