Kubernetes

部署 IPVS 高可用软 LB 集群

部署 IPVS 高可用软 LB 集群

ipvs (IP Virtual Server) 是基于 Netfilter 的,作为 linux 内核的一部分实现了传输层负载均衡,ipvs 集成在LVS(Linux Virtual Server)中,它在主机中运行,并在真实服务器集群前充当负载均衡器。ipvs 可以将对 TCP/UDP 服务的请求转发给后端的真实服务器,因此 ipvs 天然支持 Kubernetes Service。ipvs 也包含了多种不同的负载均衡算法,例如轮询、最短期望延迟、最少连接以及各种哈希方法等,ipvs 的设计就是用来为大规模服务进行负载均衡的。

ipvs 有三种负载均衡方式,分别为:

  • NAT --- ipvs 节点相当于路由器(网关角色)。
  • TUN
  • DR

1. 下载安装

1.1 在线安装

sudo yum install -y ipvsadm ipset sysstat conntrack libseccomp
# 或
sudo apt install -y ipvsadm ipset sysstat conntrack libseccomp2

1.2 离线安装

1.2.1 基于源码编译离线安装

cd /tmp
wget http://www.linuxvirtualserver.org/software/kernel-2.6/ipvsadm-1.26.tar.gz
tar -zxvf ipvsadm-*.tar.gz
cd ipvsadm-*
# 建立编译时必须的一个软链接
ln -s /usr/src/kernels/2.6.9-42.EL-i686/ /usr/src/linux
# 编译安装
make && make install
# 检查安装成功
whereis ipvsadm

1.2.2 拷贝二进制离线安装

  • 首先检查 kernel 中 ipvs 模块文件,建议使用 CentOS 7+、Ubuntu 18 + 或同族系统版本,因为他们的 kernel 版本默认内置 ipvs 模块。
