LVS-DR 原理介绍和配置实践

LVS/DR

Posted by BlueFat on Wednesday, August 8, 2018

相关术语

以下术语涉及LVS三种工作模式的原理

  • LB (Load Balancer 负载均衡)
  • HA (High Available 高可用)
  • Failover (失败切换)
  • Cluster (集群)
  • LVS (Linux Virtual Server Linux 虚拟服务器)
  • DS (Director Server),指的是前端负载均衡器节点
  • RS (Real Server),后端真实的工作服务器
  • VIP (Virtual IP),虚拟的 IP 地址,向外部直接面向用户请求,作为用户请求的目标的IP地址
  • DIP (Director IP),主要用于和内部主机通讯的 IP 地址
  • RIP (Real Server IP),后端服务器的 IP 地址
  • CIP (Client IP),访问客户端的 IP 地址

LVS 三种模式的主要区别

VS/NAT VS/TUN VS/DR
server any tunneling non-arp device
server network private LAN/WAN LAN
server number low (10~20) high high
server gateway load balancer own router own router
模式与特点 NAT 模式 IPIP 模式 DR 模式
对服务器的要求 服务节点可以使任何操作系统 必须支持 IP 隧道,目前只有 Linux 系统支持 服务器节点支持虚拟网卡设备,能够禁用设备的 ARP 响应
网络要求 拥有私有 IP 地址的局域网络 拥有合法 IP 地址的局域,网或广域网 拥有合法 IP 地址的局域,服务器节点与负载均衡器必须在同一个网段
通常支持节点数量 10 到 20 个,根据负载均衡器的处理能力而定 较高,可以支持 100 个服务节点 较高,可以支持 100 个服务节点
网关 负载均衡器为服务器节点网关 服务器的节点同自己的网关或者路由器连接,不经过负载均衡器 服务节点同自己的网关或者路由器连接,不经过负载均衡器
服务节点安全性 较好,采用内部 IP,服务节点隐蔽 较差,采用公用 IP 地址,节点安全暴露 较差,采用公用 IP 地址,节点安全暴露
IP 要求 仅需要一个合法的 IP 地址作为 VIP 地址 除了 VIPO 地址外,每个服务器界定啊需要拥有合法的 IP 地址,可以直接从路由到客户端 除了 VIP 外,每个服务节点需拥有合法的 IP 地址,可以直接从路由到客户端
特点 地址转换 封装 IP 修改 MAC 地址
配置复杂度 简单 复杂 复杂

LVS 和 Keepalived

在 lvs+keepalived 环境里面,lvs 主要的工作是提供调度算法,把客户端请求按照需求调度在 real 服务器,keepalived 主要的工作是提供 lvs 控制器的一个冗余,并且对 real 服务器做健康检查,发现不健康的 real 服务器,就把它从 lvs 集群中剔除,real 服务器只负责提供服务。

LVS-DR工作原理

11

过程:重点将请求报文的目标 MAC 地址设定为挑选出的 RS 的 MAC 地址

  1. 当用户请求到达 Director Server,此时请求的数据报文会先到内核空间的 PREROUTING 链。 此时报文的源 IP 为 CIP,目标 IP 为 VIP
  2. PREROUTING 检查发现数据包的目标 IP 是本机,将数据包送至 INPUT 链
  3. IPVS 比对数据包请求的服务是否为集群服务,若是,将请求报文中的源 MAC 地址修改为 DIP 的 MAC 地址,将目标 MAC 地址修改 RIP 的 MAC 地址,然后将数据包发至 POSTROUTING 链。 此时的源 IP 和目的 IP 均未修改,仅修改了源 MAC 地址为 DIP 的 MAC 地址,目标 MAC 地址为 RIP 的 MAC 地址
  4. 由于 DS 和 RS 在同一个网络中,所以是通过二层来传输。POSTROUTING 链检查目标 MAC 地址为 RIP 的 MAC 地址,那么此时数据包将会发至 Real Server。
  5. RS 发现请求报文的 MAC 地址是自己的 MAC 地址,就接收此报文。处理完成之后,将响应报文通过 lo 接口传送给 eth0 网卡然后向外发出。 此时的源 IP 地址为 VIP,目标 IP 为 CIP
  6. 响应报文最终送达至客户端

