4-1-k8s组件-dns

熊 0 阅读 容器

1. DNS服务概述

service发现是k8s中的一个重要机制 , 其基本功能为: 在集群内通过服务名对服务进行访问 , 即需要完成从服务名到ClusterIP的解析。 k8s主要有两种service发现机制: 环境变量和DNS。没有DNS服务的时候 , k8s会采用环境变量的形式 , 但一旦有多个service , 环境变量会变复杂 , 为解决该问题 , 我们使用DNS服务。

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-component-coredns-1

2. CoreDNS基础

2.1 CoreDNS简介

CoreDNS 是一个 Go 语言编写的灵活可扩展的 DNS 服务器, 在 Kubernetes 中, 作为一个服务发现的配置中心, 在 Kubernetes 中创建的 Service 和 Pod 都会在其中自动生成相应的 DNS 记录。Kubernetes 服务发现的特性, 使 CoreDNS 很适合作为企业云原生环境的 DNS 服务器, 保障企业容器化和非容器化业务服务的稳定运行。

构建企业 DNS 服务器时, 一般会有以下需求:

  • 用户外网域名访问服务 ;

  • 混合云业务迁移、数据共享、容灾 ;

  • 开发代码 IP 写死导致架构可用性、弹性无法实现 ;

  • 统一 DNS 管理需求, 含上下级平台对接 ;

  • DNS 劫持等网络安全风险 ;

  • 存量代码固定域名访问 ;

  • 集群外域名访问 ;

相比于 Bind 开源方案或 Windows Server DNS 商业 DNS 服务器, CoreDNS 有以下优势:

  • 无商业许可要求, 降低投资成本 ;

  • 轻量化, 通过插件实现功能添加 ;

  • 支持 DNS, DNS over TLS, DNS over HTTP/2, DNS over gRPC 协议 ;

  • 提供 kubernetes 服务发现 ;

  • 支持对接 Route53/Google Cloud DNS/AzureDNS ;

  • 支持集成 Prometheus, OpenTracing, OPA, 带来更全面的运维体验 ;

  • 支持整合容器管理平台, 提供统一 DNS 系统运维。

2.2 CoreDNS 运行原理与插件介绍

CoreDNS 基于 Caddy 框架实现模块化设计, 每个插件承载相应的具体功能, 对于 DNS 系统而言, CoreDNS 使用 File, ETCD 插件等加载 DNS 记录, 使用 Kubernetes 插件实现集群服务发现, 外部 DNS 请求到达 CoreDNS 后, 根据插件调用链依次处理 DNS 请求。

kubernetes-component-coredns-2

CoreDNS 社区官方提供了 50 多种插件, 开发者亦可根据需求开发个性化的外部插件。CoreDNS 常用插件如下图, 根据使用场景, 可分为运维、DNS 处理、后端数据存储等三个类别。

CoreDNS 定义 Corefile 配置文件, 服务器在加载 Corefile 后处理 DNS 请求, 对于以下插件, 只需在 Corefile 中引用即可, 之后 CoreDNS 会使用插件链进行调用, 插件链可参考以下链接: https://github.com/coredns/coredns/blob/master/plugin.cfg

kubernetes-component-coredns-3

3. 服务发现与 DNS 配置

部署 CoreDNS 后, 对于运维人员来说, CoreDNS 的配置大体分为两类: 一类为 Kubernetes 配置, 一类为 DNS 配置。CoreDNS 提供了 Kubernetes 插件, 支持在 kubernetes 集群中读取区域数据, 并根据 Pod 和 Service 的域名记载相应的 A 记录和 PTR 记录。

这是一个 Kubernetes 集群中的 CoreDNS corefile 默认配置, CoreDNS 会在 53 端口读取 cluster.local 后缀的 Kubernetes 集群 A 记录和 PTR 记录。并将 CoreDNS 收集到的监控指标通过 9153 端口输出到集群内的 Prometheus。而 Kubernetes 不同类型 Service 的 DNS 记录格式, CoreDNS 也有相应标准进行记录。

kubernetes-component-coredns-4

设置完 Kubernetes 后, 可以设置其他业务需求的 DNS 配置, 如:

  • 设置不同的存根域 ;

  • 设置存根域静态 DNS 条目 ;

  • 面对存量代码, 设置域名重写 ;

  • 面对集群外服务, 设置 DNS 转发 ;

  • 设置日志和监控集成 ;

  • 设置缓存、健康、就绪检查及链路追踪 ;

根据以上配置, 就构建了一个基础的企业云原生 DNS 系统:

kubernetes-component-coredns-5

3.1 Corefile介绍

Corefile 是 CoreDNS 的配置文件(源于Caddy框架的配置文件Caddyfile) , 它定义了:

  • server 以什么协议监听在哪个端口(可以同时定义多个server监听不同端口)

  • server负责哪个 zone 的权威(authoritative)DNS解析

  • server将加载哪些插件

常见地 , 一个典型的 Corefile 格式如下所示:

ZONE:[PORT] {
    [PLUGIN] ...
}
  • ZONE : 定义 server 负责的 zone , PORT 是可选项 , 默认为 53 ;

  • PLUGIN : 定义 server 所要加载的 plugin。每个 plugin 可以有多个参数 ;

比如:

. {
    whoami
}

上述配置文件表达的是 : server 负责根域 . 的解析("."表示所有的域名都会匹配),其中 plugin 是 whoami 且没有参数。

3.1.1 定义 DNS server

一个最简单的配置文件可以为:

.{}

即 server 监听 53 端口并不使用插件。如果此时在定义其他 server , 要保证监听端口不冲突 ; 如果是在原来 server 增加 zone , 则要保证 zone 之间不冲突 , 如:

# 多个server的话需要保证监听端口不冲突
.    {}
.:54 {}

另一个 server 运行于 54 端口并负责根域 . 的解析。

又如:

#同一server的话需要保证zone不冲突
example.org {
    whoami
}
org {
    whoami
}

同一个 server 但是负责不同 zone 的解析 , 有不同插件链。

3.1.2 定义 Reverse Zone

跟其他 DNS 服务器类似 , Corefile 也可以定义 Reverse Zone(反向解析 IP 地址对应的域名):

0.0.10.in-addr.arpa {
    whoami
}

或者简化版本:

10.0.0.0/24 {
    whoami
}

可以通过 dig 进行反向查询:

$ dig -x 10.0.0.1

3.1.3 使用不同的通信协议

CoreDNS 除了支持 DNS 协议 , 也支持 TLS 和 gRPC , 即 DNS-over-TLS 和 DNS-over-gRPC 模式:

tls://example.org:1443 {
#...
}

3.2 插件的工作模式

当 CoreDNS 启动后 , 它将根据配置文件启动不同 server (假设配置文件中配置了多个DNS Server) , 每台server都拥有自己的插件链。当有 DNS 请求时 , 它将依次经历如下 3 步逻辑:

  1. 如果有当前请求的 server 有多个 zone , 将采用贪心原则选择最匹配的 zone , 例如当前server含有com和baidu.com这两个zone,当客户端请求当前server解析域名www.baidu.com的话 , 根据贪心原则会选择baidu.com这个zone ;

  2. 一旦找到匹配的 server , 按照 plugin.cfg定义的顺序执行插件链上的插件 ( 目前可用插件列表) ;

  3. 每个插件将判断当前请求是否应该处理 , 将有以下几种可能:

    • 请求被当前插件处理

      插件将生成对应的响应并回给客户端 , 此时请求结束 , 下一个插件将不会被调用 , 如 whoami 插件 ;

    • 请求不被当前插件处理

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

    • 请求被当前插件以 Fallthrough 形式处理

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

    • 请求在处理过程被携带 Hint

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

3.3 CoreDNS 如何处理 DNS 请求

如果 Corefile 为:

coredns.io:5300 {
    file db.coredns.io
}
 
example.io:53 {
    log
    file db.example.io
    errors
}
 
example.net:53 {
    file db.example.net
}
 
.:53 {
    log
    errors
    cache
    kubernetes
    health
    proxy . 8.8.8.8
}

从配置文件来看 , 我们定义了两个 server(其中监听5300端口的server仅配置了coredns.io这一个zone , 监听53端口的server配置了example.io、example.net和.三个zone) , 分别监听在 5300 和 53 端口。其逻辑图可如下所示:

kubernetes-component-coredns-7

每个进入到某个 server 的请求将按照 plugin.cfg 定义顺序执行其已经加载的插件。

从上图 , 我们需要注意的是尽管在 .:53 配置了 health 插件 , 但是它并未在上面的逻辑图中出现 , 原因是: 该插件并未参与请求相关的逻辑 ( 即并没有在插件链上) , 只是修改了 server 配置。更一般地 , 我们可以将插件分为两种:

  • Normal 插件: 参与请求相关的逻辑 , 且插入到插件链中 ;

  • 其他插件 : 不参与请求相关的逻辑 , 也不出现在插件链中 , 只是用于修改 server 的配置 , 如 health , tls 等插件 ;

3.4 Kubernetes中CoreDNS配置详解

通过下面命令查看当前Kubernetes集群coredns配置

kubectl get configmaps -n=kube-system coredns -o yaml

结果如下 :

apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        health {
            lameduck 5s
        }
        ready
        kubernetes cbear.local 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 cbear.local 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: "2024-05-21T10:12:52Z"
  labels:
    addonmanager.kubernetes.io/mode: EnsureExists
  name: coredns
  namespace: kube-system
  resourceVersion: "1214"
  uid: 59e59bf5-16fc-4255-a9f6-76386b949931

参数

说明

errors

打印/存储错误日志

health

CoreDNS自身健康状态报告 , 默认监听端口8080 , 一般用来做健康检查。您可以通过http://localhost:8080/health获取健康状态(coredns Pod配置的存活探针使用此接口获取CoreDNS自身健康)。

ready

CoreDNS插件状态报告 , 默认监听端口8181 , 一般用来做可读性检查。可以通过http://localhost:8181/ready获取可读状态。当所有插件都运行后 , ready状态为200 ( coredns Pod配置的就绪探针使用此接口获取CoreDNS插件的就绪状况) 。

kubernetes

kubernetes: CoreDNS Kubernetes插件 , 提供集群内服务解析能力。该插件实现了基于Kubernetes DNS的服务发现。其中cluster.local in-addr.arpa ip6.arpa表示kubernetes插件会处理域名后缀为cluster.local的所有域名以及处理所有的in-addr.arpa中的反向dns查找和ip6.arpa形式域名 , 其中kubernetes集群域名后缀是在kubelet参数中配置的 , 默认值为cluster.local。 pods参数用于设置基于pods ip的A记录 , 例如default namespace下存在一个pod , ip为10.233.64.2 ,此Pod的A记录为10-233-64-2.default.pod.cluster.local ->10.233.64.2 , pods参数有三个值 , 其中disabled表示关闭pod ip的A记录 ; insecure总是从请求中返回一个带有IP的A记录 ( 不检查k8s) ( 例如: 1-2-3-4.ns.pod.cluster.local. in A 1.2.3.4) ; verified表示如果在同一命名空间中 , 则存在匹配IP的pod。 fallthrough: 指定特定区域查询失败 , 如果in-addr.arpa和ip6.arpa形式的域名在kubernetes插件没有匹配成功的话则进入下一个 plugin 继续。 ttl参数为响应客户端请求设置的自定义 TTL。 更多参数配置见: https://coredns.io/plugins/kubernetes/

hosts

加载 /etc/hosts 文件里面的解析信息。如果一个域名在 hosts 文件中存在 , 则优先使用这个信息返回 , 在本例中一定要注意hosts插件的位置 , 如果放在了forward插件之后 , 那么hosts插件即没有匹配的机会 ;

prometeus

CoreDNS自身metrics数据接口。可以通过http://localhost:9153/metrics获取prometheus格式的监控数据。

forward

将域名查询请求转到预定义的DNS服务器。默认配置中 , 当域名不在Kubernetes域时 , 将请求转发到预定义的解析器 ( /etc/resolv.conf) 中。默认使用宿主机的/etc/resolv.conf配置。forward . /etc/resolv.conf中的"."表示匹配所有域名。

cache

溯源得到的结果 , 缓存指定时间。类似 TTL 的概念 ; TTL以秒为单位。如果未指定 , 将使用最大 TTL , 对于 NOERROR响应为 3600 , 对于拒绝存在响应为 1800。将 TTL 设置为 30表示缓存记录长达 30 秒。

loop

环路检测 , 如果检测到环路 , 则停止CoreDNS。例如 , forward . /etc/resolv.conf中配置的dns server地址和coredns地址一致 , 客户端请求解析in-addr.arpa类型域名 , 在kubernetes插件中解析in-addr.arpa类型域名失败 , 由于配置了fallthrough in-addr.arpa ip6.arpa , 那么就会进入到forward插件继续解析 , 由于forward . /etc/resolv.conf中配置的dns server地址和coredns地址一致 , 那么又会传到dns server进行解析 , 这么就陷入了一个死循环 , 通过loop插件可以避免环路问题。

reload

允许自动重新加载已更改的Corefile。编辑ConfigMap配置后 , 请等待两分钟以使更改生效,无需重启coredns对应的Pod。

loadbalance

循环DNS负载均衡器 , 可以在答案中随机A、AAAA、MX记录的顺序。

