如何设置Docker容器之间的链接,以便重启不会中断它?


80

我有一些运行如下的Docker容器:

  • Nginx的
  • 网路应用程式1
  • 网路应用程式2
  • PostgreSQL的

由于Nginx需要连接到Web应用程序1和2中的Web应用程序服务器,并且Web应用程序需要与PostgreSQL通讯,所以我有这样的链接:

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

起初这很好用。但是,当我开发Web应用程序1和Web应用程序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映像版本,我当然需要删除它并运行新的映像,但是然后我需要重建整个容器图,因此对于实际的服务器操作而言,这不是理想的选择。

Answers:


53

的效果--link是静态的,因此它不适用于您的情况(尽管可以删除链接,但目前没有重新链接)。

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

1)使用动态DNS

通常的想法是,在启动和停止容器时,为数据库(或任何其他服务)指定一个名称,并使用实际IP更新短暂的DNS服务器。

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

这种情况的演变(我们尚未尝试过)将设置etcd或类似的东西,并使用其自定义API来学习IP和端口。该软件也应该支持动态重配置。

2)使用docker bridge ip

暴露容器端口时,您可以将它们绑定到docker0网桥,该网桥具有(或可以具有)众所周知的地址。

用新版本替换容器时,只需使新容器在相同IP上发布相同端口即可。

这比较简单,但也有更多限制。如果运行类似的软件,您可能会遇到端口冲突(例如,两个容器无法在docker0网桥的3306端口上侦听),等等…所以我们目前最喜欢的是选项1。


20

链接是针对特定容器的,而不是基于容器的名称的。因此,当您删除容器时,链接将断开,并且新容器(即使名称相同)也不会自动替换。

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

1)创建新网络

$ docker network create <network-name>       

2)将容器连接到网络

$ docker run --net=<network-name> ...

要么

$ docker network connect <network-name> <container-name>

3)按名称平容器

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

另一种选择是使用网络范围的别名而不是容器名称(必须具有全局唯一性并不总是很好)。但是答案仍然是绝对正确的。
伊万·阿尼舒克

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

Postgres的大使容器:

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

现在您可以启动postgresql客户端容器,而无需链接大使容器,而可以在网关主机上访问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=# 

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


“ -p 5432:5432”不会将PostgreSQL暴露给外界吗?
方笔林

2
是的,它会的。如果不想这样做,可以使用“ -p 172.17.42.1:5432:5432”。
SwenThümmler2014年

1
顺便说一句,为什么需要创建该“ PGDATA”容器并将其链接到postgresql容器?我不明白,为什么不只创建postgresql容器,并将其卷直接映射到主机目录?
方笔林

不需要PGDATA容器,我仅将其用于关注点分离。启动posgres容器时,不需要记住PGDATA容器中的卷是如何映射的。我添加了它,因为这就是我目前正在这样做的方式。这基本上是一个品味问题-我自己不确定这是否是个好主意...
SwenThümmler2014年

与Swen一样,使用数据量容器确实是最佳实践。
derFunk 2015年

2

如果仍然有人好奇,则必须使用每个Docker容器的/ etc / hosts文件中的主机条目,并且不应依赖于ENV变量,因为它们不会自动更新。

每个链接容器都有一个主机文件条目,格式为LINKEDCONTAINERNAME_PORT_PORTNUMBER_TCP等。

以下是来自docker docs

有关Docker环境变量的重要说明

与/ etc / hosts文件中的主机条目不同,如果重新启动源容器,则不会自动更新环境变量中存储的IP地址。我们建议使用/ etc / hosts中的主机条目来解析链接容器的IP地址。

这些环境变量仅在容器中的第一个过程中设置。在生成用于连接的shell时,某些守护程序(例如sshd)将清除它们。


2

这已包含在3周前的docker实验性构建中,并引入了服务:https : //github.com/docker/docker/blob/master/experimental/networking.md

您应该能够通过运行带有--publish-service <name>参数的docker容器来获得动态链接。可以通过DNS访问该名称。这在重新启动容器时是持久的(只要您使用当然具有相同服务名称的重新启动容器)



1
请参阅此页面以获取更多信息:github.com/docker/docker/tree/master/experimental。短版:运行wget -qO- https://experimental.docker.com/ | sh以安装实验版
Laurens Rietveld

