我们在使用docker run创建Docker容器时,可以用--net参数指定容器的网络模式,Docker有以下4种网络模式:
· host模式,使用--net=host指定。
· container模式,使用--net=container:NAME_or_ID指定。
· none模式,使用--net=none指定。
· bridge模式,使用--net=bridge指定,默认设置。
1、host模式
众所周知,Docker使用了Linux的Namespaces技术来进行资源隔离,如PID Namespace隔离进程,Mount Namespace隔离文件系统,Network Namespace隔离网络等。一个Network Namespace提供了一份独立的网络环境,包括网卡、路由、Iptable规则等都与其他的Network Namespace隔离。一个Docker容器一般会分配一个独立的Network Namespace。但如果启动容器的时候使用host模式,那么这个容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口和主机名(hostname -I)。
例如,我们在10.10.101.105/24的机器上用host模式启动一个含有web应用的Docker容器,监听tcp80端口。当我们在容器中执行任何类似ifconfig命令查看网络环境时,看到的都是宿主机上的信息。而外界访问容器中的应用,则直接使用10.10.101.105:80即可,不用任何NAT转换,就如直接跑在宿主机中一样,优点是处理速度快,不用nat转发。虽然共享网络名称空间,但是容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的。
总结:与宿主机共享network Namespace,容器与宿主机的主机名、IP一致,但容器的其他方面,如文件系统、进程列表、服务、端口等还是和宿主机隔离的,所以容器内只能看到自己开启的哪些服务、进程等,看不了宿主机或者其他容器的服务、进程等
2 、container(联合网络)模式
在理解了host模式后,这个模式也就好理解了。这个模式指定新创建的容器和已经存在的一个容器共享一个Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口、主机名(hostname -I)范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表、服务等还是隔离的。两个容器的进程可以通过lo网卡设备通信。(k8s常用网络模型,边车模式)
1.首先启动一个有网络的容器
[root@docker01 ~]# docker run -itd centos69_ssh_df:v3 /bin/bash
347c5934f47b46d8cc54e305313c8c9b84604370aac41bd61535b37a861f002c
[root@docker01 ~]# docker inspect frosty_turing | grep 'IPAddress'
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.6",
"IPAddress": "172.17.0.6",
2.接着启动需要使用共享网络的容器
[root@docker01 ~]# docker run -it --network container:frosty_turing centos69_ssh_df:v3 /bin/bash
3.查看容器的ip和主机名,会发现和刚刚启动的容器的主机名、ip都相同
[root@347c5934f47b /]# hostname -I
172.17.0.6
总结:与另一个运行中的容器共享Network Namespace(k8s常用网络模型,边车模式),容器与另一个运行中的容器的主机名、IP一致,但容器的其他方面,如文件系统、进程列表、服务、端口等还是隔离的,谁共享谁的Network Namespace,先到先得
3 、none模式
这个模式和前两个不同。在这种模式下,Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。但有lo回环网络。这种类型的网络没有办法联网,封闭的网络能很好的保证容器的安全性。
[root@docker01 ~]# docker run -it --network none centos69_ssh_df:v3 /bin/bash
2.查看是否有网络
[root@6e47719e0c83 /]# ifconfig
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
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:1000
RX bytes:0 (0.0 b) TX bytes:0 (0.0 b)
[root@6e47719e0c83 /]#
3.查看容器的属性会看到network没有指定ip
[root@docker01 ~]# docker inspect cranky_agnesi
总结:不为容器配置任何网络功能,但有lo回环网络
4 、bridge模式
bridge模式是Docker默认的网络设置,此模式会为每一个容器分配Network Namespace、设置IP等,并将一个主机上的Docker容器连接到一个虚拟网桥上。下面着重介绍一下此模式。
4.1 bridge模式的拓扑
当Docker server启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。接下来就要为容器分配IP了,Docker会从RFC1918所定义的私有IP网段中,选择一个和宿主机不同的IP地址和子网分配给docker0,连接到docker0的容器就从这个子网中选择一个未占用的IP使用。如一般Docker会使用172.17.0.0/16这个网段,并将172.17.42.1/16分配给docker0网桥(在主机上使用ifconfig命令是可以看到docker0的,可以认为它是网桥的管理接口,在宿主机上作为一块虚拟网卡使用)。单机环境下的网络拓扑如下,主机地址为10.10.101.105/24。
Docker完成以上网络配置的过程大致是这样的:
1. 在主机上创建一对虚拟网卡veth pair设备。veth设备总是成对出现的,它们组成了一个数据的通道,数据从一个设备进入,就会从另一个设备出来。因此,veth设备常用来连接两个网络设备。
2. Docker将veth pair设备的一端放在新创建的容器中,并命名为eth0。另一端放在主机中,以veth65f9这样类似的名字命名,并将这个网络设备加入到docker0网桥中,可以通过brctl show命令查看。
3. 从docker0子网中分配一个IP给容器使用,并设置docker0的IP地址为容器的默认网关。
网络拓扑介绍完后,接着介绍一下bridge模式下容器是如何通信的。
4.2 bridge模式下容器的通信
4.2 1、容器跟容器之间的访问
从上述我们知道,在bridge模式下,Docker容器的网络设备,都将加入到docker0网桥中,所以连在同一网桥上的容器可以相互通信。可以理解为docker0是容器的网关,而所有的Docker容器都在同一个网段中。(若出于安全考虑,也可以禁止它们之间通信,方法是在DOCKER_OPTS变量中设置--icc=false,这样只有使用--link才能使两个容器通信)。如下是容器内部的路由,只要是去往172.17.0.0/16的网络,不用路由。(Gateway网关地址下,”*” 表示目标是本主机所属的网络,不需要路由)
[root@localhost ~]# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default gateway 0.0.0.0 UG 100 0 0 ens33
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
192.168.18.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33
[root@localhost ~]#
可以通过brctl show命令查看docker0网桥中的设备。
[root@localhost ~]# brctl show # 查看连接docker0所有的vethfinder
bridge name bridge id STP enabled interfaces
docker0 8000.024246ae3892 no veth2631e6b
veth6e3f1c5
veth8f4f430
[root@e230f55f0b16 /]# cat /sys/class/net/eth0/iflink # 在容器中查看使用的vethfinder编号
12
[root@localhost ~]# grep -l 12 /sys/class/net/veth*/ifindex # 在宿主机中,查看12对应的vethfinder
/sys/class/net/veth2631e6b/ifindex
4.2 2、容器如何访问非容器之外的网络
容器也可以与外部通信,通过主机上的一条Iptable规则实现,如下,这条规则会将源地址为172.17.0.0/16的包(也就是从Docker容器产生的包),并且不是从docker0网卡发出的,进行源地址转换,转换成主机网卡的地址。
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
以访问百度为实际的案例:
假设主机有一块网卡为ens33,IP地址为192.168.18.6/24,网关为192.168.18.1。从主机上一个IP为172.17.0.1/16的容器中ping百度www.baidu.com,通过查看dns解析出是(110.242.68.3)
[root@e5a339b0c4d7 yum.repos.d]# cat /etc/resolv.conf # Generated by NetworkManager nameserver 192.168.18.1 [root@e5a339b0c4d7 yum.repos.d]# ping www.baidu.com ping: unknown host www.baidu.com [root@e5a339b0c4d7 yum.repos.d]# cat /etc/resolv.conf # Generated by NetworkManager nameserver 192.168.18.1 [root@e5a339b0c4d7 yum.repos.d]# ping www.baidu.com PING www.a.shifen.com (110.242.68.3) 56(84) bytes of data. 64 bytes from 110.242.68.3: icmp_seq=1 ttl=127 time=53.3 ms 64 bytes from 110.242.68.3: icmp_seq=2 ttl=127 time=52.5 m
IP包首先通过route路由,route表中获得,目的地址非172.17.0.0地址的IP,默认全部都发送给172.17.0.1网关,所以数据包从容器发往自己的默认网关docker0
[root@e5a339b0c4d7 yum.repos.d]# route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface default 172.17.0.1 0.0.0.0 UG 0 0 0 eth0 172.17.0.0 * 255.255.0.0 U 0 0 0 eth0
包到达docker0后,也就到达了宿主机上,docker0所处在宿主机上,会对刚刚到的数据包(src(172.17.0.0/16地址的IP)dst (110.242.68.3) )处理发给谁处理,所以会查询宿主机的路由表,查看到除了去172.17.0.0/16,192.168.18.0/24 ,都默认给gateway,接着包会转发给ens33,并从ens33发出去(主机的ip_forward转发应该已经打开)。
[root@localhost ~]# route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface default gateway 0.0.0.0 UG 100 0 0 ens33 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 192.168.18.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33
这时候,Iptable规则就会起作用,意思是只要源地址是172.17.0.0/16的网络要出网,而这个数据包还不是从docker0发出来的,统统对包做SNAT转换,将源地址换为ens33的地址。
这样,在外界看来,这个包就是从192.168.18.6/24上发出来的,Docker容器对外是不可见的。[root@localhost ~]# iptables-save |grep "172.17.0.0/16" -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
4.2 3、外部机器如何访问容器
将容器内部的端口挂载到宿主机某一个端口,通过Iptable规则,将访问宿主机某一个端口流量引入到容器内部的端口。此条规则对主机ens33收到的目的端口为8081的tcp流量进行DNAT转换,将流量发往172.17.0.2:80。
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8081 -j DNAT --to-destination 172.17.0.2:80
外面的机器是如何访问Docker容器的服务呢?以访问nginx为实际的案例:
查看本机的8081端口无法访问,查看Iptable规则关于8081是没有的
[root@localhost ~]# iptables-save |grep "80" [root@localhost ~]# curl 192.168.18.6:8081 curl: (7) Failed connect to 192.168.18.6:8081; 拒绝连接
用下面命令创建一个含有web应用的容器,将容器的80端口映射到主机的8081端口。并能通过192.168.18.6:8081 ,进而访问nginx的80端口
[root@localhost yum.repos.d]# docker pull nginx:1.16 [root@localhost yum.repos.d]# docker image ls -a |grep nginx nginx 1.16 dfcfd8e9a5d3 23 months ago 127 MB [root@localhost yum.repos.d]# docker run -d --name web -p 8081:80 dfcfd8e9a5d3 dac63d3282e8e537c4780e59fe91fb66e95f5892882a9f57ba5323fa7b2d2b75 [root@localhost yum.repos.d]# curl 192.168.18.6:8081 <!DOCTYPE html> ...... <h1>Welcome to nginx!</h1> ...... [root@localhost yum.repos.d]#
然后查看Iptable规则的变化,发现多了这样一条规则:
[root@localhost ~]# iptables-save |grep "8081" -A DOCKER ! -i docker0 -p tcp -m tcp --dport 8081 -j DNAT --to-destination 172.17.0.2:80 [root@localhost ~]#
此条规则就是对主机ens33收到的目的端口为8081的tcp流量进行DNAT转换,将流量发往172.17.0.2:80,也就是我们上面创建的Docker容器。通过宿主机的路由,只要是目的地址是172.17.0.0/16,都给docker0,而docker0是容器网关,它当然知道172.17.0.2是那个容器。所以,外界只需访问192.168.18.6:8081就可以访问到容器中得服务。
[root@localhost yum.repos.d]# route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface default gateway 0.0.0.0 UG 100 0 0 ens33 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 192.168.18.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33 [root@localhost yum.repos.d]#
除此之外,我们还可以自定义Docker使用的IP地址、DNS等信息,甚至使用自己定义的网桥,但是其工作方式还是一样的。