HashiCorp Nomad es un orquestador de cargas de trabajo: tú describes en un archivo job qué quieres ejecutar, cuántas copias y con qué recursos, y Nomad decide en qué nodos colocarlas y las mantiene en marcha. Es más sencillo de operar que Kubernetes (un único binario) y, además de contenedores Docker, puede ejecutar binarios nativos. En este tutorial montarás en Ubuntu 24.04 un clúster Nomad con un servidor y dos clientes Docker, desplegarás un servicio web con health checks y lo actualizarás de forma progresiva.

Requisitos previos

Para seguir esta guía necesitas:

  • Tres servidores con Ubuntu 24.04 LTS, por ejemplo tres VPS de CubePath, con al menos 1 GB de RAM el servidor y 2 GB cada cliente.
  • Un usuario no root con privilegios sudo en cada uno.
  • Una red privada entre los tres. En los ejemplos se usan estas direcciones, sustitúyelas por las tuyas:
NombreRolIP privada
nomad-serverServidor10.0.1.10
nomad-client-01Cliente10.0.1.21
nomad-client-02Cliente10.0.1.22

Los servidores Nomad guardan el estado del clúster y toman las decisiones de planificación; los clientes ejecutan las tareas. Esta guía usa un solo servidor para simplificar. En producción se usan tres servidores (con bootstrap_expect = 3) para que el clúster siga funcionando si cae uno.

Paso 1: Instalar Nomad

Ejecuta este paso en los tres nodos. Añade el repositorio oficial de HashiCorp con su clave en /etc/apt/keyrings:

sudo apt update
sudo apt install -y wget gpg lsb-release
sudo install -m 0755 -d /etc/apt/keyrings
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/hashicorp-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list

Instala Nomad:

sudo apt update
sudo apt install -y nomad
nomad version
Nomad v1.x.x
BuildDate ...
Revision ...

El paquete instala la configuración en /etc/nomad.d/nomad.hcl, el directorio de datos /opt/nomad/data y la unidad nomad.service. El agente se ejecuta como root porque los clientes necesitan gestionar contenedores, cgroups y redes.

Paso 2: Instalar Docker en los clientes

El driver docker de Nomad necesita Docker Engine en cada nodo cliente. En nomad-client-01 y nomad-client-02, instala el paquete de Ubuntu (si prefieres el repositorio oficial de Docker, el resultado para Nomad es el mismo):

sudo apt install -y docker.io
sudo systemctl enable --now docker

Comprueba que funciona:

sudo docker run --rm hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.

Paso 3: Generar la clave de cifrado gossip

Los servidores Nomad se comunican entre sí con un protocolo gossip que conviene cifrar. Genera una clave en nomad-server:

nomad operator gossip keyring generate
cg8StVXbQJ0gPvMd9o7yrg==

Guarda el valor, lo usarás en la configuración del servidor. Con varios servidores, todos deben tener la misma clave.

Paso 4: Configurar el servidor

En nomad-server, abre el archivo de configuración:

sudo nano /etc/nomad.d/nomad.hcl

Sustituye su contenido por el siguiente, con tu IP privada y la clave del paso anterior:

datacenter = "dc1"
data_dir   = "/opt/nomad/data"
bind_addr  = "0.0.0.0"

advertise {
  http = "10.0.1.10"
  rpc  = "10.0.1.10"
  serf = "10.0.1.10"
}

server {
  enabled          = true
  bootstrap_expect = 1
  encrypt          = "cg8StVXbQJ0gPvMd9o7yrg=="
}

client {
  enabled = false
}

bind_addr = "0.0.0.0" hace que Nomad escuche en todas las interfaces, y el bloque advertise indica qué IP deben usar el resto de nodos para llegar a este. La API HTTP (puerto 4646) no tiene autenticación mientras no actives las ACLs, así que en el paso 6 la limitarás con el firewall.

Valida la configuración, arranca el servicio y comprueba que el servidor se ha elegido líder:

