容器之间互通
我们都知道,在服务器上部署了docker之后,docker给容器默认分配一个 172.17.0.0/16 的网段。一台机器上新创建的第一个容器,一般都会给 172.17.0.2 这个地址。因为 172.17.0.1给了docker0网桥,作为网关的地址。
我们在服务器上,先不要安装docker。通过手动创建网桥和veth来体验docker网络是如何打通的。
veth 作为一个二层设备,可以让两个隔离的网络名称空间之间可以互相通信,不需要反复多次经过网络协议栈, veth pair 是一端连着协议栈,另一端彼此相连的,数据之间的传输变得十分简单,这也让 veth 比起 tap/tun 具有更好的性能。
手动创建名为docker0的网桥。我们这里用192.168.0.1/24网段测试。
brctl addbr docker0
ip addr add 192.168.0.1/24 dev docker0
ip link set docker0 up
(yum install bridge-utils)
创建两个network ns 表示docker启动的两个容器的network ns
ip netns add docker1
ip netns add docker2
在 Linux 下,可以创建一对 veth pair 的网卡,从一边发送包,另一边就能收到。
创建一对veth。分别取名veth1和veth1p
ip link add name veth1 mtu 1500 type veth peer name veth1p mtu 1500
veth1放到网桥docker0上
ip link set veth1 master docker0
ip link set veth1 up
(master
选项用于将一个网络接口(在这个例子中是 veth1
)设置为另一个网络接口的从属接口(在这个例子中是 docker0
))
把veth1p放到容器1的ns docker1里
ip link set veth1p netns docker1
ip netns exec docker1 ip addr add 192.168.0.2/24 dev veth1p
ip netns exec docker1 ip link set veth1p up
同理配置容器2
ip link add name veth2 mtu 1500 type veth peer name veth2p mtu 1500
ip link set veth2 master docker0
ip link set veth2 up
ip link set veth2p netns docker2
ip netns exec docker2 ip addr add 192.168.0.3/24 dev veth2p
ip netns exec docker2 ip link set veth2p up
看一下现在网桥的情况
# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.1efbcbfd0489 no veth1
veth2
查看一下
# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:b9:a2:e0 brd ff:ff:ff:ff:ff:ff
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 1e:fb:cb:fd:04:89 brd ff:ff:ff:ff:ff:ff
5: veth1@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default qlen 1000
link/ether 5a:35:cd:a2:96:0e brd ff:ff:ff:ff:ff:ff link-netnsid 0
7: veth2@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default qlen 1000
link/ether 1e:fb:cb:fd:04:89 brd ff:ff:ff:ff:ff:ff link-netnsid 1
进入docker1
# ip netns exec docker1 ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
4: veth1p@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 42:3c:fd:7d:6d:3c brd ff:ff:ff:ff:ff:ff link-netnsid 0
从上图可以看到if接口的对应关系
在docker1中ping一下docker2
可以看到,通过网桥docker0实现了容器网络的互通。
访问外网
看下现在的主机路由
# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.249.243.254 0.0.0.0 UG 100 0 0 ens192
10.249.243.0 0.0.0.0 255.255.255.0 U 100 0 0 ens192
192.168.0.0 0.0.0.0 255.255.255.0 U 0 0 0 docker0
在主机上ping百度是可以的,在容器的网络空间里ping百度的地址发现不可达。
看下现在容器的路由
[root@k8s-node2 ~]# ip netns exec docker1 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.0.0 0.0.0.0 255.255.255.0 U 0 0 0 veth1p
只有一条对192.168.0.0/24网络的路由策略。
对于外网ip完全不知道该怎么走,当然访问不到。
手动配置一条路由
添加上默认路由规则,这样只要匹配不到其它规则的就默认走这条规则,发送到veth1p上
同时指定下一跳是它所连接的 网桥ip。
ip netns exec net1 route add default gw 192.168.0.1 veth1p
再查看一下现在docker1的路由
# ip netns exec docker1 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.0.1 0.0.0.0 UG 0 0 0 veth1p
192.168.0.0 0.0.0.0 255.255.255.0 U 0 0 0 veth1p
猜猜现在能通吗?
答案是不能
# ip netns exec docker1 ping 110.242.68.4
PING 110.242.68.4 (110.242.68.4) 56(84) bytes of data.
^C
--- 110.242.68.4 ping statistics ---
13 packets transmitted, 0 received, 100% packet loss, time 11999ms
[root@k8s-node2 ~]# ip netns exec docker1 ping 110.242.68.4 -c 2
PING 110.242.68.4 (110.242.68.4) 56(84) bytes of data.
— 110.242.68.4 ping statistics —
2 packets transmitted, 0 received, 100% packet loss, time 999ms
我们通过tcpdump对网卡docker0和ens192分别进行转包就能看出这个问题
tcpdump -i docker0 'dst 110.242.68.4'
tcpdump -i ens192 'dst 110.242.68.4'
物理网卡上一直没有收到包
因为现在包发到了网桥docker0上,但是没有转发给物理网卡(我这里是ens192)。
需要打开转发
(临时设置)
sysctl -w net.ipv4.ip_forward=1
查看一下
# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
再次测试,你会发现ping还是失败
但是,我们此时对es192进行抓包,发现有包了,就是说ip包转发已经生效。
这个时候考虑一个问题,192.168.0.2是私有ip,直接发到外网。外面的服务器是不认识这个私有ip的,那么ping包肯定是无法回来的。所以我们想到需要进行NAT操作。
我们现在要实现容器网络访问外网,就是在包出去的时候把源ip(容器ip),换成主机的外网ip,所以需要使用的是 SNAT。
iptables -t nat -A POSTROUTING -s 192.168.0.0/24 ! -o docker0 -j MASQUERADE
再次测试
哦吼,通了。
此时抓取两个网卡的报文,物理网卡的包源ip已经改成了主机ip
访问容器端口
好,我们已经解决了容器内访问外网的问题。
继续,容器更重要的功能是对外提供服务。
我们经常使用的
docker run -p 8000:80 xxx
使用容器80端口承接主机8000端口的流量访问。
这个是通过DNAT来实现。
我们先使用nc模拟一个服务
ip netns exec docker1 ncat -l -p 80
在容器网络空间docke1里监听80端口
配置DNAT规则
# iptables -t nat -A PREROUTING ! -i docker0 -p tcp -m tcp --dport 8000 -j DNAT --to-destination 192.168.0.2:80
! -i docker0是不要docker0来的流量,那就剩下的是外部来的流量。如果访问8000端口,就转到192.168.0.2:80
在外面随便找台机器,
telnet 10.249.x.x 8000
测试成功。
总结
所以,docker的网络实现,就是利用了linux的network namespace的隔离性。然后通过配置iptable规则进行NAT转换达到网络互通的功能。
当然,docker的实现要比我们的实验复杂一些,但是原理是相通的。
原创文章,作者:guozi,如若转载,请注明出处:https://www.sudun.com/ask/79313.html