模拟实现docker网络

容器之间互通

docker0 网桥,通过这个网桥,容器直接实现相互访问。

我们都知道,在服务器上部署了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

image-20240520235449376

从上图可以看到if接口的对应关系

在docker1中ping一下docker2

image-20240520235634574

可以看到,通过网桥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

image-20240521001040016

在主机上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'

image-20240522161632225
tcpdump -i ens192 'dst 110.242.68.4'

image-20240522161648925

物理网卡上一直没有收到包

因为现在包发到了网桥docker0上,但是没有转发给物理网卡(我这里是ens192)。

需要打开转发

(临时设置)

sysctl -w net.ipv4.ip_forward=1

查看一下

# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1

再次测试,你会发现ping还是失败

但是,我们此时对es192进行抓包,发现有包了,就是说ip包转发已经生效。

image-20240522162720486

这个时候考虑一个问题,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

再次测试

image-20240522163434968

哦吼,通了。

image-20240522165346468

此时抓取两个网卡的报文,物理网卡的包源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

image-20240522172009041

测试成功。

总结

所以,docker的网络实现,就是利用了linux的network namespace的隔离性。然后通过配置iptable规则进行NAT转换达到网络互通的功能。

当然,docker的实现要比我们的实验复杂一些,但是原理是相通的。

 

原创文章,作者:guozi,如若转载,请注明出处:https://www.sudun.com/ask/79313.html

(0)
guozi的头像guozi
上一篇 2024年5月30日 下午6:26
下一篇 2024年5月30日 下午6:30

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注