Fecha: 2019-05-16 Tiempo de lectura: 6 minutos Categoría: Operaciones Tags: docker / docker-compose / swarm / docker-app
Casi siempre he utilizado docker-compose en mi local, y eso me ayudó mucho cuando empecé a usar Docker Swarm. El fichero docker-compose.yml varía un poco en cada entorno y cada vez que se modifica se degrada respecto al original, por no mencionar el problema de mantener actualizadas las copias.
Luego vienen los destrozos, con compañeros que hacen desaparecer pedazos de fichero, especialmente la lista completa de variables de entorno necesarias. Por eso es necesario mantener un docker-compose.yml único como si de una plantilla se tratara, y proveer alguna forma de sobreescribir los parámetros propios de cada entorno.
Vamos a suponer para este artículo que tenemos un webservice en un contenedor. Por brevedad voy a utilizar un servidor que responda solamente con un texto parametrizable.
gerard@atlantis:~/workspace/dockerapp$ cat docker-compose.yml
version: '3.2'
services:
hello:
image: hashicorp/http-echo
command: ["-text", "hello world"]
ports:
- 5678:5678
gerard@atlantis:~/workspace/dockerapp$
Lo levantamos y comprobamos que devuelve lo esperado:
gerard@atlantis:~/workspace/dockerapp$ docker-compose up -d
Creating network "dockerapp_default" with the default driver
Creating dockerapp_hello_1 ... done
gerard@atlantis:~/workspace/dockerapp$
gerard@atlantis:~/workspace/dockerapp$ curl http://localhost:5678
hello world
gerard@atlantis:~/workspace/dockerapp$
Ahora empezad a pensar en varias copias del fichero, con modificaciones locales según el servidor o el entorno… En fin, mantenerlo o versionarlo es una auténtica pesadilla.
Para ello, los mismos desarrolladores de Docker han pensado en una forma de parametrizar estos ficheros y, aunque todavía está muy verde, es un paso en la dirección correcta. Las llaman docker apps, y esta es su documentación.
Vamos a empezar “instalando” el binario docker-app, que no es más que descargarlo de https://github.com/docker/app/releases y ponerlo en el path del usuario que lo necesite. Personalmente, yo lo he puesto en /usr/local/bin.
Una docker app no es otra cosa que la unión de 3 partes:
docker-compose.yml, posiblemente con variables declaradasEstas partes se pueden distribuir como 3 ficheros dentro de una carpeta, o como un solo fichero. Como es fácil de juntarlos y partirlos a posteriori, vamos a empezar con uno solo. Esto nos simplifica las salidas, y no perdemos legibilidad por ser un ejemplo pequeño.
gerard@atlantis:~/workspace/dockerapp$ docker-app init --single-file myapp
gerard@atlantis:~/workspace/dockerapp$
Esto nos genera un fichero con el nombre indicado y la extensión .dockerapp.
gerard@atlantis:~/workspace/dockerapp$ ls
docker-compose.yml myapp.dockerapp
gerard@atlantis:~/workspace/dockerapp$
Este fichero está lleno de comentarios explicativos, pero creo personalmente que no hacen ninguna falta, así que no los mostraré. Veamos las 3 partes indicadas:
gerard@atlantis:~/workspace/dockerapp$ cat myapp.dockerapp | grep -v ^#
version: 0.1.0
name: myapp
description:
maintainers:
- name: gerard
email:
---
version: '3.2'
services:
hello:
image: hashicorp/http-echo
command: ["-text", "hello world"]
ports:
- 5678:5678
---
{}
gerard@atlantis:~/workspace/dockerapp$
Es un buen momento para eliminar el fichero original, para mantener el ejemplo mínimo.
gerard@atlantis:~/workspace/dockerapp$ rm docker-compose.yml
gerard@atlantis:~/workspace/dockerapp$
gerard@atlantis:~/workspace/dockerapp$ ls
myapp.dockerapp
gerard@atlantis:~/workspace/dockerapp$
La idea es que un fichero tipo docker-compose.yml se renderiza a demanda:
gerard@atlantis:~/workspace/dockerapp$ docker-app render
version: "3.2"
services:
hello:
command:
- -text
- hello world
image: hashicorp/http-echo
ports:
- mode: ingress
target: 5678
published: 5678
protocol: tcp
gerard@atlantis:~/workspace/dockerapp$
Hasta aquí, no ha cambiado nada. Es el momento de sacarle provecho a la complejidad añadida.
Es normal que los entornos o servidores apliquen pequeños cambios en este fichero, y para ello, docker-app introduce las variables. Se trata de declarar como variables lo que pueda cambiar, darles valores por defecto, y luego ya las sobreescribiremos si nos hace falta.
Por la sencillez del ejemplo, poco podemos cambiar; como ejemplo podemos usar el texto devuelto o el puerto en el que vamos a mapear el servicio en el servidor. Se trata de reemplazar los valores modificables de la segunda sección de la docker app por variables de la forma ${variable}, dándole valores por defecto en la tercera sección de la docker app.
gerard@atlantis:~/workspace/dockerapp$ cat myapp.dockerapp
version: 0.1.0
name: myapp
description:
maintainers:
- name: gerard
email:
---
version: '3.2'
services:
hello:
image: hashicorp/http-echo
command: ["-text", "${text}"]
ports:
- ${port}:5678
---
text: hello world
port: 5678
gerard@atlantis:~/workspace/dockerapp$
Si renderizamos la aplicación, no vamos a ver cambio ninguno:
gerard@atlantis:~/workspace/dockerapp$ docker-app render
version: "3.2"
services:
hello:
command:
- -text
- hello world
image: hashicorp/http-echo
ports:
- mode: ingress
target: 5678
published: 5678
protocol: tcp
gerard@atlantis:~/workspace/dockerapp$
Si necesitamos modificar alguna de las variables, docker-app nos ofrece dos formas:
La primera es la más fácil, necesitando solamente indicar el flag --set para indicar el cambio. Veamos la sobreescritura del puerto, pero dejando el texto por defecto:
gerard@atlantis:~/workspace/dockerapp$ docker-app render --set port=8080
version: "3.2"
services:
hello:
command:
- -text
- hello world
image: hashicorp/http-echo
ports:
- mode: ingress
target: 5678
published: 8080
protocol: tcp
gerard@atlantis:~/workspace/dockerapp$
Esta opción está bien para un caso aislado o prueba, pero si queremos versionar los cambios, no es lo recomendable. Para ello se nos ofrece la otra opción, que es creando un fichero de cambios:
gerard@atlantis:~/workspace/dockerapp$ cat test.yml
text: lorem ipsum
gerard@atlantis:~/workspace/dockerapp$
gerard@atlantis:~/workspace/dockerapp$ docker-app render -f test.yml
version: "3.2"
services:
hello:
command:
- -text
- lorem ipsum
image: hashicorp/http-echo
ports:
- mode: ingress
target: 5678
published: 5678
protocol: tcp
gerard@atlantis:~/workspace/dockerapp$
Vamos a suponer que tenemos 3 servidores, con las excepciones que siguen:
Así pues, vamos a organizar el repositorio en donde guardamos las configuraciones con un fichero plantilla, y 3 ficheros especificando las diferencias de cada instancia, que quedan así:
gerard@atlantis:~/workspace/dockerapp$ cat dev.yml
text: lorem ipsum (this is development)
gerard@atlantis:~/workspace/dockerapp$
gerard@atlantis:~/workspace/dockerapp$ cat prod1.yml
port: 5679
gerard@atlantis:~/workspace/dockerapp$
gerard@atlantis:~/workspace/dockerapp$ cat prod2.yml
gerard@atlantis:~/workspace/dockerapp$
Vemos claramente que según el fichero usado se cumple con las necesidades específicas, pero manteniendo la plantilla unificada.
gerard@atlantis:~/workspace/dockerapp$ docker-app render -f dev.yml
version: "3.2"
services:
hello:
command:
- -text
- lorem ipsum (this is development)
image: hashicorp/http-echo
ports:
- mode: ingress
target: 5678
published: 5678
protocol: tcp
gerard@atlantis:~/workspace/dockerapp$
gerard@atlantis:~/workspace/dockerapp$ docker-app render -f prod1.yml
version: "3.2"
services:
hello:
command:
- -text
- hello world
image: hashicorp/http-echo
ports:
- mode: ingress
target: 5678
published: 5679
protocol: tcp
gerard@atlantis:~/workspace/dockerapp$
gerard@atlantis:~/workspace/dockerapp$ docker-app render -f prod2.yml
version: "3.2"
services:
hello:
command:
- -text
- hello world
image: hashicorp/http-echo
ports:
- mode: ingress
target: 5678
published: 5678
protocol: tcp
gerard@atlantis:~/workspace/dockerapp$
Solo nos queda lanzar los comandos que los aplican en cada servidor, que a su vez, tendrá un clon del repositorio:
gerard@serverA:~/workspace/dockerapp$ docker-app render -f dev.yml | docker-compose -f - up -d
Creating network "dockerapp_default" with the default driver
Creating dockerapp_hello_1 ... done
gerard@serverA:~/workspace/dockerapp$
gerard@serverA:~/workspace/dockerapp$ curl http://localhost:5678
lorem ipsum (this is development)
gerard@serverA:~/workspace/dockerapp$
gerard@serverB:~/workspace/dockerapp$ docker-app render -f prod1.yml | docker-compose -f - up -d
Creating network "dockerapp_default" with the default driver
Creating dockerapp_hello_1 ... done
gerard@serverB:~/workspace/dockerapp$
gerard@serverB:~/workspace/dockerapp$ curl http://localhost:5679
hello world
gerard@serverB:~/workspace/dockerapp$
gerard@serverC:~/workspace/dockerapp$ docker-app render -f prod2.yml | docker-compose -f - up -d
Creating network "dockerapp_default" with the default driver
Creating dockerapp_hello_1 ... done
gerard@serverC:~/workspace/dockerapp$
gerard@serverC:~/workspace/dockerapp$ curl http://localhost:5678
hello world
gerard@serverC:~/workspace/dockerapp$
Y con esto mantenemos un solo repositorio, una sola plantilla, y los cambios de forma centralizada y controlada.