DNS 服务

1. 概述

作为服务发现机制的基本功能,在 Kubernetes 集群中需要能够通过服务名对服务进行访问,这就需要一个 Kubernetes 集群范围内的 DNS 服务来完成从服务名到ClusterIP 地址的解析。Kubernetes 在较新的版本中默认使用 CoreDNS 作为 DNS 服务。

2. 部署 CoreDNS 服务

部署 CoreDNS 服务时需要创建3个资源对象:ConfigMap、Deployment 和Service。在启用了 RBAC 的 Kubernetes 集群中,还可以通过设置ServiceAccount、ClusterRole、ClusterRoleBinding 来对 CoreDNS 容器进行权限设置。

2.1. 部署 ConfigMap

示例:

apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        health {
           lameduck 5s
        }
        ready
        kubernetes cluster.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
        loop
        reload
        loadbalance
    }
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
YAML

2.2. CoreDNS 的配置说明

CoreDNS 的主要功能是通过插件系统实现的。CoreDNS 实现了一种链式插件结构,将DNS 的逻辑抽象成了一个个插件,能够灵活组合使用,在进行域名解析时这些插件将以从上到下的顺序依次执行。

常用的插件介绍如下:

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

etcd 和 hosts 插件都可以用于自定义域名记录。

使用 etcd 插件自定义域名示例

  • 修改 CoreDNS ConfigMap
apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        health {
           lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        # 开始配置etcd插件
        etcd {
           stubzones
           # 前缀键路径
           path /skydns
           # etcd 集群端点地址
           endpoint 192.168.2.151:2379 192.168.2.152:2379 192.168.2.153:2379
           upstream /etc/resolv.conf
           # 配置访问etcd证书,注意顺序一定要正确(无证书删除此配置即可)
           tls /etc/etcd-tls/client.crt /etc/etcd-tls/client.key  /etc/etcd-tls/ca.crt
           fallthrough
        }
        prometheus :9153
        forward . /etc/resolv.conf {
           max_concurrent 1000
        }
        cache 30
        loop
        reload
        loadbalance
    }
kind: ConfigMap
metadata:
  creationTimestamp: "2025-07-14T01:32:50Z"
  name: coredns
  namespace: kube-system
  resourceVersion: "2049822"
  uid: f8fd4439-ae87-4854-a73e-e6825ce8beec
YAML
  • 如果 etcd 访问需要证书,需要将证书挂载到容器
# 创建 secret
kubectl create secret generic coredns-etcd-certs \
  --namespace=kube-system \
  --from-file=ca.crt=/etc/kubernetes/pki/etcd/ca.crt \
  --from-file=client.crt=/etc/kubernetes/pki/etcd/healthcheck-client.crt \
  --from-file=client.key=/etc/kubernetes/pki/etcd/healthcheck-client.key
  
# 修改 CoreDNS Deployment 挂载证书
kubectl -n kube-system get deployment coredns -o yaml > coredns.yaml

# 在 spec.template.spec 下添加以下内容:
volumes:
- name: etcd-certs
  secret:
    secretName: coredns-etcd-certs
    defaultMode: 420
# 在 containers 部分的 volumeMounts 中添加:
volumeMounts:
- name: etcd-certs
  mountPath: /etc/etcd-tls
  readOnly: true
  
Bash
  • 向 etcd 写入 DNS 记录
# etcd 中的键需遵循 SkyDNS 路径格式(CoreDNS 兼容):
/skydns/com/mydomain/[service]/[record-name] = DNS 记录值

# 添加 A 记录
sudo ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key put /skydns/com/mydomain/service1 '{"host":"10.0.0.1","ttl":60}'
Bash
  • 验证 DNS 解析
dig @10.96.0.10 service1.mydomain.com  # 10.96.0.10 CoreDNS service IP

; <<>> DiG 9.16.23-RH <<>> @10.96.0.10 service1.mydomain.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 23411
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: c8ceafcd7212a598 (echoed)
;; QUESTION SECTION:
;service1.mydomain.com.		IN	A

;; ANSWER SECTION:
service1.mydomain.com.	30	IN	A	10.0.0.1    # 解析到 IP 地址

