给 k8s nginx-ingress 添加 lvs DR 负载

背景

物理机上部署的 k8s 没有负载均衡器, 其 nginx-ingress 部署 master 节点上, 并且在 hostnetwork 上监听 80443 端口, 如果 k8s 前面没有 lvs 等负载, 那么请求只会到其中一台 nginx-ingress 上, nginx-ingress 相当于是主备模式, 单个 nginx-ingress 会成为性能瓶颈, 所以需要在 k8s 前面部署 lvs, 并且使用 DR 模式, 如图
给 k8s nginx-ingress 添加 lvs DR 负载_第1张图片

术语

CIP: 客户的 ip

DIP: lvs 上用于与后端服务器进行数据交互的 ip, lvs 修改完 MAC 地址后, 将包从该 ip 发到 lan

RIP: 后端服务器的 ip, 需要跟 DIP 在同一网段

VIP: 对外提供服务, 由 keepalived 生成的虚 ip, 一般是外网

lvs: lvs 服务器, 部署了 lvs 服务, 用于负载流量到后端真实服务器

RealServer: 后端真实服务器

DR 模式优缺点

优点

  1. 由于请求经过 lvsRealServer, 但是相应直接从 RealServer 到客户端, 不再走 lvs, 所以性能和效率都很高

缺点

  1. 不支持端口映射, 也就是 RealServervip 提供的端口必须一致, 因为 DR 模式, lvs 只修改包的 MAC 地址, 不涉及三层的修改
  2. RealServerLVS 不能在同一台机器上, 详见: http://linbo.github.io/2017/08/20/lvs-dr

备注

  1. 可以直接使用 ipvsadm 来创建 lvs 规则,但没有 vip 保证 ip 高可用,而 keepalived 本身就是 lvs 实现的,所以可以直接通过 keepalived 来实现 lvs 规则,并且有 vip
  2. NAT 模式 lvs 需要开启路由转发(echo "1" > /proc/sys/net/ipv4/ip_forward),DR 模式不需要
  3. DR 模式的每台 lvs 服务器以及 RealServer 服务器的 lo 口上都绑定有 vip, 而这么多服务器有同一个外网 vip, 如何保证客户端访问到 lvsvip, 就需要 RealServer 设置 ARP 抑制
  4. 如果 lvs 只有一个外网口, DIPRIP需要在同一网段,也就是 DIPVIPRIP 要在同一网段,也就是都要使用外网 ip,如果有两台 lvs,两台 RealServer,那么总共需要 5 个外网ip,同时因为 RealServer 有公网 ip,那么 RealServer 其实可以直接对外提供服务,但将 RealServer 暴露在外网不太安全
  5. 上面的方式太消耗外网ip,并且不安全,所以实际使用中可以只使用一个外网ip实现 DR模式,也就是 VIP 与 (DIPRIP) 使用不同网段,其中 VIP 使用外网段,DIPRIP 使用内网段,其中 lvs 要有两个网口,其中一个网口用于绑定 vip,另一个网口用于绑定内外ip,与 RealServer 的内网 ip 相通,并且设置 RealServer 的默认路由为自己的内网路由器网关,不能是 DIP,详见:(如果 RealServer 默认网关想使用 DIP,需要打补丁,太麻烦)
  6. vip 要配置在RealServerlo 口, 并且子网掩码必须是 32 位, 如果不是 32 位, 例如 24 位, lo 会相应整个 24 段的所有 ip 的请求

部署

最终环境

操作系统均为 ubuntu18.04

角色 内网ip 外网ip vip 默认网关 备注
lvs1 192.168.20.21(DIP)(接口: eno2) 119.x.x.238(VIP)(接口: eno1) 119.x.x.225
lvs2 192.168.20.22(DIP)(接口: eno2)
k8s1 192.168.20.11(RIP)(接口: enp1s0f1) 119.x.x.234(接口: enp1s0f0) 192.168.20.1 lo 上绑定 119.x.x.238(VIP)
k8s2 192.168.20.12(RIP)(接口: enp1s0f1) 119.x.x.235(接口: enp1s0f0) 192.168.20.1 lo 上绑定 119.x.x.238(VIP)
k8s3 192.168.20.13(接口: enp1s0f1) 192.168.20.11

