一、前置背景:为什么需要Service?
遗留问题
上一章学习的Deployment/RS等控制器可以实现Pod的多副本管理,但存在致命缺陷:
-
Pod是临时资源:重建后IP必然变化,无法通过固定IP访问
-
若用Nginx反向代理Pod,需手动维护
upstream中的Pod IP:-
Pod死亡后,Nginx 7层探测会停止转发到该Pod,但新Pod的IP不会自动加入
upstream -
需手动/脚本修改Nginx配置、重载才能恢复,运维成本极高
-
Service的解决方案
Service通过标签选择器匹配Pod + 自动维护后端端点 + 固定访问入口,实现前后端解耦:
-
匹配同时满足「标签匹配」+「Pod就绪(Ready,通过就绪探针)」的Pod
-
Pod扩缩容、重建、就绪状态变化时,Service自动更新后端Pod列表
-
上层应用只需访问Service的固定VIP/域名,完全感知不到Pod的变化
二、核心组件:kube-proxy的代理模式迭代
每个Node运行kube-proxy进程,负责将Service的负载均衡规则落地到节点,共三代迭代模式:
| K8s版本 | 模式 | 工作原理 | 优缺点 | 教材实操验证 |
|---|---|---|---|---|
| v1.0 | userspace | 1. kube-proxy监听APIServer的Service变化 2. 修改节点iptables规则,将流量转发到kube-proxy进程 3. kube-proxy代理请求到后端Pod | ❌ 性能极差:kube-proxy参与流量转发,请求量大时成为瓶颈 | - |
| v1.1+(默认) | iptables | 1. kube-proxy监听APIServer的Service变化 2. 仅将负载均衡规则写入节点iptables,不参与流量转发 3. 流量直接由iptables转发到后端Pod | ✅ 性能好:无流量代理,规则同步开销低 ❌ 规则规模超1万条时性能下降,仅支持随机、轮询等简单算法 | 新建Service后执行ipvsadm -Ln无规则,说明当前是iptables模式 |
| v1.8+ | ipvs | 1. kube-proxy监听APIServer的Service变化 2. 将负载均衡规则写入内核级LVS(ipvs) 3. 流量由ipvs转发到后端Pod | ✅ 性能最优:专业四层负载均衡,支持rr/wrr/lc/wlc等10+算法,规则规模大时性能稳定 ❌ 依赖内核ipvs模块,部分环境默认未开启 | 切换后ipvsadm -Ln可见Service对应的负载均衡规则 |
【⚠️疑难点】为什么K8s的ipvs默认用NAT模式而非DR/TUN?
ipvs有三种工作模式,K8s选择NAT的核心原因:
| 模式 | 优点 | 缺点 | K8s适配性 |
|---|---|---|---|
| NAT | 支持Service端口和Pod端口不一致,兼容性强 | 入站/出站流量都经过ipvs调度 | ✅ 每个节点的ipvs规则仅给本节点的Pod使用,单节点流量压力小,兼容性优先级高于性能 |
| DR | 回程流量不经过ipvs,性能更高 | 需修改ARP响应规则,且Service端口必须和Pod端口一致 | ❌ 灵活性差,不符合K8s端口映射的需求 |
| TUN | 支持跨公网组建集群 | 需对报文二次封装,性能低 | ❌ K8s集群内通信无需跨公网, overhead过高 |
【实操】切换kube-proxy为ipvs模式
-
修改kube-proxy的ConfigMap(注意在
kube-system命名空间):kubectl edit configmap kube-proxy -n kube-system
找到mode字段,修改为ipvs(默认空值为iptables)
mode: "ipvs"
-
删除kube-system命名空间下所有kube-proxy Pod,触发重建加载新配置:
通过标签筛选kube-proxy Pod
kubectl get pod -n kube-system -l k8s-app=kube-proxy
删除所有kube-proxy Pod,DS控制器会自动重建
kubectl delete pod -n kube-system -l k8s-app=kube-proxy
-
验证:节点执行
ipvsadm -Ln,能看到Service对应的ipvs规则即成功。
📌 踩坑提示:kube-proxy的ConfigMap修改后不会自动生效,必须删除Pod重建才能加载新配置;若节点未加载ipvs内核模块,会导致kube-proxy启动失败。
kubectl edit的校验机制
kubectl edit修改资源时,K8s会校验配置合法性:
-
若修改的值超出字段定义范围(如滚动更新最大副本数设为非法值),保存时会自动弹回,不允许保存错误配置
-
错误修改会缓存到
/tmp/kubectl-edit-xxxx.yaml,方便排查问题
三、Service工作模式(4种类型)
3.1 组件协同流程
-
用户通过kubectl提交Service配置到APIServer
-
APIServer将配置持久化到etcd
-
每个节点的kube-proxy监听APIServer的Service变化
-
kube-proxy将Service规则同步到本节点的iptables/ipvs
-
CoreDNS自动为Service生成DNS记录,供集群内Pod解析
3.2 ClusterIP(默认类型,仅集群内访问)
模式结构
-
分配仅集群内部可访问的虚拟IP(ClusterIP)
-
后端匹配符合条件的Pod,实现集群内负载均衡
-
底层默认使用ipvs的NAT模式,支持Service端口和Pod端口不一致
完整资源清单
# Deployment配置(带就绪探针,Service依赖该探针)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-clusterip-deploy
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: myapp
release: stabel
svc: clusterip
template:
metadata:
labels:
app: myapp
release: stabel
env: test
svc: clusterip
spec:
containers:
- name: myapp-container
image: hb.reg.com/library/myapp:1.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
readinessProbe: # 就绪探针:检测/index1.html是否存在
httpGet:
path: /index1.html
port: 80
initialDelaySeconds: 5
periodSeconds: 5
---
# ClusterIP Service配置
apiVersion: v1
kind: Service
metadata:
name: myapp-clusterip
namespace: default
spec:
type: ClusterIP
selector: # 标签选择器,和Pod标签做子集匹配
app: myapp
release: stabel
svc: clusterip
ports:
- name: http
port: 80 # Service暴露的集群内访问端口
targetPort: 80 # 后端Pod的容器端口
核心特性&教材实操案例
(1)就绪探针强依赖
Service匹配Pod必须同时满足两个条件:标签匹配 + Pod处于Ready状态,缺一不可:
-
初始创建Pod时,就绪探针检测
/index1.html不存在,Pod状态为Ready: 0/1 -
此时查看Service的ipvs规则,无后端Pod:
ipvsadm -Ln | grep 10.3.124.142 # 仅显示集群IP,无Real Server -
进入Pod创建
index1.html:date > /usr/local/nginx/html/index1.html -
就绪探针通过后,Pod变为
Ready: 1/1,Service自动将Pod加入后端,ipvs规则出现对应Real Server,此时访问Service正常。
(2)内置DNS解析
Service创建后自动生成集群内唯一的DNS记录,格式为:
<service-name>.<namespace>.svc.cluster.local
-
集群内Pod默认DNS指向CoreDNS的VIP(默认
10.0.0.10),可直接解析 -
验证方法:
# 用dig命令验证解析 dig A myapp-clusterip.default.svc.cluster.local @10.0.0.10 # 在Pod内访问域名 kubectl exec -it pod-demo -- wget http://myapp-clusterip.default.svc.cluster.local/hostname.html
(3)internalTrafficPolicy(集群内流量策略,K8s 1.26稳定)
控制集群内流量如何访问ClusterIP,教材明确两种取值的差异:
| 取值 | 说明 | 适用场景 | 教材实操结果 |
|---|---|---|---|
| Cluster(默认) | 集群内任意节点、任意Pod都可以通过ClusterIP/域名访问Service | 通用场景 | 节点上执行curl ClusterIP、Pod内访问域名均正常 |
| Local | 仅集群内的Pod可以通过域名访问,节点上无法通过ClusterIP访问,且无本地Pod时丢包 | 仅集群内访问,减少跨节点转发开销,提升性能 | 节点执行curl ClusterIP提示Connection refused,Pod内访问域名正常 |
📌 官方建议:若Service仅需要被集群内Pod访问,强烈建议设置为
Local,降低性能开销。
(4)会话保持(IPVS持久化连接)
默认Service是轮询负载,若需将同一客户端的请求固定到同一个Pod,可开启会话保持:
spec:
sessionAffinity: ClientIP # 默认是None,关闭会话保持
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800 # 超时时间,默认10800秒(3小时),范围1-86400秒
-
原理:基于客户端IP绑定,超时时间内同一IP的请求转发到同一Pod
-
验证:开启后
ipvsadm -Ln可见规则后增加persistent 10800标识 -
踩坑:会话保持仅在ipvs模式下生效,iptables模式下不生效
3.3 NodePort(对外暴露服务)
客户端
| http://192.168.24.10:30540
v
[NodePort 30540] <-- 每台节点都开同一个端口
|
v
[kube-proxy] <-- iptables/IPVS 规则匹配
| DNAT: NodeIP:30540 -> ClusterIP:80
v
[Service ClusterIP:80] <-- 根据 selector 选 Pod
| round-robin 负载均衡
v
[Pod 1 / Pod 2 / Pod 3] <-- nginx 处理请求

