Docker的网络实现
Docker网络模型
安装Docker以后,会默认创建三种网络,可以通过docker network ls
查看:
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
33f07da2cd08 bridge bridge local
4fd0353013a1 host host local
f1dc5101bd58 none null local
这三种开箱即用的网络模型说明如下:
网络模式 | 配置 | 说明 |
---|---|---|
bridge模式 | –net=bridge | (默认为该模式)此模式会为每一个容器分配、设置IP等,并将容器连接到一个docker0虚拟网桥,通过docker0网桥以及Iptables nat表配置与宿主机通信。 |
host模式 | –net=host | 容器将不会虚拟出自己的网卡,配置自己的 IP 等,而是使用宿主机的 IP 和端口。 |
none模式 | –net=none | 空置模式下,Docker 会给新容器创建独立的网络名称空间,但是不会创建任何虚拟的网络设备,此时容器能看到的只有一个回环设备(Loopback Device)而已。提供这种方式是为了方便用户去做自定义的网络配置,如自己增加网络设备、自己管理 IP 地址,等等 |
Bridge模式
在bridge模式下,Docker Daemon第一次启动时会创建一个虚拟的网桥(docker0
),然后会给这个网桥分配一个子网。针对由Docker创建的每一个容器,都会创建一个虚拟的以太网设备(veth pair设备对),其中一端关联到网桥上,以类似vethxxx命名,另一端使用Linux网络命名空间技术,映射到容器内的eth0设备,然后从网桥的地址段给eth0接口分配一个IP地址。
如上图,172.17.0.1/16
是网桥的IP地址,Docker Daemon会在几个备选地址里给它选择一个地址,通常是以172开头的一个地址,这个地址和主机的IP地址是不重叠的。172.17.0.2/16、172.17.0.3/16
分别是容器1和容器2的IP地址,10.20.0.129/16
是宿主机的IP地址。
现在,我们以一个基于buybox
镜像构建的容器作为一个具体案例,深入了解bridge模式:
-
运行一个容器名为box1,通过
ip addr
查看eth0网卡IP为:172.17.0.2/16
$ docker run -it --name box1 busybox / # ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue 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 181: eth0@if182: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 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
-
在宿主机上查看名为vethxxx网络,可以看到网络接口为:
veth3b625ab@if183
即为容器box1网卡eth0的veth pair设备对的对端$ ip addr 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: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 52:54:00:1b:75:f8 brd ff:ff:ff:ff:ff:ff inet 10.20.0.129/24 brd 10.20.0.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::5054:ff:fe1b:75f8/64 scope link 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:ee:42:61:4e 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:eeff:fe42:614e/64 scope link valid_lft forever preferred_lft forever 184: veth3b625ab@if183: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether be:f8:bf:51:ea:9a brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet6 fe80::bcf8:bfff:fe51:ea9a/64 scope link valid_lft forever preferred_lft forever
-
还可以通过
docker inspect <容器名称|ID>
进行查看,在NetworkSettings节点中可以看到详细信息$ docker inspect box1 "NetworkSettings": { "Bridge": "", "SandboxID": "665585a911b6edbe58d54582eb51973272cc8e7cf19023fd0ba76744dfafc837", "HairpinMode": false, "LinkLocalIPv6Address": "", "LinkLocalIPv6PrefixLen": 0, "Ports": { }, "SandboxKey": "/var/run/docker/netns/665585a911b6", "SecondaryIPAddresses": null, "SecondaryIPv6Addresses": null, "EndpointID": "1082b2a470ac0cf059098e3923d8940fb9e7b1bfed7457ae2e6f47d0bb573bd8", "Gateway": "172.17.0.1", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "IPAddress": "172.17.0.2", "IPPrefixLen": 16, "IPv6Gateway": "", "MacAddress": "02:42:ac:11:00:02", "Networks": { "bridge": { "IPAMConfig": null, "Links": null, "Aliases": null, "NetworkID": "33f07da2cd08d7dfb0409293dd067db4daba6d665115b340dc869b3a21029116", "EndpointID": "1082b2a470ac0cf059098e3923d8940fb9e7b1bfed7457ae2e6f47d0bb573bd8", "Gateway": "172.17.0.1", "IPAddress": "172.17.0.2", "IPPrefixLen": 16, "IPv6Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "MacAddress": "02:42:ac:11:00:02", "DriverOpts": null } } }
可以看到容器的默认网关是docker0的IP地址:172.17.0.1,也可以通过
docker network inspect bridge
命令查看所有bridge网络模式下的容器
bridge模式下的容器网络和宿主机属于不同的IP网段。这样做,结果就是在同一台机器内的容器之间可以相互通信,不同主机上的容器不能相互通信。为了让不同主机上的容器能跨节点相互通信,就必须在主机的地址上分配一个端口,然后通过这个端口路由或者代理到容器上,这种模式也就是Host模式。
Host模式
如果容器启动时采用host模式,那么这个容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个网络命名空间。也就是说容器将不会虚拟出自己的网卡,配置自己的IP地址等,而是使用宿主机的IP和端口。但容器其他方面,比如文件系统、进程列表等还是和宿主机是隔离的。
使用host模式的容器可以直接使用宿主机的IP地址与外界通信,容器内部的服务端口也可以使用宿主机的端口,不需要进行NAT。所以,host最大的优势就是网络性能比较好。但缺点也很明显,在不同容器之间协调好端口分配是一件困难的事,特别是服务水平扩展时,动态的端口会带来高度的复杂度。
让我们一个基于nginx
镜像构建的容器作为一个具体案例,深入了解host模式:
-
启动一个nginx镜像,容器名称为: ngx。查看host模型下的容器,可以看到此容器
$ docker run -it --network host --name ngx nginx $ docker network inspect host [ { "Name": "host", "Id": "4fd0353013a1827f8b7e562a554a550b7fbbd34515ce58e8377cbb02eabfb79f", "Created": "2022-03-29T11:31:19.037479611+08:00", "Scope": "local", "Driver": "host", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": { "630358a2627b5fdbbf57d630246922f2ee26d1975fe073f7a9bf1b7cff5e6196": { "Name": "ngx", "EndpointID": "f254380df3f5002c5251aacd13e058f9013599405c0e3b1881e5b17f059a038d", "MacAddress": "", "IPv4Address": "", "IPv6Address": "" } }, "Options": {}, "Labels": {} } ]
-
查看宿主机下80端口
$ netstat -ntlp Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 8209/nginx: master tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1277/sshd tcp6 0 0 :::80 :::* LISTEN 8209/nginx: master
-
尝试用
curl
命令验证nginx$ curl 127.0.0.1 <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> html { color-scheme: light dark; } body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html>
-
查看nginx日志验证宿主机上的访问是否指向容器
$ docker logs ngx /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/ /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh /docker-entrypoint.sh: Configuration complete; ready for start up 2022/12/29 06:11:12 [notice] 1#1: using the "epoll" event method 2022/12/29 06:11:12 [notice] 1#1: nginx/1.23.3 2022/12/29 06:11:12 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6) 2022/12/29 06:11:12 [notice] 1#1: OS: Linux 3.10.0-1160.11.1.el7.x86_64 2022/12/29 06:11:12 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576 2022/12/29 06:11:12 [notice] 1#1: start worker processes 2022/12/29 06:11:12 [notice] 1#1: start worker process 29 2022/12/29 06:11:12 [notice] 1#1: start worker process 30 2022/12/29 06:11:12 [notice] 1#1: start worker process 31 2022/12/29 06:11:12 [notice] 1#1: start worker process 32 127.0.0.1 - - [29/Dec/2022:06:11:50 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.29.0" "-"
none模型
none 网络模式即不为 Docker Container 创建任何的网络环境,容器内部就只能使用 loopback 网络设备,不会再有其他的网络资源。可以说 none 模式为 Docke Container 做了极少的网络设定,但是俗话说得好“少即是多”,在没有网络配置的情况下,作为 Docker 开发者,才能在这基础做其他无限多可能的网络定制开发。这也恰巧体现了 Docker 设计理念的开放。
在创建容器时通过参数 --net none
或者 --network none
指定;
比如我基于 none
网络模式创建了一个基于 busybox
镜像构建的容器 bbox-none
,查看 ip addr
:
$ docker run -it --net none --name bbox-none busybox
/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue 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
其他网络模型
除了三种开箱即用的网络外,Docker 还支持以下由用户自行创建的网络:
- 容器模式,创建容器后使用
--network=container:容器名称
指定。容器模式下,新创建的容器将会加入指定的容器的网络名称空间,共享一切的网络资源,但其他资源,如文件、PID 等默认仍然是隔离的。两个容器间可以直接使用回环地址(localhost)通信,端口号等网络资源不能有冲突。 - MACVLAN 模式:使用
docker network create -d macvlan
创建,此网络允许为容器指定一个副本网卡,容器通过副本网卡的 MAC 地址来使用宿主机上的物理设备,在追求通信性能的场合,这种网络是最好的选择。Docker 的 MACVLAN 只支持 Bridge 通信模式,因此在功能表现上与桥接模式相类似。 - Overlay 模式:使用
docker network create -d overlay
创建,Docker 说的 Overlay 网络实际上就是特指 VXLAN,这种网络模式主要用于 Docker Swarm 服务之间进行通信。然而由于 Docker Swarm 败于 Kubernetes,并未成为主流,所以这种网络模式实际很少使用。
容器模式
Container 网络模式是 Docker 中一种较为特别的网络的模式。在创建容器时通过参数 --net container:已运行的容器名称|ID
或者 --network container:已运行的容器名称|ID
指定;
这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过 lo 网卡设备通信。
如果已经掌握了Kubernetes的POD网络模型的话,一个Pod里有多个容器(docker),多个容器共享一个网络模型。多个容器之间通过lo设备通信,是不是很类似?
还是拿上面buybox
镜像构建的容器举例:
$ docker run -it --name box busybox
/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue 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
189: eth0@if190: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
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
$ docker run -it --net container:box --name box1 busybox
/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue 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
189: eth0@if190: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
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
$ ip addr
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: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 52:54:00:1b:75:f8 brd ff:ff:ff:ff:ff:ff
inet 10.20.0.129/24 brd 10.20.0.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe1b:75f8/64 scope link
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:ee:42:61:4e 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:eeff:fe42:614e/64 scope link
valid_lft forever preferred_lft forever
190: veth8e3e0c5@if189: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether fe:d2:b3:d8:57:09 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::fcd2:b3ff:fed8:5709/64 scope link
valid_lft forever preferred_lft forever
可以看到两个容器共享了第一个容器IP、端口。
小结
官网是这么定义Docker网络模型的:
- 当您需要多个容器在同一台 Docker 主机上进行通信时,用户定义的桥接网络是最佳选择。
- 当网络堆栈不应该与 Docker 主机隔离,但您希望容器的其他方面被隔离时,主机网络是最好的。
- 当您需要在不同 Docker 主机上运行的容器进行通信时,或者当多个应用程序使用 swarm 服务协同工作时,覆盖网络是最佳选择。
- 当您从 VM 设置迁移或需要您的容器看起来像网络上的物理主机时,Macvlan 网络是最好的,每个主机都有一个唯一的 MAC 地址。
- 第三方网络插件允许您将 Docker 与专用网络堆栈集成。
此外,还可以通过 docker network create
命令创建自定义网络模型。