;; Query time: 3 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Sun Aug 17 01:01:53 CST 2025
;; MSG SIZE  rcvd: 99
Bash

使用 hosts 插件自定义域名示例

  • 修改 CoreDNS ConfigMap
apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        health {
           lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        # 开始配置hosts插件
        hosts {
            # 格式:IP 域名 [别名...]
            10.0.0.1 service1.mydomain.com
            10.0.0.2 service2.mydomain.com api.mydomain.com
            # 支持从文件加载(需挂载)
            # file /etc/coredns/custom-hosts
            fallthrough  # 若未匹配,继续向下传递查询
        }
        prometheus :9153
        forward . /etc/resolv.conf {
           max_concurrent 1000
        }
        cache 30
        loop
        reload
        loadbalance
    }
kind: ConfigMap
metadata:
  creationTimestamp: "2025-07-14T01:32:50Z"
  name: coredns
  namespace: kube-system
  resourceVersion: "2049822"
  uid: f8fd4439-ae87-4854-a73e-e6825ce8beec
YAML
  • 重启 CoreDNS
kubectl rollout restart deployment/coredns -n kube-system
Bash

forward 插件用于配置上游 DNS 服务器,在 CoreDNS 配置中,forward . /etc/resolv.conf 的 /etc/resolv.conf 指的是 CoreDNS 容器内部的 /etc/resolv.conf 文件,默认情况下,容器内的 /etc/resolv.conf 通常由容器运行时自动生成,并继承宿主机的 DNS 配置(或根据用户配置覆盖)。

2.3. 部署 Deployment

示例:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    k8s-app: kube-dns
  name: coredns
  namespace: kube-system
spec:
  progressDeadlineSeconds: 600
  replicas: 2
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: kube-dns
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        k8s-app: kube-dns
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: k8s-app
                  operator: In
                  values:
                  - kube-dns
              topologyKey: kubernetes.io/hostname
            weight: 100
      containers:
      - args:
        - -conf
        - /etc/coredns/Corefile
        image: registry.k8s.io/coredns/coredns:v1.11.3
        imagePullPolicy: IfNotPresent
        livenessProbe:
          failureThreshold: 5
          httpGet:
            path: /health
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 60
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 5
        name: coredns
        ports:
        - containerPort: 53
          name: dns
          protocol: UDP
        - containerPort: 53
          name: dns-tcp
          protocol: TCP
        - containerPort: 9153
          name: metrics
          protocol: TCP
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /ready
            port: 8181
            scheme: HTTP
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        resources:
          limits:
            memory: 170Mi
          requests:
            cpu: 100m
            memory: 70Mi
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            add:
            - NET_BIND_SERVICE
            drop:
            - ALL
          readOnlyRootFilesystem: true
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /etc/coredns
          name: config-volume
          readOnly: true
      dnsPolicy: Default
      nodeSelector:
        kubernetes.io/os: linux
      priorityClassName: system-cluster-critical
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      serviceAccount: coredns
      serviceAccountName: coredns
      terminationGracePeriodSeconds: 30
      tolerations:
      - key: CriticalAddonsOnly
        operator: Exists
      - effect: NoSchedule
        key: node-role.kubernetes.io/control-plane
      volumes:
      - configMap:
          defaultMode: 420
          items:
          - key: Corefile
            path: Corefile
          name: coredns
        name: config-volume
YAML

其中 replicas 副本数量需要根据服务规模设置,也可以使用 HPA 自动扩缩。对 Pod 的资源限制也应该根据实际情况调整。

2.4. 部署 Service

示例:

apiVersion: v1
kind: Service
metadata:
  annotations:
    prometheus.io/port: "9153"
    prometheus.io/scrape: "true"
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: CoreDNS
  name: kube-dns
  namespace: kube-system
spec:
  clusterIP: 10.96.0.10
  clusterIPs:
  - 10.96.0.10
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - name: dns
    port: 53
    protocol: UDP
    targetPort: 53
  - name: dns-tcp
    port: 53
    protocol: TCP
    targetPort: 53
  - name: metrics
    port: 9153
    protocol: TCP
    targetPort: 9153
  selector:
    k8s-app: kube-dns
  sessionAffinity: None
  type: ClusterIP
