Loading... # 从零构建 eBPF/XDP 二层直连返回负载均衡器技术教程 # 一、概述 ## 1. 简介 ### A. 是什么 本教程介绍如何使用 eBPF 和 XDP(eXpress Data Path)从零开始构建一个二层(Layer 2)Direct Server Return(DSR)负载均衡器。DSR 是一种高性能负载均衡技术,允许后端服务器直接响应客户端请求,绕过负载均衡器的回程路径。 ### B. 为什么学 - 理解现代高性能负载均衡的核心原理 - 掌握 eBPF/XDP 在网络编程中的应用 - 学习 DSR 技术如何解决传统 NAT 负载均衡的性能瓶颈 - 深入理解二层网络转发机制 ### C. 学完能做什么 - 使用 eBPF/XDP 编写二层负载均衡器 - 配置虚拟 IP(VIP)实现 DSR 架构 - 理解 MAC 地址重写与 IP 转发的区别 - 部署高性能、低延迟的负载均衡解决方案 ## 2. 前置知识 ### A. 必备技能 - Linux 网络基础(IP、MAC、ARP 协议) - 基本 C 语言编程能力 - 网络命令行工具使用(ip、tcpdump、curl) ### B. 推荐知识 - eBPF 和 XDP 基础概念 - NAT 负载均衡原理 - 二层网络与三层网络的区别 # 二、背景知识 ## 1. NAT 负载均衡的局限性 在之前的教程中,我们构建了基于 NAT 的 XDP 负载均衡器。虽然这种方案可以正常工作,但存在以下问题: ### A. 资源消耗 - 负载均衡器需要处理双向流量(请求和响应) - 入站流量通常远小于出站流量 - 例如:搜索查询或 AI 提示只有几字节,但响应可能有几 KB 甚至更多 ### B. 性能瓶颈 - 负载均衡器成为网络瓶颈 - 高流量场景下资源消耗显著 ### C. 客户端信息丢失 - 后端服务器无法看到真实客户端 IP - NAT 负载均衡器重写了数据包头 - 后端无法基于源 IP 进行会话管理或日志记录 ## 2. DSR 解决方案 Direct Server Return(DSR)概念被引入以克服上述限制。DSR 有多种实现方式: - Layer 2 DSR(本教程重点) - IP-in-IP 封装 - GRE(Generic Routing Encapsulation)封装 - 基于 IP 或 TCP 头部字段的变体 # 三、网络拓扑 ## 1. 实验环境 本教程使用一个包含五个节点的网络拓扑,分布在两个不同的网络中: ``` 节点列表: - lb: 192.168.178.10/24(负载均衡器) - backend-01: 192.168.178.11/24(后端服务器 1) - backend-02: 192.168.178.12/24(后端服务器 2) - client: 10.0.0.20/16(客户端) - gateway: 192.168.178.2/24 和 10.0.0.2/16(网关) ``` ## 2. 架构图 ```mermaid graph TB C[客户端<br/>10.0.0.20/16] -->|请求| GW[网关<br/>192.168.178.2<br/>10.0.0.2] GW -->|转发| LB[负载均衡器<br/>192.168.178.10<br/>VIP: 192.168.178.15] LB -->|MAC重写| B1[后端服务器1<br/>192.168.178.11<br/>VIP on lo] LB -->|MAC重写| B2[后端服务器2<br/>192.168.178.12<br/>VIP on lo] B1 -->|直接响应| C B2 -.->|直接响应| C ```  # 四、核心概念 ## 1. DSR L2 负载均衡原理 与 NAT 负载均衡不同,DSR 保留原始客户端 IP,并允许后端服务器直接响应客户端,绕过负载均衡器的回程路径。 为了理解这是如何实现的,我们需要回答三个核心问题: 1. **后端如何看到客户端 IP?** - 数据包通过负载均衡器时,IP 头部保持不变 - 只有 MAC 地址被重写 2. **后端知道响应哪个客户端吗?** - 后端看到原始客户端 IP,可以正常响应 3. **如何确保客户端接收来自同一 IP 的响应?** - 通过虚拟 IP(VIP)实现,负载均衡器和后端共享同一 IP ## 2. 虚拟 IP(VIP) ### A. 什么是 VIP 在任何网络设置中,当客户端向特定端点发送请求时,期望响应来自相同的 IP 地址。如果响应来自不同的 IP,客户端的网络栈会认为出错并丢弃数据包。 虚拟 IP(Virtual IP,VIP)不是绑定到特定接口或节点的物理 IP,而是由多个节点共享的地址,用于处理同一服务的流量。 ### B. VIP 配置 **在负载均衡器(lb 节点)上配置 VIP:** ```bash sudo ip addr add 192.168.178.15/32 dev eth0 ``` **在后端服务器(backend-01 和 backend-02)上配置 VIP:** ```bash sudo ip addr add 192.168.178.15/32 dev lo ``` ### C. 为什么后端使用 lo 接口 两个节点配置相同的 IP 不会混淆客户端或网关吗?这正是我们将 VIP 分配到后端节点的 lo 接口的原因。我们不希望后端广播虚拟 IP(通过 ARP)。 换句话说,网络中没人应该知道 VIP 存在于后端节点上,否则客户端可能绕过负载均衡器直接连接。 ### D. ARP 配置 为防止后端节点响应 lo 接口上 VIP 地址的 ARP 请求(即广播),在每个后端节点上运行以下命令: ```bash # 仅当目标 IP 分配给接收请求的接口时才响应 ARP 请求 # (防止在 eth0 上广播 VIP) sudo sysctl -w net.ipv4.conf.eth0.arp_ignore=1 # 发送 ARP 请求时,仅使用分配给出接口的地址 # (防止节点在 ARP 中泄露 VIP 作为源 IP) sudo sysctl -w net.ipv4.conf.eth0.arp_announce=2 ``` ## 3. 二层转发原理 负载均衡器不需要知道后端节点上存在 VIP。它只需要后端的 MAC 地址在二层转发数据包。当后端接收到数据包时,它解封装并识别虚拟 IP 为自己的 IP(配置在 lo 接口上),即使该 IP 从未在网络上广播。 这也正是这个概念被称为二层 DSR 的原因——负载均衡器仅使用后端的 MAC 地址到达后端节点,如果后端位于不同的(二层)网络中,这将无法工作。 ```mermaid sequenceDiagram participant C as 客户端 participant G as 网关 participant L as 负载均衡器 participant B as 后端服务器 C->>G: 请求 VIP (192.168.178.15) G->>L: 转发到负载均衡器 Note over L: 1. 哈希选择后端<br/>2. 重写 MAC 地址<br/>3. IP 头保持不变 L->>B: 转发(MAC 重写,源 IP 保留) Note over B: 识别 VIP 为自己的地址 B->>C: 直接响应(绕过负载均衡器) ```  # 五、实现步骤 ## 1. 准备工作 ### A. 启用 IP 转发 在负载均衡器(lb 节点)上启用 IP 转发并填充 ARP 表: ```bash sudo sysctl -w net.ipv4.ip_forward=1 # ping 后端节点以填充 ARP 表(bpf_fib_lookup 需要此步骤) sudo ping -c1 192.168.178.11 # backend-01 节点/真实 IP sudo ping -c1 192.168.178.12 # backend-02 节点/真实 IP ``` ### B. 启动 HTTP 服务器 在两个后端服务器(backend-01 和 backend-02)上启动 HTTP 服务器,显式绑定到 VIP: ```bash python3 -m http.server 8000 --bind 192.168.178.15 ``` ## 2. XDP 负载均衡器代码 ### A. 核心 MAC 地址重写逻辑 在二层 DSR 负载均衡中,我们只需要在负载均衡器中更新 MAC 地址,以便数据包在二层正确传递: ```c // 使用简单哈希选择后端 struct four_tuple_t four_tuple; four_tuple.src_ip = ip->saddr; four_tuple.dst_ip = ip->daddr; four_tuple.src_port = tcp->source; four_tuple.dst_port = tcp->dest; four_tuple.protocol = IPPROTO_TCP; __u32 key = xdp_hash_tuple(&four_tuple) % NUM_BACKENDS; struct endpoint *backend = bpf_map_lookup_elem(&backends, &key); if (!backend) { return XDP_ABORTED; } // 执行 FIB 查找 struct bpf_fib_lookup fib = {}; int rc = fib_lookup_v4_full(ctx, &fib, ip->daddr, backend->ip, bpf_ntohs(ip->tot_len)); if (rc != BPF_FIB_LKUP_RET_SUCCESS) { log_fib_error(rc); return XDP_ABORTED; } // 我们只需要更新 MAC 地址 // 后端需要在 lo 接口上有虚拟 IP(与负载均衡器相同) // 源 IP 保留为客户端 IP,因此后端将直接响应客户端 __builtin_memcpy(eth->h_source, fib.smac, ETH_ALEN); __builtin_memcpy(eth->h_dest, fib.dmac, ETH_ALEN); ``` ### B. 关键要点 1. **无需连接跟踪** - 负载均衡器不需要维护任何连接跟踪状态(与 NAT 负载均衡不同) - 简单地基于 MAC 地址重定向数据包 2. **保留 IP 头部** - 负载均衡器甚至不接触 IP 头部 - 后端仍接收原始客户端源 IP - 后端可以直接回复客户端,绕过负载均衡器 3. **FIB 查找** - 通过 bpf_fib_lookup 获取 MAC 地址 - 使用 fib_lookup_v4_full 执行完整查找 ## 3. 编译和运行 ### A. 编译负载均衡器 ```bash cd lab go generate go build ``` ### B. 运行负载均衡器 ```bash sudo ./lb -i eth0 --backends 192.168.178.11,192.168.178.12 ``` **参数说明:** - `-i eth0`: 指定网络接口 - `--backends`: 后端服务器真实 IP 列表(逗号分隔) ### C. 测试负载均衡 从客户端节点查询 VIP: ```bash curl http://192.168.178.15:8000 ``` ### D. 验证流量 在后端服务器上查看 HTTP 服务器日志,确认请求确实来自客户端 IP: ``` 10.0.0.20 - - [01/Jan/2026 16:03:45] "GET / HTTP/1.1" 200 - ``` ### E. 抓包验证 MAC 地址 在两个后端服务器上运行 tcpdump 查看 MAC 地址: ```bash sudo tcpdump -i eth0 -n -t -e -q tcp port 8000 ``` **预期输出(简化版):** ``` LB_MAC > BACKEND_MAC, IPv4, length 74: CLIENT_IP/PORT > BACKEND_IP/PORT: tcp 0 # 负载均衡器 -> 后端(保留客户端源 IP) BACKEND_MAC > GATEWAY_MAC, IPv4, length 74: BACKEND_IP/PORT > CLIENT_IP/PORT: tcp 0 # 后端 -> 网关(客户端 IP 为目标地址) ``` ### F. 查看负载均衡器日志 ```bash sudo bpftool prog trace ``` # 六、技术限制 ## 1. 二层网络要求 DSR L2 负载均衡仅在负载均衡器和后端服务器位于同一子网(即具有直接二层连接)时才能正常工作。 如果负载均衡器尝试将流量重定向到不同二层网络中的后端: 1. 负载均衡器会将目标 MAC 地址设置为网关接口(试图退出当前二层网络) 2. 由于只有负载均衡器上的 VIP 在网络上广播 3. 网关会将数据包发回负载均衡器——因为这是它知道的该 VIP 的唯一路由 4. 导致环路 ```mermaid graph TB LB[负载均衡器] -->|目标MAC=网关| GW[网关] GW -->|VIP路由回LB| LB LB -.环路.-> GW style LB fill:#f9f,stroke:#333,stroke-width:2px style GW fill:#ff9,stroke:#333,stroke-width:2px ```  ## 2. 架构约束 - 要求后端服务器与负载均衡器共享同一网络 - 增加整体故障风险——网络故障导致全部服务不可用 ## 3. 解决方案 这正是 IPIP DSR 负载均衡发挥作用的地方,将在后续教程中介绍。 # 七、总结 ## 1. 关键要点 ### A. DSR 优势 - 后端服务器直接响应客户端,绕过负载均衡器 - 保留原始客户端 IP,便于会话管理和日志记录 - 减轻负载均衡器负担,提高整体性能 ### B. 二层 DSR 实现原理 - 仅重写 MAC 地址,IP 头部保持不变 - 通过虚拟 IP(VIP)实现 IP 一致性 - 后端在 lo 接口配置 VIP,不参与 ARP 广播 ### C. 技术限制 - 负载均衡器与后端必须在同一二层网络 - 跨网络场景需要使用 IPIP 或 GRE 等封装技术 ## 2. 下一步学习 - IPIP DSR 负载均衡(跨网络场景) - GRE DSR 负载均衡 - 高级哈希算法与一致性哈希 - 健康检查与故障转移 *** ## 参考资料 1. [Building an eBPF/XDP L2 Direct Server Return Load Balancer from Scratch](https://labs.iximiuz.com/tutorials/xdp-dsr-layer2-lb-92b02f3e) 最后修改:2026 年 01 月 22 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