Kubernetes DNS 与 Service 流量链路深度剖析——从真实案例看外部访问与集群内访问

本文以一个真实的生产案例为线索:https://prow-prod.mycloud.io/manual-trigger,完整还原一个外部请求从域名解析到命中 Pod 的全链路,并对比集群内访问(ClusterIP)与无头服务(Headless Service)的工作原理,帮助你建立完整的 K8s 网络脑图。


一、场景概述

我们在排查 CI 流水线时,需要调用 https://prow-prod.mycloud.io/manual-trigger 来手动触发 Prow Job。这个服务跑在 Kubernetes 集群(cluster 001)的 ci-system 命名空间里,Pod 名叫 manual-trigger

整条链路如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
外部用户

▼ DNS 解析
prow-prod.mycloud.io (CNAME → traefik.ci-system.svc.001.mycloud.io)

▼ A 记录 (kube2dns 自动注册)
10.49.32.12 ← CLB IPVS VIP

▼ 四层负载均衡
Traefik Pod :8443 (Ingress Controller)

▼ 七层路由 (/manual-trigger)
manual-trigger Service (ClusterIP 192.168.134.249:80)

▼ kube-proxy iptables/ipvs
manual-trigger Pod :8080

下面逐层拆解。


二、LoadBalancer Service:对外暴露的第一层

2.1 Service 类型回顾

类型 作用域 IP 来源
ClusterIP 仅集群内 集群内虚拟 IP 段
NodePort 节点端口 节点 IP + 随机端口
LoadBalancer 对外 外部负载均衡器分配 VIP
Headless 无 ClusterIP DNS 直接指向 Pod IP

2.2 Traefik 的 LoadBalancer Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Service
metadata:
name: traefik
namespace: ci-system
annotations:
network.mycloud.io/lbprovider: clb-ipvs # 声明使用 CLB IPVS 分配 VIP
spec:
type: LoadBalancer
selector:
app: traefik
ports:
- name: https
port: 443
targetPort: 8443
- name: http
port: 80
targetPort: 8081

关键注解 network.mycloud.io/lbprovider: clb-ipvs:告诉云平台的 LB Controller 使用 CLB(Cloud Load Balancer) 来分配一个 VIP。

CLB 工作原理

1
2
3
4
5
CLB Controller
└── 监听 type=LoadBalancer 的 Service
└── 从 VIP 池(如 10.49.32.0/24)分配一个空闲 IP
└── 在物理 LVS/IPVS 节点上配置四层转发规则
└── 将 VIP 写回到 Service 的 status.loadBalancer.ingress[0].ip

执行后,Service 状态变为:

1
2
NAME      TYPE           CLUSTER-IP        EXTERNAL-IP   PORT(S)
traefik LoadBalancer 192.168.38.189 10.49.32.12 80,443,8080

10.49.32.12 就是 CLB 分配的对外 VIP。


三、DNS 链:域名如何解析到 VIP

3.1 kube2dns:集群内自动 DNS 注册

kube2dns 是云平台的一个控制器,运行在 kube-system 中,专门监听 type=LoadBalancer 的 Service,一旦 VIP 分配完成,就自动在 CorpDNS(企业内部 DNS 系统)中注册 A 记录:

1
2
3
4
# Service 上的注解(kube2dns 写入)
network.mycloud.io/kube2dns: |
traefik.ci-system.svc.001.mycloud.io. 3600 IN A 10.49.32.12
12.32.49.10.in-addr.arpa. 3600 IN PTR traefik.ci-system.svc.001.mycloud.io.

命名规则是固定的:<svc-name>.<namespace>.svc.<cluster-id>.mycloud.io

3.2 外部 CNAME:人工配置的友好域名

traefik.ci-system.svc.001.mycloud.io 这个地址太长,对外不友好。平台管理员在 CorpDNS 中手动创建了一条 CNAME:

1
prow-prod.mycloud.io.  →  CNAME  →  traefik.ci-system.svc.001.mycloud.io.

这样最终解析链为:

1
2
3
4
dig prow-prod.mycloud.io

prow-prod.mycloud.io. 299 IN CNAME traefik.ci-system.svc.001.mycloud.io.
traefik.ci-system.svc.001.mycloud.io. 299 IN A 10.49.32.12

