1. 简介

容器网络实质上是由 Docker 为应用程序所创造的虚拟环境的一部分,它能让应用从宿主机操作系统的网络环境中独立出来,形成容器自有的网络设备、IP 协议栈、端口套接字、IP 路由表、防火墙等与网络相关的模块。

1.1 CNM

Container Network Model,它是 Docker 网络架构采用的设计规范。只要符合该模型的网络接口就能被用于容器之间通信,而通信的过程和细节可以完全由网络接口来实现。

docker 网络 - 图1

CNM 的网络组成:

  • Sandbox: 提供容器的虚拟网络栈,即端口套接字,IP路由表、iptables配置,DNS等。用于隔离容器网络和宿主机网络
  • Network: Docker 虚拟网络,与宿主机网络隔离,只有参与者能够通信
  • Endpoint: 容器内的虚拟网络接口,负责与Docker虚拟网络连接

1.2 Libnetwork

docker 网络 - 图2

1.3 网络驱动

主要讲的宿主机内部

  • 宿主机内部:bridge,host,container,none ,用户自定义
  • 跨主机:overlay,macvlan (适合swarm等容器编排工具)
  • 第三方:flannel,weave,calico

1.4 支撑技术

  • network namespace:用于隔离容器网络资源(IP、网卡、路由等)。netns可确保同一主机上的两个容器无法相互通信,甚至不能与主机本身进行通信,除非配置为通过docker网络进行通信。CNM网络驱动程序为每个容器实现单独的netns。但是,容器可以共享相同的netns,甚至可以是主机的netns的一部分。
  • veth pair:用于不同 netns 间进行通信。veth是全双工链接,在每个名称空间中都有一个接口,充当两个网络名称空间之间的连接线,负责将一个 netns 数据发往另一个 netns 的 veth。如当容器连接到docker网络时,veth的一端放置在容器内部(通常视为ethX接口),而另一端连接到Docker网络(vethxxx)。
  • iptables:包过滤,端口映射和负载均衡

1.5 禁用容器互联

默认情况下,同一台宿主机上的容器,是互通的,如果需要隔离,修改daemon.json

  1. {
  2. "icc": false
  3. }

2. 四种网络模式

Docker 安装后,会自动创建三个网络

  1. kpsmile@kpsmile-PC:~/Desktop$ docker network ls
  2. NETWORK ID NAME DRIVER SCOPE
  3. 92116da8525d bridge bridge local
  4. ced5a1c478a9 host host local
  5. 147ea2aabe54 none null local
网络模式 配置 说明
bridge –net=bridge 为每一个容器分配、设置 IP 等,并将容器连接到 docker0 虚拟网桥上,默认模式
host –net=host 容器不会创建自己的网卡,配置 IP 等,而是使用宿主机的 IP 和端口
contain –net=container:NAME_or_ID 容器不会创建自己的网卡,配置 IP 等,而是和一个指定的容器共享 IP 和端口
none –net=none 关闭网络功能,不进行任何网络设置

2.1 bridge

当Docker进程启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。

从docker0子网中分配一个IP给容器使用,并设置docker0的IP地址为容器的默认网关。在主机上创建一对虚拟网卡veth pair设备,Docker将veth pair设备的一端放在新创建的容器中,并命名为eth0(容器的网卡),另一端放在主机中,以vethxxx这样类似的名字命名,并将这个网络设备加入到docker0网桥中。可以通过brctl show命令查看。

bridge模式是docker的默认网络模式,不写–net参数,就是bridge模式。使用docker run -p时,docker实际是在iptables做了DNAT规则,实现端口转发功能。可以使用iptables -t nat -vnL查看。bridge模式如下图所示:

docker 网络 - 图3

当Docker server启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker容器会连接到这个虚拟网桥上。Docker0使用到的技术是evth-pair技术。在默认bridge网络模式下,我们每启动一个Docker容器,Docker就会给Docker容器配置一个ip。