sudo nomad config validate /etc/nomad.d/
sudo systemctl enable --now nomad
nomad server members
Name                 Address    Port  Status  Leader  Raft Version  Build  Datacenter  Region
nomad-server.global  10.0.1.10  4648  alive   true    3             1.x.x  dc1         global

Paso 5: Configurar los clientes

En cada cliente, abre el mismo archivo:

sudo nano /etc/nomad.d/nomad.hcl

Sustituye su contenido por el siguiente. Cambia las IP del bloque advertise por la del cliente en el que estés:

datacenter = "dc1"
data_dir   = "/opt/nomad/data"
bind_addr  = "0.0.0.0"

advertise {
  http = "10.0.1.21"
  rpc  = "10.0.1.21"
  serf = "10.0.1.21"
}

client {
  enabled = true
  servers = ["10.0.1.10:4647"]

  meta {
    env = "production"
  }
}

plugin "docker" {
  config {
    allow_privileged = false
  }
}
  • servers indica a qué servidor debe registrarse el cliente, por el puerto RPC 4647.
  • meta añade etiquetas libres al nodo que después puedes usar en los jobs con constraint.
  • allow_privileged = false impide que un job lance contenedores privilegiados.

Arranca Nomad en los dos clientes:

sudo systemctl enable --now nomad

Desde cualquier nodo, comprueba que los clientes aparecen como ready y que el driver Docker está detectado:

nomad node status
ID        Node Pool  DC   Name             Class   Drain  Eligibility  Status
5b1e2c3a  default    dc1  nomad-client-02  <none>  false  eligible     ready
a7f09d41  default    dc1  nomad-client-01  <none>  false  eligible     ready
nomad node status -self -verbose | grep -i 'driver.docker '

Ejecútalo en un cliente: debe mostrar driver.docker = 1. Si no aparece, revisa que Docker está en marcha y consulta sudo journalctl -u nomad -e.

Paso 6: Proteger los puertos con UFW

Nomad usa el puerto 4646 para la API HTTP y la interfaz web, 4647 para RPC y 4648 para gossip entre servidores. Permite el tráfico solo desde la red privada en los tres nodos:

sudo ufw allow OpenSSH
sudo ufw allow from 10.0.1.0/24 to any port 4646:4648 proto tcp
sudo ufw allow from 10.0.1.0/24 to any port 4648 proto udp
sudo ufw enable

Paso 7: Desplegar tu primer job

Un job describe grupos de tareas (group) que Nomad coloca juntos en un mismo cliente. Crea el archivo del job en el servidor, en tu directorio personal:

nano ~/web.nomad.hcl
job "web" {
  datacenters = ["dc1"]
  type        = "service"

  group "web" {
    count = 2

    network {
      port "http" {
        to = 80
      }
    }

    update {
      max_parallel     = 1
      min_healthy_time = "10s"
      healthy_deadline = "2m"
      auto_revert      = true
    }

    service {
      name     = "web"
      port     = "http"
      provider = "nomad"

      check {
        type     = "http"
        path     = "/"
        interval = "10s"
        timeout  = "2s"
      }
    }

    task "nginx" {
      driver = "docker"

      config {
        image = "nginx:1.26-alpine"
        ports = ["http"]
      }

      resources {
        cpu    = 200
        memory = 128
      }
    }
  }
}

Qué define este job:

  • count = 2: dos instancias del grupo, que Nomad reparte entre los clientes disponibles.
  • port "http" { to = 80 }: Nomad asigna un puerto dinámico en el host y lo redirige al puerto 80 del contenedor.
  • service con provider = "nomad": registra cada instancia en el catálogo de servicios integrado de Nomad, con un health check HTTP. No necesitas Consul para esto.
  • update: en cada despliegue actualiza las instancias de una en una y vuelve a la versión anterior si la nueva no supera los health checks.
  • resources: CPU en MHz y memoria en MB reservados para la tarea.

Revisa qué haría Nomad sin aplicar nada:

nomad job plan ~/web.nomad.hcl
+ Job: "web"
+ Task Group: "web" (2 create)
  + Task: "nginx" (forces create)

