Docker 系列(三):Docker 网络模式详解

 

一、前言

Docker作为目前最火的轻量级容器技术,有很多令人称道的功能,如Docker的镜像管理。然而,Docker同样有着很多不完善的地方,网络方面就是Docker比较薄弱的部分。因此,我们有必要深入了解Docker的网络知识,以满足更高的网络需求。

我们在使用docker run创建Docker容器时,可以用--net选项指定容器的网络模式,Docker有以下4种网络模式:

  •  host模式,使用--net=host指定。

容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。

  • container模式,使用--net=container:NAME_or_ID指定。

创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围。

  •  none模式,使用--net=none指定。

该模式关闭了容器的网络功能。

  • bridge模式,使用--net=bridge指定,默认设置。

此模式会为每一个容器分配、设置IP等,并将容器连接到一个docker0虚拟网桥,通过docker0网桥以及Iptables nat表配置与宿主机通信。

注意:使用docker network ls命令列出这些docker内置的网络模式。

 

Docker 网络模型

 

 

 

二、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和端口。

例如,我们在10.10.101.105/24的机器上用host模式启动一个含有web应用的Docker容器,监听tcp80端口。

当我们在容器中执行任何类似ifconfig命令查看网络环境时,看到的都是宿主机上的信息。而外界访问容器中的应用,则直接使用10.10.101.105:80即可,不用任何NAT转换,就如直接跑在宿主机中一样。但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的。

 

下面关于网络命令空间的介绍来源:https://yq.aliyun.com/articles/53624

那么什么是网络名称空间?一般的说法是包括了Linux共享的一组网络接口和路由表条目的装置。您可以修改路由表条目使用策略路由。(这里有我写的介绍和可能的策略路由用例),但这并没有从根本上改变网络接口和路由表/条目共享整个操作系统。网络名字空间改变这一基本假设。通过网络名字空间,你能有不同并独立的网络接口和路由表,它们各自独立运作

这个概念通过一些例子来说明可能是最好的。沿途我会介绍一些新想法。首先,我需要提供一些假设。

假设

在这个例子中,我会使用Ubuntu Server 12.04.3 LTS.请注意不同的Linux发行版对网络名字空间支持上的不同。Ubuntu支持这个,但是RedHat不支持。(我不确定Fedora,如果你知道,可以在评论中说出来。)如果你想使用网络名字空间,请确保你的Linux发行版支持。

另外,我回假设你使用root账户运行,或者你会在必要的时候加上sudo来执行命令行。

创建和列出网络名字空间

创建一个网络名字空间是相当容易的。仅仅需要使用这个命令:

1
ip netns add <new namespace name>
打个比方,你想要创建一个名为“blue”的名字空间。你会使用这个命令:

1
ip netns add blue
使用这个命令验证网络名字空间是否创建:

1
ip netns list
你应该能看到你的网络名字空间被列出,你可以使用它们了。

 给网络名字空间分配接口

创建网络名字空间仅仅是个开始,接下来的部分是给名字空间指定接口。然后为网络连接配置这些接口。有一个注意,就是在我之前探索网络名字空间的时候发现,你无法把物理接口分配给一个名字空间。到底怎样才呢个使用它们呢,然后呢?

原来你只能分配虚拟以太网接口(veth)到网络名称空间。虚拟以太网接口是一个有趣的结构。它总是一对一对的,他们像一个相连的管道——无论什么从其中一个veth接口进去,就会从另一个等同的接口出来。因此,您可以使用veth接口把一个网络名名字空间链接到存在物理接口的“默认”或“全局”网络名字空间。

让我们来看看这是如何实现的。首先,您要创建一对veth:

1
ip link add veth0 type veth peer name veth1
我发现一些站点重复这个命令去创建veth1然后链接到veth0,但是我的测试表明,只要使用上面这个命令,会创建一对端口,并且自动建立连接。当然,你可以用其它名字代替veth0和veth1,如果你需要的话。

你可以使用这个命令验证veth对被创建。

1
ip link list
你应该看到一对veth接口(使用你在上面的命令中指定的名称)列出。现在它们都属于”默认”或“全局”名字空间,和物理接口一道。

假设你想把全局名字空间链接到blue名字空间。这样做,你需要移动一个veth接口道blue名字空间中,可以使用这个命令。

1
ip link set veth1 netns blue
如果你再次运行ip link list 命令,你会看到veth1接口从列表中消失了。现在它在blue名字空间中,因此你需要运行下面的命令来看。

1
ip netns exec blue ip link list
哇哦!这是一个有点复杂的命令,让我们慢慢讲:

