用来实现高可用集群中Failover时虚拟IP(VIP)的设置和切换。Netlink Reflector的所有请求最后都发送到内核空间的NETLINK模块来完成。
Keepalived的安装
环境:LVS+keepalived
前期,安装相关包:
yum -y installopenssl-devel
进行安装
[root@RServer2 soft]# wget http://www.linuxvirtualserver.org/software/kernel-2.6/ipvsadm-1.24.tar.gz
[root@RServer2 soft]# wget http://www.keepalived.org/software/keepalived-1.1.17.tar.gz
[root@RServer2 soft]#ln -s /usr/src/kernels/2.6.32-71.el6.i686/ /usr/src/linux
[root@RServer2 soft]# tar -zxvf ipvsadm-1.24.tar.gz
[root@RServer2 soft]# cd ipvsadm-1.24
[root@RServer2 ipvsadm-1.24]# make;make install
[root@RServer2 ipvsadm-1.24]# cd ..
[root@RServer2 soft]# tar -zxvf keepalived-1.1.17.tar.gz
[root@RServer2 soft]# cd keepalived-1.1.17
[root@RServer2 soft]# ./configure
[root@RServer2 keepalived-1.1.17]# make;make install
下面是keepalived详细配置文件解析:
[root@localhost kernels]# cat /etc/keepalived/keepalived.conf
! Configuration File for keepalived
global_defs {
# notification_email {
# [email protected]
# [email protected]
# [email protected]
# }
# notification_email_from [email protected]
# smtp_server 192.168.200.1
# smtp_connect_timeout 30
router_id LVS_DEVEL //负载均衡器标识,同一网段内,可以相同
}
vrrp_sync_group VGM { //定义一个vrrp组
group {
VI_1
}
}
vrrp_instance VI_1 { //定义vrrp实例
state MASTER //主LVS是MASTER,从的BACKUP
interface eth0 LVS监控的网络接口
virtual_router_id 51 //同一实例下virtual_router_id必须相同
priority 100 //定义优先级,数字越大,优先级越高
advert_int 5 //MASTER与BACKUP负载均衡器之间同步检查的时间间隔,单位是秒
authentication { //验证类型和密码
auth_type PASS
auth_pass 1111
}
virtual_ipaddress { //虚拟IP
192.168.1.8
# 192.168.1.9 //如果有多个,往下加就行了
# 192.168.1.7
}
}
virtual_server 192.168.1.8 80 { //定义虚拟服务器
delay_loop 6 //健康检查时间,单位是秒
lb_algo rr //负载调度算法,这里设置为rr,即轮询算法
lb_kind DR //LVS实现负载均衡的机制,可以有NAT、TUN和DR三个模式可选
persistence_timeout 50 //会话保持时间,单位是秒(可以适当延长时间以保持session)
protocol TCP //转发协议类型,有tcp和udp两种
sorry_server 127.0.0.1 80 //web服务器全部失败,vip指向本机80端口
real_server 192.168.1.16 80 { //定义WEB服务器
weight 1 //权重
TCP_CHECK { //通过tcpcheck判断RealServer的健康状态
connect_timeout 5 //连接超时时间
nb_get_retry 3 //重连次数
delay_before_retry 3 //重连间隔时间
connect_port 80 //检测端口
}
}
real_server 192.168.1.17 80 {
weight 1
TCP_CHECK {
connect_timeout 5
nb_get_retry 3
delay_before_retry 3
connect_port 80
}
}
}
keepalived配置文件解析系列之(三)配置文件解析过程
virHappy([email protected])
如(一)所言, keepalived在配置文件解析方面拥有非常灵活的方式, 采用关键字分层(每层的关键字数量不限,且关键字的层次也不限制)的方法进行组织一个配置文件, 且支持平行或者嵌套地include多个其它配置文件语句和正则表达式记法的配置文件名。文(二)中介绍了keepalived关键字的存储和相关的操作,下面将具体分析怎么样解析配置文件。
一、入口点及全局流程
位于parser.c文件中的init_data(char *conf_file, vector (*init_keywords) (void))函数是解析配置文件的入口点, 该函数第一个参数为需要解析的配置文件名(可以由正则表达式构成,如*.conf), 第二个参数为函数指针,指向关键字组织结构(实际取值为check_init_keywords 或者vrrp_init_keywords)。
解析配置文件过程中用到的几个全局变量包括current_keywords(当前关键字列表)、keywords(层次为0的关键字列表, 整个关键字组织的最顶层)、current_stream(当前打开的文件的流, 即文件指针, 指向当前打开的文件,因为keepalived支持嵌套的文件,所以必须设置这样的变量)、current_conf_file(当前解析的文件的名字, 同上, 是为支持嵌套的配置文件而设立的)、kw_level(当前关键字列表的层次, 处理下一层关键字列表之前递增, 回退到上一层关键字列表时递减网络推广)。
全局流程如下: 在init_data()中,调用read_conf_file(conf_file), 把要解析的配置文件的名字传递给read_conf_file()函数;read_conf_file()函数先利用glob库取得所有的conf_file(尤其是带正则表达式符号的名字), 然后依次根据 当前关键字列表current_keywords, 调用process_stream(current_keywords)进行配置文件的解析。解析 的方法是依次读取配置文件中的一行, 把该行网络转化为一个字符串数组, 再根据该字符串数组的第一个元素(即该行的第一个字符串)进行以下四种情况的处理: 1)若该字符串是井号或者感叹号(#or!) 表明这是一行注释, 不做处理, 继续读取下一行; 2)若该字符串为右大括号(”}”),且不是层次0的关键字列表, 意味着当前网络层次的关键字解析结束, 直接退出process_stream()函数; 3)若该字符串为”include”, 说明包含嵌套配置文件, 则在保存了当前的文件流和配置文件名字后,递归调用read_conf_file(conf_file)处理include进行来的配置文件, 处理结束后, 再恢复之前保存的文件流和配置文件名字, 继续原配置文件的处理; 4)若该字符串不属于上述三种情况, 那它预期是一个关键字, 则遍历当前关键字列表current_keywords, 并逐一和该字符串进行配置, 若找到了确切的关键字, 则调用 该关键字的handler()进行处理, 若该关键字还关联着下一层次的关键字列表, 则先递增关键字列表层次变量kw_level, 再递归调用process_stream(keyword->sub)处理下一层次的关键字列表, 处理结束后再递减kw_level。 若该字符串不属于于当前关键字列表中的关键字, 则忽略该行。配置文件的解析结束发生在读取到EOF, 而 一个合法的配置文件,一般都 是从层次0开始处理关键字, 中间会依次处理下一层次的关键字列表, 而最终又回归到层次0的关键字列表中。
二、函数的具体分析
根据上面的总体流程, 在整个解析 配置文件的过程中有5个关键的函数: init_data(char *conf_file, vector (*init_keywords) (void))、 read_conf_file(char *conf_file)、 process_stream(vector keywords_vec)、 read_line(char *buf, int size) 和check_include(char *buf)。下面分别对这个5个函数 进行分析。
init_data(char *conf_file, vector (*init_keywords) (void))该函数首先调用vector_alloc()分配一个关键字列表, 并令全局变量keywords指向该列表, 然后设置当前的关键字列表current_keywords为keywords, 表示从层次0的关键字列表开始解析配置文件, 接着调用read_conf_file(conf_file)进行具体的配置文件解析, 结束后,释放一开始分配的关键字列表keywords。
read_conf_file(char *conf_file)该函数首先调用glob(conf_file, 0, NULL, &globbuf)取得配置文件名字conf_file对应的所有配置文件, 然后依次取出每个配置文件进行处理。每个配置文件具体是这样处理的:先用fopen()打开该配置文件, 并保存文件流到当前文件流变量current_stream中, 并保存当前配置文件名字到current_conf_file中, 同时为了在被递归调用后可以回退到当前的配置文件继续处理, 它还保存了当前的路径到prev_path中, 然后调用process_stream(current_keywords)根据当前的关键字层次解析配置文件。直到由glob()取得的所有的配置文件解析完毕才退出read_conf_file()函数。
process_stream(vector keywords_vec)该函数以当前关键字列表为参数, 并从当前的文件流current_stream中取得配置文件中的内容, 进行相应的处理。 具体地, 先保存当前的关键字列表到prev_keywords中(同样地, 为了后面网络公司的递归调用结束后可以回退到当前的处理), 然后循环调用read_line(buf, MAXBUF)从当前 文件流中读取一行, 若读取到的是EOF, 则退出循环;否则调用alloc_strvec(buf)把读取的该行数据转化为字符串列表, 并取得该列表的第一个元素(即行首的字符串)到str中,若str是大括号(“}”), 且当前 的关键字列表层次变量kw_level大于0, 则退出循环, 结束当前层次的关键字处理; 否则依次遍历当前关键字列表, 若逐一匹配str, 若找到与str相同的关键字, 则调用该关键字的handler()处理该行数据,若该关键字存在下一层次关键字列表, 则递增关键字列表变量kw_level, 再递归调用process_stream(keyword->sub)处理下一层次的关键字列表, 处理结束后,再递减关键字列表变量kw_level。实际上, 一个process_stream()对应着一层关键字列表处理(这里要注意一点, 即同一层次的关键字列表可以有多个, 只要它们的上一层关键字不同即可)。 (可参考下面的网络流程图)
process_stream()函数流程
read_line(char *buf, int size)该函数用来从当前文件流中读取一行数据,并调用check_include(检测是否以”include”开头,若是的话,进入”include”的情况处理(下面的check_include()函数详细介绍), 且不返回该行数据给调用者, 继续读取下一行数据。若读到非”include”语句则返回该行数据(通过参数), 若遇到EOF, 则返回0( return 0)。
check_include(char *buf)该函数检查该行数据是否包含”include”, 、网络策划、若不含”include”直接返回0, 否则会进一步解析include进来的文件。 具体地, 调用alloc_strvec(buf) 把该行数据转化为字符串列表, 然后判断行首的字符串是否为”include”, 是的话, 先保存当前的文件流current_stream到prev_stream, 当前的配置文件current_conf_file到prev_conf_file,当前的路径到prev_path中, 然后递归调用read_conf_file(conf_file)函数解析include进来的配置文件, 解析结束后, 还原current_stream, current_conf_file为之前保存的值。
三、小结
keepalived在解析配置文件时充分地利用了递归, 如process_stream()函数中,会根据当前的关键字是否有下一层的关键字列表递归调用process_stream();在check_include()函数中, 会递归调用read_conf_file()去解析新的配置文件。网络营销正是这些递归的设计, 使它在解析配置文件方面显得很强大。
然而,事情并不总是完美的。 keepalived在配置文件解析方面也存在一些问题, 如没有限制配置文件的嵌套层次(考虑这样一个场景include * 或者自包含),没有对关键字进行严格检查(相应的出错提示可能不够详细)等等, 这些问题其实也可以在现有的代码框架里面进行修改增强的。