下面是coredns的配置模板

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: namespace-test
data:
  Corefile: |
    .:53 {
        errors
        health
        ready
        kubernetes cluster.local  10.200.0.0/16 {
          pods insecure
          upstream 114.114.114.114
          fallthrough in-addr.arpa ip6.arpa
          namespaces namespace-test
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }

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: 对错误信息镜像日志记录

讲一下我自己的理解:

  1. 配置文件类似于 nginx 配置文件的格式 ;

  2. 最外面一级的大括号 , 对应‘服务’的概念。多个服务可以共用一个端口 ;

  3. 往里面一级的大括号 , 对应 plugins 的概念 , 每一个大括号都是一个 plugin。这里可以看出 , plugins 是 CoreDNS 的一等公民 ;

  4. 服务之间顺序有无关联没有感觉 , 但 plugins 之间是严重顺序相关的。某些 plugin 必须用 fallthrough 关键字流向下一个 plugin ;

  5. plugin 内部的配置选项是顺序无关的 ;

  6. 从 CoreDNS 的性能指标看 , 适合做大型服务。

4. Pod的dns策略

4.1 Pod dns策略

dnsPolicy 配置 DNS 查询解析在 Pod 级别的工作方式。它决定了 Pod 如何定位集群内外的服务并与之通信。

使用 dnsPolicy , 您可以设置用于解析容器的 DNS 请求的规则 - 是使用集群的内部 DNS 服务、遵循节点的 DNS 设置 , 还是使用自定义配置。

此策略有四个可能的值。让我们看看它们是如何工作的 , 以及它们之间的区别。

这里有一个简短的总结。

Policy

说明

ClusterFirst

默认值 ; 非集群域的 DNS 查询转发到外部名称服务器 ; 对于外部网络通信很有用

Default

Pod 使用与其主机节点相同的 DNS 设置

ClusterFirstWithHostNet

专门针对启用了 hostNetwork 的 pod ; 与 ClusterFirst 类似 , 但确保与主机网络设置的兼容性 ; 不支持 windows

None

绕过默认的 Kubernetes DNS 设置;需要在 pod 的 dnsConfig 中手动配置 DNS

  • Default : 继承Pod所在宿主机的DNS设置

  • ClusterFirst : 优先使用kubernetes环境的dns服务 , 将无法解析的域名转发到从宿主机继承的dns服务器

  • ClusterFirstWithHostNet : 和ClusterFirst相同 , 对于以hostNetwork模式运行的Pod应明确知道使用该策略

  • None : 忽略kubernetes环境的dns配置 , 通过spec.dnsConfig自定义DNS配置

    自定义Dns配置可以通过spec.dnsConfig字段进行设置 , 可以设置如下信息

    1. nameservers: 一组dns服务器的列表 , 最多可设置3个

    2. searchs : 一组用于域名搜索的dns域名后缀 , 最多6个

    3. options : 配置其他可选参数 , 例如ndots、timeout等

    例如:

    spec:
      dnsPolicy: "None"
      dnsConfig:
        nameservers:
          - 1.2.3.4
        searchs:
          - xx.ns1.svc.cluster.local
          - xx.daemon.com
        options:
          - name: ndots
            values: "2"

pod被创建后 , 容器内的/etc/resolv.conf会根据这个信息进行配置。

4.2 测试解析结果

创建nslookup服务

$ cat >busybox.yaml<<EOF
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
  - name: busybox
    image: busybox:1.28
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
  restartPolicy: Always
EOF

查看并验证

root@harbor:~# kubectl create -f busybox.yaml 
pod/busybox created
root@harbor:~# kubectl get pods busybos
Error from server (NotFound): pods "busybos" not found
root@harbor:~# kubectl get pods busybox
NAME      READY   STATUS    RESTARTS   AGE
busybox   1/1     Running   0          17s
root@harbor:~# kubectl exec busybox -- cat /etc/resolv.conf
search default.svc.cbear.local svc.cbear.local cbear.local openstacklocal
nameserver 169.254.20.10
options ndots:5

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

Kubernetes 域名的全称 , 必须是service-name.namespace.svc.cluster.local这种模式 , 服务名。

root@harbor:~# kubectl exec -ti busybox -- nslookup kubernetes.default
Server:    169.254.20.10
Address 1: 169.254.20.10

Name:      kubernetes.default
Address 1: 10.68.0.1 kubernetes.default.svc.cbear.local

5. 测试CoreDNS

5.1 pod验证

现在我们来创建一个busybox的pod , 测试一下pod内是否可以解析

$ cat >busybox.yaml<<EOF
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
  - name: busybox
    image: busybox:1.28
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
  restartPolicy: Always
EOF

创建并测试解析kubernetes.default

$ kubectl apply -f busybox.yaml
$ kubectl get pods busybox
$ kubectl exec busybox -- cat /etc/resolv.conf
$ kubectl exec -ti busybox -- nslookup kubernetes.default
$ kubectl exec -ti busybox -- nslookup kubernetes.default.svc.cluster.local

5.2 创建service和Deployment来验证

cat >dns-Service-Deployment-test001.yaml<<EOF
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc-old
  labels:
    app: nginx-svc
spec:
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-old
spec:
  replicas: 1
  selector:
    matchLabels:
     app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
EOF

创建

root@harbor:~# kubectl apply -f dns-Service-Deployment-test001.yaml 
service/nginx-svc-old created
deployment.apps/nginx-old created
root@harbor:~# kubectl get pod|grep  nginx-old
nginx-old-7c5ddbdf54-qz52b   1/1     Running   0          43s
root@harbor:~# kubectl get svc nginx-svc-old
NAME            TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
nginx-svc-old   ClusterIP   10.68.12.0   <none>        80/TCP    77s

发现直接在宿主机上是不能解析域名的。然后用以下yaml创建了一个busybox作为调试工具 :

$ cat >dns-Deployment-test002.yaml<<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: busybox-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
     app: busybox
  template:
    metadata:
      labels:
        app: busybox
    spec:
      restartPolicy: Always
      containers:
      - name: busybox
        command:
        - sleep
        - "3600"
        image: busybox
EOF

创建好之后 , exec进入容器 , 执行测试命令。

root@harbor:~# kubectl apply -f dns-Deployment-test002.yaml
deployment.apps/busybox-deployment created
root@harbor:~# kubectl get pod|grep busybox-deployment
busybox-deployment-6fc6c44c5b-p785n   1/1     Running   0               13s
root@harbor:~# kubectl exec -it busybox-deployment-6fc6c44c5b-p785n -- nslookup nginx-svc-old.default.svc
Server:		169.254.20.10
Address:	169.254.20.10:53

** server can't find nginx-svc-old.default.svc: NXDOMAIN

** server can't find nginx-svc-old.default.svc: NXDOMAIN

command terminated with exit code 1

发现也是无法解析。 根据coredns解析集群内域名原理可知 :

服务 a 访问服务 b , 对于同一个 Namespace下 , 可以直接在 pod 中 , 通过 curl b 来访问。对于跨 Namespace 的情况 , 服务名后边对应 Namespace即可 , 比如 curl b.default。DNS 如何解析 , 依赖容器内 resolv 文件的配置。

查看busybox容器内的resolve.conf文件 :

root@harbor:~# kubectl exec -it busybox-deployment-6fc6c44c5b-p785n -- sh
/ # 
/ # nslookup nginx-svc-old.default
Server:		169.254.20.10
Address:	169.254.20.10:53

** server can't find nginx-svc-old.default: NXDOMAIN

** server can't find nginx-svc-old.default: NXDOMAIN

/ # cat /etc/resolv.conf 
search default.svc.cbear.local svc.cbear.local cbear.local openstacklocal
nameserver 169.254.20.10
options ndots:5

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

在容器内发请求时 , 会根据 /etc/resolv.conf 进行解析流程。选择 nameserver 10.1.0.10 进行解析 , 然后用nginx-svc-old , 依次带入 /etc/resolve.conf 中的 search 域 , 进行DNS查找 , 分别是 : search 内容类似如下 ( 不同的pod , 第一个域会有所不同)

search default.svc.cbear.local svc.cbear.local cbear.local openstacklocal

nginx-svc-old.default.svc.cbear.local -> nginx-svc-old.svc.cbear.local -> nginx-svc-old.cbear.local

这下更加怀疑core-dns有问题了。 但查看coredns日志 , 可以看到并没有报错 :

root@harbor:~# kubectl get pod -n kube-system  | grep dns
coredns-64f7575f5c-4x7vx                     1/1     Running            0          28h
coredns-64f7575f5c-fm9k2                     1/1     Running            0          28h
node-local-dns-7vml4                         1/1     Running            0          39h
node-local-dns-8gsdm                         1/1     Running            0          43h
node-local-dns-j2cjr                         1/1     Running            0          43h
node-local-dns-ls827                         1/1     Running            0          43h
node-local-dns-ps2m4                         1/1     Running            0          43h
node-local-dns-vs5kl                         1/1     Running            0          39h
root@harbor:~# kubectl logs -f coredns-64f7575f5c-4x7vx -n kube-system
.:53
[INFO] plugin/reload: Running configuration SHA512 = 89fa5715d77e9eec22921c1378e00639f6792aa14fdb18902fdd1c189282fa0ee7d9093626a7079f3ed3c8d6cc14c88ddd792b229ac19ecba7172b8d36a282c1
CoreDNS-1.11.1
linux/amd64, go1.20.7, ae2bbc2
^C
root@harbor:~# kubectl logs -f coredns-64f7575f5c-fm9k2  -n kube-system
.:53
[INFO] plugin/reload: Running configuration SHA512 = 89fa5715d77e9eec22921c1378e00639f6792aa14fdb18902fdd1c189282fa0ee7d9093626a7079f3ed3c8d6cc14c88ddd792b229ac19ecba7172b8d36a282c1
CoreDNS-1.11.1
linux/amd64, go1.20.7, ae2bbc2
^C

那就说明不是coredns问题了。。 经过一番查找 , 发现是busybox版本问题。busybox<=1.28.4

kubernetes-component-coredns-6

发现都说是busybox镜像的问题 , 从1.28.4以后的镜像都存在这问题。把镜像换成1.28.4试试?修改yaml版本号:

$ kubectl apply -f dns-Deployment-test002.yaml
$ kubectl get pod|grep busybox-deployment
$ kubectl exec  -ti busybox-deployment-564c775bbd-g6228 -- sh
$ nslookup nginx-svc-old.default.svc
# exit
$ kubectl get svc

root@harbor:~# kubectl get pod|grep busybox-deployment
busybox-deployment-7bb59c97cf-dcd6l   1/1     Running   0             12s
root@harbor:~# kubectl exec -it busybox-deployment-7bb59c97cf-dcd6l   -- sh
/ # nslookup nginx-svc-old.default.svc
Server:    169.254.20.10
Address 1: 169.254.20.10

Name:      nginx-svc-old.default.svc
Address 1: 10.68.12.0 nginx-svc-old.default.svc.cbear.local

确实可以成功解析域名了。所以这样看还真是busybox版本问题了。

5.3 宿主机上解析域名验证

nameserver关键字 , 如果没指定nameserver就找不到DNS服务器 , 其它关键字是可选的。nameserver表示解析域名时使用该地址指定的主机为域名服务器。其中域名服务器是按照文件中出现的顺序来查询的 , 且只有当第一个nameserver没有反应时才查询下面的nameserver , 一般不要指定超过3个服务器。

在宿主上/etc/resolv.conf中nameserver如下 :

$ kubectl get svc -n kube-system
$ kubectl get pod -n kube-system -o wide|grep dns
$ kubectl exec  -ti busybox-deployment-564c775bbd-g6228 -- cat /etc/resolv.conf
$ cat /etc/resolv.conf

root@harbor:~# kubectl get svc -n kube-system
NAME                        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                  AGE
dashboard-metrics-scraper   ClusterIP   10.68.139.177   <none>        8000/TCP                 44h
kube-dns                    ClusterIP   10.68.0.2       <none>        53/UDP,53/TCP,9153/TCP   44h
kube-dns-upstream           ClusterIP   10.68.210.31    <none>        53/UDP,53/TCP            44h
kubernetes-dashboard        NodePort    10.68.66.135    <none>        443:30741/TCP            44h
metrics-server              ClusterIP   10.68.166.80    <none>        443/TCP                  24h
node-local-dns              ClusterIP   None            <none>        9253/TCP                 44h
root@harbor:~# kubectl get pod -n kube-system -o wide|grep dns
coredns-64f7575f5c-4x7vx                     1/1     Running            0          28h   10.88.0.3       k8s-node03.cbear.top     <none>           <none>
coredns-64f7575f5c-fm9k2                     1/1     Running            0          28h   10.88.0.9       k8s-node02.cbear.top     <none>           <none>
node-local-dns-7vml4                         1/1     Running            0          39h   192.168.71.37   k8s-node03.cbear.top     <none>           <none>
node-local-dns-8gsdm                         1/1     Running            0          44h   192.168.71.35   k8s-node01.cbear.top     <none>           <none>
node-local-dns-j2cjr                         1/1     Running            0          44h   192.168.71.29   k8s-master01.cbear.top   <none>           <none>
node-local-dns-ls827                         1/1     Running            0          44h   192.168.71.30   k8s-master02.cbear.top   <none>           <none>
node-local-dns-ps2m4                         1/1     Running            0          44h   192.168.71.36   k8s-node02.cbear.top     <none>           <none>
node-local-dns-vs5kl                         1/1     Running            0          39h   192.168.71.31   k8s-master03.cbear.top   <none>           <none>
root@harbor:~# kubectl exec  -ti busybox-deployment-7bb59c97cf-dcd6l -- cat /etc/resolv.conf 
search default.svc.cbear.local svc.cbear.local cbear.local openstacklocal
nameserver 169.254.20.10
options ndots:5
root@harbor:~# cat /etc/resolv.conf 
# This is /run/systemd/resolve/stub-resolv.conf managed by man:systemd-resolved(8).
# Do not edit.
#
# This file might be symlinked as /etc/resolv.conf. If you're looking at
# /etc/resolv.conf and seeing this text, you have followed the symlink.
#
# This is a dynamic resolv.conf file for connecting local clients to the
# internal DNS stub resolver of systemd-resolved. This file lists all
# configured search domains.
#
# Run "resolvectl status" to see details about the uplink DNS servers
# currently in use.
#
# Third party programs should typically not access this file directly, but only
# through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a
# different way, replace this symlink by a static file or a different symlink.
#
# See man:systemd-resolved.service(8) for details about the supported modes of
# operation for /etc/resolv.conf.

nameserver 127.0.0.53
options edns0 trust-ad
search openstacklocal

后面四个是k8s dns的配置 , 把后面四个都放到前面来 , 再验证 :

nameserver 10.68.0.2
search default.svc.cbear.local svc.cbear.local cbear.local openstacklocal

6. NodeLocalDns

NodeLocal DNSCache 通过在集群节点上运行一个 DaemonSet 来提高 clusterDNS 性能和可靠性。处于 ClusterFirst 的 DNS 模式下的 Pod 可以连接到 kube-dns 的 serviceIP 进行 DNS 查询。通过 kube-proxy 组件添加的 iptables 规则将其转换为 CoreDNS 端点。通过在每个集群节点上运行 DNS 缓存 , NodeLocal DNSCache 可以缩短 DNS 查找的延迟时间、使 DNS 查找时间更加一致 , 以及减少发送到 kube-dns 的 DNS 查询次数。

借助这种新架构 , Pod 将可以访问在同一节点上运行的 DNS 缓存代理 , 从而避免 iptables DNAT 规则和连接跟踪。 本地缓存代理将查询 kube-dns 服务以获取集群主机名的缓存缺失 ( 默认为 "cluster.local" 后缀) 。

6.1 使用NodeLocal DNS原因

  • 使用当前的 DNS 体系结构 , 如果没有本地 kube-dns/CoreDNS 实例 , 则具有最高 DNS QPS 的 Pod 可能必须延伸到另一个节点。 在这种场景下 , 拥有本地缓存将有助于改善延迟。

  • 跳过 iptables DNAT 和连接跟踪将有助于减少 conntrack 竞争并避免 UDP DNS 条目填满 conntrack 表。

  • 从本地缓存代理到 kube-dns 服务的连接可以升级为 TCP。 TCP conntrack 条目将在连接关闭时被删除 , 相反 UDP 条目必须超时 ( 默认nf_conntrack_udp_timeout是30秒) 。

  • 将 DNS 查询从 UDP 升级到 TCP 将减少由于被丢弃的 UDP 包和 DNS 超时而带来的尾部等待时间 ; 这类延时通常长达 30 秒 ( 3 次重试 +10秒超时) 。 由于nodelocal缓存监听UDP DNS查询 , 应用不需要变更。

  • 在节点级别对 DNS 请求的度量和可见性。

  • 可以重新启用负缓存 , 从而减少对 kube-dns 服务的查询数量。

  • 避免IPVS缺陷导致的DNS概率性解析超时问题

  • 默认解析使用UDP , UDP默认返回报文长度为512字节 , 大于512字节后就会将包丢弃 , 升级为TCP协议可以解决这个问题。TCP可以进行数据包的切片可以完整的得到返回的response

6.2 工作原理

kubernetes-component-coredns-8
  • 配置了NodeLocal DNS本地缓存的Pod , 默认会通过NodeLocal DNSCache监听于节点上的IP(169.254.20.10)解析域名。

  • NodeLocal DNSCache本地若无缓存应答解析请求 , 则会通过kube-dns服务请求CoreDNS进行解析

  • CoreDNS对于非集群内域名 , 会通过VPC DNS服务器进行解析

  • 已注入DNS本地缓存的Pod , 当无法连通NodeLocal DNSCache时 , 会继而直接通过kube-dns服务连接到CoreDNS进行解析 , 此链路为备用链路

  • 未注入DNS本地缓存的Pod , 会通过标准的kube-dns服务链路连接到CoreDNS进行解析

6.3 安装Node Local DNS

  1. 获取文件将文件保存为nodelocaldns.yaml

    项目文件预览 - GitCode

  2. 如果使用 IPv6 , 在使用 'IP:Port' 格式的时候需要把 CoreDNS 配置文件里的所有 IPv6 地址用方括号包起来。 如果你使用上述的示例清单 , 需要把配置行 L70 修改为 : "health [__PILLAR__LOCAL__DNS__]:8080"。

  3. 把清单里的变量更改为正确的值:

    获取coredns的IP
    kubedns=`kubectl get svc coredns -n kube-system -o jsonpath={.spec.clusterIP}`
     
    # 表示集群域 , 默认就是 cluster.local
    domain=cluster.local
     
    # 表示 DNSCache 本地的 IP , 默认为169.254.20.10
    localdns=169.254.20.10
     
    <cluster-domain> 的默认值是 "cluster.local"。
    <localdns> 是 NodeLocal DNSCache 选择的本地侦听 IP 地址。

    如果 kube-proxy 运行在 IPTABLES模式:

    sed -i "s/__PILLAR__LOCAL__DNS__/$localdns/g; s/__PILLAR__DNS__DOMAIN__/$domain/g; s/__PILLAR__DNS__SERVER__/$kubedns/g" nodelocaldns.yaml

    node-local-dns Pod会设置 __PILLAR__CLUSTER__DNS__和 __PILLAR__UPSTREAM__SERVERS__。 在此模式下, node-local-dns Pod会同时侦听 kube-dns 服务的 IP 地址和 <node-local-address> 的地址 , 以便 Pod 可以使用其中任何一个 IP 地址来查询 DNS 记录。

    如果 kube-proxy 运行在 IPVS 模式:

    sed -i "s/__PILLAR__LOCAL__DNS__/$localdns/g; s/__PILLAR__DNS__DOMAIN__/$domain/g; s/,__PILLAR__DNS__SERVER__//g; s/__PILLAR__CLUSTER__DNS__/$kubedns/g" nodelocaldns.yaml

    在此模式下 , node-local-dns Pod 只会侦听 <node-local-address> 的地址。 node-local-dns 接口不能绑定 kube-dns 的集群 IP 地址 , 因为 IPVS 负载均衡使用的接口已经占用了该地址。 node-local-dns Pod 会设置 __PILLAR__UPSTREAM__SERVERS__

  4. 将NodeLocalDNS 部署到集群

    1. 如果是线上集群 , 推荐不要直接apply 文件 , 采用灰度的方式部署。我们可以在yaml中增加配置如下
     
        spec:
          nodeSelector:
            nodelocaldns: "true"
     
    2. 挑选集群的一些机器打上nodelocaldns=true的标签
    for i in `cat 1.txt`; do kubectl  label node $i nodelocaldns=true;done
     
    3. apply 文件
    kubectl apply -f nodelocaldns.yaml 
     
    4. 这个时候我们需要进入到pod内验证服务是否有异常 (这些打了标签的node 上面的pod)
    验证方式:
       1) pod内是否可以解析外网域名(前提条件 node节点可以访问域名)
       2) pod内解析内网域名
       2) pod内解析集群service name
    如果都没有问题 , 说明部署NodeLocalDNS后并没有影响我们集群现有的服务
     
    5. 将yaml中NodeSelect 配置注释 , 重新apply 
    kubectl apply -f nodelocaldns.yaml 
     
    6. 查看kube-system 命名空间下的node-local-dns pod
    kubectl  get pod -n kube-system | grep node-local
    kubernetes-component-coredns-9

    自此NodeLocalDNS就部署完成了 , 但是还没有应用到它