在第一部分,ip nets exec,这是在不同网络名字空间执行命令的方法。
接下来是这个命令应该运行在的特定名称空间。(在这个例子中,是blue名字空间)
最后,你需要实际的命令在远程名字空间中执行。在这个例子中,你想要查看在blue名字空间的接口,因此你运行 ip link list。
当你运行那个命令,你就能看到一个回环接口和你刚才移入的veth1接口。

在网络名字空间配置接口

现在,veth1已经移动到蓝色的名称空间,我们需要去实际配置那个接口。再一次,我们会使用 ip netns exec 命令,这一次在blue名字空间中配置veth1接口。

1
ip netns exec blue ifconfig veth1 10.1.1.1/24 up
和之前一样,这个命令的格式如下:

1
ip netns exec <network namespace> <command to run against that namespace>
在这个例子里,你使用ifconfig给veth1接口指定了一个IP地址,并且开启了这个接口。(注解:你可以使用 ip addr , ip route ,以及 ip link 命令来完成相同的事情)

一旦veth1接口开启,你能仅仅通过一些不同的命令验证blue名字空间中的网络设置是完全隔离的。打个比方,让我们确定你的”全局”名字空间有物理接口在 172.16.1.0/24 范围,你的veth1接口在一个隔离的名字空间,并且分配了10.1.1.0/24范围。你可以使用这些命令验证网络名字空间是如何保证网络配置隔离的:

ip addr list 在全局名字空间没有显示任何10.1.1.0/24有关的接口或者地址。
ip netns exec blue ip addr list 将仅仅显示10.1.1.0/24有关的接口和地址,也不会显示任何来自全局名字空间的接口或地址。
同样的,ip route list 在每个名称空间将显示不同的路由表条目,包括不同的默认网关。
链接网络名字空间到物理网络

这部分花了我一些时间。我没法解释为什么,但是它这样运作了。一旦我搞懂了,一旦我搞懂了,它是显而易见的。让网络名字空间链接到物理网络的就是使用网桥。在我的例子中,我是用一个Open vSwitch(OVS)桥,但是一个标准Linux网桥也能工作的很好。把一个或多个物理接口和veth接口一样放到网桥中,然后,啪,它们就能用了。当然,如果你有不同的名字空间,你可能想要/需要将它们连接到不同的物理网络或不同的vlan。
关于网络名称空间的一段介绍

 

三、container模式

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

联盟式容器是指使用某个已存在容器的网络接口的容器,接口被联盟内的各容器共享使用;因此,联盟式容器彼此间完全隔离,例如:
联盟式容器彼此虽然共享同一个网络名称空间,但其它名称空间如User、Mount等还是隔离的。
联盟式容器彼此间存在端口冲突的可能性,因此,通常只会在多个容器上的程序需要程序loopback接口互相通信,或对已存的容器的网络属性进行监控时才使用此模式的网络模型。

bridge模式:(默认没有设置网络模式工作在bridge模式),我们启动两个容器,两个容器之间互相隔离,并拥有自己的私有IP,并可以互相通信,等会会介绍:

docker run --name b1 -it --rm busybox

docker run --name b2 -it --rm busybox

container模式:

docker run --name b1 -it --rm busybox

docker run --name b2 -it --rm --network container:b1 busybox

此时我们在b2中使用ifconfig命令,可以看到b2与b1IP相同,彼此之间也可以通信,效果如同一个主机上的两个进程。

 

四、none模式

此模式下容器不参与网络通信,运行于此类容器中的进程仅能访问本地环回接口,仅适用于进程无须网络通信的场景中,例如备份,进程诊断及各种离线任务等。

--network none:设置模式容器工作在none模式下。
在此模式下使用ifconfig -a 显示只有lo网卡。

 

五、Bridge模式(默认网络模式)

桥接式容器一般拥有两个接口:一个环回接口和一个连接至主机上某桥设备的以太网接口,在上面的图片就可以看到。

docker daemon启动时默认会创建一个名为docker0的网络桥,docker0网卡扮演二层交换机与网卡设备,不给IP就是交换机,给了IP即可以当交换机也可以当网卡。

并且我们以后在此模式下创建的容器为桥接式容器,我们每次启动一个容器,就会为这个容器分配一对网卡设备,其中一个网卡在容器上,另外一个网卡在宿主机上,在宿主机上的网卡接口桥接至docker0。

--network bridge:设置容器工作在bridge模式下,即为将容器接口添加至docker0桥。
docker0桥为NAT桥,因此,桥接式容器可通过此桥接口访问外部网络,但防火墙规则阻止一切从外部网络访问桥接式容器的请求,当然也有解决办法,后面会介绍外部访问容器。

 这里容器使用独立network Namespace,并连接到docker0虚拟网卡(默认模式)。通过docker0网桥以及Iptables nat表配置与宿主机通信;bridge模式是Docker默认的网络设置,此模式会为每一个容器分配Network Namespace、设置IP等,并将一个主机上的Docker容器连接到一个虚拟网桥上。

 

 

常用参数的用法

--name:设置容器名
docker run --name  t1 -it  busybox:latest

--rm:退出容器即刻删除容器
docker run --name  Nginx -it --network bridge --rm nginx

--network:设置网络模式,默认bridge模式
docker run --name  t1 -it --network bridge --rm  busybox:latest

--hostname:为容器设置指定主机名
docker run --name  t1 -it --network bridge  --hostname  t1.magedu.com  --rm  busybox:latest
注意:使用hostname命令查看主机名

--dns:设置dns服务器
docker run --name  t1 -it --network bridge  --hostname  t1.magedu.com  --dns 114.114.114.114  --rm  busybox:latest

--dns-search:设置dns搜索域
docker run --name  t1 -it --network bridge  --hostname  t1.magedu.com  --dns 114.114.114.114   --dns-search ilinux.io  --rm  busybox:latest

--add-host:在/etc/hosts文件中添加主机与ip的解析记录
docker run --name  t1 -it --network bridge  --hostname  t1.magedu.com  --dns 114.114.114.114   --dns-search ilinux.io  --rm   --add-host  "docker.com:172.16.0.100"     busybox:latest

 

 

六、 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模式下容器的通信

 

在bridge模式下,连在同一网桥上的容器可以相互通信(若出于安全考虑,也可以禁止它们之间通信,方法是在DOCKER_OPTS变量中设置–icc=false,这样只有使用–link才能使两个容器通信)。

Docker可以开启容器间通信(意味着默认配置--icc=true),也就是说,宿主机上的所有容器可以不受任何限制地相互通信,这可能导致拒绝服务攻击。进一步地,Docker可以通过--ip_forward和--iptables两个选项控制容器间、容器和外部世界的通信。

容器也可以与外部通信,我们看一下主机上的Iptable规则,可以看到这么一条(查看规则:iptables -t nat -vnL):

-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE

 这条规则会将源地址为172.17.0.0/16的包(也就是从Docker容器产生的包),并且不是从docker0网卡发出的,进行源地址转换,转换成主机网卡的地址。这么说可能不太好理解,举一个例子说明一下。假设主机有一块网卡为eth0,IP地址为10.10.101.105/24,网关为10.10.101.254。从主机上一个IP为172.17.0.1/16的容器中ping百度(180.76.3.151)。IP包首先从容器发往自己的默认网关docker0,包到达docker0后,也就到达了主机上。然后会查询主机的路由表,发现包应该从主机的eth0发往主机的网关10.10.105.254/24。接着包会转发给eth0,并从eth0发出去(主机的ip_forward转发应该已经打开)。这时候,上面的Iptable规则就会起作用,对包做SNAT转换,将源地址换为eth0的地址。这样,在外界看来,这个包就是从10.10.101.105上发出来的,Docker容器对外是不可见的。

 

上面添加规则来实现外部访问略为麻烦,我们还有一种更好的方法,端口映射:
为docker run命令使用-p选项即可实现端口映射,无须手动添加规则

-p选项的使用格式
-p <containerPort>
---将指定的容器端口映射至主机所有地址的一个动态端口(随机端口)
-p <hostPort>:<containerPort>
---将容器端口<containerPort>映射至指定的主机端口<hostPort>
-p <ip>::<containerPort>
---将指定的容器端口<containerPort>映射至主机指定<ip>的动态端口
-p <ip>:<hostPort>:<containerPort>
---将指定的容器端口<containerPort>映射至主机指定<ip>的端口<hostPort>
"动态端口"指随机端口,具体的映射结果可使用docker port命令查看

那么,外面的机器是如何访问Docker容器的服务呢?我们首先用下面命令创建一个含有web应用的容器,将容器的80端口映射到主机的80端口。

docker run --name=nginx_bridge --net=bridge -p 80:80 -d nginx

然后查看Iptable规则的变化,发现多了这样一条规则:

-A DOCKER ! -i docker0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.2:80

此条规则就是对主机eth0收到的目的端口为80的tcp流量进行DNAT转换,将流量发往172.17.0.2:80,也就是我们上面创建的Docker容器。所以,外界只需访问10.10.101.105:80就可以访问到容器中的服务。

除此之外,我们还可以自定义Docker使用的IP地址、DNS等信息,甚至使用自己定义的网桥,但是其工作方式还是一样的。

 

参考:

https://www.cnblogs.com/zuxing/articles/8780661.html

https://yq.aliyun.com/articles/53624

https://www.cnblogs.com/jsonhc/p/7823286.html

 

posted @ 2019-08-19 18:11  -零  阅读(2777)  评论(0编辑  收藏  举报