我该如何设置Docker容器之间的链接,以免重启时出现故障?

79

我有一些正在运行的Docker容器,例如:

  • Nginx
  • Web应用程序1
  • Web应用程序2
  • PostgreSQL

由于Nginx需要连接到Web应用程序1和2中的Web应用程序服务器,并且Web应用程序需要与PostgreSQL通信,因此我设置了如下链接:

  • Nginx---link--->Web应用程序1
  • Nginx---link--->Web应用程序2
  • Web应用程序1---link--->PostgreSQL
  • Web应用程序2---link--->PostgreSQL

起初这个方案效果不错。但是,当我开发Web应用程序1和2的新版本时,我需要替换它们。我所做的是删除Web应用程序容器,设置新容器并启动它们。

对于Web应用程序容器,它们的IP地址最初可能是这样的:

  • 172.17.0.2
  • 172.17.0.3

在我替换它们后,它们将具有新的IP地址:

  • 172.17.0.5
  • 172.17.0.6

现在,Nginx容器中公开的环境变量仍然指向旧的IP地址。这就出现了问题。如何替换容器而不破坏容器之间的链接?同样的问题也会发生在PostgreSQL中。如果我想升级PostgreSQL镜像版本,我肯定需要删除它并运行新的镜像,但是这样做需要重新构建整个容器图,因此对于实际的服务器操作来说,这不是理想的方案。

11个回答

53
< p > --link 的效果是静态的,所以它在您的情况下不起作用(尽管您可以 remove links),目前还没有重新链接。

我们在dockerize.it上使用了两种不同的方法来解决这个问题,没有链接或ambassadors(尽管您也可以添加ambassadors)。

1)使用动态DNS

一般的想法是为您的数据库(或任何其他服务)指定一个单一的名称,并使用实际IP更新短暂的DNS服务器,因此您可以启动和停止容器。

我们从SkyDock开始。它与两个docker容器一起工作,DNS服务器和监视器自动更新它。后来,我们转向使用更定制化的Consul(还使用了一个dockerized版本:docker-consul)。

1)这个的进化(我们还没有尝试过)将是设置etcd或类似工具,并使用其自定义API来学习IP和端口。该软件应支持动态重配置。
2)使用docker桥接IP
当暴露容器端口时,您可以将它们绑定到`docker0`桥上,该桥有(或可以具有)已知地址。
当用新版本替换容器时,只需使新容器在相同IP上发布相同的端口即可。
这种方法更简单,但也更受限制。如果运行类似的软件(例如,两个容器无法在`docker0`桥上侦听3306端口),您可能会遇到端口冲突等问题...因此,我们当前的首选项是选项1。

19

链接是针对特定容器而非基于容器名称的。因此,一旦您删除容器,链接将断开连接,并且新的容器(即使具有相同的名称)也不会自动取代它。

新的网络功能允许您通过容器名称连接到容器,因此,如果您创建了一个新网络,则连接到该网络的任何容器都可以通过其名称访问其他容器。例如:

1)创建新网络

$ docker network create <network-name>       

2) 连接容器到网络

$ docker run --net=<network-name> ...
或者
$ docker network connect <network-name> <container-name>

3) 按名称对容器进行 Ping

docker exec -ti <container-name-A> ping <container-name-B> 

64 bytes from c1 (172.18.0.4): icmp_seq=1 ttl=64 time=0.137 ms
64 bytes from c1 (172.18.0.4): icmp_seq=2 ttl=64 time=0.073 ms
64 bytes from c1 (172.18.0.4): icmp_seq=3 ttl=64 time=0.074 ms
64 bytes from c1 (172.18.0.4): icmp_seq=4 ttl=64 time=0.074 ms

请参阅文档中的此部分;

注意:与传统的links不同,新的网络不会创建环境变量,也不会与其他容器共享环境变量。

此功能目前不支持别名。


我想指出这仅适用于1.9版本或更高版本。一些发行版尚未发布最新版本。 - John Giotta
另一个选项是使用网络范围的别名而不是容器名称(必须全局唯一,这并不总是好的)。但答案仍然是绝对正确的。 - Ivan Anishchuk