sudo ls -al /usr/lib/modules/*/kernel/net/netfilter/ipvs/
  • 找一台可连接公网的相同OS版本的机器,使用 yum/apt 安装,然后拷贝二进制文件: /sbin/ipvsadm、/sbin/ipvsadm-restore、/sbin/ipvsadm-save,丢到目标机器的 /sbin 目录。

  • 加载及配置

# 先查看 ip_vs 模块加载情况
sudo lsmod | grep ip_vs

# 使用 ipvsadm 查看转发规则,如果是首次未加载,执行此命令时会加载)
sudo ipvsadm
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn

# 或使用 modprobe 加载
sudo modprobe ip_vs

# 再次查看会显示已加载 ip_vs 模块
sudo lsmod | grep ip_vs
ip_vs_sh               16384  0
ip_vs_wrr              16384  19
ip_vs_rr               16384  0
ip_vs                 155648  25 ip_vs_rr,ip_vs_sh,ip_vs_wrr
nf_conntrack          139264  5 xt_conntrack,nf_nat,nf_conntrack_netlink,xt_MASQUERADE,ip_vs
nf_defrag_ipv6         24576  2 nf_conntrack,ip_vs
libcrc32c              16384  3 nf_conntrack,nf_nat,ip_vs

2. 配置 IPVS

  • 集群网络拓扑
Host eth1 (绑公网IP) eth0 (绑内网IP) Role Services
vm1 10.0.0.200 (模拟公网VIP) 10.0.0.11 (DIP) DS1 ipvs / keepalived
vm2 10.0.0.200 (模拟公网VIP) 10.0.0.12 (DIP) DS2 ipvs / keepalived
vm3 - 10.0.0.13 (RIP) RS1 http.server
vm4 - 10.0.0.14 (RIP) RS2 http.server
local - 10.0.0.114 宿主机 -
  • 说明 1:

    • 以上是使用 vmwarevirtualbox 模拟请求(客户端 -> VIP -> RIP) 过程的网络拓扑,推荐将如上eth0 使用Bridge模式(用的物理主机网卡,可与物理局域网其他物理机互通),目的是模拟外网IP要让外部客户端能访问,eth1 网卡使用Host-Only模式(其实就是NAT模式的特殊情况,只是没创建虚拟NAT设备让其不与外界互通),目的是模拟内网服务器不与外部互通。关于虚拟网桥、网卡、NAT设备理解请参考: https://blogs.wl4g.com/archives/2568
  • 说明 2:

    • VIP,即 Virtual IP,Director 用于向客户端计算机提供服务的 IP 地址,如: example.com 就要解析到此IP。
    • DIP,即 Director IP,Director 用于连接内网络的 IP 地址,绑定在另一张网卡上。DS 同理。
    • RIP,即 Real IP,Director 转发请求数据包到后端服务的实际 IP 地址。RS 同理。
    • CIP,即 Client IP,客户端 IP,请求数据包中的源 IP。

2.1 配置 LVS-NAT 模式

通过网络地址转换,调度器重写请求报文的目标地址,根据预设的调度算法,将请求分派给后端的真实服务器;真实服务器的响应报文通过调度器时,报文的源地址被重写,再返回给客户,完成整个负载调度过程。

DIP / RIP 必须在同一 LAN。

ipvs-nat1.jpg由于墙可移步查看

  • 在 vm1、vm2 上操作
# 先清空规则
sudo ipvsadm -C

# 必须开启转发报文到别的主机,为演示简单这里使用临时生效
echo 1 > /proc/sys/net/ipv4/ip_forward

# 给 Director 绑定 VIP(实测发现其实只使用一张网卡 eth0 分别绑定 DIP/VIP 2个IP也可以)
sudo ip addr add 10.0.0.200 dev eth1

# 添加一个 DR,并指定调度算法为 wrr(加权轮询),可选: rr 、wrr 、lc 、wlc 、lblc 、lblcr 、dh 、sh 、sed 、nq
sudo ipvsadm -A -t 10.0.0.200:8080 -s wrr
sudo ipvsadm -a -t 10.0.0.200:8080 -r 10.0.0.13 -m # 添加转发到 RealServer1 的规则
sudo ipvsadm -a -t 10.0.0.200:8080 -r 10.0.0.14 -m # 添加转发到 RealServer2 的规则

# 查看转发规则 (-n 表示不解析,类同 route -n 和 iptables -L -n)
sudo ipvsadm -Ln
  • 在 vm3、vm4 上操作
# 必须将 eth0 的网关设置为 DIP(与 DR 模式相反),这也是 NAT 模式精华所在
sudo sed -i -r 's/^GATEWAY(.*)=.*/GATEWAY=10.0.0.11/g' /etc/sysconfig/network-scripts/ifcfg-eth0

# 重启
sudo systemctl restart network

# 如果是 ubuntu18+ 则是修改: /etc/netplan/01-network-manager-all.yaml
# 重启: sudo netplan --debug apply

其中 -m 即: --masquerading NAT 模式

# vm3 上执行
mkdir -p /tmp/test/
touch /tmp/test/my-vm3.txt
python3 -m http.server 8080 --directory /tmp/test/

# vm4 上执行
mkdir -p /tmp/test/
touch /tmp/test/my-vm4.txt
python3 -m http.server 8080 --directory /tmp/test/
  • 在物理机(10.0.0.114)上访问 VIP,模拟外网客户端请求
for i in `seq 1 10`; do curl -s 10.0.0.200:8080 | grep txt; done
<li><a href="vm3.txt">vm3.txt</a></li>
<li><a href="vm4.txt">vm4.txt</a></li>
<li><a href="vm3.txt">vm3.txt</a></li>
<li><a href="vm4.txt">vm4.txt</a></li>
...
  • 查看 IPVS 当前调度状态
sudo ipvsadm -lnc

2.2 配置 LVS-DR 模式

