Kubernetes服务发现机制(二):Service、Endpoint、EndpointSlice

云原生学习路线导航页(持续更新中)

我们在前面学习了负载均衡相关的基础知识,那么负载均衡在Kubernetes中究竟是如何实现的呢?本节将进行详细介绍Service、Endpoint、EndpointSlice,负载均衡的实际执行者。

1.Service 是什么

  • Service 定义了一个 逻辑上的服务端点一组访问它的策略
  • Service 为运行在集群内的一组功能相同的 Pod (通常由 Label Selector 选择 ) 提供一个稳定的虚拟 IP (VIP) 和 DNS 名称
  • 客户端只需要连接到这个稳定的 VIP 或 DNS 名称,Service 就会自动将请求负载均衡到后端的健康 Pod 上,无需关心后端 Pod 的具体 IP 和数量变化。

2.为什么需要 Service?

为什么需要Service,可以看Service的引入之前有哪些痛点

2.1.Service 的引入之前有哪些问题要解决?

  • Pod 动态性: Pod 会被调度、重建、扩缩容,它们的 IP 地址会随之改变,每个pod都没有稳定的唯一标识
  • Pod 多副本负载均衡: 一个应用通常由多个 Pod 副本组成,需要一种机制将流量均衡地分发到这些健康的 Pod 上。
  • 集群内外部的服务发现: 集群内部或外部如何稳定地找到一个应用(一组 Pod)的访问入口?

2.2.引入 Service 后,如何解决了这些问题

  1. Service 具有稳定的网络标识

    • Cluster IP: Service 创建时会被分配一个仅在集群内部可路由的虚拟 IP 地址(VIP)。这个 VIP 在 Service 的生命周期内是稳定不变的(除非手动删除重建Service)。
    • DNS 名称: Kubernetes 内置的 DNS 服务(如 CoreDNS)会自动为 Service 创建 DNS 记录。
      • 格式:<service-name>.<namespace-name>.svc.cluster.local
      • 例如:my-service.default.svc.cluster.local
      • 同一 Namespace 内的 Pod 可以直接通过 <service-name> 访问服务。
      • 不同 Namespace 的 Pod 需要使用 <service-name>.<namespace-name> 访问。
  2. Service 提供负载均衡能力

    • Service 核心功能之一就是将发送到其 VIP 或 NodePort 的流量自动、均匀地分发到其后端匹配的所有健康 Pod 上。
    • 默认负载均衡算法是轮询
    • 通过 spec.sessionAffinity 字段可以配置会话亲和性(例如 ClientIP),使来自同一客户端的请求尽可能转发到同一个后端 Pod(但这只是尽力而为,不保证严格会话)。
  3. Service 提供服务发现能力

    • 环境变量: Kubelet 会在每个 Pod 启动时,将当前 Namespace 内所有有效的 Service 信息(如 {SVCNAME}_SERVICE_HOST, {SVCNAME}_SERVICE_PORT)注入到 Pod 的环境变量中。这是最早期的方式,但依赖于启动顺序(Service 必须先于 Pod 创建)。
    • DNS: 推荐方式。集群 DNS 服务提供 Service 的 DNS 解析。Pod 只需知道目标 Service 的名称(或 FQDN),通过 DNS 查询即可获得其 Cluster IP 或 Endpoint 信息。这种方式解耦强,不依赖启动顺序。

2.3.Service、Endpoint 和 Pod 的关系

2.3.1.Service

  • 重点关注字段
    • spec.selector:根据label选择pod服务列表
    • spec.ports:定义pod服务的端口协议等信息
      • spec.ports.port:服务端口,即service的虚拟端口
      • spec.ports.targetPort:容器端口,即service选择的pod提供服务的真实容器端口

2.3.2.Endpoint

2.3.3.Service、Endpoint、Pod 关系

  • Service (服务)

    • 定义了一个逻辑抽象层,代表一组提供相同功能的 Pod。
    • 拥有一个稳定的虚拟 IP 地址 (ClusterIP) 和 DNS 名称
    • 定义了访问策略(如端口、协议)。
  • Endpoint (端点)

    • 是 Service 的具体后端实现
    • 由一个或多个 EndpointSlice 对象(现代实现)组成。
    • 动态跟踪 并存储着 Service 所匹配的所有 Pod 的 IP:Port 列表
  • 核心关系:

    • Service 是访问入口,Endpoint 是实际目的地。
    • Service 负责流量分发,Endpoint 提供分发目标列表。
    • Endpoint 自动维护 Service 后端 Pod 的实时网络地址。
  • 简单来说: 当流量访问 Service vip:vport 时,流量会根据规则(如负载均衡)被路由到 Endpoint 列表中的一个具体 Pod。

3.Service通过selector选择pod

3.1.Service 的 selector 不为空

  • service定义了selector,Endpoint Controller会为该Service自动创建Endpoint
  • 会把selector选中的pod全部加入Endpoint的subsets中,控制pod可以接入负载均衡

3.2.Service 的 selector 为空

4.Service 完整API 结构

kuberenetes 1.22版本,Service.spec/status 下完整结构如下

4.1.Service.spec 结构

4.1.1.Service.spec 字段整理

  • 重点关注:selector、type、ports、publishNotReadyAddresses
字段名称 类型 默认值 描述 适用 Service 类型 重要注意事项
allocateLoadBalancerNodePorts boolean true 是否自动为 LoadBalancer 类型分配 NodePort。若设为 false,需确保云提供商 LB 不依赖 NodePort LoadBalancer Beta 特性,需启用 ServiceLBNodePortControl;显式指定 NodePort 时此字段无效。
topologyKeys \[\]string 指定服务的 拓扑感知流量路由,就是优先将流量路由到符合拓扑配置的后端实例上,如果没有符合条件的实例,则拒绝本次请求 所有类型 1.通配符 "*":表示"任意拓扑"。2.兼容性:不能与 externalTrafficPolicy: Local 同时使用。3.有效拓扑键:仅限内置标签(最多16个)
clusterIP string 自动分配 服务的虚拟 IP(VIP)。设为 "None" 时创建 Headless Service(无 VIP)。 ClusterIP, NodePort, LoadBalancer 修改需同时更改 typeExternalName 类型必须为空。
clusterIPs \[\]string 自动分配 服务的 VIP 列表(支持双栈)。需与 ipFamilies 顺序一致。 ClusterIP, NodePort, LoadBalancer 需启用 IPv6DualStack 特性;clusterIPs[0] 必须等于 clusterIP
externalIPs \[\]string - 节点将接受的额外 IP 列表(非 Kubernetes 管理)。用户需确保流量可达。 所有类型 常用于集成外部负载均衡器。
externalName string - 外部服务的别名(如 DNS CNAME 记录)。不代理流量。 ExternalName 必须是合法的 DNS 名称;其他类型不可用。
externalTrafficPolicy string Cluster 外部流量路由策略: - Local:保留客户端 IP,仅路由到节点本地端点 - Cluster:均衡到所有端点(隐藏客户端 IP) NodePort, LoadBalancer Local 模式可能导致流量不均;需配合 healthCheckNodePort 使用。
healthCheckNodePort integer 自动分配 健康检查端口(当 type=LoadBalancerexternalTrafficPolicy=Local 时生效)。外部系统通过此端口探测节点是否就绪。 LoadBalancer 显式指定时需确保端口可用;类型变更时自动清除。
internalTrafficPolicy string Cluster 集群内部流量路由策略: - Local:仅路由到节点本地端点 - Cluster:路由到所有端点 ClusterIP, NodePort, LoadBalancer Local 模式下无本地端点时丢弃流量。
ipFamilies \[\]string 自动分配 IP 协议栈列表(IPv4/IPv6)。需与 clusterIPs 匹配。 ClusterIP, NodePort, LoadBalancer 需启用 IPv6DualStack;主 IP 协议栈不可修改。
ipFamilyPolicy string 单栈 双栈策略: - SingleStack:单栈 - PreferDualStack:优先双栈 - RequireDualStack:必须双栈 ClusterIP, NodePort, LoadBalancer 修改将影响 clusterIPsipFamilies
loadBalancerClass string - 负载均衡器实现类标识符(如 internal-vip)。 LoadBalancer 一旦设置不可修改;云提供商默认 LB 会忽略此字段。
loadBalancerIP string - 指定 LoadBalancer 的 IP 地址。 LoadBalancer 依赖云提供商支持;不支持时忽略。
loadBalancerSourceRanges \[\]string - 限制访问 LB 的客户端 IP CIDR 范围。 LoadBalancer 依赖云提供商支持;不支持时忽略。
ports \[\]Object - 服务暴露的端口列表,子字段包括: - name:端口名称 - protocol:协议(TCP/UDP) - port:服务端口 - targetPort:容器端口 - nodePort:节点端口(NodePort/LoadBalancer 类型) 所有类型(除 ExternalName) targetPort 可引用 Pod 端口名称。
publishNotReadyAddresses boolean false 是否发布未就绪的端点(如 StatefulSet 的 Headless Service 需设为 true 支持 DNS 发现)。 所有类型 影响 Endpoints/EndpointSlice 生成;未就绪端点会被标记为 "ready"。
selector mapstringstring - Pod 标签选择器,用于动态管理端点。 ClusterIP, NodePort, LoadBalancer 为空时需手动管理端点;ExternalName 类型忽略此字段。
sessionAffinity string None 会话亲和性: - None:无亲和性 - ClientIP:基于客户端 IP ClusterIP, NodePort, LoadBalancer 非严格会话保持;NAT 环境下可能失效。
sessionAffinityConfig Object - 会话亲和性配置(如 ClientIP 的超时时间 timeoutSeconds)。 ClusterIP, NodePort, LoadBalancer 仅当 sessionAffinity=ClientIP 时生效。
type string ClusterIP 服务暴露方式: - ClusterIP:集群内 VIP - NodePort:节点端口 - LoadBalancer:云提供商 LB - ExternalName:外部别名 - HeadlessclusterIP=None 所有类型 修改 type 会重置部分字段(如 externalNameClusterIP 需重新指定 clusterIP)。

4.1.2.关键说明

  1. 双栈支持

    • 需启用 IPv6DualStack 特性门控。
    • ipFamiliesclusterIPs 必须一一对应,且受 ipFamilyPolicy 控制。
  2. Headless Service

    • 设置 clusterIP: None,无 VIP,DNS 返回所有 Pod IP。
    • 适用于 StatefulSet 或自定义服务发现场景。
  3. 流量策略

    • externalTrafficPolicy=Local + healthCheckNodePort 确保云 LB 仅转发到健康节点。
    • internalTrafficPolicy=Local 优化集群内部流量(减少跨节点跳转)。
  4. 端口配置

    yaml 复制代码
    ports:
    - name: http
      protocol: TCP
      port: 80      # 服务端口
      targetPort: 8080  # 容器端口(可引用名称)
      nodePort: 30080   # 节点端口(范围 30000-32767)
  5. 字段重置规则

    • type 变更时(如 ExternalNameClusterIP),externalNamehealthCheckNodePort 等字段自动清除。
  6. 外部服务 自定义Service行为

    • 如果一个Service的Selector为空,那么这个Service就不会匹配任何Pod,这个时候云厂商可以自行开发组件创建EndPoint,自己控制后端pod的管理

4.2.Service.status 结构

4.2.1.Service.status 字段整理

字段名称 类型 描述 子字段 示例值/说明
conditions \[\]Object 服务的当前状态条件(Kubernetes 1.20+) 见下方子字段表 用于诊断服务配置问题
loadBalancer Object 负载均衡器状态(仅当 spec.type=LoadBalancer 时有效) 见下方子字段表 包含云厂商分配的负载均衡器信息

4.2.2.Service.status.conditions 结构

  • conditions 每个 Condition 对象包含以下字段:
字段名称 类型 描述 可能值
type string 条件类型 LoadBalancerReady ExternalTrafficPolicy
status string 条件状态 True/False/Unknown
lastTransitionTime string 最后一次状态转换的时间戳 2023-08-15T10:12:34Z
reason string 机器可读的状态原因代码 SyncLoadBalancerFailed
message string 人类可读的状态描述 "Error syncing load balancer"
  • 典型条件类型
    • LoadBalancerReady:负载均衡器是否就绪
    • ExternalTrafficPolicy:外部流量策略是否生效
    • ClusterIPReady:ClusterIP 是否分配成功

4.2.3.Service.status.loadBalancer 结构

  • loadBalancer ,包含负载均衡器的入口点信息:
    • status.loadBalancer 下只有ingress一个属性,下面的ip、hostname等都是ingress的子属性
字段名称 类型 描述 示例值
ingress \[\]Object 负载均衡器的入口点列表(数组)
ip string 负载均衡器的公网 IP 地址 203.0.113.10
hostname string 负载均衡器的 DNS 主机名 a1234567.us-east-1.elb.amazonaws.com
ports \[\]Object 端口映射状态(Kubernetes 1.27+)
→ → port integer 负载均衡器监听端口 443
→ → protocol string 协议类型 TCP/UDP
→ → error string 端口配置错误信息 "Port 80 not available"

4.2.4.Service.status 完整yaml示例

yaml 复制代码
status:
  conditions:
  - type: "LoadBalancerReady"
    status: "True"
    lastTransitionTime: "2023-08-15T10:12:34Z"
    reason: "Success"
    message: "Load balancer is ready"
  - type: "ExternalTrafficPolicy"
    status: "True"
    lastTransitionTime: "2023-08-15T10:13:45Z"
    reason: "Applied"
    message: "External traffic policy set to Local"
  loadBalancer:
    ingress:
    - ip: "203.0.113.10"
      ports:
      - port: 80
        protocol: TCP
      - port: 443
        protocol: TCP
        error: "Certificate validation failed"
    - hostname: "a1234567.us-east-1.elb.amazonaws.com"

5.Service 的模式类型

Service 的行为由 spec.type 决定,具有4种行为模式:ClusterIP、NodePort、LoadBalancer、ExternalName

  • Service支持的这些类型并不是独立使用,在一些场景下可以同时存在。比如nodePort一般也会存在clusterIP。loadBalancer一般也同时使用nodePort、clutsterIP等

5.1.spec.type==ClusterIP(默认类型)

5.1.1.核心功能

  • 为 Service 分配一个集群内部的 VIP,该 ip 仅允许在kubernetes集群内部的其他 Pod 或 Service 访问,外部不通
  • ClusterIP 是 spec.type 的默认类型

5.1.2.VIP 分配范围

  • service的clusterIP是apiserver分配的,所以配置范围是在apiserver的启动参数
  • apiserver 的配置项 --service-cluster-ip-range 指定了clusterIP的分配范围
  • 查看方式:ps -ef | grep apiserver | grep service-cluster-ip-range

5.1.3.适用场景

  • 微服务间的内部通信、前端访问后端 API、数据库服务供应用访问等

5.1.4.使用示例

