1. 简介
容器网络实质上是由 Docker 为应用程序所创造的虚拟环境的一部分,它能让应用从宿主机操作系统的网络环境中独立出来,形成容器自有的网络设备、IP 协议栈、端口套接字、IP 路由表、防火墙等与网络相关的模块。
1.1 CNM
Container Network Model,它是 Docker 网络架构采用的设计规范。只要符合该模型的网络接口就能被用于容器之间通信,而通信的过程和细节可以完全由网络接口来实现。
CNM 的网络组成:
- Sandbox: 提供容器的虚拟网络栈,即端口套接字,IP路由表、iptables配置,DNS等。用于隔离容器网络和宿主机网络
- Network: Docker 虚拟网络,与宿主机网络隔离,只有参与者能够通信
- Endpoint: 容器内的虚拟网络接口,负责与Docker虚拟网络连接
1.2 Libnetwork
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
{
"icc": false
}
2. 四种网络模式
Docker 安装后,会自动创建三个网络
kpsmile@kpsmile-PC:~/Desktop$ docker network ls
NETWORK ID NAME DRIVER SCOPE
92116da8525d bridge bridge local
ced5a1c478a9 host host local
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 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
直接使用宿主机的 IP 地址与外界进行通信,不再需要额外进行NAT 转换。
容器将不会获得一个独立的Network Namespace, 而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡而是使用宿主机的IP和端口。
#错误写法 docke启动时会发出警告
#原因:docker启动时指定--network=host或-net=host,如果还指定了-p映射端口,那这个时候就会有此警告,并且通过-p设置的参数将不会起到任何作用,端口号会以主机端口号为主,重复时则递增。
#解决:解决的办法就是使用docker的其他网络模式,例如--network=bridge,这样就可以解决问题,或者直接无视
docker run -d -p 8083:8080 --network host --name tomcat83 billygoo/tomcat8-jdk8
#正确写法
docker run -d --network host --name tomcat83 billygoo/tomcat8-jdk8
2.3 container
这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过 lo 网卡设备通信。Container模式模型示意图如下:
2.4 none
使用none模式,Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。
这种网络模式下容器只有lo回环网络,没有其他网卡。none模式可以在容器创建时通过–network=none来指定。这种类型的网络没有办法联网,封闭的网络能很好的保证容器的安全性。
None模式示意图:
3.自定义网桥
# 创建网桥
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
796772d9de414642d99e52fdf32a6a044de991756cde34979cdfb994c218517a
# 参数说明
# --driver bridge #指定bridge驱动程序来管理网络
# --subnet 10.0.0.0/16 #指定网段的CIDR格式的子网
# --gateway 10.0.0.1 #指定主子网的IPv4或IPv6网关
# --opt com.docker.network.bridge.name 创建Linux网桥时要使用的网桥名称
# --opt com.docker.network.bridge.enable_ip_masquerade 启用ip伪装
# --opt com.docker.network.bridge.enable_icc 启用或禁用跨容器连接
# --opt com.docker.network.bridge.host_binding_ipv4 绑定容器端口时的默认IP
# --opt com.docker.network.driver.mtu 设置容器网络MTU
kpsmile@kpsmile-PC:~/Desktop$ docker network ls
NETWORK ID NAME DRIVER SCOPE
92116da8525d bridge bridge local
ced5a1c478a9 host host local
796772d9de41 myBridge bridge local
147ea2aabe54 none null local
kpsmile@kpsmile-PC:~/Desktop$ ip addr show docker1
9: docker1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:bb:47:c1:10 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.1/16 brd 10.0.255.255 scope global docker1
valid_lft forever preferred_lft forever
# 运行容器使用新创建的网桥
kpsmile@kpsmile-PC:~/Desktop$ docker run -itd --name busybox --network myBridge busybox
903e9abfe34d729a118ea535904a592530fa351dbecf35e90229d334297f0df2
kpsmile@kpsmile-PC:~/Desktop$ ip add
......
11: veth751becc@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker1 state UP group default
link/ether b2:91:62:3b:9b:4e brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::b091:62ff:fe3b:9b4e/64 scope link
valid_lft forever preferred_lft forever
kpsmile@kpsmile-PC:~/Desktop$ docker exec -it 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
10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:0a:00:00:02 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.2/16 brd 10.0.255.255 scope global eth0
valid_lft forever preferred_lft forever
4.容器互联
# 使用上一节创建的网桥
docker run -d -P --name tomcat-net-01 --net myBridge tomcat
docker run -d -P --name tomcat-net-02 --net myBridge tomcat
# 网络描述中Containers字段有创建的两个tomcat的Container
docker network inspect myBridge
...
"Containers": {
"6a0f9671566a566c37b8ebb8dd1b9ee4bfee53cdc51563120b1688d6b5aac508": {
"Name": "tomcat-net-01",
"EndpointID": "a68744684c3bd2ac004d23e48e77f8b7cd85ab731bcf2726bc38899081907071",
"MacAddress": "02:42:0a:00:00:02",
"IPv4Address": "10.0.0.2/16",
"IPv6Address": ""
},
"a2b9c222c65865ab55c8577b15ba3729347a77ca4bab42dbd39204550db64718": {
"Name": "tomcat-net-02",
"EndpointID": "71282783cf8a3bf560894e42d59f060b18693e419b83beff1e4f44cc72464a67",
"MacAddress": "02:42:0a:00:00:03",
"IPv4Address": "10.0.0.3/16",
"IPv6Address": ""
}
},
...
root@kpsmile-machine:/home/kpsmile/Desktop# docker exec -it tomcat-net-01 ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.0.2 netmask 255.255.0.0 broadcast 10.0.255.255
ether 02:42:0a:00:00:02 txqueuelen 0 (Ethernet)
RX packets 3870 bytes 9079380 (8.6 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 3477 bytes 244413 (238.6 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: ...
root@kpsmile-machine:/home/kpsmile/Desktop# docker exec -it tomcat-net-02 ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.0.3 netmask 255.255.0.0 broadcast 10.0.255.255
ether 02:42:0a:00:00:02 txqueuelen 0 (Ethernet)
RX packets 3870 bytes 9079380 (8.6 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 3477 bytes 244413 (238.6 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: ...
# 假如不使用自定义网络容器之间只能通过ip来进行
docker exec -it tomcat-net-01 ping 10.0.0.3
PING 10.0.0.3 (10.0.0.3): 56 data bytes
64 bytes from 10.0.0.3: icmp_seq=0 ttl=64 time=0.073 ms
64 bytes from 10.0.0.3: icmp_seq=1 ttl=64 time=0.115 ms
docker exec -it tomcat-net-02 ping 10.0.0.2
PING 10.0.0.2 (10.0.0.2): 56 data bytes
64 bytes from 10.0.0.2: icmp_seq=0 ttl=64 time=0.071 ms
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.122 ms
# 假如使用自定义网络,同一网络下容器之间可以通过容器名来进行
docker exec -it tomcat-net-02 ping tomcat-net-01
PING tomcat-net-01 (10.0.0.2): 56 data bytes
64 bytes from 10.0.0.2: icmp_seq=0 ttl=64 time=0.089 ms
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.117 ms
docker exec -it tomcat-net-01 ping tomcat-net-02
PING tomcat-net-02 (10.0.0.3): 56 data bytes
64 bytes from 10.0.0.3: icmp_seq=0 ttl=64 time=0.109 ms
64 bytes from 10.0.0.3: icmp_seq=1 ttl=64 time=0.233 ms
5.docker网络之间的互联
没有设置的情况下,不同网络间的容器是无法进行网络连接的。如图,两个不同的网络docker0和自定义网络mynet的网络模型图:
在默认网络bridge下启动容器tomcat-01,尝试连接mynet网络下的tomcat-net-01容器:
docker run -d -P --name=myTomcat01 tomcat
# myTomcat01容器ip为172.17.0.2,此时是挂载在默认网络bridge下
docker exec -it myTomcat01 ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.2 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:ac:11:00:02 txqueuelen 0 (Ethernet)
RX packets 3950 bytes 9086194 (8.6 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 3399 bytes 236706 (231.1 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
# 此时ping不通
docker exec -it myTomcat01 ping tomcat-net-02
ping: unknown host
可以看到是无法网络连接的。不同Docker网络之间的容器需要连接的话需要把作为调用方的容器注册一个ip到被调用方所在的网络上。需要使用docker connect命令。
下面设置容器myTomcat01连接到mynet网络上。并查看mynet的网络详情,可以看到给容器myTomcat01分配了一个ip地址。
docker network connect myBridge myTomcat01
docker network inspect myBridge
...
"Containers": {
"32168b140f7d5f51ca2617581cd6670e8b9590b88567f1fd5cd979c605003318": {
"Name": "tomcat-net-02",
"EndpointID": "ace2762662651b91006ef00291d714664ea86dcc34b8a0974126f84a95a2a625",
"MacAddress": "02:42:0a:00:00:03",
"IPv4Address": "10.0.0.3/16",
"IPv6Address": ""
},
"73b29f1586b6d243e33878e4333712b8c175492912bc41701e8683548a943425": {
"Name": "myTomcat01",
"EndpointID": "ff955362bad60596946c54805aea0038ed33039acb07c0957cd3b85439b4abdb",
"MacAddress": "02:42:0a:00:00:04",
"IPv4Address": "10.0.0.4/16",
"IPv6Address": ""
},
"d39492cab3af71032b505fa6f440f37e2f1b9adca0e8a7078becc7d89e28bdaf": {
"Name": "tomcat-net-01",
"EndpointID": "a266cd157671450fcc660d1a844d27ad7ebacdb149793719f3481803201ccd2f",
"MacAddress": "02:42:0a:00:00:02",
"IPv4Address": "10.0.0.2/16",
"IPv6Address": ""
}
},
...
# 设置完成后我们就可以实现不同网络之间的容器互联了
docker exec -it myTomcat01 ping tomcat-net-02
PING tomcat-net-02 (10.0.0.3): 56 data bytes
64 bytes from 10.0.0.3: icmp_seq=0 ttl=64 time=0.168 ms
64 bytes from 10.0.0.3: icmp_seq=1 ttl=64 time=0.070 ms