更新时间:2023 年 2 月

CoreDNS 简介

官网:CoreDNS: DNS and Service Discovery

官方文档:CoreDNS: DNS and Service Discovery

CoreDNS 插件以及插件文档:Plugins (coredns.io)

kubernetes 关于 CoreDNS 的描述:使用 CoreDNS 进行服务发现 | Kubernetes

CoreDNS 是一个用 GO 语言编写的 DNS 服务器。不同于其他优秀的 DNS 服务器(例如: BINDKnotPowerDNSUnbound),CoreDNS 非常灵活,几乎所有功能都外包到插件中

kubernetes 早期的 dns 组件有 skydns、kube-dns。目前,CoreDNS 就成了 Kubernetes 的默认 DNS 服务器

Kubernetes 版本与 kubeadm 安装的 CoreDNS 版本对应关系参考:deployment/CoreDNS-k8s_version.md

CoreDNS 的作用

参考:Service 与 Pod 的 DNS | Kubernetes

Kubernetes 中 Pod 的 IP 可能会随着销毁或创建而改变。为了保证访问,需要实时为 Service 和 Pod 创建 DNS 记录,然后应用之间的访问使用一致的 DNS 名称而非 IP 地址,这样即使 IP 发生改变,应用之间依旧可以相互调用

Service 的 FQDN 如下

{ServiceName}.{Namespace}.svc.{ClusterDomain} 

Corefile 配置

参考:Corefile Explained (coredns.io)

Corefile 是 CoreDNS 的配置文件,定义了一些内容

  • DNS Server 的监听协议和端口
  • DNS Server 负责哪个 Zone 的权威(authoritative)DNS 解析
  • DNS Server 加载的插件

格式

Corefile 由一个个服务配置块组成,每个服务配置块定义了要解析的区域端口使用的插件等信息,如果需要解析根域,则使用点号 . 表示

格式

ZONE:[PORT] {
    # Zone Block
    [PLUGIN] ... {
        # Plugin Block
    }
}

解析根域,端口未指定则使用默认的 53 端口

. {
    # Plugins defined here.
}

目前,CoreDNS 接受四种不同的协议,可以在区域之前添加协议来指定使用的协议。四种协议如下

  • dns:// for plain DNS (默认协议,如果未明确指定协议,则使用该协议)
  • tls:// for DNS over TLS, see RFC 7858
  • https:// for DNS over HTTPS, see RFC 8484
  • grpc:// for DNS over gRPC
tls://example.net:53 {
    file db.example.net
    forward . tls://223.5.5.5:853 {
        tls_servername dns.alidns.com
        force_tcp
        max_fails 3
    }
}

Kubernetes 中的默认配置

在 Kubernetes 集群中,可以通过 configmap 获取安装的 CoreDNS 的默认配置,即 Corefile 文件

$ /etc/kubeasz/bin/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 {
        errors
        health {
            lameduck 5s
        }
        ready
        kubernetes skynemo.cn 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
        reload
        loadbalance
    }
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"Corefile":".:53 {\n    errors\n    health {\n        lameduck 5s\n    }\n    ready\n    kubernetes skynemo.cn in-addr.arpa ip6.arpa {\n        pods insecure\n        fallthrough in-addr.arpa ip6.arpa\n        ttl 30\n    }\n    prometheus :9153\n    forward . /etc/resolv.conf {\n        max_concurrent 1000\n    }\n    cache 30\n    reload\n    loadbalance\n}\n"},"kind":"ConfigMap","metadata":{"annotations":{},"labels":{"addonmanager.kubernetes.io/mode":"EnsureExists"},"name":"coredns","namespace":"kube-system"}}
  creationTimestamp: "2023-01-31T15:46:09Z"
  labels:
    addonmanager.kubernetes.io/mode: EnsureExists
  name: coredns
  namespace: kube-system
  resourceVersion: "35013"
  uid: 45af18ca-ef5d-452a-a6b6-a9a550b83c47

CoreDNS 插件

参考:CoreDNS: DNS and Service Discovery

插件的工作模式

