-
docker0网桥
当在一台未经过特殊网络配置的centos 或 ubuntu机器上安装完docker之后,在宿主机上通过ifconfig命令可以看到多了一块名为docker0的网卡,假设IP为 172.17.0.1/16。有了这样一块网卡,宿主机也会在内核路由表上添加一条到达相应网络的静态路由,可通过route -n查看:# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface ... 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 ...
此条路由表示所有目的IP地址为172.17.0.0/16的数据包从docker0网卡转发。
然后使用docker run命令创建一个执行shell(/bin/bash)的Docker容器,假设容器名称为con1。
在con1容器中可以看到它有两个网卡lo和eth0。lo设备不必多说,是容器的回环网卡;eth0即为容器与外界通信的网卡,eth0的ip 为 172.17.0.2/16,和宿主机上的网桥docker0在同一个网段。
查看con1的路由表,可以发现con1的默认网关正是宿主机的docker0网卡,通过测试, con1可以顺利访问外网和宿主机网络,因此表明con1的eth0网卡与宿主机的docker0网卡是相互连通的。
这时在来查看(ifconfig)宿主机的网络设备,会发现有一块以“veth”开头的网卡,如veth60b16bd,我们可以大胆猜测这块网卡肯定是veth设备了,而veth pair总是成对出现的。veth pair通常用来连接两个network namespace,那么另一个应该是Docker容器con1中的eth0了。之前已经判断con1容器的eth0和宿主机的docker0是相连的,那么veth60b16bd也应该是与docker0相连的,不难想到,docker0就不只是一个简单的网卡设备了,而是一个网桥。
真实情况正是如此,下图即为Docker默认网络模式(bridge模式)下的网络环境拓扑图,创建了docker0网桥,并以eth pair连接各容器的网络,容器中的数据通过docker0网桥转发到eth0网卡上。
“深入浅出”来解读Docker网络核心原理
这里的网桥概念等同于交换机,为连在其上的设备转发数据帧。网桥上的veth网卡设备相当于交换机上的端口,可以将多个容器或虚拟机连接在上面,这些端口工作在二层,所以是不需要配置IP信息的。图中docker0网桥就为连在其上的容器转发数据帧,使得同一台宿主机上的Docker容器之间可以相互通信。
大家应该注意到docker0既然是二层设备,它上面怎么设置了IP呢?docker0是普通的linux网桥,它是可以在上面配置IP的,可以认为其内部有一个可以用于配置IP信息的网卡接口(如同每一个Open vSwitch网桥都有一个同名的内部接口一样)。在Docker的桥接网络模式中,docker0的IP地址作为连于之上的容器的默认网关地址存在。
在Linux中,可以使用brctl命令查看和管理网桥(需要安装bridge-utils软件包),比如查看本机上的Linux网桥以及其上的端口:
# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02420b69b449 no veth1b11267
更多关于brctl命令的功能和用法,大家通过man brctl或brctl --help查阅。
docker0网桥是在Docker daemon启动时自动创建的,其IP默认为172.17.0.1/16,之后创建的Docker容器都会在docker0子网的范围内选取一个未占用的IP使用,并连接到docker0网桥上。
除了使用docker0网桥外,还可以使用自己创建的网桥,比如创建一个名为br0的网桥,配置IP:
# brctl addbr br0
# ifconfig br0 18.18.0.1
-
iptables规则
Docker安装完成后,将默认在宿主机系统上增加一些iptables规则,以用于Docker容器和容器之间以及和外界的通信,可以使用iptables-save命令查看。其中nat表中的POSTROUTING链有这么一条规则:-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
参数说明:
-s :源地址172.17.0.0/16
-o:指定数据报文流出接口为docker0
-j :动作为MASQUERADE(地址伪装)
上面这条规则关系着Docker容器和外界的通信,含义是:将源地址为172.17.0.0/16的数据包(即Docker容器发出的数据),当不是从docker0网卡发出时做SNAT。这样一来,从Docker容器访问外网的流量,在外部看来就是从宿主机上发出的,外部感觉不到Docker容器的存在。
那么,外界想到访问Docker容器的服务时该怎么办呢?我们启动一个简单的web服务容器,观察iptables规则有何变化。
1、首先启动一个 tomcat容器,将其8080端口映射到宿主机上的8080端口上:
#docker run -itd --name tomcat01 -p 8080:8080 tomcat:latest
2、然后查看iptabels规则,省略部分无用信息:
#iptables-save
*nat
-A POSTROUTING -s 172.17.0.4/32 -d 172.17.0.4/32 -p tcp -m tcp --dport 8080 -j MASQUERADE
...
*filter
-A DOCKER -d 172.17.0.4/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8080 -j ACCEPT
可以看到,在nat、filter的Docker链中分别增加了一条规则,这两条规则将访问宿主机8080端口的流量转发到了172.17.0.4的8080端口上(真正提供服务的Docker容器IP和端口),所以外界访问Docker容器是通过iptables做DNAT(目的地址转换)实现的。
此外,Docker的forward规则默认允许所有的外部IP访问容器,可以通过在filter的DOCKER链上添加规则来对外部的IP访问做出限制,比如只允许源IP192.168.0.0/16的数据包访问容器,需要添加如下规则:
iptables -I DOCKER -i docker0 ! -s 192.168.0.0/16 -j DROP
不仅仅是与外界间通信,Docker容器之间互个通信也受到iptables规则限制。同一台宿主机上的Docker容器默认都连在docker0网桥上,它们属于一个子网,这是满足相互通信的第一步。同时,Docker daemon会在filter的FORWARD链中增加一条ACCEPT的规则(--icc=true):
-A FORWARD -i docker0 -o docker0 -j ACCEPT
这是满足相互通信的第二步。当Docker datemon启动参数--icc(icc参数表示是否允许容器间相互通信)设置为false时,以上规则会被设置为DROP,Docker容器间的相互通信就被禁止,这种情况下,想让两个容器通信就需要在docker run时使用 --link选项。
在Docker容器和外界通信的过程中,还涉及了数据包在多个网卡间的转发(如从docker0网卡转发到宿主机ens160网卡),这需要内核将ip-forward功能打开,即将ip_forward系统参数设1。Docker daemon启动的时候默认会将其设为1(--ip-forward=true),也可以通过命令手动设置:
# echo 1 > /proc/sys/net/ipv4/ip_forward
# cat /proc/sys/net/ipv4/ip_forward
1
-
Docker容器的DNS和主机名
同一个Docker镜像可以启动很多Docker容器,通过查看,它们的主机名并不一样,也即是说主机名并非是被写入镜像中的。实际上容器中/etc/目录下有3个文件是容器启动后被虚拟文件覆盖的,分别是/etc/hostname、/etc/hosts、/etc/resolv.conf,通过在容器中运行mount命令可以查看:# docker exec -it tomcat01 bash root@3d95d30c69d3:/usr/local/tomcat# mount ... /dev/mapper/centos-root on /etc/resolv.conf type xfs (rw,relatime,attr2,inode64,noquota) /dev/mapper/centos-root on /etc/hostname type xfs (rw,relatime,attr2,inode64,noquota) /dev/mapper/centos-root on /etc/hosts type xfs (rw,relatime,attr2,inode64,noquota) ...
这样能解决主机名的问题,同时也能让DNS及时更新(改变resolv.conf)。由于这些文件的维护方法随着Docker版本演进而不断变化,因此尽量不修改这些文件,而是通过Docker提供的参数进行相关设置,配置方式如下:
-
-h HOSTNAME 或 --hostname=HOSTNAME:设置容器的主机名,此名称会写在/etc/hostname和/etc/hosts文件中,也会在容器的bash提示符看到。但是在外部,容器的主机名是无法查看的,不会出现在其他容器的hosts文件中,即使使用docker ps命令也查看不到。此参数是docker run命令的参数,而非docker daemon的启动参数。
-
--dns=IP_ADDRESS...:为容器配置DNS,写在/etc/resolv.conf中。该参数即可以在docker daemon 启动的时候设置,也可以在docker run时设置,默认为8.8.8或8.8.4.4。
注意:对以上3个文件的修改不会被docker commit保存,也就是不会保存在镜像中,重启容器也会导致修改失效。另外,在不稳定的网络环境下使用需要特别注意DNS的设置。
网友评论