VS/DR通过改写请求报文的MAC地址,将请求发送到真实服务器,而真实服务器将响应直接返回给客户。同VS/TUN技术一样,VS/DR技术可极大地 提高集群系统的伸缩性。这种方法没有IP隧道的开销,对集群中的真实服务器也没有必须支持IP隧道协议的要求,但是要求调度器与真实服务器都有一块网卡连 在同一物理网段上。

注:与 NAT 模式相反,必须不能设置 RS 的 gateway 为 DIP。
本节实验条件为:VIP / DIP / RIP 在同一 LAN,这样比较简单,无需额外路由配置(不同网段也可以,只需加对路由即可)。

ipvs-dr1.jpg由于墙可移步查看

  • 在 vm1、vm2 上操作
# 先清空规则
sudo ipvsadm -C

# 必须开启转发报文到别的主机,为演示简单这里使用临时生效
echo 1 > /proc/sys/net/ipv4/ip_forward

# 给 Director 绑定 VIP(实测发现其实只使用一张网卡 eth0 分别绑定 DIP/VIP 2个IP也可以)
sudo ip addr add 10.0.0.200 dev eth1

# 添加一个 DR,并指定调度算法为 wrr(加权轮询),可选: rr 、wrr 、lc 、wlc 、lblc 、lblcr 、dh 、sh 、sed 、nq
sudo ipvsadm -A -t 10.0.0.200:8080 -s wrr
sudo ipvsadm -a -t 10.0.0.200:8080 -r 10.0.0.13 -g # 添加转发到 RealServer1 的规则
sudo ipvsadm -a -t 10.0.0.200:8080 -r 10.0.0.14 -g # 添加转发到 RealServer2 的规则

# 查看转发规则 (-n 表示不解析,类同 route -n 和 iptables -L -n)
sudo ipvsadm -Ln
  • 在 vm3、vm4 上操作
# 首先,必须要还原网关配置(与 NAT 模式相反)
sudo sed -i -r 's/^GATEWAY(.*)=.*/GATEWAY=10.0.0.1/g' /etc/sysconfig/network-scripts/ifcfg-eth0
sudo systemctl restart network

# 必须禁用 arp 响应
# 原因:路由表根据 arp 表项,会将新来的请求报文转发给RealServer,导致Director的VIP失效,使系统不使用IP包的源地址来设置 arp 请求的源地址,而选择发送接口的IP地址
sudo echo "1" > /proc/sys/net/ipv4/conf/lo/arp_ignore
sudo echo "1" > /proc/sys/net/ipv4/conf/all/arp_ignore
sudo echo "2" > /proc/sys/net/ipv4/conf/lo/arp_announce
sudo echo "2" > /proc/sys/net/ipv4/conf/all/arp_announce

# 必须绑定 VIP 到 lo,这是为了让回包时的 SIP = VIP,这就是 DR 模式的精华所在
sudo ip addr add 10.0.0.200 dev lo

# 必须让 RS 回包走 lo 接口,这样才能使 SIP = VIP,不设置默认也能这样走包,可选???
sudo ip route add 10.0.0.200 dev lo
  • 准备测试 (分别在 vm3、vm4 启动测试 http.server)
# vm3 上执行
mkdir -p /tmp/test/
touch /tmp/test/my-vm3.txt
python3 -m http.server 8080 --directory /tmp/test/

# vm4 上执行
mkdir -p /tmp/test/
touch /tmp/test/my-vm4.txt
python3 -m http.server 8080 --directory /tmp/test/
  • 在物理机(10.0.0.114)上访问 VIP,模拟外网客户端请求
for i in `seq 1 10`; do curl -s 10.0.0.200:8080 | grep txt; done
<li><a href="vm3.txt">vm3.txt</a></li>
<li><a href="vm4.txt">vm4.txt</a></li>
<li><a href="vm3.txt">vm3.txt</a></li>
<li><a href="vm4.txt">vm4.txt</a></li>
...

其中 -g 即: --gatewaying direct routing 模式

注: 实测发现,只能在 Director 机以外的其他机器上请求 VIP 才通? 分析参见:#5.1

  • 查看 IPVS 当前调度状态
