在 k8s 上, kube-proxy 使用 ipvs 模式时,发现相同的 node port 类型的 service, 使用 localhost:nodePort , 127.0.0.1:nodePort 访问时, ipvs 模式下不通, iptables 模式下可以正常。最后我们将服务的访问 改成了 service ip + service port ,绕开了该问题,那为什么 ipvs 模式下会存在该问题那?
找了一些 issue,了解下情况,ipvs 模式确实存在该问题,不是我们的使用配置存在问题。
https://github.com/kubernetes/kubernetes/issues/96879
https://github.com/kubernetes/kubernetes/issues/67730
iptables 模式下, kube-proxy 通过设置了 net.ipv4.conf.all.route_localnet=1,允许了路由本地地址,才让 localhost:nodePort 这类 nat 规则生效。
ipvs 模式下虽然也设置了 该选项,但是实际上是没必要的。
开启内核该选项也容易引起安全问题。
ipvs 模式下 通过 ipvsadm 可以看到 127.0.0.1:nodePort 的配置已经正常建立,当访问该地址时,连接数也正常的增长,但是最终没有数据包发出, 我们来了解下代码层面是不是对这个特殊地址做了什么魔法。
nat 建立后,最后的发送代码 应该是这个函数 ip_vs_nat_xmit
之后 关注下 __ip_vs_get_out_rt , 该函数在 通过路由查找之后, 通过 crosses_local_route_boundary
做了一次关于 local 的检查, 检查了这种情况,失败告终。
/*
* NAT transmitter (only for outside-to-inside nat forwarding)
* Not used for related ICMP
*/
int
ip_vs_nat_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh)
{
struct rtable *rt; /* Route to the other host */
int local, rc, was_input;
EnterFunction(10);
.........
省略
.........
was_input = rt_is_input_route(skb_rtable(skb));
local = __ip_vs_get_out_rt(cp->ipvs, cp->af, skb, cp->dest, cp->daddr.ip,
IP_VS_RT_MODE_LOCAL |
IP_VS_RT_MODE_NON_LOCAL |
IP_VS_RT_MODE_RDR, NULL, ipvsh);
if (local < 0)
goto tx_error;
rt = skb_rtable(skb);
..............
省略
..............
LeaveFunction(10);
return rc;
tx_error:
kfree_skb(skb);
LeaveFunction(10);
return NF_STOLEN;
}
复制代码
/* Get route to destination or remote server */
static int
__ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb,
struct ip_vs_dest *dest,
__be32 daddr, int rt_mode, __be32 *ret_saddr,
struct ip_vs_iphdr *ipvsh)
{
struct net *net = ipvs->net;
struct ip_vs_dest_dst *dest_dst;
struct rtable *rt; /* Route to the other host */
int mtu;
int local, noref = 1;
.........
省略
.........
local = (rt->rt_flags & RTCF_LOCAL) ? 1 : 0;
if (unlikely(crosses_local_route_boundary(skb_af, skb, rt_mode,
local))) {
IP_VS_DBG_RL("We are crossing local and non-local addresses"
" daddr=%pI4\n", &daddr);
goto err_put;
}
.........
省略
.........
if (!ensure_mtu_is_adequate(ipvs, skb_af, rt_mode, ipvsh, skb, mtu))
goto err_put;
skb_dst_drop(skb);
if (noref) {
if (!local)
skb_dst_set_noref(skb, &rt->dst);
else
skb_dst_set(skb, dst_clone(&rt->dst));
} else
skb_dst_set(skb, &rt->dst);
return local;
err_put:
if (!noref)
ip_rt_put(rt);
return -1;
err_unreach:
dst_link_failure(skb);
return -1;
}
复制代码
static inline bool crosses_local_route_boundary(int skb_af, struct sk_buff *skb,
int rt_mode,
bool new_rt_is_local)
{
bool rt_mode_allow_local = !!(rt_mode & IP_VS_RT_MODE_LOCAL);
bool rt_mode_allow_non_local = !!(rt_mode & IP_VS_RT_MODE_NON_LOCAL);
bool rt_mode_allow_redirect = !!(rt_mode & IP_VS_RT_MODE_RDR);
bool source_is_loopback;
bool old_rt_is_local;
#ifdef CONFIG_IP_VS_IPV6
if (skb_af == AF_INET6) {
int addr_type = ipv6_addr_type(&ipv6_hdr(skb)->saddr);
source_is_loopback =
(!skb->dev || skb->dev->flags & IFF_LOOPBACK) &&
(addr_type & IPV6_ADDR_LOOPBACK);
old_rt_is_local = __ip_vs_is_local_route6(
(struct rt6_info *)skb_dst(skb));
} else
#endif
{
# 我们的原地址是 127, 所有 source_is_loopback 为 true
source_is_loopback = ipv4_is_loopback(ip_hdr(skb)->saddr);
old_rt_is_local = skb_rtable(skb)->rt_flags & RTCF_LOCAL;
}
# new_rt_is_local 通过得出的路由判断而来,我们的nat的下一条地址,不是local
# 所以此处是 false
if (unlikely(new_rt_is_local)) {
if (!rt_mode_allow_local)
return true;
if (!rt_mode_allow_redirect && !old_rt_is_local)
return true;
} else {
if (!rt_mode_allow_non_local)
return true;
# 最后这种情况下,该函数返回 true,
# 最后上面走到错误处理
if (source_is_loopback)
return true;
}
return false;
}
复制代码
评论