LVS/DR 模型的特性

  • 特点 1:保证前端路由将目标地址为 VIP 报文统统发给 Director Server,而不是 RS
  • RS 可以使用私有地址;也可以是公网地址,如果使用公网地址,此时可以通过互联网对 RIP 进行直接访问
  • RS 跟 Director Server 必须在同一个物理网络中
  • 所有的请求报文经由 Director Server,但响应报文必须不能进过 Director Server
  • 不支持地址转换,也不支持端口映射
  • RS 可以是大多数常见的操作系统
  • RS 的网关绝不允许指向 DIP(因为我们不允许他经过 director)
  • RS 上的 lo 接口配置 VIP 的 IP 地址
  • 缺陷:RS 和 DS 必须在同一机房中

DR(Direct Routing 直接路由模式)此模式时 LVS 调度器只接收客户发来的请求并将请求转发给后端服务器,后端服务器处理请求后直接把内容直接响应给客户,而不用再次经过 LVS 调度器。LVS 只需要将网络帧的 MAC 地址修改为某一台后端服务器 RS 的 MAC,该包就会被转发到相应的 RS 处理,注意此时的源 IP 和目标 IP 都没变。RS 收到 LVS 转发来的包时,链路层发现 MAC 是自己的,到上面的网络层,发现 IP 也是自己的,于是这个包被合法地接受,RS 感知不到前面有 LVS 的存在。而当 RS 返回响应时,只要直接向源 IP(即用户的 IP)返回即可,不再经过 LVS。

缺点:唯一的缺陷在于它要求 LVS 调度器及所有应用服务器在同一个网段中,因此不能实现集群的跨网段应用。
优点:可见在处理过程中 LVS Route 只处理请求的直接路由转发,所有响应结果由各个应用服务器自行处理,并对用户进行回复,网络流量将集中在 LVS 调度器之上。

LVS-DR ARP说明

DR 模型的 Realserver 禁止 ARP 响应

对于 Linux 来说,地址是属于主机的,Linux 主机在开机时会通告连接所有网络内的所有其他主机自己的所有 ip 地址和 mac 地址。 可以利用 Linux 的特性,将VIP配置在 Realserver 的本地回环接口上作为别名,并使用 arp_ignore 和 arp_annouce 内核参数。

禁止 ARP 响应的方式

arptables:红帽系类系统上提供的程序

修改内核参数: arp_ignore:定义接收到 ARP 请i去时的响应级别

  • 0:只要本地配置有响应地址,就给与响应
  • 1:仅在请求的目标地址配置请i去到达的接口上的时候,才进行响应

arp_announce:定义主机将自己的地址想外通告时的通告级别

  • 0:将本地任何接口上的任何地址向外通告
  • 1:向目标网络通告与其网络匹配的地址
  • 2:仅向本地接口上匹配的网络进行通告 添加特殊的路由条目 Linux 主机在使用某一接口发出报文时,默认会使用此接口的 IP 作为源 IP 地址。

当请求 Realserver 时,Realserver 的 VIP 是 lo 接口的别名,而 VIP 对外的通信实际需要使用的却是 eth0 接口,因此需要添加路由条目,让主机在使用 VIP 向外通信时,强制使用 lo 端口,因而它会使用 lo 端口的地址作为源 IP 进行响应,并最终由 lo 接口转发至 eth0 接口发出报文。

/sbin/route add -host $VIP dev lo:0

持久连接 添加一个集群服务为192.168.10.10:80,使用的调度算法为wrr,持久连接的保持时间是3600秒。当超过3600秒都没有请求时,则清空LVS的持久连接模板。这里我们可以在上面的策略后面加其他服务,指向其他realserver服务器 。

