LVS NAT配合MASQUERADE实现FULLNAT的场景,及此场景下net.ipv4.vs.conntrack参数的重要作用
1、LVS基本原理:
流程:
- 当用户向负载均衡调度器(Director Server)发起请求,调度器将请求发往至内核空间
PREROUTING 链首先会接收到用户请求,判断目标 IP 确定是本机 IP,将数据包发往 INPUT 链 - IPVS 是工作在 INPUT 链上的,当用户请求到达 INPUT 时,IPVS 会将用户请求和自己已定义好的集群服务进行比对,如果用户请求的就是定义的集群服务,那么此时 IPVS 会强行修改数据包里的目标 IP 地址及端口,并将新的数据包发往 POSTROUTING 链
- POSTROUTING 链接收数据包后发现目标 IP 地址刚好是自己的后端服务器,那么此时通过选路,将数据包最终发送给后端的服务器
2、LVS NAT模式
上面的步骤中,数据包达到RS后,如何返回,有不同的方式,如果直接沿着RS=>DS=>Client方式返回,那么就是NAT模式:
NAT模式的数据包请求流程:
- 1、当用户请求到达 Director Server,此时请求的数据报文会先到内核空间的 PREROUTING 链。 此时报文的源 IP 为 CIP,目标 IP 为 VIP
- 2、PREROUTING 检查发现数据包的目标 IP 是本机,将数据包送至 INPUT 链
- 3、IPVS 比对数据包请求的服务是否为集群服务,若是,修改数据包的目标 IP 地址为后端服务器 IP,然后将数据包发至 POSTROUTING 链。 此时报文的源 IP 为 CIP,目标 IP 为 RIP
- 4、POSTROUTING 链通过选路,将数据包发送给 Real Server
- 5、Real Server 比对发现目标为自己的 IP,开始构建响应报文发回给 Director Server。 此时报文的源 IP 为 RIP,目标 IP 为 CIP
- 6、Director Server 在响应客户端前,此时会将源 IP 地址修改为自己的 VIP 地址,然后响应给客户端。 此时报文的源 IP 为 VIP,目标 IP 为 CIP
NAT模式特点:
- RS 应该使用私有地址,RS 的网关必须指向 DIP
- DIP 和 RIP 必须在同一个网段内
- 请求和响应报文都需要经过 Director Server,高负载场景中,Director Server 易成为性能瓶颈
- 支持端口映射
- RS 可以使用任意操作系统
- 缺陷:对 Director Server 压力会比较大,请求和响应都需经过 director server
- 包对客户端看起来就是 LVS 直接返回给它的。客户端无法感知到后端 RS 的存在。
3、LVS NAT模式下,为什么一般需要设置RS 的网关必须指向 DS
因为RS收到的包的源ip是client ip,如果不设置RS 的网关必须指向 DS,默认将包送回DS,再由DS回复给client的话,那么RS直接回给client就会有问题,因为client发出的时候目的ip是vip,不是RS的ip,那么此时client对RS直接回过来的包会回复RST来终止这个连接,因为在client看来,不认为这个包是自己发出去的请求包的回包
这个原理跟三角流量一样:三角流量问题
4、、LVS NAT模式下需要额外做MASQUERADE的场景
需要额外做MASQUERADE场景:
为了让real service把包送回director server,我们在nat模式下可以采用将rs的网关指向ds,但是并不是所有情况下都可以的,比如:rs还负责处理其它的事情,那么不可以统一将包都送到ds那里去,那么此时我们可以通过做MASQUERADE,让ds将包送给rs时,将源ip换成ds的ip,这样rs就看不到client ip,看到的是包从ds给过来的,处理后就会回复给ds
默认情况下直接通过iptables来做MASQUERADE产生的问题:
一般通过以下方法做MASQUERADE:
iptables -t nat -A POSTROUTING -m ipvs --vaddr xxxx --vport xxxxx -j MASQUERADE
做完之后出现的问题:
做完会网络不通,抓包看会发现MASQUERADE没生效,因为nat 是依赖 conntrack 的,而 IPVS 默认不会记录 conntrack,我们需要开启 IPVS 的 conntrack 才可以让 MASQUERADE 生效。
设置方法:
启用内核参数:net.ipv4.vs.conntrack=1,在kube-proxy ipvs源码中也可以发现开启了这个
启动方法:echo 1 > /proc/sys/net/ipv4/vs/conntrack
5、为什么LVS NAT模式下要开启net.ipv4.vs.conntrack才能做MASQUERADE
查看ipvs手册: https://www.kernel.org/doc/Documentation/networking/ipvs-sysctl.txt
conntrack - BOOLEAN
0 - disabled (default)
not 0 - enabled
If set, maintain connection tracking entries for
connections handled by IPVS.
This should be enabled if connections handled by IPVS are to be
also handled by stateful firewall rules. That is, iptables rules
that make use of connection tracking. It is a performance
optimisation to disable this setting otherwise.
Connections handled by the IPVS FTP application module
will have connection tracking entries regardless of this setting.
Only available when IPVS is compiled with CONFIG_IP_VS_NFCT enabled.
查看ipvs模块帮助: https://github.com/torvalds/linux/blob/master/net/netfilter/ipvs/Kconfig
config IP_VS_NFCT
bool "Netfilter connection tracking"
depends on NF_CONNTRACK
---help---
The Netfilter connection tracking support allows the IPVS
connection state to be exported to the Netfilter framework
for filtering purposes.
总结下来:
让Netfilter的状态管理功能也能应用于IPVS模块,反过来也就是说默认nf_conntract没法作用于ipvs
原因剖析:
LVS(Linux Virtual Server)能实现负载均衡主要依靠内核IPVS (IP Virtual Server)模块,而IPVS是基于内核Netfilter实现。IPVS利用了Netfilter的Hook机制,主要在三个Hook点放置了钩子函数,分别是:NF_INET_LOCAL_IN、NF_INET_LOCAL_OUT和NF_INET_FORWARD
钩子源码如下:
static struct nf_hook_ops ip_vs_ops[] __read_mostly = {
/* After packet filtering, change source only for VS/NAT */
// @xnile RIP->DIP修改为VIP->DIP,用于本机请求VIP后的回包
{
.hook = ip_vs_reply4,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP_PRI_NAT_SRC - 2,
},
/* After packet filtering, forward packet through VS/DR, VS/TUN,
* or VS/NAT(change destination), so that filtering rules can be
* applied to IPVS. */
// @xnile CIP->VIP修改为CIP->RIP
{
.hook = ip_vs_remote_request4,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP_PRI_NAT_SRC - 1,
},
/* Before ip_vs_in, change source only for VS/NAT */
// @xnile NF_INET_LOCAL_OUT 本机应用层发出去的包
// @xnile DIP->DIP 修改为DIR->VIP
{
.hook = ip_vs_local_reply4,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_OUT,
.priority = NF_IP_PRI_NAT_DST + 1,
},
/* After mangle, schedule and forward local requests */
// @xnile NF_INET_LOCAL_OUT 本机应用层发出去的包
// @xnile DIP->VIP修改为DIP->RIP
{
.hook = ip_vs_local_request4,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_OUT,
.priority = NF_IP_PRI_NAT_DST + 2,
},
/* After packet filtering (but before ip_vs_out_icmp), catch icmp
* destined for 0.0.0.0/0, which is for incoming IPVS connections */
{
.hook = ip_vs_forward_icmp,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_FORWARD,
.priority = 99,
},
/* After packet filtering, change source only for VS/NAT */
//@xnile RIP->CIP to src为VIP
{
.hook = ip_vs_reply4,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_FORWARD,
.priority = 100,
}
iptables nat勾子函数的定义:
static struct nf_hook_ops nf_nat_ipv4_ops[] __read_mostly = {
/* Before packet filtering, change destination */
// @xnile iptables -t nat -A REROUTING
{
.hook = nf_nat_ipv4_in,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_PRE_ROUTING,
.priority = NF_IP_PRI_NAT_DST,
},
/* After packet filtering, change source */
// @xnile iptables -t nat -A POSTROUTING
{
.hook = nf_nat_ipv4_out,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_POST_ROUTING,
.priority = NF_IP_PRI_NAT_SRC,
},
/* Before packet filtering, change destination */
// @xnile iptables -t nat -A OUTPUT
// @xnile DNAT 本机出去的包
{
.hook = nf_nat_ipv4_local_fn,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_OUT,
.priority = NF_IP_PRI_NAT_DST,
},
/* After packet filtering, change source */
// @xnile SNAT
{
.hook = nf_nat_ipv4_fn,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP_PRI_NAT_SRC,
},
};
优先级图如下: 我将ip_confirm标识出来了,他的优先级比其它hook点,比如lvs的hook点要低
原因分析:
-
1、NAT构建于nf_conntrack之上,当数据包进入nf_conntrack后,
会建立一个tuple以及相应的replay tuple,而应答的数据包,
会直接查找与之匹配的repaly tupletuple example:
tuple[ORIGINAL]={10.0.5.119–>10.0.0.254,TCP}
tuple[REPLY]={10.0.0.254–>10.0.5.119,TCP} -
2、如果要做SNAT就修改replay tuple中的目的地址,如果要做DNAT就修改replay tuple中的源地址。另外,nf_conntrack有一个confirm的逻辑,就是上图中最上边绿色的部分,只有当数据流的头包离开协议栈的时候才会被confirm,被confirm过的conntrack才会加入到conntrack哈希表。
-
3、困为LVS钩子函数的优先级要高于confirm,数据流会首先导向到lvs的钩子函数中处理,然而钩子函数处理后会返回NF_STOLEN,也就是数据流不会再往下走了,当然也就不会执行confirm逻辑,因此conntrack表中就不会有对应的tuple和replay tuple,SNAT当然也就不起作用