¿Cómo configuro la vinculación entre contenedores Docker para que el reinicio no la rompa?

80

Tengo algunos contenedores Docker que se ejecutan como:

  • Nginx
  • Aplicación web 1
  • Aplicación web 2
  • PostgreSQL

Dado que Nginx necesita conectarse a los servidores de aplicaciones web dentro de la aplicación web 1 y 2, y las aplicaciones web necesitan hablar con PostgreSQL, tengo enlaces como este:

  • Nginx --- enlace ---> Aplicación web 1
  • Nginx --- enlace ---> Aplicación web 2
  • Aplicación web 1 --- enlace ---> PostgreSQL
  • Aplicación web 2 --- enlace ---> PostgreSQL

Esto funciona bastante bien al principio. Sin embargo, cuando desarrollo una nueva versión de la aplicación web 1 y la aplicación web 2, necesito reemplazarlas. Lo que hago es eliminar los contenedores de la aplicación web, configurar nuevos contenedores e iniciarlos.

Para los contenedores de aplicaciones web, sus direcciones IP al principio serían algo como:

  • 172.17.0.2
  • 172.17.0.3

Y después de que los reemplace, tendrán nuevas direcciones IP:

  • 172.17.0.5
  • 172.17.0.6

Ahora, esas variables de entorno expuestas en el contenedor Nginx todavía apuntan a las antiguas direcciones IP. Aquí viene el problema. ¿Cómo reemplazo un contenedor sin romper la conexión entre contenedores? El mismo problema también le sucederá a PostgreSQL. Si quiero actualizar la versión de la imagen de PostgreSQL, ciertamente necesito eliminarla y ejecutar la nueva, pero luego necesito reconstruir todo el gráfico del contenedor, por lo que esto no es ideal para el funcionamiento del servidor en la vida real.

Fang-Pen Lin
fuente

Respuestas:

53

El efecto de --linkes estático, por lo que no funcionará para su escenario (actualmente no hay reenlace, aunque puede eliminar enlaces ).

Hemos estado usando dos enfoques diferentes en dockerize.it para resolver esto, sin enlaces ni embajadores (aunque también podrías agregar embajadores).

1) Utilice DNS dinámico

La idea general es que especifique un nombre único para su base de datos (o cualquier otro servicio) y actualice un servidor DNS de corta duración con la IP real a medida que inicia y detiene los contenedores.

Empezamos con SkyDock . Funciona con dos contenedores docker, el servidor DNS y un monitor que lo mantiene actualizado automáticamente. Más tarde pasamos a algo más personalizado usando Consul (también usando una versión dockerizada: docker-consul ).

Una evolución de esto (que no hemos probado) sería configurar etcd o similar y usar su API personalizada para aprender las IP y los puertos. El software también debería admitir la reconfiguración dinámica.

2) Utilice la ip de Docker Bridge

Al exponer los puertos del contenedor, puede vincularlos al docker0puente, que tiene (o puede tener) una dirección bien conocida.

Cuando reemplace un contenedor con una nueva versión, simplemente haga que el nuevo contenedor publique el mismo puerto en la misma IP.

Esto es más simple pero también más limitado. Es posible que tenga conflictos de puertos si ejecuta un software similar (por ejemplo, dos contenedores no pueden escuchar en el puerto 3306 en el docker0puente), etcétera ... así que nuestro favorito actual es la opción 1.

Abel Muiño
fuente
20

Los enlaces son para un contenedor específico, no se basan en el nombre de un contenedor. Por lo tanto, en el momento en que elimina un contenedor, el enlace se desconecta y el nuevo contenedor (incluso con el mismo nombre) no ocupará su lugar automáticamente.

La nueva función de red le permite conectarse a contenedores por su nombre, por lo que si crea una nueva red, cualquier contenedor conectado a esa red puede llegar a otros contenedores por su nombre. Ejemplo:

1) Crear nueva red

$ docker network create <network-name>       

2) Conectar contenedores a la red

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

o

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

3) Contenedor de ping por nombre

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

Consulte esta sección de la documentación;

Nota: A diferencia de la herencia, linksla nueva red no creará variables de entorno ni las compartirá con otros contenedores.

Actualmente, esta función no admite alias

Hemerson Varela
fuente
Me gustaría señalar que esto solo funciona en la versión 1.9 o posterior. Algunas distribuciones aún no se han lanzado con la última versión.
John Giotta
Otra opción es usar un alias de ámbito de red en lugar del nombre del contenedor (que tiene que ser globalmente único no siempre es bueno). Sin embargo, la respuesta es absolutamente correcta.
Ivan Anishchuk
10