YAML

2.5. 修改每个 Node 上的 kubelet 启动参数

  • --cluster-dns=10.96.0.10:指定 CoreDNS 的 clusterIP 地址。
  • --cluster-domain=cluster.local:指定 k8s 集群域名地址,在 CoreDNS 的 Corefile 配置中,会引用集群域名,例如 kubernetes cluster.local。

3. Service 和 Pod 的 DNS 域名相关特性

3.1. Service 的 DNS 域名

对于 Service 来说,Kubernetes 会为其设置一个如下格式的 DNS 域名:

<service-name>.<namespace-name>.sve.<cluster-domain>

# cluster-domain是集群配置的域后缀名称。如 --cluster-domain=cluster.local
TeX

解析 Service IP 示例:

# 这里使用 CoreDNS 的 Service 示例
dig @10.96.0.10 kube-dns.kube-system.svc.cluster.local

; <<>> DiG 9.16.23-RH <<>> @10.96.0.10 kube-dns.kube-system.svc.cluster.local
; (1 server found)
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 25578
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 68ae0df35d89eb84 (echoed)
;; QUESTION SECTION:
;kube-dns.kube-system.svc.cluster.local.	IN A

;; ANSWER SECTION:
kube-dns.kube-system.svc.cluster.local.	30 IN A	10.96.0.10

;; Query time: 1 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Sun Aug 17 11:36:58 CST 2025
;; MSG SIZE  rcvd: 133
Bash

因为 HedlessService 没有 ClusterIP 地址,所以 DNS 解析的结果会是后端 Pod 的 IP 地址列表。另外,当 HedlessService 关联了 StatefulSet 的 Pod 时,还会为每个 Pod 指定一个域名,格式为:

<pod-name>.<service-name>.<namespace-name>.sve.<cluster-domain>
TeX

另外,Kubernetes 系统也会为 Service 的每个命名端口号(port.name)都创建一个 SRV 记录,格式如下:

_<port-name>._<port-protoco1>.<service-name>.<namespace-name>.sve.<cluster-domain>
TeX

示例:

dig @10.96.0.10 _dns-tcp._tcp.kube-dns.kube-system.svc.cluster.local

; <<>> DiG 9.16.23-RH <<>> @10.96.0.10 _dns-tcp._tcp.kube-dns.kube-system.svc.cluster.local
; (1 server found)
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 17902
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: d9f0d9c34a96462e (echoed)
;; QUESTION SECTION:
;_dns-tcp._tcp.kube-dns.kube-system.svc.cluster.local. IN A

;; ANSWER SECTION:
_dns-tcp._tcp.kube-dns.kube-system.svc.cluster.local. 30 IN A 10.96.0.10    # 同样能解析到 Service 的 IP 地址

;; Query time: 1 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Sun Aug 17 11:48:22 CST 2025
;; MSG SIZE  rcvd: 161


# HedlessService 同理,也会解析到后端 Pod 的 IP 地址列表
Bash

3.2. Pod 的 DNS 域名

对所有 Pod,Kubernetes 会为其设置一个如下格式的 DNS 域名:

<pod-ip>.<namespace>.pod.<cluster-domain>
TeX

对于通过 Service 关联的 Pod,Kubernetes 会为每个 Pod 都以其 IP 地址和 Service名称设置一个 DNS 域名,格式如下:

<pod-ip>.<service-name>.<namespace-name>.sve.<cluster-domain>

# 通过 Service 关联的 Pod 这两个 DNS 域名是同时存在的
TeX

示例:

$ kubectl get -n dev-env pod -o wide 
NAME                              READY   STATUS    RESTARTS      AGE   IP            NODE          NOMINATED NODE   READINESS GATES
python-dev-env-69b4b5f6f4-928dw   1/1     Running   3 (69m ago)   46h   10.244.3.54   k8s-node-01   <none>           <none>

$ kubectl get -n dev-env svc -o wide 
NAME                 TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE   SELECTOR
python-dev-service   NodePort   10.102.210.249   <none>        22:30022/TCP   46h   app=python-dev


