Neutron L3 HA使用多台物理机(agent)作网络节点,可以将每一个router(router是和subnet相关联的)调度到多个agent上,再由keepalived实现的路由冗余协议vrrp为每一个router上关联的子网网关port及外网网关port提供active/passive的弹性以太网服务。
代码正在评审之中:
https://blueprints.launchpad.net/neutron/+spec/l3-high-availability
为了review这个patch(https://review.openstack.org/#/c/70700/)的代码,不得不花时间再次熟悉keeplived + ip contrack + VRRP技术.
VRRP是用来解决静态路由的网关的单点故障问题的. VRRP的VIP(IP + MAC)对外表现都是相同的, 虚拟路由器(由1-255范围之内的VRID和一组IP地址组成,对外表现一个周知的MAC地址:00-00-5E-00-01-{VRID})通过多播(224.0.0.18)传输协议报文实现竞选master路由器,在一个VRRP中,只有master路由器会一直发送VRRP广告包(既然是广播包,那么backup也能收到,即backup知道master是否活着,backup不会抢点master,除非它的优先级更高),当master不可用时(backup收不到广播包), 多台backup中的优先级最高的这台就会抢占为master. 这种抢占是非常快速的(<1s),以保证服务的连续性.
(CONNTRACK),顾名思义,就是跟踪并且记录连接状态。Linux为每一个经过网络堆栈的数据包,生成一个新的连接记录项(Connection entry)。此后,所有属于此连接的数据包都被唯一地分配给这个连接,并标识连接的状态。连接跟踪是防火墙模块的状态检测的基础,同时也是地址转换中实 现SNAT和DNAT的前提。发送机往接受机发数据时,路过中间的防火墙机会在内核创建contrack表项,注意: 创建表项会有一个时间即NEW时间,如果把这个时间设置过短,且对于恶劣的网络防火墙将发送机的请求转给接收机再回到防火墙机的过程中时间就过长超过了这个NEW时间的话,防火墙才会认为这个一个establish状态的连接,否则被视为NEW状态的连接,这样,如果有FORWARD/INPUT -m state -state ESTABLISH -j ACCEPT防火墙规则的话就会将NEW状态的包丢弃, 所以随意设置ip_conntrack_tcp_timeout_synsent值过小可能造成无法完成TCP三次握手.同时,只有ip_conntrack_tcp_timeout_established设置的establish状态过期之后,conntrack才会被释放内存,毕竟内核内存是有限制的,这样就产生了conntrack容量太小或者表项保留时间过长引发的conntrack full问题. 宝贵的内核内存资源肯定不可能随便浪费,实际上,并不是所有流量都是需要被跟踪的, 可见,必要时同时采取三种方式比较有效:1.增大conntrack_max(net.ipv4.ip_conntrack_max = 655360);2.减少状态保存时间(net.ipv4.netfilter.ip_conntrack_tcp_timeout_established = 180);3.分离无关流量。然而除了第三种方式,其余两种方式在操作时必须给自己十足的理由那么做才行,对于1,比必须明白内核内存被占有的方式
iptables -t raw -A PREROUTING ! –I $网卡 -j NOTRACK
iptables –t raw –A OUTPUT –j NOTRACK
iptables -A FORWARD -m state --state UNTRACKED -j ACCEPT
Linux的NAT基于ip_conntrack。iptables设置的nat规则仅仅对一个流的第一个数据包有效。对于设有keepalive的TCP连接而言,试想服务器和客户端在establish状态之后5天内都没有互相通信,5天后的一天,服务器主动发送了一个数据包给客户端,然而此时防火墙/NAT设备上的conntrack状态已经过期被删除,此时该数据包将会被认为是NEW状态的数据包,被DROP,客户端永远收不到这个数据包,进而也不会发送ACK,服务器端不断重发,不断被防火墙DROP,当重发次数达到一定次数后,服务器RESET该连接,然而客户端如何得知,只有客户端主动发包才能打破这个僵局,然而谁能保证客户端一定会主动发包?这是不是Linux的ip_conntrack的一种缺陷,设计5天时间的establish状态是不是一种极限措施,然而谁又能保证5天内两端不断通信呢? 对于UDP包,由于它本身没有状态,无需建立连接,无需确认,纯粹就是一个数据报协议,因此对它无影响.
数据结构:
1, 在router表中添加了ha_vr_id字段
2,增加表ha_router_agent_port_bindings (port_id, router_id, l3_agent_id, priority)
3, 添加表ha_router_networks (id, tenant_id, network_id)
router下关联subnet, HARouterScheduler将router bind到多个l3-agent上, 每个l3-agent会定期的从DB中发现添加到它的router, 然后调用process_ha_router_added方法添加ha port,并在它之上设置vip, 最后设置keeplived配置文件实现l3-agent这个stateful的active/active负载均衡.每个l3-agent上对每个子网(如10.0.1.0/24)都会对应的, 会生成(l3_ha_net_cidr=172.31.255.0/24)网段的router_ha_interface端口(keepalived支持-f参数指定不同的配置文件/opt/stack/data/neutron/ha_confs/<router id>/keepalived.conf 以支持namespace)用于通过conntractd来同步tcp seesion(同步tcp session的目的猜测可能是因为路过一个l3-agent的tcp包在切换master到其它l3-agent上后不至于丢包重发,理解不一定对).
子网内部网关ip, 外部网关ip, 浮动ip等三项会作为vip在不同的l3-agent之间通过keeplived的virtual_ipaddress+virtual_routes的两大配置进行vip的飘移:
virtual_ipaddress {
% if EXTERNAL_PORT:
${EXTERNAL_PORT['ip_cidr']} dev ${L3_AGENT.get_external_device_name(EXTERNAL_PORT['id'])}
% if FLOATING_IPS:
${FLOATING_IPS[0]['floating_ip_address']}/32 dev ${L3_AGENT.get_external_device_name(EXTERNAL_PORT['id'])}
% endif
% endif
% if INTERNAL_PORTS:
${INTERNAL_PORTS[0]['ip_cidr']} dev ${L3_AGENT.get_internal_device_name(INTERNAL_PORTS[0]['id'])}
% endif
}
% if EXTERNAL_PORT:
virtual_routes {
0.0.0.0/0 via ${EXTERNAL_PORT['ip_cidr'].split('/')[0]} dev ${L3_AGENT.get_external_device_name(EXTERNAL_PORT['id'])}
}
% endif
代码实现:
l3-ha-agent在sync router时,会生成专有的device_owner=network:router_ha_interface的port.类似于:
外部网关, neutron router-gateway-set $ROUTER_ID $EXT_NET_ID (内部实现时会创建一个port, port的属性中有device_owner=network:router_gateway,device_id=router_id, 同时也会分配一个公网IP)
子网网关, neutron router-interface-add $ROUTER_ID $SUBNET_ID (内部实现时会创建一个port, port的属性为device_owner=network:router_interface,device_id=router_id,fixed_ip={'ip_address': subnet['gateway_ip'], 'subnet_id': subnet['id']},且分配一个fixed_ip.
一个实验用的keepalived配置文件:
vrrp_instance master_ins {
state BACKUP
nopreempt
interface wlp3s0
virtual_router_id 1
priority 250
advert_int 2
virtual_ipaddress {
192.168.99.122/24
}
}
virtual_routes {
0.0.0.0/0 via 192.168.99.0 dev lo
}
vrrp_instance backup_ins {
state BACKUP
nopreempt
interface wlp2s6
virtual_router_id 1
priority 249
advert_int 2
virtual_ipaddress {
192.168.99.122/24
}
}
参考:
1, Keepalived权威指南中文
2, 再次深入到ip_conntrack的conntrack full问题, http://blog.csdn.net/dog250/article/details/7262619
3, 解決Linux NAT ip_conntrack: table full的方法, http://www.linuxdiyf.com/viewarticle.php?id=58421
4, How to test L3 HA VRRP, https://docs.google.com/document/d/1P2OnlKAGMeSZTbGENNAKOse6B2TRXJ8keUMVvtUCUSM/edit?pli=1#heading=h.a4976nu3qtel
5, http://zeldor.biz/2010/12/activepassive-cluster-with-pacemaker-corosync/
6, http://www.cnblogs.com/sammyliu/p/4692081.html