ipvsadm -A -t 192.168.10.10:80 -s wrr -p 3600

LVS-DR配置

DS

# Install keepalived
# Ubuntu
apt-get install keepalived ipvsadm
# CentOS
yum install keepalived ipvsadm

# update iptables
vim /etc/sysconfig/iptables

# For keepalived:
# allow vrrp 
-A INPUT -p vrrp -j ACCEPT
-A INPUT -p igmp -j ACCEPT
# allow multicast
-A INPUT -d 224.0.0.18 -j ACCEPT

# reload iptables
service iptables reload

# open ip_forward
echo "1" > /proc/sys/net/ipv4/ip_forward
# edit sysctl.conf
vi /etc/sysctl.conf
net.ipv4.ip_forward = 1

sysctl -p

# keepalived for lvs-dr
vim /etc/keepalived/keepalived.conf

vrrp_sync_group GOP {
    group {
        VI_PRI_CONNECT
        VI_PRI_AUTH
    }
}

vrrp_instance VI_PRI_CONNECT {
    state BACKUP
    interface bond0
    virtual_router_id 128
    priority 100
    advert_int 1
    nopreempt
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        10.65.32.28/23 dev bond0
    }
}

virtual_server 10.65.32.28 80 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    protocol TCP

    real_server 10.65.32.13 80 {
        weight 100
        TCP_CHECK {
                connect_timeout 3
                retry 3
                delay_before_retry 3
                connect_port 80
        }
    }
    real_server 10.65.32.14 80 {
        weight 100
        TCP_CHECK {
                connect_timeout 3
                retry 3
                delay_before_retry 3
                connect_port 80
        }
    }
}

virtual_server 10.65.32.28 443 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    protocol TCP

    real_server 10.65.32.13 443 {
        weight 100
        TCP_CHECK {
                connect_timeout 3
                retry 3
                delay_before_retry 3
                connect_port 443
        }
    }
    real_server 10.65.32.14 80 {
        weight 100
        TCP_CHECK {
                connect_timeout 3
                retry 3
                delay_before_retry 3
                connect_port 443
        }
    }
}

vrrp_instance VI_PRI_AUTH {
    state BACKUP
    interface bond0
    virtual_router_id 129
    priority 100
    advert_int 1
    nopreempt
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        10.65.32.29/23 dev bond0
    }
}

virtual_server 10.65.32.29 80 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    protocol TCP

    real_server 10.65.32.22 80 {
        weight 100
        TCP_CHECK {
                connect_timeout 3
                retry 3
                delay_before_retry 3
                connect_port 80
        }
    }
    real_server 110.65.32.23 80 {
        weight 100
        TCP_CHECK {
                connect_timeout 3
                retry 3
                delay_before_retry 3
                connect_port 80
        }
    }
}

virtual_server 10.65.32.29 443 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    protocol TCP

    real_server 10.65.32.22 443 {
        weight 100
        TCP_CHECK {
                connect_timeout 3
                retry 3
                delay_before_retry 3
                connect_port 443
        }
    }
    real_server 110.65.32.23 443 {
        weight 100
        TCP_CHECK {
                connect_timeout 3
                retry 3
                delay_before_retry 3
                connect_port 443
        }
    }
}

# enable and start keepalived
systemctl start keepalived
systemctl enable keepalived
watch ipvsadm -L -n --stats

RS

  1. Edit “/etc/sysconfig/network-scripts/ifcfg-lo” to patch bug in Centos 7 (if using Centos 7). Add TYPE=Loopback to the file.
  2. Add loopback for each Virtual IP on each worker. E.g. first virtual IP create file “/etc/sysconfig/network-scripts/ifcfg-lo:0”.
  3. Start adapters if not yet started