Puede utilizar un contenedor de embajador . Pero no vincule el contenedor del embajador a su cliente, ya que esto crea el mismo problema que el anterior. En su lugar, use el puerto expuesto del contenedor ambassador en el host de la ventana acoplable (normalmente 172.17.42.1). Ejemplo:

volumen de postgres:

$ docker run --name PGDATA -v /data/pgdata/data:/data -v /data/pgdata/log:/var/log/postgresql phusion/baseimage:0.9.10 true

contenedor de postgres:

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

embajador-contenedor para postgres:

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

Ahora puede iniciar un contenedor de cliente postgresql sin vincular el contenedor ambassador y acceder a postgresql en el host de la puerta de enlace (normalmente 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=# 

Ahora puede reiniciar el contenedor ambassador sin tener que reiniciar el cliente.

Swen Thümmler
fuente
¿"-p 5432: 5432" no expone PostgreSQL al mundo exterior?
Fang-Pen Lin
2
Sí lo será. Si no desea esto, puede usar "-p 172.17.42.1:5432:5432".
Swen Thümmler
1
por cierto, ¿por qué necesita crear ese contenedor "PGDATA" y vincularlo a un contenedor postgresql? No entiendo, ¿por qué no crear un contenedor postgresql y asignar su volumen a un directorio de host directamente?
Fang-Pen Lin
El contenedor PGDATA no es necesario, lo uso solo para separar preocupaciones. Al iniciar el contenedor posgres, no necesito recordar cómo se asignan los volúmenes en el contenedor PGDATA. Lo agregué porque así es como lo estoy haciendo actualmente. Es básicamente una cuestión de gustos, yo mismo no estoy seguro de si es una buena idea o no ...
Swen Thümmler
De hecho, es una buena práctica utilizar un contenedor de volumen de datos como lo hace Swen.
derFunk
2

Si alguien todavía tiene curiosidad, debe usar las entradas de host en el archivo / etc / hosts de cada contenedor de la ventana acoplable y no debe depender de las variables ENV, ya que no se actualizan automáticamente.

Habrá una entrada de archivo de host para cada contenedor vinculado en el formato LINKEDCONTAINERNAME_PORT_PORTNUMBER_TCP, etc.

Lo siguiente es de docker docs

Notas importantes sobre las variables de entorno de Docker

A diferencia de las entradas de host en el archivo / etc / hosts, las direcciones IP almacenadas en las variables de entorno no se actualizan automáticamente si se reinicia el contenedor de origen. Recomendamos utilizar las entradas de host en / etc / hosts para resolver la dirección IP de los contenedores vinculados.

Estas variables de entorno solo se establecen para el primer proceso en el contenedor. Algunos demonios, como sshd, los eliminarán cuando generen shells para la conexión.

marco de nieve
fuente
2

Esto se incluye en la compilación experimental de Docker hace 3 semanas, con la introducción de los servicios: https://github.com/docker/docker/blob/master/experimental/networking.md

Debería poder instalar un enlace dinámico ejecutando un contenedor acoplable con los --publish-service <name>argumentos. Este nombre será accesible a través del DNS. Esto es persistente en los reinicios del contenedor (siempre que reinicie el contenedor con el mismo nombre de servicio que es, por supuesto)

Laurens Rietveld
fuente
¿Cómo instalas esa versión? github.com/docker/docker/releases/tag/v1.8.0-rc1
m59
1
Consulte esta página para obtener más información: github.com/docker/docker/tree/master/experimental . Versión corta: ejecutar wget -qO- https://experimental.docker.com/ | shpara instalar la versión experimental
Laurens Rietveld
1
Esta respuesta era válida, pero ahora está desactualizada porque Docker eliminó la publish-serviceopción experimental . Ahora tienen alias de ámbito de red. Sin embargo, esencialmente lo mismo.
Ivan Anishchuk
1

Puede utilizar enlaces acoplables con nombres para solucionar este problema.

La configuración más básica sería crear primero un contenedor de base de datos con nombre :

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

luego cree un contenedor web que se conecte a db:

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

Con esto, no es necesario que conecte contenedores manualmente con sus direcciones IP.

Pak
fuente
2
Hm ... parece que Docker generará de alguna manera el nombre de host vinculado para usted, pero de la forma en que lo hace, genera el nombre en / etc / hosts, es estático, cuando reinicio el contenedor vinculado, la IP cambia, pero / etc / hosts sigue siendo el mismo, por lo que no funcionará.
Fang-Pen Lin
2
Dado que la versión 1.0 de Docker es más agresiva, la asignación de direcciones IP. Cuando reinicia un contenedor ( dben este caso), recibirá una nueva dirección IP. Su otro contenedor (reiniciado o no) retendrá los valores ENV desde el momento en que lo lanzó y es inútil.
GermanDZ
1
fyi parece que se acerca una solución, / etc / hosts se actualizará cuando se reinicie un contenedor vinculado: github.com/docker/docker/issues/6350
jamshid
1
Este problema parece estar solucionado y el método propuesto me está funcionando.
Jens Piegsa
1
Esta es la respuesta más correcta aquí. El único problema es que el vínculo es unidireccional y agrega una dependencia entre contenedores: no puede vincular dos contenedores y no puede detener el contenedor vinculado y luego iniciarlo nuevamente (con nuevas opciones o algo así). En cualquiera de esos casos, utilice redes (y el net-aliasnombre del contenedor o).
Ivan Anishchuk
1

con el enfoque OpenSVC, puede solucionarlo mediante:

  • utilizar un servicio con su propia dirección IP / nombre dns (al que se conectarán sus usuarios finales)
  • dígale a Docker que exponga los puertos a esta dirección IP específica (opción de Docker "--ip")
  • configure sus aplicaciones para conectarse a la dirección IP del servicio

Cada vez que reemplaza un contenedor, está seguro de que se conectará a la dirección IP correcta.

Tutorial aquí => Docker Multi Containers con OpenSVC

no se pierda la parte de "orquestación compleja" al final de tuto, que puede ayudarlo a iniciar / detener contenedores en el orden correcto (1 subconjunto de postgresql + 1 subconjunto de aplicaciones web + 1 subconjunto de nginx)

el principal inconveniente es que expone los puertos de la aplicación web y PostgreSQL a la dirección pública y, en realidad, solo el puerto nginx tcp debe exponerse en público.

usuario3757286
fuente
1

También puede probar el método ambassador de tener un contenedor intermediario solo para mantener el enlace intacto ... (consulte https://docs.docker.com/articles/ambassador_pattern_linking/ ) para obtener más información

Gekkie
fuente
Ambassador es un patrón agradable, pero tiene el mismo problema: la dirección IP no se actualizará necesariamente al reiniciar. Sin embargo, son excelentes para la conectividad entre hosts. Bueno, tal vez con la nueva versión de Docker que tampoco sea necesaria.
Ivan Anishchuk
@IvanAnishchuk es cierto, pero en el momento en que se hizo el comentario, este era el camino a seguir ... (hace +2 años;))
Gekkie
0

Puede vincular los puertos de conexión de sus imágenes a puertos fijos en el host y configurar los servicios para usarlos en su lugar.

Esto también tiene sus inconvenientes, pero podría funcionar en su caso.

ivant
fuente
La vinculación de los puertos de localhost tiene sus inconvenientes. La nueva red de Docker lo hace obsoleto.
Ivan Anishchuk
0

Otra alternativa es utilizar la --net container:$CONTAINER_IDopción.

Paso 1: cree contenedores de "red"

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

Paso 2: inyectar servicios en contenedores de "red"

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

Mientras no toque los contenedores de "red", las direcciones IP de sus enlaces no deberían cambiar.

user1202136
fuente
La red puente creada por el usuario con un nombre significativo es probablemente una mejor opción. No requiere crear contenedores solo para usar sus redes.
Ivan Anishchuk
0

En este caso, lo que necesita es un alias de ámbito de red . Es una característica bastante nueva, que se puede utilizar para "publicar" un contenedor que proporciona un servicio para toda la red, a diferencia de los alias de enlaces accesibles solo desde un contenedor.

No agrega ningún tipo de dependencia entre los contenedores: pueden comunicarse siempre que ambos estén en ejecución, independientemente de los reinicios y el orden de reemplazo y lanzamiento. Utiliza DNS internamente, creo, en lugar de / etc / hosts

Úselo así: docker run --net=some_user_definied_nw --net-alias postgres ... y puede conectarse usando ese alias desde cualquier contenedor en la misma red.

No funciona en la red predeterminada, desafortunadamente, debe crear uno con docker network create <network>y luego usarlo con --net=<network>para cada contenedor ( compose también lo admite ).

Además de que el contenedor está inactivo y, por lo tanto, inalcanzable por alias, varios contenedores también pueden compartir un alias, en cuyo caso no se garantiza que se resuelva en el correcto. Pero en algún caso, probablemente eso puede ayudar con una actualización perfecta.

No está todo muy bien documentado hasta el momento, es difícil de entender con solo leer la página de manual.

Ivan Anishchuk
fuente