Instalación y Uso de HashiCorp Nomad

HashiCorp Nomad es un orquestador de cargas de trabajo flexible que puede ejecutar aplicaciones en contenedores Docker, binarios nativos y máquinas virtuales en un clúster multi-nodo, con soporte para despliegues en múltiples regiones y centros de datos. A diferencia de Kubernetes, Nomad tiene una curva de aprendizaje más gradual y puede orquestar cargas de trabajo no contenedorizadas, lo que lo hace ideal para migraciones progresivas. Esta guía cubre la instalación de un clúster Nomad, definición de jobs y uso práctico.

Requisitos Previos

  • Servidores Linux (Ubuntu 22.04/Debian 12 o CentOS 9/Rocky 9)
  • 3 nodos para clúster de producción (1+ servidores, 2+ clientes)
  • Docker instalado en nodos cliente (para el driver Docker)
  • Opcional: HashiCorp Consul para descubrimiento de servicios
  • Puertos 4646 (HTTP API), 4647 (RPC) y 4648 (Serf) entre nodos

Instalación de Nomad

# Añadir repositorio HashiCorp (Ubuntu/Debian)
wget -O- https://apt.releases.hashicorp.com/gpg | \
  gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
  https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
  tee /etc/apt/sources.list.d/hashicorp.list

apt-get update && apt-get install -y nomad

# CentOS/Rocky Linux
dnf config-manager --add-repo \
  https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
dnf install -y nomad

# Verificar la instalación
nomad version

# Crear directorios de datos y configuración
mkdir -p /opt/nomad/data /etc/nomad.d
chmod 700 /opt/nomad/data

Configuración del Clúster

Configura los nodos servidor (ejecutar en los nodos designados como servidores):

# Configuración del nodo servidor Nomad
cat > /etc/nomad.d/server.hcl << 'EOF'
# Identificador del datacenter
datacenter = "dc1"
region     = "global"

# Directorio de datos persistentes
data_dir = "/opt/nomad/data"

# Clave de encriptación para comunicaciones Gossip
encrypt = "cg8StVXbQJ0gPvMd9o7yrg=="  # Generar con: nomad operator keygen

# Logging
log_level = "INFO"
log_file  = "/var/log/nomad/nomad.log"

# Servidor del clúster
server {
    enabled          = true
    bootstrap_expect = 3  # Número de servidores para formar quórum
    
    # Tiempo de retención de jobs y allocations
    job_gc_threshold          = "4h"
    deployment_gc_threshold   = "1h"
    eval_gc_threshold         = "1h"
    node_gc_threshold         = "24h"
}

# Telemetría y métricas (Prometheus)
telemetry {
    prometheus_metrics         = true
    publish_allocation_metrics = true
    publish_node_metrics       = true
}
EOF

Configura los nodos cliente (donde se ejecutarán los jobs):

# Configuración del nodo cliente Nomad
cat > /etc/nomad.d/client.hcl << 'EOF'
datacenter = "dc1"
region     = "global"
data_dir   = "/opt/nomad/data"

encrypt = "cg8StVXbQJ0gPvMd9o7yrg=="

client {
    enabled = true
    
    # Servidores a los que conectarse
    servers = ["10.0.1.10:4647", "10.0.1.11:4647", "10.0.1.12:4647"]
    
    # Metadatos del nodo para selección de jobs
    meta {
        "env"  = "production"
        "role" = "worker"
    }
    
    # Recursos disponibles para Nomad (dejar algo para el SO)
    reserved {
        cpu            = 500    # MHz reservados para el SO
        memory         = 512    # MB reservados para el SO
        disk           = 1024   # MB reservados para el SO
    }
}

# Habilitar driver Docker
plugin "docker" {
    config {
        allow_privileged = false
        volumes {
            enabled = true
        }
    }
}
EOF

Crea el servicio systemd:

cat > /etc/systemd/system/nomad.service << 'EOF'
[Unit]
Description=HashiCorp Nomad - Workload Orchestrator
Documentation=https://www.nomadproject.io/docs
Wants=network-online.target
After=network-online.target

[Service]
ExecReload=/bin/kill -HUP $MAINPID
ExecStart=/usr/bin/nomad agent -config /etc/nomad.d
KillMode=process
KillSignal=SIGINT
LimitNOFILE=65536
LimitNPROC=infinity
Restart=on-failure
RestartSec=2
TasksMax=infinity

[Install]
WantedBy=multi-user.target
EOF

mkdir -p /var/log/nomad
systemctl daemon-reload
systemctl enable --now nomad

# Verificar el estado del clúster
nomad server members
nomad node status

Definición de Jobs

Un Job en Nomad es la unidad de despliegue. Define qué ejecutar, cuántas instancias y con qué recursos:

# Crear un job de ejemplo para una aplicación web
cat > /tmp/webapp.nomad.hcl << 'EOF'
job "webapp" {
    # Datacenter y región
    datacenters = ["dc1"]
    region      = "global"
    
    # Tipo de scheduler: service (long-running), batch, system
    type = "service"
    
    # Grupo de tareas
    group "web" {
        count = 3  # Número de instancias
        
        # Política de actualización progresiva
        update {
            max_parallel      = 1       # Actualizar una instancia a la vez
            min_healthy_time  = "30s"   # Tiempo mínimo sano antes de continuar
            healthy_deadline  = "3m"    # Tiempo máximo para que una instancia sea sana
            auto_revert       = true    # Revertir si el despliegue falla
        }
        
        # Reiniciar la tarea si falla
        restart {
            attempts = 3
            interval = "5m"
            delay    = "15s"
            mode     = "fail"
        }
        
        # Red del grupo
        network {
            port "http" {
                to = 8080  # Puerto dentro del contenedor
                # static = 8080  # Puerto fijo en el host (opcional)
            }
        }
        
        # Definición de la tarea
        task "server" {
            driver = "docker"
            
            config {
                image = "nginx:1.25-alpine"
                ports = ["http"]
            }
            
            # Recursos asignados a esta tarea
            resources {
                cpu    = 500   # MHz
                memory = 256   # MB
            }
            
            # Comprobación de salud
            service {
                name = "webapp"
                port = "http"
                tags = ["production", "web"]
                
                check {
                    type     = "http"
                    path     = "/health"
                    interval = "10s"
                    timeout  = "3s"
                }
            }
        }
    }
}
EOF

