Docker网络本质上是通过修改iptables规则(Linux下)或路由表(Windows下)实现的
Docker的网络子系统是插件化的,以driver插件的形式提供:
-
bridge
-
host
-
overlay
-
macvlan
-
none
-
第三方的网络插件
使用driver创建名为<NETWORK>的网络:
# docker network create --driver xxx <NETWORK>
不指定driver时,默认使用bridge,此时创建出来的是用户自定义Bridge网络
run一个容器的时候,使用参数--network=<NETWORK>来确定容器连接到哪个网络(只能使用一次、指定一个)
不过容器运行后,可以让容器连接多个网络:
# docker network connect <NETWORK> <容器名>
安装Docker后会自动安装三种网络:
# docker network ls NETWORK ID NAME DRIVER SCOPE 34390e4712c7 bridge bridge localkubeku 9e4981e4d429 host host local 5f961fc8f8f9 none null local
默认Bridge网络
当Docker进程启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的容器会连接到这个虚拟网桥上。
虚拟网桥的工作方式和物理交换机类似,主机上的所有容器相当于通过它(作为交换机)连在了一个二层网络中。
该网桥可以看作主机的网络堆栈(network stack)的一部分,可以在主机上展示:
$ if addr show 5: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:16:51:bb:4d 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:16ff:fe51:bb4d/64 scope link valid_lft forever preferred_lft forever
从docker0子网中分配一个IP给容器使用,并设置docker0的IP地址为容器的默认网关。
在主机上创建一对虚拟网卡veth pair设备,Docker将veth pair设备的一端放在新创建的容器中,并命名为eth0(容器的网卡),另一端放在主机中,以veth*这样类似的名字命名,并将这个网络设备加入到docker0网桥中。
即,docker使用Linux namespace技术将veth映射成在容器中显示的以太网卡eth0。因此,docker容器只能通过IP地址(不支持自动服务发现)与同一台宿主机中的容器(同一个虚拟网桥上的网段)通信。
旧版docker提供了--link参数来允许docker0网络中容器的通信(不推荐),不映射它们的端口到宿主主机上,而是直接在两个容器之间建立一个安全的隧道。--link参数的局限性在于:使用 /etc/hosts 静态文件来进行的解析,比如一个主机挂了后,重新启动IP可能会改变。虽然说这种改变Docker是可能更新/etc/hosts文件,但是这有诸多问题,可能会因为竞争冒险导致/etc/hosts文件损毁,也可能还在运行的容器在取得/etc/hosts的解析结果后,不再去监视该文件是否变动。种种原因都可能会导致旧的主机无法通过容器名访问到新的主机。
在dockerfile中使用expose关键字(相当于docker run的时候使用--expose参数),就可以expose端口。
但expose只是记录了哪个端口被使用了,并不实际映射或者打开某个端口,因为构建镜像时不知道创建容器的时候宿主机上哪些端口可用。
在docker run时使用--publish(-p)参数就可以publish端口。不指定主机端口的话会将容器端口映射到宿主机的随机可用的高位端口(>30000)。
发往这些端口的容量最终会被转发到各自对应的容器内,docker实际是在iptables做了DNAT规则,实现端口转发功能;容器所有到外部网络的连接,源地址都会使用iptables的源地址伪装操作(SNAT规则)变为主机的IP地址。
可以使用iptables -t nat -vnL查看。
Host网络
如果启动容器的时候使用host网络,那么这个容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡、配置自己的IP等,而是使用宿主机的IP和端口。
但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的。
容器被添加到了主机的容器网络堆栈。容器与主机的网络是没有隔离的,例如使用Host模式的容器在80端口运行了一个web服务,那么在宿主机的80端口也能访问这个web服务。
None网络
Docker容器拥有自己的Network Namespace,但不会为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。
attach进入一个none模式的容器,查看它的网络堆栈:
$ docker attach nonenetcontainer root@0cb243cd1293:/# cat /etc/hosts 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters root@0cb243cd1293:/# ifconfig lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
Container模式
这是一种较为特别的网络模式。
这个模式指定新创建的容器没有自己的网络(不会创建自己的网卡、配置自己的IP)、也不和宿主机共享,而是和已经存在的一个容器共享一个Network Namespace。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过eth0网卡通信。
这种模式的网络隔离性处于bridge模式和host模式之间。
用户自定义网络
如果想要通过容器名获得IP地址,可以创建一个用户自定义Bridge网络
同该网络中,可以使用对方容器的容器名、服务名、网络别名来找到对方。这个时候帮助进行服务发现的是Docker内置的DNS。所以,无论容器是否重启、更换IP,内置的DNS都能正确指定到对方的位置。
该网络提供了更好的隔离性,因为:容器不会默认加入此网络,可以随时加入、移出;每一个用户自定义Bridge网络都有自己的网桥
macvlan网络
macvlan网络会为每个容器的网卡分配一个mac地址:
$ docker network create -d macvlan --subnet=172.16.86.0/24 --gateway=172.16.86.1 -o parent=eth0 pub_net
parent如果是ent0.xxx,Docker会认为它是子接口,会自动创建这个网络接口
Overlay网络
容器有两个网卡(两个ip)。创建Overlay网络时若指定--internal参数,容器只有一个Overlay网络的网卡eth0,不会创建eth1。
容器通过eth0实现与其他容器通信:veth一端绑定在容器网卡eth0,一端绑在网桥br0;网桥br0上还绑定了vxlan0(一个VxLan虚拟网络设备),通过vxlan隧道实现容器跨主机通信。
容器通过eth1实现与外部通信:veth一端绑在容器网卡eth1,一端绑在网桥gwbridge,通过NAT实现与外部通信。
为了减少广播,Docker通过读取KV数据静态配置ARP表和FDB表,容器创建或者删除等事件会通过Serf以及Gossip协议通知Node更新ARP表和FDB表。