一、 遗留的容器连接方式 --link
--link是docker 的一个遗留的特征,最终可能被删除。除非绝对需要使用,不然,建议使用 user-defined network 建立容器间的连接。不同的是,使用--link,容器间可共享变量,而使用 user-defined 网络不行,但是也通过一种更可控方式实现容器间变量共享,例如数据卷。
本文章主要默认桥接网络下 容器间采用遗留连接特征(--link)通讯细节,并且讨论容器间的安全交互问题。随着docker 网络特征(network features)的引入,仍然可以使用 创建连接方式促进容器间交互,但是它与 默认桥接网络和自定义的桥接网络又存在不同。
本文章首先讨论容器间端口连接问题,然后深入 讨论容器在 默认桥接网络下的连接问题。
1.1 使用网络端口映射方式连接
首先,运行一个简单的python flask 程序
[root@localhost docker]# docker run -P -d training/webapp python app.py
容器创建后,由于采用 -P(大写)参数,docker自动将容器内部需要开放的端口自动映射到主机上一个随机较高的短暂端口范围内的端口。我们可使用docker ps 或 docker container ls 查看
[root@localhost docker]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a9e336610a0a training/webapp "python app.py" 7 seconds ago Up 6 seconds 0.0.0.0:32768->5000/tcp elated_hugle
可发现,将容器的5000 tcp端口映射到主机端口32768.
也可以通过指定具体端口映射关系,例如将主机端口80映射到容器端口5000 标记 -p(小写)
[root@localhost docker]# docker run -p 80:5000 -d training/webapp python app.py
b9401a847804210e665e540ff8bc3e9b80863731eeb96f4aa5e0b82465834095
[root@localhost docker]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b9401a847804 training/webapp "python app.py" 4 seconds ago Up 2 seconds 0.0.0.0:80->5000/tcp affectionate_mahavira
[root@localhost docker]#
如此方式,智能一个容器的5000端口映射到主机,但是如此存在多个容器间该端口需要通讯,则不适用。解决方式,采用主机的一个较高临时端口范围来绑定不同容器的临时端口。
[root@localhost docker]# docker run -d -p 8000-9000:5000 --name node1 --hostname test1 training/webapp python app.py
c92a9244159a569031882bc9429dbdad9b5993d99cac61128eb529103f1ff543
[root@localhost docker]# docker run -d -p 8000-9000:5000 --name node2 --hostname test2 training/webapp python app.py
a7387aae557b039e26a13b7c0bad41b2f366283f778654efa60fdcc67697b672
[root@localhost docker]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a7387aae557b training/webapp "python app.py" 2 seconds ago Up 1 second 0.0.0.0:8001->5000/tcp node2
c92a9244159a training/webapp "python app.py" 13 seconds ago Up 12 seconds 0.0.0.0:8000->5000/tcp node1
[root@localhost docker]#
如此,每个容器的5000端口,将自动映射到主机的端口范围内。
默认情况下,使用 -p标记,是将指定端口绑定到主机所有接口(0.0.0.0)上,使用 -p另一种方式可把端口映射到本地 local host上,或者指定某个网卡上。
[root@localhost docker]# docker run -d -p 127.0.0.1:80:5000 training/webapp python app.py
37ebfc97fbf60d7cdc2b939e34fb555847395499fe00aa183a16810a118d450e
[root@localhost docker]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
37ebfc97fbf6 training/webapp "python app.py" 10 seconds ago Up 8 seconds 127.0.0.1:80->5000/tcp brave_albattani
如此方式将主机 localhost 的端口80 绑定到容器端口 5000。但是和上述说的确定一样,只能存在一个容器能够绑定,可以使用动态绑定方式
[root@localhost docker]# docker run -d -p 127.0.0.1::5000 training/webapp python app.py
d654f947c0f7ab2bfb2a70e20e9266dc598276641cc544986b4e71c899bd8753
[root@localhost docker]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d654f947c0f7 training/webapp "python app.py" 5 seconds ago Up 3 seconds 127.0.0.1:32768->5000/tcp trusting_noether
此时,动态端口为32768.再动态启动一个, 32769
[root@localhost docker]# docker run -d -p 127.0.0.1::5000 training/webapp python app.py
c5e7b5dfc039652d3d5545c622b38bc01637a056d8bad4fbf47e3578c3a9d612
[root@localhost docker]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c5e7b5dfc039 training/webapp "python app.py" 4 seconds ago Up 3 seconds 127.0.0.1:32769->5000/tcp sharp_easley
d654f947c0f7 training/webapp "python app.py" About a minute ago Up About a minute 127.0.0.1:32768->5000/tcp trusting_noether
默认情绪下,绑定端口一般都为tcp协议,可在 -p的参数下带 /udp /tcp /sctp等方式,指定协议类型
[root@localhost docker]# docker run -d -p 127.0.0.1:82:5000/udp training/webapp python app.py
1304cc996745431dea2460a1d9c87b4549c5f29521b8aec4d9a7e001a9d0e3eb
[root@localhost docker]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1304cc996745 training/webapp "python app.py" 2 seconds ago Up 1 second 5000/tcp, 127.0.0.1:82->5000/udp clever_murdock
同样,可使用 docker port 指令,查看容器具体端口的映射情况 可通过容器ID 或者容器名
[root@localhost docker]# docker port node1 5000
0.0.0.0:8000
注意: -p 标记可重复使用,用于指定不同端口映射关系。
1.2 连接容器到链接系统
这一章节,主要介绍,默认桥接网络模式下容器连接。网络端口映射不是容器间通讯的唯一方式,docker 还拥有一个链接系统,允许容器相互连接,并共享连接信息,当一个容器被连接,则源容器的信息将可被发送给被连接的容器。因此,被连接的容器可查看源容器的被选定数据源的各方面信息,例如变量等。
1.2.1 命名的重要性
为建立链接系统,docker依赖容器的命名,容器在创建时,如果未指定命名,则自动给一个名字,使用 --name 标记,在启动容器时,可自定义指定容器名字。使用名字具有如下两个好处:
(1) 便于记忆,例如一个web程序的容器,可命名为 web
(2) 为docker 提供了一个方便的引用点,例如将容器 web 连接到 容器db
例如启动一个名为web的容器,然后调用 docker ps , 或者docker container inspect web 查看容器具体信息
[root@localhost hadoop]# docker run -d -P --name web training/webapp python app.py
93e7d4982c5436783ad2f57270aeb73feaaa57e7273e932c5524542c0aeb967f
[root@localhost hadoop]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
93e7d4982c54 training/webapp "python app.py" 6 seconds ago Up 5 seconds 0.0.0.0:32769->5000/tcp web
注意 :容器名称必须唯一,意味着一台主机的daemon 下,只能存在一个名为web的容器,如果需要重新建立一个名为web的容器,需要先删除旧的容器,这里有个建议,在启动容器时 docker create / run 带上标记 --rm , 一旦容器停止,则自动删除该容器。
1.2.2 容器间沟通 --link
链接 允许容器发现各自对方,同时构建安全的信息交互链路,当创建一个链接时,则在源容器和接收容器间创建了一条沟通渠道,接收容器可浏览源容器的部分数据信息。我们使用标记 --link ,这次我们创建一个包含数据库的容器,。
[root@localhost hadoop]# docker run -d --name db training/postgres
8ebe0029de23186cfd729b48c4e227be6392ad6d56f1cb53b96e9e789883ee7a
[root@localhost hadoop]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8ebe0029de23 training/postgres "su postgres -c '/us…" 49 seconds ago Up 47 seconds 5432/tcp db
93e7d4982c54 training/webapp "python app.py" 13 minutes ago Up 13 minutes 0.0.0.0:32769->5000/tcp web
我们在再把之前的容器web 删除, 然会在新建一个容器 连接db
[root@localhost hadoop]# docker container rm -f web
web
[root@localhost hadoop]# docker run -d -P --link db:db --name web training/webapp python app.py
ae71a962f945a14a1f879258e326318d374d91fe99628120d3f69d369f4ba4d5
[root@localhost hadoop]#
使用 --link 标记的个数 --link <name or id>:alias 冒号左边为容器的名称或者ID 右边为 源容器的别名。
然后我们可调用 docker inspect -f 格式查看web 容器的链接信息
[root@localhost hadoop]# docker inspect -f {{.HostConfig.Links}} web
[/db:/web/db]
[root@localhost hadoop]#
可发现,web容器已经链接到 db容器,/web/db, 允许web容器访问db容器的部分信息,进入容器 web 打开 /etc/hosts 就可以看到db容器的信息,同时可直接ping db
[root@localhost hadoop]# docker exec -ti web /bin/bash
root@ae71a962f945:/opt/webapp# pwd
/opt/webapp
root@ae71a962f945:/opt/webapp# 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
192.168.0.3 db 8ebe0029de23
192.168.0.2 ae71a962f945
root@ae71a962f945:/opt/webapp# ping db
PING db (192.168.0.3) 56(84) bytes of data.
64 bytes from db (192.168.0.3): icmp_seq=1 ttl=64 time=0.130 ms
64 bytes from db (192.168.0.3): icmp_seq=2 ttl=64 time=0.038 ms
那么当我们使用 --link 建立容器间的链接时,实际进行什么操作? 我们知道,一个链接运行 源容器将自身部分信息传递给接收容器,这里接收容器是 web, 源容器是db, web可方式db的某些信息, 实际上,docker 在web容器和db容器新建一个安全通道,该通道并不需要容器对外开放任何端口。而且也不用在启动容器时,采用参数 -p 或者 -P指定容器开发端口,这就是 link的最大好处之一,不需要向容器公开通道,这里就是 PostgreSQL数据库不需要向 web公开端口。
docker传递源容器的信息给接收容器,一般采用如下两种方式:
(1) 环境变量
(2) 更新/etc/hosts 文件 。 前面通过查看该文件可观察到 添加了db容器的IP地址和容器ID
1.3 环境变量
在创建容器间的链接时,docker会创建根据 --link 的参数,创建一些环境变量,同时docker可能暴露源 容器的环境变量,那些可能被暴露源容器的环境变量来自于如下:
(1)源容器的Dockerfile 中使用ENV 指定的环境变量
(2)源容器在启动时,-e 或 --env 或者 --env-file 指定的环境变量
那些带有源容器信息的环境变量,被允许在接收容器程序化发现(特定规则,下文讲到)。因此,注意,环境变量中避免存储一些敏感信息,避免被那些连接容器发现。
docker在创建链接时,根据--link参数 为接收端容器生成一个 <alias>_NAME 的环境变量,这里指web容器,我们可进入容器内部查询。
[root@localhost hadoop]# docker exec -ti web /bin/bash
root@0af1176d2df9:/opt/webapp# echo $BACKDB_NAME
/web/backdb
root@0af1176d2df9:/opt/webapp#
我们使用 --link db:backdb 将web容器链接到db容器,则在web容器新建一个环境变量 BACKDB_NAME=/web/backdb
同样 docker 还为源容器的公开的端口新建了一些列的环境变量。以如下的规则;
<name>_PORT_<port>_<protocol>
每个段部分的命名与介绍:
<name> 即为 --link 指定的别名, 例如 这里的backdb
<port> 开放的端口
<protocol> TCP 或者UDP
docker 使用这些前缀定义,定义了三个不同的环境变量:
固定前缀 ADDR 返回源容器地址信息 例如 BACKDB_PORT_5432_TCP_ADDR =192.168.0.2
固定前缀PORT返回源容器的开放端口信息 例如: BACKDB_PORT_5432_TCP_PORT = 5432
固定前缀PROTO 返回源容器开放端口通讯协议: 例如: BACKDB_PORT_5432_TCP_PROTO=tcp
当有多个开放端口时,针对每个端口定义该三个变量,如果有4个开放端口,则存在12个如此规则的环境变量。
[root@localhost hadoop]# docker exec -ti web /bin/bash
root@0af1176d2df9:/opt/webapp# echo $BACKDB_PORT_5432_TCP_ADDR
192.168.0.2
root@0af1176d2df9:/opt/webapp# echo $BACKDB_PORT_5432_TCP_PORT
5432
root@0af1176d2df9:/opt/webapp# echo $BACKDB_PORT_5432_TCP_PROTO
tcp
root@0af1176d2df9:/opt/webapp#
另外,docker 还创建一个环境变量 <alias>_PORT 返回第一个开放端口的具体信息 (包含了ip、port 、protocol)
root@0af1176d2df9:/opt/webapp# echo $BACKDB_PORT
tcp://192.168.0.2:5432
root@0af1176d2df9:/opt/webapp#
最后,针对源容器的环境变量,在接收容器,将每个变量采用<alias>_ENV_<name>方式保存,并在接收容器启动时创建该些变量,例如前面,如果我们启动db容器时 带入参数 -e DBNAME="testName". 则我们在接收容器web内可查看该变量,注意alias别名前缀使用大写。
root@0af1176d2df9:/opt/webapp# echo $BACKDB_ENV_DBNAME
testName
注意:与/etc/hosts文件中的主机条目不同,如果源容器重新启动,存储在环境变量中的IP地址不会自动更新。因此,一般使用/etc/hosts 解析源容器的IP地址信息。
那些变量,仅为容器第一个进程设置,当一些守护经常构建时,例如sshd, 那些变量将被清除。
1.4 更新 /etc/hosts文件
容器间创建链接后,docker自动更新接收容器的/etc/hosts文件,添加源容器的ip地址等相关信息到条目中,源容器的/etc/hosts未保存接收容器的信息。
root@0af1176d2df9:/opt/webapp# 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
192.168.0.2 backdb 92aca4dce8fc db
192.168.0.3 0af1176d2df9
root@0af1176d2df9:/opt/webapp# exit
[root@localhost hadoop]#
[root@localhost hadoop]# docker exec -ti db /bin/bash
root@92aca4dce8fc:/# 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
192.168.0.2 92aca4dce8fc
root@92aca4dce8fc:/#
如web容器内保存 db容器的 名称,ID和别名,web容器分别利用名称 ID和别名 均可访问到db容器
root@0af1176d2df9:/opt/webapp# 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
192.168.0.2 backdb 92aca4dce8fc db
192.168.0.3 0af1176d2df9
root@0af1176d2df9:/opt/webapp# ping db
PING backdb (192.168.0.2) 56(84) bytes of data.
64 bytes from backdb (192.168.0.2): icmp_seq=1 ttl=64 time=0.145 ms
64 bytes from backdb (192.168.0.2): icmp_seq=2 ttl=64 time=0.038 ms
64 bytes from backdb (192.168.0.2): icmp_seq=3 ttl=64 time=0.044 ms
^C
--- backdb ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.038/0.075/0.145/0.050 ms
root@0af1176d2df9:/opt/webapp# ping backdb
PING backdb (192.168.0.2) 56(84) bytes of data.
64 bytes from backdb (192.168.0.2): icmp_seq=1 ttl=64 time=0.112 ms
64 bytes from backdb (192.168.0.2): icmp_seq=2 ttl=64 time=0.037 ms
^C
--- backdb ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.037/0.074/0.112/0.038 ms
root@0af1176d2df9:/opt/webapp# ping 92aca4dce8fc
PING backdb (192.168.0.2) 56(84) bytes of data.
64 bytes from backdb (192.168.0.2): icmp_seq=1 ttl=64 time=0.053 ms
64 bytes from backdb (192.168.0.2): icmp_seq=2 ttl=64 time=0.045 ms
^C
--- backdb ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.045/0.049/0.053/0.004 ms
root@0af1176d2df9:/opt/webapp# ping 192.168.0.2
PING 192.168.0.2 (192.168.0.2) 56(84) bytes of data.
64 bytes from 192.168.0.2: icmp_seq=1 ttl=64 time=0.098 ms
64 bytes from 192.168.0.2: icmp_seq=2 ttl=64 time=0.041 ms
^C
--- 192.168.0.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.041/0.069/0.098/0.029 ms
root@0af1176d2df9:/opt/webapp#
一个容器可允许被多个容器同时连接,但是注意不能启动相同容器名