# Planificar el job (muestra el plan sin ejecutar)
nomad job plan /tmp/webapp.nomad.hcl

# Ejecutar el job
nomad job run /tmp/webapp.nomad.hcl

# Ver el estado del job
nomad job status webapp
nomad alloc status <alloc-id>

Driver Docker

# Job con configuración avanzada de Docker
cat > /tmp/redis.nomad.hcl << 'EOF'
job "redis" {
    datacenters = ["dc1"]
    type = "service"
    
    group "cache" {
        count = 1
        
        network {
            port "redis" { static = 6379 }
        }
        
        # Volumen persistente
        volume "redis-data" {
            type   = "host"
            source = "redis-data"
        }
        
        task "redis" {
            driver = "docker"
            
            config {
                image   = "redis:7-alpine"
                ports   = ["redis"]
                command = "redis-server"
                args    = ["--appendonly", "yes", "--maxmemory", "256mb"]
            }
            
            volume_mount {
                volume      = "redis-data"
                destination = "/data"
            }
            
            resources {
                cpu    = 500
                memory = 384
            }
        }
    }
}
EOF

# Primero crear el volumen host en el cliente
mkdir -p /opt/nomad/volumes/redis-data
# Añadir en client.hcl:
# host_volume "redis-data" {
#   path      = "/opt/nomad/volumes/redis-data"
#   read_only = false
# }

nomad job run /tmp/redis.nomad.hcl

Driver Exec (Aplicaciones Nativas)

# Job para ejecutar un binario directamente (sin contenedor)
cat > /tmp/api-server.nomad.hcl << 'EOF'
job "api-server" {
    datacenters = ["dc1"]
    type = "service"
    
    group "api" {
        count = 2
        
        network {
            port "api" { to = 3000 }
        }
        
        task "server" {
            driver = "exec"
            
            config {
                command = "/opt/api/bin/servidor"
                args    = ["--port", "${NOMAD_PORT_api}", "--env", "production"]
            }
            
            # Variables de entorno para la tarea
            env {
                DB_HOST = "10.0.3.10"
                DB_PORT = "5432"
                LOG_LEVEL = "info"
            }
            
            # Artefacto a descargar antes de ejecutar
            artifact {
                source = "https://releases.empresa.com/api/latest/servidor-linux-amd64"
                destination = "local/servidor"
                mode = "file"
            }
            
            resources {
                cpu    = 1000
                memory = 512
            }
        }
    }
}
EOF

Descubrimiento de Servicios con Consul

# Cuando Consul y Nomad están integrados, los servicios se registran automáticamente
# Verificar que Nomad puede comunicarse con Consul
nomad agent-info | grep consul

# Los servicios registrados por Nomad aparecen en Consul con el prefijo "nomad-"
consul catalog services | grep nomad

# Consultar los servicios de Nomad via DNS de Consul
dig @127.0.0.1 -p 8600 webapp.service.consul

Despliegue Multi-región

# Job configurado para múltiples regiones
cat > /tmp/global-app.nomad.hcl << 'EOF'
job "global-webapp" {
    # Múltiples regiones
    region      = "global"
    datacenters = ["dc1", "dc2"]
    
    # Configuración multi-región
    multiregion {
        strategy {
            max_parallel = 1
            on_failure   = "fail_all"
        }
        
        region "eu-west" {
            datacenters = ["dc1"]
            count       = 3
        }
        
        region "us-east" {
            datacenters = ["dc2"]
            count       = 2
        }
    }
    
    group "web" {
        task "server" {
            driver = "docker"
            config {
                image = "myapp:latest"
            }
            resources {
                cpu    = 500
                memory = 256
            }
        }
    }
}
EOF

Solución de Problemas

# Ver logs del agente Nomad
journalctl -u nomad -f

# Ver eventos del clúster
nomad operator debug -duration=2m -log-level=DEBUG

# Estado de un job fallido
nomad job status webapp
nomad alloc status <alloc-id>

# Logs de una allocation específica
nomad alloc logs <alloc-id>
nomad alloc logs -stderr <alloc-id>

# Acceder a una tarea en ejecución
nomad alloc exec <alloc-id> /bin/sh

# Ver el plan antes de ejecutar para verificar cambios
nomad job plan /tmp/webapp.nomad.hcl

# Forzar un reschedule de una allocation fallida
nomad alloc restart <alloc-id>

# Ver el estado del raft en servidores
nomad operator raft list-peers

Conclusión

HashiCorp Nomad ofrece una experiencia de orquestación simple pero potente, capaz de gestionar cargas de trabajo heterogéneas desde contenedores Docker hasta binarios nativos y trabajos batch en el mismo clúster. Su integración nativa con el ecosistema HashiCorp (Consul, Vault) y su menor complejidad operativa respecto a Kubernetes lo convierten en una excelente opción para equipos que necesitan orquestación sin la sobrecarga administrativa de Kubernetes.