Introdução ao Nomad: orquestração de containers sem a complexidade do Kubernetes

1. O que é o Nomad e por que ele existe?

O Nomad é um orquestrador de workloads desenvolvido pela HashiCorp, lançado em 2015 como resposta à crescente necessidade de uma ferramenta de orquestração que fosse ao mesmo tempo poderosa e simples. Enquanto o Kubernetes se consolidava como o padrão da indústria, sua complexidade operacional tornava-se um obstáculo para equipes menores e workloads mais simples.

A principal diferença entre Nomad e Kubernetes está na filosofia de design. O Kubernetes nasceu como um sistema completo de orquestração com dezenas de componentes internos (etcd, kube-apiserver, kube-controller-manager, kube-scheduler, kubelet, kube-proxy, entre outros). O Nomad, por outro lado, é um único binário que pode operar como servidor, cliente ou ambos, reduzindo drasticamente a complexidade operacional.

Casos de uso ideais para o Nomad incluem:
- Workloads batch e processamento de dados
- Serviços stateful que exigem simplicidade de configuração
- Ambientes com recursos limitados que não justificam um cluster Kubernetes completo
- Equipes pequenas que precisam de orquestração sem uma equipe dedicada de infraestrutura

2. Arquitetura simplificada do Nomad

A arquitetura do Nomad é composta por três componentes principais:

Servidores (Servers): Responsáveis pelo gerenciamento do cluster, tomada de decisões de scheduling e manutenção do estado. Utilizam o protocolo Raft para consenso e gossip para descoberta de membros.

Clientes (Clients): Executam as workloads alocadas pelos servidores. Cada cliente possui agentes que gerenciam os drivers (Docker, exec, raw, etc.) e reportam o estado das alocações.

Scheduler: Responsável por distribuir as workloads entre os clientes baseado em recursos disponíveis, restrições e afinidades.

O ciclo de vida de um job no Nomad segue este fluxo:
1. Um operador submete um arquivo HCL para o servidor
2. O servidor avalia o job e cria alocações
3. As alocações são despachadas para clientes apropriados
4. Os clientes executam as tasks dentro das alocações
5. O servidor monitora continuamente o estado das alocações

Diferentemente do Kubernetes que depende do etcd para armazenamento de estado, o Nomad utiliza seu próprio protocolo de gossip e Raft integrados, eliminando a necessidade de um datastore externo.

3. Instalação e configuração inicial de um cluster Nomad

A instalação do Nomad é notavelmente simples. Primeiro, baixe o binário único:

# No Linux
wget https://releases.hashicorp.com/nomad/1.7.6/nomad_1.7.6_linux_amd64.zip
unzip nomad_1.7.6_linux_amd64.zip
sudo mv nomad /usr/local/bin/

Configuração do servidor (/etc/nomad.d/server.hcl):

server {
  enabled = true
  bootstrap_expect = 1
  data_dir = "/opt/nomad/data"
}

datacenter = "dc1"
region = "global"

Configuração do cliente (/etc/nomad.d/client.hcl):

client {
  enabled = true
  servers = ["127.0.0.1:4647"]
  data_dir = "/opt/nomad/data"
}

plugin "docker" {
  config {
    allow_privileged = false
  }
}

Inicie o servidor e o cliente:

sudo nomad agent -config /etc/nomad.d/server.hcl
sudo nomad agent -config /etc/nomad.d/client.hcl

Comandos essenciais para verificar o cluster:

nomad server members
nomad node status
nomad status

4. Escrevendo e executando jobs no Nomad

Jobs no Nomad são escritos em HCL (HashiCorp Configuration Language), uma sintaxe declarativa mais limpa que o YAML do Kubernetes. A estrutura básica inclui:

  • job: Define a workload completa
  • group: Agrupa tasks que devem co-localizar
  • task: Unidade atômica de trabalho (um container, um script, etc.)
  • resources: CPU, memória, disco e rede necessários

Exemplo prático: deploy de um container Nginx:

job "nginx" {
  datacenters = ["dc1"]

  group "web" {
    count = 2

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

    service {
      name = "nginx"
      port = "http"

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

    task "nginx" {
      driver = "docker"

      config {
        image = "nginx:latest"
        ports = ["http"]
      }

      resources {
        cpu    = 500
        memory = 256
      }
    }
  }
}

Execute o job:

nomad job run nginx.hcl
nomad job status nginx
nomad alloc status <alloc-id>

Comparado ao YAML do Kubernetes, o HCL do Nomad é significativamente mais conciso. Um deployment equivalente no Kubernetes exigiria pelo menos 50 linhas de YAML com múltiplos objetos (Deployment, Service, ConfigMap).

5. Gerenciamento de estado, volumes e redes

O Nomad suporta volumes de várias formas:

Host volumes: Montam diretórios do host diretamente no container:

volume "data" {
  type = "host"
  source = "/opt/data"
}

task "app" {
  volume_mount {
    volume = "data"
    destination = "/var/lib/data"
  }
}

CSI volumes: Para sistemas de armazenamento externo como AWS EBS ou NFS.

Para redes, o Nomad oferece:
- Modo bridge: Rede interna com NAT (padrão)
- Modo host: Usa diretamente a rede do host
- CNI plugins: Para integração com soluções de rede avançadas

Variáveis de ambiente e secrets:

task "app" {
  env {
    DATABASE_URL = "postgres://localhost:5432/mydb"
  }

  template {
    data = <<EOF
API_KEY={{ with secret "secret/data/api" }}{{ .Data.data.key }}{{ end }}
EOF
    destination = "secrets/api.env"
    env = true
  }
}

O Nomad integra-se nativamente com o Vault da HashiCorp para gerenciamento de segredos, eliminando a necessidade de ferramentas externas como External Secrets Operator.

6. Observabilidade e operações do dia a dia

O Nomad oferece ferramentas nativas de observabilidade:

# Visualizar logs de uma alocação
nomad alloc logs <alloc-id> <task-name>

# Status detalhado de uma alocação
nomad alloc status <alloc-id>

# Estatísticas de recursos
nomad alloc stats <alloc-id>

Para métricas do cluster, o Nomad expõe endpoints no formato Prometheus:

# Habilitar métricas no servidor
server {
  telemetry {
    prometheus_metrics = true
  }
}

Atualizações rolling e canary deployments:

job "my-service" {
  group "api" {
    update {
      max_parallel = 1
      health_check = "checks"
      min_healthy_time = "10s"
      auto_revert = true
      canary = 1
    }

    task "api" {
      # ...
    }
  }
}

O bloco update permite configurar deploys graduais com verificações de saúde automáticas e reversão em caso de falha.

7. Comparação prática: Nomad vs. Kubernetes para cenários reais

Característica Nomad Kubernetes
Complexidade de instalação Um binário único Múltiplos componentes
Curva de aprendizado Baixa (dias) Alta (semanas/meses)
Consumo de recursos ~50MB por nó ~500MB+ por nó
Ecossistema Focado, integrado com HashiCorp Gigantesco, milhares de extensões
Service mesh Consul Connect Istio, Linkerd, Consul
Workloads batch Suporte nativo excelente Requer Jobs/CronJobs
Stateful applications Simples com host volumes Complexo com StatefulSets
Escalabilidade horizontal Excelente Excelente

Quando escolher Nomad:
- Equipes pequenas (2-5 pessoas) sem time dedicado de infraestrutura
- Workloads batch e processamento de dados
- Ambientes com recursos limitados (edge computing, IoT)
- Serviços stateful simples que não exigem operadores complexos

Quando Kubernetes é melhor:
- Ecossistema maduro com milhares de extensões disponíveis
- Service mesh avançado (Istio, Linkerd)
- Operadores personalizados para aplicações complexas
- Grandes organizações com equipes dedicadas de plataforma

8. Próximos passos e integrações com o ecossistema

O Nomad faz parte do ecossistema HashiCorp e integra-se nativamente com:

Consul: Service discovery e mesh de serviços:

service {
  name = "web"
  port = 80
  connect {
    sidecar_service {}
  }
}

Vault: Gerenciamento de segredos com injeção automática:

task "app" {
  vault {
    policies = ["app-policy"]
  }
}

Migrando workloads simples do Kubernetes para o Nomad:

Um deployment simples do Kubernetes:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

Torna-se no Nomad:

job "nginx" {
  datacenters = ["dc1"]
  group "web" {
    count = 2
    task "nginx" {
      driver = "docker"
      config {
        image = "nginx:latest"
      }
      resources {
        cpu = 500
        memory = 256
      }
    }
  }
}

A migração é direta para workloads simples, eliminando a complexidade de Services, Ingresses, ConfigMaps e Secrets que seriam necessários no Kubernetes.

Referências