yaml 复制代码
# 多端口
apiVersion: v1
kind: Service
metadata:
  name: web-monitor-service
spec:
  type: ClusterIP
  selector:
    app: web-app
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 8080    # Web 服务端口
    - name: metrics
      protocol: TCP
      port: 9090
      targetPort: 9090    # 监控指标端口(如 Prometheus)
--- 
# 会话保持
apiVersion: v1
kind: Service
metadata:
  name: sticky-service
spec:
  selector:
    app: stateful-app
  sessionAffinity: ClientIP  # 启用会话保持
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 3600   # 会话保持超时时间(秒)
  ports:
    - port: 80
      targetPort: 8080

5.2.spec.type==NodePort

5.2.1.核心功能

  • ClusterIP 只能在集群内访问,为了将服务发布到集群外部,NodePort 模式通过将 Service Port 映射到节点端口上,让集群外部可以访问服务
  • 请求转发链路:用户请求 --> nodeiIp:nodePort --> service clusterIP:servicePort --> [podA IP:targetPort; podB IP:targetPort;...]
  • NodePort属于Service的一个固定属性,只要这个service不删除,NodePort就不会发生变化
  • 注意:NodePort模式下,集群中每个节点都会开放同一个端口,访问任何一个nodeIP: nodePort都可以映射到service上

5.2.2.NodePort 分配范围

  • service的nodePort是apiserver分配的,所以配置范围是在apiserver的启动参数
  • apiserver 的配置项 --service-node-port-range 指定了NodePort的分配范围,默认 30000-32767
  • 查看方式:ps -ef | grep apiserver | grep service-node-port-range

5.2.3.访问方式

  • 集群内部:通过 ClusterIP:Port 或 DNS 名称。
  • 集群外部:通过 <NodeIP>:<NodePort> 访问(可以访问集群的任意 Node)。

5.2.4.适用场景

  • 本地开发测试、裸金属集群等,需要从集群外部访问服务,可以使用NodePort将服务发布出去
  • 但不建议放在生产环境使用,开放node端口有安全风险,且nodePort数量总归是有限的,port占用完了,新服务就没办法发布了

5.2.5.使用示例

yaml 复制代码
# 单端口
apiVersion: v1
kind: Service
metadata:
  name: web-nodeport
spec:
  type: NodePort
  selector:
    app: web-app  # 匹配 Deployment 中 Pod 的标签
  ports:
    - port: 80          # Service 的集群内访问端口
      targetPort: 8080  # Pod 容器的实际端口
      nodePort: 30080   # 节点暴露的端口(范围 30000-32767)
---      
# 多端口
apiVersion: v1
kind: Service
metadata:
  name: multi-port-service
spec:
  type: NodePort
  selector:
    app: web-app
  ports:
    - name: http
      port: 80
      targetPort: 8080
      nodePort: 30081  # HTTP 节点端口
    - name: https
      port: 443
      targetPort: 8443
      nodePort: 30443  # HTTPS 节点端口

5.3.spec.type==LoadBalancer

5.3.1.LoadBalancer是什么

  • 为了将服务暴露到集群外部,前面我们讲了NodePort方式,但直接将节点port暴露出去让用户感知是非常不安全的,且nodePort数量总归是有限的。
  • 因此我们需要 更灵活更安全的 将服务发布到集群外部 的方式,LoadBalancer 就可以应对这种场景。

5.3.2.核心功能

  • ClusterIPNodePort 的基础上,在 用户 和 nodePort 之间 又加了一层云厂商LoadBalancer。集群里的服务虽然暴露到了nodePort上,但是 nodePort 并不是直接给用户访问,而是给外部的云厂商LB访问。
  • 用户去访问 云厂商LB,云厂商LB来访问集群的nodePort,然后转发到service的clusterIP,最终转发到pod上,所以整个请求链路 变成了:用户请求 --> cloud LB --> nodeiIp:nodePort --> service clusterIP:servicePort --> [podA IP:targetPort; podB IP:targetPort;...]
  • Service 的 status.loadBalancer.ingress 字段会提供外部负载均衡器的 IP 地址或主机名。

5.3.3.Service 如何与云厂商的外部LB交互

5.3.3.1.Cloud Controller Manager(CCM)
  • 云厂商通过 Kubernetes 的 Cloud Controller Manager(CCM) 组件实现对 Service 的监听和负载均衡器(LB)的自动化管理。其核心流程可分为 事件监听、资源创建、配置同步和后端管理 四个阶段。
  • 对CCM不了解的同学可以阅读:Kubernetes控制平面组件:Controller Manager详解。在常见Controller中有介绍CCM。
5.3.3.2.CCM 管理 Service LB 的核心流程

#mermaid-svg-uFZ5bJKW8e5ZKmq7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .error-icon{fill:#552222;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .marker.cross{stroke:#333333;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 p{margin:0;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .cluster-label text{fill:#333;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .cluster-label span{color:#333;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .cluster-label span p{background-color:transparent;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .label text,#mermaid-svg-uFZ5bJKW8e5ZKmq7 span{fill:#333;color:#333;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .node rect,#mermaid-svg-uFZ5bJKW8e5ZKmq7 .node circle,#mermaid-svg-uFZ5bJKW8e5ZKmq7 .node ellipse,#mermaid-svg-uFZ5bJKW8e5ZKmq7 .node polygon,#mermaid-svg-uFZ5bJKW8e5ZKmq7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .rough-node .label text,#mermaid-svg-uFZ5bJKW8e5ZKmq7 .node .label text,#mermaid-svg-uFZ5bJKW8e5ZKmq7 .image-shape .label,#mermaid-svg-uFZ5bJKW8e5ZKmq7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .rough-node .label,#mermaid-svg-uFZ5bJKW8e5ZKmq7 .node .label,#mermaid-svg-uFZ5bJKW8e5ZKmq7 .image-shape .label,#mermaid-svg-uFZ5bJKW8e5ZKmq7 .icon-shape .label{text-align:center;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .node.clickable{cursor:pointer;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .arrowheadPath{fill:#333333;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .cluster text{fill:#333;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .cluster span{color:#333;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .icon-shape,#mermaid-svg-uFZ5bJKW8e5ZKmq7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .icon-shape p,#mermaid-svg-uFZ5bJKW8e5ZKmq7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .icon-shape .label rect,#mermaid-svg-uFZ5bJKW8e5ZKmq7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uFZ5bJKW8e5ZKmq7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户创建 LoadBalancer Service
CCM Watch 到事件
调用云 API 创建 LB/监听器
配置健康检查与后端
持续同步 Endpoints/节点变化
流量通过 LB 转发到 Pod

5.3.3.3.第一步:事件监听:Watch Service 资源
  • CCM 的监听机制
    • CCM 作为 Kubernetes 控制平面的组件,通过 Kubernetes API Server 监听集群内 Service 资源的事件(Create/Update/Delete)。当用户创建或修改 type: LoadBalancer 的 Service 时,CCM 会捕获以下关键字段:
      • spec.type: LoadBalancer
      • spec.ports(端口配置)
      • metadata.annotations(云厂商特定配置,如 LB 类型、IP 白名单等)
      • spec.externalTrafficPolicy(流量策略)
    • 事件触发条件
      • 仅当 Service 的 type 被设为 LoadBalancer 时,CCM 才会触发 LB 创建流程。其他类型(如 ClusterIP)会被忽略。
