Headless 与 ClusterIP 是什么
两者都是 Service,差别在有没有虚拟 IP。ClusterIP 会分配一个仅集群内可达的虚拟地址,kube-proxy 把访问这个地址的流量转发到后端 Pod;Headless 把 clusterIP 显式写成 None,不分配虚拟地址,集群 DNS 直接返回后端 Pod 的地址列表,由客户端挑一个连。选择哪一种,本质上是在选「谁来负载均衡」。
基本信息
| 关键字段 | spec.clusterIP: None 表示 Headless |
|---|---|
| DNS 返回 | Headless 返回 Pod 地址列表,ClusterIP 返回一个虚拟地址 |
| 转发主体 | Headless 由客户端直连,ClusterIP 由 kube-proxy 转发 |
| 后端来源 | 两者都由选择器匹配 Pod,或由手工 EndpointSlice 提供 |
| 常见搭配 | StatefulSet 的 serviceName 必须指向 Headless Service |
行为对比
| 维度 | ClusterIP | Headless |
|---|---|---|
| DNS 返回 | 一个虚拟 IP | 就绪 Pod 的地址列表 |
| 流量路径 | kube-proxy 规则做四层转发 | 客户端直连某个 Pod |
| 负载均衡 | 由 kube-proxy 承担 | 由客户端库或应用自行实现 |
| 端口与协议 | 可映射任意端口,支持 TCP/UDP | 端口由 Pod 实际监听决定,配合 SRV 记录做发现 |
| 典型场景 | 无状态服务的统一入口 | 有状态集群互发现、自定义均衡、直连每个实例 |
创建与验证
创建与验证
apiVersion: v1
kind: Service
metadata:
name: db-headless
namespace: prod
spec:
clusterIP: None # 关键:不分配虚拟 IP
selector:
app: db
ports:
- name: mysql
port: 3306
targetPort: 3306对比解析结果
对比解析结果
kubectl get svc db-headless -o jsonpath='{.spec.clusterIP}' # 输出 None
kubectl get endpointslices -n prod -l kubernetes.io/service-name=db-headless # 后端地址来源
kubectl run dns-test --rm -it --restart=Never --image=busybox:1.36 -- \
nslookup db-headless.prod.svc.cluster.local # 返回多个 Pod 地址
kubectl run dns-srv --rm -it --restart=Never --image=busybox:1.36 -- \
nslookup -type=SRV _mysql._tcp.db-headless.prod.svc.cluster.local # 带端口的服务发现典型使用场景
- 有状态集群互发现:数据库、消息队列的节点需要互相拿到对方地址
- 稳定网络标识:StatefulSet 配合 Headless 得到 实例名.服务名 这样的固定名字
- 客户端自定义均衡:应用自己维护连接池并挑选实例,不希望中间再转一层
- 直连排查:需要确认流量真正落到哪个 Pod,绕开虚拟地址更容易定位
- 无选择器直连外部地址:用手工 EndpointSlice 把集群外的地址直接暴露成服务名
Headless 的 DNS 默认只返回就绪 Pod:滚动重启期间列表会动态变化,未就绪的实例不在应答里。需要把未就绪地址也发布出去时,设置 publishNotReadyAddresses: true,但要清楚这样客户端可能连到还没准备好的实例。
ClusterIP 模式下业务拿到的是一个虚拟地址,看不到真实后端;排查时先用 kubectl get endpointslices 确认后端列表,再决定是查 kube-proxy 规则还是查 Pod 本身。