**这是本文档旧的修订版!**
K3s ServiceLB(LoadBalancer)工作原理
为什么 K3s 不需要安装 MetalLB?
很多第一次接触 K3s 的用户都会发现:
kubectl create service loadbalancer nginx --tcp=80:80
仅仅创建一个 Service,几秒钟之后,整个集群居然就可以直接访问了。
甚至执行:
kubectl get svc
可以看到:
NAME TYPE CLUSTER-IP EXTERNAL-IP
nginx LoadBalancer 10.43.120.55 192.168.1.10
而 Kubernetes 官方并没有提供 LoadBalancer 的实现。
这是因为:
K3s 默认集成了 ServiceLB(以前叫 Klipper LB)。
安装 K3s 时,这个组件已经自动安装完成。
它负责模拟云厂商 LoadBalancer 的行为。
ServiceLB 主要由哪些组件组成
整体架构如下:
+----------------+
| ServiceLB |
| Controller |
+-------+--------+
|
Watch Service(type=LoadBalancer)
|
v
自动创建 DaemonSet
|
+-------------------+-------------------+
| | |
v v v
+----------------+ +----------------+ +----------------+
| Node1 | | Node2 | | Node3 |
| svclb Pod | | svclb Pod | | svclb Pod |
| HostNetwork | | HostNetwork | | HostNetwork |
| Listen :80 | | Listen :80 | | Listen :80 |
| Listen :443 | | Listen :443 | | Listen :443 |
+--------+-------+ +--------+-------+ +--------+-------+
| | |
+---------iptables---+-------------------+
|
v
kube-proxy Service
|
EndpointSlice
|
Backend Pods
整个过程中实际上涉及四个组件:
- ServiceLB Controller
- DaemonSet
- kube-proxy
- EndpointSlice
创建 LoadBalancer Service 后发生了什么?
例如:
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- port: 80
targetPort: 80
用户提交之后,流程如下:
kubectl apply
│
▼
API Server
│
▼
Service(type=LoadBalancer)
│
▼
ServiceLB Controller
│
▼
创建 DaemonSet
│
▼
每台 Node 启动 svclb Pod
│
▼
HostNetwork 监听 80/443
│
▼
iptables 转发
│
▼
ClusterIP
│
▼
kube-proxy
│
▼
Pod
整个过程全部自动完成。
用户无需创建任何 DaemonSet。
为什么每台机器都会启动一个 Pod?
ServiceLB 创建的是:
DaemonSet
DaemonSet 的特点就是:
每一个 Node 都运行一个副本。
例如:
kubectl get ds -A
NAMESPACE NAME
kube-system svclb-nginx
对应:
Node1
└── svclb-nginx
Node2
└── svclb-nginx
Node3
└── svclb-nginx
这样意味着:
任意节点 IP 都可以访问:
http://Node1
http://Node2
http://Node3
都会进入同一个 Service。
svclb Pod 为什么能够监听宿主机 80、443?
svclb 使用:
hostNetwork: true
因此:
Pod 实际就是运行在宿主机网络空间。
例如:
Node
80
443
6443
10250
svclb 可以直接绑定:
0.0.0.0:80
0.0.0.0:443
并不是 Pod Network。
因此:
curl http://NodeIP
实际上就是访问 svclb。
流量如何进入 nginx Pod?
假设:
Node1
nginx-1
nginx-2
nginx-3
访问路径:
Browser
│
NodeIP:80
│
svclb
│
iptables
│
ClusterIP
│
kube-proxy
│
EndpointSlice
│
随机选择 Pod
│
nginx-2
注意:
svclb 不会自己实现负载均衡。
真正做负载均衡的是:
kube-proxy
kube-proxy 如何知道有哪些 Pod?
它监听:
EndpointSlice
例如:
kubectl get endpointslice
内容类似:
10.42.0.10
10.42.1.25
10.42.2.13
每一个都是 Pod IP。
kube-proxy 根据这些 Endpoint 自动生成:
- iptables
- IPVS(如果开启)
规则。
后端 Pod 如何选择?
默认情况下:
iptables 模式:
随机概率
例如:
Pod1
33%
Pod2
33%
Pod3
34%
IPVS 模式:
支持:
- rr
- lc
- wrr
- sed
- sh
等算法。
默认通常是:
Round Robin
粘性 Session(Session Affinity)如何实现?
如果配置:
sessionAffinity: ClientIP
例如:
spec:
sessionAffinity: ClientIP
则:
同一个 Client IP:
192.168.1.100
↓
永远进入
nginx-2
直到:
- Pod 消失
- 超时
- Session 失效
实现方式:
iptables/IPVS 会维护 ClientIP 与 Backend 的映射关系。
并不是 ServiceLB 自己维护。
是否支持 IP Hash?
很多用户会想到 Nginx 的:
ip_hash;
实际上:
Kubernetes Service:
没有 IP Hash 配置。
只有:
sessionAffinity=ClientIP
如果需要真正:
- consistent hash
- ip hash
- uri hash
一般放在:
- Nginx Ingress
- Traefik
- Envoy
- HAProxy
而不是 Service。
如果访问 Node2,但 Pod 在 Node3,会发生什么?
例如:
Client
│
Node2
│
svclb
│
ClusterIP
│
kube-proxy
│
Pod(Node3)
kube-proxy 会自动跨节点转发。
因此:
Node2
↓
Node3
↓
Pod
整个过程用户无感知。
如何避免跨节点转发?
很多情况下:
跨节点会多走一次 VXLAN。
例如:
Client
↓
Node1
↓
Node3
↓
Pod
如果希望:
尽量访问本地 Pod。
可以配置:
externalTrafficPolicy: Local
例如:
spec:
externalTrafficPolicy: Local
此时:
Node1
只会转发:
Node1 上面的 Pod。
如果:
Node1 没有 Pod:
直接返回:
No Endpoint
不会跨节点。
优点:
- 保留真实 Client IP
- 不经过二次 NAT
- 延迟更低
- 避免 VXLAN
externalTrafficPolicy=Cluster 与 Local 对比
| Cluster | Local |
|---|---|
| 所有节点均可访问 | 只有存在 Pod 的节点可访问 |
| 允许跨节点转发 | 禁止跨节点 |
| 可能 SNAT | 保留 Client IP |
| 负载均衡更均匀 | 效率更高 |
生产环境推荐:
如果:
Deployment 每个 Node 都有副本:
建议:
externalTrafficPolicy: Local
这样:
基本可以做到:
Client
↓
Node
↓
Local Pod
避免:
Node
↓
Overlay
↓
另一台 Node
的二次网络跳转。
查看 ServiceLB 创建的 DaemonSet
查看:
kubectl get ds -A
查看对应 Pod:
kubectl get pod -A -o wide | grep svclb
查看监听端口:
ss -lntp
查看 iptables:
iptables-save
可以观察到:
ServiceLB 主要负责:
把宿主机端口接入 Kubernetes Service。
真正决定:
- 选择哪个 Pod
- 如何负载均衡
- Session 保持
- Endpoint 更新
全部由 kube-proxy 完成。
总结
ServiceLB 可以理解为 Kubernetes 集群最外层的一层“入口适配器”。
它负责:
- 自动监听宿主机端口
- 自动创建 DaemonSet
- 接收外部流量
- 转发至 ClusterIP
而真正的 Service 负载均衡、Endpoint 管理、Pod 选择则由 kube-proxy 和 Kubernetes 控制平面完成。
因此,K3s 的 LoadBalancer 实际上是:
Client
│
NodeIP
│
ServiceLB(DaemonSet)
│
ClusterIP(Service)
│
kube-proxy
│
EndpointSlice
│
Backend Pods
理解这一层关系之后,就能够清晰定位 K3s 中 LoadBalancer、DaemonSet、Service、kube-proxy 之间各自的职责,也能在出现网络问题时快速判断问题所在。
评论