docker容器单机网络
# 前言
通过文章 容器的本质 (opens new window)可知,容器只是一个进程,而容器所能看到的网络栈,是隔离在自己的 Network Namespace (opens new window) 中。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网络模型 (opens new window)
宿主机创建了 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
2
3
4
5
6
7
8
9
10
11
veth pair 的另一端会接入到 docker0 上,在容器中执行以下命令可以看到 veth pair 另一端的序号
[root@localhost ~]# docker exec nginx1 cat /sys/class/net/eth0/iflink
21
2
在宿主机上可以看到 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
再查看 docker0 插入的设备可以发现 veth702ba20 是插入在 docker0 上的。
[root@localhost ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.024284c13bea no veth702ba20
2
3
# 容器网络互通
再创建另一个容器 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
2
3
4
5
6
7
8
9
10
11
为什么可以互通呢?
我们来看下 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
2
3
当通过 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
由上可知,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
2
3
4
# 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 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
2
3
4
5
6
7
8