Docker容器完成bridge网络配置的过程如下:

  • 在主机上创建一对虚拟网卡**<font style="color:#E8323C;">veth pai</font>**r设备。veth设备总是成对出现的,它们组成了一个数据的通道,数据从一个设备进入,就会从另一个设备出来。因此,veth设备常用来连接两个网络设备。
  • **<font style="color:#E8323C;">Docker</font>****<font style="color:#E8323C;">veth pair</font>**设备的一端放在新创建的容器中,并命名为**<font style="color:#E8323C;">eth0</font>**。另一端放在主机中,以**<font style="color:#E8323C;">veth65f9</font>**这样类似的名字命名,并将这个网络设备加入到**<font style="color:#E8323C;">docker0</font>**网桥中。
  • **<font style="color:#E8323C;">docker0</font>**子网中分配一个IP给容器使用,并设置**<font style="color:#E8323C;">docker0</font>**的IP地址为容器的默认网关

2.2 host

docker 网络 - 图4

直接使用宿主机的 IP 地址与外界进行通信,不再需要额外进行NAT 转换。

容器将不会获得一个独立的Network Namespace, 而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡而是使用宿主机的IP和端口。

  1. #错误写法 docke启动时会发出警告
  2. #原因:docker启动时指定--network=host或-net=host,如果还指定了-p映射端口,那这个时候就会有此警告,并且通过-p设置的参数将不会起到任何作用,端口号会以主机端口号为主,重复时则递增。
  3. #解决:解决的办法就是使用docker的其他网络模式,例如--network=bridge,这样就可以解决问题,或者直接无视
  4. docker run -d -p 8083:8080 --network host --name tomcat83 billygoo/tomcat8-jdk8
  5. #正确写法
  6. docker run -d --network host --name tomcat83 billygoo/tomcat8-jdk8

2.3 container

这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过 lo 网卡设备通信。Container模式模型示意图如下:

docker 网络 - 图5

2.4 none

使用none模式,Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。

这种网络模式下容器只有lo回环网络,没有其他网卡。none模式可以在容器创建时通过–network=none来指定。这种类型的网络没有办法联网,封闭的网络能很好的保证容器的安全性。

None模式示意图:

docker 网络 - 图6

3.自定义网桥

  1. # 创建网桥
  2. kpsmile@kpsmile-PC:~/Desktop$ docker network create --driver bridge --subnet 10.0.0.0/16 --gateway 10.0.0.1 --opt "com.docker.network.bridge.name"="docker1" myBridge
  3. 796772d9de414642d99e52fdf32a6a044de991756cde34979cdfb994c218517a
  4. # 参数说明
  5. # --driver bridge #指定bridge驱动程序来管理网络
  6. # --subnet 10.0.0.0/16 #指定网段的CIDR格式的子网
  7. # --gateway 10.0.0.1 #指定主子网的IPv4或IPv6网关
  8. # --opt com.docker.network.bridge.name 创建Linux网桥时要使用的网桥名称
  9. # --opt com.docker.network.bridge.enable_ip_masquerade 启用ip伪装
  10. # --opt com.docker.network.bridge.enable_icc 启用或禁用跨容器连接
  11. # --opt com.docker.network.bridge.host_binding_ipv4 绑定容器端口时的默认IP
  12. # --opt com.docker.network.driver.mtu 设置容器网络MTU
  13. kpsmile@kpsmile-PC:~/Desktop$ docker network ls
  14. NETWORK ID NAME DRIVER SCOPE
  15. 92116da8525d bridge bridge local
  16. ced5a1c478a9 host host local
  17. 796772d9de41 myBridge bridge local
  18. 147ea2aabe54 none null local
  19. kpsmile@kpsmile-PC:~/Desktop$ ip addr show docker1
  20. 9: docker1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
  21. link/ether 02:42:bb:47:c1:10 brd ff:ff:ff:ff:ff:ff
  22. inet 10.0.0.1/16 brd 10.0.255.255 scope global docker1
  23. valid_lft forever preferred_lft forever
  1. # 运行容器使用新创建的网桥
  2. kpsmile@kpsmile-PC:~/Desktop$ docker run -itd --name busybox --network myBridge busybox
  3. 903e9abfe34d729a118ea535904a592530fa351dbecf35e90229d334297f0df2
  4. kpsmile@kpsmile-PC:~/Desktop$ ip add
  5. ......
  6. 11: veth751becc@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker1 state UP group default
  7. link/ether b2:91:62:3b:9b:4e brd ff:ff:ff:ff:ff:ff link-netnsid 0
  8. inet6 fe80::b091:62ff:fe3b:9b4e/64 scope link
  9. valid_lft forever preferred_lft forever
  10. kpsmile@kpsmile-PC:~/Desktop$ docker exec -it busybox ip addr
  11. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
  12. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  13. inet 127.0.0.1/8 scope host lo
  14. valid_lft forever preferred_lft forever
  15. 10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
  16. link/ether 02:42:0a:00:00:02 brd ff:ff:ff:ff:ff:ff
  17. inet 10.0.0.2/16 brd 10.0.255.255 scope global eth0
  18. valid_lft forever preferred_lft forever

