乐趣区

关于ebpf:用eBPFXDP来替代LVS二

随着 eBPF 的倒退,咱们曾经能够将 eBPF/XDP 程序间接部署在一般服务器上来实现负载平衡,从而节俭掉用于专门部署 LVS 的机器。

前文 分享了如何应用 xdp/ebpf 替换 lvs 来实现 slb,采纳的是 slb 独立机器部署模式,并且采纳 bpftool 和硬编码配置的模式来进行加载 xdp 程序,这是 版本 0.1。

版本 0.2 在 0.1 根底上,批改为基于 bpf skeleton 的程序化加载模式,要想简略地体验下这种工作流而不改变 版本 0.1 中整体部署模式的,能够去看看 https://github.com/MageekChiu/xdp4slb/tree/dev-0.2。

版本 0.3 在 0.2 根底上,反对以配置文件和命令行参数的模式动静加载 slb 配置

本文属于 版本 0.4,反对 slb 和 application 混布的模式,去除了专用的 slb 机器。
混布模式使得一般机器也能间接做负载平衡,同时不影响利用(off load 模式下能够体现),有老本效益;另外,在路由到本地的场景下,缩小了路由跳数,整体性能更好。

创立网络环境

# 不同发行版命令不一样
systemctl start docker

docker network create south --subnet 172.19.0.0/16 --gateway 172.19.0.1

# check
docker network inspect south
# or
ip link

# 先用 ifconfig 取得刚创立的 network 应的 bridge
# 后续则能够在宿主机上抓取这个 network 的所有 IP 包
tcpdump -i br-3512959a6150 ip
# 也能够取得某个容器的 veth , 抓取这个容器进出的所有包
tcpdump -i vethf01d241  ip

原理解析

SLB 集群路由

slb 为了高可用,个别都会集群化部署,那么申请怎么路由到每一台 slb 上呢?个别由(动静)路由协定(ospf bgp)实现 ecmp,使得各个 slb 实例从 router/switch 那里平均地取得流量。
因为配置动静路由协定十分繁冗,不在本文思考范畴之内,这里采纳一个简略的脚本来模仿 ecmp。

#!/bin/bash

dst="172.19.0.10"
rs1="172.19.0.2"
rs2="172.19.0.3"
ip route del $dst/32
ip route add $dst/32 nexthop via $rs1 dev eth0 weight 1
while true; do
    nexthop=$(ip route show $dst/32 | awk '{print $3}')
    # nexthop=$(ip route show "$dst" | grep -oP "nexthop \K\S+")
    echo "to ${dst} via ${nexthop} now!"
    sleep 3
    
    # the requirements for blank is crazy!
    if ["$nexthop" = "$rs1"]; then
        new_nexthop="$rs2"
    else
        new_nexthop="$rs1"
    fi
    ip route del $dst/32
    ip route add $dst/32 nexthop via $new_nexthop dev eth0 weight 1
done

其实就是将达到 vip 的下一跳在几个 host(混布有 slb 和 app 的机器,以下简称 mix)之间重复批改即可。

NAT 模式

版本 0.1~0.3 都采纳了 full nat 模式,在以后混布的模式下不再适合,可能会导致数据包无穷循环。因为不对数据包做一些标记的话,xdp 程序无奈辨别是来自 client 的数据包还是来自另一个 slb 的包。咱们采纳 DR 模式,除了能防止循环问题以外,性能也更好,因为

  1. 回包少了一跳
  2. 包的批改少了,也不必从新计算 ip、tcp 校验和等

架构图如下,这里做了简化,client 和 mix 之间实际上是有 router/switch 的,然而咱们采纳下面的模仿脚本把 router/switch 路由性能也间接放 client 外面了。

深蓝色示意申请,浅蓝色示意响应。
vip 采纳了 ecmp,一次申请只会路由到一个 mix 上,mis 上的 slb 可能会把这个转发到本地 app(本文以 Nginx 为例)或其它 mix,然而响应肯定是从 mix 间接回去的,而不会再次通过其它 mix。

负载平衡算法

目前反对以下几种算法

  • random
  • round_roubin
  • hash

本文不在 slb 集群中同步会话状态,所以只能抉择 hash 算法,也就是不管申请路由到哪个 slb,都会被转发到同一个 backend app。

SLB 路由伪代码

if (dest_ip = local_ip){
    // 间接交给本机协定栈
    return
}
if (dest_ip = vip && dest_port = vport){
    应用负载平衡算法筛选一个 rs
    若 RS 就是本机,则 间接交给本机协定栈 并 return

    否则,将 rs 的 mac 作为新包的 dst    
    此外保留 client 和 rs 的双向映射关系
    便于后续 路由间接 应用

    将本机 mac 作为新包的 src

    将新包扔出去从新路由
}else{报错,丢包}

配置 SLB 和利用

Mix 的 Dockerfile 如下

FROM debian:bookworm
RUN apt-get update -y && apt-get upgrade -y \
    && apt install -y nginx procps bpftool iproute2 net-tools telnet kmod curl tcpdump

WORKDIR /tmp/
COPY src/slb /tmp/
COPY slb.conf /tmp/