sudo ipvsadm -lnc

3. 备份还原

# 备份规则
sudo ipvsadm -S > ipvs.rule.bak1

# 还原规则
sudo ipvsadm -R < ipvs.rule.bak1

4. 部署 keepalived (实现 HA)

注: 虽然可以使用 keepalived 自动选举 ipvs 的 active 节点 (绑定 VIP) 来实现 HA,其相当于 k8s 中 metalLB 的 Layer2 模式,即主备模式,也即集中式负载均衡架构,始终只有一个节点转发流量并非真正意义的 HA,更优雅的做法应该是如 metalLB 的 BGP 模式,即分布式负载均衡架构,它可从外部网络 (客户端网络或互联网) 最早就可将数据包分流到各地域的服务器网关,相关技术有 AnyCast BGP。

5. FAQ

5.1 在 Director 机上请求 VIP 不通卡死?

  • 现象:实测发现,不能在 Director 机上请求 VIP (即在 10.0.0.11 上执行 curl -v 10.0.0.200) 会卡死。

  • 分析:分别在 Director 和 RS 上执行 tcpdump -i any dst 10.0.0.200 or dst 10.0.0.200 捕获会发现,请求已经到达了 RS ,但是响应没有成功,初步分析应该是跟 arp 有关,也可以侧证通过在 Director 机上执行 arping 10.0.0.200 和在 RS 上执行,前者会 Timeout 但后者能响应,响应的 MAC 就是 Director 的地址。

5.2 实测使用 tcpdump 抓包原理分析(DR模式)

  • 在物理机(10.0.0.114)抓包
jameswrong@jameswrong-pro:~$ sudo tcpdump -i any dst port 8080 or src port 8080

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
13:16:00.883659 IP 10.0.0.114.37644 > 10.0.0.200.http-alt: Flags [S], seq 2149029805, win 64240, options [mss 1460,sackOK,TS val 1594908454 ecr 0,nop,wscale 7], length 0
13:16:00.884602 IP 10.0.0.200.http-alt > 10.0.0.114.37644: Flags [S.], seq 4132810452, ack 2149029806, win 65160, options [mss 1460,sackOK,TS val 11697075 ecr 1594908454,nop,wscale 7], length 0
13:16:00.884714 IP 10.0.0.114.37644 > 10.0.0.200.http-alt: Flags [.], ack 1, win 502, options [nop,nop,TS val 1594908455 ecr 11697075], length 0
13:16:00.884881 IP 10.0.0.114.37644 > 10.0.0.200.http-alt: Flags [P.], seq 1:83, ack 1, win 502, options [nop,nop,TS val 1594908455 ecr 11697075], length 82: HTTP: GET / HTTP/1.1
13:16:00.885377 IP 10.0.0.200.http-alt > 10.0.0.114.37644: Flags [.], ack 83, win 509, options [nop,nop,TS val 11697076 ecr 1594908455], length 0
13:16:00.889646 IP 10.0.0.200.http-alt > 10.0.0.114.37644: Flags [P.], seq 1:186, ack 83, win 509, options [nop,nop,TS val 11697080 ecr 1594908455], length 185: HTTP: HTTP/1.0 404 File not found
13:16:00.889686 IP 10.0.0.114.37644 > 10.0.0.200.http-alt: Flags [.], ack 186, win 501, options [nop,nop,TS val 1594908460 ecr 11697080], length 0
13:16:00.889861 IP 10.0.0.200.http-alt > 10.0.0.114.37644: Flags [FP.], seq 186:655, ack 83, win 509, options [nop,nop,TS val 11697080 ecr 1594908455], length 469: HTTP
  • 在 DS2 (10.0.0.12) 上同一时刻抓包输出如下