# add TYPE=Loopback
echo "TYPE=Loopback" >> /etc/sysconfig/network-scripts/ifcfg-lo
# add ifcfg-lo:0
cat > /etc/sysconfig/network-scripts/ifcfg-lo:0 << EOF
DEVICE=lo:0
IPADDR=10.65.32.28
NETMASK=255.255.255.255
ONBOOT=yes
EOF

# ifup lo:0
ifup lo:0

# add real_start
cat > /root/real_start.sh << EOF
#!/bin/bash
echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/lo/arp_announce
echo "1" >/proc/sys/net/ipv4/conf/all/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/all/arp_announce
EOF

# chmod 755
chmod 755 /root/real_start.sh

# add real.service
cat > /usr/lib/systemd/system/real.service << EOF
[Unit]
Description=autostart lvs real
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
ExecStart=/root/real_start.sh

[Install]
WantedBy=multi-user.target
EOF

# enable service
systemctl enable real.service


# lvs real server example
vim /root/lvs_real.sh

#!/bin/bash
### BEGIN INIT INFO
# Provides:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start realserver
# Description:       Start realserver
### END INIT INFO

# change the VIP to proper value
VIP=10.65.32.28

case "$1" in
    start)

    echo "Start REAL Server"
    /sbin/ifconfig lo:0 $VIP broadcast $VIP netmask 255.255.255.255 up
    echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore
    echo "2" >/proc/sys/net/ipv4/conf/lo/arp_announce
    echo "1" >/proc/sys/net/ipv4/conf/all/arp_ignore
    echo "2" >/proc/sys/net/ipv4/conf/all/arp_announce

    ;;

    stop)

    /sbin/ifconfig lo:0 down
    echo "Stop REAL Server"
    echo "0" >/proc/sys/net/ipv4/conf/lo/arp_ignore
    echo "0" >/proc/sys/net/ipv4/conf/lo/arp_announce
    echo "0" >/proc/sys/net/ipv4/conf/all/arp_ignore
    echo "0" >/proc/sys/net/ipv4/conf/all/arp_announce

    ;;

    restart)

    $0 stop
    $0 start

    ;;

    *)

    echo "Usage: $0 {start|stop}"
    exit 1

    ;;
esac

exit 0

配置 LVS/DR 和 LVS/TUN 混合模式

DS

# 关于 3 中模式的参数 
[packet-forwarding-method]
       -g, --gatewaying  Use gatewaying (direct routing). This is the default.
       -i, --ipip  Use ipip encapsulation (tunneling).
       -m, --masquerading  Use masquerading (network access translation, or NAT).
       Note:  Regardless of the packet-forwarding mechanism specified, real servers for addresses for which there are interfaces on the local node will  be  use  the
       local  forwarding  method, then packets for the servers will be passed to upper layer on the local node. This cannot be specified by ipvsadm, rather it set by
       the kernel as real servers are added or modified.

# ipvsadm 命令行混配 
/sbin/ifconfig tunl0 10.10.36.11 broadcast 10.10.36.11 netmask 255.255.255.255 up
/sbin/route add -host 10.10.36.11 dev tunl0
/sbin/ipvsadm -At 10.10.36.11:80 -s rr
/sbin/ipvsadm -at 10.10.36.11:80 -r 10.10.36.4:80 -g -w 1
/sbin/ipvsadm -at 10.10.36.11:80 -r 10.10.36.7:80 -i -w 1

# keepalived 混配 
vrrp_sync_group GOP {
    group {
        VI_PRI_AUTH
    }
}

vrrp_instance VI_PRI_AUTH {
    state BACKUP
    interface em1
    virtual_router_id 11
    priority 100
    advert_int 1
    nopreempt
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        10.10.36.11/23 dev em1
    }
}

virtual_server 10.10.36.11 80 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    protocol TCP

    real_server 10.10.36.4 80 {
        weight 100
        TCP_CHECK {
                connect_timeout 3
                retry 3
                delay_before_retry 3
        }
    }
}