6.4 在应用中使用NodeLocal DNSCache

为了能使应用原本请求CoreDNS的流量改为由DNS缓存DaemonSet代理 , 需要使Pod内部的中nameservers配置成169.254.20.10和kube-dns对应的IP地址 , 您有以下几种方式可以选择:

  • 方式一:借助DNSConfig动态注入控制器在Pod创建时配置DNSConfig自动注入。

  • 方式二:创建Pod时手动指定DNSConfig。

  • 方式三:修改kubelet参数 , 并重启节点kubelet。存在业务中断风险 , 不推荐使用此方式。

第一种方式 :

需要集群具备adminssion webhook功能 , 或者可以使用第三方的一些插件完成部署

adminssion webhook:基于Admission Webhook机制拦截Pod创建的请求 , 自动注入使用DNS缓存的Pod DNSConfig信息。

第二种方式 :

由于我用了原生的部署方式 , 在我们生产环境我采用了是第二种应用方式

背景:我们的每一个服务都是通过helm 模板渲染生成一个chart, 我们gitlab代码仓库会管理一个helm 模板 , 所以我只要修改helm 模板 , 服务重新发布即可使用NodeLocal DNSCache了。你也可以手动修改yaml文件具体方式看你们当前环境是怎么部署的服务了。下面是我的配置

