IPVS 实践

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
4
ip 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
2
3
4
5
# 使用 tcpdump 捕获 b 命名空间中的数据包
ip netns exec b tcpdump -i any -n port 80

# 使用 tcpdump 捕获 c 命名空间中的数据包
ip netns exec c tcpdump -i any -n port 80

命名空间 b 中捕获的信息
可以看见 10 发往 20 的请求被转为 10 向 30 的请求
命名空间 c 中捕获的信息
而 30 在响应数据包时, 30 直接向 10 进行响应数据包而不是经过 20, 所以可以看见直接被 10 发送了 RST 连接重置的响应.

所以我们要做的就是, 让 30 响应 10 的数据包, 不是直接发往 10, 而是交由 20 进行转发处理.
那么就有以下两种解决方案:

  1. 在 b 命名空间设置 SNAT 将 10 向 30 请求的流量 SNAT 代理变为 20 向 30 请求, 待请求返回后,再使用 DNAT 转为 20 向 10 响应, 并返回给 10.
  2. 在 c 中单独设置 172.30.0.10 的路由

使用 NAT 方案

我们可以针对虚拟 ip 服务进行 NAT

1
2
3
ip 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 进行管理 NAT

1
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
3
ip 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

这样的路由网关方式可能更加适合不同网段的这样添加路由

Author: Sean
Link: https://blog.whileaway.io/posts/9b526be/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.