# 解析 pod 格式的域名
$ dig @10.96.0.10 10-244-3-54.dev-env.pod.cluster.local

; <<>> DiG 9.16.23-RH <<>> @10.96.0.10 10-244-3-54.dev-env.pod.cluster.local
; (1 server found)
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 45879
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: bfba189f40bf842c (echoed)
;; QUESTION SECTION:
;10-244-3-54.dev-env.pod.cluster.local. IN A

;; ANSWER SECTION:
10-244-3-54.dev-env.pod.cluster.local. 30 IN A	10.244.3.54

;; Query time: 2 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Sun Aug 17 12:18:38 CST 2025
;; MSG SIZE  rcvd: 131


# 解析 svc 格式的域名
$ dig @10.96.0.10 10-244-3-54.python-dev-service.dev-env.svc.cluster.local

; <<>> DiG 9.16.23-RH <<>> @10.96.0.10 10-244-3-54.python-dev-service.dev-env.svc.cluster.local
; (1 server found)
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 34686
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 583873fc05da7573 (echoed)
;; QUESTION SECTION:
;10-244-3-54.python-dev-service.dev-env.svc.cluster.local. IN A

;; ANSWER SECTION:
10-244-3-54.python-dev-service.dev-env.svc.cluster.local. 30 IN	A 10.244.3.54

;; Query time: 1 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Sun Aug 17 12:19:16 CST 2025
;; MSG SIZE  rcvd: 169
Bash

3.3. Pod 自定义固定域名

由上面的示例可知,这些域名都与 Pod 的 IP 地址有关,但在实际场景通常 IP 地址都不是固定的,所以无法实现通过一个固定域名解析到相应的 Pod IP 地址。
除了上一节提到,使用 HedlessService 关联 StatefulSet 的 Pod 来使每个 Pod 拥有一个固定域名外,我们还可以使用 hostname 和 subdomain 字段来为 Pod 配置域名。

单独创建 Pod

# individual-pods-example.yaml
apiVersion: v1
kind: Pod
metadata:
  name: busybox1
  labels:
    name: busybox  # 这个标签两个 pod 需要一样,方便后续统一选中
spec:
  hostname: busybox-1
  subdomain: default-subdomain  # 这里的名字就是一会要创建出的无头服务的名字
  containers:
  - image: busybox:1.28
    command:
    - sleep
    - '3600'
    name: busybox
    
---
apiVersion: v1
kind: Pod
metadata:
  name: busybox2
  labels:
    name: busybox  # 这个标签两个 pod 需要一样,方便后续统一选中
spec:
  hostname: busybox-2
  subdomain: default-subdomain  # 这里的名字就是一会要创建出的无头服务的名字
  containers:
  - image: busybox:1.28c	
    command:
    - sleep
    - '3600'
    name: busybox
YAML

为上面的 Pod 创建一个 Headless Service:

# headless-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: default-subdomain # 名字与 subdomain 字段保持一致
spec:
  selector:
    name: busybox
  clusterIP: None
  ports:
  - name: test
    port: 1234  
    targetPort: 1234  # 随意指定,因为被选中的 Pod 中也没监听端口
YAML

验证:

$ kubectl get pod -o wide 
NAME         READY   STATUS    RESTARTS   AGE     IP            NODE          NOMINATED NODE   READINESS GATES
busybox1     1/1     Running   0          7m22s   10.244.4.96   k8s-node-02   <none>           <none>
busybox2     1/1     Running   0          7m22s   10.244.4.97   k8s-node-02   <none>           <none>


$ dig @10.96.0.10 busybox-1.default-subdomain.default.svc.cluster.local

;; ANSWER SECTION:
busybox-1.default-subdomain.default.svc.cluster.local. 30 IN A 10.244.4.96


$ dig @10.96.0.10 busybox-2.default-subdomain.default.svc.cluster.local

;; ANSWER SECTION:
busybox-2.default-subdomain.default.svc.cluster.local. 30 IN A 10.244.4.97
Bash

3.4. Pod 的 DNS 策略

