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 | 外部用户 |
下面逐层拆解。
二、LoadBalancer Service:对外暴露的第一层
2.1 Service 类型回顾
| 类型 | 作用域 | IP 来源 |
|---|---|---|
| ClusterIP | 仅集群内 | 集群内虚拟 IP 段 |
| NodePort | 节点端口 | 节点 IP + 随机端口 |
| LoadBalancer | 对外 | 外部负载均衡器分配 VIP |
| Headless | 无 ClusterIP | DNS 直接指向 Pod IP |
2.2 Traefik 的 LoadBalancer Service
1 | apiVersion: v1 |
关键注解 network.mycloud.io/lbprovider: clb-ipvs:告诉云平台的 LB Controller 使用 CLB(Cloud Load Balancer) 来分配一个 VIP。
CLB 工作原理
1 | CLB Controller |
执行后,Service 状态变为:
1 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) |
10.49.32.12 就是 CLB 分配的对外 VIP。
三、DNS 链:域名如何解析到 VIP
3.1 kube2dns:集群内自动 DNS 注册
kube2dns 是云平台的一个控制器,运行在 kube-system 中,专门监听 type=LoadBalancer 的 Service,一旦 VIP 分配完成,就自动在 CorpDNS(企业内部 DNS 系统)中注册 A 记录:
1 | # Service 上的注解(kube2dns 写入) |
命名规则是固定的:<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 | dig prow-prod.mycloud.io |
谁负责什么:
| 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 | apiVersion: networking.k8s.io/v1 |
4.2 Traefik 配置
Traefik 通过 ConfigMap 配置,指定使用 kubernetesIngress Provider:
1 | [providers.kubernetesIngress] |
Traefik 持续 watch ci-system 命名空间的 Ingress 对象,动态更新路由表。当请求路径为 /manual-trigger 时,路由到 manual-trigger Service 的 80 端口。
五、ClusterIP Service:集群内访问的核心
manual-trigger Service 是一个典型的 ClusterIP 类型:
1 | apiVersion: v1 |
5.1 ClusterIP 的实现原理
ClusterIP 192.168.134.249 是一个虚拟 IP,不存在于任何网卡上。它的实现依赖 kube-proxy:
1 | kube-proxy(每个节点运行) |
集群内任意 Pod 访问 192.168.134.249:80,内核 netfilter 拦截并 DNAT 到真实 Pod IP。
5.2 集群内 DNS 解析
集群内的 Pod 通过 CoreDNS 解析 Service 名:
1 | # 同命名空间内: |
CoreDNS 的 A 记录指向的是 ClusterIP(虚拟 IP),而非 Pod IP。
1 | 集群内 Pod |
六、Headless Service:直达 Pod IP
6.1 什么是 Headless Service
将 clusterIP: None 设置时,Service 变为 Headless,不再分配虚拟 IP:
1 | apiVersion: v1 |
6.2 Headless Service DNS 解析行为
CoreDNS 对 Headless Service 的处理完全不同:
1 | DNS 查询:manual-trigger-headless.ci-system.svc.cluster.local |
同时,CoreDNS 还会为每个 Pod 生成独立的 DNS 记录(StatefulSet 场景尤为重要):
1 | # Pod 级别的 DNS(格式:pod-ip.namespace.pod.cluster.local) |
6.3 Headless Service 的典型场景
| 场景 | 原因 |
|---|---|
| StatefulSet(如 etcd、Kafka) | 需要稳定的 Pod 身份标识(pod-0, pod-1…),客户端直连特定 Pod |
| 自定义客户端负载均衡 | gRPC 等长连接协议需要客户端感知所有 Pod IP |
| 服务发现 | 应用自己实现故障切换,不依赖 kube-proxy |
6.4 无头服务 vs 普通服务对比
1 | 普通 ClusterIP 访问流程: |
kube-proxy 完全不参与 Headless Service 的流量转发,减少了一次内核 NAT 开销。
七、完整对比:三种 Service 的 DNS 与流量链路
1 | 外部访问 (LoadBalancer) |
八、关键组件职责总结
| 组件 | 职责 |
|---|---|
| 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?
整条链路是完全正常的:
- DNS 解析 ✓(CNAME → A 记录 → 10.49.32.12)
- CLB 四层转发 ✓(到 Traefik)
- Traefik 七层路由 ✓(
/manual-trigger规则存在) - Service 和 Pod 健康 ✓(Pod 日志显示正常心跳)
问题在于:/manual-trigger 是一个 POST-only 的 API 端点,浏览器发 GET 请求,服务器返回 405 Method Not Allowed。这是应用层行为,与 K8s 网络无关。
正确的调用方式:
1 | curl -k -X POST https://prow-prod.mycloud.io/manual-trigger \ |
十、总结
| 访问场景 | 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 无缝衔接。