当 CoreDNS 启动时,将根据配置文件中的服务配置块运行一组 Server。每个 Server 由其服务的区域和端口定义,且每个服 Server 都有自己的插件链。CoreDNS 处理查询会经过以下几个步骤

  • 如果在请求查询的端口上有多个 Server,会根据最长后缀匹配原则来适配 Server。例如,有两个 Server,一个 example.org,另一个 a.example.org,此时,请求为 www.a.example.org,将会匹配路由到到后者

  • 找到对应的 Server 后,会根据该 Server 配置的插件链进行路由。插件顺序是固定的,其静态排序由配置文件 plugin.cfg 确定

  • 每个插件会判断是否对该请求进行处理,有以下几种处理情况

    • 请求由当前插件处理

      插件将生成对应的相应返回给客户端。此时,请求到此结束,插件链的下一个插件不会被调用。如 whoami 插件

    • 请求不被当前插件处理

      直接调用下一个插件。如果最后一个插件执行错误,服务器返回 SERVFAIL 响应

    • 请求由当前插件以 fallthrough 方式处理

      如果请求在该插件处理过程中有可能将跳转至下一个插件,该过程称为 fallthrough,并以关键字 fallthrough 来决定是否允许此项操作。例如 host 插件,当查询域名未位于 /etc/hosts,则调用下一个插件

    • 请求由当前插件处理,添加 hint 并调用下一个插件

      插件在其响应中添加了某些信息(hint)后继续交由下一个插件处理,这些额外的信息将组成对客户端的最终响应。如:metric 插件

示例

参考:How Queries Are Processed in CoreDNS

Corefile 如下

coredns.io:5300 {
  file /etc/coredns/zones/coredns.io.db
}

example.io:53 {
  errors
  log
  file /etc/coredns/zones/example.io.db
}

example.net:53 {
  file /etc/coredns/zones/example.net.db
}

.:53 {
  errors
  log
  health
  rewrite name foo.example.com foo.default.svc.cluster.local
  kubernetes cluster.local 10.0.0.0/24
  file /etc/coredns/example.db example.org
  forward . /etc/resolv.conf
  cache 30
}

注意这里有两个不同的端口:5300 和 53。在 CoreDNS 内部,每个端口都将生成一个 dnssever.Server。即使有四个服务配置块(server blocks),我们也只能得到两个实际的 Server

CoreDNS 将收集与同一端口相关的所有服务配置块,并将它们组合到同一个 dnssever.Server 中。这个 dnssever.Server 将对端口上的查询进行多路复用,根据区域(Zone)的不同,选择特定的匹配服务配置块,并传递到不同的插件链。如果没有匹配的服务配置块,则返回 SERVFAIL 。下图直观地显示了这一点

01-query-processing

常用插件解析

参考官方插件文档以及 kubernetes 关于 DNS 的文档:自定义 DNS 服务 | Kubernetes

error

插件文档:errors (coredns.io)

查询处理过程中遇到的任何错误都将打印到标准输出。特定类型的错误可以合并,每一段时间打印一次

每个服务的配置块只能使用此插件一次

health

插件文档:health (coredns.io)

健康状态接口插件,提供 pod 的存活探针(Liveness)接口。启用 health 插件(提供主动查询)时,默认情况下,运行状况在 :8080/health (端口 8080,路径 /health)上导出,当 CoreDNS 启动并运行时(不管插件是否正常),会返回 200 OK 的 HTTP 状态码

ready

插件文档:ready (coredns.io)

插件就绪状态接口插件,提供 pod 的就绪(Readiness)探针接口。启用 ready 插件时,会在:8181/health 上以 HTTP 状态码形式返回 CoreDNS 的状态

health 不同的是,只有使用到的所有插件(能够发出就绪信号的插件)都准备就绪,才会返回 200 OK;否则将返回 503 状态码,并附带出现问题的插件列表。一旦插件发出就绪信号,就不会再次查询它

kubernetes

插件文档:kubernetes (coredns.io)

kubernetes 插件是 CoreDNS 与 kubernetes 集成的最主要的插件

该插件实现了 Kubernetes 基于 DNS 的服务发现规范( Kubernetes DNS-Based Service Discovery Specification.),提供了 kubernetes service name 的 DNS 解析等功能

运行 kubernetes 插件的 CoreDNS 可以用作 kubernetes 集群中 kube-dns 的替代品。请参阅:deployment/kubernetes(现已转为 helmkubeadm 维护)

stubDomainupstreamNameserver 通过转发插件实现,详情请阅:Configuring Private DNS Zones and Upstream Nameservers in Kubernetes | Kubernetes

注:每个服务配置块只能使用此插件一次

启动

当 CoreDNS 在启用 kubernetes 插件的情况下启动时,它将延迟对外 DNS 服务 5 秒,直到它可以连接到Kubernetes API 并同步所有对象监视。如果这不能在 5 秒内完成连接 kubernetes API,那么 CoreDNS 将开始提供对外 DNS 服务,而 kubernetes 插件将继续尝试连接和同步所有对象监视。对于尚未同步的 Kubernetes 记录的任何请求,CoreDNS 都会回答 SERVFAIL