1
这个答案是有效的,但由于docker删除了实验性publish-service选项,因此现在已过时。现在,他们有了网络范围的别名。本质上是一样的。
伊万·阿尼舒克

1

您可以使用带有名称的dockerlink解决此问题。

最基本的设置是首先创建一个命名数据库容器:

$ sudo docker run -d --name db training/postgres

然后创建一个连接到db的Web容器:

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

这样,您无需手动连接具有其IP地址的容器。


2
嗯...看起来docker会以某种方式为您生成链接的主机名,但是这样做的方式是,它在/ etc / hosts中生成名称,这是静态的,当我重新启动链接的容器时,IP发生了变化,但是/ etc / hosts保持不变,因此将无法使用。
方笔林

2
由于Docker 1.0版更积极地分配IP地址。重新启动容器时(db在这种情况下),它将收到一个新的IP地址。您的另一个容器(无论是否重新启动)从启动它的那一刻起便会保留ENV值,并且它是无用的。
GermanDZ 2014年

1
fyi似乎正在修复,重新启动链接的容器时/ etc / hosts将被更新:github.com/docker/docker/issues/6350
jamshid

1
这个问题似乎已经解决,建议的方法对我有用。
Jens Piegsa 2015年

1
这是最正确的答案。唯一的问题是链接是单向的,并且在容器之间添加了依赖性:您不能交叉链接两个容器,也不能停止链接的容器然后再次启动它(使用新选项或其他方法)。在任何情况下,请使用网络(和/net-alias或容器名称)。
伊万·阿尼舒克

1

使用OpenSVC方法,可以通过以下方法解决:

  • 使用具有自己的IP地址/ DNS名称的服务(您的最终用户将连接到的服务)
  • 告诉docker将端口暴露给该特定ip地址(“ --ip” docker选项)
  • 配置您的应用程序以连接到服务IP地址

每次更换容器时,请确保将其连接到正确的IP地址。

此处的教程=>具有OpenSVC的Docker多个容器

不要错过tuto末尾的“复杂编排”部分,它可以帮助您以正确的顺序启动/停止容器(1个postgresql子集+ 1个webapp子集+ 1个nginx子集)

主要缺点是您将webapp和PostgreSQL端口公开给公共地址,实际上只有nginx tcp端口需要公开。


1

您还可以尝试使用具有中间容器的ambassador方法来保持链接完整无缺...(请参阅https://docs.docker.com/articles/ambassador_pattern_linking/)以获取更多信息


大使是一个很好的模式,但它也遇到了同样的问题:重启后不一定会更新ip地址。但是,它们非常适合主机间的连接。好吧,也许不需要新的Docker版本。
伊万·阿尼舒克

@IvanAnishchuk是真实的,但当时发表评论是这条路要走...(+2年前;))
Gekkie

0

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

这也有其缺点,但是可能适合您的情况。


绑定本地主机端口确实有其缺点。新的Docker网络使其过时。
伊万·阿尼舒克

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

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

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地址就不应更改。


用户创建的具有有意义名称的网桥可能是一个更好的选择。不需要仅创建容器来使用其网络。
伊万·阿尼舒克

0

这种情况下,您需要网络范围的别名。这是一项相当新的功能,可用于“发布”一个为整个网络提供服务的容器,这不同于只能从一个容器访问的链接别名。

它不会在容器之间添加任何类型的依赖关系,只要它们都在运行,它们就可以进行通信,而无论重新启动,替换和启动顺序如何。我相信它在内部使用DNS,而不是/ etc / hosts

像这样使用它:docker run --net=some_user_definied_nw --net-alias postgres ...您可以使用该别名从同一网络上的任何容器连接到它。

不能在默认网络上工作,不幸的是,您必须使用来创建一个,docker network create <network>然后将其--net=<network>用于每个容器(compose也支持它)。

除了容器发生故障并因此无法通过别名访问之外,多个容器还可以共享一个别名,在这种情况下,不能保证将其解析为正确的别名。但是在某些情况下,可能有助于无缝升级。

到目前为止,还没有很好的文档记录,仅通过阅读手册页就很难弄清楚。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.