Scheduler dry-run:
- All tasks successfully allocated.

Lanza el job:

nomad job run ~/web.nomad.hcl

Nomad muestra el progreso del despliegue y termina cuando las dos instancias están sanas. Consulta el estado del job:

nomad job status web
Allocations
ID        Node ID   Task Group  Version  Desired  Status   Created  Modified
3f0a91c2  a7f09d41  web         0        run      running  45s ago  30s ago
b82d7e55  5b1e2c3a  web         0        run      running  45s ago  31s ago

Cada fila es una allocation, es decir, una instancia del grupo colocada en un cliente concreto.

Paso 8: Descubrir el servicio y ver los logs

Consulta en qué direcciones y puertos está disponible el servicio:

nomad service info web
Job ID  Address          Tags  Node ID   Alloc ID
web     10.0.1.21:24561  []    a7f09d41  3f0a91c2
web     10.0.1.22:28734  []    5b1e2c3a  b82d7e55

Pruébalo con una petición HTTP a una de esas direcciones:

curl -I http://10.0.1.21:24561
HTTP/1.1 200 OK
Server: nginx/1.26.x

Otras aplicaciones del clúster pueden leer estas direcciones con plantillas en el bloque template de sus jobs (función nomadService), de modo que un proxy inverso se reconfigura solo cuando las instancias cambian.

Para ver los logs de una instancia, usa el ID de su allocation:

nomad alloc logs 3f0a91c2 nginx

Y si necesitas entrar en el contenedor:

nomad alloc exec -task nginx 3f0a91c2 /bin/sh

Paso 9: Escalar y actualizar el job

Para cambiar el número de instancias o la imagen, edita el archivo del job y vuelve a ejecutarlo. Cambia count = 2 por count = 3 y la imagen por nginx:1.27-alpine:

nano ~/web.nomad.hcl

Revisa el plan: verás una instancia nueva y las existentes marcadas para actualizarse.

nomad job plan ~/web.nomad.hcl
nomad job run ~/web.nomad.hcl

Gracias al bloque update, Nomad sustituye las instancias de una en una y espera a que cada una pase su health check antes de seguir. Consulta el despliegue:

nomad job status web

Si algo sale mal y auto_revert no lo ha resuelto por sí mismo, vuelve a la versión anterior del job. Consulta el historial y revierte a la versión 0:

nomad job history web
nomad job revert web 0

Para eliminar el job y detener todas sus instancias:

nomad job stop -purge web

Paso 10: Acceder a la interfaz web

La interfaz web de Nomad está en el puerto 4646 del servidor, que solo es accesible desde la red privada. Ábrela desde tu equipo con un túnel SSH:

ssh -L 4646:10.0.1.10:4646 your_user@your_server_ip

Con el túnel abierto, visita http://localhost:4646/ui. Verás los jobs, las allocations, los clientes y el uso de recursos de cada uno.

Solución de problemas

  • Un cliente no aparece en nomad node status: comprueba que llega al servidor con nc -zv 10.0.1.10 4647 y que la IP de servers es correcta. Revisa sudo journalctl -u nomad -e en el cliente.
  • Constraint "missing drivers" filtered 2 nodes: Docker no está instalado o no está en marcha en los clientes. Arráncalo con sudo systemctl start docker y reinicia Nomad.
  • Dimension "memory" exhausted: los clientes no tienen memoria libre para las instancias pedidas. Reduce memory en resources o añade clientes.
  • El despliegue no termina y se revierte: el health check falla. Mira el motivo con nomad alloc status <alloc-id> y los logs de la tarea con nomad alloc logs.

Conclusión

Tienes un clúster Nomad con un servidor y dos clientes Docker, y un servicio web desplegado con health checks, descubrimiento de servicios integrado y actualizaciones progresivas con vuelta atrás automática. Como siguientes pasos, activa las ACLs con nomad acl bootstrap antes de dar acceso a otras personas, amplía el clúster a tres servidores para tener alta disponibilidad y pon delante de tus servicios un proxy inverso que lea el catálogo con nomadService.