摘要:Pod 的 IP 是短暂的,随 Pod 重建、扩缩容而变化。Service 提供稳定的虚拟 IP 和 DNS 名称,实现服务发现与负载均衡,是 Kubernetes 网络模型的核心组件。
一、为什么需要 Service?
Pod IP 会随 Pod 重建、扩缩容而变化,客户端若直接使用 Pod IP 将面临连接失效。Service 提供稳定的虚拟 IP(ClusterIP)和 DNS 名称,将流量负载均衡到后端 Pod,实现服务发现与流量转发。
Service 方案
Service ClusterIP: 10.96.0.100
Pod 1
Pod 2
Pod 3
问题:Pod IP 不固定
IP 变化
T1: Pod A 10.244.1.5
T2: Pod B 重建后 10.244.3.7
Service 的 ClusterIP 在生命周期内保持不变,DNS 名称稳定。后端 Pod 变更时由 Endpoints Controller 与 kube-proxy 自动更新转发规则。
二、Service 的工作原理
2.1 Label Selector(标签选择器)
Service 通过标签选择器(Label Selector)匹配后端 Pod,只有 metadata.labels 满足 spec.selector 的 Pod 才会被加入 Endpoints。
不匹配
Service selector: app=nginx
Pod nginx-1 app=nginx
Pod nginx-2 app=nginx
Pod redis-1 app=redis
selector 与 Pod 的 metadata.labels 匹配时,该 Pod 被纳入 Service 的后端;不匹配的 Pod 不参与负载均衡。
2.2 Endpoints 与 EndpointSlice
Service 会对应一个 Endpoints 对象,记录所有匹配 Pod 的 IP 和端口,由 Endpoints Controller 自动维护。Kubernetes 1.17+ 引入 EndpointSlice,将 Endpoints 拆分为多个 Slice,每个 Slice 最多 100 个端点,便于大规模集群扩展。
Service nginx-svc
Endpoints
10.244.1.5:80
10.244.2.3:80
10.244.3.7:80
EndpointSlice 由 kube-controller-manager 的 EndpointSlice Controller 创建,当 Endpoints 超过 100 个端点时自动拆分。kube-proxy 监听 Endpoints/EndpointSlice 变化并更新转发规则。
| 对象 | 说明 |
|---|---|
| Endpoints | 传统对象,存储所有匹配 Pod 的 IP:Port 列表 |
| EndpointSlice | 新对象,将端点分片存储,单 Slice 最多 100 个端点,支持大规模集群 |
2.3 kube-proxy 工作流程
kube-proxy 运行在每个节点上,监听 Service 和 Endpoints 变化,在本地配置流量转发规则。支持三种模式:userspace(已废弃)、iptables、IPVS。
Watch Service/Endpoints
更新规则
请求 ClusterIP:port
DNAT 转发
API Server
kube-proxy
iptables / IPVS
Client Pod
Pod 1
Pod 2
Pod 3
kube-proxy 通过 Watch API 监听 Service 和 Endpoints 对象,当变更发生时在节点上更新 iptables 或 IPVS 规则,将发往 ClusterIP 的流量 DNAT 到后端 Pod。
2.3.1 iptables 模式
kube-proxy 在 iptables 的 NAT 表中创建链:KUBE-SERVICES(入口)、KUBE-SVC-xxx(按 Service 分)、KUBE-SEP-xxx(按端点分)。流量命中 ClusterIP 后,经 KUBE-SVC-xxx 随机选择一条 KUBE-SEP-xxx 规则完成 DNAT。规则数量与 Service 数、端点数成正比,超大规模集群下规则膨胀可能影响性能。
2.3.2 IPVS 模式
IPVS 基于内核 Netfilter,支持多种负载均衡算法(rr、lc、dh、sh、sed、nq)。kube-proxy 创建虚拟 Service(ClusterIP)并绑定后端 Real Server(Pod IP),转发在内核完成,性能优于 iptables。支持 sessionAffinity: ClientIP 实现会话保持。启用方式:kube-proxy --proxy-mode=ipvs。
| 模式 | 实现方式 | 负载均衡算法 | 会话保持 | 适用场景 |
|---|---|---|---|---|
| iptables | NAT 表规则 | 随机 | 需 sessionAffinity | 中小规模 |
| IPVS | 内核 IPVS 模块 | rr/lc/dh/sh 等 | 原生支持 | 大规模、高并发 |
三、Service 的四种类型
不同类型的 Service 提供不同的访问范围和实现方式。
Service 类型
ClusterIP 集群内部
NodePort 节点端口
LoadBalancer 云负载均衡
ExternalName CNAME
| 类型 | 访问范围 | 说明 |
|---|---|---|
| ClusterIP | 集群内部 | 默认类型,分配虚拟 IP,最常用 |
| NodePort | 集群外可通过节点 IP:端口 | 在每个节点开放 30000--32767 端口 |
| LoadBalancer | 外部 | 依赖云厂商,创建云 LB,包含 NodePort 与 ClusterIP |
| ExternalName | 集群内部 | 不创建代理,通过 CNAME 指向外部域名 |
3.1 ClusterIP
ClusterIP 是默认类型,分配一个集群内可路由的虚拟 IP,仅集群内 Pod 可访问。
yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: default
spec:
type: ClusterIP
selector:
app: nginx
ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
ClusterIP 字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| spec.type | string | ClusterIP(默认,可省略) |
| spec.selector | map | 与 Pod 的 labels 匹配,为空则需手动创建 Endpoints |
| spec.ports[].port | int32 | Service 暴露的端口 |
| spec.ports[].targetPort | int/string | Pod 容器端口,可为数字或 named port |
| spec.ports[].protocol | string | TCP(默认)或 UDP |
| spec.clusterIP | string | 可指定固定 IP,省略则自动分配 |
bash
# 创建 Deployment 与 Service
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
EOF
# 验证
kubectl get svc nginx-svc
kubectl get endpoints nginx-svc
kubectl run test --rm -it --image=busybox -- wget -qO- http://nginx-svc
3.2 NodePort
NodePort 在每个节点上开放 30000--32767 范围内的端口,外部可通过任意节点 IP:NodePort 访问。
yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-nodeport
spec:
type: NodePort
selector:
app: nginx
ports:
- port: 80
targetPort: 80
nodePort: 30080
NodePort 字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| spec.type | string | NodePort |
| spec.ports[].nodePort | int32 | 30000--32767,省略则自动分配 |
| spec.externalTrafficPolicy | string | Cluster(默认)或 Local,Local 仅转发到本节点 Pod |
bash
kubectl apply -f service-nodeport.yaml
kubectl get svc nginx-nodeport
# 外部访问:curl http://<任意节点IP>:30080
3.3 LoadBalancer
在云环境中,type 为 LoadBalancer 时,云控制器会创建负载均衡器并分配外部 IP。
yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-lb
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- port: 80
targetPort: 80
LoadBalancer 字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| spec.type | string | LoadBalancer |
| spec.loadBalancerIP | string | 部分云厂商支持指定 IP |
| spec.loadBalancerSourceRanges | []string | 限制 LB 访问 IP 段 |
| status.loadBalancer.ingress | array | 云厂商分配的 External IP |
外部用户
云 LB
Node 1:30080
Node 2:30080
Node 3:30080
Service:80
Pod 1/2/3
流量路径:云 LB → NodePort → Service → Pod。裸金属环境需配合 MetalLB 等实现 LoadBalancer。
3.4 ExternalName
ExternalName 不创建 ClusterIP,仅创建一条 CNAME DNS 记录,指向集群外域名。适用于将集群内服务名映射到外部服务。
yaml
apiVersion: v1
kind: Service
metadata:
name: external-db
spec:
type: ExternalName
externalName: mysql.example.com
ExternalName 字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| spec.type | string | ExternalName |
| spec.externalName | string | 外部域名,DNS 解析为 CNAME |
| spec.selector | - | 不可用,无 Endpoints |
| spec.ports | - | 不可用,无端口映射 |
使用场景 :迁移阶段应用配置使用 mysql-svc,将 ExternalName 指向 mysql.legacy.example.com;多集群场景下用 Service 名统一访问外部服务。
bash
# 创建后,集群内解析 external-db 得到 mysql.example.com
kubectl run test --rm -it --image=busybox -- nslookup external-db
四、DNS 服务发现
Kubernetes 内置 CoreDNS,为每个 Service 自动创建 DNS 记录。
DNS 查询
CoreDNS
..svc.cluster.local
ClusterIP
DNS 解析规则
| 格式 | 示例 | 说明 |
|---|---|---|
<service> |
nginx-svc | 同命名空间可简写 |
<service>.<namespace> |
nginx-svc.production | 跨命名空间 |
<service>.<namespace>.svc |
nginx-svc.production.svc | 含 svc 子域 |
| FQDN | nginx-svc.production.svc.cluster.local | 完整域名 |
CoreDNS 的 Kubernetes 插件监听 Service 和 Pod 资源,按上述规则生成 DNS 记录。Pod 的 dnsPolicy: ClusterFirst 时,集群 DNS 为 kube-dns Service 的 ClusterIP(通常 10.96.0.10)。
bash
# 同命名空间
curl http://nginx-svc:80
# 跨命名空间
curl http://nginx-svc.production:80
# 完整域名
curl http://nginx-svc.default.svc.cluster.local
五、Headless Service
当不需要负载均衡、需直接访问每个 Pod 时,使用 Headless Service(clusterIP: None)。
Headless Service
DNS 查询
Pod IP 列表
普通 Service
DNS 查询
ClusterIP
普通 Service 的 DNS 解析为单个 ClusterIP;Headless Service 返回所有匹配 Pod 的 IP 列表(A 记录),由客户端选择目标。StatefulSet 常配合 Headless Service 使用,Pod 具有稳定的 DNS 名 pod-name.service-name.namespace.svc.cluster.local。
yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-headless
spec:
clusterIP: None
selector:
app: nginx
ports:
- port: 80
bash
# 解析 Headless Service 会返回多个 A 记录
kubectl run test --rm -it --image=busybox -- nslookup nginx-headless
六、多端口 Service
Pod 暴露多个端口时,Service 可配置多个端口映射,每个端口需指定 name。
yaml
apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
selector:
app: my-app
ports:
- name: http
port: 80
targetPort: 8080
- name: https
port: 443
targetPort: 8443
- name: metrics
port: 9090
targetPort: 9090
| 字段 | 说明 |
|---|---|
| ports[].name | 必填,多端口时用于区分 |
| ports[].port | Service 暴露端口 |
| ports[].targetPort | Pod 端口,可引用容器 named port |
七、流量转发流程
在 iptables 模式下,客户端访问 Service 的完整路径如下:
请求 nginx-svc:80
ClusterIP
随机选择
Client Pod
DNS 解析
iptables DNAT
Pod 1
Pod 2
Pod 3
客户端通过 DNS 解析得到 ClusterIP,请求到达节点后由 kube-proxy 配置的 iptables 规则进行 DNAT,将流量转发到其中一个后端 Pod。
八、生产环境建议
| 建议 | 说明 |
|---|---|
| 集群内访问优先 ClusterIP | 避免通过 NodePort 在集群内访问,减少一跳 |
| 生产外部访问用 LoadBalancer 或 Ingress | NodePort 适合开发测试,生产建议云 LB 或 Ingress |
| 设置 sessionAffinity 时考虑 IPVS | iptables 模式对 sessionAffinity 支持有限,IPVS 更佳 |
| 监控 Endpoints 与 Service 健康 | Ready 的 Pod 才加入 Endpoints,关注 readiness 探针 |
| 大规模集群启用 EndpointSlice | 降低 Endpoints 对象体积,提升 Watch 效率 |
九、常用命令
bash
# 创建 Service
kubectl apply -f service.yaml
# 查看 Service
kubectl get svc
kubectl get services -o wide
# 查看详情
kubectl describe svc nginx-svc
# 查看 Endpoints
kubectl get endpoints nginx-svc
# 查看 EndpointSlice(若启用)
kubectl get endpointslices
# 为 Deployment 快速创建 Service
kubectl expose deployment nginx --port=80 --target-port=8080
# 测试连通性(集群内 Pod)
kubectl run test --rm -it --image=busybox -- wget -qO- http://nginx-svc
十、常见问题(FAQ)
Q1:Service 无法访问的可能原因?
检查顺序:1)selector 是否与 Pod 标签匹配;2)Pod 是否 Ready(readiness 探针);3)Endpoints 是否有条目(kubectl get endpoints);4)NetworkPolicy 是否拦截;5)同一命名空间内优先使用 Service 名称访问;6)targetPort 是否与容器端口一致。
Q2:ClusterIP 和 NodePort 如何选择?
仅集群内访问用 ClusterIP;需要从集群外访问时,开发测试可用 NodePort,生产环境在云上优先使用 LoadBalancer 或 Ingress。
Q3:如何实现会话保持?
配置 sessionAffinity: ClientIP,并设置 sessionAffinityConfig.clientIP.timeoutSeconds(默认 10800)。iptables 模式支持有限,建议改用 kube-proxy 的 IPVS 模式;或使用支持会话保持的 Ingress Controller。
yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-session
spec:
type: ClusterIP
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 3600
selector:
app: nginx
ports:
- port: 80
targetPort: 80
Q4:没有 selector 的 Service 如何使用?
创建不指定 selector 的 Service,手动创建同名 Endpoints,将外部服务 IP 加入。适用于将集群内服务名映射到集群外 IP。
yaml
# Service 无 selector
apiVersion: v1
kind: Service
metadata:
name: external-mysql
spec:
ports:
- port: 3306
---
# 手动创建 Endpoints 指向外部 IP
apiVersion: v1
kind: Endpoints
metadata:
name: external-mysql
subsets:
- addresses:
- ip: 192.168.1.100
ports:
- port: 3306
集群内访问 external-mysql:3306 即转发到 192.168.1.100:3306。
十一、总结
| 要点 | 说明 |
|---|---|
| 核心作用 | 解决 Pod IP 不稳定、多副本负载均衡和服务发现问题 |
| 类型选择 | ClusterIP(集群内)、NodePort(简单外部访问)、LoadBalancer(生产云环境)、ExternalName(访问集群外服务) |
| 转发机制 | Label Selector → Endpoints/EndpointSlice → kube-proxy(iptables/IPVS) |
| DNS | CoreDNS 提供 <svc>.<ns>.svc.cluster.local 解析 |
| 特殊类型 | Headless Service(直接解析 Pod IP)、ExternalName(CNAME 到外部) |