- You can see that the Docker server creates a # masquerade rule that let containers connect # to IP addresses in the outside world: $ sudo iptables -t nat -L -n … Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all — 172.17.0.0/16 0.0.0.0/0 …
- What your NAT rules might look like when Docker # is finished setting up a -P forward: $ iptables -t nat -L -n … Chain DOCKER (2 references) target prot opt source destination DNAT tcp — 0.0.0.0/0 0.0.0.0/0 tcp dpt:49153 to:172.17.0.2:80 # What your NAT rules might look like when Docker # is finished setting up a -p 80:80 forward: Chain DOCKER (2 references) target prot opt source destination DNAT tcp — 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80
1,了解Docker网络管理
Docker通过使用网络容器支持网络驱动程序。默认情况下,Docker提供了两个网络驱动器,bridge和overlay驱动程序。同时,你也可以写一个网络驱动程序的插件。
Docker默认会提供三种网络模式:
docker network ls
当docker启动时,它会在宿主机上创建一个名为docker0的虚拟网络接口。它会从RFC_1918 定义的私有地址中随机选择一个主机不用的地址和子网掩码,并将它分配为docker0。之后创建的容器都会在docker0子网的范围内选取一个未占用的IP使用。Docker默认网络模式为bridege。
route -n
docker0并不是正常的网络接口。它只是一个在绑定到这上面的其他网卡间自动转发数据包的虚拟以太网桥。它可以使容器与主机相互通信。每次Docker创建一个容器,它就会创建一对对等接口(peer interface),类似于一个管子的两端-在这边可以收到另一边发送的数据包。Docker会将对等接口中的一个做为eth0接口连接到容器上,并使用类似于vethAQI2QT这样的惟一名称来持有另一个,该名称取决于主机的命名空间。通过将所有veth*接口绑定到docker0桥接网卡上,Docker在主机和所有Docker容器间创建一个共享的虚拟子网。
(图103)
这里网桥的概念等同于交换机,为连在其上的设备转发数据帧。网桥上的veth网卡设备相当于交换机上 的端口。可以将多个容器或虚拟机连接在其上,这些端口工作在二层,所以是不需要配置ip信息的。途中docker0网桥就为连在其上的容器转发数据帧,使得同一台宿主机上的Docker容器之间可以相互通信。docker0是普通的Linux网桥,它是可以在上面配置IP的,可以认为其内部有一个可以用于配置IP信息的网卡接口。在Docker的桥接网络模式中,docker0的IP地址作为连接之上的容器的默认网关地址存在。
2,Docker容器的4种网络模式
我们在使用docker run创建Docker容器时,可以用—net选项指定容器的网络模式,Docker有以下四种网络模式:
1,birdge模式
使用—net=bridge指定,为Dockerd的默认设置。这种模式就是将创建出来的docker容器连接到Docker网桥上(docker0网桥或者其他自定义的网桥),我们之前所创建的容器都是这种模式。在bridge模式下,Docker初始化Docker容器网络的步骤如下。
(1)创建一对虚拟网卡。
(2)赋予其中一块网卡一个类似”veth65f9”的名字,将其留在宿主机root network namespace中,并绑到Docker网桥上。
(3)将另一块网卡放入新创建的network namespace中(Docker容器中),命名为eth0。
(4)从Docker网桥的子网中选取一个未使用的IP分配给eth0,并未Docker容器设置默认路由,默认网关为Docker。
补充:
作为最常规的模式,bridge模式已经可以满足Docker容器最基本的使用需求。然而其与外界通信使用NAT协议,增加了通信的复杂性,在复杂场景下使用会有诸多限制。
2,host模式
使用—net=host指定。这种模式Docker server将不为Docker容器场景网络协议栈,即不会创建独立的network namespace,那么上面bridge模式下的步骤都不会进行。Docker容器中的进程处于宿主机的网络环境中,相当于Docker容器和宿主机共用一个network namespace,使用宿主机的网卡、IP和端口等信息。但是,容器其他方面,如文件系统、进程列表等还是和宿主机格力的。host模式很好地解决了容器与外界通信的地址转换问题,可以直接使用宿主机的IP进行通信。但是也降低了隔离性,同事还会引起网络资源的竞争与冲突。
3,container模式
container模式和host模式类似,指定新创建的容器和已经存在的某个容器共享同一个network namespace。都是共享network namespace,区别就在于host模式与宿主机共享,而container模式与某个存在的容器共享。新创建的容器不会创建自己的网卡,也不配置IP,而是与一个指定的容器共享IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。在这种情况下,两个容器的进程可以通过lo回环安科设备通信,增加了容器间通信的便利性和效率。container模式的应用场景就在于可以将一个应用的多个组件放在不同的容器中,这些容器配置container模式的网络,这样它们就可以座位一个整体对外提供服务。统一,这种模式也降低了容器间的隔离性。
4,none模式
这种模式下,Docker容器拥有自己的network namespace,但是,并不为Docker容器镜像任何网络配置。也就是说,这个Docker容器除了network namespace自带的loopback网卡外没有其他的任何网卡、IP、路由等信息,需要用户为Docker容器添加网卡、配置IP等。这种模式如果不进行特定的配置是无法正常使用的,但是优点也非常明显,它给了用户最大的自由度来定义容器的网络环境。
3,定制docker0
默认地,docker服务会在linux内核新建一个网络桥接docker0,使得物理主机和其他虚拟网络接口之间可以传递发送数据包,因此,这表现如一个独立的网络。
1,—bip=CIDR
设置docker0的IP地址和子网范围,使用CIDR格式,如192.168.100.1/24。注意这个参数仅仅是配置docker0的,对其他自定义的网桥无效。并且在指定这个参数的时候,宿主机是不存在docker0的,或者docker0已存在且docker0的IP和参数指定的IP一致才行。
2,—fixed-cidr=CIDR
限制Docker容器获取IP的范围。Docker容器默认获取的IP范围为Docker网桥(docker0网桥或者—bridge指定的网桥)的整个子网范围,此参数可将其缩小到某个子网范围内,所以这个参数比在Docker网桥的子网范围内。如docker0的IP为172.17.42.1/16,可将—fixed-cidr设为172.17.1.1/24,那么Docker容器的IP范围将为172.17.1.1~172.17.1.254。
3,—mtu=BYTES
指定docker0的最大传输单元(MTU),从写docker0的最大数据包长度。
在ubuntu系统上,你可以增加以上的配置到 /etc/default/docker 文件中的DOCKER_OPTS参数中,然后重启docker服务。
当你有一个或多个正常运行的容器时,你可以通过在主机上运行brct1命令,观察interfaces列的输出,来确定Docker已经将这些容器正确地连接到docker0网桥。下面是一个连接了两个不同容器的主机:
$ sudo brctl show bridge name bridge id STP enabled interfaces docker0 8000.3a1d7362b4ee no veth65f9 vethdda6
最后,每次新建一个容器的时候都会用到docker0 桥接网络。每次在执行docker run命令新建一个容器的时候,docker从可利用的桥接网络中随机选择一个未被使用的IP地址,以及使用桥接网络的子网掩码,用来配置容器 eth0网络接口。docker宿主主机的IP地址被docker容器作为默认的网关。
$ docker run -i -t —rm base /bin/bash [root@e623fd7cf734 /] ip addr show eth0 24: eth0:
4,新建网桥
如果你希望建立完整的自己的桥接网络,你可以在启动docker之前用 -b BRIDGE 或者 —bridge=BRIDGE选项参数高数docker使用你自己的桥接网络。
如果你已经用docker0启动docker了,你需要停止docker服务然后移除docker0。
$ sudo service docker stop $ sudo ip link set dev docker0 down $ sudo brctl delbr docker0 $ sudo iptables -t nat -F POSTROUTING
然后,在启动docker服务之前,新建你自己的桥接网络,写上你想要的配置。接下来我们新建一个简单的桥接网络,刚好用这些选项来定做docker0 ,这刚好足够说明这个技术。
$ sudo brctl addbr bridge0 $ sudo ip addr add 192.168.4.1/24 dev bridge0 $ sudo ip link set dev bridge0 up Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all — 192.168.4.0/24 0.0.0.0/0
创建好网桥之后在docker配置文件中写入启动参数,或者手动指定:
$ sudo docker -d —bridge=bridge0
这样我们自己定义的网桥就做好了,当利用docker run启动容器时,会自动绑定网络到bridge0上:
$ sudo docker run -it —rm centos [root@e623fd7cf734 /]# ip addr 1: lo:
我们可以看到新的网桥已经生效。
4,Docker容器与外部通信
决定容器是否可以访问外网取决于两个因素:
1,主机是否会转发IP数据包。这取决于转发系统内的ip_forward这个参数的配置。如果ip_forward值为1,数据包就可以被转发。Docker会使用—ip_forward=true的默认设置,一旦你docker服务启动docker会将系统的ip_forward的值修改为1。使用-ip_forward=false对系统没有改变。通常设置方法如下:
$ sysctl net.ipv4.conf.all.forwarding net.ipv4.conf.all.forwarding = 0 $ sysctl net.ipv4.conf.all.forwarding=1 $ sysctl net.ipv4.conf.all.forwarding net.ipv4.conf.all.forwarding = 1
设置这个值很重要,它将决定你的容器是否可以与外部通信以及容器间的通信。在多网桥系统中,内嵌的容器也需要配置此项。
2,你的iptables防火墙是否允许特殊连接。当deamon启动的时候,如果你设置—iptables=false,Docker将不会改变主机的iptables的防火墙规则。否则,Docker将追加规则到DOCKER filter chain
Docker 不会删除或修改已经存在在DOCKER filter chain中的规则。这允许用户进一步创建限制访问容器的任何规则。
Docker的默认规则是允许所有的外部IPs。如果只是想允许一个IP连接到容器,在DOCKER filter chain的顶部增加一条否定规则:
iptables -I DOCKER -i ext_if ! -s 8.8.8.8 -j DROP
这将只允许 8.8.8.8这个IP连接到容器。
5,容器间相互访问
决定容器能否互相访在系统层面上问取决于两个因素:
1,网络的拓扑结构是否已经连接到容器的网络接口。默认情况下,Docker会把所有的容器附加到docker0网桥下,并为两个容器间的包传输提供路径。
2,iptables是否允许特殊连接?如果你把设置 —iptables=false,当守护进程启动时,Docker不会改变你的系统iptables规则。另外,如果你保留默认设置 —icc=true,Docker服务器或向FORWARD链添加一个带有全局ACCEPT策略的默认规则。如果不保留默认设置即—icc=false,系统会把策略设为DROP。
使用docker都希望ip_forward 是打开的,至少使容器间的通讯成为可能。
但是否同意 —icc=true 或者更改为 —icc=false 使得iptables 可以保护容器以及宿主主机不被任意地端口扫描、避免被已经被渗透的容器所访问,这是一个策略问题。 (在ubuntu,是编辑/etc/default/docker文件中的DOCKER_OPTS参数,然后重启docker服务)
如果你选择最安全的设置 —icc=false ,那么当你想让它们彼此提供服务的时候如何让它们相互通讯?
答案是:使用前文提到的 —link=CONTAINER_NAME:ALIAS 选项。如果docker守护进程正在以 —icc=false 和 —iptables=true 参数运行,当以选项 —link= 执行 docker run 命令时,docker服务将插入一部分 iptables ACCEPT 规则使得新容器可以连接其他容器所暴露出来的端口(此端口指前文在 Dockerfile 中提到的EXPOSE这一行)。Docker访问互联.note
注意: —link 选项中的 CONTAINER_NAME 的值必须是 docker自动分配的容器名称,比如stupefied_pare,或者是在执行docker run 的时候用—name=指定的容器名称. 这不能使一个docker无法识别的主机名
你可以在你的Docker主机上运行iptables命令,来观察FORWARD链是否有默认的ACCEPT或DROP策略。
# When —icc=false, you should see a DROP rule: $ sudo iptables -L -n … Chain FORWARD (policy ACCEPT) target prot opt source destination DOCKER all — 0.0.0.0/0 0.0.0.0/0 DROP all — 0.0.0.0/0 0.0.0.0/0 … # When a —link= has been created under —icc=false, # you should see port-specific ACCEPT rules overriding # the subsequent DROP policy for all other packets: $ sudo iptables -L -n … Chain FORWARD (policy ACCEPT) target prot opt source destination DOCKER all — 0.0.0.0/0 0.0.0.0/0 DROP all — 0.0.0.0/0 0.0.0.0/0 Chain DOCKER (1 references) target prot opt source destination ACCEPT tcp — 172.17.0.2 172.17.0.3 tcp spt:80 ACCEPT tcp — 172.17.0.3 172.17.0.2 tcp dpt:80
6,配置DNS
怎样为Docker提供的每一个容器进行主机名和DNS配置,而不必建立自定义镜像并将主机名写 到里面?它的诀窍是覆盖三个至关重要的在/etc下的容器内的虚拟文件,那几个文件可以写入 新的信息。你可以在容器内部运行mount看到这个:
$$ mount … /dev/disk/by-uuid/1fec…ebdf on /etc/hostname type ext4 … /dev/disk/by-uuid/1fec…ebdf on /etc/hosts type ext4 … /dev/disk/by-uuid/1fec…ebdf on /etc/resolv.conf type ext4 … …
HCP配置之后,保持resolv.conf的数据到所有的容器中。Docker怎样维护在容器内的这些文件从Docker的一个版本到下一个版本的具体细节,你应该抛开这些单独的文件本身并且使用下面的Docker选项代替。
有四种不同的选项会影响容器守护进程的服务名称。
1,-h HOSTNAME 或 —hostname=HOSTNAME —设置容器的主机名,仅本机可见。这种方式是写到/etc/hostname ,以及/etc/hosts 文件中,作为容器主机IP的别名,并且将显示在容器的bash中。不过这种方式设置的主机名将不容易被容器之外可见。这将不会出现在 docker ps 或者 其他的容器的/etc/hosts 文件中。
$ sudo docker run —hostname ‘myhost’ -it centos [root@myhost /]# cat /etc/hosts 172.17.0.7 myhost …
2,—link=CONTAINER_NAME:ALIAS —使用这个选项去run一个容器将在此容器的/etc/hosts文件中增加一个主机名ALIAS,这个主机名是名为CONTAINER_NAME 的容器的IP地址的别名。这使得新容器的内部进程可以访问主机名为ALIAS的容器而不用知道它的IP。—link= 关于这个选项的详细讨论请看:容器互联
3,—dns=IP_ADDRESS —设置DNS服务器的IP地址,写入到容器的/etc/resolv.conf文件中。当容器中的进程尝试访问不在/etc/hosts文件中的主机A时,容器将以53端口连接到IP_ADDRESS这个DNS服务器去搜寻主机A的IP地址。
$ sudo docker run -it —dns=192.168.5.1 centos [root@6a38049c9052 /]# cat /etc/resolv.conf nameserver 192.168.5.1
—dns-search=DOMAIN —设置DNS服务器的搜索域,以防容器尝试访问不完整的主机名时从中检索相应的IP。这是写入到容器的/etc/resolv.conf文件中的。当容器尝试访问主机 host,而DNS搜索域被设置为 example.com ,那么DNS将不仅去查寻host主机的IP,还去查询host.example.com的IP。
$ sudo docker run -it —dns-search=www.domain.com centos [root@ae0e9e99596f /]# cat /etc/resolv.conf nameserver 192.168.4.1 search www.mydomain.com
在docker中,如果启动容器时缺少以上最后两种选项设置时,将使得容器的/etc/resolv.conf文件看起来和宿主主机的/etc/resolv.conf文件一致。这些选项将修改默认的设置。(本宿主机在实验时有一行“nameserver 192.168.4.1”,所以默认容器的配置会与宿主机一样。)
7,绑定容器端口
默认情况下,Docker容器可以连接到外部区域,但外部区域不能连接到容器。在Docker启动时,由于它在主机上创建了一个iptables伪装规则,使得每一个输出连接看起来都是由主机IP地址建立起来的。
You can see that the Docker server creates a # masquerade rule that let containers connect # to IP addresses in the outside world: $ sudo iptables -t nat -L -n … Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all — 172.17.0.0/16 0.0.0.0/0 …
当调用docker run的时候,如果你想让容器接受输入连接,你需要提供特殊选项。有两种方法实现:
首先,你可以提供 -P 或者—publish-all=true|false 选项参数来执行 docker run 命令,这将会识别所有在dockerfile中暴露的端口或者—expose参数制定的端口,然后用检查端口映射,临时端口范围由/proc/sys/net/ipv4/ip_local_port_range内核参数配置,并且随机映射到 32768 ~ 61000 之间的主机端口。
更方便的操作是使用 -p SPEC 或者—publish=SPEC 选项,这两个选项让你明确的指定docker容器的端口映射到任意的主机端口中,不局限于32768 ~ 61000.
无论如何,你应该通过审查你的NAT表,去看看docker在你的网络占做了什么。
What your NAT rules might look like when Docker # is finished setting up a -P forward: $ iptables -t nat -L -n … Chain DOCKER (2 references) target prot opt source destination DNAT tcp — 0.0.0.0/0 0.0.0.0/0 tcp dpt:49153 to:172.17.0.2:80 # What your NAT rules might look like when Docker # is finished setting up a -p 80:80 forward: Chain DOCKER (2 references) target prot opt source destination DNAT tcp — 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80
可以看到,docker暴露了这些容器的端口到通配IP地址:0.0.0.0 ,这个通配IP地址可以匹配宿主主机上任意一个可以进入的端口。如果你希望更多的限制,并且只允许容器服务通过特殊的宿主主机的外部网络接口来相互联系,那么你有两种选择。当你执行 docker run 命令时,你可以使用 -p IP:host_port:container_port 或者 -p IP::port来明确地绑定外部接口。
+
或者如果你希望dokcer永远转发到一个特殊的IP地址上,你可以编辑你的docker系统设置文件(ubuntu系统的设置方法为:编辑 /etc/default/docker文件,改写DOCKER_OPTS参数),增加选项 —ip=IP_ADDRESS 。修改完之后记得重启你的docker服务。
