IPVS 是 IP Virtual Server, 它是一个实现传输层负载均衡的 Linux 内核组件, 也称为四层 LAN 交换。 IPVS 用于在网络中的多个服务器之间分配传入网络流量。它支持TCP和UDP转发以及多种负载均衡算法。
情况模拟
先进行环境配置, 我们使用 docker almalinux 镜像作为实验环境。
首先启动环境, 需要使用特权方式启动, 因为创建 网络命名空间 需要管理员权限:1
docker run -d --privileged -p 8088:80 --name ipvs_1 -it almalinux:latest /bin/bash
安装需要的软件包1
yum install ipvsadm tcpdump python iproute conntrack telnet procps
安装完成后, 进行网络命名空间的创建1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40此处创建 三个 网络命名空间
ip netns add a
ip netns add b
ip netns add c
创建一个网桥 br0
ip link add br0 type bridge
创建网络设备以及对应的 peer 端
ip link add aa type veth peer name aa-br
ip link add bb type veth peer name bb-br
ip link add cc type veth peer name cc-br
将网络设备接入到对应的命名空间
ip link set aa netns a
ip link set bb netns b
ip link set cc netns c
将另一端接入到 网桥
ip link set aa-br master br0
ip link set bb-br master br0
ip link set cc-br master br0
启用网桥
ip link set br0 up
启用位于网桥的网卡
ip link set aa-br up
ip link set bb-br up
ip link set cc-br up
为设备设置 IP 地址信息
ip netns exec a ip addr add 172.30.0.10/24 dev aa
ip netns exec b ip addr add 172.30.0.20/24 dev bb
ip netns exec c ip addr add 172.30.0.30/24 dev cc
启用各命名空间的网卡
ip netns exec a ip link set aa up
ip netns exec b ip link set bb up
ip netns exec c ip link set cc up
检查三个命名空间之间是否能 ping 通.
到目前为止, 我们的环境准备完毕. 我们开始分配各自命名空间的作用.
- a 假设为客户端空间
- b 为 IPVS 中转空间
- c 为要被负载均衡的服务
所以我们现在需要在 c 的命名空间中启动一个服务, 我们使用 Python 快速的启动一个http 服务:1
ip netns exec c python -m http.server 80 -d .
在 b 的命名空间中使用 ipvs 进行配置虚拟服务:1
2
3
4ip netns exec b ipvsadm -A -t 172.30.0.20:80 -s rr
ip netns exec b ipvsadm -a -t 172.30.0.20:80 -r 172.30.0.30:80 -m
上方命令的回退操作
ip netns exec b ipvsadm -D -t 172.30.0.20:80
检查服务的配置是否正常:1
2检查服务的启动情况
ip netns exec b ipvsadm -ln
此时我们就可以在 a 命名空间向 b 的虚拟服务进行请求了1
ip netns exec a curl 172.30.0.20
但是 curl 出现错误, 提示不能访问, 这是为什么呢?
别担心, 我们可以使用 tcpdump 查看发生了什么
1 | 使用 tcpdump 捕获 b 命名空间中的数据包 |
可以看见 10 发往 20 的请求被转为 10 向 30 的请求
而 30 在响应数据包时, 30 直接向 10 进行响应数据包而不是经过 20, 所以可以看见直接被 10 发送了 RST 连接重置的响应.
所以我们要做的就是, 让 30 响应 10 的数据包, 不是直接发往 10, 而是交由 20 进行转发处理.
那么就有以下两种解决方案:
- 在 b 命名空间设置 SNAT 将 10 向 30 请求的流量 SNAT 代理变为 20 向 30 请求, 待请求返回后,再使用 DNAT 转为 20 向 10 响应, 并返回给 10.
- 在 c 中单独设置 172.30.0.10 的路由
使用 NAT 方案
我们可以针对虚拟 ip 服务进行 NAT1
2
3ip netns exec b iptables -t nat -A POSTROUTING -m ipvs --vaddr 172.30.0.20 -j MASQUERADE
上面命令的回退命令
ip netns exec b iptables -t nat -D POSTROUTING -m ipvs --vaddr 172.30.0.20 -j MASQUERADE
这样 当 请求从 10 发往 20 时, 将会转变为 20 请求 30
但是执行后 tcpdump 并没有发现任何变化, 这是因为还需要启用 conntrack 进行管理 NAT1
2
3
4
5
6
7
8检查当前 conntrack 启用状态
ip netns exec b sysctl -n net.ipv4.vs.conntrack
开启 conntrack
ip netns exec b sysctl -w net.ipv4.vs.conntrack=1
关闭 conntrack
ip netns exec b sysctl -w net.ipv4.vs.conntrack=0
查看 conntrack 状态
ip netns exec b ip netns exec b conntrack -L -j
可以看到 10 -> 20 的请求包被转成了 20 -> 30 请求, 并且 30 -> 20 响应了 SYN-ACK. 但是并没有看到 20 -> 10 进行响应.
这是因为当 30 -> 20 的数据包在 conntack 被转成 20 -> 10. 而这样目标地址变成了 10, 当数据包重新进入 20 的网络栈后发现并不是本机处理的数据包, 所以就进行了丢弃. 我们可以通过观察 IpInAddrErrors 计数增长进行确定1
2
3
4
5
6
7[root@98a2b714fe9d /]# ip netns exec b nstat -a
kernel
IpInReceives 461 0.0
IpInAddrErrors 63 0.0
IpForwDatagrams 96 0.0
IpInDelivers 29 0.0
IpOutRequests 418 0.0
所以我们需要开启 ip 报文转发.1
2
3
4
5
6查看当前的转发状态
ip netns exec b sysctl -n net.ipv4.ip_forward
开启 ipv4 报文转发
ip netns exec b sysctl -w net.ipv4.ip_forward=1
关闭 ipv4 报文转发
ip netns exec b sysctl -w net.ipv4.ip_forward=0
这时在 a 命名空间向 b 发送请求, 就能正常收到响应了1
ip netns exec a curl 172.30.0.20
增加路由表记录的方式
因为是增加路由表记录的方式, 需要使用 b 作为中转, 所以命名空间 b 需要设置 ip 报文转发1
2
3
4
5
6查看当前的转发状态
ip netns exec b sysctl -n net.ipv4.ip_forward
开启 ipv4 报文转发
ip netns exec b sysctl -w net.ipv4.ip_forward=1
关闭 ipv4 报文转发
ip netns exec b sysctl -w net.ipv4.ip_forward=0
可将之前测试的 iptable 设置和 contrack 设置回退
然后在 命名空间c 中设置, 10 ip 的数据报文交由 20 处理即可.1
2
3ip netns exec c ip route add 172.30.0.10 via 172.30.0.20
回退路由设置
ip netns exec c ip route del 172.30.0.10 via 172.30.0.20
这样的路由网关方式可能更加适合不同网段的这样添加路由