谁负责什么

DNS 记录 管理方式 组件
traefik.ci-system.svc.001.mycloud.io → 10.49.32.12 自动 kube2dns
prow-prod.mycloud.io → CNAME → ... 手动 CorpDNS 管理平台

四、Ingress + Traefik:七层路由

流量到达 VIP 10.49.32.12:443 后,CLB 将其转发到 Traefik Pod 的 8443 端口。Traefik 作为 Ingress Controller,读取集群中的 Ingress 资源做七层路由。

4.1 Ingress 资源

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
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ing
namespace: ci-system
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
rules:
- http:
paths:
- path: /manual-trigger
pathType: ImplementationSpecific
backend:
service:
name: manual-trigger
port:
number: 80
- path: /hook
backend:
service: { name: hook, port: { number: 8888 } }
- path: /
backend:
service: { name: deck, port: { number: 80 } }

4.2 Traefik 配置

Traefik 通过 ConfigMap 配置,指定使用 kubernetesIngress Provider:

1
2
[providers.kubernetesIngress]
namespaces = ["ci-system"]

Traefik 持续 watch ci-system 命名空间的 Ingress 对象,动态更新路由表。当请求路径为 /manual-trigger 时,路由到 manual-trigger Service 的 80 端口。


五、ClusterIP Service:集群内访问的核心

manual-trigger Service 是一个典型的 ClusterIP 类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: manual-trigger
namespace: ci-system
spec:
type: ClusterIP
clusterIP: 192.168.134.249
selector:
app: manual-trigger
ports:
- name: http
port: 80
targetPort: 8080

5.1 ClusterIP 的实现原理

ClusterIP 192.168.134.249 是一个虚拟 IP,不存在于任何网卡上。它的实现依赖 kube-proxy

1
2
3
4
kube-proxy(每个节点运行)
└── watch API Server 中的 Service 和 Endpoints 变化
└── 在节点 iptables/ipvs 中写入转发规则:
DNAT: 192.168.134.249:80 → Pod IP:8080 (轮询/随机)

集群内任意 Pod 访问 192.168.134.249:80,内核 netfilter 拦截并 DNAT 到真实 Pod IP。

5.2 集群内 DNS 解析

集群内的 Pod 通过 CoreDNS 解析 Service 名:

1
2
3
4
5
6
# 同命名空间内:
curl http://manual-trigger/api
# CoreDNS 解析:manual-trigger.ci-system.svc.cluster.local → 192.168.134.249

# 跨命名空间:
curl http://manual-trigger.ci-system.svc.cluster.local/api

CoreDNS 的 A 记录指向的是 ClusterIP(虚拟 IP),而非 Pod IP。

1
2
3
4
5
6
7
8
9
10
集群内 Pod
│ DNS 查询:manual-trigger.ci-system.svc.cluster.local

CoreDNS (kube-dns)
│ 返回 A 记录:192.168.134.249

kube-proxy iptables 拦截
│ DNAT: 192.168.134.249:80 → Pod:8080

manual-trigger Pod

六、Headless Service:直达 Pod IP

6.1 什么是 Headless Service

clusterIP: None 设置时,Service 变为 Headless,不再分配虚拟 IP:

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Service
metadata:
name: manual-trigger-headless
namespace: ci-system
spec:
clusterIP: None # 关键:无 ClusterIP
selector:
app: manual-trigger
ports:
- port: 8080

6.2 Headless Service DNS 解析行为

CoreDNS 对 Headless Service 的处理完全不同:

1
2
3
4
5
6
7
8
DNS 查询:manual-trigger-headless.ci-system.svc.cluster.local

# 普通 ClusterIP:返回 1 条 A 记录(虚拟 IP)
manual-trigger.ci-system.svc.cluster.local → 192.168.134.249

# Headless Service:直接返回所有 Pod IP
manual-trigger-headless.ci-system.svc.cluster.local → 10.x.x.1
→ 10.x.x.2 (多副本时)

同时,CoreDNS 还会为每个 Pod 生成独立的 DNS 记录(StatefulSet 场景尤为重要):