10
您可以使用大使容器。但不要将大使容器链接到客户端,因为这会创建与上述相同的问题。相反,使用docker主机上大使容器的公开端口(通常为172.17.42.1)。例如: postgres卷:
$ docker run --name PGDATA -v /data/pgdata/data:/data -v /data/pgdata/log:/var/log/postgresql phusion/baseimage:0.9.10 true

Postgres容器:

$ docker run -d --name postgres --volumes-from PGDATA -e USER=postgres -e PASS='postgres' paintedfox/postgresql

PostgreSQL的ambassador-container:

$ docker run -d --name pg_ambassador --link postgres:postgres -p 5432:5432 ctlc/ambassador

现在您可以启动一个postgresql客户端容器,而不需要链接ambassador容器,然后访问网关主机上的postgresql(通常为172.17.42.1):
$ docker run --rm -t -i paintedfox/postgresql /bin/bash
root@b94251eac8be:/# PGHOST=$(netstat -nr | grep '^0\.0\.0\.0 ' | awk '{print $2}')
root@b94251eac8be:/# echo $PGHOST
172.17.42.1
root@b94251eac8be:/#
root@b94251eac8be:/# psql -h $PGHOST --user postgres
Password for user postgres: 
psql (9.3.4)
SSL connection (cipher: DHE-RSA-AES256-SHA, bits: 256)
Type "help" for help.

postgres=#
postgres=# select 6*7 as answer;
 answer 
--------
     42
(1 row)

bpostgres=# 

现在,您无需重启客户端即可重新启动ambassador容器。

“-p 5432:5432” 不会将 PostgreSQL 暴露给外部世界吗? - Fang-Pen Lin
2
是的,它会这样做。如果您不想这样,可以使用“-p 172.17.42.1:5432:5432”。 - Swen Thümmler
1
顺便问一下,为什么你需要创建那个“PGDATA”容器并将其链接到一个PostgreSQL容器?我不明白,为什么不直接创建PostgreSQL容器,并将其卷映射到主机目录呢? - Fang-Pen Lin
PGDATA容器并不是必需的,我只是用它来分离关注点。启动posgres容器时,我不需要记住PGDATA容器中的卷是如何映射的。我添加它只是因为这是我目前的做法。这基本上是一个品味问题 - 我自己还不确定这是否是一个好主意... - Swen Thümmler
使用数据卷容器像 Swen 所做的那样确实是最佳实践。 - derFunk

2
这是在三周前Docker的实验版本中引入了服务功能:https://github.com/docker/docker/blob/master/experimental/networking.md。通过运行带有--publish-service <name>参数的Docker容器,您应该能够放置一个动态链接。这个名称将可以通过DNS访问。在容器重新启动时,这是持久化的(当然只要您使用相同的服务名称重新启动容器)。请注意保留HTML标签。

你如何安装那个版本?https://github.com/docker/docker/releases/tag/v1.8.0-rc1 - m59
1
请查看此页面获取更多信息:https://github.com/docker/docker/tree/master/experimental。简短版:运行“wget -qO- https://experimental.docker.com/ | sh”安装实验版本。 - Laurens Rietveld
1
这个答案曾经是正确的,但现在已经过时了,因为Docker删除了实验性的publish-service选项。现在他们使用网络范围的别名来代替。本质上是一样的。 - Ivan Anishchuk

2
如果有人仍然感到好奇,您必须在每个Docker容器的/etc/hosts文件中使用主机条目,而不应依赖于环境变量,因为它们不会自动更新。
针对每个链接的容器,将存在一个主机文件条目,其格式为LINKEDCONTAINERNAME_PORT_PORTNUMBER_TCP等等。
以下是来自Dockerdocs的说明:
重要提示关于Docker环境变量
与/etc/hosts文件中的主机条目不同,存储在环境变量中的IP地址如果源容器重新启动,则不会自动更新。我们建议使用/etc/hosts中的主机条目来解析链接容器的IP地址。
这些环境变量仅适用于容器中的第一个进程。一些守护程序(如sshd)在生成用于连接的shell时会清除它们。