root@lvs-ds2:~# sudo tcpdump -i any dst port 8080 or src port 8080

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
13:16:00.845812 IP 10.0.0.114.37644 > lvs-ds2.http-alt: Flags [S], seq 2149029805, win 64240, options [mss 1460,sackOK,TS val 1594908454 ecr 0,nop,wscale 7], length 0
13:16:00.845907 IP 10.0.0.114.37644 > lvs-ds2.http-alt: Flags [S], seq 2149029805, win 64240, options [mss 1460,sackOK,TS val 1594908454 ecr 0,nop,wscale 7], length 0
13:16:00.846756 IP 10.0.0.114.37644 > lvs-ds2.http-alt: Flags [.], ack 4132810453, win 502, options [nop,nop,TS val 1594908455 ecr 11697075], length 0
13:16:00.846757 IP 10.0.0.114.37644 > lvs-ds2.http-alt: Flags [P.], seq 0:82, ack 1, win 502, options [nop,nop,TS val 1594908455 ecr 11697075], length 82: HTTP: GET / HTTP/1.1
13:16:00.846810 IP 10.0.0.114.37644 > lvs-ds2.http-alt: Flags [.], ack 1, win 502, options [nop,nop,TS val 1594908455 ecr 11697075], length 0
13:16:00.846851 IP 10.0.0.114.37644 > lvs-ds2.http-alt: Flags [P.], seq 0:82, ack 1, win 502, options [nop,nop,TS val 1594908455 ecr 11697075], length 82: HTTP: GET / HTTP/1.1
13:16:00.851738 IP 10.0.0.114.37644 > lvs-ds2.http-alt: Flags [.], ack 186, win 501, options [nop,nop,TS val 1594908460 ecr 11697080], length 0
13:16:00.851799 IP 10.0.0.114.37644 > lvs-ds2.http-alt: Flags [.], ack 186, win 501, options [nop,nop,TS val 1594908460 ecr 11697080], length 0
  • 在 RS1 (10.0.0.13) 上同一时刻抓包输出如下
root@lvs-rs1:~# sudo tcpdump -i any dst port 8080 or src port 8080

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
13:16:00.822295 IP 10.0.0.114.37644 > lvs-rs1.http-alt: Flags [S], seq 2149029805, win 64240, options [mss 1460,sackOK,TS val 1594908454 ecr 0,nop,wscale 7], length 0
13:16:00.822406 IP lvs-rs1.http-alt > 10.0.0.114.37644: Flags [S.], seq 4132810452, ack 2149029806, win 65160, options [mss 1460,sackOK,TS val 11697075 ecr 1594908454,nop,wscale 7], length 0
13:16:00.823016 IP 10.0.0.114.37644 > lvs-rs1.http-alt: Flags [.], ack 1, win 502, options [nop,nop,TS val 1594908455 ecr 11697075], length 0
13:16:00.823017 IP 10.0.0.114.37644 > lvs-rs1.http-alt: Flags [P.], seq 1:83, ack 1, win 502, options [nop,nop,TS val 1594908455 ecr 11697075], length 82: HTTP: GET / HTTP/1.1
13:16:00.823159 IP lvs-rs1.http-alt > 10.0.0.114.37644: Flags [.], ack 83, win 509, options [nop,nop,TS val 11697076 ecr 1594908455], length 0
13:16:00.827446 IP lvs-rs1.http-alt > 10.0.0.114.37644: Flags [P.], seq 1:186, ack 83, win 509, options [nop,nop,TS val 11697080 ecr 1594908455], length 185: HTTP: HTTP/1.0 404 File not found
13:16:00.827691 IP lvs-rs1.http-alt > 10.0.0.114.37644: Flags [FP.], seq 186:655, ack 83, win 509, options [nop,nop,TS val 11697080 ecr 1594908455], length 469: HTTP
13:16:00.828006 IP 10.0.0.114.37644 > lvs-rs1.http-alt: Flags [.], ack 186, win 501, options [nop,nop,TS val 1594908460 ecr 11697080], length 0

6. 参考文献

留言

您的电子邮箱地址不会被公开。