virtual_server 10.10.36.11 80 {
    delay_loop 6
    lb_algo rr
    lb_kind TUN
    protocol TCP

    real_server 10.10.36.7 80 {
        weight 100
        TCP_CHECK {
                connect_timeout 3
                retry 3
                delay_before_retry 3
        }
    }
}

# 检查结果可用 
[root@d126027 wangao]# for i in {1..100}; do curl 10.10.36.11; sleep 0.5; done
rs2
rs1
rs2
rs1
rs2

[root@d126009 keepalived]# ipvsadm -Ln --stats
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port               Conns   InPkts  OutPkts  InBytes OutBytes
  -> RemoteAddress:Port
TCP  10.10.36.11:80                    100      700        0    36700        0
  -> 10.10.36.4:80                      50      350        0    18350        0
  -> 10.10.36.7:80                      50      350        0    18350        0

[root@d126009 wangao]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  10.10.36.11:80 rr
  -> 10.10.36.4:80                Route   100    0          0
  -> 10.10.36.7:80                Tunnel  100    0          0

RS

DR 和 TUN 的模式基本不用做改动

基于 Keepalived 的双活实践

Keepalived 官方的文档并没有给出实践案例,我对上面的代码改进之后的效果如下

  1. 实现双活,支持不中断 LVS 人工干预任意节点运行位置
  2. 实现 status 状态无变化时无告警邮件

主要差异体现在 keepalived.conf 和相关的脚本,所有节点均为 Backup,配置是一模一样的,不再解释细节了,如果不明白可以参考之前的 Keepalived 专题或者官方文档

keepalived.conf

vrrp_sync_group VI_GROUP_xxx {
    group {
        VI_PRI_xxx
        VI_PUB_xxx
    }
    notify /etc/keepalived/notify.sh
}

vrrp_sync_group VI_GROUP_xxx {
    group {
        VI_PRI_xxx
        VI_PUB_xxx
    }
    notify /etc/keepalived/notify.sh
}

vrrp_script maint-xxx {
    script "/bin/bash -c '[[ -e /etc/keepalived/xxx ]]' && exit 1 || exit 0"
    interval 2
    fall 2
    rise 2
}

vrrp_script maint-xxx {
    script "/bin/bash -c '[[ -e /etc/keepalived/xxx ]]' && exit 1 || exit 0"
    interval 2
    fall 2
    rise 2
}

vrrp_script maint-xxx {
    script "/bin/bash -c '[[ -e /etc/keepalived/xxx ]]' && exit 1 || exit 0"
    interval 2
    fall 2
    rise 2
}

vrrp_script maint-xxx {
    script "/bin/bash -c '[[ -e /etc/keepalived/xxx ]]' && exit 1 || exit 0"
    interval 2
    fall 2
    rise 2
}

vrrp_instance VI_PRI_xxx {
    state BACKUP
    interface bond0
    virtual_router_id 138
    priority 100
    advert_int 1
    nopreempt
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        xxx/23 dev bond0
    }
    track_script {
        maint-xxx
    }
}

virtual_server xxx 80 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    protocol TCP

    real_server xxx 80 {
        weight 100
        TCP_CHECK {
                connect_timeout 3
                retry 3
                delay_before_retry 3
        }
    }
    real_server xxx 80 {
        weight 100
        TCP_CHECK {
                connect_timeout 3
                retry 3
                delay_before_retry 3
        }
    }
}

vrrp_instance VI_PRI_xxx {
    state BACKUP
    interface bond0
    virtual_router_id 139
    priority 100
    advert_int 1
    nopreempt
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        xxx/23 dev bond0
    }
    track_script {
        maint-xxx
    }
}

virtual_server xxx 443 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    protocol TCP

    real_server xxx 443 {
        weight 100
        TCP_CHECK {
                connect_timeout 3
                retry 3
                delay_before_retry 3
        }
    }
    real_server xxx 443 {
        weight 100
        TCP_CHECK {
                connect_timeout 3
                retry 3
                delay_before_retry 3
        }
    }
}