1
2
# Pod 级别的 DNS(格式:pod-ip.namespace.pod.cluster.local)
10-x-x-1.ci-system.pod.cluster.local → 10.x.x.1

6.3 Headless Service 的典型场景

场景 原因
StatefulSet(如 etcd、Kafka) 需要稳定的 Pod 身份标识(pod-0, pod-1…),客户端直连特定 Pod
自定义客户端负载均衡 gRPC 等长连接协议需要客户端感知所有 Pod IP
服务发现 应用自己实现故障切换,不依赖 kube-proxy

6.4 无头服务 vs 普通服务对比

1
2
3
4
5
普通 ClusterIP 访问流程:
Client → DNS(返回VIP) → iptables/ipvs(DNAT) → Pod

Headless Service 访问流程:
Client → DNS(返回Pod IP列表) → 直连 Pod(无 kube-proxy 介入)

kube-proxy 完全不参与 Headless Service 的流量转发,减少了一次内核 NAT 开销。


七、完整对比:三种 Service 的 DNS 与流量链路

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
33
34
35
36
37
38
39
40
41
                    外部访问 (LoadBalancer)
========================
浏览器/curl
│ DNS: prow-prod.mycloud.io

CorpDNS (CNAME → traefik.ci-system.svc.001.mycloud.io)
│ kube2dns 注册的 A 记录 → 10.49.32.12

CLB IPVS (四层,VIP 10.49.32.12:443)

Traefik Pod (七层,Ingress 路由 /manual-trigger)

manual-trigger Service (ClusterIP 虚拟 IP)

kube-proxy iptables DNAT

manual-trigger Pod :8080


集群内访问 (ClusterIP)
======================
集群内 Pod
│ DNS: manual-trigger.ci-system.svc.cluster.local

CoreDNS → 返回 ClusterIP 192.168.134.249

kube-proxy iptables DNAT

manual-trigger Pod :8080


集群内访问 (Headless)
=====================
集群内 Pod
│ DNS: manual-trigger-headless.ci-system.svc.cluster.local

CoreDNS → 返回所有 Pod IP(如 10.x.x.1, 10.x.x.2)

客户端直连 Pod(无 kube-proxy)

manual-trigger Pod :8080

八、关键组件职责总结

组件 职责
CLB / LB Controller 监听 LoadBalancer Service,从 VIP 池分配外部 IP,配置 IPVS 转发规则
kube2dns 监听 LoadBalancer Service,VIP 就绪后自动在 CorpDNS 注册 A/PTR 记录
CorpDNS 企业内部权威 DNS,存储 *.svc.<cluster>.mycloud.io 的 A 记录和手动 CNAME
CoreDNS 集群内 DNS,解析 *.svc.cluster.local;ClusterIP→虚拟IP,Headless→Pod IP
kube-proxy 每节点运行,维护 iptables/ipvs 规则,将 ClusterIP 流量 DNAT 到 Pod
Traefik Ingress Controller,读取 Ingress 资源,做七层 HTTP 路由
Ingress 资源 声明 URL 路径到后端 Service 的映射规则(由 Traefik 消费)

九、排查 405 的经验教训

回到最初的问题:为什么浏览器访问 https://prow-prod.mycloud.io/manual-trigger 会得到 405?

整条链路是完全正常的

  1. DNS 解析 ✓(CNAME → A 记录 → 10.49.32.12)
  2. CLB 四层转发 ✓(到 Traefik)
  3. Traefik 七层路由 ✓(/manual-trigger 规则存在)
  4. Service 和 Pod 健康 ✓(Pod 日志显示正常心跳)

问题在于:/manual-trigger 是一个 POST-only 的 API 端点,浏览器发 GET 请求,服务器返回 405 Method Not Allowed。这是应用层行为,与 K8s 网络无关。

正确的调用方式:

1
2
3
curl -k -X POST https://prow-prod.mycloud.io/manual-trigger \
-H "Content-Type: application/json" \
-d '{"job": "my-job-name", "refs": {...}}'

十、总结

访问场景 DNS 解析者 流量路径 负载均衡
外部访问 LoadBalancer CorpDNS + kube2dns CLB → Ingress → Service → Pod CLB(四层) + Ingress(七层)
集群内访问 ClusterIP CoreDNS kube-proxy DNAT → Pod kube-proxy iptables/ipvs
集群内访问 Headless CoreDNS 直连 Pod IP 客户端自行负载均衡

