我们在前面学习了负载均衡相关的基础知识,那么负载均衡在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 后,如何解决了这些问题
-
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>访问。
- 格式:
-
Service 提供负载均衡能力
- Service 核心功能之一就是将发送到其 VIP 或 NodePort 的流量自动、均匀地分发到其后端匹配的所有健康 Pod 上。
- 默认负载均衡算法是轮询。
- 通过
spec.sessionAffinity字段可以配置会话亲和性(例如ClientIP),使来自同一客户端的请求尽可能转发到同一个后端 Pod(但这只是尽力而为,不保证严格会话)。
-
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 信息。这种方式解耦强,不依赖启动顺序。
- 环境变量: Kubelet 会在每个 Pod 启动时,将当前 Namespace 内所有有效的 Service 信息(如
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 | 修改需同时更改 type;ExternalName 类型必须为空。 |
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=LoadBalancer 且 externalTrafficPolicy=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 | 修改将影响 clusterIPs 和 ipFamilies。 |
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:外部别名 - Headless:clusterIP=None |
所有类型 | 修改 type 会重置部分字段(如 externalName → ClusterIP 需重新指定 clusterIP)。 |
4.1.2.关键说明
-
双栈支持:
- 需启用
IPv6DualStack特性门控。 ipFamilies与clusterIPs必须一一对应,且受ipFamilyPolicy控制。
- 需启用
-
Headless Service:
- 设置
clusterIP: None,无 VIP,DNS 返回所有 Pod IP。 - 适用于 StatefulSet 或自定义服务发现场景。
- 设置
-
流量策略:
externalTrafficPolicy=Local+healthCheckNodePort确保云 LB 仅转发到健康节点。internalTrafficPolicy=Local优化集群内部流量(减少跨节点跳转)。
-
端口配置:
yamlports: - name: http protocol: TCP port: 80 # 服务端口 targetPort: 8080 # 容器端口(可引用名称) nodePort: 30080 # 节点端口(范围 30000-32767) -
字段重置规则:
type变更时(如ExternalName→ClusterIP),externalName、healthCheckNodePort等字段自动清除。
-
外部服务 自定义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.核心功能
- 在
ClusterIP和NodePort的基础上,在 用户 和 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: LoadBalancerspec.ports(端口配置)metadata.annotations(云厂商特定配置,如 LB 类型、IP 白名单等)spec.externalTrafficPolicy(流量策略)
- 事件触发条件
- 仅当 Service 的
type被设为LoadBalancer时,CCM 才会触发 LB 创建流程。其他类型(如 ClusterIP)会被忽略。
- 仅当 Service 的
- CCM 作为 Kubernetes 控制平面的组件,通过 Kubernetes API Server 监听集群内
5.3.3.4.第二步:资源创建:调用云厂商 API
-
CCM 根据 Service 配置调用云厂商的 LB 管理 API,完成以下资源创建:
-
负载均衡器实例
-
根据
metadata.annotations找到当前使用的外部负载均衡器,创建公有云 LB(如腾讯云CLB、阿里云 SLB、华为云 ELB等) -
示例注解:
yamlannotations: 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)。 -
高级功能 :如华为云支持区间端口监听 (监听连续端口范围):
yamlannotations: 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→ 所有节点加入 LBexternalTrafficPolicy: Local→ 仅运行 Pod 的节点加入 LB(保留客户端 IP)
- Endpoint 监控
- CCM 监听 Service 关联的
Endpoints变化,实时更新 LB 的后端 Pod IP。
- CCM 监听 Service 关联的
- LB 配置防覆盖
- 重要限制:用户不得手动修改 LB 配置(如控制台修改监听器),否则 CCM 会在下次同步时覆盖为 Service 声明的状态。
5.3.3.6.第四步:后端管理:流量策略与优化
- 云厂商通过 CCM 实现精细化流量管理
- 会话保持(Session Affinity)
- 通过注解配置 Cookie 植入或重写(如阿里云
StickySession=insert)。
- 通过注解配置 Cookie 植入或重写(如阿里云
- 调度算法
- 支持轮询(RR)、加权轮询(WRR)等(如阿里云
Scheduler=wrr)。
- 支持轮询(RR)、加权轮询(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 实现 需客户端或外部组件实现 -
举例:
yamlapiVersion: v1 kind: Service metadata: name: nginx-headless spec: ClusterIP: None ports: - port: 80 protocol: TCP name: http selector: app: nginx
6.2.为什么要设计Headless Service
- 解决有状态应用需求
- StatefulSet 管理的 Pod(如数据库节点)需稳定网络标识(固定域名),普通 Service 的负载均衡会破坏节点间的直接通信。
- 支持自定义路由
- 部分场景(如 gRPC、Redis 集群)需客户端实现一致性哈希等高级负载策略,绕过 kube-proxy 的默认轮询。
- 比如:目前部署了一个Redis集群,Redis的主节点、从节点之间需要通信,同步状态、同步数据等等,互相之间的通信就可以使用 Headless Service,通过每个pod的稳定网络标识(域名),就可以实现通信,不需要经过kube-proxy的转发,效率更高
6.3.Headless Service 核心功能
- 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)。
- 查询服务域名(如
- 无中间层转发 :
- 流量直达 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发生重建,名称也不变,域名也不变
- 因为sts的pod名称是固定的,设置一个headless service之后,每一个pod就有自己固定的dns域名了,比如
-
数据库集群 (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.local、mysql-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上显式配置:
yamlspec: publishNotReadyAddresses: true # 返回所有 Pod IP(包括未就绪)
6.5.3.网络策略隔离
-
Headless Service 暴露所有 Pod IP,需通过
NetworkPolicy限制访问来源yamlapiVersion: 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 列表顺序匹配后端节点标签。
- 优先选择与客户端节点标签值相同的后端(如都在同一可用区)。
- 若无匹配,尝试列表中的下一个拓扑键,直到匹配或失败。
- 当客户端访问 Service 时,系统按 topologyKeys 列表顺序匹配后端节点标签。
-
topologyKeys 在 Kubernetes v1.17 作为正式特性引入,于 v1.21 弃用,v1.24+ 移除。
-
移除原因:topologyKeys 需手动维护标签优先级,且与 externalTrafficPolicy 冲突
-
替代方案 :自动计算最优路由,通过注解动态配置
yamlannotations: 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本质上是多对多关系。一个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举例:
yamlapiVersion: 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 个端点。
- 默认分片规则:每个 EndpointSlice 默认存储最多 100 个端点(Pod IP+端口组合),可通过
-
1000 Pod 的分片示例
- 若采用默认配置(100 端点/分片),将生成 10 个 EndpointSlice,每个分片包含 100 个 Pod 的端点信息。
- 若手动调整分片容量至 1000,则生成 1 个 EndpointSlice,包含全部 1000 个端点。
-
数据格式特征
-
每个 EndpointSlice 包含以下核心字段:
yamladdressType: 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(优化后) | 应用场景 |
|---|---|---|---|
| 拓扑感知路由 | 无 | 通过 nodeName 和 zone 字段支持 |
优化跨可用区流量调度(如优先同区域转发) |
| 双栈服务支持 | 混合存储 IPv4/IPv6(易混乱) | 独立分片管理(通过 addressType 区分) |
同时支持 IPv4 和 IPv6 端点(需创建两个分片) |
| 状态精细控制 | 仅 ready 状态 |
新增 serving 和 terminating 状态 |
支持优雅终止流程(如滚动更新时流量平滑切换) |
- 可扩展性
- 容量上限:
- Endpoints 最多支持 1000 个端点(受 etcd 限制),而 EndpointSlice 可扩展至 10 万+端点(实测数据)。
- 分片灵活性:
- 支持动态调整分片容量(默认 100,最大 1000),平衡更新频率与管理开销。
- 容量上限:
8.6.使用注意事项
- 版本兼容性
- v1.21+ 默认启用,旧版本需设置特性门控
EndpointSlice=true
- v1.21+ 默认启用,旧版本需设置特性门控
- 分片容量调整
- 通过
--max-endpoints-per-slice调整分片大小(默认 100,最大 1000)
- 通过
- 标签管理
- 使用
endpointslice.kubernetes.io/managed-by标注管理实体 - 系统管理切片标签为
endpointslice-controller.k8s.io
- 使用
- 拓扑路由优化
- 结合
topologyKeys实现区域优先路由,减少跨区流量
- 结合
- 混合环境管理
- 手动创建切片时需确保与服务同名且
addressType匹配
- 手动创建切片时需确保与服务同名且