1
你可以使用dockerlinks和名称来解决这个问题。
最基本的设置是首先创建一个命名数据库容器:
$ sudo docker run -d --name db training/postgres

然后创建一个连接到数据库的Web容器:

$ sudo docker run -d -P --name web --link db:db training/webapp python app.py

有了它,您就不需要手动连接容器和它们的 IP 地址。


2
嗯...看起来docker会为您生成链接的主机名,但是它生成的方式是在/etc/hosts中生成名称,这是静态的。当我重新启动链接的容器时,IP地址会更改,但/etc/hosts仍然保持不变,因此它无法正常工作。 - Fang-Pen Lin
2
自 Docker 1.0 版本以来,其分配 IP 地址的方式更加积极。当您重新启动一个容器(在此情况下是 db)时,它将获得一个新的 IP 地址。您的其他容器(无论是否重启)将保留从启动时刻开始的 ENV 值,这是无用的。 - GermanDZ
1
FYI,看起来修复即将到来,当链接的容器重新启动时,/etc/hosts 将会被更新:https://github.com/docker/docker/issues/6350 - jamshid
1
这个问题似乎已经解决了,而且提出的方法对我来说有效。 - Jens Piegsa
1
这是这里最正确的答案。唯一的问题是链接是单向的,并在容器之间添加了依赖关系:您无法交叉链接两个容器,也无法停止链接的容器,然后再次启动它(使用新选项或其他内容)。在任何这些情况下,请使用网络(以及net-alias或容器名称)。 - Ivan Anishchuk

1
使用OpenSVC方法,您可以通过以下方式解决问题:
  • 使用具有自己IP地址/ DNS名称的服务(您的终端用户将连接到此服务)
  • 告诉Docker将端口暴露给此特定IP地址(“--ip” Docker选项)
  • 配置您的应用程序以连接到服务IP地址
每次更换容器时,您都可以确保它将连接到正确的IP地址。
教程链接 => Docker Multi Containers with OpenSVC 不要错过教程末尾的“复杂编排”部分,它可以帮助您按正确顺序启动/停止容器(1个postgresql子集+ 1个webapp子集+ 1个nginx子集)
主要缺点是您将Web应用程序和PostgreSQL端口暴露给公共地址,实际上只需要将nginx TCP端口暴露给公共地址。

1

大使模式是一种不错的模式,但它也存在同样的问题:IP地址在重新启动后不一定会更新。但对于主机间的连接非常有用。也许随着新的Docker发布,这也将不再需要。 - Ivan Anishchuk
@IvanAnishchuk 是的,但是在发表这条评论时,这是正确的做法...(+2年前 ;)) - Gekkie

0

另一种选择是使用--net container:$CONTAINER_ID选项。

步骤1:创建“网络”容器

docker run --name db_net ubuntu:14.04 sleep infinity
docker run --name app1_net --link db_net:db ubuntu:14.04 sleep infinity
docker run --name app2_net --link db_net:db ubuntu:14.04 sleep infinity
docker run -p 80 -p 443 --name nginx_net --link app1_net:app1 --link app2_net:app2 ubuntu:14.04 sleep infinity

步骤二:将服务注入“网络”容器中

docker run --name db --net container:db_net pgsql
docker run --name app1 --net container:app1_net app1
docker run --name app2 --net container:app1_net app2
docker run --name nginx --net container:app1_net nginx

只要不触及“网络”容器,您的链接的IP地址就不应该改变。

使用有意义的名称创建用户自定义桥接网络可能是更好的选择。这不需要创建容器来使用它们的网络。 - Ivan Anishchuk

0

您可以将图像的连接端口绑定到主机上的固定端口,并配置服务使用它们。

这也有其缺点,但在您的情况下可能会起作用。


绑定本地端口确实有其缺点。新的Docker网络使其过时了。 - Ivan Anishchuk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接