Kubernetes 的 Service 设计非常精妙:用统一的抽象屏蔽了底层 Pod 的变化,无论 Pod 漂移到哪个节点、IP 如何变化,Service 名始终有效。而 kube2dns 和 CLB 这样的平台扩展,则把这层抽象延伸到了集群外部,让外部域名与集群内 Service 无缝衔接。


深挖一:kube2dns 控制器内部机制

源码位于 controllers 仓库 pkg/controller/kube2udns/

控制器架构

kube2dns 是一个标准的 Kubernetes Controller,基于 client-go 的 Informer + WorkQueue 模式构建:

1
2
3
4
5
6
7
8
9
10
11
12
13
API Server
│ Watch Service / Endpoints / Pod 变化事件

Informer (cache + event handler)
│ AddFunc / UpdateFunc / DeleteFunc

WorkQueue (rate-limited)
│ 去重 + 限速

reconcile loop(handler.Handle)
│ 调用 go-corpDNS 客户端

CorpDNS REST API

控制器内部分为三种 Handler,分别处理不同场景:

Handler 监听对象 触发条件 DNS 操作
lbHandler Service (type=LoadBalancer) status.loadBalancer.ingress 有 IP A 记录 + PTR 记录
headlessHandler Service (clusterIP=None) + Endpoints Endpoints 的 Subsets 有 Pod IP A 记录 + PTR 记录
podHandler Pod Pod Running,有 Pod IP A 记录 + PTR 记录

LoadBalancer Handler 代码逻辑

以 Traefik 的 LoadBalancer Service 为例,lbHandler.updateLB 的核心逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1. 构建 FQDN:<svc>.<namespace>.svc.<cluster-id>.mycloud.io.
subdomain := buildDNSNameString(h.domain, "svc", service.Namespace, service.Name)
// → "traefik.ci-system.svc.001.mycloud.io."

// 2. 遍历所有 LB Ingress IP(通常只有 1 个 VIP)
for _, ingress := range service.Status.LoadBalancer.Ingress {
// 创建 A 记录:traefik.ci-system.svc.001.mycloud.io. → 10.49.32.12
recA := rr.New(subdomain, ttl, rrtype.A, ingress.IP)
// 创建 PTR 记录:12.32.49.10.in-addr.arpa. → traefik.ci-system.svc.001.mycloud.io.
recPTR := rr.New(rr.IPV4ToPTRDomain(ingress.IP), ttl, rrtype.PTR, subdomain)
newRecs = append(newRecs, recA, recPTR)
}

// 3. 调用 go-corpDNS 将记录 diff 写入 CorpDNS
h.udnsHelper.Update(ctx, oldRecs, newRecs)

// 4. 将已写入的记录序列化存入 Service 注解,用于下次 diff
// 注解 key:network.mycloud.io/kube2dns
h.metaHelper.SetCachedRecs(ctx, service, newRecs)

状态管理:kube2dns 将已注册的 DNS 记录序列化写入 Service 的 Annotationnetwork.mycloud.io/kube2dns),下次 reconcile 时先读取旧记录,与期望状态做 diff,再调用 CorpDNS API 增删。这避免了”先查后改”的竞态问题。

Finalizer 机制:kube2dns 在处理 Service 时会加上 Finalizer,确保 Service 被删除前 DNS 记录已先清理,防止残留 A 记录。

Headless Handler 与 Endpoints

Headless Service 的 DNS 不是注册 VIP,而是注册每一个 Endpoint(Pod IP)

1
2
3
4
5
6
7
8
9
Service: manual-trigger-headless (clusterIP: None)
└── Endpoints subsets:
└── addresses: [{ip: 10.x.x.1}, {ip: 10.x.x.2}]

kube2dns 生成:
manual-trigger-headless.ci-system.svc.001.mycloud.io. → 10.x.x.1
manual-trigger-headless.ci-system.svc.001.mycloud.io. → 10.x.x.2
12.x.x.01.in-addr.arpa. → manual-trigger-headless.ci-system.svc.001.mycloud.io.
12.x.x.02.in-addr.arpa. → manual-trigger-headless.ci-system.svc.001.mycloud.io.

