IPVS和Keepalived
关于IPVS,可以参考这个网站:http://www.austintek.com/LVS/LVS-HOWTO/
关于IPVS在内核中的实现,可以参考:IPVS模式下ClusterIP泄露宿主机端口的问题
IPVS在内核中实现了传输层负载均衡,是一个L4的交换机。IPVS在一群真实服务器的前面,运行一个LB角色的主机,该主机面向客户端,提供了单一IP地址的虚拟服务。
在Director上,LVS钩子在netfilter框架中的位置,不管是来程、回程报文,均从左侧进入、右侧出去。对于DR/TUN模式,回程报文不经过Director:
- 当Director接收到封包时,如果它的目的地址是VIP,则PREROUTING后路由决策,会导致封包被发送到LOCAL_IN。这是因为VIP是Local地址,ip_vs_in挂钩到LOCAL_IN,是VIP必须在Director上的原因,因为只有目标是本地地址,才能走到LOCAL_IN
- LVS在LOCAL_IN注册钩子ip_vs_in,需要注意的是,钩子具有优先级。最低优先级的钩子,最先看见封包。LVS的钩子的优先级,比iptables规则高,因此iptables规则首先应用到封包,然后是LVS
- 上一步中,如果ip_vs_in得到封包,它会根据负载均衡算法,选择一个RS,然后如变魔法一般,将封包直接瞬移到POSTROUTING链
- LVS不会在FORWARD中寻找入站封包,仅当NAT模式下,收到来自RS的回程报文时,LVS才和FORWARD链相关(NAT模式下回程报文的目的IP是客户端IP地址,是进不了LOCAL_IN的),封包在此unNATed(源地址从RS改为Director)。同样需要注意,LVS在任何iptables规则之后看到封包
只有fullNAT或者NAT才支持端口映射,也就是说VIP和RIP使用不同的端口。
不是标准内核的一部分。
来程报文: ClientIP: ClientPort - VIP:VPORT ⇨ IPVS Director ⇨ DirectorIP: DirectorPort - RIP:RPORT
回程报文:RIP: RPORT - DirectorIP: DirectorPort ⇨ IPVS Director ⇨ VIP:VPORT - ClientIP: ClientPort
和NAT模式相比,IPVS Director在进行DNAT的同时,还进行SNAT。这样获得以下优势:
- 客户端可以和Director、真实服务器在同一个局域网内
缺点是:
- 真实服务器看不到客户端的真实IP地址。只能由Director通过TCP Options携带真实IP,但是TCP Option只有40字节,可能客户端已经占用导致空间不足
来程报文: ClientIP: ClientPort - VIP:VPORT ⇨ IPVS Director ⇨ ClientIP: ClientPort - RIP:RPORT
回程报文:RIP: RPORT - ClientIP: ClientPort ⇨ IPVS Director ⇨ VIP:VPORT - ClientIP: ClientPort
基于NAT的虚拟服务器,当LB具有两个网络接口时:
- 一个接口分配面向外部的IP地址
- 另一个接口分配私有的、面向内部的IP地址
LB从外部接口接受流量,然后经过NAT,转发给内部网络中的真实服务器。
该模式的缺陷:
- LB是性能瓶颈,它需要转发两个方向的流量
- RS上必须进行适当的路由,确保针对ClientIP的路由转发给IPVS Director处理
- 客户端必须和RS不在同一网段。这个限制可以通过配置解决:
- 在Director上关闭ICMP重定向:
123echo 0 > /proc/sys/net/ipv4/conf/all/send_redirectsecho 0 > /proc/sys/net/ipv4/conf/default/send_redirectsecho 0 > /proc/sys/net/ipv4/conf/eth0/send_redirects - 在RS上,把Director配置为出口包的唯一网关
- 在Director上关闭ICMP重定向:
在此模式下,LB通过IP隧道将请求发送给真实服务器。
好处是可扩容,LB将请求转发给真实服务器后,真实服务器直接将响应发送给客户端,不再需要经过LB。由于大部分情况下都是请求包小、应答包大,这种模式下LB的性能压力较小。
该模式的缺陷:对网络结构依赖很大,真实服务器必须支持IP隧道协议。
用户请求LB上的VIP,LB将请求直接转发(通过修改L2封包的方式,L3保持不变)给真实服务器。
好处是可扩容,而且没有Tunneling模式的封包解包开销。
真实服务器要直接应答客户端,则应答包的源IP必须是VIP。这就导致LB和所有真实服务器都需要共享VIP+MAC的组合。不经过合理配置,则真实服务器可能直接接收到请求,绕过LB。
该模式的缺陷:
- RS和LVS的VIP,必须使用相同端口
- RS和LVS不能在同一机器上,否则该RS访问VIP不经过LVS的负载均衡,而是直接访问自己
- RS和LVS必须位于同一VLAN或局域网
简单的轮询算法,均等地对待每一台服务器,而不管服务器上实际的连接数和系统负载。
带权重的轮询算法,可以保证处理能力强的服务器处理更多的访问流量?调度器可以自动问询真实服务器的负载情况,并动态地调整其权值。
IPVS表存储了所有活动的连接,基于此信息可以将请求发送给具有最少已建立连接的RS。如果集群系统的真实服务器具有相近的系统性能,采用"最小连接"调度算法可以较好地均衡负载。
考虑权重的最少连接算法,具有较高权值的服务器将承受较大比例的活动连接负载。
通常用于缓存集群,将来自同一个目的地址的请求分配给同一台RS,如果RS满载则将这个请求分配给连接数最小的RS。
根据请求的目标IP地址找出该目标IP地址对应的服务器组,按“最小连接”原则从该服务器组中选出一台服务器,若
- 服务器没有超载, 将请求发送到该服务器
- 若服务器超载;则按“最小连接”原则从整个集群中选出一台服务器,将该服务器加入到服务器组中,将请求发送到该服务器
当该服务器组有一段时间没有增减成员,将最忙的服务器从服务器组中删除
源地址哈希调度以源地址为关键字查找一个静态hash表来获得需要的RS
目的地址哈希调度以目的地址为关键字查找一个静态hash表来获得需要的RS
如果存在空闲服务器,则请求转发给它;否则,按seq算法
最短期望延迟(Shortest Expected Delay)。期望延迟公式: (Ci + 1) / Ui ,其中Ci为服务器i的当前连接数,Ui为服务器i的固定服务速率(权重)
管理Linux虚拟服务器的底层命令行工具,可以创建、修改、查看位于Linux内核中的虚拟服务器表
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 41 42 43 44 45 46 47 48 49 |
# 添加或修改虚拟服务 ipvsadm -A|E virtual-service [-s scheduler] [-p [timeout]] [-M netmask] [-b sched-flags] # virtual-service可以是: # -t, --tcp-service service-address # -u, --udp-service service-address # 示例 ipvsadm -A -t 10.0.10.100:80 -s rr -p 600 # 删除虚拟服务 ipvsadm -D virtual-service # 清空虚拟服务表 ipvsadm -C # 从标准输入还原虚拟服务表 ipvsadm -R # 将虚拟服务表导出到标准输出 ipvsadm -S [-n] # 为虚拟服务添加、编辑一个真实服务器 # 封包转发方式: # -g --gatewaying 直接路由 # -i, --ipip 隧道(IPIP封装) # -m, --masquerading NAT ipvsadm -a|e virtual-service -r server-address [-g|i|m] [-w weight] [-x upper] [-y lower] # 示例 ipvsadm -a -t 10.0.10.100:80 -r 10.1.0.10:80 -m # 为虚拟服务删除一个真实服务器 ipvsadm -d virtual-service -r server-address # 示例 ipvsadm -d -t 10.0.10.100:80 -r 10.1.0.10:80 # 列出虚拟服务 ipvsadm -L|l [virtual-service] [options] # 清零包、字节、速率计数器 ipvsadm -Z [virtual-service] # 设置IPVS连接的超时值,三个值,分别用于TCP会话、接收到FIN后的TCP会话、UDP包 ipvsadm --set tcp tcpfin udp # 启动连接同步守护进程,state可以为master或backup。连接同步守护进程在内核中运行 # 主守护进程周期性的组播连接的变更,备份守护进程则接收组播消息并创建对应的连接。一旦主宕机,则从具有几乎全部的连接,不会出现已创建连接损坏的情况 ipvsadm --start-daemon state [--mcast-interface interface] [--syncid syncid] # 停止连接同步守护进程 ipvsadm --stop-daemon state |
选项 | 说明 |
-s alg | 负载均衡算法,即分别TCP连接或UDP报到RS的方法 |
-p [timeout] | 指示虚拟服务是否“持久的”。如果指定该选项,则同一客户端的后续请求,被转发给同一个RS |
-M netmask | 持久虚拟服务的客户端被分组的粒度,可以将某个网络的所有客户端都转发给同一RS |
-r server-address | 指定真实服务器的地址 |
-g | 使用DR模式 |
-i | 使用隧道模式,即ipip封装 |
-m | 使用NAT模式 |
-w | 指定服务器的相对权重,整数 |
-t service-address | 使用TCP服务 |
-u service-address | 使用UDP服务 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# 创建TCP虚拟服务207.175.44.110:80,使用轮询调度 ipvsadm -A -t 207.175.44.110:80 -s rr # 添加两台RS ipvsadm -a -t 207.175.44.110:80 -r 192.168.10.1:80 -m ipvsadm -a -t 207.175.44.110:80 -r 192.168.10.2:80 -m # 打印连接列表,可以看到IPVS建立的连接的状态 ipvsadm -lc # IPVS connection entries # pro expire state source virtual destination # TCP 01:19 FIN_WAIT zircon:33568 k8s:6443 neon:6443 # TCP 119:57 ESTABLISHED zircon:33570 k8s:6443 xenon:6443 # TCP 01:19 FIN_WAIT zircon:33569 k8s:6443 radon:6443 |
Keepalived提供负载均衡和高可用的框架。
负载均衡基于IP虚拟服务器(IPVS)内核模块,支持L4负载均衡。Keepalived提供了一组健康检查器,可以动态、自适应的管理上游服务器池。
高可用基于虚拟路由冗余协议(VRRP)。Keepalived实现了一系列钩子,和VRRP状态机进行底层/高层交互。
出于健壮性的考虑,Keepalived被拆分为三个进程:
- 一个最小化的父进程,负责监控子进程
- 两个子进程,分别负责VRRP、上游服务健康检查
通过包管理器安装:
1 2 3 4 5 |
# CentOS yum install keepalived # Ubuntu apt install libipset-dev apt install keepalived |
完成配置工作后,执行下面的命令启动:
1 |
systemctl restart keepalived.service |
选项 | 说明 |
f, –use-file=FILE | 指定配置文件路径 |
-P, –vrrp | 仅仅运行VRRP子系统,也就是仅仅提供HA |
-C, –check | 仅仅运行健康检查子系统,也就是仅仅提供LB |
-l, –log-console | 打印日志到控制台,默认打印到syslog |
-D, –log-detail | 记录详细日志 |
-S, –log-facility=[0-7] | 日志级别 |
-V, –dont-release-vrrp | 进程退出后,不移除VRRP虚IP和VROUTE |
-I, –dont-release-ipvs | 进程退出后,不移除IPVS拓扑 |
-n, –dont-fork | 前台运行 |
-d, –dump-conf | 导出配置文件 |
在Ubuntu 16.04上,配置文件的位置为/etc/keepalived/keepalived.conf。
1 2 3 4 5 6 7 8 9 10 11 12 |
global_defs { # 通知邮件配置 notification_email { email email } notification_email_from email smtp_server host smtp_connect_timeout num # LVS Director的名字 lvs_id string } |
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# 提示此虚拟服务器是一个FWMARK virtual_server (@IP PORT)|(fwmark num) { # 健康检查周期,秒 delay_loop num # 负载均衡算法(调度算法) lb_algo rr|wrr|lc|wlc|sh|dh|lblc # IPVS模式 lb_kind NAT|DR|TUN (nat_mask @IP) # 持久化连接,即会话亲和,让一段时间内同一客户端的连接(短)都发送到同一个RS # 持久化连接的超时 persistence_timeout num # 持久化连接的粒度掩码 persistence_granularity @IP # 用于HTTP/SSL_GET的虚拟主机名 virtualhost string # 协议类型 protocol TCP|UDP # 如果所有RS宕机,将下面的服务器加入池中 sorry_server @IP PORT # 定义一个真实服务器(RS) real_server @IP PORT { # 负载均衡权重 weight num # 通过TCP进行健康检查 TCP_CHECK { connect_port num connect_timeout num } } real_server @IP PORT { weight num # 通过脚本进行健康检查 MISC_CHECK { misc_path /path_to_script/script.sh (or misc_path “ /path_to_script/script.sh <arg_list>”) } } } real_server @IP PORT { weight num # 通过HTTP/HTTPS进行健康检查 HTTP_GET|SSL_GET { # 可以指定多个url块 url { # URL路径 path alphanum # 摘要信息 digest alphanum } # 端口 connect_port num # 超时 connect_timeout num # 重试次数 retry num # 重试延迟 delay_before_retry num } } |
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# 组 vrrp_sync_group string { group { string string } # 可以为脚本传递参数,整体用引号包围 notify_master /path_to_script/script_master.sh notify_backup /path_to_script/script_backup.sh notify_fault /path_to_script/script_fault.sh } # 默认情况下,Keepavelid只能对网络故障、Keepalived自身故障进行监控,并依此进行Master切换 # 使用vrrp_script则可以通过脚本进行自定义的(对本节点)健康检查 vrrp_script chk { script "/bin/bash -c 'curl -m1 -k -s https://127.0.0.1:6443/healthz -o/dev/null'" # 每两秒执行一次 interval 2 # 如果检测成功(脚本结果为0),且weight大于0,则当前实例的优先级升高 # 如果检测失败(脚本结果为非0),且weight小于0,则当前实例的优先级降低 weight -10 # 连续3次为0才认为成功 fall 3 # 连续1次失败则认为失败 rise 1 } # 实例 vrrp_instance string { # 实例状态 # 如果所有实例都配置为BACKUP,则初始时高优先级的成为第一个Master # 如果一个设置为MASTER其它设置为BACKUP,那么不设置nopreempt时每当Master恢复都会强占成为主 state MASTER|BACKUP # 高优先级的实例恢复正常后,不会去强占,成为Master nopreempt # 使用的网络接口 interface string # <span style="color: #404040;" data-mce-style="color: #404040;">VRRP通知的IP头上的IP地址</span> mcast_src_ip @IP # LVS sync_daemon在什么网络接口上运行 lvs_sync_daemon_interface string # 此实例所属的虚拟路由器ID virtual_router_id num # 优先级,其运行时值可以随着vrrp_script的检测结果动态变化 # 如果接收到Peer的vrrp广播包,发现自己的优先级最高,则自动切换为Master priority num # VRRP通知间隔 advert_int num # 当MASTER状态变化时进行邮件通知 smtp_alert # VRRP身份验证配置 authentication { auth_type PASS|AH auth_pass string } # 定义虚IP virtual_ipaddress { @IP @IP @IP } # 排除虚IP virtual_ipaddress_excluded { @IP @IP @IP } # 进入MASTER状态后执行的脚本 notify_master /path_to_script/script_master.sh # 进入BACKUP状态后执行的脚本 notify_backup /path_to_script/script_backup.sh # 进入FAULT状态后执行的脚本 notify_fault /path_to_script/script_fault.sh } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
global_defs { notification_email { admin@example1.com } smtp_server 127.0.0.1 [<PORT>] smtp_helo_name <HOST_NAME> smtp_connect_timeout 30 router_id my_hostname # 机器标识 vrrp_mcast_group4 224.0.0.18 # VRRP组播地址 vrrp_mcast_group6 ff02::12 default_interface eth0 # 静态地址的默认网络接口 # LVS连接同步守护进程的配置 # 监听接口 对应VRRP实例 lvs syncd的ID 最大包长度 使用的UDP端口 组播地址 lvs_sync_daemon <INTERFACE> <VRRP_INSTANCE> [id <SYNC_ID>] [maxlen <LEN>] [port <PORT>] [ttl <TTL>] [group <IP ADDR>] lvs_timeouts tcp 7200 tcpfin 120 udp 300 # LVS超时配置 lvs_flush # 启动时清空所有LVS配置 vrrp_garp_master_delay 5 # 转变为MASTER后多久发起ARP宣告 vrrp_garp_master_repeat 5 # 发起ARP宣告的次数 vrrp_garp_master_refresh 0 # ARP重新宣告的周期,默认0表示不重复宣告 vrrp_garp_master_refresh_repeat 1 # 重新宣告时发送ARP的次数 vrrp_version 2 # 使用的VRRP协议版本 } |
本示例的Keepalived版本为1.2.24。
使用DR模式时,必须保证:
- RS本机必须有网络接口,配置为VIP。DR模式下LB节点仅仅修改以太网帧的目标MAC地址,IP包不做任何修改,因此RS必须作为IP包的Destination
- RS不去响应针对VIP的ARP请求
- RS不去发送关于VIP的ARP通知
要保证上面三点,需要调整RS内核的行为。具体实施方法有几种:
1 2 |
arptables -A IN -d 10.0.10.1 -j DROP arptables -A OUT -s 10.0.10.1 -j mangle --mangle-ip-s 10.0.10.1 |
关于内核参数/proc/sys/net/ipv4/conf/*/arp_ignore:
DR模式下,每个真实服务器节点都要在环回网卡上绑定VIP。如果客户端对于VIP的ARP请求广播到了各个真实服务器节点,当:
- arp_ignore=0,则各个真实服务器节点都会响应该arp请求,此时客户端就无法正确获取LVS(LB)节点上正确的VIP所在网卡的MAC地址。客户端可能将以太网帧绕过LB直接发给RS
- arp_ignore=1,只响应目的IP地址为接收网卡上的本地地址的arp请求
关于内核参数/proc/sys/net/ipv4/conf/*/arp_announce:
每台服务器或者交换机中都有一张arp表,该表用于存储对端通信节点IP地址和MAC地址的对应关系。当
- 收到一个未知IP地址的arp请求,就会在本机的arp表中新增对端的IP和MAC记录
- 当收到一个已知IP地址(arp表中已有记录的地址)的arp请求,则会根据arp请求中的源MAC刷新自己的arp表
如果arp_announce参数配置为0,则网卡在发送arp请求时,可能选择的源IP地址并不是该网卡自身的IP地址,这时候收到该arp请求的其他节点或者交换机上的arp表中记录的该网卡IP和MAC的对应关系就不正确,可能会引发一些未知的网络问题,存在安全隐患。
DR模式下要求arp_ignore参数配置为1,arp_announce参数配置为2:
1 2 3 4 5 6 7 8 9 10 |
# 修改所有RS的内核参数 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 # 在环回网卡上绑定虚拟IP ifconfig lo:k8s 10.0.10.1 netmask 255.255.255.255 broadcast 10.0.10.1 ifconfig lo:etcd 10.0.10.1 netmask 255.255.255.255 broadcast 10.0.10.1 ifconfig lo:ceph 10.0.10.1 netmask 255.255.255.255 broadcast 10.0.10.1 |
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
global_defs { router_id oxygen # 增加LVS超时 # 可以防止TCP连接静置一段时间后服务器返回RST # 可通过ipvsadm -l --timeout确保生效 # 设置超时大于7200,因为TCP保活定时器默认2小时执行,保持行为一致 lvs_timeouts tcp 7200 tcpfin 120 udp 300 lvs_sync_daemon eth0 51 } vrrp_instance apiserver { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass gmem } virtual_ipaddress { 10.0.10.1/32 brd 10.0.10.1 dev eth0 label eth0:k8s 10.0.10.2/32 brd 10.0.10.2 dev eth0 label eth0:etcd 10.0.10.3/32 brd 10.0.10.3 dev eth0 label eth0:ceph } } virtual_server 10.0.10.1 6443 { delay_loop 5 lb_algo wlc lb_kind DR protocol TCP real_server 10.0.5.1 6443 { weight 10 SSL_GET { url { path /healthz status_code 200 } connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } real_server 10.0.2.1 6443 { weight 10 SSL_GET { url { path /healthz status_code 200 } connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } real_server 10.0.3.1 6443 { weight 10 SSL_GET { url { path /healthz status_code 200 } connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } } virtual_server 10.0.10.2 2379 { delay_loop 5 lb_algo wlc lb_kind DR protocol TCP real_server 10.0.5.1 2379 { weight 10 TCP_CHECK { connect_timeout 3 } } real_server 10.0.2.1 2379 { weight 10 TCP_CHECK { connect_timeout 3 } } real_server 10.0.3.1 2379 { weight 10 TCP_CHECK { connect_timeout 3 } } } virtual_server 10.0.10.3 6789 { delay_loop 5 lb_algo wlc lb_kind DR protocol TCP # 要启用持久化连接,必须配置 persistence_timeout 300 real_server 10.0.5.1 6789 { weight 10 TCP_CHECK { connect_timeout 3 } } real_server 10.0.2.1 6789 { weight 10 TCP_CHECK { connect_timeout 3 } } real_server 10.0.3.1 6789 { weight 10 TCP_CHECK { connect_timeout 3 } } } |
在此配置下:
- LVS节点无法访问VIP的6443端口
- RS节点可以访问VIP的6443端口,但是只会访问本机的服务,不会走负载均衡
可能原因是:
- RS没有配置虚IP
- RS上的服务没有监听虚IP所在的网络接口
Leave a Reply