本质上要解决的问题是连通 host 和 netns
, 来体会一下两者的不同思路.
用变量方便大家阅读, 否则区分不出语法和变量
NS=dock-ns3
VETH=vd3
BRIDGE=dock3
先创建 network namespace, 以下简称 netns
ip netns add $NS
创建 veth pair:
ip l add $VETH type veth peer name $VETH-peer
veth 拉起来, 一端塞到 netns 里, 在 netns 里拉起来, 设置 ip
ip l set $VETH-peer up
ip l set $VETH netns $NS
ip netns exec $NS ip l set $VETH up
ip netns exec $NS ip a add 10.5.0.2/16 dev $VETH
创建 bridge, 拉起来
ip l add name $BRIDGE type bridge
ip l set $BRIDGE up
把 veth 另一端加入 bridge
ip l set $VETH-peer master $BRIDGE
最后把 bridge 也设置上 ip:
ip a add 10.5.0.1/16 dev $BRIDGE
宿主机上的路由也会自动加上:
# ip r | grep dock3
10.5.0.0/16 dev dock3 proto kernel scope link src 10.5.0.1
这时候就可以在宿主机 ping netns 里的 ip 了.
如果按照上面的流程再创建另一个 netns 和 veth pair 并加入 bridge, 那么两个 netns 已经可以互 ping 了.
不过还有问题, netns 里 ping 不了外面的世界, 这是因为 netns 里的路由没有默认 gateway:
ip netns exec $NS ip r add default via 10.5.0.1
不过依然不可以, 这是因为没有 SNAT
iptables -t nat -A POSTROUTING -s 10.5.0.0/16 -j MASQUERADE
完工.
这里的几个细节几乎和大家熟悉的 docker0 if 一模一样, 我枚举一下:
- docker0 bridge 等价于 dock3 bridge
- netns 里的 route 设置一模一样, default gw 指向 bridge, 相同网络的指向 veth
- iptables nat 表 POSTROUTING chain 里把所在网络全部 SNAT
calico 的做法完全没有 bridge 这种妖艳贱货, 而是 veth 直通, 路由指路.
先设置变量:
NS=cali
VETH=v-cali
创建 netns 和 veth, veth 一端塞进去, 射 ip:
ip netns add $NS
ip l add $VETH type veth peer name $VETH-peer
ip l set $VETH-peer up
ip l set $VETH netns $NS
ip netns exec $NS ip l set $VETH up
ip netns exec $NS ip a add 10.2.0.1/32 dev $VETH
然后在宿主机直接路由 ip 到 veth:
ip r add 10.2.0.1/32 dev $VETH-peer
netns 里设置 default gw 到 veth:
ip netns exec $NS ip r add default dev $VETH
这时候可以从 host ping netns, 但是反过来就不可达, 抓包发现是因为 arp 不知道 mac 地址, 加上 arp proxy
echo 1 > /proc/sys/net/ipv4/conf/$VETH-peer/proxy_arp
sysctl -p
发现还是不行, 抓包发现 arp 没问题了但是 ICMP 没有回复, 这是因为 SNAT:
iptables -t nat -A POSTROUTING -s 10.6.0.1/32 -j MASQUERADE
这下可以和 host 互 ping 了, google.com 也没问题了, 功能上没问题.
不过有个优化的问题, arp proxy 会有一些问题, 比如这里会导致 netns 里的 arp cache 无限扩张, 所有的 outbound ip 都会产生一条 arp entry.
为了解决这个问题, 我们用一个假的 ip 169.254.1.1 作为 link-local address, 绕一下:
ip netns exec $NS ip r del default dev $VETH
ip netns exec $NS ip r add 169.254.1.1 dev $VETH scope link
ip netns exec $NS ip r add default via 169.254.1.1 dev $VETH
这下所有的 outbound 都会变成对 169.254.1.1 arp, 也不会产生无数的 entry.
和 calico 比较一下, 要点有这么几个:
- arp proxy 代替了 bridge 打通二层
- 169.254.1.1 这个 ip 也是 calico 使用的 ip, 在 calico 创建的 netns 路由里这个迷一样的 ip 让无数人神魂颠倒
当然剩下的事情我就不说了, bird 监听到宿主机的新路由后用 BGP 广播 Update 消息, 从而在完成跨宿主机的路由建立.