前言
通过文章 容器的本质可知,容器只是一个进程,而容器所能看到的网络栈,是隔离在自己的 Network Namespace 中。docker 容器单机网络支持四种网络模式,也都是基于 Network Namespace 实现的。本文主要是介绍这四种模式的使用方法及实现原理。
host
使用该模式的容器和宿主机是在同一个 Network Namespace 中的,所以和宿主机用的是同一个网络栈,那么容器暴露的端口,也就是宿主机上端口。
注意,使用该模式,需要关注端口冲突
通过添加 --net=host
参数即可开启 host 模式
docker run -d --net=host nginx
因为和宿主机使用的是同一个网络栈,所以容器与宿主机是可以互相连通的,在宿主机上直接可以通过 127.0.0.1 访问到该容器的的端口。
curl 127.0.0.1
运行另一个容器进入其中执行 curl 127.0.0.1
可以看到一样可以访问到 nginx 暴露的 80 端口,因为都是使用宿主机网络栈。
docker run -it --net=host curlimages/curl curl 127.0.0.1
bridge
原理
该模式为桥接模式,创建容器时会创建属于自己的 Network Namepsace,该容器和宿主机使用的是不同的 Network Namespace,也就是说它们使用的是不同的网络栈。
bridge 网络模型的实现原理可以参考文章 手动实现docker容器bridge网络模型
宿主机创建了 docker0 作为虚拟网桥,其作用主要是作为交换机在二层网络,再将使用 bridge 模式创建的容器通过 veth pair 连接到 dcoker0 上,这样连接到 docker0 上的容器都可以互相网络通信。
veth pair 类似一个管道,数据包会从一端到另一端。
验证
默认运行容器时使用的就是 bridge
模式,docker 会自动为容器添加 veth pair 并配置好其 ip 地址,这里的 eth0 就是其中的一端,可以看到其 ip 地址为 172.17.0.2
[root@localhost ~]# docker run -d --name nginx1 nginx
[root@localhost ~]# docker exec -it nginx1 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
20: eth0@if21: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
veth pair 的另一端会接入到 docker0 上,在容器中执行以下命令可以看到 veth pair 另一端的序号
[root@localhost ~]# docker exec nginx1 cat /sys/class/net/eth0/iflink
21
在宿主机上可以看到 21 序号上的 veth pair 的名称是 veth702ba20,也就是管道的另一端。
[root@localhost ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether fe:fc:fe:af:4b:ea brd ff:ff:ff:ff:ff:ff
inet 10.61.74.37/23 brd 10.61.75.255 scope global noprefixroute ens18
valid_lft forever preferred_lft forever
inet6 fe80::1bdd:fe7:4a90:1a67/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:84:c1:3b:ea brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:84ff:fec1:3bea/64 scope link
valid_lft forever preferred_lft forever
21: veth702ba20@if20: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 0a:21:51:0c:7e:db brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::821:51ff:fe0c:7edb/64 scope link
valid_lft forever preferred_lft forever
再查看 docker0 插入的设备可以发现 veth702ba20 是插入在 docker0 上的。
[root@localhost ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.024284c13bea no veth702ba20
容器网络互通
再创建另一个容器 nginx2,查看其 ip 为 172.17.0.3,可以发现 nginx1 是可以 ping 通 nginx2 的该 ip 的。
[root@localhost ~]# docker run -d --name nginx2 nginx
[root@localhost ~]# docker exec nginx2 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
54: eth0@if55: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
为什么可以互通呢?
我们来看下 nginx1 的路由,当 ping nginx2 的 ip 时,会匹配到第二条路由,然后走 eth0 网卡,因为其是 veth pair 的一端,数据包会在另一端出现,另一端接入到了 docker0 上,最终数据包到达 docker0
[root@localhost ~]# docker exec nginx1 ip r
default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2
当通过 nginx1 ping nginx2 的 ip 时,我过监听 docker0 网卡看一下数据包:
[root@localhost ~]# docker exec -it nginx1 ping 172.17.0.3 -c 3
[root@localhost ~]# tcpdump -i docker0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on docker0, link-type EN10MB (Ethernet), capture size 262144 bytes
04:32:57.596814 ARP, Request who-has 172.17.0.3 tell 172.17.0.2, length 28
04:32:57.596848 ARP, Reply 172.17.0.3 is-at 02:42:ac:11:00:03 (oui Unknown), length 28
04:32:57.596853 IP 172.17.0.2 > 172.17.0.3: ICMP echo request, id 17, seq 1, length 64
04:32:57.596896 IP 172.17.0.3 > 172.17.0.2: ICMP echo reply, id 17, seq 1, length 64
04:32:58.596437 IP 172.17.0.2 > 172.17.0.3: ICMP echo request, id 17, seq 2, length 64
04:32:58.596492 IP 172.17.0.3 > 172.17.0.2: ICMP echo reply, id 17, seq 2, length 64
04:32:59.596444 IP 172.17.0.2 > 172.17.0.3: ICMP echo request, id 17, seq 3, length 64
04:32:59.596491 IP 172.17.0.3 > 172.17.0.2: ICMP echo reply, id 17, seq 3, length 64
04:33:02.598361 ARP, Request who-has 172.17.0.2 tell 172.17.0.3, length 28
04:33:02.598386 ARP, Reply 172.17.0.2 is-at 02:42:ac:11:00:02 (oui Unknown), length 28
由上可知,nginx1(10.17.0.2) 会发送 ARP 获取 nginx2(10.17.0.3) 的 mac 地址,然后使用该 mac 地址通过二层设备 bridge 向 nginx2 转发数据包,进入到了 nginx2 的 Network Namespace 中,由它的网络栈处理该数据包,最后回包。
容器连接其他主机
容器内连接其他主机时,比如 ping 10.65.132.187 时,会先通过 docker0 达到宿主机上,然后通过宿主机的网络栈处理。
通过查看宿主机路由表,到达宿主机的数据表会走第一条默认路由,通过 eth0 网卡下一跳到 10.61.74.1,然后最终达到另一台主机的 eth0 中。
[root@localhost ~]# ip r
default via 10.61.74.1 dev eth0 proto static metric 100
10.61.74.0/23 dev eth0 proto kernel scope link src 10.61.74.37 metric 100
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
container
使用该模式的容器会加入到指定容器的 Network Namespace 中,也就是两个容器共用同一个网络栈。
首先使用 bridge 模式创建容器 nginx1,该容器会拥有自己的 Network Namespace,然后再使用 container 模式创建 nginx2 容器并加入 nginx1 的 Network Namespace 中。
通过查看两个容器的网卡可以发现两个是一样的。
[root@localhost ~]# docker run -d --name nginx1 nginx
[root@localhost ~]# docker exec -it nginx1 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
56: eth0@if57: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
[root@localhost ~]# docker run -it --name nginx2 --net=container:nginx1 nginx /bin/bash
[ root@20069e4c2bde:/etc/nginx ]$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
56: eth0@if57: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
none
该模式创建容器也会创建新的属于自己的 Network Namespace,但是容器内不会有任何的网络配置,没有网卡、路由、路由等信息,需要由我们自己去配置。
[root@localhost ~]# docker run -it --name nginx --net=none nginx /bin/bash
[ root@52480b0a4725:/etc/nginx ]$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
[ root@52480b0a4725:/etc/nginx ]$ ip r
欢迎关注,互相学习,共同进步~
我的个人博客
公众号:编程黑洞