1.docker网络原理
在安装 Docker时,会创建一个新的网络接口docker0,docker0是一个虚拟的以太网桥,用于连接容器和本地宿主网络。通过命令查看目前 Docker宿主机上这个网络接口的信息。
ip a show docker0
docker0接口有符合RFC1918规范的私有IP地址,范围是172.16~172.30。接口本身的地址172.17.0.1,是这个Docker网络的网关地址,也是所有 Docker容器的网关地址。Docker会默认使用172.17.x.x作为子网地址,如果这个子网被占用了,Docker会在172.16~172.30这个范围内尝试创建子网。
Docker每创建一个容器,就会创建一对veth pair互联接口。这对接口就像管道的两端(从一端发送的数据会在另一端接收到)。这对接口中位于容器端的称为eth0接口,另一端在本地并被挂载 docker0网桥,名称以veth开头。通过这种方式,主机可以与容器通信,容器之间也可以相互通信。如此一来,Docker就创建了在主机和所有容器之间一个虚拟共享网络,如图所示。
Docker最初基于操作系统上的本地网络支持技术,较快提供了基本的网络支持。 随着Docker越来越多地应用在各种分布式环境,网络方面的需求越来越复杂,容器网络目前已经成为了云计算领域的关键技术。
2.网络参数配置
(1)配置容器 DNS 和主机名
Docker服务启动后会默认启用一个内嵌的DNS服务,来自动解析同一个网络中的容器主机名和地址,如果无法解析,则通过容器内的 DNS 相关配置进行解析。用户可以通过如下方式自定义容器的主机名和 DNS 配置 。
①通过配置文件管理
容器中主机名 和 DNS 配置信息可以通过三个系统配置文件来管理:
- /etc/resolv.conf:容器启动时,会从宿主机上复制该文件,并删除其中无法连接到的 DNS 服务器 。
- /etc/hosts:文件中默认只记录了容器自身的地址和名称。
- /etc/hostname:文件则记录了容器的主机名 。
可以在运行中的容器里直接编辑/etc/hosts、/etc/hostname 和 /etc/resolve.conf 文件。但是这些修改是临时的,只在运行的容器中保留,容器终止或重启后并不会被保存下来,也不会被docker commit 提交 。
②通过启动参数管理
容器 DNS 和主机名可以在创建或启动容器时利用下面的参数指定,注意一般不推荐与-net=host 一起使用,会破坏宿主机上的配置信息。
-h HOSTNAME 或 --hostname=HOSTNAME
设定容器的主机名,容器主机名会被写到容器内的/etc/hostname和/etc/hosts文件 。但这个主机名只有容器内能看到,在容器外部则看不到,既不会在 docker ps 中显示,也不会在其他容器的/etc/hosts 中看到。
--link=CONTAINER_NAME:ALIAS
记录其他容器主机名, 在创建容器的时候,添加一个所连接容器的主机名到容器内/etc/hosts 文件中。这样,新建容器可以直接使用主机名与所连接容器通信;
--dns=IP_ ADDRESS
指定DNS服务器,添加DNS服务器到容器的/etc/resolv.conf 中,容器会用指定的服务器来解析所有不在/etc/hosts中的主机名;
3.容器访问控制
容器的访问控制主要通过Linux 上的iptables 防火墙软件来进行管理和实现。iptables 是Linux 系统流行的防火墙软件,在大部分发行版中都自带。
(1)容器访问外部网络
容器默认指定了网关为 docker0 网桥上的 docker0内部接口 。docker0内部接口同时也是宿主机的一个本地接口 。因此,容器默认情况下可以访问到宿主机本地网络。如果容器要想通过宿主机访问到外部网络,则需要宿主机进行辅助转发,Docker服务启动时会默认开启–ip-forward=true ,自动配置宿主机系统的转发规则。查看宿主机是否开启转发的语句如下。
sudo sysctl net.ipv4.ip_forward
(2)容器之间访问
容器之间相互访问需要两方面的支持 :
- 网络拓扑是否已经连通。默认情况下,所有容器都会连接到 docker0网桥上,这意味着默认情况下拓扑是互通的;
- 本地系统的防火墙软件 iptables 是否允许访问通过, 取决于防火墙的默认规则是允许(大部分情况)还是禁止 。
4.映射容器端口到宿主机的实现
默认情况下,容器可以主动访问到外部网络的连接,但是外部网络无法访问到容器 。
(1)容器访问外部
假设容器内部的网络地址为172.17.0.2,本地网络地址为10.0.2.2,容器要能访问外部网络,源地址不能为172.17.0.2,需要进行源地址映射(Source nat,SNAT),修改为本地系统的IP地址。**映射是通过 iptables的源地址伪装操作实现的,**查看宿主机nat表上POSTROUTING链的规则,该链负责网包要离开主机前,改写其源地址。
sudo iptables -t nat -nvL POSTROUTING
上述规则将所有源地址在172.17.0.0/16网段,且不是从 docker0接口发出的流量( 即从容器中出来的流量),动态伪装为从系统网卡发出。MASQUERADE行动与传统SNAT行动相比,好处是能动态地从网卡获取地址。
备注:
127.0.0.1: 是本地环回地址,专供自己访问自己,速度快(不用经过整个协议栈);
localhost: 是个域名,一般指向127.0.0.1这个ip,操作系统支持ipv6后,也同时指向::1。
本机地址: 指的是本机物理网卡所绑定的网络协议地址,可以有多个网卡,分别连接不同的物理网络,比如192.168.0.1/255.255.255.0 和 192.168.1.1/255.255.255.0,如果服务端socket绑到192.168.0.1,能够访问192.168.0.2,不能访问192.168.1.1。
0.0.0.0: 代表本机的所有ip地址,一台服务器,一个外网地址 A,一个内网地址 B,如果我绑定的端口指定了0.0.0.0,那么通过内网地址或外网地址都可以访问应用。
(2)外部访问容器
容器允许外部访问,可以在 docker run时候通过-p或-P参数来启用。不管用哪种办法,其实也是在本地的iptable的nat表中添加相应的规则,将访问外部IP地址的包进行目标地址DNAT,将目标地址修改为容器的IP地址。
iptables -t nat -nvL
nat表中涉及两条链:PREROUTING 链负责包到达网络接口时,改写其目的地址,其中规则将所有流量都转发到 DOCKER 链;而 DOCKER 链将所有不是从 docker0进来的包(意味着不是本地主机产生)同时目标端口为指定端口的数据包的目标地址改为容器的IP地址与端口。
规则映射地址为0.0.0.0,意味着将接受主机来自所有网络接口(同一主机在不同网络中有不同的IP地址)上的流量。用户可以通过 -p IP:host_port:container_port或 -p IP::port来指定绑定的外部网络接口,以制定更严格的访问规则;如果希望映射绑定到某个固定的宿主机 IP地址,可以在 Docker 配置文件中指定DOCKER_OPTS="–ip=IP_ADDRESS",之后重启Docker 服务即可生效。
5.配置容器网桥
Docker服务默认会创建一个名称为docker0的Linux网桥(其上有一个 docker0内部接口),它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。用户使用 Docker创建多个自定义网络时可能会出现多个容器网桥。
Docker默认指定了docker0接口的IP地址和子网掩码,让主机和容器之间可以通过网桥相互通信,它还给出了MTU(接口允许接收的最大传输单元),通常是1500B,或宿主机网络路由上支持的默认值。这些值都可以在服务启动的时候进行配置。
sudo apt-get install bridge-utils sudo brctl show
每次创建一个新容器的时候,Docker从可用的地址段中选择一个空闲的IP地址分配给容器的eth0端口,并且使用本地主机上docker0接口的IP作为容器的默认网关。容器默认使用Linux 网桥,用户也可以替换为 OpenvSwitch 等功能更强大的网桥实现,支持更多的软件定义网络特性。
6.网络配置
在使用 docker run命令启动容器的时候,可以通过–net参数来指定容器的网络配置。有5个可选值bridge、none、container、host和user_defined network。
–net = bridge: 默认值,Docker安装时会创建一个命名为docke0的Linux bridge。如果不指定-network,创建的容器默认都会挂到 docker0上。bridge网络配置的subnet就是172.17.0.0/16,并且网关是172.17.0.1,这个网关就是docker。容器创建时,docker会自动从172.17.0.0/16中分配一个IP,这里16位的掩码保证有足够多的IP可以供容器使用。
–net = none: 让 Docker将新容器放到隔离的网络栈中,但是不进行网络配置。之后,可以使用自行创建veth pair配置网络。当容器终止后,Docker会清空容器,容器内的网络接口会随网络命名空间一起被清除,宿主机端接口也被自动从docker0卸载并清除 。挂在这个网络下的容器除了lo,没有其他任何网卡。应用场景为一些对安全性要求高并且不需要联网的场景,比如某个容器的唯一用途是生成随机密码,就可以放到none网络中避免密码被窃取。
–net = container:NAME_orID: 让 Docker将新建容器的进程放到一个已存在容器的网络栈中,新容器进程有自己的文件系统、进程列表和资源限制,但会和已存在的容器共享IP地址和端口等网络资源,两者进程可以直接通过lo环回接口通信。
–net=host: 不将容器网络放到隔离的命名空间中,此时容器使用本地主机的网络,它拥有完全的本地主机接口访问权限。容器进程跟主机其他root进程一样可以打开低范围的端口,可以访问本地网络服务(比如D-bus),还可以让容器做一些影响整个主机系统的事情,比如重启主机。因此使用这个选项的时候要非常小心。如果进一步使用–privileged=true参数,容器甚至会被允许直接配置主机的网络栈。在容器中可以看到host的所有网卡。
直接使用host网络最大的好处就是性能,如果容器对网络传输效率有较高要求,则可以选择host网络,牺牲一些灵活性,比如要考虑端口冲突问题,Docker host上已经使用的端口就不能再用了。
Docker host的另一个用途是让容器可以直接配置host网路,比如某些跨host的网络解决方案,其本身也是以容器方式运行的,这些方案需要对网络进行配置,比如管理 iptables,大家将会在后面进阶技术章节看到。
–net=user_defined network: 用户自行用 network相关命令创建一个网络,之后将容器连接到指定的己创建网络上去 。自行创建的网络,可以指定IP网段,该网络中的Docker主机也可以指定IP地址;
7.创建点到点连接
在默认情况下,Docker会将所有容器连接到由docker0提供的虚拟网络中。如果需要两个容器之间可以直连通信,而不用通过主机网桥进行桥接。解决办法很简单:创建一对 peer 接口,分别放到两个容器中,配置成点到点链路类型即可。
创建容器时,不设置网络,然后通过创建点到点连接进行通信。
docker run -it --net=none ubuntu /bin/bash
点到点链路不需要子网和子网掩码。此外,也可以不指定 --net=none来创建点到点链路。 这样容器还可以通过原先的网络来通信。利用类似的办法,可以创建一个只跟主机通信的容器。但是一般情况下,更推荐使用 --icc=false 命令来关闭容器之间的通信。