vrrp_instance VI_PUB_xxx {
    state BACKUP
    interface bond1
    virtual_router_id 101
    priority 100
    advert_int 1
    nopreempt
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        xxx/26 dev bond1
    }
    track_script {
        maint-xxx
    }
}

virtual_server xxx 80 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    protocol TCP

    real_server xxx 80 {
        weight 100
        TCP_CHECK {
                connect_timeout 3
                retry 3
                delay_before_retry 3
        }
    }
    real_server xxx 80 {
        weight 100
        TCP_CHECK {
                connect_timeout 3
                retry 3
                delay_before_retry 3
        }
    }
}

vrrp_instance VI_PUB_xxx {
    state BACKUP
    interface bond1
    virtual_router_id 102
    priority 100
    advert_int 1
    nopreempt
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        xxx/26 dev bond1
    }
    track_script {
        maint-xxx
    }
}

virtual_server xxx 443 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    protocol TCP

    real_server xxx 443 {
        weight 100
        TCP_CHECK {
                connect_timeout 3
                retry 3
                delay_before_retry 3
        }
    }
    real_server xxx 443 {
        weight 100
        TCP_CHECK {
                connect_timeout 3
                retry 3
                delay_before_retry 3
        }
    }
}

notify.sh

Keepalived tasks some action depending on the VRRP state.

vrrp_sync_group VI_GROUP_xxx {
    group {
        VI_PRI_xxx
        VI_PUB_xxx
    }
    notify /etc/keepalived/notify.sh
}

The script is called after any state change with the following parameters:

$1 = “GROUP” or “INSTANCE” $2 = name of group or instance $3 = target state of transition (“MASTER”, “BACKUP”, “FAULT”)

#!/bin/bash

TYPE=$1
NAME=$2
STATE=$3
FILE="/etc/keepalived/${NAME}"

if [ ! -f "${FILE}" ]; then
  touch "${FILE}"
fi

ORI_STATE=`cat ${FILE}`

if [ ${STATE} == ${ORI_STATE} ];then
   exit 0
else
    case $STATE in
            "MASTER") /bin/python /etc/keepalived/sendmail.py ${STATE} ${TYPE} ${NAME}
                      echo "${STATE}" > "${FILE}"
                      exit 0
                      ;;
            "BACKUP") /bin/python /etc/keepalived/sendmail.py ${STATE} ${TYPE} ${NAME}
                      echo "${STATE}" > "${FILE}"
                      exit 0
                      ;;
            "FAULT")  /bin/python /etc/keepalived/sendmail.py ${STATE} ${TYPE} ${NAME}
                      echo "${STATE}" > "${FILE}"
                      exit 0
                      ;;
            *)        echo "unknown state"
                      exit 1
                      ;;
    esac
fi

sendmail.py

import sys
import socket
import smtplib


EMAIL_CONFIG = {
    'EMAIL_HOST': 'xxx',
    'EMAIL_HOST_USER': 'xxx',
    'EMAIL_RECEIVER': 'xxx'
}


def _get_private_ip():
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        sock.connect(('10.255.255.255', 1))
        return sock.getsockname()[0]
    except:
        return '127.0.0.1'
    finally:
        sock.close()


def send_email():
    ip = _get_private_ip()
    hostname = socket.gethostname()
    message = 'Subject: Keepalived Failover Alert %s \n\nHOSTNAME %s on LANIP %s %s %s status has changed to %s' % (
        sys.argv[1], hostname, ip, sys.argv[2], sys.argv[3], sys.argv[1])
    server = smtplib.SMTP(EMAIL_CONFIG["EMAIL_HOST"])
    server.sendmail(EMAIL_CONFIG['EMAIL_HOST_USER'],
                    EMAIL_CONFIG['EMAIL_RECEIVER'], message)
    server.quit()


send_email()

转载: LVS-DR 原理介绍和配置实践