K8s 系列 | 第 13 天:Headless Service 与服务发现机制深度解析


tags: [Kubernetes, K8s系列, DevOps, Service, 服务发现, DNS, 网络, Headless Service]

K8s 系列 | 第 13 天:Headless Service 与服务发现机制深度解析

第 13/30 天

引言

在前面的文章中,我们学习了 Service(第 5 天)和 Ingress(第 11 天),了解了如何通过 ClusterIP 暴露 Pod 以及如何通过 Ingress 实现外部流量接入。但 Kubernetes 中还有一种特殊的 Service 类型——Headless Service(无头服务),它不为 Pod 提供统一的虚拟 IP 和负载均衡,而是直接将 DNS 解析指向后端 Pod 的真实 IP 地址。

为什么需要这种”无头”模式?当你的应用是有状态集群(如数据库、消息队列、ETCD、ZooKeeper),每个 Pod 都需要以独立身份进行通信,而不是被负载均衡隐藏在一个 VIP 后面时,Headless Service 就派上了大用场。本文将深入解析 Headless Service 的工作原理、服务发现机制、实战配置以及与普通 Service 的核心差异。


一、核心概念

1.1 什么是 Headless Service?

Headless Service 就是在 Service 定义中将 clusterIP 字段设置为 None 的一种特殊 Service。它不分配 ClusterIP,kube-proxy 不会为它创建 iptables/IPVS 规则,因此不会进行流量转发和负载均衡。取而代之的是,DNS 查询会直接返回所有后端 Pod 的 IP 地址列表。

Headless Service 的核心特性:

特性 普通 Service (ClusterIP) Headless Service
ClusterIP 自动分配虚拟 IP clusterIP: None
负载均衡 kube-proxy 自动负载均衡 无负载均衡
DNS 解析 返回 Service 的 VIP(单个 A 记录) 返回所有 Endpoint 的 IP 列表(多个 A 记录)
Pod 身份 无状态,被 VIP 隐藏 每个 Pod 有独立 DNS 名称
使用场景 无状态应用 有状态应用、服务发现

1.2 服务发现机制的两种模式

Headless Service 支持两种 DNS 解析模式:

  1. 普通解析(不带 service-name): 查询 svc-cluster.local 返回所有 Pod IP 的 A/AAAA 记录列表
  2. 带 Pod 序号的解析(StatefulSet 专用): 查询 pod-0.svc.cluster.local 返回指定 Pod 的 IP——这对 StatefulSet 至关重要

1.3 与 StatefulSet 的天作之合

StatefulSet 创建的 Pod 具有稳定的网络标识——每个 Pod 的名称固定(如 my-sts-0my-sts-1)。当 StatefulSet 关联一个 Headless Service 时,每个 Pod 都能通过以下 DNS 名称被唯一访问:

<pod-name>.<service-name>.<namespace>.svc.cluster.local

这对于数据库集群(如 MySQL Group Replication、MongoDB Replica Set、Cassandra 等)来说是刚需——每个节点需要知道其他所有节点的真实地址以进行数据同步和选举。


二、实战步骤

2.1 创建一个 Headless Service

下面是一个最基本的 Headless Service YAML 定义:

apiVersion: v1
kind: Service
metadata:
  name: my-headless-svc
  namespace: default
spec:
  clusterIP: None          # 关键:设置为 None 即为 Headless
  selector:
    app: my-app
  ports:
    - name: http
      port: 80
      targetPort: 8080

与普通 Service 的唯一区别就是 clusterIP: None

2.2 StatefulSet + Headless Service 完整示例

下面是一个完整的 Nginx StatefulSet + Headless Service 示例:

# headless-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-hl
  labels:
    app: nginx
spec:
  clusterIP: None
  selector:
    app: nginx
  ports:
    - port: 80
      name: web
---
# statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: nginx-hl      # 关联 Headless Service
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        ports:
        - containerPort: 80
          name: web

部署这个 StatefulSet 后,三个 Pod 的名称将是 web-0web-1web-2,并且可以通过以下 DNS 名称解析:

web-0.nginx-hl.default.svc.cluster.local  → 10.244.1.10
web-1.nginx-hl.default.svc.cluster.local  → 10.244.1.11
web-2.nginx-hl.default.svc.cluster.local  → 10.244.1.12

2.3 部署与验证

第一步:创建资源

# 创建 Headless Service 和 StatefulSet
kubectl apply -f headless-service.yaml
kubectl apply -f statefulset.yaml

# 查看 Pod 状态
kubectl get pods -l app=nginx
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          2m
web-1   1/1     Running   0          1m
web-2   1/1     Running   0          30s

第二步:验证 DNS 解析

启动一个调试 Pod 来进行 DNS 查询:

# 启动调试 Pod
kubectl run dns-test --image=busybox:1.28 -- rm -rf /etc/resolv.conf 2>/dev/null; kubectl exec -it dns-test -- sh

# 在调试 Pod 中查询 Headless Service 的 DNS
/ # nslookup nginx-hl
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      nginx-hl
Address 1: 10.244.1.12 web-2.nginx-hl.default.svc.cluster.local
Address 2: 10.244.1.11 web-1.nginx-hl.default.svc.cluster.local
Address 3: 10.244.1.10 web-0.nginx-hl.default.svc.cluster.local

# 查询单个 Pod 的 DNS
/ # nslookup web-1.nginx-hl
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.nginx-hl
Address 1: 10.244.1.11 web-1.nginx-hl.default.svc.cluster.local

对比普通 Service 的 DNS 解析,普通 Service 只返回一个 ClusterIP,而不是 Pod IP 列表:

/ # nslookup kubernetes
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes
Address 1: 10.96.0.1

2.4 在应用中使用 Headless Service 进行服务发现

以 Cassandra 集群为例,每个 Cassandra 节点需要发现集群中的其他节点。使用 Headless Service + StatefulSet,应用可以通过以下方式发现所有节点:

# cassandra-headless.yaml
apiVersion: v1
kind: Service
metadata:
  name: cassandra
  labels:
    app: cassandra
spec:
  clusterIP: None
  selector:
    app: cassandra
  ports:
    - port: 9042
      name: cql

在 Cassandra 的配置中,通过 DNS 查询 cassandra 即可获取所有集群节点的 IP,而种子节点(seed provider)也能通过 cassandra-0.cassandra.default.svc.cluster.local 这样的稳定 DNS 名称来配置。


三、常见问题

3.1 Headless Service 与 ClusterIP Service 可以共存吗?

可以。 许多生产环境会同时定义一个 Headless Service 用于服务发现,以及一个普通 ClusterIP Service 用于客户端流量负载均衡。例如:

# 用于内部节点通信 - Headless
apiVersion: v1
kind: Service
metadata:
  name: my-app-headless
spec:
  clusterIP: None
  selector:
    app: my-app
  ports:
    - port: 8080

---
# 用于客户端流量 - 带负载均衡
apiVersion: v1
kind: Service
metadata:
  name: my-app
spec:
  clusterIP: 10.96.100.100
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080

3.2 Headless Service 不支持端口映射吗?

准确地说,Headless Service 支持端口定义,但 DNS 查询只返回 Pod IP,不返回端口信息。客户端需要自行知道端口。可以有多端口定义,但应用必须使用正确的端口号连接对应 Pod。

3.3 如果 Selector 为空会发生什么?

如果 Headless Service 没有设置 Selector(或者 Selector 不匹配任何 Pod),DNS 会优先查找同名的 Endpoints 或 EndpointSlice 对象。如果你手动创建了 Endpoints,DNS 解析将返回你指定的地址,而不是任何 Pod 的 IP。

这对于将外部服务引入集群内部的场景非常有用:

apiVersion: v1
kind: Service
metadata:
  name: external-db
spec:
  clusterIP: None
  # 没有 selector

---
apiVersion: v1
kind: Endpoints
metadata:
  name: external-db
subsets:
  - addresses:
      - ip: 192.168.1.100
    ports:
      - port: 3306

3.4 所有 Pod 都 Ready 时 DNS 才生效?

是的,Headless Service 的 DNS 解析只包含 Ready 状态的 Pod(即 readinessProbe 通过)。如果一个 Pod 没有通过就绪检查,它的 IP 将不会出现在 DNS 响应中。这使得应用可以通过 DNS 感知后端 Pod 的健康状态——这就是就绪感知服务发现


四、生产最佳实践

4.1 选择合适的服务发现模式

应用类型 推荐方式 原因
无状态 Web 应用 普通 ClusterIP Service 需要负载均衡,无需关心后端 Pod 身份
MySQL 主从 / PostgreSQL 流复制 Headless Service + StatefulSet 主节点身份稳定,从节点通过 DNS 发现主节点
ETCD / ZooKeeper 集群 Headless Service + StatefulSet 每个节点有固定标识,集群成员互相发现
Kafka / Pulsar Headless Service + StatefulSet Broker ID 对应 Pod 序号,客户端直连每个 broker
监控 / 日志采集(Prometheus) Headless Service 需要直接抓取每个 Pod 的 metrics,不经过负载均衡

4.2 调试 DNS 解析的实用命令

# 安装调试工具
kubectl run debug --image=nicolaka/netshoot:latest -- sleep 3600

# 进入调试容器
kubectl exec -it debug -- bash

# 查询 SRV 记录(获取端口信息)
dig SRV nginx-hl.default.svc.cluster.local

# 查询所有 A 记录
dig +short nginx-hl.default.svc.cluster.local

# 反向 DNS 查询
dig -x 10.244.1.10

4.3 性能与限制

  • Headless Service 的 DNS 解析结果受 CoreDNS 缓存策略影响(默认 30s TTL),Pod 启停后 DNS 记录不会立即更新
  • 对于大规模集群(数千 Pod),DNS 响应包可能很大,影响解析性能
  • 建议在应用中实现重试逻辑和主动缓存失效机制
  • 使用 kubectl get endpointslices 可以查看底层 EndpointSlice 的实时状态

总结

Headless Service 是 Kubernetes 中一个小而美的关键组件。它虽然名字叫”无头”,实际上提供了比普通 Service 更直接、更透明的 Pod 访问方式。通过 clusterIP: None 这一简单的声明,我们获得了:

  • 透明的 Pod 身份暴露——每个 Pod 都有独立的 DNS 名称
  • 原生的服务发现机制——基于 DNS 的 SRV/A 记录查询
  • 与 StatefulSet 的完美集成——有状态应用的标准部署模式
  • 就绪感知——只有健康的 Pod 才会出现在 DNS 结果中

理解 Headless Service 是理解 Kubernetes 有状态工作负载的敲门砖。无论是部署数据库集群、消息队列,还是构建自定义服务发现体系,Headless Service 都是不可或缺的基础能力。


📖 系列目录

下期预告: 第 14 天我们将深入探讨 CSI 存储插件与生产存储选型指南,了解如何通过容器存储接口(CSI)对接各类生产级存储后端,包括 Ceph、Longhorn、NFS 等主流方案。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容