En este tutorial vamos a conocer Nomad y ver cómo puede facilitarnos los despliegues y el escalado de aplicaciones.
Índice de contenidos
- 1. Introducción
- 2. Entorno
- 3. Qué hace y qué no hace
- 4. Instalación y arquitectura
- 5. Uso
- 6. Nomad vs Otro software
- 7. Conclusiones
- 8. Referencias
1. Introducción
Cuando comienzas a desarrollar aplicaciones complejas orientadas a servicios, o incluso con microservicios, el número de artefactos a desplegar se vuelve inmanejable.
Si además quieres tener un entorno de alta disponibilidad con varios nodos balanceados ejecutando tu aplicación esto es aún más tedioso.
Tanto si quieres desplegar contenedores de Docker, como aplicaciones Java directamente, o apps de otro tipo como Apache Spark; Nomad es lo que necesitas.
Nomad es una pequeña pieza de software que se instala en los servidores que formarán parte de tu clúster. Se compone de dos partes: una servidor (gestiona los despliegues) y otra cliente (aloja los depliegues).
Se recomienda tener 3 servidores y tantos clientes como se necesiten por cada zona.
Se declaran los trabajos, se planifican y muestran por pantalla los cambios que se aplicarán y se ejecuta el despliegue distribuido. Puedes ver el progreso en todo momento. Además te supervisarán los nodos y desplegará nuevas instancias en caso de caídas.
Permite el uso combinado de una nube pública (tipo AWS o Azure) y una entorno privado, distribuyendo las aplicaciones como si de uno solo se tratara.
2. Entorno
El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil MacBook Pro Retina 15′ (2.2 Ghz Intel Core I7, 16GB DDR3).
- Sistema Operativo: Mac OS X El Capitan 10.11.6
- Entorno de desarrollo: Atom
- Sistema operativa de las VM: CentOS/7
- Nomad 0.5.0
- Consul 0.7.0
- Vagrant
- Ansible
- VirtualBox
- Nginx
3. Qué hace y qué no hace
Nomad es una utilidad de «HashiCorp» que se instala en servidores y puede tener dos roles. Actuar como servidor para gestionar y supervisar los despliegues; o actuar como cliente para alojar estos despliegues.
Nomad no pretende ser una solución completa para orquestar una red de contenedores de Docker. Al menos no por sí solo.
Nomad no es un servidor de descubrimiento para resolver las ubicaciones de las aplicaciones desplegadas, pero para ello se integra muy bien con «Consul», que es una herramienta también de «HashiCorp».
Nomad no es un balanceador de carga ni un proxy para redirigir las peticiones a los diferentes nodos que tienen una aplicación. Aunque para ello se puede utilizar Nginx, Fabio, Traefik, HAProxy, etc. y automatizar su configuración desde los datos de consul utilizando consul-template.
Nomad no dispone de una gestión de secretos pero para ello se integra muy bien con «Vault», que es una herramienta también de «HashiCorp».
4. Instalación y arquitectura
Vamos a automatizar la instalación de nomad utilizando Vagrant (creación de máquinas virtuales) y Ansible (instalación y configuración de aplicaciones).
Si no conocéis estas herramientas podéis ver un tutorial aquí.
Podéis clonaros el siguiente repositorio para seguir el tutorial:
https://github.com/miyoda/nomad-consul-nginx
Como podréis ver en el fichero «inventory» vamos a crear 6 máquinas de las cuales 3 serán servidores y 3 clientes de nomad. Con ips desde 192.168.10.11 hasta 16.
[server] cluster1 ansible_host=192.168.10.11 ansible_ssh_host=192.168.10.11 consul_bootstrap=true cluster2 ansible_host=192.168.10.12 ansible_ssh_host=192.168.10.12 cluster3 ansible_host=192.168.10.13 ansible_ssh_host=192.168.10.13 [client] cluster4 ansible_host=192.168.10.14 ansible_ssh_host=192.168.10.14 cluster5 ansible_host=192.168.10.15 ansible_ssh_host=192.168.10.15 cluster6 ansible_host=192.168.10.16 ansible_ssh_host=192.168.10.16
Si ejecutamos desde línea de comando «vagrant up» se crearán automáticamente las máquinas tal cual se define en el fichero «Vagrantfile».
$ vagrant up Bringing machine 'cluster1' up with 'virtualbox' provider... Bringing machine 'cluster2' up with 'virtualbox' provider... Bringing machine 'cluster3' up with 'virtualbox' provider... Bringing machine 'cluster4' up with 'virtualbox' provider... Bringing machine 'cluster5' up with 'virtualbox' provider... Bringing machine 'cluster6' up with 'virtualbox' provider... ......
Ya tendremos las máquinas creadas en nuestro VirtualBox
Además se ejecutarán los siguientes roles de ansible (en función de la maquina) como se define en el fichero «nomad.yml»:
- hosts: client become: yes roles: - ansible-role-docker - ansible-role-java - hosts: all become: yes roles: - ansible-role-consul - ansible-role-nomad - hosts: server become: yes roles: - ansible-role-proxy
Los roles «ansible-role-docker» y «ansible-role-java» son para que los servidores que sean clientes nomad que ejecutarán las aplicaciones dispongan de un entorno de ejecución Docker y Java respectivamente.
Si, por ejemplo, un nodo no tiene Java instalado y das la orden de ejecutar un Job de Java, el nodo en cuestión no será seleccionado por Nomad para desplegar la aplicación por falta de drivers.
El role «ansible-role-consul» se encarga de la instalación de Consul en todos los nodos. Consul es un servicio de autodescubrimiento que nos será útil para que Nomad pueda descubrir al resto de nodos y para poder gestionar/almacenar dónde está desplegado cada aplicación/artefacto.
El role «ansible-role-nomad» se encarga de la instalación de Nomad en todos los nodos. Tres de ellos lo usaran como servidor y otros tres como cliente.
El role «ansible-role-proxy» se encarga de la instalación de nginx en los nodos servidores. Este role podría ser instalado en otro servidor independiente. La función de nginx aquí es servir de balanceador para las aplicaciones utilizando Consul para saber a que servidor redirigir cada petición en función de donde la haya desplegado Nomad. Configura también un tarea de consul-template para que la configuración de nginx cambie automáticamente si se despliega en nuevos nodos o desaparece alguno.
El flujo de comunicaciones y despliegue del sistema es, como se ve en la imagen, el siguiente:
- 1. Nomad despliega una aplicación en uno o varios de sus nodos.
- 2. Nomad registra en Consul los nodos que tienen aplicación desplegada.
- 3. Nginx se autoconfigura con los datos de Consul mediante el proceso de consul-template.
- 4. Las peticiones entrarán a «nginx» (cualquiera de los 3 nodos servidores que lo tienen desplegado).
- 5. Nginx redirige la petición a uno de los nodos de Nomad que tienen la aplicación solicitada en función de la u
Url de entrada.
5. Uso
Una vez finalice la creación de los servidores, lo cual puede llevar unos minutos, ya podemos conectarnos a ellos. Para conectarnos a una de las máquinas ejecutar «vagrant ssh cluster1» o «vagrant ssh cluster2», etc.
Consul
Podemos ejecutar comandos para ver el estado del servicio de Consul y sus nodos. Esto se puede ejecutar en cualquier nodo puesto que todos tienen Consul instalado.
$ consul members Node Address Status Type Build Protocol DC cluster1 192.168.10.11:8301 alive server 0.7.0 2 europe cluster2 192.168.10.12:8301 alive server 0.7.0 2 europe cluster3 192.168.10.13:8301 alive server 0.7.0 2 europe cluster4 192.168.10.14:8301 alive server 0.7.0 2 europe cluster5 192.168.10.15:8301 alive server 0.7.0 2 europe cluster6 192.168.10.16:8301 alive server 0.7.0 2 europe $ consul monitor ... $ consul info ...
Si todo ha ido bien se habrá desplegado Consul en todos los nodos, se habrá seleccionado un líder, y debería estar accesible su UI desde la url siguiente (de cualquiera de los servidores):
http://192.168.10.11:8500/ui/
Nomad
Podemos ejecutar comandos para ver el estado del servicio de Nomad y sus nodos. Esto se puede ejecutar en cualquier nodo puesto que todos tienen nomad instalado; y si no es de tipo servidor se conectará a uno para obtener la información necesaria.
$ nomad status No running jobs $ nomad server-members Name Address Port Status Leader Protocol Build Datacenter Region cluster1.global 192.168.10.11 4648 alive false 2 0.5.0 dc1 global cluster2.global 192.168.10.12 4648 alive true 2 0.5.0 dc1 global cluster3.global 192.168.10.13 4648 alive false 2 0.5.0 dc1 global $ nomad node-status ID DC Name Class Drain Status cf4a08d8 dc1 cluster6 false ready 9f1d65ca dc1 cluster5 false ready 11e9ba9d dc1 cluster4 false ready
Como vemos ahora mismo no tenemos ningún job lanzado, y en clúster tiene listos tres nodos servidores con uno de ellos asignado como líder, y otros tres nodos clientes.
Job de ejemplo
Una vez desplegado y verificada la configuración de Consul y Nomad es el momento de comenzar a lanzar ‘Jobs’!
Los jobs son las configuraciones que se le pasan a Nomad para que despliegue. Están formados por ‘Tasks’ que representan las aplicaciones/artefactos a desplegar.
Vamos a comenzar creando y ejecutando nuestro primer job de ejemplo con los siguientes comandos:
$ nomad init Example job file written to example.nomad $ nomad run example.nomad ==> Monitoring evaluation "26cfc69e" Evaluation triggered by job "example" Allocation "8ba85cef" created: node "171a583b", group "cache" Evaluation status changed: "pending" -> "complete" ==> Evaluation "26cfc69e" finished with status "complete"
Ahora podremos ver más información sobre el estado de este despliegue utilizando los siguientes comandos:
$ nomad status ID Type Priority Status example service 50 running $ nomad status example ID = example Name = example Submit Date = 09/27/17 16:14:43 UTC Type = service Priority = 50 Datacenters = dc1 Status = running Periodic = false Parameterized = false Summary Task Group Queued Starting Running Failed Complete Lost cache 0 0 1 0 0 0 Latest Deployment ID = 11c5cdc8 Status = successful Description = Deployment completed successfully Deployed Task Group Desired Placed Healthy Unhealthy cache 1 1 1 0 Allocations ID Node ID Task Group Version Desired Status Created At 8ba85cef 171a583b cache 0 run running 07/25/17 23:14:43 UTC
En la parte final del estado de una ‘task’ se puede ver sus ‘allocations’ que son las instancias de ejecución en un nodo concreto. en este caso en el nodo ‘171a583b’ el cual se puede identificar en el listado obtenido del comando ‘nomad node-status’. Puedes obtener más información sobre los logs de una ‘allocation’ concreta usando el comando:
$ nomad alloc-status 8ba85cef ID = 8ba85cef Eval ID = 61b0b423 Name = example.cache[0] Node ID = 171a583b Job ID = example Job Version = 0 Client Status = running Client Description = Desired Status = run Desired Description = Created At = 09/27/17 16:14:43 UTC Deployment ID = fa882a5b Deployment Health = healthy Task "redis" is "running" Task Resources CPU Memory Disk IOPS Addresses 2/500 6.3 MiB/256 MiB 300 MiB 0 db: 127.0.0.1:30329 Recent Events: Time Type Description 07/25/17 23:14:53 UTC Started Task started by client 07/25/17 23:14:43 UTC Driver Downloading image redis:3.2 07/25/17 23:14:43 UTC Task Setup Building Task Directory 07/25/17 23:14:43 UTC Received Task received by client
Job tipo Docker
En el directorio ‘jobs’ del repositorio tenemos entre otros un ejemplo de job de una app web sobre un docker llamado ‘hello-docker.nomad’.
Tenemos el directorio ‘/vagrant/jobs’ en todas las máquinas virtuales con el contenido del directorio ‘jobs’ del proyecto.
Vamos a ejecutar este job:
$ nomad run /vagrant/jobs/hello-docker.nomad ==> Monitoring evaluation "38debb73" Evaluation triggered by job "hello-docker" Allocation "42c9d7c6" created: node "11e9ba9d", group "hello-docker" Allocation "87e451bc" created: node "9f1d65ca", group "hello-docker" Allocation "d284bf40" created: node "cf4a08d8", group "hello-docker" Evaluation status changed: "pending" -> "complete" ==> Evaluation "38debb73" finished with status "complete"
Esta aplicación está configurada en ese fichero para tener dos instancias. Por tanto debería estar desplegada en dos de los tres nodos clientes y accesible por tanto en dos de las siguientes urls:
http://192.168.10.14/
http://192.168.10.15/
http://192.168.10.16/
$ nomad status hello-docker ID = hello-docker Name = hello-docker Type = service Priority = 50 Datacenters = dc1 Status = running Periodic = false Summary Task Group Queued Starting Running Failed Complete Lost web 0 0 2 0 0 0 Allocations ID Eval ID Node ID Task Group Desired Status Created At 42c9d7c6 38debb73 11e9ba9d web run running 09/26/17 13:21:45 UTC 87e451bc 38debb73 9f1d65ca web run running 09/26/17 13:21:45 UTC
Nginx como proxy balanceador nos redirigirá automáticamente a los dos nodos que la tengan desplegada accediendo a la siguiente url. La web nos dirá qué nodo es el que ha respondido y podéis observar que cada vez será uno de los dos donde está desplegado:
http://192.168.10.11/hello-docker
Nginx se ha configurado automáticamente con una tarea cron que ejecuta el proceso «consul-template». Éste, basándose en la configuración de Consul y las plantillas del directorio ‘/usr/bin/consul-template’, configura los ficheros de ‘/etc/nginx’ y reinicia el servicio de nginx.
Job tipo Java
Ahora vamos a ejecutar un job que desplegará una aplicación Java directamente en los nodos clientes (sin Docker). Para ello usamos el ejemplo ‘hello-java.nomad’.
$ nomad run /vagrant/jobs/hello-java.nomad ==> Monitoring evaluation "749505c3" Evaluation triggered by job "java" Allocation "d648fc4b" created: node "9f1d65ca", group "java" Evaluation status changed: "pending" -> "complete" ==> Evaluation "749505c3" finished with status "complete"
Esta aplicación debería estar desplegada en dos de los tres nodos clientes y accesible por tanto en dos de las siguientes urls:
http://192.168.10.14:8080/
http://192.168.10.15:8080/
http://192.168.10.16:8080/
$ nomad status hello-java ID = hello-java Name = hello-java Type = service Priority = 50 Datacenters = dc1 Status = running Periodic = false Summary Task Group Queued Starting Running Failed Complete Lost java 0 0 1 0 0 0 Allocations ID Eval ID Node ID Task Group Desired Status Created At d648fc4b 749505c3 9f1d65ca hello-java run running 09/26/17 13:38:05 UTC 78119df7 14af1ff8 11e9ba9d hello-java run running 09/26/17 13:42:50 UTC
Modificando un Job
Vamos a planificar el despliegue de una nueva versión de la aplicación Java. Para ello tenemos preparado otro fichero de job llamado ‘hello-java-v2.nomad’. Con el siguiente comando vemos que acciones se ejecutarían para realizar el despliegue de este nuevo fichero de configuración:
$ nomad plan hello-java-v2.nomad +/- Job: "hello-java" Datacenters { Datacenters: "dc1" } +/- Task Group: "hello-java" (2 create/destroy update) +/- Task: "java" (forces create/destroy update) + Artifact { + GetterSource: "https://.../hello2.jar" + RelativeDest: "local/" } - Artifact { - GetterSource: "https://.../hello.jar" - RelativeDest: "local/" } Scheduler dry-run: - All tasks successfully allocated. Job Modify Index: 90 To submit the job with version verification run: nomad run -check-index 90 hello-java-v2.nomad When running the job with the check-index flag, the job will only be run if the server side version matches the job modify index returned. If the index has changed, another user has modified the job and the plan's results are potentially invalid.
Ahora podemos confirmar la ejecución de estos cambios que conllevan cambiar el .jar que se ejecutará y Nomad se encargará de parar las versiones antiguas y desplegar las nuevas.
$ nomad run -check-index 90 hello-java-v2.nomad ==> Monitoring evaluation "d77bfe4e" Evaluation triggered by job "java" Allocation "1914aac7" created: node "9f1d65ca", group "java" Allocation "d5b08cff" created: node "11e9ba9d", group "java" Evaluation status changed: "pending" -> "complete" ==> Evaluation "d77bfe4e" finished with status "complete"
Veamos cómo va el despliegue… ¡todo correcto!
$ nomad status hello-java ID = java Name = java Type = service Priority = 50 Datacenters = dc1 Status = running Periodic = false Summary Task Group Queued Starting Running Failed Complete Lost java 0 2 0 0 2 0 Allocations ID Eval ID Node ID Task Group Desired Status Created At 1914aac7 d77bfe4e 9f1d65ca java run running 09/26/17 13:48:58 UTC d5b08cff d77bfe4e 11e9ba9d java run running 09/26/17 13:48:58 UTC 78119df7 14af1ff8 11e9ba9d java stop complete 09/26/17 13:42:50 UTC d648fc4b 14af1ff8 9f1d65ca java stop complete 09/26/17 13:38:05 UTC
Pruebas de estabilidad del clúster
¡Ahora es el momento de probar lo estable que es el sistema!
Puedes probar a tirar la máquina virtual que actualmente es el líder de Consul y verás con el comando ‘consul monitor’ desde cualquiera de la otras máquinas que otro nodo adquiere la responsabilidad de líder.
Puedes probar a tirar al servidor Nomad que sea líder y verás con el comando ‘nomad server-members’ desde cualquier de las otras máquinas que otro nodo tipo server adquiere la responsabilidad de líder.
Puedes probar a tirar alguno de los nodos nomad cliente y observar que las task que tengan una allocation en este nodo crearán una nueva allocation en otro de los nodos clientes. Puedes verlo con el comando ‘nomad status hello-docker’ por ejemplo.
6. Nomad vs Otro software
No es fácil comparar Nomad con un software como Kubernetes. Kubernetes es una solución centrada más en los contendeores de Docker y que ofrece un sistema completo de orquestación con autodescubrimiento, balanceo de carga, gestión de volúmenes, networking, gestión de secretos, configuración, etc.
En cambio Nomad es de propósito más general soportando aplicaciones virtualizadas, standalone y con contenedores de Docker. Nomad tiene una arquitectura mucho mas simple con un simple binario y requiere de otras piezas externas para añadir funcionalidades de coordinación o almacenamiento.
Parecida es la comparación con otros sistemas como Docker Swarm.
Si lo comparamos con AWS ECS la principal diferencia es que ECS es para desplegar en los servidores de Amazon, mientras que Nomad es totalmente open source y permite el despliegue en cualquier nube privada o pública. Además ECS está centrado en contenedores de Docker mientras que Nomad tiene un propósito más general aunque soporta Docker también.
7. Conclusiones
Si necesitas una pieza de software que se encargue simplemente de gestionar y supervisar los despliegues distribuidos de tus aplicaciones o contenedores Docker; Nomad es una buena opción!
Además, combinado con otras herramientas de ‘HashiCorp DevOps Infrastructure’ como Consul, Terraform y Vault puedes tener una solución bastante completa de infraestructura.