在 Kubernetes 里,Pod 是可以随时被替换的:扩缩容会变、滚动更新会变、节点故障迁移也会变。也正因为如此,Pod IP 不应该被当成稳定访问入口。Service 的出现,就是为了给这批不断变化的 Pod 提供一个稳定的网络抽象层。::cite[455]
你可以把它理解成:Pod 是后端实例集合,Service 是对外暴露的“稳定门牌号”。客户端不需要知道后端 Pod 有几个、IP 是什么,只要访问 Service 即可。与此同时,Kubernetes 会通过 EndpointSlice 和集群 DNS 持续维护“Service 名称 → 当前可用后端”的映射关系。::cite[455]::cite[454]::cite[283]
2.3.1 为什么 Pod IP 不适合直接访问
Pod 天生是 临时性资源。在 Deployment 控制下,某个时刻运行的 Pod 与下一刻运行的 Pod,名称、IP、所在节点都可能不同。即使业务逻辑没变,后端实例集合也会变化。::cite[455]
这会带来三个直接问题:
- 客户端无法记住一个长期稳定的 Pod IP。
- 滚动更新期间,旧 Pod 下线、新 Pod 上线,访问目标不断变化。
- 扩缩容后,后端数量变化,客户端自己维护地址列表很麻烦。::cite[455]
所以,Kubernetes 的标准做法不是“让客户端直接追 Pod”,而是:
- 用 标签选择器 选出一组 Pod。
- 用 Service 给这组 Pod 提供稳定的入口。
- 用 EndpointSlice 记录当前可用后端。
- 用 DNS 把 Service 名称解析到可访问地址。::cite[455]::cite[454]::cite[283]
2.3.2 Service 是什么
Service 是 Kubernetes API 中的一个对象,用来暴露一组网络应用后端。一个 Service 定义了两件事:
- 哪些 Pod 是它的后端(通常通过 selector)
- 这些后端应该如何被访问::cite[455]
最常见的 Service 写法如下:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
这段 YAML 的意思是:
- 把带有
app.kubernetes.io/name=MyApp标签的 Pod 作为后端。 - 对外暴露 80 端口。
- 实际流量转发到 Pod 的 9376 端口。::cite[455]
最小可运行示例:Deployment + ClusterIP Service
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
labels:
app: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
selector:
app: web
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
实操步骤
- 创建 Deployment 与 Service。
kubectl apply -f web-service-demo.yaml
- 查看 Service。
kubectl get svc web-service
- 查看后端 EndpointSlice。
kubectl get endpointslice -l kubernetes.io/service-name=web-service
- 在集群内测试访问。
kubectl run curl-test --image=busybox:1.36 -it --rm -- sh
wget -qO- http://web-service
2.3.3 Service 的类型:ClusterIP、NodePort、LoadBalancer、ExternalName
Kubernetes 提供了几种常见 Service 类型,它们不是互相平级替代,而是逐层扩展访问能力。::cite[455]
| 类型 | 访问范围 | 典型用途 |
|---|---|---|
ClusterIP |
仅集群内部 | 微服务间调用、默认选择 |
NodePort |
通过任意节点 IP + 端口访问 | 测试环境、无云 LB 场景 |
LoadBalancer |
通过云厂商或外部负载均衡访问 | 对外提供正式服务 |
ExternalName |
映射到外部 DNS 名称 | 把集群内访问统一到外部服务域名 |
1)ClusterIP:默认类型
如果不显式指定 type,Service 默认就是 ClusterIP。它会分配一个集群内部虚拟 IP,只能在集群内部访问。::cite[455]
apiVersion: v1
kind: Service
metadata:
name: api-clusterip
spec:
type: ClusterIP
selector:
app: web
ports:
- port: 80
targetPort: 80
这类 Service 最适合:
- 前后端服务在同一集群内通信
- 只希望被 Ingress / Gateway 间接暴露
- 不直接暴露给外部用户
2)NodePort:节点端口暴露
如果设置 type: NodePort,Kubernetes 会在每个节点上开放同一个端口,把请求转发到 Service。默认范围是 30000-32767。::cite[455]
apiVersion: v1
kind: Service
metadata:
name: api-nodeport
spec:
type: NodePort
selector:
app: web
ports:
- port: 80
targetPort: 80
nodePort: 30080
访问方式类似:
http://<NodeIP>:30080
NodePort 常用于:
- 本地实验
- 裸机环境
- 临时对外调试
但它一般不是生产环境最优入口,因为:
- 需要暴露节点 IP
- 端口范围固定且不够友好
- 通常还需要自己再配一层负载均衡
3)LoadBalancer:对外暴露的标准选择
如果集群所在环境支持外部负载均衡器,type: LoadBalancer 会自动为 Service 准备一个外部入口。::cite[455]
apiVersion: v1
kind: Service
metadata:
name: api-loadbalancer
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 80
targetPort: 80
查看外部地址:
kubectl get svc api-loadbalancer
典型特点:
- 对用户最友好
- 常用于公网或内网统一入口
- 实际创建 LB 的能力取决于云厂商或接入组件::cite[455]
4)ExternalName:映射外部域名
ExternalName 很特别,它不代理流量,也不选 Pod,而是直接在集群 DNS 里返回一个 CNAME,把 Service 名称映射到外部 DNS 名称。::cite[455]
apiVersion: v1
kind: Service
metadata:
name: external-db
namespace: default
spec:
type: ExternalName
externalName: my.database.example.com
适合场景:
- 把外部数据库、外部 API 统一封装成集群内 Service 名称
- 迁移期把“集群外服务”和“集群内服务”的调用方式统一
但要注意:它是 DNS 级别映射,不是代理,因此某些依赖 Host 头或 TLS 域名校验的协议,使用时要特别小心。::cite[455]
2.3.4 Service 与 EndpointSlice 的关系
很多人只会看 kubectl get svc,但真正决定 Service 后端是谁的,是 EndpointSlice。官方把 EndpointSlice 作为 Service 后端网络端点的分片对象,它是现在推荐使用的后端信息来源。::cite[454]::cite[455]
关系可以这样理解
- Service:声明“我要暴露谁、怎么暴露”。
- EndpointSlice:记录“现在真正可用的后端地址有哪些”。
当 Service 使用 selector 时,控制平面会自动扫描匹配的 Pod,并维护对应的 EndpointSlice。只要 Pod 变化,EndpointSlice 也会更新。::cite[455]::cite[454]
查看某个 Service 的 EndpointSlice
kubectl get endpointslice -l kubernetes.io/service-name=web-service
手动维护后端:无 selector 的 Service
Service 不一定非得选 Pod。没有 selector 时,Kubernetes 不会自动创建 EndpointSlice,你需要自己补一个。::cite[455]
下面是一个“手动指定后端地址”的完整示例:
apiVersion: v1
kind: Service
metadata:
name: external-backend-service
spec:
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
---
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: external-backend-service-1
labels:
kubernetes.io/service-name: external-backend-service
endpointslice.kubernetes.io/managed-by: staff
addressType: IPv4
ports:
- name: http
protocol: TCP
port: 9376
endpoints:
- addresses:
- 10.4.5.6
- addresses:
- 10.1.2.3
这类写法常见于:
- 集群外数据库或中间件接入
- 跨集群服务代理
- 迁移阶段的兼容接入::cite[455]
2.3.5 集群内 DNS 与服务发现机制
Kubernetes 支持两种主要的服务发现方式:
- 环境变量
- DNS
但在现代集群里,DNS 几乎总是首选。::cite[455]::cite[283]
1)环境变量发现
当 Pod 被创建时,kubelet 会把当前活跃 Service 的地址信息注入成环境变量,例如:
REDIS_PRIMARY_SERVICE_HOSTREDIS_PRIMARY_SERVICE_PORT
这种方式的限制是:Service 必须先创建,后启动客户端 Pod,否则环境变量不会自动补上。::cite[455]
2)DNS 发现:最常用方式
集群 DNS 会为 Service 自动创建 DNS 记录。官方文档说明,Pod 可以通过名字而不是 IP 去访问 Service。默认情况下,Pod 的 DNS 搜索路径会包含当前命名空间和集群默认域。::cite[283]
一个 Service 的完整域名一般长这样:
<service-name>.<namespace>.svc.cluster.local
例如:
web-service.default.svc.cluster.local
同命名空间下的简写
如果客户端 Pod 和目标 Service 在同一个命名空间里,通常直接写服务名就可以:
http://web-service
这是因为 Pod 的 DNS 搜索列表会优先补当前命名空间。::cite[283]
跨命名空间访问
如果要跨命名空间调用,建议至少写成:
http://web-service.backend
或者写完整 FQDN:
http://web-service.backend.svc.cluster.local
头服务(Headless Service)与 DNS
如果你把 .spec.clusterIP 设为 None,就创建了 Headless Service。此时 Kubernetes 不再给它分配虚拟 IP,而是直接通过 DNS 返回后端 Pod 的地址集合。::cite[455]::cite[283]
这类 Service 常用于:
- StatefulSet
- 需要直接感知每个后端实例的场景
- 自己做客户端负载均衡的组件
示例:
apiVersion: v1
kind: Service
metadata:
name: web-headless
spec:
clusterIP: None
selector:
app: web
ports:
- port: 80
targetPort: 80
2.3.6 实操:验证 DNS 服务发现
启动测试容器
kubectl run dns-test --image=busybox:1.36 -it --rm -- sh
在容器中执行 DNS 查询
nslookup web-service
nslookup web-service.default.svc.cluster.local
访问 Service
wget -qO- http://web-service
验证 Headless Service
nslookup web-headless
观察重点:普通 Service 通常返回一个稳定入口;Headless Service 更倾向于暴露后端 Pod 地址集合。::cite[455]::cite[283]
2.3.7 常见注意事项
1)Service 选不中 Pod,通常是标签问题
最常见故障不是网络,而是 selector 和 Pod labels 对不上。先看:
kubectl get pods --show-labels
kubectl describe svc web-service
2)Readiness Probe 会影响 Service 后端列表
如果 Pod 的 readinessProbe 失败,Kubernetes 会把该 Pod 从 Service 对应的后端地址中移除。也就是说,“Pod 还活着”并不等于“Pod 还能接流量”。::cite[373]::cite[455]
3)无 selector 的 Service 不会自动生成 EndpointSlice
这是非常容易忽略的点。没写 selector,就需要你自己创建 EndpointSlice。::cite[455]
4)ExternalName 不等于反向代理
它只是返回 CNAME,不会做转发,也不会帮你处理应用层协议兼容问题。::cite[455]
5)尽量优先使用 DNS,而不是环境变量
环境变量方式对创建顺序敏感;DNS 更灵活,也更符合云原生服务发现模式。::cite[455]::cite[283]
2.3.8 本章小结
这一章最重要的理解有五点:
- Pod IP 是易变的,不能作为稳定访问入口。
- Service 提供稳定入口,EndpointSlice 维护实时后端。
- ClusterIP 解决集群内访问,NodePort / LoadBalancer 解决对外访问,ExternalName 解决外部域名映射。
- 集群 DNS 是 Kubernetes 服务发现的主流方式。
- 标签选择器、Readiness Probe、EndpointSlice 三者共同决定“流量到底会打到谁”。
掌握 Service 后,你会发现 Kubernetes 网络模型一下子清晰很多:
- Pod 负责跑实例
- Deployment 负责管副本和发布
- Service 负责稳定入口
- DNS 负责名字解析
这几层拼起来,才是微服务在 Kubernetes 中可持续运转的基础。
📝 版权声明:本文为原创技术博客,转载请注明出处。
如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!