{{-if eq .Values.cluster "offline" }}
      dnsPolicy: None
      dnsConfig:
        nameservers: ["169.254.20.10","10.11.128.10"]
        searches:
        - yidian-prod.svc.cluster.local
        - default.svc.cluster.local
        - svc.cluster.local
        - cluster.local
        - yidian.com
        - yidian-inc.com
        options:
        - name: ndots
          value: "3"
        - name: attempts
          value: "2"
        - name: timeout 
          value: "1"
{{- end }}

第三种方式:

不建议使用尤其是生产环境禁止使用

如果是二进制部署的集群 , 需要修改/etc/systemd/system/kubelet.service.d/10-kubeadm.conf 文件中的--cluster-dns=xxx.xxx.xxx.xxx参数。

查看kbuelet启动依赖的配置文件
ps -elf | grep kubelet | grep cluster-dns  
systemctl  status  kubelet

如果是kubeadm部署的集群 , 修改/var/lib/kubelet/config.yaml文件中的clusterDNS参数

6.5 验证

上面说到由于我们是使用的第二种方式 , 需要重新发布才可以生效。下图是发布后pod内部的状况,可以看到我们的pod内有两行nameserver信息第一行为NodeLocalDNS的IP , 第二行是coreDNS的IP。

kubernetes-component-coredns-10