5.3.3.4.第二步:资源创建:调用云厂商 API
  • CCM 根据 Service 配置调用云厂商的 LB 管理 API,完成以下资源创建:

  • 负载均衡器实例

    • 根据 metadata.annotations 找到当前使用的外部负载均衡器,创建公有云 LB(如腾讯云CLB、阿里云 SLB、华为云 ELB等)

    • 示例注解:

      yaml 复制代码
      annotations:
        service.beta.kubernetes.io/alibaba-cloud-loadbalancer-id: lb-xxxxx # 指定已有 LB
        service.beta.kubernetes.io/aws-load-balancer-type: "nlb"         # 指定 LB 类型
  • 监听器(Listener)

    • 依据 spec.ports 配置监听端口和协议(TCP/HTTP/HTTPS)。

    • 高级功能 :如华为云支持区间端口监听 (监听连续端口范围):

      yaml 复制代码
      annotations:
        kubernetes.io/elb.port-ranges: '{"http-port":["80,90"]}' # 监听 80-90 端口
  • 健康检查规则

    • 基于 spec.healthCheckNodePort 或云厂商默认策略配置健康检查。
    • 参数包括超时时间、检查间隔、成功阈值等(例如阿里云的 HealthyThreshold=4)。
  • 后端服务器组

    • 初始创建时,CCM 将集群节点加入 LB 后端(需排除 Master 节点)。
5.3.3.5.第三步:配置同步--动态维护 LB 状态
  • CCM 持续监听集群变化,动态更新 LB 配置
  • 节点变化同步
    • 当新增/删除节点时,CCM 自动更新 LB 后端服务器组。
    • 策略差异
      • externalTrafficPolicy: Cluster → 所有节点加入 LB
      • externalTrafficPolicy: Local → 仅运行 Pod 的节点加入 LB(保留客户端 IP)
  • Endpoint 监控
    • CCM 监听 Service 关联的 Endpoints 变化,实时更新 LB 的后端 Pod IP。
  • LB 配置防覆盖
    • 重要限制:用户不得手动修改 LB 配置(如控制台修改监听器),否则 CCM 会在下次同步时覆盖为 Service 声明的状态。
5.3.3.6.第四步:后端管理:流量策略与优化
  • 云厂商通过 CCM 实现精细化流量管理
  • 会话保持(Session Affinity)
    • 通过注解配置 Cookie 植入或重写(如阿里云 StickySession=insert)。
  • 调度算法
    • 支持轮询(RR)、加权轮询(WRR)等(如阿里云 Scheduler=wrr)。
  • 源 IP 透传
    • 启用 externalTrafficPolicy: Local + X-Forwarded-For 头字段保留客户端 IP。
5.3.3.7.扩展机制:注解(Annotations)控制
  • 云厂商通过注解提供高级功能,覆盖 90% 的 LB 定制需求:
功能类别 注解示例 作用
LB 实例控制 service.beta.kubernetes.io/alibaba-cloud-loadbalancer-id: lb-xxx 绑定已有 LB
协议扩展 service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:xxx 配置 TLS 证书
端口范围监听 kubernetes.io/elb.port-ranges: '{"web":["80,90"]}' 监听连续端口(华为云)
健康检查定制 service.beta.kubernetes.io/alicloud-healthcheck-timeout: "5" 自定义健康检查超时
节点过滤 service.beta.kubernetes.io/alibaba-cloud-loadbalancer-remove-unscheduled-backend: "on" 自动移除非调度节点

5.3.4.适用场景

  • 需要在云环境下将服务暴露给互联网或外部网络,并利用云提供商的负载均衡器特性(如健康检查、SSL 终止、WAF 集成)。

5.4.spec.type==ExternalName

5.4.1.核心功能

  • ExternalName 是 Kubernetes Service 的一种特殊类型,它不需要指定selector去选择哪些pods实例提供服务,而是使用DNS CNAME机制把服务请求透明地重定向到指定的DNS名称
    • 因为service后面压根没有pod,所以不会为service生成endpoint,不会创建VIP,也不进行任何流量代理。
    • ExternalName模式的Service,可以指向任何的服务,可以是内部其他ns下的service,也可以是集群外部的服务
  • 当访问这个 service 的时候,service不会做流量代理转发,而是直接返回自己内部配置的 CNAME 记录,供调用方使用

5.4.2.适用场景

  • 将集群内部的服务访问重定向到运行在 Kubernetes 外部的服务(如遗留数据库、SaaS API),提供命名抽象。

5.4.2.使用示例

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: external-api-service
spec:
  type: ExternalName
  externalName: api.thirdparty-provider.com  # 目标域名

---

apiVersion: v1
kind: Service
metadata:
  name: legacy-system
  annotations:
    # 文档说明(非功能注解)
    description: "映射到旧系统API"
spec:
  type: ExternalName
  externalName: 192-168-1-100.nip.io  # 兼容IP的域名格式  

6.Headless Service

