LVS是内核代码(ip_vs)和用户空间控制器(ipvsadm)
neutron-lbaas-lvs底层实现机制:ipvsadm
下载ipvsadm客户端源码,ipvsad-1-26.8中有使用相关配置描述:
echo "1" > /proc/sys/net/ipv4/ip_forward
cat /proc/sys/net/ipv4/vs/amemthresh
/proc/sys/net/ipv4/vs/timeout_*
cat /proc/sys/net/ipv4/vs/snat_reroute
重要资料:
http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/LVS-HOWTO.LVS-NAT.html
http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/
http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/LVS-HOWTO.what_is_an_LVS.html
http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/LVS-HOWTO.ipvsadm.html
http://www.linuxvirtualserver.org/software/ipvs.html
http://kb.linuxvirtualserver.org/wiki/Ipvsadm#Compiling_ipvsadm
# ll /proc/net/ip_vs*
ip_vs
ip_vs_app
ip_vs_conn
ip_vs_conn_sync
ip_vs_stats
ip_vs_stats_percpu
# cat /proc/net/ip_vs
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
UDP 0A212E47:22B8 rr
-> 0A212E04:1E61 Masq 10000 0 0
# ipvsadm -S
-A -u host-10-33-46-71.openstacklocal:ddi-udp-1 -s rr
-a -u host-10-33-46-71.openstacklocal:ddi-udp-1 -r host-10-33-46-4.openstacklocal:cbt -m -w 10000
# ipvsadm -L
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
UDP host-10-33-46-71.openstacklo rr
-> host-10-33-46-4.openstackloc Masq 10000 0 0
ipvsadm通用模型:CIP<-->VIP--DIP<-->RIP
Direct Routing:直接路由
Director:负载均衡层(Loader Balancer)
RS real server :真实提供服务的计算机
VIP:Virtual IP(VIP)address:Director用来向客户端提供服务的IP地址
RIP:Real IP (RIP) address:集群节点(后台真正提供服务的服务器)所使用的IP地址
DIP:Director's IP (DIP) address:Director用来和D/RIP 进行联系的地址
CIP:Client computer's IP (CIP) address:公网IP,客户端使用的IP
ipvsadm -A|E -t|u|f service-address [-s scheduler] [-p [timeout]] [-M netmask] [--pe persistence_engine] [-b sched-flags]
LVS类型:
--gatewaying -g gatewaying (direct routing) (default)
--ipip -i ipip encapsulation (tunneling)
--masquerading -m masquerading (NAT)
--scheduler -s scheduler one of rr|wrr|lc|wlc|lblc|lblcr|dh|sh|sed|nq, the default scheduler is wlc.
1.Fixed Scheduling Method 静态调服方法
(1).RR 轮询
(2).WRR 加权轮询
(3).DH 目标地址hash
(4).SH 源地址hash
2.Dynamic Scheduling Method 动态调服方法
(1).LC 最少连接
(2).WLC 加权最少连接
(3).SED 最少期望延迟
(4).NQ 从不排队调度方法
(5).LBLC 基于本地的最少连接
(6).LBLCR 带复制的基于本地的最少连接
应用举例,典型配置
网卡配置:
Director,内网网卡,外网网卡
外网eth0 : 10.19.172.188
外网eth0:0 (vip):10.19.172.184
内网eth1 : 192.168.1.1
RS(Realserver), 内网网卡
eth0: 192.168.1.2
192.168.1.3
192.168.1.4
gateway: 192.168.1.1
配置IPVS Table脚本 :
VIP=192.168.34.41
RIP1=192.168.34.27
RIP2=192.168.34.26
GW=192.168.34.1
#清除IPVS Table
ipvsadm -C
#设置IPVS Table
ipvsadm -A -t $VIP:443 -s wlcipvsadm -a -t $VIP:443 -r $RIP1:443 -g -w 1
ipvsadm -a -t $VIP:443 -r $RIP2:443 -g -w 1
#将IPVS Table保存到/etc/sysconfig/ipvsadm/etc/rc.d/init.d/
ipvsadm save
#启动IPVSservice
ipvsadm start
#显示IPVS状态
ipvsadm -l
Director配置集群,添加RS
# ipvsadm -A -t 172.16.251.184:80 -s sh
# ipvsadm -a -t 172.16.251.184:80 -r 192.168.1.2 -m
# ipvsadm -a -t 172.16.251.184:80 -r 192.168.1.3–m
# ipvsadm -a -t 172.16.251.184:80 -r 192.168.1.4 -m
LVS优于HAProxy的一个优点是LVS支持客户端的透明性(即不要SNAT)为什么
查看IPVS详情 查看 /proc/net目录下的 ip_vs ip_vs_app ip_vs_conn ip_vs_conn_sync ip_vs_ext_stats ip_vs_stats
ipvs相关的proc文件:
/proc/net/ip_vs :IPVS的规则表
/proc/net/ip_vs_app :IPVS应用协议
/proc/net/ip_vs_conn :IPVS当前连接
/proc/net/ip_vs_stats :IPVS状态统计信息
# depmod -n|grep ipvs
kernel/net/netfilter/xt_ipvs.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs.ko.xz: kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_rr.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_wrr.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_lc.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_wlc.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_lblc.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_lblcr.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_dh.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_sh.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_sed.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_nq.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_ftp.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_nat.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_pe_sip.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack_sip.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
alias ip6t_ipvs xt_ipvs
alias ipt_ipvs xt_ipvs
# modinfo ip_vs
filename: /lib/modules/3.10.0-862.11.6.el7.x86_64/kernel/net/netfilter/ipvs/ip_vs.ko.xz
\license: GPL
retpoline: Y
rhelversion: 7.5
srcversion: 69C7A8962537C8009A78FBC
depends: nf_conntrack,libcrc32c
intree: Y
vermagic: 3.10.0-862.11.6.el7.x86_64 SMP mod_unload modversions
signer: CentOS Linux kernel signing key
sig_key: C0:02:A7:AD:CC:7C:84:36:68:A1:BC:B4:97:4C:1A:30:2D:FF:EA:35
sig_hashalgo: sha256
parm: conn_tab_bits:Set connections' hash size (int)
# modinfo iptable_nat
filename: /lib/modules/3.10.0-862.11.6.el7.x86_64/kernel/net/ipv4/netfilter/iptable_nat.ko.xz
license: GPL
retpoline: Y
rhelversion: 7.5
srcversion: 291B36E315928812DAB1A47
depends: ip_tables,nf_nat_ipv4
intree: Y
vermagic: 3.10.0-862.11.6.el7.x86_64 SMP mod_unload modversions
signer: CentOS Linux kernel signing key
sig_key: C0:02:A7:AD:CC:7C:84:36:68:A1:BC:B4:97:4C:1A:30:2D:FF:EA:35
sig_hashalgo: sha256
源码:http://www.linuxvirtualserver.org/software/ipvs.html
ipvsadm.sh:
# config: /etc/sysconfig/ipvsadm
# config: /etc/ipvsadm.rules
ipvsadm.c
int main(int argc, char **argv) { int result; if (ipvs_init()) { /* try to insmod the ip_vs module if ipvs_init failed */ if (modprobe_ipvs() || ipvs_init()) fail(2, "Can't initialize ipvs: %s\n" "Are you sure that IP Virtual Server is " "built in the kernel or as module?", ipvs_strerror(errno)); } /* warn the user if the IPVS version is out of date */ check_ipvs_version(); /* list the table if there is no other arguement */ if (argc == 1){ list_all(FMT_NONE); ipvs_close(); return 0; } /* process command line arguments */ result = process_options(argc, argv, 0); ipvs_close(); return result; }
static int parse_options(int argc, char **argv, struct ipvs_command_entry *ce, unsigned int *options, unsigned int *format) { int c, parse; poptContext context; char *optarg=NULL; struct poptOption options_table[] = { { "add-service", 'A', POPT_ARG_NONE, NULL, 'A', NULL, NULL }, { "edit-service", 'E', POPT_ARG_NONE, NULL, 'E', NULL, NULL }, { "delete-service", 'D', POPT_ARG_NONE, NULL, 'D', NULL, NULL }, { "clear", 'C', POPT_ARG_NONE, NULL, 'C', NULL, NULL }, { "list", 'L', POPT_ARG_NONE, NULL, 'L', NULL, NULL }, { "list", 'l', POPT_ARG_NONE, NULL, 'l', NULL, NULL }, { "zero", 'Z', POPT_ARG_NONE, NULL, 'Z', NULL, NULL }, { "add-server", 'a', POPT_ARG_NONE, NULL, 'a', NULL, NULL }, { "edit-server", 'e', POPT_ARG_NONE, NULL, 'e', NULL, NULL }, { "delete-server", 'd', POPT_ARG_NONE, NULL, 'd', NULL, NULL }, { "set", '\0', POPT_ARG_NONE, NULL, TAG_SET, NULL, NULL }, { "help", 'h', POPT_ARG_NONE, NULL, 'h', NULL, NULL }, { "version", 'v', POPT_ARG_NONE, NULL, 'v', NULL, NULL }, { "restore", 'R', POPT_ARG_NONE, NULL, 'R', NULL, NULL }, { "save", 'S', POPT_ARG_NONE, NULL, 'S', NULL, NULL }, { "start-daemon", '\0', POPT_ARG_STRING, &optarg, TAG_START_DAEMON, NULL, NULL }, { "stop-daemon", '\0', POPT_ARG_STRING, &optarg, TAG_STOP_DAEMON, NULL, NULL }, { "tcp-service", 't', POPT_ARG_STRING, &optarg, 't', NULL, NULL }, { "udp-service", 'u', POPT_ARG_STRING, &optarg, 'u', NULL, NULL }, { "fwmark-service", 'f', POPT_ARG_STRING, &optarg, 'f', NULL, NULL }, { "scheduler", 's', POPT_ARG_STRING, &optarg, 's', NULL, NULL }, { "persistent", 'p', POPT_ARG_STRING|POPT_ARGFLAG_OPTIONAL, &optarg, 'p', NULL, NULL }, { "netmask", 'M', POPT_ARG_STRING, &optarg, 'M', NULL, NULL }, { "real-server", 'r', POPT_ARG_STRING, &optarg, 'r', NULL, NULL }, { "masquerading", 'm', POPT_ARG_NONE, NULL, 'm', NULL, NULL }, { "ipip", 'i', POPT_ARG_NONE, NULL, 'i', NULL, NULL }, { "gatewaying", 'g', POPT_ARG_NONE, NULL, 'g', NULL, NULL }, { "weight", 'w', POPT_ARG_STRING, &optarg, 'w', NULL, NULL }, { "u-threshold", 'x', POPT_ARG_STRING, &optarg, 'x', NULL, NULL }, { "l-threshold", 'y', POPT_ARG_STRING, &optarg, 'y', NULL, NULL }, { "numeric", 'n', POPT_ARG_NONE, NULL, 'n', NULL, NULL }, { "connection", 'c', POPT_ARG_NONE, NULL, 'c', NULL, NULL }, { "mcast-interface", '\0', POPT_ARG_STRING, &optarg, TAG_MCAST_INTERFACE, NULL, NULL }, { "syncid", '\0', POPT_ARG_STRING, &optarg, 'I', NULL, NULL }, { "timeout", '\0', POPT_ARG_NONE, NULL, TAG_TIMEOUT, NULL, NULL }, { "daemon", '\0', POPT_ARG_NONE, NULL, TAG_DAEMON, NULL, NULL }, { "stats", '\0', POPT_ARG_NONE, NULL, TAG_STATS, NULL, NULL }, { "rate", '\0', POPT_ARG_NONE, NULL, TAG_RATE, NULL, NULL }, { "thresholds", '\0', POPT_ARG_NONE, NULL, TAG_THRESHOLDS, NULL, NULL }, { "persistent-conn", '\0', POPT_ARG_NONE, NULL, TAG_PERSISTENTCONN, NULL, NULL }, { "nosort", '\0', POPT_ARG_NONE, NULL, TAG_NO_SORT, NULL, NULL }, { "sort", '\0', POPT_ARG_NONE, NULL, TAG_SORT, NULL, NULL }, { "exact", 'X', POPT_ARG_NONE, NULL, 'X', NULL, NULL }, { "ipv6", '6', POPT_ARG_NONE, NULL, '6', NULL, NULL }, { "ops", 'o', POPT_ARG_NONE, NULL, 'o', NULL, NULL }, { "pe", '\0', POPT_ARG_STRING, &optarg, TAG_PERSISTENCE_ENGINE, NULL, NULL }, { NULL, 0, 0, NULL, 0, NULL, NULL } }; context = poptGetContext("ipvsadm", argc, (const char **)argv, options_table, 0); if ((c = poptGetNextOpt(context)) < 0) tryhelp_exit(argv[0], -1); switch (c) { case 'A': set_command(&ce->cmd, CMD_ADD); break; case 'E': set_command(&ce->cmd, CMD_EDIT); break; case 'D': set_command(&ce->cmd, CMD_DEL); break; case 'a': set_command(&ce->cmd, CMD_ADDDEST); break; case 'e': set_command(&ce->cmd, CMD_EDITDEST); break; case 'd': set_command(&ce->cmd, CMD_DELDEST); break; case 'C': set_command(&ce->cmd, CMD_FLUSH); break; case 'L': case 'l': set_command(&ce->cmd, CMD_LIST); break; case 'Z': set_command(&ce->cmd, CMD_ZERO); break; case TAG_SET: set_command(&ce->cmd, CMD_TIMEOUT); break; case 'R': set_command(&ce->cmd, CMD_RESTORE); break; case 'S': set_command(&ce->cmd, CMD_SAVE); break; case TAG_START_DAEMON: set_command(&ce->cmd, CMD_STARTDAEMON); if (!strcmp(optarg, "master")) ce->daemon.state = IP_VS_STATE_MASTER; else if (!strcmp(optarg, "backup")) ce->daemon.state = IP_VS_STATE_BACKUP; else fail(2, "illegal start-daemon parameter specified"); break; case TAG_STOP_DAEMON: set_command(&ce->cmd, CMD_STOPDAEMON); if (!strcmp(optarg, "master")) ce->daemon.state = IP_VS_STATE_MASTER; else if (!strcmp(optarg, "backup")) ce->daemon.state = IP_VS_STATE_BACKUP; else fail(2, "illegal start_daemon specified"); break; case 'h': usage_exit(argv[0], 0); break; case 'v': version_exit(0); break; default: tryhelp_exit(argv[0], -1); } while ((c=poptGetNextOpt(context)) >= 0){ switch (c) { case 't': case 'u': set_option(options, OPT_SERVICE); ce->svc.protocol = (c=='t' ? IPPROTO_TCP : IPPROTO_UDP); parse = parse_service(optarg, &ce->svc); if (!(parse & SERVICE_ADDR)) fail(2, "illegal virtual server " "address[:port] specified"); break; case 'f': set_option(options, OPT_SERVICE); /* * Set protocol to a sane values, even * though it is not used */ ce->svc.af = AF_INET; ce->svc.protocol = IPPROTO_TCP; ce->svc.fwmark = parse_fwmark(optarg); break; case 's': set_option(options, OPT_SCHEDULER); strncpy(ce->svc.sched_name, optarg, IP_VS_SCHEDNAME_MAXLEN); break; case 'p': set_option(options, OPT_PERSISTENT); ce->svc.flags |= IP_VS_SVC_F_PERSISTENT; ce->svc.timeout = parse_timeout(optarg, 1, MAX_TIMEOUT); break; case 'M': set_option(options, OPT_NETMASK); if (ce->svc.af != AF_INET6) { parse = parse_netmask(optarg, &ce->svc.netmask); if (parse != 1) fail(2, "illegal virtual server " "persistent mask specified"); } else { ce->svc.netmask = atoi(optarg); if ((ce->svc.netmask < 1) || (ce->svc.netmask > 128)) fail(2, "illegal ipv6 netmask specified"); } break; case 'r': set_option(options, OPT_SERVER); ipvs_service_t t_dest = ce->svc; parse = parse_service(optarg, &t_dest); ce->dest.af = t_dest.af; ce->dest.addr = t_dest.addr; ce->dest.port = t_dest.port; if (!(parse & SERVICE_ADDR)) fail(2, "illegal real server " "address[:port] specified"); /* copy vport to dport if not specified */ if (parse == 1) ce->dest.port = ce->svc.port; break; case 'i': set_option(options, OPT_FORWARD); ce->dest.conn_flags = IP_VS_CONN_F_TUNNEL; break; case 'g': set_option(options, OPT_FORWARD); ce->dest.conn_flags = IP_VS_CONN_F_DROUTE; break; case 'm': set_option(options, OPT_FORWARD); ce->dest.conn_flags = IP_VS_CONN_F_MASQ; break; case 'w': set_option(options, OPT_WEIGHT); if ((ce->dest.weight = string_to_number(optarg, 0, 65535)) == -1) fail(2, "illegal weight specified"); break; case 'x': set_option(options, OPT_UTHRESHOLD); if ((ce->dest.u_threshold = string_to_number(optarg, 0, INT_MAX)) == -1) fail(2, "illegal u_threshold specified"); break; case 'y': set_option(options, OPT_LTHRESHOLD); if ((ce->dest.l_threshold = string_to_number(optarg, 0, INT_MAX)) == -1) fail(2, "illegal l_threshold specified"); break; case 'c': set_option(options, OPT_CONNECTION); break; case 'n': set_option(options, OPT_NUMERIC); *format |= FMT_NUMERIC; break; case TAG_MCAST_INTERFACE: set_option(options, OPT_MCAST); strncpy(ce->daemon.mcast_ifn, optarg, IP_VS_IFNAME_MAXLEN); break; case 'I': set_option(options, OPT_SYNCID); if ((ce->daemon.syncid = string_to_number(optarg, 0, 255)) == -1) fail(2, "illegal syncid specified"); break; case TAG_TIMEOUT: set_option(options, OPT_TIMEOUT); break; case TAG_DAEMON: set_option(options, OPT_DAEMON); break; case TAG_STATS: set_option(options, OPT_STATS); *format |= FMT_STATS; break; case TAG_RATE: set_option(options, OPT_RATE); *format |= FMT_RATE; break; case TAG_THRESHOLDS: set_option(options, OPT_THRESHOLDS); *format |= FMT_THRESHOLDS; break; case TAG_PERSISTENTCONN: set_option(options, OPT_PERSISTENTCONN); *format |= FMT_PERSISTENTCONN; break; case TAG_NO_SORT: set_option(options, OPT_NOSORT ); *format |= FMT_NOSORT; break; case TAG_SORT: /* Sort is the default, this is a no-op for compatibility */ break; case 'X': set_option(options, OPT_EXACT); *format |= FMT_EXACT; break; case '6': if (ce->svc.fwmark) { ce->svc.af = AF_INET6; ce->svc.netmask = 128; } else { fail(2, "-6 used before -f\n"); } break; case 'o': set_option(options, OPT_ONEPACKET); ce->svc.flags |= IP_VS_SVC_F_ONEPACKET; break; case TAG_PERSISTENCE_ENGINE: set_option(options, OPT_PERSISTENCE_ENGINE); strncpy(ce->svc.pe_name, optarg, IP_VS_PENAME_MAXLEN); break; default: fail(2, "invalid option `%s'", poptBadOption(context, POPT_BADOPTION_NOALIAS)); } } if (c < -1) { /* an error occurred during option processing */ fprintf(stderr, "%s: %s\n", poptBadOption(context, POPT_BADOPTION_NOALIAS), poptStrerror(c)); poptFreeContext(context); return -1; } if (ce->cmd == CMD_TIMEOUT) { char *optarg1, *optarg2; if ((optarg=(char *)poptGetArg(context)) && (optarg1=(char *)poptGetArg(context)) && (optarg2=(char *)poptGetArg(context))) { ce->timeout.tcp_timeout = parse_timeout(optarg, 0, MAX_TIMEOUT); ce->timeout.tcp_fin_timeout = parse_timeout(optarg1, 0, MAX_TIMEOUT); ce->timeout.udp_timeout = parse_timeout(optarg2, 0, MAX_TIMEOUT); } else fail(2, "--set option requires 3 timeout values"); } if ((optarg=(char *)poptGetArg(context))) fail(2, "unexpected argument %s", optarg); poptFreeContext(context); return 0; }
IP_VS_CONN_F_MASQ在kernel代码中搜索相关处理
http://kb.linuxvirtualserver.org/wiki/Ipvsadm#Compiling_ipvsadm
IPVS源代码分析---总述和初始化
主要参考了http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/ 这个系列讲的非常详细。以及yfydz的博客,很多对代码的注释都是直接转载的他的内容,先说明一下,我自己写的主要是对代码整体脉络和思路的分析。
IPVS这部分的代码看了挺长时间了,对于普通应用的处理,相对简单。
对于FTP这种多连接的处理,IPVS虽然目前只支持FTP,但是用了很多代码来处理这种affinity connection,其中用到了persistent connection和template。其中,persistent connection是指对于多连接的服务,需要把connection都定向到同一个server上面,persistent这个标记是在ipvsadm 添加service时就配置的。template是指对于多连接的服务,创建了一个template connection作为其他连接的template,(这里的其他连接是指从同一个src发出的,被iptables打过mark的连接,iptables可以对相关的连接根据端口号打上mark,方便IPVS的处理。)这样其他连接就根据template中指向的dest,也定向到了dest。也就说相关的连接都发到了同一个dest。
根据LVS官方网站的介绍,LVS支持三种负载均衡模式:NAT,tunnel和direct routing(DR)。 NAT是通用模式,所有交互数据必须通过均衡器;后两种则是一种半连接处理方式,请求数据通过均衡器,而服务器的回应则是直接路由返回的, 而这两种方法的区别是tunnel模式下由于进行了IP封装所以可路由,而DR方式是修改MAC地址来实现,所以必须同一网段. [主要数据结构] 这个结构用来描述IPVS支持的IP协议。IPVS的IP层协议支持TCP, UDP, AH和ESP这4种IP层协议 struct ip_vs_protocol { //链表中的下一项 struct ip_vs_protocol *next; //协议名称, "TCP", "UDP". char *name; //协议值 __u16 protocol; //不进行分片 int dont_defrag; //协议应用计数器,根据是该协议的中多连接协议的数量 atomic_t appcnt; //协议各状态的超时数组 int *timeout_table; void (*init)(struct ip_vs_protocol *pp); //协议初始化 void (*exit)(struct ip_vs_protocol *pp); //协议释放 int (*conn_schedule)(struct sk_buff *skb, struct ip_vs_protocol *pp, int *verdict, struct ip_vs_conn **cpp); //协议调度 //查找in方向的IPVS连接 struct ip_vs_conn * (*conn_in_get)(const struct sk_buff *skb, struct ip_vs_protocol *pp, const struct iphdr *iph, unsigned int proto_off, int inverse); //查找out方向的IPVS连接 struct ip_vs_conn * (*conn_out_get)(const struct sk_buff *skb, struct ip_vs_protocol *pp, const struct iphdr *iph, unsigned int proto_off, int inverse); //源NAT操作 int (*snat_handler)(struct sk_buff **pskb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp); //目的NAT操作 int (*dnat_handler)(struct sk_buff **pskb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp); //协议校验和计算 int (*csum_check)(struct sk_buff *skb, struct ip_vs_protocol *pp); //当前协议状态名称: 如"LISTEN", "ESTABLISH" const char *(*state_name)(int state); //协议状态迁移 int (*state_transition)(struct ip_vs_conn *cp, int direction, const struct sk_buff *skb, struct ip_vs_protocol *pp); //登记应用 int (*register_app)(struct ip_vs_app *inc); //去除应用登记 void (*unregister_app)(struct ip_vs_app *inc); int (*app_conn_bind)(struct ip_vs_conn *cp); //数据包打印 void (*debug_packet)(struct ip_vs_protocol *pp, const struct sk_buff *skb, int offset, const char *msg); //调整超时 void (*timeout_change)(struct ip_vs_protocol *pp, int flags); //设置各种状态下的协议超时 int (*set_state_timeout)(struct ip_vs_protocol *pp, char *sname, int to); }; 这个结构用来描述IPVS的连接。IPVS的连接和netfilter定义的连接类似 struct ip_vs_conn { struct list_head c_list; //HASH链表 __u32 caddr; //客户机地址 __u32 vaddr; //服务器对外的虚拟地址 __u32 daddr; //服务器实际地址 __u16 cport; //客户端的端口 __u16 vport; //服务器对外虚拟端口 __u16 dport; //服务器实际端口 __u16 protocol; //协议类型 atomic_t refcnt; //连接引用计数 struct timer_list timer; //定时器 volatile unsigned long timeout; //超时时间 spinlock_t lock; //状态转换锁 volatile __u16 flags; /* status flags */ volatile __u16 state; /* state info */ struct ip_vs_conn *control; //主连接, 如FTP atomic_t n_control; //子连接数 struct ip_vs_dest *dest; //真正服务器 atomic_t in_pkts; //进入的数据统计 int (*packet_xmit)(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp); //数据包发送 struct ip_vs_app *app; //IPVS应用 void *app_data; //应用的私有数据 struct ip_vs_seq in_seq; //进入数据的序列号 struct ip_vs_seq out_seq; //发出数据的序列号 }; 这个结构用来描述IPVS对外的虚拟服务器信息 struct ip_vs_service { struct list_head s_list; //按普通协议,地址,端口进行HASH的链表 struct list_head f_list; //按nfmark进行HASH的链表 atomic_t refcnt; //引用计数 atomic_t usecnt; //使用计数 __u16 protocol; //协议 __u32 addr; //虚拟服务器地址 __u16 port; //虚拟服务器端口 __u32 fwmark; //就是skb中的nfmark unsigned flags; //状态标志 unsigned timeout; //超时 __u32 netmask; //网络掩码 struct list_head destinations; //真实服务器的地址链表 __u32 num_dests; //真实服务器的数量 struct ip_vs_stats stats; //服务统计信息 struct ip_vs_app *inc; //应用 struct ip_vs_scheduler *scheduler; //调度指针 rwlock_t sched_lock; //调度锁 void *sched_data; //调度私有数据 }; 这个结构用来描述具体的真实服务器的信息 struct ip_vs_dest { struct list_head n_list; /* for the dests in the service */ struct list_head d_list; /* for table with all the dests */ __u32 addr; //服务器地址 __u16 port; //服务器端口 volatile unsigned flags; //目标标志,易变参数 atomic_t conn_flags; //连接标志 atomic_t weight; //服务器权重 atomic_t refcnt; //引用计数 struct ip_vs_stats stats; //统计数 atomic_t activeconns; //活动的连接 atomic_t inactconns; //不活动的连接 atomic_t persistconns; //保持的连接,常驻 __u32 u_threshold; //连接上限 __u32 l_threshold; //连接下限 /* for destination cache */ spinlock_t dst_lock; /* lock of dst_cache */ struct dst_entry *dst_cache; /* destination cache entry */ u32 dst_rtos; struct ip_vs_service *svc; /* service it belongs to */ __u16 protocol; /* which protocol (TCP/UDP) */ __u32 vaddr; /* virtual IP address */ __u16 vport; /* virtual port number */ __u32 vfwmark; /* firewall mark of service */ }; 这个结构用来描述IPVS调度算法,目前调度方法包括rr,wrr,lc, wlc, lblc, lblcr, dh, sh等 struct ip_vs_scheduler { struct list_head n_list; /* d-linked list head */ char *name; /* scheduler name */ atomic_t refcnt; /* reference counter */ struct module *module; /* THIS_MODULE/NULL */ /* scheduler initializing service */ int (*init_service)(struct ip_vs_service *svc); /* scheduling service finish */ int (*done_service)(struct ip_vs_service *svc); /* scheduler updating service */ int (*update_service)(struct ip_vs_service *svc); /* selecting a server from the given service */ struct ip_vs_dest* (*schedule)(struct ip_vs_service *svc, const struct sk_buff *skb); }; IPVS应用是针对多连接协议的, 目前也就只支持FTP。 由于ip_vs_app.c是从2.2过来的,没有管内核是否本身有NAT的情况,所以相当于自身实现了应用协议的NAT处理,包 括内容信息的改变, TCP序列号确认号的调整等,而现在这些都由netfilter实现了,IPVS可以不用管这些,只处理连接调度就行了。 IPVS的应用模块化还不是很好,在处理连接端口时,还要判断是否是FTPPORT,也就是说不支持其他多连接协议的, 应该象netfilter一样为每个多连接协议设置一个helper,自动调用,不用在程序里判断端口。 struct ip_vs_app { struct list_head a_list; //用来挂接到应用链表 int type; /* IP_VS_APP_TYPE_xxx */ char *name; /* application module name */ __u16 protocol; //协议, TCP, UD struct module *module; /* THIS_MODULE/NULL */ struct list_head incs_list; //应用的具体实例链表 /* members for application incarnations */ struct list_head p_list; //将应用结构挂接到对应协议(TCP, UDP...)的应用表 struct ip_vs_app *app; /* its real application */ __u16 port; /* port number in net order */ atomic_t usecnt; /* usage counter */ /* output hook: return false if can't linearize. diff set for TCP. */ int (*pkt_out)(struct ip_vs_app *, struct ip_vs_conn *, struct sk_buff **, int *diff); /* input hook: return false if can't linearize. diff set for TCP. */ int (*pkt_in)(struct ip_vs_app *, struct ip_vs_conn *, struct sk_buff **, int *diff); /* ip_vs_app initializer */ int (*init_conn)(struct ip_vs_app *, struct ip_vs_conn *); /* ip_vs_app finish */ int (*done_conn)(struct ip_vs_app *, struct ip_vs_conn *); /* not used now */ int (*bind_conn)(struct ip_vs_app *, struct ip_vs_conn *, struct ip_vs_protocol *); void (*unbind_conn)(struct ip_vs_app *, struct ip_vs_conn *); int * timeout_table; int * timeouts; int timeouts_size; int (*conn_schedule)(struct sk_buff *skb, struct ip_vs_app *app, int *verdict, struct ip_vs_conn **cpp); struct ip_vs_conn * (*conn_in_get)(const struct sk_buff *skb, struct ip_vs_app *app, const struct iphdr *iph, unsigned int proto_off, int inverse); struct ip_vs_conn * (*conn_out_get)(const struct sk_buff *skb, struct ip_vs_app *app, const struct iphdr *iph, unsigned int proto_off, int inverse); int (*state_transition)(struct ip_vs_conn *cp, int direction, const struct sk_buff *skb, struct ip_vs_app *app); void (*timeout_change)(struct ip_vs_app *app, int flags); }; 用户空间信息是ipvsadm程序接收用户输入后传递给内核ipvs的信息,信息都是很直接的,没有各种控制信息。 ipvsadm和ipvs的关系相当于iptables和netfilter的关系. 用户空间的虚拟服务信息 struct ip_vs_service_user { /* virtual service addresses */ u_int16_t protocol; u_int32_t addr; /* virtual ip address */ u_int16_t port; u_int32_t fwmark; /* firwall mark of service */ /* virtual service options */ char sched_name[IP_VS_SCHEDNAME_MAXLEN]; unsigned flags; /* virtual service flags */ unsigned timeout; /* persistent timeout in sec */ u_int32_t netmask; /* persistent netmask */ }; 用户空间的真实服务器信息 struct ip_vs_dest_user { /* destination server address */ u_int32_t addr; u_int16_t port; /* real server options */ unsigned conn_flags; /* connection flags */ int weight; /* destination weight */ /* thresholds for active connections */ u_int32_t u_threshold; /* upper threshold */ u_int32_t l_threshold; /* lower threshold */ }; 用户空间的统计信息 struct ip_vs_stats_user { __u32 conns; /* connections scheduled */ __u32 inpkts; /* incoming packets */ __u32 outpkts; /* outgoing packets */ __u64 inbytes; /* incoming bytes */ __u64 outbytes; /* outgoing bytes */ __u32 cps; /* current connection rate */ __u32 inpps; /* current in packet rate */ __u32 outpps; /* current out packet rate */ __u32 inbps; /* current in byte rate */ __u32 outbps; /* current out byte rate */ }; 用户空间的获取信息结构 struct ip_vs_getinfo { /* version number */ unsigned int version; /* size of connection hash table */ unsigned int size; /* number of virtual services */ unsigned int num_services; }; 用户空间的服务规则项信息 struct ip_vs_service_entry { /* which service: user fills in these */ u_int16_t protocol; u_int32_t addr; /* virtual address */ u_int16_t port; u_int32_t fwmark; /* firwall mark of service */ /* service options */ char sched_name[IP_VS_SCHEDNAME_MAXLEN]; unsigned flags; /* virtual service flags */ unsigned timeout; /* persistent timeout */ u_int32_t netmask; /* persistent netmask */ /* number of real servers */ unsigned int num_dests; /* statistics */ struct ip_vs_stats_user stats; }; 用户空间的服务器项信息 struct ip_vs_dest_entry { u_int32_t addr; /* destination address */ u_int16_t port; unsigned conn_flags; /* connection flags */ int weight; /* destination weight */ u_int32_t u_threshold; /* upper threshold */ u_int32_t l_threshold; /* lower threshold */ u_int32_t activeconns; /* active connections */ u_int32_t inactconns; /* inactive connections */ u_int32_t persistconns; /* persistent connections */ /* statistics */ struct ip_vs_stats_user stats; }; 用户空间的获取服务器项信息 struct ip_vs_get_dests { /* which service: user fills in these */ u_int16_t protocol; u_int32_t addr; /* virtual address */ u_int16_t port; u_int32_t fwmark; /* firwall mark of service */ /* number of real servers */ unsigned int num_dests; /* the real servers */ struct ip_vs_dest_entry entrytable[0]; }; 用户空间的获取虚拟服务项信息 struct ip_vs_get_services { /* number of virtual services */ unsigned int num_services; /* service table */ struct ip_vs_service_entry entrytable[0]; }; 用户空间的获取超时信息结构 struct ip_vs_timeout_user { int tcp_timeout; int tcp_fin_timeout; int udp_timeout; }; 用户空间的获取IPVS内核守护进程信息结构 struct ip_vs_daemon_user { /* sync daemon state (master/backup) */ int state; /* multicast interface name */ char mcast_ifn[IP_VS_IFNAME_MAXLEN]; /* SyncID we belong to */ int syncid; }; [/主要数据结构] static int __init ip_vs_init(void) { int ret; //初始化ipvs的控制接口,set/get sockopt操作 ret = ip_vs_control_init(); if (ret < 0) { IP_VS_ERR("can't setup control.\n"); goto cleanup_nothing; } //协议初始化 ip_vs_protocol_init(); //应用层辅助接口初始化 ret = ip_vs_app_init(); if (ret < 0) { IP_VS_ERR("can't setup application helper.\n"); goto cleanup_protocol; } //主要数据结构初始化 ret = ip_vs_conn_init(); if (ret < 0) { IP_VS_ERR("can't setup connection table.\n"); goto cleanup_app; } //下面分别挂接各个处理点到netfilter架构中,看下面hook点实现 //关于hook点知识,参考ip_conntrack实现 ret = nf_register_hook(&ip_vs_in_ops); if (ret < 0) { IP_VS_ERR("can't register in hook.\n"); goto cleanup_conn; } ret = nf_register_hook(&ip_vs_out_ops); if (ret < 0) { IP_VS_ERR("can't register out hook.\n"); goto cleanup_inops; } ret = nf_register_hook(&ip_vs_post_routing_ops); if (ret < 0) { IP_VS_ERR("can't register post_routing hook.\n"); goto cleanup_outops; } ret = nf_register_hook(&ip_vs_forward_icmp_ops); if (ret < 0) { IP_VS_ERR("can't register forward_icmp hook.\n"); goto cleanup_postroutingops; } IP_VS_INFO("ipvs loaded.\n"); return ret; ...... } 控制接口初始化 int ip_vs_control_init(void) { int ret; int idx; //登记ipvs的sockopt控制,这样用户空间可通过setsockopt函数来和ipvs进行通信,看下面控制接口实现 ret = nf_register_sockopt(&ip_vs_sockopts); if (ret) { IP_VS_ERR("cannot register sockopt.\n"); return ret; } //建立/proc/net/ip_vs和/proc/net/ip_vs_stats只读项 //看下面控制接口实现 proc_net_fops_create("ip_vs", 0, &ip_vs_info_fops); proc_net_fops_create("ip_vs_stats",0, &ip_vs_stats_fops); //建立/proc/sys/net/ipv4/vs目录下的各可读写控制参数 sysctl_header = register_sysctl_table(vs_root_table, 0); //初始化各种双向链表 //svc_table是根据协议地址端口等信息进行服务结构struct ip_vs_service查找的HASH表 //svc_fwm_table是根据数据包的nfmark信息进行服务结构struct ip_vs_service查找的HASH表 for(idx = 0; idx < IP_VS_SVC_TAB_SIZE; idx++) { INIT_LIST_HEAD(&ip_vs_svc_table[idx]); INIT_LIST_HEAD(&ip_vs_svc_fwm_table[idx]); } //rtable是目的结构struct ip_vs_dest的HASH链表 for(idx = 0; idx < IP_VS_RTAB_SIZE; idx++) { INIT_LIST_HEAD(&ip_vs_rtable[idx]); } //ipvs统计信息 memset(&ip_vs_stats, 0, sizeof(ip_vs_stats)); spin_lock_init(&ip_vs_stats.lock); //统计锁 //对当前统计信息建立一个预估器,可用于计算服务器的性能参数 ip_vs_new_estimator(&ip_vs_stats); //挂一个定时操作,根据系统当前负载情况定时调整系统参数 schedule_delayed_work(&defense_work, DEFENSE_TIMER_PERIOD); return 0; } //协议初始化,具体看下面协议实现 int ip_vs_protocol_init(void) { //挂接ipvs能进行均衡处理的各种协议,目前支持TCP/UDP/AH/ESP char protocols[64]; #define REGISTER_PROTOCOL(p) \ do { \ register_ip_vs_protocol(p); \ strcat(protocols, ", "); \ strcat(protocols, (p)->name); \ } while (0) //0,1字符是给", "预留的 protocols[0] = '\0'; protocols[2] = '\0'; #ifdef CONFIG_IP_VS_PROTO_TCP REGISTER_PROTOCOL(&ip_vs_protocol_tcp); #endif #ifdef CONFIG_IP_VS_PROTO_UDP REGISTER_PROTOCOL(&ip_vs_protocol_udp); #endif #ifdef CONFIG_IP_VS_PROTO_AH REGISTER_PROTOCOL(&ip_vs_protocol_ah); #endif #ifdef CONFIG_IP_VS_PROTO_ESP REGISTER_PROTOCOL(&ip_vs_protocol_esp); #endif IP_VS_INFO("Registered protocols (%s)\n", &protocols[2]); return 0; } #define IP_VS_PROTO_TAB_SIZE 32 static int register_ip_vs_protocol(struct ip_vs_protocol *pp) { //#define IP_VS_PROTO_HASH(proto) ((proto) & (IP_VS_PROTO_TAB_SIZE-1)) unsigned hash = IP_VS_PROTO_HASH(pp->protocol); //计算一个hash值 pp->next = ip_vs_proto_table[hash]; ip_vs_proto_table[hash] = pp; if (pp->init != NULL) pp->init(pp); return 0; } 应用层辅助接口初始化 int ip_vs_app_init(void) { //建立一个/proc/net/ip_vs_app项 proc_net_fops_create("ip_vs_app", 0, &ip_vs_app_fops); return 0; } 主要数据结构初始化 int ip_vs_conn_init(void) { int idx; //ipvs连接HASH表 static struct list_head *ip_vs_conn_tab; ip_vs_conn_tab = vmalloc(IP_VS_CONN_TAB_SIZE*sizeof(struct list_head)); if (!ip_vs_conn_tab) return -ENOMEM; //ipvs连接cache ip_vs_conn_cachep = kmem_cache_create("ip_vs_conn", sizeof(struct ip_vs_conn), 0, SLAB_HWCACHE_ALIGN, NULL, NULL); if (!ip_vs_conn_cachep) { vfree(ip_vs_conn_tab); return -ENOMEM; } //初始化HASH链表头 for (idx = 0; idx < IP_VS_CONN_TAB_SIZE; idx++) { INIT_LIST_HEAD(&ip_vs_conn_tab[idx]); } //初始化各读写锁 for (idx = 0; idx < CT_LOCKARRAY_SIZE; idx++) { rwlock_init(&__ip_vs_conntbl_lock_array[idx].l); } //建立/proc/net/ip_vs_conn项 proc_net_fops_create("ip_vs_conn", 0, &ip_vs_conn_fops); //初始化随机数 get_random_bytes(&ip_vs_conn_rnd, sizeof(ip_vs_conn_rnd)); return 0; }
分类: LINUX
2013-05-07 20:44:15
NAT模式是IPVS最常用的一种模式。相比于TUN和DR模式,NAT模式更容易部署,仅仅是需要更改真实服务器的默认网关配置。
IPVS是基于Netfilter实现的。它注册了4个Netfilter钩子函数,其中与NAT模式相关的是ip_vs_in和ip_vs_out两个钩子函数。前者处理了客户端-〉服务器的数据包,后者则针对服务器-〉客户端的数据包。
1、ip_vs_in钩子函数
ip_vs_in函数的处理流程非常清晰:
预校验--〉ip_vs_conn对象的查找或生成--〉更新统计信息--〉更新ip_vs_conn对象的状态--〉修改sk_buff并转发数据包--〉IPVS状态同步--〉结束
1.1、预校验
预校验的过程很简单,主要包括:
- 确保数据包的类型为PACKET_HOST
- 确保数据包不是loopback设备发出来的
- 确保数据包的协议类型是IPVS所支持的,目前IPVS支持TCP、UDP、AH和ESP协议
1.2、ip_vs_conn对象的查找或生成
既然有数据包来了,必然有对应的ip_vs_conn对象。首先根据数据包的<源地址-源端口-目的地址-目的端口>,查找当前的ip_vs_conn对象列表。如果没有找到的话,说明这是一个新连接,于是运行调度过程,找到一个合适的真实服务器,然后再生成一个新的ip_vs_conn对象。这里先不对调度过程进行展开描述。
1.3、更新统计信息
这里先看一下ip_vs_stats结构,其各个成员的作用很容易看出来:
|
这是一个专门用于统计的数据结构,每个ip_vs_service对象、每个ip_vs_dest对象都包含有这么一个结构,另外还有一个ip_vs_stats全局变量。
函数ip_vs_in_stats和ip_vs_out_stats分别统计两个方向的数据包流量,函数ip_vs_conn_stats用于统计新建连接数。
|
conns、inpkts、outpkts、inbytes和outbytes统计比较容易,只需简单的加1。但cps等统计起来就要复杂一些了,它是通过内核定时器来实现的。每个ip_vs_stats对象都对应有一个ip_vs_estimator结构:
|
所有的ip_vs_estimator结构形成一张链表,通过全局变量est_list可以遍历这个链表。定时器的时间间隔为2秒,对应的触发函数为:
|
以cps的统计为例,其计算过程很简单,cps = (rate+cps)/2,其中单位为2^10。
1.4、更新ip_vs_conn对象的状态
在TCP数据包处理过程中,每个ip_vs_conn对象对应于一个TCP连接,因此也必须有一个状态转换过程,才能够引导此TCP连接正常建立和终止。这个状态转换颇为复杂,在后续内容将IN/OUT结合一起,来看TCP连接的状态转换。
1.5、修改sk_buff并转发数据包
NAT模式下的数据包转发由ip_vs_nat_xmit函数完成。对sk_buff数据结构的操作不熟悉,略过。
1.6、IPVS状态同步
先判断此ip_vs_conn对象是否需要进行主备机同步。首先当前IPVS必须是MASTER,并且此ip_vs_conn对象的状态为ESTABLISHED。另外,满足这些条件时,并非每个Packet转发的时候都进行同步,而是每50个Packet,才同步一次。
同步过程由函数ip_vs_sync_conn完成:
|
1.7、ip_vs_out钩子函数
ip_vs_out函数处理 服务器-〉客户端 的数据包。相比于ip_vs_in函数,它要简单得多。这里不再描述。
2、TCP状态转换过程
IPVS支持TCP、UDP、AH和ESP四种协议。由于TCP协议的逻辑相对复杂一些,所以IPVS对TCP协议的特殊处理也更多。
IPVS针对TCP协议的处理主要是体现在TCP状态维护上,而TCP状态维护依赖于一个状态转换矩阵:
|
与NAT模式相关的为INPUT和OUTPUT两张表,其意思也较容易理解:
- sNO、sES等为TCP状态,tcp_state_name_table为状态名称表,而tcp_timeouts表指明了每个状态的维系时间。此维系时间决定了ip_vs_conn对象的生命期,当维系时间内此连接无任何输入输出,则ip_vs_conn对象自动销毁,它是通过设置ip_vs_conn对象的timeout来实现的。
- 当连接处于某状态时,在tcp_states矩阵中查找对应状态列,然后根据当前的输入(INPUT指客户端输入,OUTPUT指真实服务器输出),查找到下一个状态值。
状态转换矩阵也可以用一张状态转换图来表示: