上篇文章我们已经 Kind 搭建了 k8s 环境,并且测试了 ClusterIP 和 NodePort 类型的 Service,这篇文章我们继续探索 k8s 中的 LoadBalancer 类型的 Service,之后我们还会简单介绍一下 k8s 的 ExternalName Service 和 Headless Service。
LoadBalancer Service
什么是 LoadBalancer Service
LoadBalancer 是 Kubernetes Service 的一种类型,用于将服务暴露给外部网络,下面简单对比了 k8s 的三种主要 Service 类型:
| 类型 | 外部访问方式 | 适用场景 |
|---|---|---|
| ClusterIP | 仅集群内部访问 | 内部服务通信 |
| NodePort | 节点 IP + 端口(30000-32767) | 测试、简单暴露 |
| LoadBalancer | 外部负载均衡器分配的 IP | 生产环境、云环境 |
当需要从外部访问 k8s 集群内的服务时,NodePort 类型的 Service 有很多 痛点:
- 单点故障风险:外部流量通常会硬编码指向某几个 Node 的 IP。如果那个特定的 Node 宕机了,用户的连接就会断开
- 缺乏真正的负载均衡:NodePort Service 的流量转发路径是:流量到任意节点,节点再转发给 Port。如果大量流量涌入同一个 Node IP,这个节点的网络带宽会成为瓶颈
- 端口难以管理:默认使用的是 30000-32767 范围内的端口,不能直接用标准的 80 或 443 端口
而 LoadBalancer 类型的 Service 则是为了解决这些问题而设计的。它具有如下优点:
- 避免节点故障影响:云厂商的负载均衡器会自动监控所有后端的节点。如果一个节点健康检查失败,负载均衡器会自动停止向该节点转发流量,只发给健康的节点。对客户端来说,它始终访问同一个 IP(负载均衡器的 IP),完全感知不到后端节点的故障。
- 提供一个稳定、统一的访问入口:客户端始终通过固定的、公开的外部 IP 地址来访问服务
- 使用标准的服务端口 (80, 443):客户端看到的就是一个标准的网站地址
当你向 Kubernetes 提交一个 type: LoadBalancer 的 Service 的 YAML 文件时,集群内部会发生以下联动:
- 分配内部资源: API Server 收到请求后,会先给这个 Service 分配一个内部的 ClusterIP,并在所有节点上随机开启一个
NodePort(比如 31080) - 云控制器介入:此时,集群中的一个核心组件 Cloud Controller Manager (CCM) 检测到了这个特殊类型的 Service。
- 调用云厂商 API:CCM 会拿着你的凭证,通过 API 去呼叫底层的云提供商(如阿里云、AWS、腾讯云):请给我创建一个公网负载均衡实例,并将它的后端挂载到我这些 Kubernetes 节点的 31080 端口上
- 回写状态: 云厂商创建完毕后,返回一个公网 IP。CCM 会把这个外部 IP 写回到你的 Service 的 EXTERNAL-IP 字段中
而当通过这个外部 IP 访问集群内的服务时:
- 流量到达云厂商的负载均衡(Cloud LB)
- LB 分发到 Node (利用 NodePort):云 LB 会根据自身的负载均衡算法(如轮询),将请求转发给集群中任意一个健康节点的宿主机 IP 和指定的 NodePort(如 Node-A-IP:31080)
- Node 内部的拦截与分发 (kube-proxy 发力):流量进入节点后,该节点上的
kube-proxy配置的 iptables/ipvs 规则会立刻拦截这个包:哦,你是访问 31080 端口的,我知道你想去哪个 Pod - 抵达目标 Pod:流量最终路由到真正运行代码的 Pod IP 上
1 | 用户 -> (访问公网IP:80) -> 云厂商负载均衡器 (如 AWS ELB, 阿里云 SLB) |
为什么需要 MetalLB?
在云环境(AWS、阿里云、GCP)中,创建 LoadBalancer Service 时,云厂商会自动创建一个外部负载均衡器(如 AWS ELB),并分配一个外部 IP。但在本地/裸金属环境(如 Kind、Minikube、自建集群)中,没有云厂商支持,LoadBalancer Service 的 EXTERNAL-IP 会一直处于 <pending> 状态。而 MetalLB 是面向裸金属 Kubernetes 集群的负载均衡器实现,基于标准路由协议,让裸金属 K8s 也能正常使用 Service: LoadBalancer 类型:
- 为裸金属环境提供 LoadBalancer 功能
- 分配虚拟 IP 给 LoadBalancer Service
- 支持 Layer2(ARP)和 BGP 模式
| 模式 | 原理 | 适用场景 |
|---|---|---|
| Layer2 | 通过 ARP 响应,让网络认为某节点拥有 LoadBalancer IP | 小规模网络、简单测试 |
| BGP | 通过 BGP 协议向路由器广播 IP | 大规模网络、生产环境 |
这篇文章我们将以 Layer2 模式来部署使用 MetalLB,如下简单介绍了 Layer2 模式工作流程:
- MetalLB 从 IP 地址池中选择一个 IP 分配给 Service
- MetalLB speaker(DaemonSet)在某节点上响应 ARP 请求
- 网络流量到达该节点
- kube-proxy 将流量转发到 Service 的后端 Pod
创建 LoadBalancer Service
首先我们看下在没有部署安装 MetalLB 时,如果创建一个 LoadBalancer 类型的 Service,会发生什么?环境仍然基于上篇文章所部署的 Kind 环境:
- 创建 service 配置文件
nginx-lb-svc.yaml,Service 的 Type 为LoadBalancer:
1 | apiVersion: v1 |
-
type: LoadBalancer:Service 类型为 LoadBalancer -
selector: app: nginx:选择标签为app: nginx的 Pod 作为后端 -
port: 80:Service 暴露的端口 -
targetPort: 80:Pod 内部应用监听的端口 -
创建服务
1 | kubectl apply -f nginx-lb-svc.yaml |
- 查看 Service 状态
1 | # kubectl get svc nginx-lb |
| 字段 | 值 | 说明 |
|---|---|---|
| NAME | nginx-lb | Service 名称 |
| TYPE | LoadBalancer | Service 类型 |
| CLUSTER-IP | 10.104.44.36 | 集群内部虚拟 IP |
| EXTERNAL-IP | <pending> |
等待分配外部 IP(无负载均衡器实现) |
| PORT(S) | 80:32617/TCP | Service 端口:NodePort 端口 |
| AGE | 4s | 创建时间 |
这里 EXTERNAL-IP: <pending> 表示没有负载均衡器来分配外部 IP。虽然 EXTERNAL-IP 为 <pending>,但 NodePort 已自动分配(32617),可通过 NodePort 方式访问。
- 查看 Service 详细信息
1 | # kubectl describe svc nginx-lb |
| 字段 | 说明 |
|---|---|
| Selector | 选择 Pod 的标签匹配规则 |
| IP/IPs | Service 的 ClusterIP |
| NodePort | 自动分配的节点端口(32617) |
| Endpoints | 后端 Pod 的 IP:端口列表 |
| Events | 事件记录(当前无事件) |
安装 MetalLB
因为我们将使用 Layer2 模式来部署 MetalLB,因此需要确保 MetalLB 的 IP 地址池必须与 Kind 集群在同一网络,否则无法通信。
实际部署
- 我们首先确认 Kind Docker 网络范围:
1 | docker network inspect kind --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}' |
- 安装 MetalLB v0.15.3
1 | kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.15.3/config/manifests/metallb-native.yaml |
- 等待 MetalLB 就绪
1 | kubectl wait --namespace metallb-system --for=condition=ready pod --selector=app=metallb --timeout=90s |
- 创建配置文件
metallb-config.yaml,配置 MetalLB IP 地址池:
1 | apiVersion: metallb.io/v1beta1 |
IPAddressPool 详解:
- 定义 MetalLB 可以分配给 LoadBalancer Service 的 IP 范围
- 每个 LoadBalancer Service 会从这个池中获取一个 IP
- 这里 IP 范围必须与集群网络在同一网段
L2Advertisement 详解:
-
Layer2 模式通过 ARP 协议让网络知道 LoadBalancer IP 在哪
-
speaker Pod 会响应 ARP 请求,告诉网络
这个 IP 在我这台节点上 -
流量到达该节点后,由 kube-proxy 转发到 Service
-
应用
metallb-config.yaml配置文件:
1 | kubectl apply -f metallb-config.yaml |
检查 metallb 状态
我们来看一下 metallb 相关的状态:
1 | # kubectl get all -n metallb-system |
1 | # kubectl get ipaddresspool,l2advertisement -n metallb-system |
1 | # kubectl describe ipaddresspool default-pool -n metallb-system |
这里,metallb-system 是 MetalLB 组件运行的 命名空间(Namespace),命名空间是 Kubernetes 中用于 隔离资源 的逻辑分区,例如 k8s 核心组件本身运行在 kube-system 命名空间,用户应用运行在 default 命名空间,而 MetalLB 组件运行在 metallb-system 命名空间。
-
controller:主要用于监听 Service,分配 IP,通过 Deployment(1个副本)运行
- 监听 Kubernetes API,发现 type=LoadBalancer 的 Service
- 如果 Service 的 EXTERNAL-IP 为空,从 IPAddressPool 选择 IP
- 更新 Service 的 EXTERNAL-IP 字段
- 记录分配状态
-
speaker:响应 ARP/BGP,宣布节点拥有 LoadBalancer IP,通过 DaemonSet(每个节点一个副本)运行。
Layer2模式工作 流程:- 监听 IP 分配事件
- 当某节点被选中,开始响应 ARP 请求
- ARP 响应告诉网络:
LoadBalancer IP 在我这台节点上 - 流量到达该节点,由 kube-proxy 转发
IPAddressPool 和 L2Advertisement 则是 MetalLB 自定义资源(Custom Resource Definition,CRD),用于配置 IP 地址池和 Layer2 通告。
检查 Service 状态
安装 MetalLB 后,之前创建的 LoadBalancer Service 会自动获得外部 IP:
1 | # kubectl get svc nginx-lb |
可以看到,MetalLB 从地址池分配为该 Service 分配了 172.19.0.100 IP 地址。
服务访问测试
接下来访问该 172.19.0.100 这个 EXTERNAL-IP IP 地址,可以看到页面正常返回:
1 | # --noproxy 关闭宿主机的代理配置 |
整个请求流程如下:
1 | 客户端请求 http://172.19.0.100/ |
我们可以看下对应的 ARP 表,验证 Layer2 模式的工作原理:
1 | # arp -an | grep 172.19.0.100 |
这里也可以看到 Layer2 模式的特点:
- 单节点响应 ARP,非真正多节点负载均衡
- 该节点故障时,speaker 会迁移到其他节点
- 适合小规模测试环境
以上我们就通过 MetalLB 部署了 Kubernetes 的 LoadBalancer 服务。接下来简单介绍下 k8s 的 ExternalName 服务 和 Headless 服务。
ExternalName 服务
ExteranlName 服务 是 Kubernetes 提供的一种特殊类型的 Service,它允许你将服务映射到集群外部的域名,这对于需要将内部服务和外部系统集成时非常有用。ExternalName 服务 不指向集群内部的任何 Pod,而是指向集群外部的一个域名。
- 它不涉及任何代理(Proxy)或转发(Forwarding),它在 DNS 层面上工作
- 当你访问这个 Service 的域名时,Kubernetes DNS 会返回一个 CNAME 记录,指向你指定的外部域名
如下是一个配置示例:
1 | kind: Service |
那为什么不直接在代码中编写外部域名呢:
- 解耦(隔离变化):代码里只需要访问集群内部域名
http://my-database,如果以后数据库迁移了(比如从阿里云切到腾讯云),你只需要修改 Service 的配置,而不需要修改并重新发布代码 - 统一管理:让外部资源在 K8s 内部像普通 Service 一样被管理
Headless 服务
Headless 服务(也称为无头服务)没有 ClusterIP。当你请求 DNS 查询这个 Service 的域名时,DNS 不会返回一个虚拟 IP,而是直接返回所有后端 Pod 的具体 IP 列表。要定义 Headless 服务,只需在 Service 的 YAML 文件中设置 clusterIP: None。
Headless 服务 的核心特点:
- 直接暴露 Pod:客户端可以绕过 Service 层的负载均衡,直接与特定的 Pod 建立连接
- DNS 记录:DNS 会为每个 Pod 生成唯一的域名,格式为:
<pod-name>.<service-name>.<namespace>.svc.cluster.local。 - 用于有状态应用:最常用于 StatefulSet(有状态应用,如 MongoDB、Redis 集群、Kafka)、客户端 LB(客户端自己决定访问哪个节点)、需要稳定的 Pod 域名
1 | 普通 Service: |
小结
这篇文章我们重点学习了 k8s 的 LoadBalancer 服务,并通过 MetalLB 在 Kind 环境中实际部署了一个 LoadBalancer 服务。我们还介绍了 k8s 的 ExternalName 和 Headless 两种特殊类型的服务,他们都有各自的应用场景。