验证方式:

  1. pod内是否可以解析外网域名(前提条件node节点可以访问域名)

  2. pod内解析内网域名

  3. pod内解析集群service name

下面是我的nodelocaldns.yaml , 也可以使用我的yaml , 需要注意的是对应的那些变量要做变更 , 一定要改coredns的ip

apiVersion: v1
kind: ServiceAccount
metadata:
  name: node-local-dns
  namespace: kube-system
  labels:
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
---
apiVersion: v1
kind: Service
metadata:
  name: kube-dns-upstream
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
    kubernetes.io/name: "KubeDNSUpstream"
spec:
  ports:
  - name: dns
    port: 53
    protocol: UDP
    targetPort: 53
  - name: dns-tcp
    port: 53
    protocol: TCP
    targetPort: 53
  selector:
    k8s-app: coredns
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: node-local-dns
  namespace: kube-system
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
data:
  Corefile: |
    cluster.local:53 {
        errors
        cache {
                success 9984 30
                denial 9984 5
        }
        reload
        loop
        bind 169.254.20.10 __PILLAR__DNS__SERVER__
        forward . 10.11.128.10 {
                force_tcp
        }
        prometheus :9253
        health 169.254.20.10:8080
        }
    in-addr.arpa:53 {
        errors
        cache 30
        reload
        loop
        bind 169.254.20.10 __PILLAR__DNS__SERVER__
        forward . 10.11.128.10 {
                force_tcp
        }
        prometheus :9253
        }
    ip6.arpa:53 {
        errors
        cache 30
        reload
        loop
        bind 169.254.20.10 __PILLAR__DNS__SERVER__
        forward . 10.11.128.10 {
                force_tcp
        }
        prometheus :9253
        }
    .:53 {
        errors
        cache 30
        reload
        loop
        bind 169.254.20.10 __PILLAR__DNS__SERVER__
        forward . __PILLAR__UPSTREAM__SERVERS__ {
               force_tcp
        }
        prometheus :9253
        }
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-local-dns
  namespace: kube-system
  labels:
    k8s-app: node-local-dns
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
spec:
  updateStrategy:
    rollingUpdate:
      maxUnavailable: 10%
  selector:
    matchLabels:
      k8s-app: node-local-dns
  template:
    metadata:
      labels:
        k8s-app: node-local-dns
      annotations:
        prometheus.io/port: "9253"
        prometheus.io/scrape: "true"
    spec:
      priorityClassName: system-node-critical
      serviceAccountName: node-local-dns
      hostNetwork: true
      dnsPolicy: Default  # Don't use cluster DNS.
      tolerations:
      - key: "CriticalAddonsOnly"
        operator: "Exists"
      - effect: "NoExecute"
        operator: "Exists"
      - effect: "NoSchedule"
        operator: "Exists"
      containers:
      - name: node-cache
        image: hub.kce.ksyun.com/ksyun/k8s-dns-node-cache:1.22.8
        resources:
          requests:
            cpu: 25m
            memory: 5Mi
        args: [ "-localip", "169.254.20.10", "-conf", "/etc/Corefile", "-upstreamsvc", "kube-dns-upstream" ]
        securityContext:
          privileged: true
        ports:
        - containerPort: 53
          name: dns
          protocol: UDP
        - containerPort: 53
          name: dns-tcp
          protocol: TCP
        - containerPort: 9253
          name: metrics
          protocol: TCP
        livenessProbe:
          httpGet:
            host: 169.254.20.10
            path: /health
            port: 8080
          initialDelaySeconds: 60
          timeoutSeconds: 5
        volumeMounts:
        - mountPath: /run/xtables.lock
          name: xtables-lock
          readOnly: false
        - name: config-volume
          mountPath: /etc/coredns
        - name: kube-dns-config
          mountPath: /etc/kube-dns
      volumes:
      - name: xtables-lock
        hostPath:
          path: /run/xtables.lock
          type: FileOrCreate
      - name: kube-dns-config
        configMap:
          name: coredns
          optional: true
      - name: config-volume
        configMap:
          name: node-local-dns
          items:
            - key: Corefile
              path: Corefile.base
---
# A headless service is a service with a service IP but instead of load-balancing it will return the IPs of our associated Pods.
# We use this to expose metrics to Prometheus.
apiVersion: v1
kind: Service
metadata:
  annotations:
    prometheus.io/port: "9253"
    prometheus.io/scrape: "true"
  labels:
    k8s-app: node-local-dns
  name: node-local-dns
  namespace: kube-system
spec:
  clusterIP: None
  ports:
    - name: metrics
      port: 9253
      targetPort: 9253
  selector:
    k8s-app: node-local-dns


参考链接

Kubernetes ( k8s) DNS ( CoreDNS) 介绍 (qq.com)

基于 CoreDNS 和 K8s 构建云原生场景下的企业级 DNS (qq.com)

Kubernetes系列 ( 一四七) 访问其它服务的 Pod 策略 — dnsPolicy (qq.com)

CoreDNS和NodeLocalDNS配置详解 - 人艰不拆_zmc - 博客园 (cnblogs.com)

NodeLocal DNS介绍及部署应用_nodelocaldns-CSDN博客