插件运行

  1. 首先,kube-apiserver 会将 Service 和对应的 Endpoint 信息存储在 etcd 中。这些信息包括 Service 名称、Service IP 地址和后端 Pod 的 IP 地址。并对外提供 API 接口
  2. CoreDNS 的 kubernetes 插件会向 kube-apiserver 发出 REST API 请求,以获取 Kubernetes 集群中的所有 Service 和 Endpoint 的信息
  3. kube-apiserver 返回一个 JSON 响应,其中包含 Kubernetes 集群中所有 Service 和 Endpoint 的信息
  4. kubernetes 插件解析 JSON 响应,将 Service 和 Endpoint 信息转换成 DNS 记录,包括服务名称、IP 地址和端口号
  5. 当 Kubernetes Service 的 IP 地址或端口号发生变化时,CoreDNS 的 kubernetes 插件会自动更新 DNS 记录,确保服务仍然能够正确地被解析。

监听 Kubernetes Endpoints

默认情况下,kubernetes 插件通过 API discovery.EndpointSlices 监视 Endpoints,以获取最新的 Endpoints 信息。但是,如果 Kubernetes 版本默认不支持 EndpointSliceProxying 特性(即 Kubernetes < 1.19),则使用 API api.Endpoints

prometheus

插件文档:prometheus (coredns.io)

监控指标(度量指标)插件。指标输出路径固定为 /metrics,端口默认为:localhost:9153。数据符合 Prometheus 的 key-value 格式

forward

插件文档:forward (coredns.io)

DNS 解析转发插件,用于支持迭代查询。支持 UDP、TCP 和 TLS 上的 DNS 查询,并内置了健康检查

注:每个服务配置块只能使用此插件一次

cache

插件文档:cache (coredns.io)

缓存插件。启用缓存后,除区域传输和元数据记录外的默认所有记录都将缓存 3600 秒,可以指定缓存时间

注:每个服务配置块只能使用此插件一次

reload

插件文档:reload (coredns.io)

重载配置插件。此插件会自动重新加载已更改的核心文件 corefile(不包括分区文件,分区文件重载使用 auto 插件)

注:每个服务配置块只能使用此插件一次

loadbalance

负载均衡插件。如果同一域名的 A,AAAA 和 MX 解析记录有多条,轮训应答

注:每个服务配置块只能使用此插件一次

CoreDNS 的域名解析流程

普通 DNS 解析流程

  • 查询发起:当应用程序尝试通过其域名访问资源时,它会向本地 CoreDNS 服务器发送 DNS 查询。在 kubernetes 的应用中,CoreDNS 的地址由 决定
  • 转发查询:如果本地 CoreDNS 服务器在其缓存中没有所请求的资源的 IP 地址,它将将查询转发到上游 DNS 服务器(例如递归解析器)。
  • 递归解析:上游 DNS 服务器通过将查询转发到层次结构中的其他 DNS 服务器来递归解析查询,直到到达具有所请求资源的 IP 地址的 DNS 服务器为止。
  • 缓存响应:随着 DNS 查询返回所请求资源的 IP 地址,本地 CoreDNS 服务器会将响应缓存起来,以避免将来需要递归解析。
  • 返回响应:本地 CoreDNS 服务器将 IP 地址返回给请求应用程序,从而允许它访问该资源。

除了上述步骤之外,CoreDNS 还支持插件,这些插件可以修改或增强域名解析流程。这些插件可以执行各种功能,例如 DNSSEC 验证、负载均衡和提供自定义 DNS 记录等

k8s 中应用的发起的 DNS 解析流程

kubelet 启动时,读取 /var/lib/kubelet/config.yaml 中设置的 DNS 地址(clusterDNS),为后续的每个 Pod 配置 /etc/resolv.conf 中的 nameserver,生成的 /etc/resolv.conf 如下:

$ kubectl exec -it deployment-busybox-54bb44f8bd-dmmx8 -- cat /etc/resolv.conf 
search default.svc.skynemo.cn svc.skynemo.cn skynemo.cn
nameserver 169.254.20.10
options ndots:5

注:自定义 pod 中的 /etc/resolv.conf 设置,可以参考:Service 与 Pod 的 DNS | Kubernetes

如果启动了 NodeLocal DNSCache,则一般该 clusterDNS 会指定为本地运行的 DNS 缓存的 DaemonSet(例如上述例子中的 IP :169.254.20.10),然后 NodeLocal DNSCache 再转发到上游的 CoreDNS 的 Service(一般为 Service IP 段的第二个 IP);如果未启动,则直接指定为 CoreDNS 的 Service

NodeLocal DNSCache 以 DaemonSet 的形式运行在每个节点,为查询 DNS 解析提供一层缓存

主体流程如下图

02-NodeLocal-DNSCache

其他 DNS 相关文档