headlessHandler 同时监听 Service 和 Endpoints 两类对象(共享 namespace/name),任意一个变化都会触发 reconcile。


深挖二:go-corpDNS —— CorpDNS 的 Go 客户端

源码位于 go-udns 仓库

go-corpDNS 是与企业内部权威 DNS(CorpDNS)交互的 HTTP REST 客户端库,kube2dns 依赖它来完成实际的 DNS 记录写入。

CorpDNS 系统架构

1
2
3
4
5
6
7
8
9
10
11
12
13
                  外部请求

GTM (Global Traffic Manager)
/ \
SLC 负载均衡器 LVS 负载均衡器
/ | \ / | \
UDNS-1 UDNS-2 UDNS-3 UDNS-4 UDNS-5 UDNS-6
\ | / \ | /
ANSP-SLC ◄──── 同步 ────► ANSP-LVS

Bind Master
│ Zone 传输(~30s)
Bind Slaves(各环境)
  • UDNS(CorpDNS 服务实例):每个区域 3 个实例,接收 REST API 请求
  • ANSP:权威 DNS 后端,SLC 与 LVS 互为 active/active,自动同步
  • Bind Slaves:负责将权威记录传播给生产/Corp/Dev 等各环境的解析服务器,传播延迟 ~30 秒

客户端接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// A 记录操作
type AClient interface {
Update(ctx, name string, ttl uint32, ips []string) Result // 批量设置 A 记录
Get(ctx, name string) GetAResult
Delete(ctx, name, ip string) Result
List(ctx, zone string) GetAllAResult
}

// PTR 记录操作
type PTRClient interface {
Update(ctx, ip string, ttl uint32, name string) Result // 反向解析
Get(ctx, ip string) GetPTRResult
Delete(ctx, ip, name string) Result
}

// CNAME / SRV 等
type CNAMEClient interface { ... }
type SRVClient interface { ... }

认证机制

go-corpDNS 支持两种认证方式:

方式 适用场景 原理
Keystone Token 人工/脚本 OpenStack Keystone /v2.0/tokens 获取临时 token,附加在 X-Auth-Token 请求头
TrustFabric 平台自动化(kube2dns) 基于服务身份证书,自动续期,无需人工操作

kube2dns 在集群内运行时使用 TrustFabric 认证,scope 为 udns,对应的 Keystone 权限角色为 CLOUD-ROLE-UnifiedDNS-USERS-WRITE

记录写入时序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
kube2dns lbHandler.updateLB()

├─ 读取 Service annotation 获取 oldRecs(已注册的记录)

├─ 根据当前 VIP 构造 newRecs(A + PTR)

├─ udnsHelper.Update(ctx, oldRecs, newRecs)
│ │
│ ├─ 对比 diff:需要删除的 / 需要新增的
│ │
│ ├─ go-corpDNS AClient.Update(name, ttl, ips) → POST /zones/<zone>/a/<name>
│ │
│ └─ go-corpDNS PTRClient.Update(ip, ttl, name) → POST /zones/<zone>/ptr/<reverse-ip>

└─ 将 newRecs 写回 Service annotation(状态缓存)

深挖三:CoreDNS 插件链与 cloud_ndots 优化

源码位于 coredns 仓库(定制版)

CoreDNS 插件链架构

CoreDNS 本质上是一个插件链(Plugin Chain),每个插件实现 Handler 接口:

1
2
3
4
5
6
7
type Handler interface {
ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error)
Name() string
}

// 链式调用:plugin.NextOrFailure(name, next, ctx, w, r)
// 若当前插件无法处理,调用 next.ServeDNS() 向后传递

云平台使用的 CoreDNS 典型插件链(Corefile 伪配置):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cluster.local {
cloud_ndots 001.mycloud.io { # 1. ndots 问题优化(自研插件)
domains ebay.com ebayc3.com
}
kubernetes cluster.local { # 2. 集群内 Service/Pod DNS
pods verified
fallthrough in-addr.arpa ip6.arpa
}
forward . /etc/resolv.conf { # 3. 兜底:转发给 CorpDNS
except cluster.local
}
cache 30 # 4. 响应缓存
errors # 5. 错误日志
prometheus :9153 # 6. metrics 暴露
}