4.容器互联

  1. # 使用上一节创建的网桥
  2. docker run -d -P --name tomcat-net-01 --net myBridge tomcat
  3. docker run -d -P --name tomcat-net-02 --net myBridge tomcat
  4. # 网络描述中Containers字段有创建的两个tomcat的Container
  5. docker network inspect myBridge
  6. ...
  7. "Containers": {
  8. "6a0f9671566a566c37b8ebb8dd1b9ee4bfee53cdc51563120b1688d6b5aac508": {
  9. "Name": "tomcat-net-01",
  10. "EndpointID": "a68744684c3bd2ac004d23e48e77f8b7cd85ab731bcf2726bc38899081907071",
  11. "MacAddress": "02:42:0a:00:00:02",
  12. "IPv4Address": "10.0.0.2/16",
  13. "IPv6Address": ""
  14. },
  15. "a2b9c222c65865ab55c8577b15ba3729347a77ca4bab42dbd39204550db64718": {
  16. "Name": "tomcat-net-02",
  17. "EndpointID": "71282783cf8a3bf560894e42d59f060b18693e419b83beff1e4f44cc72464a67",
  18. "MacAddress": "02:42:0a:00:00:03",
  19. "IPv4Address": "10.0.0.3/16",
  20. "IPv6Address": ""
  21. }
  22. },
  23. ...
  24. root@kpsmile-machine:/home/kpsmile/Desktop# docker exec -it tomcat-net-01 ifconfig
  25. eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
  26. inet 10.0.0.2 netmask 255.255.0.0 broadcast 10.0.255.255
  27. ether 02:42:0a:00:00:02 txqueuelen 0 (Ethernet)
  28. RX packets 3870 bytes 9079380 (8.6 MiB)
  29. RX errors 0 dropped 0 overruns 0 frame 0
  30. TX packets 3477 bytes 244413 (238.6 KiB)
  31. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
  32. lo: ...
  33. root@kpsmile-machine:/home/kpsmile/Desktop# docker exec -it tomcat-net-02 ifconfig
  34. eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
  35. inet 10.0.0.3 netmask 255.255.0.0 broadcast 10.0.255.255
  36. ether 02:42:0a:00:00:02 txqueuelen 0 (Ethernet)
  37. RX packets 3870 bytes 9079380 (8.6 MiB)
  38. RX errors 0 dropped 0 overruns 0 frame 0
  39. TX packets 3477 bytes 244413 (238.6 KiB)
  40. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
  41. lo: ...
  42. # 假如不使用自定义网络容器之间只能通过ip来进行
  43. docker exec -it tomcat-net-01 ping 10.0.0.3
  44. PING 10.0.0.3 (10.0.0.3): 56 data bytes
  45. 64 bytes from 10.0.0.3: icmp_seq=0 ttl=64 time=0.073 ms
  46. 64 bytes from 10.0.0.3: icmp_seq=1 ttl=64 time=0.115 ms
  47. docker exec -it tomcat-net-02 ping 10.0.0.2
  48. PING 10.0.0.2 (10.0.0.2): 56 data bytes
  49. 64 bytes from 10.0.0.2: icmp_seq=0 ttl=64 time=0.071 ms
  50. 64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.122 ms
  51. # 假如使用自定义网络,同一网络下容器之间可以通过容器名来进行
  52. docker exec -it tomcat-net-02 ping tomcat-net-01
  53. PING tomcat-net-01 (10.0.0.2): 56 data bytes
  54. 64 bytes from 10.0.0.2: icmp_seq=0 ttl=64 time=0.089 ms
  55. 64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.117 ms
  56. docker exec -it tomcat-net-01 ping tomcat-net-02
  57. PING tomcat-net-02 (10.0.0.3): 56 data bytes
  58. 64 bytes from 10.0.0.3: icmp_seq=0 ttl=64 time=0.109 ms
  59. 64 bytes from 10.0.0.3: icmp_seq=1 ttl=64 time=0.233 ms