在 Kubernetes 中,Pod 的 DNS 解析行为由 dnsPolicy 字段控制,它决定了 Pod 如何获取 DNS 配置以及如何解析域名。

Pod 的 dnsPolicy 可以设置为以下四种模式:

  • ClusterFirst(默认):优先使用集群 DNS(如 CoreDNS),无法解析的域名转发到上游 DNS。

示例:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
  dnsPolicy: ClusterFirst  # 默认值,可省略
YAML
  • Default:继承 Pod 所在节点的 /etc/resolv.conf。
    • 这种方式其实是让 kubelet 来决定使用何种 DNS 策略。而 kubelet 默认的方式,就是使用宿主机的 /etc/resolv.conf,kubelet 是可以灵活来配置使用什么文件来进行 DNS 策略的,我们完全可以使用 kubelet 的参数 –resolv-conf=/etc/resolv.conf 来决定你的 DNS 解析文件地址。

示例:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
  dnsPolicy: Default
YAML
  • ClusterFirstWithHostNet:即使 Pod 使用 hostNetwork: true,仍优先使用集群 DNS。

示例:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  hostNetwork: true  # 使用主机网络
  dnsPolicy: ClusterFirstWithHostNet
  containers:
  - name: nginx
    image: nginx
YAML
  • None:不自动配置 DNS,需通过 dnsConfig 手动指定。

示例:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  dnsPolicy: None
  dnsConfig:
    nameservers:
      - 8.8.8.8  # 自定义 DNS 服务器
    searches:
      - mydomain.com  # 自定义搜索域
    options:
      - name: ndots
        value: "2"
YAML

4. LocalDNS 配置

集群规模大的情况,CoreDNS 可能会出现超时 5s 的情况(dns 请求超时,客户端默认策略时等待 5s 自动重试,如果重试成功,我们看到的现象就是 dns 请求有 5s的延时),这对我们影响很大,因为 k8s 集群都依赖它来实现域名解析。

超时的原因:无论你用的是 iptables 模式还是 ipvs 模式,它们的底层都会用到 conntrack 内核模块来组织 dns 的 udp 查询包,高并发场景,会出现 conntrack 冲突问题,导致一些 dns 请求包被丢掉,从而导致超时。

为了解决这些问题,k8s 引入了 Node 本地 DNS 缓存(Node Local DNS Cache)。

4.1. 部署 LocalDNS

直接获取官方的资源清单:

# 官方下载地址:
wget https://github.com/kubernetes/kubernetes/raw/master/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml

# 该资源清单文件中需要配置的几个变量说明
 __PILLAR__DNS__SERVER__  # 表示 CoreDNS Service 的 ClusterIP
 __PILLAR__LOCAL__DNS__   # 设置 LocalDNS 本地的 IP,默认为 169.254.20.10
 __PILLAR__DNS__DOMAIN__  # 表示集群域,默认就是 cluster.local
 
Bash

安装脚本:

# 定义变量
kubedns=10.96.0.10      # CoreDNS Service 的 ClusterIP
domain=cluster.local    # 表示集群域,默认就是 cluster.local
localdns=169.254.20.10  # 设置 LocalDNS  本地的 IP,默认为 169.254.20.10

wget https://github.com/kubernetes/kubernetes/raw/master/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml

# 修改 nodelocaldns.yaml 文件
# 如果 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

# 如果 kube-proxy 运行在 IPVS 模式
sed -ri "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

# 镜像地址修改
# sed -ri 's@registry.k8s.io/dns/k8s-dns-node-cache:1.23.0@registry.cnhangzhou.aliyuncs.com/egon-k8s-test/nodelocaldns:1.23.0@g' nodelocaldns.yaml

# 部署
# 需要注意的是这里使用 DaemonSet 部署 node-local-dns 使用了 hostNetwork=true ,会占用宿主机的 8080 端口,所以需要保证该端口未被占用。
kubectl apply -f nodelocaldns.yaml
Bash

查看:

kubectl get pods -n kube-system -l k8s-app=node-local-dns