cloud_ndots 插件:解决”ndots 噩梦”

Kubernetes 默认设置 Pod 的 /etc/resolv.conf ndots 为 5,这导致集群内 Pod 解析外部域名时会触发大量无效查询。

问题复现:Pod 内执行 curl https://github.corp.mycloud.io,DNS 解析器会依次尝试:

1
2
3
4
5
# ndots=5,github.corp.mycloud.io 只有 3 个 dot,触发 search 补全
github.corp.mycloud.io.ci-system.svc.cluster.local. # 第1次(失败)
github.corp.mycloud.io.svc.cluster.local. # 第2次(失败)
github.corp.mycloud.io.cluster.local. # 第3次(失败)
github.corp.mycloud.io. # 第4次(成功)

前 3 次都会打到 CoreDNS,CoreDNS 再 forward 给 CorpDNS,7 次 DNS 往返才能拿到结果

cloud_ndots 的优化:该插件识别出 github.corp.mycloud.io.ci-system.svc.001.mycloud.io 这类”带了 search suffix 的外部域名”请求,直接重写查询,一次命中,并在响应中加入 CNAME 记录(与 autopath 类似):

1
2
3
4
5
6
7
8
9
10
# 实际查询(带 search 补全的垃圾请求)
github.corp.mycloud.io.ci-system.svc.001.mycloud.io. IN A

# cloud_ndots 检测到外部域名 + .svc.<cluster> 后缀 → 重写为
github.corp.mycloud.io. IN A

# 响应(CNAME + 最终 A 记录,一次往返)
github.corp.mycloud.io.ci-system.svc.001.mycloud.io. CNAME github.corp.mycloud.io.
github.corp.mycloud.io. CNAME github-prod.glb.corp.mycloud.io.
github-prod.glb.corp.mycloud.io. A 10.x.x.x

效果:DNS 往返从 7 次减少到 1 次,外部域名解析延迟大幅下降。

kubernetes 插件:ClusterIP vs Headless

kubernetes 插件连接 K8s API Server,对 *.svc.cluster.local 查询实现不同处理:

1
2
3
4
5
6
7
查询:manual-trigger.ci-system.svc.cluster.local  (A)
└── 插件查找 Service(namespace=ci-system, name=manual-trigger)
└── 若 Service.Spec.ClusterIP != "None"
→ 返回 ClusterIP: 192.168.134.249
└── 若 Service.Spec.ClusterIP == "None"(Headless)
→ 查找 Endpoints,返回所有 Ready 的 Pod IP 列表
└── 若查不到 → fallthrough 到下一个插件(forward)

对于 Pod DNS10-x-x-1.ci-system.pod.cluster.local),插件通过 pods verified 模式查找真实 Pod 并验证 IP 匹配后返回。


深挖四:cluster 001 manual-trigger 完整 DNS 数据流

https://prow-prod.mycloud.io/manual-trigger 为主线,完整追踪每一层 DNS 交互:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
┌─────────────────────────────────────────────────────────────┐
│ 外部访问路径 │
└─────────────────────────────────────────────────────────────┘

① 用户浏览器发起请求
DNS Query: prow-prod.mycloud.io (A)


② 企业内部递归 DNS(Corp Resolver)
→ 查询 CorpDNS 权威服务器
← CNAME: prow-prod.mycloud.io → traefik.ci-system.svc.001.mycloud.io.
→ 继续查询 A 记录
← A: traefik.ci-system.svc.001.mycloud.io → 10.49.32.12

│ (此 A 记录由 kube2dns 写入,流程见 ⑥)

③ HTTP 请求到达 CLB IPVS VIP: 10.49.32.12:443
四层转发 → Traefik Pod :8443


④ Traefik 七层路由
path: /manual-trigger → Service: manual-trigger:80


⑤ CoreDNS(Traefik 内部解析 manual-trigger Service)
Query: manual-trigger.ci-system.svc.cluster.local
kubernetes 插件 → ClusterIP: 192.168.134.249
kube-proxy DNAT → Pod IP:8080


