{{htmlmetatags>metatag-robots=() metatag-keywords=(k3s,ServiceLB,LoadBalancer,DaemonSet,klipper-lb,kube-proxy,iptables,ipvs) metatag-description=(详细介绍 K3s 中 ServiceLB 的工作原理,包括 LoadBalancer 创建流程、DaemonSet 工作机制、流量转发路径以及最佳实践) metatag-media-og:image=(:wiki:k3s-servicelb.jpg) metatag-og:description=(深入分析 K3s ServiceLB、DaemonSet、iptables、kube-proxy 的完整流量转发过程) metatag-og:any=(K3s LoadBalancer 原理解析、klipper-lb 工作流程、架构图与最佳实践) }} ====== 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 之间各自的职责,也能在出现网络问题时快速判断问题所在。