模式结构
-
在ClusterIP的基础上,在每个节点的物理网卡上绑定一个静态端口(NodePort)
-
外部可以通过
NodeIP:NodePort访问集群内的Service -
默认端口范围:
30000-32767,可通过kube-apiserver的--service-node-port-range参数修改(如设置为12000-22000)
完整资源清单
# Deployment配置
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-nodeport-deploy
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: myapp
release: stabel
svc: nodeport
template:
metadata:
labels:
app: myapp
release: stabel
env: test
svc: nodeport
spec:
containers:
- name: myapp-container
image: hb.reg.com/library/myapp:1.0
ports:
- name: http
containerPort: 80
---
# NodePort Service配置
apiVersion: v1
kind: Service
metadata:
name: myapp-nodeport
namespace: default
spec:
type: NodePort
selector:
app: myapp
release: stabel
svc: nodeport
ports:
- name: http
port: 80 # 集群内访问的Service端口
targetPort: 80 # Pod端口
nodePort: 30010 # 节点暴露的端口,不指定会自动分配
核心特性&实操案例
-
完全兼容ClusterIP的所有能力:集群内可以通过ClusterIP、域名访问
-
每个节点的所有可用网卡 都会绑定NodePort的ipvs规则,因此任意节点的IP:NodePort都可以访问:
# 所有节点都可查到30010端口的规则 ipvsadm -Ln | grep 30010 # 浏览器访问任意节点IP:30010均可正常响应 -
externalTrafficPolicy(外部流量策略,重点区分)
取值 说明 优缺点 适用场景 Cluster(默认) 外部流量可以被转发到任意节点的Pod,会做SNAT,丢失客户端源IP 可用性高,即使访问的节点没有Pod副本也能转发 通用场景 Local 外部流量只能转发到当前节点的Pod,没有本地Pod则丢包,保留客户端源IP 性能好,保留源IP;但节点没有Pod时访问失败 配合DaemonSet使用,每个节点都有Pod副本的场景
⚠️ 易混点区分(教材特别强调):
策略 控制范围 作用 internalTrafficPolicy集群内访问ClusterIP 控制节点/ Pod是否能通过ClusterIP访问Service externalTrafficPolicy集群外访问NodePort 控制外部流量是否保留源IP、是否跨节点转发
- 生产建议:NodePort本身无高可用能力,生产环境需在集群外部额外部署负载均衡器(Nginx、F5、云LB),将流量转发到所有节点的NodePort,避免单节点故障。
3.4 LoadBalancer(仅云厂商可用)
-
仅在云厂商环境可用,依托云供应商的负载均衡能力,自动创建外部LB,将流量转发到NodePort
-
私有云环境一般不用,用「NodePort + 自建LB」替代
-
阿里云资源清单示例:
apiVersion: v1
kind: Service
metadata:
name: my-nginx-svc
namespace: default
annotations:
service.beta.kubernetes.io/alibaba-cloud-loadbalancer-id: ${YOUR_LB_ID}
service.beta.kubernetes.io/alicloud-loadbalancer-force-override-listeners: 'true'
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- port: 80
targetPort: 80
3.5 ExternalName(集群外服务映射)
模式结构
-
特殊的Service类型,不涉及kube-proxy、ipvs,仅通过CoreDNS实现CNAME域名别名
-
用于将集群外的服务映射到集群内,让Pod可以通过固定的Service名称访问外部服务
-
不需要Label Selector、不需要端口映射(也可自定义端口)
完整资源清单&案例
案例1:映射外部百度域名
apiVersion: v1
kind: Service
metadata:
name: my-service-1
namespace: default
spec:
type: ExternalName
externalName: www.baidu.com
-
验证:Pod内执行
ping my-service-1.default.svc.cluster.local,解析结果为百度的IP -
限制:仅集群内Pod可访问,外部客户端不会使用集群的CoreDNS,因此无法通过该Service访问外部服务
案例2:映射外部MySQL数据库(生产常用)
apiVersion: v1
kind: Service
metadata:
name: mysql-service
namespace: default
spec:
type: ExternalName
externalName: mysql.prod.company.com # 外部MySQL域名
ports:
- name: mysql
port: 3306
targetPort: 3306
-
应用配置时,只需填写
jdbc:mysql://mysql-service:3306/dbname,无需关心外部MySQL的真实地址 -
外部MySQL地址变化时,仅需修改Service的
externalName字段,所有Pod无需修改配置,实现解耦
【⚠️疑难点】ExternalName和其他类型的本质区别
| 类型 | 实现方式 | 能力 |
|---|---|---|
| ClusterIP/NodePort/LoadBalancer | kube-proxy + ipvs | 四层负载均衡,感知后端健康状态 |
| ExternalName | CoreDNS CNAME | 仅域名别名,无负载均衡能力,不感知后端健康状态 |
四、EndpointSlice(Service的底层实现)
Service的后端Pod列表由EndpointSlice维护,替代了旧版的Endpoints资源,支持更大规模(单Slice支持1000个端点,单Service支持多个Slice)的端点管理。
4.1 自动关联(带Label Selector的Service,默认场景)
当Service配置了spec.selector时:
-
Service控制器自动监听符合标签的Pod
-
自动创建同名的EndpointSlice,包含所有
Ready状态的Pod的IP、端口 -
kube-proxy将EndpointSlice中的端点同步到本节点的ipvs规则
-
Pod就绪状态变化、IP变化、扩缩容时,EndpointSlice自动更新,无需人工干预
实操验证
-
创建带selector的Service后,自动生成同名EndpointSlice:
kubectl get endpointslice # 输出可见myapp-clusterip对应的EndpointSlice,包含就绪Pod的IP -
Pod就绪状态变化时,EndpointSlice自动更新:当一个Pod创建
index1.html就绪后,EndpointSlice自动加入该Pod的IP;Pod未就绪时,自动移除。
4.2 手动关联(不带Label Selector的Service,特殊场景)
当Service没有配置spec.selector时(如访问集群外服务、非Pod端点):
-
Service不会自动创建EndpointSlice
-
需用户手动创建EndpointSlice,通过标签
kubernetes.io/service-name关联到对应的Service -
kube-proxy将手动创建的EndpointSlice同步到ipvs规则
手动创建EndpointSlice完整示例
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: my-service-noselector-1
labels:
# 必须关联Service的名称,否则Service无法识别该Slice
kubernetes.io/service-name: my-service-noselector
addressType: IPv4
ports:
- name: http
protocol: TCP
port: 80
endpoints:
- addresses:
- "192.168.10.12" # 后端服务IP(可以是集群外IP)
- addresses:
- "192.168.10.13"
【⚠️教材严格限制】手动EndpointSlice的IP规范
端点IP不能是以下类型,否则kube-proxy不支持:
-
本地回环地址(127.0.0.0/8、::1/128)
-
链路本地地址(169.254.0.0/16、fe80::/64)
-
其他K8s Service的ClusterIP
五、常见问题
| 问题 | 排查思路 | 依据 |
|---|---|---|
| Pod Running但Service访问不通 | 检查Pod是否Ready(就绪探针是否通过),只有Ready的Pod才会被加入EndpointSlice |
就绪探针未通过时,Service无后端端点 |
| Service会话保持不生效 | 确认sessionAffinity设置为ClientIP,且集群使用ipvs模式 |
iptables模式不支持会话保持 |
| NodePort外部访问不通 | 1. 检查externalTrafficPolicy是否为Cluster 2. 检查节点防火墙是否放行NodePort端口 3. 检查Pod是否Ready |
Local模式下无本地Pod会丢包;防火墙拦截端口 |
| ExternalName解析失败 | 1. 确认CoreDNS正常运行 2. 检查Pod的/etc/resolv.conf是否指向集群CoreDNS IP(默认10.0.0.10) |
外部客户端不使用集群CoreDNS,无法解析 |
| 修改kube-proxy后不生效 | 修改ConfigMap后必须删除kube-proxy Pod重建才能加载新配置 | kube-proxy配置热加载机制 |
| 修改Service后不生效 | 确认修改的是正确的Namespace;Service修改后EndpointSlice自动更新,最多延迟10秒 | Service控制器同步机制 |
| Local模式ClusterIP节点访问不通 | internalTrafficPolicy: Local时,仅Pod内可通过域名访问,节点上无法通过ClusterIP访问 |
官方流量策略定义 |
六、实操命令速查
| 操作 | 命令 |
|---|---|
| 创建ClusterIP Service | kubectl create svc clusterip myapp --tcp=80:80 |
| 查看所有Service | kubectl get svc -A |
| 查看Service详情 | kubectl describe svc <svc-name> |
| 查看EndpointSlice | kubectl get endpointslice -A |
| 查看ipvs规则 | ipvsadm -Ln |
| 修改Service配置 | kubectl edit svc <svc-name> |
| 查看Service的yaml定义 | kubectl get svc <svc-name> -o yaml |
| 验证Service DNS解析 | kubectl exec -it <pod-name> -- nslookup <svc-name>.<ns>.svc.cluster.local |
| 修改kube-proxy为ipvs模式 | kubectl edit configmap kube-proxy -n kube-system,修改mode为ipvs后删除kube-proxy Pod |
| 查看kube-proxy日志 | kubectl logs -n kube-system <kube-proxy-pod-name> |
七、本章重点总结
-
Service的核心是解耦Pod的动态变化和上层访问,避免Pod重建导致的访问失效
-
集群内访问优先用ClusterIP,开启
internalTrafficPolicy: Local提升性能 -
对外暴露优先用NodePort+外部LB,避免直接用NodePort暴露公网;云环境可使用LoadBalancer
-
访问集群外服务优先用ExternalName,减少配置耦合
-
EndpointSlice是Service的底层数据载体,理解它才能真正搞懂Service的工作原理
-
所有Service的流量转发能力最终依赖kube-proxy落地到节点的iptables/ipvs规则