manual-trigger Pod


┌─────────────────────────────────────────────────────────────┐
│ kube2dns 注册 DNS 记录(异步,VIP 就绪时触发) │
└─────────────────────────────────────────────────────────────┘

⑥ CLB Controller 为 traefik Service 分配 VIP: 10.49.32.12
写入 service.status.loadBalancer.ingress[0].ip


kube2dns lbHandler(watch Service 变化)
检测到 status.loadBalancer.ingress 就绪


buildDNSNameString(domain="001.mycloud.io", "svc", "ci-system", "traefik")
→ FQDN: "traefik.ci-system.svc.001.mycloud.io."


go-corpDNS AClient.Update(
name="traefik.ci-system.svc.001.mycloud.io.",
ttl=3600, ips=["10.49.32.12"])
→ POST https://corpDNS.vip.mycloud.io/zones/001.mycloud.io/a/...

go-corpDNS PTRClient.Update(
ip="10.49.32.12", ttl=3600,
name="traefik.ci-system.svc.001.mycloud.io.")
→ POST https://corpDNS.vip.mycloud.io/zones/.../ptr/...


CorpDNS ANSP 写入权威记录
→ Bind Slaves 同步(~30s)
→ 全网解析生效


┌─────────────────────────────────────────────────────────────┐
│ 集群内访问路径(ClusterIP) │
└─────────────────────────────────────────────────────────────┘

集群内 Pod curl http://manual-trigger/api


/etc/resolv.conf search 展开(ndots=5)
→ manual-trigger.ci-system.svc.cluster.local.(最终命中)


CoreDNS(Pod 的 nameserver 指向 CoreDNS ClusterIP)
cloud_ndots 插件:不是外部域名,pass through
kubernetes 插件:
Service ci-system/manual-trigger 存在
ClusterIP: 192.168.134.249(非 None)
→ 返回 A: 192.168.134.249


kube-proxy iptables DNAT
192.168.134.249:80 → Pod IP:8080(轮询)

DNS 记录生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Service 创建
└── CLB Controller 分配 VIP
└── kube2dns 注册 CorpDNS A+PTR 记录
└── CoreDNS kubernetes 插件缓存 Service(TTL=5s)

Service 更新(VIP 变更)
└── kube2dns diff oldRecs vs newRecs
└── 删除旧 A 记录,写入新 A 记录
└── Finalizer 确保顺序:先删 DNS,再删 Service

Service 删除
└── Kubernetes 触发 Finalizer 流程
└── kube2dns deleteLB → go-corpDNS 删除 A + PTR
└── 移除 Finalizer → Service 真正删除

十一、扩展总结:三层 DNS 体系

理解了源码之后,可以归纳出云平台 K8s 集群的三层 DNS 体系

1
2
3
4
5
6
7
8
9
10
11
12
┌──────────────────────────────────────────────────────┐
│ 层次 │ 组件 │ 解析范围 │
├──────────┼───────────────┼──────────────────────────── │
│ L1 │ CorpDNS │ 全局:*.mycloud.io 外部域名 │
│ (外部) │ (权威 DNS) │ 由 kube2dns 自动注册 │
├──────────┼───────────────┼──────────────────────────── │
│ L2 │ CoreDNS │ 集群内:*.svc.cluster.local │
│ (集群) │ (kube-dns) │ kubernetes 插件实时查询 API │
├──────────┼───────────────┼──────────────────────────── │
│ L3 │ cloud_ndots │ 跨层优化:减少 ndots 引起 │
│ (优化) │ (自研插件) │ 的无效 CorpDNS 转发请求 │
└──────────────────────────────────────────────────────┘

数据面总结

组件 职责 实现
go-corpDNS CorpDNS REST 客户端 HTTP + Keystone/TrustFabric 认证
kube2dns K8s → CorpDNS 桥接器 Informer + WorkQueue + Finalizer
CoreDNS kubernetes 插件 集群内 Service DNS Watch API Server,ClusterIP/Headless 分支处理
cloud_ndots 插件 ndots 优化 Regexp 匹配 + 查询重写 + CNAME 注入
CorpDNS (UDNS) 企业权威 DNS 多区域 ANSP active/active + Bind Slaves 传播