注意: 内网网关192.168.20.1 和外网网关119.x.x.225是交换机或路由器上的同一接口

RealServer 部署待负载的服务

nginx-ingress 就是 RealServer, 待负载端口为 80443

80443 端口要么监听 VIP, 要么监听 0.0.0.0, 因为到达 RealServer 时的包目标 ipVIP

总共两台 RealServer, 主机名分别为 k8s1k8s2, RIP 分别为 192.168.20.11192.168.20.12

lvs 上部署 keepalived

有两台 lvs, 主机名分别为 lvs1lvs2, 其中 lvs1 作为 keepalivedMASTER 节点, lvs2 作为 BACKUP 节点

eno2 为内网口, eno1 为外网口, 并且其上除了将绑定的 vip 不存在其他外网 ip, vip119.x.x.238

mkdir -p /opt/keepalived

vim /opt/keepalived/keepalived.conf

lvs MASTER 节点配置

global_defs {
    router_id LVS_MASTER
    script_user root
    enable_script_security
}

vrrp_instance VI_1 {                # 设置vrrp组,唯一且同一LVS服务器组要相同
    state MASTER                    # 备份LVS服务器设置为BACKUP
    interface eno2                  # 设置发送 vrrp 包的接口, 设置为内网口, 因为外网口 eno1 上没有外网 ip, 所以无法发送 vrrp 包
    virtual_router_id 51            # 设置虚拟路由标识, 同一网段下不通 keepalived 该值不能相同
    priority 100                    # 设置优先级,数值越大,优先级越高,backup设置小于100,当master宕机后自动将backup高的变为master。
    advert_int 1                    # 设置同步时间间隔

    authentication {                # 设置验证类型和密码,master和buckup一定要设置一样
        auth_type PASS
        auth_pass 1111
    }

    virtual_ipaddress {             # 设置VIP,可以多个,每个占一行
        119.x.x.238/27 dev eno1  # 在外网口上绑定 vip, 并且指定子网掩码为 27, 否则不能绑定默认路由
    }

    notify_master "/sbin/route add default gw 119.x.x.225"  # 节点成为 master 时, 配置默认路由, 这样 vip 才能通外网
}

virtual_server 119.x.x.238 80 {
    delay_loop 6                    # 健康检查时间间隔,单位s
    lb_algo wrr                     # 负载均衡调度算法设置为加权轮叫
    lb_kind DR                      # lvs DR 模式
    #persistence_timeout 5          # 会话保持时间,单位s
    protocol TCP                    # 协议
    real_server 192.168.20.11 80 {  # 真实服务器配置,80表示端口
        weight 3                    # 权重
        TCP_CHECK {                 # 服务器检测方式设置
            connect_timeout 5       # 连接超时时间
            retry 3
            delay_before_retry 3
            connect_port 80
        }
    }
    real_server 192.168.20.12 80 {
        weight 3
        TCP_CHECK {
            connect_timeout 10
            retry 3
            delay_before_retry 3
            connect_port 80
        }
    }
}

virtual_server 119.x.x.238 443 {
    delay_loop 6
    lb_algo wrr
    lb_kind DR
    #persistence_timeout 5
    protocol TCP
    real_server 192.168.20.11 443 {
        weight 3
        TCP_CHECK {
            connect_timeout 5
            retry 3
            delay_before_retry 3
            connect_port 443
        }
    }
    real_server 192.168.20.12 443 {
        weight 3
        TCP_CHECK {
            connect_timeout 10
            retry 3
            delay_before_retry 3
            connect_port 443
        }
    }
}

lvs BACKUP 节点配置

除了 state MASTER 修改为 state BACKUP, priority 100 改为小一点的值以外, 其他配置一样, 例如:

...
vrrp_instance VI_1 {
    state BACKUP
    interface eno2
    virtual_router_id 51
    priority 50
...

在两台 lvs 上安装docker, 并启动 keepalived 容器

# 设置内核参数
vim /etc/sysctl.conf
net.ipv4.ip_nonlocal_bind=1

sysctl -p


# 启用 ipvs 内核
modprobe ip_vs
modprobe ip_vs_wrr
modprobe ip_vs_rr
modprobe ip_vs_sh

# 开机自动加载内核
cat <> /etc/modules
ip_vs
ip_vs_wrr
ip_vs_rr
ip_vs_sh
EOF

# 验证 ipvs 内核是否加载
lsmod | grep ip_vs

# 一键安装 docker
curl -sSL https://get.daocloud.io/docker | sh

docker run -d --net=host --cap-add=NET_ADMIN --name keepalived --restart always \
           -e 'KEEPALIVED_AUTOCONF=false' \
           -v /opt/keepalived/keepalived.conf:/etc/keepalived/keepalived.conf \
           -v /var/run/docker.sock:/var/run/docker.sock \
           img.chinamye.com/arcts/keepalived:1.2.2

之后可以在 master 上验证
给 k8s nginx-ingress 添加 lvs DR 负载_第2张图片

RealServer 配置

k8s1 netplan 网络配置, 默认网关为内网网关, 所以即使配置了外网 ip 119.x.x.234 也是不通外网的, 这点后面会解释

network:
  ethernets:
          lo:
                  addresses: [119.x.x.238/32] # 必须为 32 位
          enp1s0f0:
                  addresses: [119.x.x.234/27]
          enp1s0f1:
                  addresses: [192.168.20.11/24]
                  gateway4: 192.168.20.1

  version: 2

k8s2 netplan 网络配置

network:
  ethernets:
          lo:
                  addresses: [119.x.x.238/32]
          enp1s0f0:
                  addresses: [119.x.x.235/27]
          enp1s0f1:
                  addresses: [192.168.20.12/24]
                  gateway4: 192.168.20.1

  version: 2

验证
给 k8s nginx-ingress 添加 lvs DR 负载_第3张图片
在这里插入图片描述

k8s1k8s2 设置 arp 抑制, 以及 rp_filter(后文解释)

vim /etc/sysctl.conf

net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.default.arp_ignore = 1
net.ipv4.conf.default.arp_announce = 2
net.ipv4.conf.lo.arp_ignore = 1
net.ipv4.conf.lo.arp_announce = 2
net.ipv4.conf.all.rp_filter = 2

sysctl -p

这样之后, lvs DR 模式就生效的, 在任意 CIP 上测试, 能正常返回内容
给 k8s nginx-ingress 添加 lvs DR 负载_第4张图片

原理图

给 k8s nginx-ingress 添加 lvs DR 负载_第5张图片

  1. CIP 请求 VIP 80 端口, 到达 lvs MASTER 节点
  2. lvs MASTER 根据 lvs 规则, 将请求转发给 RealServer, 比如 k8s1, 这时候 lvs 会修改包二层目的 MACk8s1 RIPMAC, 此时数据包的源和目标 ip 地址仍然为 CIP->VIP, 但源和目标 MAC 地址为 DIP_MAC->RIP_MAC, 通过 lvs 内网口(eno2) 发出
  3. k8s1 内网口 enp1s0f1 接收到数据包以后, 由于源 IP 119.x.x.238 确实存在(lo上), 所以不会被丢弃, 但 lo 口无法和外界直接通信, 所以回复包最终会从内网口 enp1s0f1 发送到默认网关, 此时数据包的源和目标 ip 地址为 VIP->CIP, 源和目标 MAC 地址为 RIP_MAC->g 0/0/1_MAC
  4. 最终由 g 0/0/1 上的外网网关 119.x.x.225 返回给 Client

接下来解释以下问题:

Q: 为什么 RealServer 上默认网关得设置成内网网关, 而不是外网网关?

A: 因为 RealServer 回包时, 由其内网口回包, 而内网 ip 无法直接通过外网网关访问外网, 其内网 ipCIP 肯定不在同一个段, 所以需要通过路由来转发, 这也解释了为什么内网网关和外网网关要绑定在交换机或路由器同一端口上, 所以回包时, RealServer 通过内网口发送到默认路由上, 交换机或路由器再通过外网网关发送给客户端

Q: RealServer 如何访问外网?

A: RealServer 上设置了默认路由为 192.168.20.1, 所以访问外网时, 数据包的源 ip 是内网 ip 192.168.20.11, 但是公网上无该 ip, 所以无法回包. 所以要想访问外网, 如果设备是路由器(可以使用 linux 充当路由器), 只需要在路由上配置 NAT 即可, 而如果设置是交换机, 由于交换机上无法设置 NAT, 所以我们现在 RealServer 上绑定一个外网 ip, 然后通过 RealServer 上的外网口来访问外网, 添加如下 iptables 规则

iptables -t nat -A POSTROUTING -s 192.168.20.11/32 -d 192.168.20.0/24 -j RETURN
iptables -t nat -A POSTROUTING -s 192.168.20.11/32 -d 10.233.0.0/16 -j RETURN
iptables -t nat -A POSTROUTING -s 192.168.20.11/32 -j SNAT --to-source 119.x.x.234

该规则含义为: 如果出去的包源地址是 192.168.20.11, 并且目的地址不是自身内网段, 也不是 k8s pod 段和 service 段的包, 则将包的源地址改为 RealServer 外网口 enp1s0f0 上的外网 ip 119.x.x.234

然后设置内核参数 net.ipv4.conf.all.rp_filter = 2, 防止出外网的回包被 linux 丢弃

实际上原理就是出外网时, 将包的源 ip 由内网 192.168.20.11 改为 119.x.x.234

使用该 iptables 规则容易对现有环境造成影响, 可以通过 route -n 查看应该过滤的网段, 过滤掉使用 enp1s0f1 的网段

给 k8s nginx-ingress 添加 lvs DR 负载_第6张图片
结论:

  1. 如果路由设备是路由器, 在路由器上配置 NAT 即可
  2. 可以使用 linux 来充当路由器, 然后 RealServer 的默认网关填该 linux routerip, 最后在 linux router 上配置 iptables nat 实现 RealServer 访问外网, 并且配置rp_filter = 2(该方式测试可以玩一玩, 不建议上生产环境)
  3. 如果路由设备是交换机, 通过在 RealServer 上绑定其他外网 ip, 并通过 iptables snat 实现访问外网

Q: 有没有更简单的做法?

A: VIP, DIP, RIP 均使用同一网段的外网 ip 即可. 这种情况只需要在 RealServer lo 口配置 vip 并设置 arp 抑制, 默认网关用外网网关, 但总共需要 5 个外网 ip(VIP, 两个 DIP, 两个 RIP)

而上面的方式 VIP 使用了外网段, DIPRIP 使用了内网段

如果路由设备是路由器, 总共只需要 1 个外网 ip(VIP), 也不需要配置 iptables 实现 RealServer 访问外网

如果路由设备是交换机, RealServer 不需要访问外网, 总共只需要 1 个外网 ip(VIP), 如果 RealServer 需要访问外网, 总共只需要 3 个外网 ip(VIP, 以及 k8s1k8s2 上用于访问外网的外网 ip), 而其他节点如 k8s3 要想访问外网, 通过 k8s1nat 即可, 操作如下:

  1. 设置 k8s3 默认网关为 k8s1 内网 ip 192.168.20.11
  2. k8s1 打开路由转发: net.ipv4.ip_forward = 1
  3. k8s1 上配置 SNAT: iptables -t nat -A POSTROUTING -s 192.168.20.13 -j SNAT --to-source 119.x.x.234

参考

  1. 详细剖析VS/NAT和VS/DR模式
  2. 一个公网地址部署LVS/DR模式
  3. LVS DR模式的一些问题
  4. 最详细的keepalived+lvs-dr配置文档
  5. LVS-DR+keepalived高可用群集

你可能感兴趣的:(kubernetes,运维,k8s,nginx-ingress,lvs,DR,keepalived)