在前文《手撸容器 VxLAN 组网》中我们完成了容器组网,容器之间访问是没有问题了,但是容器怎么访问外网呢?

我们直接在容器 A(172.18.1.2) 中尝试一下访问百度
ip netns exec nsctr curl 110.242.68.66 -H "Host:baidu.com" 发现并不能通,为什么呢?抓个包看看

tcpdump -i any -nnvv  host 110.242.68.66 

23:41:50.391308 veth1 In  IP (tos 0x0, ttl 64, id 37680, offset 0, flags [DF], proto TCP (6), length 60)
    172.18.1.2.35630 > 110.242.68.66.80: Flags [S], cksum 0x6077 (incorrect -> 0xb8a8), seq 13603725, win 32430, options [mss 1410,sackOK,TS val 3606952377 ecr 0,nop,wscale 7], length 0
23:41:50.391330 enp0s1 Out IP (tos 0x0, ttl 63, id 37680, offset 0, flags [DF], proto TCP (6), length 60)
    172.18.1.2.35630 > 110.242.68.66.80: Flags [S], cksum 0x6077 (incorrect -> 0xb8a8), seq 13603725, win 32430, options [mss 1410,sackOK,TS val 3606952377 ecr 0,nop,wscale 7], length 0
23:41:51.449450 veth1 In  IP (tos 0x0, ttl 64, id 37681, offset 0, flags [DF], proto TCP (6), length 60)
    172.18.1.2.35630 > 110.242.68.66.80: Flags [S], cksum 0x6077 (incorrect -> 0xb486), seq 13603725, win 32430, options [mss 1410,sackOK,TS val 3606953435 ecr 0,nop,wscale 7], length 0

发现只有容器发出的 syn 包,并没有回包。分析一下即可明白:即使 baidu.com 收到了这个包,回包的 dst ip172.18.1.2,公网也根本不知道如何路由(实际上,baidu.com 收到的包的 src ip 应该是我们节点的公网出口 IP,所以实际上不知道怎么路由回包的应该是我们的出口网关,这里简化起见,你可以认为节点 IP 本身公网可路由)。所以我们需要做一个 SNAT,在 192.168.64.4 上执行: iptables -t nat -A POSTROUTING -s 172.18.1.2 ! -d 172.18.1.130 -j MASQUERADE --random-fully,意思是对于源 IP 是 容器 A 且目的 IP 不是容器 B 的报文需要做 SNAT。然后测试就通了:

00:16:34.435399 veth1 In  IP (tos 0x0, ttl 64, id 15150, offset 0, flags [DF], proto TCP (6), length 60)
    172.18.1.2.54472 > 110.242.68.66.80: Flags [S], cksum 0x6077 (incorrect -> 0xfd1f), seq 3155590472, win 32430, options [mss 1410,sackOK,TS val 3609036421 ecr 0,nop,wscale 7], length 0
00:16:34.435453 enp0s1 Out IP (tos 0x0, ttl 63, id 15150, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.64.4.30792 > 110.242.68.66.80: Flags [S], cksum 0xb40f (incorrect -> 0x0608), seq 3155590472, win 32430, options [mss 1410,sackOK,TS val 3609036421 ecr 0,nop,wscale 7], length 0
00:16:34.499941 enp0s1 In  IP (tos 0x0, ttl 48, id 15150, offset 0, flags [none], proto TCP (6), length 60)
    110.242.68.66.80 > 192.168.64.4.30792: Flags [S.], cksum 0xd3e2 (correct), seq 3872983729, ack 3155590473, win 8192, options [mss 1380,sackOK,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,wscale 5], length 0
00:16:34.499989 veth1 Out IP (tos 0x0, ttl 47, id 15150, offset 0, flags [none], proto TCP (6), length 60)
    110.242.68.66.80 > 172.18.1.2.54472: Flags [S.], cksum 0xcafa (correct), seq 3872983729, ack 3155590473, win 8192, options [mss 1380,sackOK,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,wscale 5], length 0
00:16:34.500327 veth1 In  IP (tos 0x0, ttl 64, id 15151, offset 0, flags [DF], proto TCP (6), length 40)
    172.18.1.2.54472 > 110.242.68.66.80: Flags [.], cksum 0x6063 (incorrect -> 0x4e89), seq 1, ack 1, win 254, length 0
00:16:34.500353 enp0s1 Out IP (tos 0x0, ttl 63, id 15151, offset 0, flags [DF], proto TCP (6), length 40)
    192.168.64.4.30792 > 110.242.68.66.80: Flags [.], cksum 0xb3fb (incorrect -> 0x5771), seq 1, ack 1, win 254, length 0
00:16:34.500779 veth1 In  IP (tos 0x0, ttl 64, id 15152, offset 0, flags [DF], proto TCP (6), length 112)

抓包可以看到完整的三步握手报文,及其对应的 SNAT 后的报文。

我们这条规则写的非常限制,做做 demo 还可以,实际上,集群中的容器非常多,如果每个容器的 ip 都来一条规则,规则会很多,不仅效率低下,而且每个节点上的规则还不一样,维护很困难。怎么改呢?

集群中,pod 网段通常可以用一个 cidr(我们的 demo 就是 172.18.1.0/24) 来表示,这样我们的规则改成 iptables -t nat -A POSTROUTING -s 172.18.1.0/24 ! -d 172.18.1.0/24 -j MASQUERADE --random-fully,意思是容器访问非容器的话需要做 SNAT。这样每个节点的规则都一样了。

但是大规模集群中,无论是处于多租户还是区分规格的目的,我们可能会设计多个 cidr,那不是又有多条规则了?
ipset 可以拯救你于水火之中。

首先创建一个 ipset,然后加入 cidr (可以加多个)

ipset create ctr-cidrs hash:net
ipset add ctr-cidrs 172.18.1.0/25

然后在 iptables 中引用该 setiptables -t nat -A POSTROUTING -m set --match-set ctr-cidrs src -m set ! --match-set ctr-cidrs dst -j MASQUERADE --random-fully,意思是源 IP 属于 set,而目的 IP 不属于 set 的报文需要做 SNAT(也就是容器访问非容器)。这样就能所有节点共享一条 iptables 规则,既提升了效率,又降低了维护成本

额外可以再提一下,容器访问节点 IP 的话,即使不做 SNAT 也能通,只是去包走 underlay link,回包却走的是 vxlan,这种巧合有没有用呢?留给读者去分析吧~


MageekChiu
4.4k 声望1.7k 粉丝

T