Docker(四)网络管理
一、Docker网络基础配置
官方文档:https://docs.docker.com/engine/userguide/networking/
1.1 端口映射实现访问容
指定端口不指定IP映射:
在启动容器的时候,如果不指定对应参数,在容器外部是无法通过网络来访问容器内的网络应用和服务的。当容器中运行一些网络应用,要让外部访问这些应用时,可以通过-P或-p参数来指定端口映射。当使用-P标记时,Docker会随机映射一个49000~49900的端口至容器内部开放的网络端口。
# docker run -it -d --privileged=true --name ssh_test1 -p 20022:22 centos /usr/sbin/init
#这是创建了一个ssh_test1的容器,将容器的22端口映射到宿主机的20022,因为没有指定IP,也就是所有人都可以通过宿主机的20022端口访问ssh_test1。加--privileged=true是让容器拥有特权用户权限可以启动服务,/usr/sbin/init不指定,也就是不将容器的CMD或者entrupoint设置为/usr/sbin/init的haunted,dbus等服务就起不来,你还是不能用service启动服务。如果不这么做会有下面的报错:
[root@58b2b3ac310f /]# service sshd start Redirecting to /bin/systemctl start sshd.service Failed to get D-Bus connection: Operation not permitted
[root@slave06 ~]# docker ps
[root@slave06 ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2ec2c3123de9 centos "/bin/bash" 6 seconds ago Up 3 seconds 0.0.0.0:20022->22/tcp ssh_test1
[root@slave06 ~]# docker exec -it ssh_test1 /bin/bash #要进入容器一下,因为默认从官网搞得镜像没有启动ssh服务,通过exec指定/bin/bash进入容器
[root@665d3bcc1e6b /]# yum install openssh-server -y
[root@665d3bcc1e6b /]# yum install initscripts -y #需要service服务
[root@665d3bcc1e6b /]# service sshd start #启动sshd服务
#通过截图可以看到容器的sshd服务可以正常启动了,我们给其设置一个密码。
#通过另外一台机器,通过访问宿主机的20022端口,然后密码验证可以成功的登录我们刚才映射22端口的容器了。
指定IP和端口映射:
上面是没有指定映射的IP地址,这里加上映射的IP地址。
[root@slave06 ~]# docker run -it -d --privileged=true --name ssh_test2 -p 127.0.0.1:20023:22 centos /usr/sbin/init #指定只能本地通过20023端口登录容器ssh_test2的22端口。
[root@slave06 ~]# docker ps #可以看到ssh_test2的映射规则
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 79c38aa511f2 centos "/usr/sbin/init" 44 seconds ago Up 41 seconds 127.0.0.1:20023->22/tcp ssh_test2 665d3bcc1e6b centos "/usr/sbin/init" 12 minutes ago Up 12 minutes 0.0.0.0:20022->22/tcp ssh_test1
[root@slave06 ~]# docker exec -it ssh_test2 /bin/bash
[root@79c38aa511f2 /]# yum install openssh-server initscripts net-tools -y
[root@79c38aa511f2 /]# service sshd start
[root@79c38aa511f2 /]# echo "51niux.com"|passwd --stdin root
#本地ssh登录是可以的
#非宿主机以外的主机想登录是不可以的因为监听地址是127.0.0.1
映射到指定地址的任意端口映射:
# docker run -it -d --privileged=true --name ssh_test3 -p 192.168.14.56::22 centos /usr/sbin/init #指定容器ssh_testg2的22端口映射到192.168.14.56的任意端口
# docker run -it -d --privileged=true --name ssh_test4 -p 192.168.14.56::22/tcp centos /usr/sbin/init #可以再端口/ 后面指定IP协议。如果是udp协议就可以是/udp
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8679effec8cf centos "/usr/sbin/init" About a minute ago Up About a minute 192.168.14.56:32769->22/tcp ssh_test4 530c496c9a74 centos "/usr/sbin/init" About a minute ago Up About a minute 192.168.14.56:32768->22/tcp ssh_test3
# docker inspect --format={{.NetworkSettings.Ports}} 8679effec8cf #这也可以看到端口映射关系,如果不加--format={{.NetworkSettings.Ports}}就是获取容器ID8679effec8cf 的所有json格式的信息,加了就是字典的取值了
map[22/tcp:[{192.168.14.56 32769}]]
# netstat -lntup|grep docker-proxy #可以看到几个进程启动了不同的端口,就是为了给容器进行端口映射。
tcp 0 0 127.0.0.1:20023 0.0.0.0:* LISTEN 23107/docker-proxy- tcp 0 0 192.168.14.56:32768 0.0.0.0:* LISTEN 25421/docker-proxy- tcp 0 0 192.168.14.56:32769 0.0.0.0:* LISTEN 25804/docker-proxy- tcp6 0 0 :::20022 :::* LISTEN 21117/docker-proxy- tcp6 0 0 :::32770 :::* LISTEN 7301/docker-proxy-c
#这些映射怎么来的呢,iptables设置的,这些映射都是iptables规则。
1.2 在运行容器上增加端口映射
#那现在都是开机指定好端口映射,那如果我想再加端口映射咋办,第一种就是加iptables规则,第二将现在的容器做成镜像然后再次创建容器指定额外的映射端口。
#现在我们试着为ssh_test1加一个80端口的映射。
# iptables -t nat --list-rules PREROUTING #先来查看NAT表的PREROUTING链
-P PREROUTING ACCEPT -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
#从上面可以看出iptables将满足条件的数据都转发到DOCKER链上去了。
# iptables -t nat --list-rules DOCKER #查看NAT表中的DOCKER链设置的规则
-N DOCKER -A DOCKER -i docker0 -j RETURN -A DOCKER ! -i docker0 -p tcp -m tcp --dport 32770 -j DNAT --to-destination 172.17.0.6:5000 -A DOCKER ! -i docker0 -p tcp -m tcp --dport 20022 -j DNAT --to-destination 172.17.0.5:22 -A DOCKER -d 127.0.0.1/32 ! -i docker0 -p tcp -m tcp --dport 20023 -j DNAT --to-destination 172.17.0.7:22 -A DOCKER -d 192.168.14.56/32 ! -i docker0 -p tcp -m tcp --dport 32768 -j DNAT --to-destination 172.17.0.8:22 -A DOCKER -d 192.168.14.56/32 ! -i docker0 -p tcp -m tcp --dport 32769 -j DNAT --to-destination 172.17.0.9:22
# iptables -t nat -A DOCKER ! -i docker0 -p tcp -m tcp --dport 8082 -j DNAT --to-destination 172.17.0.5:80 #仿照上面,我们添加一条自己的映射规则,将宿主机的8082端口映射到172.17.0.5的80端口上。
# iptables -t filter --list-rules DOCKER #查看fifter表的DOCKER链规则
-N DOCKER -A DOCKER -d 172.17.0.6/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 5000 -j ACCEPT -A DOCKER -d 172.17.0.5/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 22 -j ACCEPT -A DOCKER -d 172.17.0.7/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 22 -j ACCEPT -A DOCKER -d 172.17.0.8/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 22 -j ACCEPT -A DOCKER -d 172.17.0.9/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 22 -j ACCEPT
# iptables -t filter -A DOCKER -d 172.17.0.5/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT
#然后为了测试,登录ssh_test1这台机器然后yum个httpd,然后设置个index.html的测试页面,然后浏览器访问以下,下面就是浏览器访问的效果。
#访问web页面效果实现了,但是你查看docker ps或者netstat或者# docker inspect --format={{.NetworkSettings.Ports}} ssh_test1
#都是看不到这条端口映射的,因为不是通过docker来添加的,而是一条iptables手工设置的规则。
1.3 容器互联实现容器间通信
容器的连接(linking)系统是除了端口映射外另一种可以与容器中应用进行交互的方式。它会在源和接收容器之间创建一个隧道,接收容器可以看到源容器指定的信息。
Link是一种比端口映射更亲密的Docker容器间通信方式,提供了更安全、高效的服务,通过环境变量和/etc/hosts文件的设置提供了从别名到具体通信地址的发现,适合于一些需要各组件间通信的应用。
自定义容器命名
连接系统依据容器的名称来执行。因此,首先需要自定义一个好记的容器命名。虽然当创建容器的时候,系统默认会分配一个名字,但自定义命名容器有两个好处:自定义的命名,比较好记,比如一个Web应用容器,我们可以给它起名叫web。当要连接其他容器,可以作为一个有用的参考点,比如连接web容器到db容器。
# docker run -d -P --name web3 training/webapp python app.py
#创建了一个名称叫做web3的容器,-d让Docker容器在后台以守护态(Daemonized)形式运行
# docker ps -l #可以查看最后一次创建的容器
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a1a94810ae8b training/webapp "python app.py" 3 minutes ago Up 2 minutes 0.0.0.0:32771->5000/tcp web3
# docker logs web3 #可以看到容器web3的实时日志信息,当然还有很多参数,可以--help查看先。
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
# docker inspect --format="{{ .Name }}" a1a94810ae8b #可以看到容器的名称
/web3
#容器的名字是唯一的。如果已经命名了一个叫web3的容器,当你要再次使用web这个名称的时候,需要先用docker rm来删除之前创建的同名容器。在执行docker run的时候如果添加 -- rm标记,则容器在终止后会立即删除。注意,-- rm和-d参数不能同时使用。
容器互联
使用--link参数可以让容器之间安全的进行交互。
# docker run -d --name db training/postgres #创建一个新的数据库容器
# docker rm -f web3 #删除之前的容器
# docker run -d -P --name web3 --link db:db training/webapp python app.py
#创建一个新的web3容器,并将它连接到db容器。此时db容器和web容器建立互联关系。--link参数的格式为--link name:alias,其中name是要链接的容器的名称,alias是这个连接的别名。
#使用--link方式让Docker在两个互联的容器之间创建了一个安全隧道,而且不用映射它们的端口到宿主机上。在启动db容器的时候并没有使用-p和-P标记,从而避免了暴露数据库端口到外部网络上。
Docker通过两种方式为容器公开连接信息
# docker run --rm --name web5 --link db:db training/webapp env #再次做个link,可以在后面加env来打印一下环境变量
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOSTNAME=4caf177efee5 DB_PORT=tcp://172.17.0.10:5432 DB_PORT_5432_TCP=tcp://172.17.0.10:5432 DB_PORT_5432_TCP_ADDR=172.17.0.10 DB_PORT_5432_TCP_PORT=5432 DB_PORT_5432_TCP_PROTO=tcp DB_NAME=/web5/db DB_ENV_PG_VERSION=9.3 HOME=/root
#其中DB_开头的环境变量是供web容器连接db容器使用,前缀采用大写的连接别名。除了环境变量,Docker还添加host信息到父容器的/etc/hosts的文件。
#容器启动过程中需要调用setupLinkedContainers函数,这个函数最终返回的是env变量。如上面输出的那些环境变量,包含了由于link操作,所需要额外为启动容器创建的所有环境变量,其执行过程如下:
找到要启动容器的所有自容器,即所有连接到源容器。 遍历所有源容器,将link信息记录起来。 将记录的link信息保存在以源容器别名为索引的隐射表中。 将link相关的环境变量(包括源容器的名称、源容器中设置的环境变量以及源容器暴露的端口信息)放入到env中,最后将env变量返回。 若以上过程中出现错误则取消做过的修改。
# docker run -t -i --rm --link db:db training/webapp /bin/bash #再次做个--link然后登陆容器查看一下。
root@9a34820fb119:/opt/webapp# cat /etc/hosts #查看一下这个容器的hosts文件
127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters 172.17.0.10 db 7234eb041b25 172.17.0.11 9a34820fb119
#这两个hosts信息,第二个是web容器,web容器用自己的id作为默认主机名,第一个是db容器的ip和主机名。
Docker容器的IP地址是不固定的,容器重启后IP地址可能就和之前不同了。在有Link关系的两个容器中,虽然接收方容器中包含有源容器的IP的环境变量, 但是如果源容器重启,接收方容器中的环境变量不会自动更新。这些环境变量主要是为容器中的第一个进程所设置的,如sshd等守护进程。 因此,link操作除了在将link信息保存在接收容器之外,还在/etc/hosts中添加了一项-------源容器的IP和别名(--link参数指定的别名),以用来解析源容器的IP地址。 并且当源容器重启后,会自动更新接收容器的/etc/hosts文件。需要注意的是这里仍然用的是别名,而不是源容器的主机名(实际上,主机名对外界是不可见的)。 因此,可以用这个别名来配置引用程序,而不需要担心IP的变化。
root@9a34820fb119:/opt/webapp# apt-get install -yqq inetutils-ping
#可以再web容器中安装ping命令来测试跟db容器的连通。官方的ubuntu 镜像默认没有安装Ping,需要自行安装。
root@9a34820fb119:/opt/webapp# env #下面是容器环境变量部分内容截取结果
HOSTNAME=9a34820fb119 DB_NAME=/angry_meninsky/db TERM=xterm DB_PORT_5432_TCP_ADDR=172.17.0.10 DB_PORT=tcp://172.17.0.10:5432 DB_PORT_5432_TCP=tcp://172.17.0.10:5432 PWD=/opt/webapp DB_PORT_5432_TCP_PORT=5432 SHLVL=1 HOME=/root DB_PORT_5432_TCP_PROTO=tc
二、Docker网络基础解释
2.1 docker0网桥
#如上图一台未经特殊网络配置的机器安装完Docker之后,在宿主机上通过ifconfig命令可以看到多了一块名为docker0的网卡。有了这样一块网卡宿主机也会在内核路由表上添加相应网络的静态路由,如下面:
# route -n
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
#上面的路由表示所有的目的的IP地址为172.17.0.0/16的数据包从docker0网卡发出。
#进入一个容器,可以看到它的两块网卡lo和eth0。lo网卡是本机的回环网卡。eth0即是与外界通信的网卡,eth0的IP为172.17.0.2/16,和宿主机上的网桥docker0在同一个网段。
#查看容器的路由表,可以发现容器的默认网关正是宿主机的docker0网卡,通过测试,容器可以顺利访问外网和宿主网络,因此表明容器的eth0网卡与宿主机的docker0网卡使相互连通的。
如果查看宿主机会发现有以“veth”开头的的网卡,那么这种网卡应该是与容器的eth0和宿主机的docker0相连的。也就是说docker0就不只是一个简单的网卡设备了,而是一个网桥。
#上图为Docker默认网络模式(bridge模式)下的网络环境拓扑图,创建了docker0网桥,并以veth pair连接各容器的网络,容器中的数据通过docker0网桥转发到eth0网卡上。
#这里网桥的概念等同于交换机,为连在其他的设备转发数据帧。网桥上的veth网卡设备相当于交换机上的端口,可以将多个容器或虚拟机连接在其上,这些端口工作在二层,所以是不需要配置IP信息的。上图中的docker0网桥就为连在其上的容器转发数据帧,使得同一台宿主机上的Docker容器之间可以相互通信。docker0是普通的Linux网桥,它是可以在上面配置IP的,可以认为其内部有一个可以用于配置IP信息的网卡接口(如同每一个Open vSwitch网桥都有一个同名的内部接口一样)。在Docker的桥接网络模式中,docker0的IP地址作为连接容器的默认网关地址存在。
# yum install bridge-utils -y #安装网桥管理工具包
# brctl show #可以查看本机的Linux网桥以及其上的端口
bridge name bridge id STP enabled interfaces docker0 8000.0242b7e59ba4 no veth1cb4597 veth628586f veth7d2a705
#docker0网桥是在Docker daemon启动时自动创建的,之后创建的Docker容器都会在docker0子网的范围内选取一个未占用的IP使用,并连接到docker0网桥上。
2.2 iptables规则
Docker安装完成后,将默认在宿主机系统上增加一些iptables规则,以用于Docker容器和容器之间以及和外界的通信。
# iptables-save
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
#这条规则关系着Docker容器和外界的通信,含义是将原地址为172.17.0.0/16的数据包(即Docker容器发出的数据),当不是从docker0网卡发出时做SNAT(源地址转换,将IP包的源地址替换为相应的网卡的地址)。这样一来,从Docker容器访问外网的流量,在外部看来就是从宿主机上发出的,外部感觉不到Docker容器的存在。
# docker run -d -p 5000:5000 training/webapp python app.py #启动一个web容器,将其5000端口映射到宿主机的5000端口
#镜像不存在不用担心,会自动下载的。
# netstat -lntup|grep :5000
tcp6 0 0 :::5000 :::* LISTEN 26702/docker-proxy-
# iptables-save #只粘贴两条有用的iptables规则了。
*nat -A DOCKER ! -i docker0 -p tcp -m tcp --dport 5000 -j DNAT --to-destination 172.17.0.5:5000 *filter -A DOCKER -d 172.17.0.5/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 5000 -j ACCEPT
#可以看到,在nat和filter的DOCKER链中分别增加了一条规则,这两条规则将访问宿主机的5000端口的流量转发到了172.1.0.5的5000端口上(真正提供服务的Docker容器的IP端口),所以外界访问Docker容器时通过iptables做DNAT(目的地址转换)实现的。此外,Docker的forward规则默认允许所有的外部IP访问容器,可以通过在fifter的DOCKER链上添加规则来对外部的IP访问做出限制,如只允许源IP为什么的数据包访问容器。
#从浏览器的结果可以看到,已经可以通过访问宿主机的IP加映射端口,将请求转交给docker容器来处理了。
#不仅与外界的通信,Docker容器之间互相通信也受到iptables规则限制。前面已经了解到同一台宿主机上的Docker容器默认都连在docker0网桥上,它们属于一个子网,这是满足相互通信的第一步。同时Docker server会在fifter的FORWORD链中增加一条ACCEPT的规则(--icc=true):
-A FORWARD -i docker0 -o docker0 -j ACCEPT
#上面这条规则是满足相互通信的第二步。当Docker server启动参数--icc(icc参数表示是否允许容器间相互通信)设置为false时,以上规则会被设置为DROP,Docker容器间的相互通信就被禁止,这种情况下,想让两个容器通信就需要在docker run时使用--link选项。
#在Docker容器与外界通信的过程中,还涉及了数据包在多个网卡间的转发(如从docker0网卡到宿主机eth0的转发),这需要内核将ip-farward功能打开,即将ip_forward系统参数设置为1。Docker server启动的时候默认会将其设置为1(--ip-forward=true),也可以通过命令手工设置:# echo 1>/proc/sys/net/ipv4/ip_forward
#以上过程中涉及的Docker server启动参数如下:
--iptables :是否允许Docker server设置宿主机的iptables规则,默认为true。当设为false时,Docker server将不会改变你宿主机上的iptables规则。 --icc : 是否允许Docker容器间相互通信,默认为true。true或false改变的是FORWARD链中相应iptables规则的策略(ACCEPT、DROP)。由于操作的是iptables规则,所以需要iptables=true才能生效。 --ip-forward : 是否将ip_forward参数设为1,默认为true,用于打开Linux内核的ip数据包转发功能。
#这些参数也是在Docker server启动时进行设置的,所以可以设置在DOCKER_OPTS变量中。
3.3 Docker容器的DNS和主机名
同一个Docker镜像可以启动很多个Docker容器,通过查看,它们的主机名并不一样,也即是说主机名并非是被写入镜像中的。实际上容器中/etc目录下有3个文件是容器启动后背虚拟文件覆盖掉的,分别是/etc/hostname、/etc/hosts、/etc/resolv.conf,通过进入容器中查看:
# mount #下面是mount的部分结果输出
/dev/vda2 on /etc/resolv.conf type ext4 (rw,relatime,data=ordered) /dev/vda2 on /etc/hostname type ext4 (rw,relatime,data=ordered) /dev/vda2 on /etc/hosts type ext4 (rw,relatime,data=ordered)
[root@9a045a6e3ea6 /]# cat /etc/hosts
127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters 172.17.0.4 9a045a6e3ea6
# cat /etc/resolv.conf
# Generated by NetworkManager search hadoop nameserver 114.114.114.114 nameserver 223.5.5.5
[root@9a045a6e3ea6 /]# cat /etc/hostname
9a045a6e3ea6
#这样能解决主机名的问题,同时也能让DNS及时更新(改变resolv.conf)。由于这些文件的维护方法随着Docker版本演进而不断变化,因此尽量不修改这些文件,而是通过Docker提供的参数进行相关设置,参数配置方式如下:
-h HOSTNAME或者--hostname=HOSTNAME : 设置容器的主机名,此名称会写在/etc/hostname和/etc/hosts文件中,也会在容器的bash提示符中看到。但是在外部,容器的主机名是无法查看的,不会出现在其他容器的hosts文件中,即 #使使用docker ps命令也差看不到。此参数是docker run命令的参数,而非Docker server的启动参数。 --dns=IP_ADDRESS... : 为容器配置DNS,写在/etc/resolv.conf中。该参数既可以在Docker server启动的时候设置也可以在docker run时设置。
#注意对以上3个文件的修改不会被docker commit保存,也就是不会保存在镜像中,重启容器也会导致修改失效。另外,在不稳定的网络环境下使用需要特别注意DNS的设置。
#上面是哪个配置文件挂载在哪呢?# docker inspect 容器ID ,都在/var/lib/docker/containers/目录下面的容器Id目录/下
4.4 Docker容器的4种网络模式
在使用docker run创建Docker容器时,可以用--net选项指定容器的网络模式,下面介绍下四种网络模式。
bridge模式
使用--net=bridge指定,为Docker的默认设置。这种模式就是将创建出来的docker容器连接到Docker网桥上(docker0网桥或其他自定义的网桥),之前创建的容器都是这种模式。在bridge模式下,Docker初始化Docker容器网络的步骤如下:
创建一对虚拟网卡(veth pair)。
赋予其中一块网卡一个类似“veth628586f”的名字,将其留在宿主机root network namespace中,并绑定到Docker网桥上。
将另一块网卡放入新创建的network namespace中(Docker容器中),命名为eth0.
从Docker网桥的子网中选取一个未使用的IP分配给eth0,并为Docker容器设置默认路由,默认网关为Docker网桥。
作为最常规的模式,bridge模式已经可以满足Docker容器最基本的使用需求。然而其与外界通信使用NAT协议,增加了通信的复杂性,在复杂场景下使用会有诸多限制。
host模式
使用--net=host指定。这种模式Docker server将不为Docker容器创建网络协议栈,即不会创建独立的network namespace,那么上面bridge模式下的步骤都不会进行。Docker容器中的进程处于宿主机的网络环境下,相当于Docker容器和宿主机共用一个network namespace,使用宿主机的网卡、IP和端口等信息。但是,容器其他方面,如文件系统、进程列表等还是和宿主机隔离的。host模式很好的解决了容器与外界通信的地址转换问题,可以直接使用宿主机的IP镜像通信。但是也降低了隔离性,同时还会引起网络资源的竞争和冲突。
container模式
container模式与host模式类似,指定新创建的容器和已存在的某个容器共享同一个network namespace。都是共享network namespace,区别就在于host模式与宿主机共享,而container模式与某个存在的容器共享。新创建的容器不会创建自己的网卡,也不配置IP,而是与一个指定的容器共享IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。在这种模式下,两个容器的进程可以通过lo回环网卡设备通信,增加了容器间通信的便利性和效率。container模式的应用场景就在于可以将一个应用的多个组件放在不同的容器中,这些容器配成container模式的网络,这样它们就可以作为一个整体对外提供服务。同样这种模式也降低了容器间的隔离性。
none模式
这种模式下,Docker容器用友自己的network namespace,但是,并不为Docker容器进行任何的网络配置。也就是说,这个Docker容器除了network namespace自带的loopback网卡外没有其他任何网卡、IP、路由等信息,需要用户为Docker容器添加网卡、配置等。这种模式如果不进行特定的配置是无法正常使用的,但是有点也非常明显,它给了用户最大的自由度来自定义容器的网络环境。
1.5 Link原理解析
在很多情况下,为了保证容器以及主机的安全,--icc通常设置为false。通过容器向外界进行端口映射的方式可以实现通信,但这种方式不够安全,因为提供服务的容器仅希望个别容器可以访问。除此之外,这种方式需要经过NAT,效率也不高。这时候,就需要使用Docker的连接(linking)系统了。Docker的连接系统可以再两个容器之间建立一个安全的通道,使得接收容器(如web应用)可以通过通道得到源容器(如数据库服务)指定的相关信息。
使用Link通信
Link是在容器创建的过程中通过--link参数创建的。
# docker run -d --name db1 training/postgres
# docker run -d -P --name web --link db1:webdb1 training/webapp python app.py #-P 是随机给映射一个端口,从下图可以看到32768端口映射了容器的5000端口
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9968d3cacabb training/webapp "python app.py" 18 seconds ago Up 15 seconds 0.0.0.0:32768->5000/tcp web 989d092979d9 training/postgres "su postgres -c '/usr" 3 minutes ago Up 3 minutes 5432/tcp db1
#--link参数的格式:--link <name or id>:alias。其中name是容器通过--name参数指定或自动生成的名字,如"db1",alias位容器的别名,这里指定别名为webdb1。这样一个link就完成了,web容器可以从db1容器中获取数据。Web容器叫做接收容器或者父容器,db1容器叫做源容器或子容器。一个接收容器可以设置多个源容器,一个源容器也可以有多个接收容器。做link的时候,Docker将连接信息以下面两种方式保存在接收容器中。
设置接收容器的环境变量 更新接收容器的/etc/hosts文件 添加iptables规则使容器连接的两个容器可以通信
设置接收容器的环境变量
当两个容器通过--link建立了连接后,会在接收容器中额外设置一些环境变量,以保持源容器的一些信息,这些信息变量包含以下几个方面:
每有一个源容器,接收容器都会设置一个名为<alias>_NAME环境变量,"alias"为源容器的别名,如上面例子的web容器中会有一个WEBDB1_NAME=/web/webdb1的环境变量 预先在源容器中设置的部分环境变量同样会设置在接收容器的环境变量中,这些环境变量包括Dockerfile中使用ENV命令设置的以及docker run命令中使用-e、--env=[]参数设置的。如db1容器中若包含doc=docker的环境变量,则web容器的环境变量则包含WEBDB1_ENV_doc=docker。
接收容器同样会为源容器中暴露的端口设置环境变量。如db1容器暴露了8432的tcp端口,当然如果有IP的话也会暴露IP地址。
更新接收容器的/etc/hosts文件
Docker容器/etc/hosts文件的设置 也是在容器启动的时候完成的。buildHostsFiles函数来完成/etc/hosts文件的设置。buildHostsFiles函数首先找到接收容器(将要启动的容器)的所有源容器,(若接收容器为bridge模式,则更新接收容器的/etc/hosts文件)然后将源容器的别名和IP地址添加到接收容器的/etc/hosts文件中。
建立iptables规则进行通信
在接收容器上设置了环境变量和更改了/etc/hosts文件之后,接收容器仅仅是得到了源容器的相关信息(环境变量、IP地址),并不代表源容器和接收容器在网络上可以互相通信。当用户为了完全起见,将Docker daemon的--icc参数设置为false时,容器间的通信就被禁止了。那么,Docker daemon如何保证两个容器间的通信呢?那就是在iptables上面添加两条规则了。