6.1.Headless Service是什么

  • Headless Service 是 Kubernetes 中一种特殊的 Service 类型,专为需要绕过负载均衡、直连 Pod 的场景设计。

  • Headless Service 的配置方法:

    • service.spec.clusterIP 设置为 None,kubernetes不会为svc分配 ClusterIP,也不会进行负载均衡或代理。
    • 但 Headless Service 一般都会配置Selector,所以会创建endpoint
    • 在 DNS 解析该svc域名时,会直接返回后端 Pod 的 IP 地址列表
  • Headless Service 与 普通 Service 的 核心区别

    特性 普通 Service Headless Service
    ClusterIP 有(如 10.96.0.1 无(clusterIP: None
    DNS 解析结果 返回 Service ClusterIP 返回所有 Pod IP 列表
    负载均衡 由 kube-proxy 实现 需客户端或外部组件实现
  • 举例:

    yaml 复制代码
    apiVersion: v1
    kind: Service
    metadata:
      name: nginx-headless
    spec:
      ClusterIP: None
      ports:
        - port: 80
          protocol: TCP
          name: http
      selector:
        app: nginx

6.2.为什么要设计Headless Service

  1. 解决有状态应用需求
    • StatefulSet 管理的 Pod(如数据库节点)需稳定网络标识(固定域名),普通 Service 的负载均衡会破坏节点间的直接通信。
  2. 支持自定义路由
    • 部分场景(如 gRPC、Redis 集群)需客户端实现一致性哈希等高级负载策略,绕过 kube-proxy 的默认轮询。
    • 比如:目前部署了一个Redis集群,Redis的主节点、从节点之间需要通信,同步状态、同步数据等等,互相之间的通信就可以使用 Headless Service,通过每个pod的稳定网络标识(域名),就可以实现通信,不需要经过kube-proxy的转发,效率更高

6.3.Headless Service 核心功能

  1. DNS 直连 Pod
    • 查询服务域名(如 my-svc.default.svc.cluster.local)返回所有 Pod IP 列表。
    • 每个 Pod 拥有独立域名:<pod-name>.<svc-name>.<namespace>.svc.cluster.local(如 mysql-0.mysql.default.svc.cluster.local)。
  2. 无中间层转发
    • 流量直达 Pod,减少延迟,避免 kube-proxy 性能瓶颈。

6.4.适用场景

6.4.1.有状态应用(StatefulSet)

  • StatefulSet有一个属性为serviceName,一般要我们指定一个headless service。

    • 因为sts的pod名称是固定的,设置一个headless service之后,每一个pod就有自己固定的dns域名了,比如 my-sts-0.svc.cluster....,其他服务就可以通过这个域名直接访问到这个pod。
    • 即使pod发生重建,名称也不变,域名也不变
  • 数据库集群 (MySQL 主从、MongoDB 副本集)

    • 节点间需直接通信(如主节点同步数据),通过固定域名精确访问特定 Pod。
    yaml 复制代码
    # Headless Service 示例(配合 StatefulSet)
    apiVersion: v1
    kind: Service
    metadata:
      name: mysql
    spec:
      clusterIP: None
      selector:
        app: mysql
      ports:
        - port: 3306
    ---
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: mysql
    spec:
      serviceName: "mysql"  # 绑定 Headless Service
      replicas: 3
      template:
        metadata:
          labels:
            app: mysql
        spec:
          containers:
          - name: mysql
            image: mysql:5.7
  • 此时 Pod 域名:mysql-0.mysql.default.svc.cluster.localmysql-1.mysql.default.svc.cluster.local....

  • 主从节点之间就可以使用域名直接通信

6.4.2.分布式系统

  • 消息队列 (Kafka、Zookeeper):
    • 节点需发现集群所有成员 IP 以建立通信链路。
  • 服务发现中间件 (Etcd、Consul):
    • 通过 DNS 获取集群节点列表,实现自注册。

6.4.3.客户端自定义负载均衡

  • gRPC 长连接 :客户端 SDK 根据 Pod IP 列表实现加权轮询或地域感知路由。

    go 复制代码
    // Go 示例:客户端负载均衡
    func getEndpoints() []string {
        addrs, _ := net.LookupHost("my-grpc-service.default.svc.cluster.local")
        return addrs // 返回所有 Pod IP
    }

6.4.4.避免负载均衡干扰

  • 主从选举:需直连特定 Pod(如 Redis 哨兵节点)。

6.5.使用注意事项

6.5.1.DNS 缓存问题

  • Pod IP 变更后,客户端可能因 DNS 缓存延迟感知(默认 TTL 5秒)。

  • 优化方案 :调整 CoreDNS 配置,延长成功查询的缓存时间:

    yaml 复制代码
    # CoreDNS ConfigMap 调整 TTL
    cache {
        success 2048 300  # 成功查询缓存 300 秒
        denial 1024 60    # 失败查询缓存 60 秒
    }

6.5.2.未就绪 Pod 处理

  • 默认 DNS 仅返回 Ready 状态的 Pod IP。

  • 若需包含未就绪 Pod(如 StatefulSet 初始化),需在service上显式配置:

    yaml 复制代码
    spec:
     publishNotReadyAddresses: true  # 返回所有 Pod IP(包括未就绪)

6.5.3.网络策略隔离

  • Headless Service 暴露所有 Pod IP,需通过 NetworkPolicy 限制访问来源

    yaml 复制代码
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: restrict-db-access
    spec:
      podSelector:
        matchLabels:
          app: mysql
      ingress:
      - from:
        - namespaceSelector:
            matchLabels:
              env: prod  # 仅允许 prod 命名空间访问

6.6.与普通 Service 的协作

  • 混合架构示例

    • 内部通信:通过 Headless Service 直连数据库 Pod。
    • 外部访问:通过普通 Service + Ingress 暴露 Web 服务。

    #mermaid-svg-59f2Qz2xtIbg4Ptk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-59f2Qz2xtIbg4Ptk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-59f2Qz2xtIbg4Ptk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-59f2Qz2xtIbg4Ptk .error-icon{fill:#552222;}#mermaid-svg-59f2Qz2xtIbg4Ptk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-59f2Qz2xtIbg4Ptk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-59f2Qz2xtIbg4Ptk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-59f2Qz2xtIbg4Ptk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-59f2Qz2xtIbg4Ptk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-59f2Qz2xtIbg4Ptk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-59f2Qz2xtIbg4Ptk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-59f2Qz2xtIbg4Ptk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-59f2Qz2xtIbg4Ptk .marker.cross{stroke:#333333;}#mermaid-svg-59f2Qz2xtIbg4Ptk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-59f2Qz2xtIbg4Ptk p{margin:0;}#mermaid-svg-59f2Qz2xtIbg4Ptk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-59f2Qz2xtIbg4Ptk .cluster-label text{fill:#333;}#mermaid-svg-59f2Qz2xtIbg4Ptk .cluster-label span{color:#333;}#mermaid-svg-59f2Qz2xtIbg4Ptk .cluster-label span p{background-color:transparent;}#mermaid-svg-59f2Qz2xtIbg4Ptk .label text,#mermaid-svg-59f2Qz2xtIbg4Ptk span{fill:#333;color:#333;}#mermaid-svg-59f2Qz2xtIbg4Ptk .node rect,#mermaid-svg-59f2Qz2xtIbg4Ptk .node circle,#mermaid-svg-59f2Qz2xtIbg4Ptk .node ellipse,#mermaid-svg-59f2Qz2xtIbg4Ptk .node polygon,#mermaid-svg-59f2Qz2xtIbg4Ptk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-59f2Qz2xtIbg4Ptk .rough-node .label text,#mermaid-svg-59f2Qz2xtIbg4Ptk .node .label text,#mermaid-svg-59f2Qz2xtIbg4Ptk .image-shape .label,#mermaid-svg-59f2Qz2xtIbg4Ptk .icon-shape .label{text-anchor:middle;}#mermaid-svg-59f2Qz2xtIbg4Ptk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-59f2Qz2xtIbg4Ptk .rough-node .label,#mermaid-svg-59f2Qz2xtIbg4Ptk .node .label,#mermaid-svg-59f2Qz2xtIbg4Ptk .image-shape .label,#mermaid-svg-59f2Qz2xtIbg4Ptk .icon-shape .label{text-align:center;}#mermaid-svg-59f2Qz2xtIbg4Ptk .node.clickable{cursor:pointer;}#mermaid-svg-59f2Qz2xtIbg4Ptk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-59f2Qz2xtIbg4Ptk .arrowheadPath{fill:#333333;}#mermaid-svg-59f2Qz2xtIbg4Ptk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-59f2Qz2xtIbg4Ptk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-59f2Qz2xtIbg4Ptk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-59f2Qz2xtIbg4Ptk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-59f2Qz2xtIbg4Ptk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-59f2Qz2xtIbg4Ptk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-59f2Qz2xtIbg4Ptk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-59f2Qz2xtIbg4Ptk .cluster text{fill:#333;}#mermaid-svg-59f2Qz2xtIbg4Ptk .cluster span{color:#333;}#mermaid-svg-59f2Qz2xtIbg4Ptk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-59f2Qz2xtIbg4Ptk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-59f2Qz2xtIbg4Ptk rect.text{fill:none;stroke-width:0;}#mermaid-svg-59f2Qz2xtIbg4Ptk .icon-shape,#mermaid-svg-59f2Qz2xtIbg4Ptk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-59f2Qz2xtIbg4Ptk .icon-shape p,#mermaid-svg-59f2Qz2xtIbg4Ptk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-59f2Qz2xtIbg4Ptk .icon-shape .label rect,#mermaid-svg-59f2Qz2xtIbg4Ptk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-59f2Qz2xtIbg4Ptk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-59f2Qz2xtIbg4Ptk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-59f2Qz2xtIbg4Ptk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HTTP请求
    路由
    负载均衡
    直连
    返回所有IP
    Client
    Ingress
    ClusterIP Service
    Web Pod 1
    Web Pod 2
    HeadlessService
    DB Pod 1
    DB Pod 2

6.7.Service Topology 服务拓扑

  • 在上面Service Spec结构中,有个 topologyKeys 字段就是指定service拓扑的属性

    • 当客户端访问 Service 时,系统按 topologyKeys 列表顺序匹配后端节点标签。
      • 优先选择与客户端节点标签值相同的后端(如都在同一可用区)。
      • 若无匹配,尝试列表中的下一个拓扑键,直到匹配或失败。
  • topologyKeys 在 Kubernetes v1.17 作为正式特性引入,于 v1.21 弃用,v1.24+ 移除。

    • 移除原因:topologyKeys 需手动维护标签优先级,且与 externalTrafficPolicy 冲突

    • 替代方案 :自动计算最优路由,通过注解动态配置

      yaml 复制代码
      annotations:
        service.kubernetes.io/topology-aware-hints: "auto"
  • topologyKeys 的使用包括两种方式:

    yaml 复制代码
    # 强制拓扑服务,如果没有匹配的实例,则拒绝服务
    apiVersion: v1
    kind: Service
    metadata:
      name: nodelocal
    spec:
      ports:
        - port: 80
          protocol: TCP
          name: http
      selector:
        app: nginx
      topologyKeys:
        - "kubernetes.io/hostname"
    ---
    
    # prefer 尽量拓扑服务,如果没有匹配的实例,最后兜底任意服务
    apiVersion: v1
    kind: Service
    metadata:
      name: prefer-nodelocal
    spec:
      ports:
        - port: 80
          protocol: TCP
          name: http
      selector:
        app: nginx
      topologyKeys:
        - "kubernetes.io/hostname"
        - "topology.kubernetes.io/zone"
        - "topology.kubernetes.io/region"
        - "*"

7.Endpoint对象

7.1.Endpoint 基本功能

  • Endpoint 是 Kubernetes 的核心资源,用于管理服务(Service)与后端 Pod 的网络映射关系
  • 有了Service,为什么还要有Endpoint?
    • Service 本身不直接管理 Pod。它通过 spec.selector 字段定义一组标签选择器。
    • Kubernetes 控制器会自动创建和管理 一个与 Service 同名Endpoint 对象(或 EndpointSlice 对象,更新、更高效)。
    • Endpoint 对象动态地存储了当前所有匹配 Service 选择器的、健康的 Pod 的 IP:Port 列表。也会存储notReady的pod列表,根据需要使用。如果pod状态有变化,ep也会实时更新
    • kube-proxy 和 Ingress 控制器等组件正是 监视 Endpoint 的变化 来实时更新负载均衡规则的。当 Pod 创建、销毁或健康状态改变时,Endpoint 列表会动态更新,确保流量只路由到健康的 Pod。
  • 设计初衷
    • service和pod本质上是多对多关系。一个service可以关联多个pod,一个pod也可以被多个service关联
    • 在web应用中,多对多关系我们一般都是增加一个中间表存储这个关系,而endpoint就是这样的一张中间表
  • Endpoint主要功能包括:
    • 服务发现
      • 提供 Service 对应的后端 Pod 的实时 IP 地址和端口,供集群内组件(如 kube-proxy)发现服务。
    • 动态更新
      • 自动感知 Pod 状态变化(创建、删除、重启),实时更新地址列表。
    • 负载均衡
      • Service 基于 Endpoint 中的地址列表实现流量分发,支持轮询、随机等策略。
    • 外部服务接入
      • 允许通过手动创建 Endpoint 将集群外服务(如数据库)映射为 Kubernetes Service。
      • 如果一个Service的Selector为空,那么这个Service就不会匹配任何Pod,这个时候云厂商可以自行开发组件创建EndPoint,自己控制后端pod的管理

7.2.Endpoint 使用示例

7.2.1.自动生成 Endpoint(Service 带 Selector)

  • 创建Service,kubernetes会 自动创建一个同名的endpoint
yaml 复制代码
# Service 定义(自动关联 Pod)
apiVersion: v1
kind: Service
metadata:
annotations:
  meta.helm.sh/release-name: my-release-1
  meta.helm.sh/release-namespace: default
creationTimestamp: "2025-02-16T09:18:44Z"
labels:
  app.kubernetes.io/component: etcd
  app.kubernetes.io/instance: my-release-1
  app.kubernetes.io/managed-by: Helm
  app.kubernetes.io/name: etcd
  app.kubernetes.io/version: 3.5.18
  helm.sh/chart: etcd-11.0.7
name: my-release-1-etcd
namespace: default
resourceVersion: "82111449"
spec:
clusterIP: 10.96.196.24
ports:
  - name: client
    port: 2379
    protocol: TCP
    targetPort: client
  - name: peer
    port: 2380
    protocol: TCP
    targetPort: peer
selector:
  app.kubernetes.io/component: etcd
  app.kubernetes.io/instance: my-release-1
  app.kubernetes.io/name: etcd
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}

---
apiVersion: v1
kind: Endpoints
metadata:
annotations:
  endpoints.kubernetes.io/last-change-trigger-time: "2025-04-16T06:10:36Z"
creationTimestamp: "2025-02-16T09:18:44Z"
labels:
  app.kubernetes.io/component: etcd
  app.kubernetes.io/instance: my-release-1
  app.kubernetes.io/managed-by: Helm
  app.kubernetes.io/name: etcd
  app.kubernetes.io/version: 3.5.18
  helm.sh/chart: etcd-11.0.7
name: my-release-1-etcd
namespace: default
subsets:
- addresses:
    - ip: 10.244.0.111
      nodeName: vm-226-235-tencentos
      targetRef:
        kind: Pod
        name: my-release-1-etcd-0
        namespace: default
        resourceVersion: "93610940"
        uid: 417e2aec-38f8-42c1-877f-906b863437ee
  ports:
    - name: peer
      port: 2380
      protocol: TCP
    - name: client
      port: 2379
      protocol: TCP
  • 当 Pod 启动后,Kubernetes 会自动创建同名 Endpoint:
bash 复制代码
kubectl get endpoints my-release-1-etcd
  • 输出示例:
bash 复制代码
NAME                ENDPOINTS                             AGE
my-release-1-etcd   10.244.0.111:2380,10.244.0.111:2379   76d

7.2.2.手动创建 Endpoint(接入外部服务)

  • 创建 Selector 为空的 Service,这个Service就不会匹配任何Pod,这个时候云厂商可以自行开发组件创建EndPoint,自己控制后端pod的管理
yaml 复制代码
# 手动定义外部数据库 Endpoint
apiVersion: v1
kind: Endpoints
metadata:
  name: external-mysql  # 必须与 Service 同名
subsets:
  - addresses:
      - ip: 192.168.1.100   # 外部数据库 IP
    ports:
      - port: 3306          # 外部数据库端口


# 创建无 Selector 的 Service
apiVersion: v1
kind: Service
metadata:
  name: external-mysql
spec:
  ports:
    - port: 3306

7.3.Endpoint subsets 字段详解

  • endpoint没有spec,有个subsets字段,用于描述后端地址分组,每个分组对应一组 IP:Port
字段名称 类型 必填 功能说明
addresses Array of Object 后端实例地址列表
ports Array of Object 端口定义(需与 Service 的 targetPort 匹配)
notReadyAddresses Array of Object 未就绪的后端地址(默认如果pod未就绪,入 Readiness Probe 未通过,则会加入这个列表)
  • yaml举例:

    yaml 复制代码
    apiVersion: v1
    kind: Endpoints
    metadata:
      name: my-service-endpoints  # 需与对应 Service 同名
      namespace: default
    subsets:
    - addresses:  # 就绪的后端实例列表
      - ip: 192.168.1.10
        targetRef:
          kind: Pod
          name: pod-1
          namespace: default
      - ip: 192.168.1.11
        targetRef:
          kind: Pod
          name: pod-2
          namespace: default
      notReadyAddresses:  # 未就绪的后端实例列表
      - ip: 192.168.1.12
        targetRef:
          kind: Pod
          name: pod-3
          namespace: default
      ports:  # 端口定义(需与 Service 的 targetPort 匹配)
      - name: http
        port: 8080
        protocol: TCP
      - name: metrics
        port: 9100
        protocol: TCP
  • addresses、ports、notReadyAddresses 三个字段的结构如下:

    bash 复制代码
    # addresses
    RESOURCE: addresses <[]Object>
    FIELDS:
      hostname     <string>
      The Hostname of this endpoint
      
      ip   <string> -required-
      The IP of this endpoint. May not be loopback (127.0.0.0/8), link-local
      (169.254.0.0/16), or link-local multicast ((224.0.0.0/24). IPv6 is also
      accepted but not fully supported on all platforms. Also, certain kubernetes
      components, like kube-proxy, are not IPv6 ready.
      
      nodeName     <string>
      Optional: Node hosting this endpoint. This can be used to determine
      endpoints local to a node.
      
      targetRef    <Object>
      Reference to object providing the endpoint.
    
    # notReadyAddresses
    RESOURCE: notReadyAddresses <[]Object>
    FIELDS:
      hostname     <string>
      The Hostname of this endpoint
      
      ip   <string> -required-
      The IP of this endpoint. May not be loopback (127.0.0.0/8), link-local
      (169.254.0.0/16), or link-local multicast ((224.0.0.0/24). IPv6 is also
      accepted but not fully supported on all platforms. Also, certain kubernetes
      components, like kube-proxy, are not IPv6 ready.
      
      nodeName     <string>
      Optional: Node hosting this endpoint. This can be used to determine
      endpoints local to a node.
      
      targetRef    <Object>
      Reference to object providing the endpoint.
    
    # ports
    RESOURCE: ports <[]Object>
    FIELDS:
      appProtocol  <string>
      The application protocol for this port. This field follows standard
      Kubernetes label syntax. Un-prefixed names are reserved for IANA standard
      service names (as per RFC-6335 and
      http://www.iana.org/assignments/service-names). Non-standard protocols
      should use prefixed names such as mycompany.com/my-custom-protocol. This is
      a beta field that is guarded by the ServiceAppProtocol feature gate and
      enabled by default.
      
      name <string>
      The name of this port. This must match the 'name' field in the
      corresponding ServicePort. Must be a DNS_LABEL. Optional only if one port
      is defined.
      
      port <integer> -required-
      The port number of the endpoint.
      
      protocol     <string>
      The IP protocol for this port. Must be UDP, TCP, or SCTP. Default is TCP.

7.4.Status 字段说明

  • Endpoint 没有独立的状态字段 ,其健康状态通过以下方式体现:
    • addresses/notReadyAddresses 区分就绪与未就绪实例
    • Kube-Controller-Manager 监控 Pod 状态并更新 subsets
    • kube-proxy 根据 Endpoint 更新 iptables/IPVS 规则

7.5.使用注意事项

  • 命名强制关联
    • Endpoint 必须与 Service 同名同命名空间 才能生效。
  • 避免 Selector 冲突
    • 手动创建 Endpoint 时,对应的 Service 必须不设 selector,否则会被自动生成的 Endpoint 覆盖。
  • 外部服务接入规范
    • 外部服务 IP 必须可达,且端口需开放访问权限。
    • 建议通过 nodeName 字段标注外部节点(例:- ip: 192.168.1.100
      nodeName: db-node1)。
  • 大规模集群优化
    • 当 Pod 数量超过 1000 时,建议使用 EndpointSlice 替代 Endpoint 以提高性能。
  • 监控与调试
    • 关键命令:kubectl describe endpoints <name> 查看详细信息
    • 监控指标:kube_endpoint_address_available(可用地址数)

8.EndpointSlice对象

  • 切片之后,如果某个pod状态发生变化,只会更新该pod所在的那一个endpointSlice,其他的endpointSlice不会变化,这样只需要同步一个endpointSlice到所有的node即可,不回占用大量的网络带宽

8.1.EndpointSlice 基本功能

  • EndpointSlice 是 Kubernetes 用于优化大规模服务后端管理 的核心 API 资源,主要特性包括:
    • 可扩展分片机制
      • 将服务端点拆分为多个切片(默认每个切片最多 100 个端点)
      • 支持扩展到 10 万+ 端点的大规模服务
    • 多维拓扑感知
      • 记录端点所在的节点名称、可用区等信息,支持智能路由
    • 双栈服务支持
      • 独立管理 IPv4/IPv6/FQDN 地址类型
    • 精细化状态追踪
      • 包含 ready/serving/terminating 三种状态条件
      • EndpointSlice 通过label:kubernetes.io/service-name 指定关联的service

8.2.EndpointSlice 使用示例

8.2.1.自动生成 EndpointSlice(Service 带 Selector)

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
annotations:
  meta.helm.sh/release-name: my-release-1
  meta.helm.sh/release-namespace: default
creationTimestamp: "2025-02-16T09:18:44Z"
labels:
  app.kubernetes.io/component: etcd
  app.kubernetes.io/instance: my-release-1
  app.kubernetes.io/managed-by: Helm
  app.kubernetes.io/name: etcd
  app.kubernetes.io/version: 3.5.18
  helm.sh/chart: etcd-11.0.7
name: my-release-1-etcd
namespace: default
resourceVersion: "82111449"
selfLink: /api/v1/namespaces/default/services/my-release-1-etcd
uid: 3c4d2738-bd6e-4e18-93f0-1e3270728e53
spec:
clusterIP: 10.96.196.24
ports:
  - name: client
    port: 2379
    protocol: TCP
    targetPort: client
  - name: peer
    port: 2380
    protocol: TCP
    targetPort: peer
selector:
  app.kubernetes.io/component: etcd
  app.kubernetes.io/instance: my-release-1
  app.kubernetes.io/name: etcd
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}

---

apiVersion: discovery.k8s.io/v1beta1
kind: EndpointSlice
metadata:
annotations:
  endpoints.kubernetes.io/last-change-trigger-time: "2025-04-16T06:10:36Z"
creationTimestamp: "2025-02-16T09:18:44Z"
generateName: my-release-1-etcd-
generation: 577
labels:
  endpointslice.kubernetes.io/managed-by: endpointslice-controller.k8s.io
  kubernetes.io/service-name: my-release-1-etcd
name: my-release-1-etcd-rlcrn
namespace: default
ownerReferences:
  - apiVersion: v1
    blockOwnerDeletion: true
    controller: true
    kind: Service
    name: my-release-1-etcd
    uid: 3c4d2738-bd6e-4e18-93f0-1e3270728e53
resourceVersion: "93610945"
selfLink: /apis/discovery.k8s.io/v1beta1/namespaces/default/endpointslices/my-release-1-etcd-rlcrn
uid: 4d78aabf-7227-43ae-acf3-bd0ec3c57acd
addressType: IPv4
endpoints:
- addresses:
    - 10.244.0.111
  conditions:
    ready: true
  targetRef:
    kind: Pod
    name: my-release-1-etcd-0
    namespace: default
    resourceVersion: "93610940"
    uid: 417e2aec-38f8-42c1-877f-906b863437ee
  topology:
    kubernetes.io/hostname: vm-226-235-tencentos
ports:
- name: peer
  port: 2380
  protocol: TCP
- name: client
  port: 2379
  protocol: TCP
  • Kubernetes 会自动创建关联的 EndpointSlice:
bash 复制代码
# kubectl get endpointslice my-release-1-etcd-rlcrn
NAME                      ADDRESSTYPE   PORTS       ENDPOINTS      AGE
my-release-1-etcd-rlcrn   IPv4          2380,2379   10.244.0.111   76d

8.2.2.手动创建 EndpointSlice(高级场景)

yaml 复制代码
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
  name: custom-ep
  labels:
    kubernetes.io/service-name: external-service
addressType: IPv4
ports:
  - name: https
    protocol: TCP
    port: 443
endpoints:
  - addresses: ["203.0.113.89"]
    conditions:
      ready: true
    nodeName: node-01
    zone: us-west2-a

8.3.EndpointSlice Spec 字段详解

  • endpointslice没有spec字段,其配置全部为一级参数(与apiVersion/kind同级)
字段名称 类型 必填 功能说明 示例值/约束条件
addressType string 地址类型:IPv4/IPv6/FQDN IPv4
ports \[\]Port 协议端口定义 - name: http, protocol: TCP
endpoints \[\]Endpoint 端点集合(每个端点包含地址、状态、拓扑信息) addresses: ["10.1.2.3"]
metadata.labels map 标签系统:kubernetes.io/service-name 必填 kubernetes.io/service-name: svc
  • Endpoint结构如下:
字段名称 类型 必填 功能说明 示例值/约束条件
addresses \[\]string 端点 IP 地址列表
conditions.ready bool 是否就绪(排除终止中 Pod) true
nodeName string 所在节点名称 node-01
zone string 可用区信息 us-west2-a
  • Port结构如下:
字段名称 类型 必填 功能说明 示例值/约束条件
name string 端口唯一标识符,与 Service 端口名称一致 web-metrics
protocol string 指定传输层协议类型 true
port integer 服务暴露的端口号,空值时需由消费者上下文解析 80、443
appProtocol string 标识应用层协议,支持高级流量管理(如 HTTP/2、WebSocket) http、myapp.com/grpc

8.4.EndpointSlice Status 字段说明

  • EndpointSlice 无独立 Status 字段,其状态通过以下方式体现:
  • Endpoint 条件字段
yaml 复制代码
endpoints:
  - conditions:
      ready: true        # 是否就绪(排除终止中 Pod)
      serving: true      # 包含终止中 Pod 的就绪状态
      terminating: false # 是否处于终止流程
  • 控制器监控指标
    • endpoint_slices_managed_per_service:每个服务管理的切片数量
    • endpoint_slices_created_total:总切片创建数

8.5.大规模pod数量下,endpointslice相比endpoint的优势

本节探讨某应用有1000 个 Pod,EndpointSlice 和 Endpoints 的表现如何

8.5.1.EndpointSlice 的生成结构

  • 分片机制

    • 默认分片规则:每个 EndpointSlice 默认存储最多 100 个端点(Pod IP+端口组合),可通过 kube-controller-manager --max-endpoints-per-slice=1000 调整为每个分片最多 1000 个端点。
  • 1000 Pod 的分片示例

    • 若采用默认配置(100 端点/分片),将生成 10 个 EndpointSlice,每个分片包含 100 个 Pod 的端点信息。
    • 若手动调整分片容量至 1000,则生成 1 个 EndpointSlice,包含全部 1000 个端点。
  • 数据格式特征

    • 每个 EndpointSlice 包含以下核心字段:

      yaml 复制代码
      addressType: IPv4  # 地址类型(IPv4/IPv6/FQDN)
      ports:              # 端口列表(最多 100 个端口)
        - name: http
          protocol: TCP
          port: 80
      endpoints:          # 端点列表(最多 1000 个端点)
        - addresses: ["10.1.2.3"]
          conditions:
            ready: true     # 端点状态三元组(ready/serving/terminating)
            serving: true
            terminating: false
          nodeName: node-1  # 拓扑信息(节点、可用区)
          zone: us-west1-a

8.5.2.相比 Endpoints 的核心优势

  • Endpoint 与 EndpointSlice 对比
特性 Endpoint EndpointSlice
设计目标 小规模集群 大规模集群(支持 10 万+端点)
数据结构 单资源包含所有地址 分片存储(每片最多 1000 端点)
地址类型 仅 IP 支持 IP/FQDN
拓扑信息 包含节点名称、可用区等元数据
兼容性 所有 Kubernetes 版本 v1.21+ 默认启用
  • 性能优化
维度 Endpoints(传统) EndpointSlice(优化后) 优势说明
数据更新效率 单对象全量更新(即使 1 个 Pod 变化) 仅更新受影响的分片 1000 Pod 场景下,单次更新数据量从 1.5MB 降至 15KB(降低 99%)
网络传输压力 更新需广播整个对象(如 1.5MB * 3000 节点) 仅传输变更的分片(如 15KB * 1 分片) 在 3000 节点集群中,单次更新总数据量从 4.5GB 降至 45MB(减少 99%)
etcd 负载 单对象频繁全量写入(易触发大小限制) 分片写入(避免大对象问题) 避免因单个对象超过 etcd 1.5MB 限制导致数据截断
  • 功能扩展
特性 Endpoints(传统) EndpointSlice(优化后) 应用场景
拓扑感知路由 通过 nodeNamezone 字段支持 优化跨可用区流量调度(如优先同区域转发)
双栈服务支持 混合存储 IPv4/IPv6(易混乱) 独立分片管理(通过 addressType 区分) 同时支持 IPv4 和 IPv6 端点(需创建两个分片)
状态精细控制 ready 状态 新增 servingterminating 状态 支持优雅终止流程(如滚动更新时流量平滑切换)
  • 可扩展性
    • 容量上限:
      • Endpoints 最多支持 1000 个端点(受 etcd 限制),而 EndpointSlice 可扩展至 10 万+端点(实测数据)。
    • 分片灵活性:
      • 支持动态调整分片容量(默认 100,最大 1000),平衡更新频率与管理开销。

8.6.使用注意事项

  • 版本兼容性
    • v1.21+ 默认启用,旧版本需设置特性门控 EndpointSlice=true
  • 分片容量调整
    • 通过 --max-endpoints-per-slice 调整分片大小(默认 100,最大 1000)
  • 标签管理
    • 使用 endpointslice.kubernetes.io/managed-by 标注管理实体
    • 系统管理切片标签为 endpointslice-controller.k8s.io
  • 拓扑路由优化
    • 结合 topologyKeys 实现区域优先路由,减少跨区流量
  • 混合环境管理
    • 手动创建切片时需确保与服务同名且 addressType 匹配
相关推荐
姚不倒13 小时前
Nginx 反向代理:5 种负载均衡策略与实战
运维·nginx·负载均衡
再卷还是菜20 小时前
Cephfs总结及kubernetes+Cephfs的整合项目
云原生·容器·kubernetes
底层玩家老张20 小时前
数据库上K8s,运维为什么反而更累了?从StatefulSet到Operator的复盘
数据库·kubernetes·operator·数据库运维·架构选型
飞鸟真人21 小时前
亿级用户IM系统 之 接入网关负载均衡架构(一、网络结构与DPVS部署)
架构·负载均衡·亿级im系统
YOU OU21 小时前
多机部署/负载均衡-LoadBalance
运维·负载均衡
新兴AI民工1 天前
# 【Linux内核三十六】进程管理模块:CFS负载均衡(一):sched_domain与sched_group层级构建
linux·运维·负载均衡
IT大白鼠1 天前
使用 Docker 部署 Kubernetes 集群:容器方式搭建 K8s 环境
docker·容器·kubernetes
报错小能手2 天前
Kubernetes入门实战课 3
云原生·容器·kubernetes