NAME                   READY   STATUS    RESTARTS   AGE
node-local-dns-76cfc   1/1     Running   0          106s
node-local-dns-7nzsl   1/1     Running   0          106s
node-local-dns-98dhj   1/1     Running   0          106s
node-local-dns-jvr9l   1/1     Running   0          106s
node-local-dns-mqtcj   1/1     Running   0          106s
Bash

如果 kube-proxy 组件使用的是 ipvs 模式的话我们还需要修改每个节点上的
kubelet 的 –cluster-dns 参数,将其指向 169.254.20.10。

vim /var/lib/kubelet/config.yaml
clusterDNS:
- 169.254.20.10  # 原值为10.96.0.10
 
 
systemctl restart kubelet  # 然后重启kubelet
Bash

Daemonset 会在每个节点创建一个网卡来绑这个 IP,Pod 向本节点这个 IP 发 DNS 请求,缓存没有命中的时候才会再代理到上游集群 DNS 进行查询。

# 宿主机上执行
$ ip a
...
5: nodelocaldns: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default 
    link/ether be:2d:83:60:e4:99 brd ff:ff:ff:ff:ff:ff
    inet 169.254.20.10/32 scope global nodelocaldns
       valid_lft forever preferred_lft forever
Bash

iptables 模式下 Pod 还是向原来的集群 DNS 请求,节点上有这个 IP 监听,会被本机拦截,再请求集群上游 DNS,所以不需要更改 –cluster-dns 参数。如果担心线上环境修改 –cluster-dns 参数会产生影响,我们也可以直接在新部署的 Pod 中通过
dnsConfig 配置使用新的 localdns 的地址来进行解析。

测试:

# 用 coredns 来解析
$ dig @10.96.0.10 busybox-1.default-subdomain.default.svc.cluster.local  

;; ANSWER SECTION:
busybox-1.default-subdomain.default.svc.cluster.local. 30 IN A 10.244.4.96


# 用 nodelocaldns 来解析
$ dig @169.254.20.10 busybox-1.default-subdomain.default.svc.cluster.local 

;; ANSWER SECTION:
busybox-1.default-subdomain.default.svc.cluster.local. 30 IN A 10.244.4.96
Bash

对于部署 nodelocaldns 之前启动的 Pod 要想在 Pod 内部使用 node-local-dns 则需要重建。

# 使用前面创建的 busybox-1 Pod 示例
$ kubectl exec -it busybox1 -- sh
/ # cat /etc/resolv.conf 
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10    # 还是指向 CoreDNS
options ndots:5

# 重建 Pod
$ kubectl exec -it busybox1 -- sh
/ # cat /etc/resolv.conf 
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 169.254.20.10    # 指向了 Node Local DNS
options ndots:5
Bash

4.2. Node Local DNS 解析流程

部署 Node Local DNS Cache 采用的是 DaemonSet 控制器,会确保每个节点都有一个实例,来提升集群 DNS 的性能和可靠性 Node Local DNS Cache会在每个物理节点上缓存 DNS 解析,这可以缩短 DNS 查找的延迟时间、使 DNS 查找时间更加一致,以及减少发送到 CoreDNS 的 DNS 查询次数。

Node Local DNS 的解析流程

Pod 的 /etc/resolv.conf 配置指向 NodeLocal DNS(169.254.20.10)。

+-------------------+       +-------------------+       +-------------------+
|       Pod 1       |       |       Pod 2       |       |       Pod N       |
|  DNS请求:         |       |  DNS请求:         |        |  DNS请求:         |
|  ->169.254.20.10  |       |  ->169.254.20.10  |       |  ->169.254.20.10  |
+-------------------+       +-------------------+       +-------------------+
            |                        |                        |
            v                        v                        v
+-----------------------------------------------------------------------+
|                     NodeLocal DNS (DaemonSet)                         |
| 监听: 169.254.20.10:53                                                 |
| 作用:                                                                  |
| 1. 检查本地缓存                                                         |
| 2. 未命中则转发请求                                                      |
+-----------------------------------------------------------------------+
            |
            v
+-----------------------------------------------------------------------+
|                    上游 DNS (如 CoreDNS)                               |
+-----------------------------------------------------------------------+
TeX
上一篇
下一篇