这里批改镜像是因为我的宿主机 fedora:37 的 libc 版本是 2.36,而 debian:bullseye 对应的版本是 2.31,不能间接运行宿主机编译的可执行文件。

构建镜像并运行 app (这里是 nginx)

docker build -t mageek/mix:0.1 .

# in case you want to run a brand new container
docker rm mix1 mix2 -f

docker run -itd --name mix1 --hostname mix1 --privileged=true \
    --net south -p 8888:80 --ip 172.19.0.2 --mac-address="02:42:ac:13:00:02" \
    -v "$(pwd)"/rs1.html:/var/www/html/index.html:ro mageek/mix:0.1 nginx -g "daemon off;"

docker run -itd --name mix2 --hostname mix2 --privileged=true \
    --net south -p 9999:80 --ip 172.19.0.3 --mac-address="02:42:ac:13:00:03" \
    -v "$(pwd)"/rs2.html:/var/www/html/index.html:ro mageek/mix:0.1 nginx -g "daemon off;"

# check on host
docker ps
curl 127.0.0.1:8888
curl 127.0.0.1:9999

别离进入容器,配置 VIP,因为咱们曾经有模仿的路由协定了,所以在 mix 中配置好 vip 当前,要敞开 arp,防止影响 client 的包的路由

docker exec -it mix1 bash
docker exec -it mix2 bash

ifconfig lo:0 172.19.0.10/32 up
echo "1">/proc/sys/net/ipv4/conf/all/arp_ignore
echo "1">/proc/sys/net/ipv4/conf/lo/arp_ignore
echo "2">/proc/sys/net/ipv4/conf/all/arp_announce
echo "2">/proc/sys/net/ipv4/conf/lo/arp_announce

而后运行 slb

# 启动 slb 并指定网卡和配置文件
./slb -i eth0 -c ./slb.conf

# in another terminal 
bpftool prog list
# bpftool prog show name xdp_lb  --pretty

# check global variables
# bpftool map list
# bpftool map dump name slb_bpf.rodata

# check attaching with 
ip link

日志间接在宿主机(整机一份)上看即可,不要开好几个终端(会导致日志不残缺)

bpftool prog tracelog

此外,测试阶段能够间接在宿主机编译实现可执行文件后,拷贝到容器里 (当然,前提是你曾经创立好了这些容器和相干的网络)


docker start mix1 mix2 client

docker cp src/slb mix1:/tmp/ && \
docker cp slb.conf mix1:/tmp/ && \
docker cp src/slb mix2:/tmp/ && \
docker cp slb.conf mix2:/tmp/ && \
docker cp routing.sh client:/tmp/ 

测试

新起一个 client 容器

docker run -itd --name client --hostname client --privileged=true \
    --net south -p 10000:80 --ip 172.19.0.9 --mac-address="02:42:ac:13:00:09" \
    -v "$(pwd)"/routing.sh:/tmp/routing.sh mageek/mix:0.1 nginx -g "daemon off;"

进入 client 配置并运行以下路由脚本脚本

docker exec -it client bash

sh routing.sh

另开一个 client terminal 进行申请测试

docker exec -it client bash

# visit rs first
curl 172.19.0.2:80
curl 172.19.0.3:80

# visit slb 
curl 172.19.0.10:80
rs-1
curl 172.19.0.10:80
rs-2

咱们能够在 client 外面压测一把,然而要留神压测时不要运行 routing.sh,因为并发场景下存在 “老路由刚被删,新路由尚未建设的两头态” 的问题,导致申请失败。

apt-get install apache2-utils

# 并发 50,总申请数 5000
ab -c 50 -n 5000 http://172.19.0.10:80/

压测后果如下,可见都胜利了

erver Software:        nginx/1.22.1
Server Hostname:        172.19.0.10
Server Port:            80

Document Path:          /
Document Length:        5 bytes

Concurrency Level:      50
Time taken for tests:   3.141 seconds
Complete requests:      5000
Failed requests:        0
Total transferred:      1170000 bytes
HTML transferred:       25000 bytes
Requests per second:    1591.81 [#/sec] (mean)
Time per request:       31.411 [ms] (mean)
Time per request:       0.628 [ms] (mean, across all concurrent requests)
Transfer rate:          363.75 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0   15   3.9     15      31
Processing:     5   16   4.7     16      48
Waiting:        0   11   4.4     10      34
Total:         17   31   3.6     30      60

Percentage of the requests served within a certain time (ms)
  50%     30
  66%     32
  75%     32
  80%     33
  90%     35
  95%     37
  98%     40
  99%     47
 100%     60 (longest request)

还能够增大并发数测试,并发最大理论值是咱们存储 conntrack 条目标 back_map 数量最大值。超过这个并发数,会导致从新路由映射,可能导致 tcp reset。

下文预报

要想打造一个残缺的 slb,还有许多工作要做,比方利用内核能力进行 mac 主动寻址、许多边界查看等。这都是前面要做的工作,欢送大家一起参加 https://github.com/MageekChiu/xdp4slb/。

退出移动版