5.docker网络之间的互联

没有设置的情况下,不同网络间的容器是无法进行网络连接的。如图,两个不同的网络docker0和自定义网络mynet的网络模型图:

docker 网络 - 图7

在默认网络bridge下启动容器tomcat-01,尝试连接mynet网络下的tomcat-net-01容器:

  1. docker run -d -P --name=myTomcat01 tomcat
  2. # myTomcat01容器ip为172.17.0.2,此时是挂载在默认网络bridge下
  3. docker exec -it myTomcat01 ifconfig
  4. eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
  5. inet 172.17.0.2 netmask 255.255.0.0 broadcast 172.17.255.255
  6. ether 02:42:ac:11:00:02 txqueuelen 0 (Ethernet)
  7. RX packets 3950 bytes 9086194 (8.6 MiB)
  8. RX errors 0 dropped 0 overruns 0 frame 0
  9. TX packets 3399 bytes 236706 (231.1 KiB)
  10. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
  11. lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
  12. inet 127.0.0.1 netmask 255.0.0.0
  13. loop txqueuelen 1000 (Local Loopback)
  14. RX packets 0 bytes 0 (0.0 B)
  15. RX errors 0 dropped 0 overruns 0 frame 0
  16. TX packets 0 bytes 0 (0.0 B)
  17. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
  18. # 此时ping不通
  19. docker exec -it myTomcat01 ping tomcat-net-02
  20. ping: unknown host

可以看到是无法网络连接的。不同Docker网络之间的容器需要连接的话需要把作为调用方的容器注册一个ip到被调用方所在的网络上。需要使用docker connect命令。

下面设置容器myTomcat01连接到mynet网络上。并查看mynet的网络详情,可以看到给容器myTomcat01分配了一个ip地址。

  1. docker network connect myBridge myTomcat01
  2. docker network inspect myBridge
  3. ...
  4. "Containers": {
  5. "32168b140f7d5f51ca2617581cd6670e8b9590b88567f1fd5cd979c605003318": {
  6. "Name": "tomcat-net-02",
  7. "EndpointID": "ace2762662651b91006ef00291d714664ea86dcc34b8a0974126f84a95a2a625",
  8. "MacAddress": "02:42:0a:00:00:03",
  9. "IPv4Address": "10.0.0.3/16",
  10. "IPv6Address": ""
  11. },
  12. "73b29f1586b6d243e33878e4333712b8c175492912bc41701e8683548a943425": {
  13. "Name": "myTomcat01",
  14. "EndpointID": "ff955362bad60596946c54805aea0038ed33039acb07c0957cd3b85439b4abdb",
  15. "MacAddress": "02:42:0a:00:00:04",
  16. "IPv4Address": "10.0.0.4/16",
  17. "IPv6Address": ""
  18. },
  19. "d39492cab3af71032b505fa6f440f37e2f1b9adca0e8a7078becc7d89e28bdaf": {
  20. "Name": "tomcat-net-01",
  21. "EndpointID": "a266cd157671450fcc660d1a844d27ad7ebacdb149793719f3481803201ccd2f",
  22. "MacAddress": "02:42:0a:00:00:02",
  23. "IPv4Address": "10.0.0.2/16",
  24. "IPv6Address": ""
  25. }
  26. },
  27. ...
  28. # 设置完成后我们就可以实现不同网络之间的容器互联了
  29. docker exec -it myTomcat01 ping tomcat-net-02
  30. PING tomcat-net-02 (10.0.0.3): 56 data bytes
  31. 64 bytes from 10.0.0.3: icmp_seq=0 ttl=64 time=0.168 ms
  32. 64 bytes from 10.0.0.3: icmp_seq=1 ttl=64 time=0.070 ms