Kubernetes Service 完整学习笔记

一、前置背景:为什么需要Service?

遗留问题

上一章学习的Deployment/RS等控制器可以实现Pod的多副本管理,但存在致命缺陷:

  • Pod是临时资源:重建后IP必然变化,无法通过固定IP访问

  • 若用Nginx反向代理Pod,需手动维护upstream中的Pod IP:

    1. Pod死亡后,Nginx 7层探测会停止转发到该Pod,但新Pod的IP不会自动加入upstream

    2. 需手动/脚本修改Nginx配置、重载才能恢复,运维成本极高

Service的解决方案

Service通过标签选择器匹配Pod + 自动维护后端端点 + 固定访问入口,实现前后端解耦:

  1. 匹配同时满足「标签匹配」+「Pod就绪(Ready,通过就绪探针)」的Pod

  2. Pod扩缩容、重建、就绪状态变化时,Service自动更新后端Pod列表

  3. 上层应用只需访问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模式

  1. 修改kube-proxy的ConfigMap(注意在kube-system命名空间):

    kubectl edit configmap kube-proxy -n kube-system

    找到mode字段,修改为ipvs(默认空值为iptables)

    mode: "ipvs"

  2. 删除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

  3. 验证:节点执行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 组件协同流程

  1. 用户通过kubectl提交Service配置到APIServer

  2. APIServer将配置持久化到etcd

  3. 每个节点的kube-proxy监听APIServer的Service变化

  4. kube-proxy将Service规则同步到本节点的iptables/ipvs

  5. 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状态,缺一不可:

  1. 初始创建Pod时,就绪探针检测/index1.html不存在,Pod状态为Ready: 0/1

  2. 此时查看Service的ipvs规则,无后端Pod:

    复制代码
    ipvsadm -Ln | grep 10.3.124.142  # 仅显示集群IP,无Real Server
  3. 进入Pod创建index1.htmldate > /usr/local/nginx/html/index1.html

  4. 就绪探针通过后,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   # 节点暴露的端口,不指定会自动分配
核心特性&实操案例
  1. 完全兼容ClusterIP的所有能力:集群内可以通过ClusterIP、域名访问

  2. 每个节点的所有可用网卡 都会绑定NodePort的ipvs规则,因此任意节点的IP:NodePort都可以访问

    复制代码
    # 所有节点都可查到30010端口的规则
    ipvsadm -Ln | grep 30010
    # 浏览器访问任意节点IP:30010均可正常响应
  3. externalTrafficPolicy(外部流量策略,重点区分)

    取值 说明 优缺点 适用场景
    Cluster(默认) 外部流量可以被转发到任意节点的Pod,会做SNAT,丢失客户端源IP 可用性高,即使访问的节点没有Pod副本也能转发 通用场景
    Local 外部流量只能转发到当前节点的Pod,没有本地Pod则丢包,保留客户端源IP 性能好,保留源IP;但节点没有Pod时访问失败 配合DaemonSet使用,每个节点都有Pod副本的场景

⚠️ 易混点区分(教材特别强调):

策略 控制范围 作用
internalTrafficPolicy 集群内访问ClusterIP 控制节点/ Pod是否能通过ClusterIP访问Service
externalTrafficPolicy 集群外访问NodePort 控制外部流量是否保留源IP、是否跨节点转发
  1. 生产建议:NodePort本身无高可用能力,生产环境需在集群外部额外部署负载均衡器(Nginx、F5、云LB),将流量转发到所有节点的NodePort,避免单节点故障。

3.4 LoadBalancer(仅云厂商可用)


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时:

  1. Service控制器自动监听符合标签的Pod

  2. 自动创建同名的EndpointSlice,包含所有Ready状态的Pod的IP、端口

  3. kube-proxy将EndpointSlice中的端点同步到本节点的ipvs规则

  4. Pod就绪状态变化、IP变化、扩缩容时,EndpointSlice自动更新,无需人工干预

实操验证
  1. 创建带selector的Service后,自动生成同名EndpointSlice:

    复制代码
    kubectl get endpointslice
    # 输出可见myapp-clusterip对应的EndpointSlice,包含就绪Pod的IP
  2. Pod就绪状态变化时,EndpointSlice自动更新:当一个Pod创建index1.html就绪后,EndpointSlice自动加入该Pod的IP;Pod未就绪时,自动移除。

4.2 手动关联(不带Label Selector的Service,特殊场景)

当Service没有配置spec.selector时(如访问集群外服务、非Pod端点):

  1. Service不会自动创建EndpointSlice

  2. 需用户手动创建EndpointSlice,通过标签kubernetes.io/service-name关联到对应的Service

  3. 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>

七、本章重点总结

  1. Service的核心是解耦Pod的动态变化和上层访问,避免Pod重建导致的访问失效

  2. 集群内访问优先用ClusterIP,开启internalTrafficPolicy: Local提升性能

  3. 对外暴露优先用NodePort+外部LB,避免直接用NodePort暴露公网;云环境可使用LoadBalancer

  4. 访问集群外服务优先用ExternalName,减少配置耦合

  5. EndpointSlice是Service的底层数据载体,理解它才能真正搞懂Service的工作原理

  6. 所有Service的流量转发能力最终依赖kube-proxy落地到节点的iptables/ipvs规则

相关推荐
xieliyu.1 小时前
UPD协议结构以及开发中注意事项
java·开发语言·笔记·java-ee
卡布叻_星星1 小时前
后端架构笔记之Maven多模块与微服务
笔记·架构
何事误红尘12 小时前
全志A40i学习笔记():开发资料、启用串口
学习
for_ever_love__12 小时前
python基础语法学习: 异常的传递性
开发语言·python·学习
流烟默13 小时前
Kubernetes 调度器完全指南:从原理到生产实战
云原生·容器·kubernetes
PC2005-cloud13 小时前
DeepSeek Harness 创建 skill 指南:结构、安装、使用
笔记·学习
YM52e13 小时前
分页查询的基石:ArkTS 为鸿蒙商品列表设计 LIMIT/OFFSET 的表
android·学习·华为·harmonyos
math_hongfan13 小时前
主从嵌套层次分明:ArkUI 订单卡片内嵌明细的鸿蒙界面
学习·华为·harmonyos
库玛西15 小时前
现代 C++ 智能指针全景指南:从 RAII 思想到工业级实践
c语言·开发语言·c++·笔记·面试