HashiCorp Nomad: Quản lý Docker Container nhẹ nhàng hơn Kubernetes cho hệ thống vừa và nhỏ

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Kubernetes quá nặng, Swarm thì thiếu tính năng — Nomad ở đâu?

Cách đây 2 năm, khi mình bắt đầu migrate hệ thống e-commerce sang microservices, câu hỏi đầu tiên là: dùng gì để orchestrate container? Kubernetes thì nghe hoành tráng, nhưng sau 2 tuần setup thử trên 3 VPS, team mình kiệt sức với etcd cluster, control plane, RBAC, CRD… Chưa kể hồi đó mình gặp lỗi memory leak trong một container, mất đúng 2 ngày mới tìm ra. Debug trên K8s với logs từ chục pod chồng chéo nhau — nếu chưa từng trải thì khó tưởng tượng nó tệ thế nào.

Docker Swarm thì đơn giản hơn, nhưng tính năng scheduling lại khá thô. Không có health check linh hoạt, không có resource quota chi tiết, không support non-Docker workload.

Đó là lúc mình thử HashiCorp Nomad — và sau hơn 1 năm dùng thực tế, đây là công cụ mình muốn chia sẻ với các bạn đang vận hành hệ thống vừa và nhỏ.

Nomad là gì và tại sao nó phù hợp với team nhỏ?

Nomad là workload orchestrator của HashiCorp — không bó buộc vào Docker. Binary thường, Java app, shell script, hay container đều được; mỗi loại là một “task driver” khác nhau. Và nó có lợi thế rõ so với K8s ở mấy điểm:

  • Binary duy nhất: Cả server lẫn agent đều là 1 file binary ~100MB. Không cần etcd riêng, không cần nhiều component.
  • Setup 15 phút: Cụm 3 node Nomad có thể chạy trong vòng 15 phút, so với K8s tối thiểu vài giờ.
  • Tích hợp sẵn với Consul và Vault: Service discovery và secret management từ HashiCorp ecosystem — không cần cài thêm gì nếu bạn đã dùng stack này.
  • Job spec đơn giản: Dùng HCL (HashiCorp Configuration Language) — dễ đọc hơn YAML của K8s nhiều.

Khái niệm cốt lõi cần biết

Không cần đọc docs từ đầu. Hiểu 4 khái niệm này là có thể bắt đầu deploy được rồi:

  • Job: Đơn vị deployment lớn nhất. Một job có thể chứa nhiều task group.
  • Task Group: Nhóm các task chạy cùng nhau trên 1 node (giống Pod trong K8s).
  • Task: Đơn vị nhỏ nhất — 1 container Docker, 1 script, hay 1 binary.
  • Allocation: Khi Nomad scheduler quyết định chạy 1 task group trên 1 node cụ thể, đó gọi là allocation.

Cài đặt Nomad và chạy cluster đơn giản

Cài Nomad trên Ubuntu/Debian

# Thêm HashiCorp repo
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /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" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install nomad

# Kiểm tra version
nomad version

Chạy Nomad ở chế độ dev (test nhanh trên local)

# Chạy server + agent trên cùng 1 máy — chỉ dùng để test!
sudo nomad agent -dev

# Mở terminal khác, kiểm tra node
nomad node status

Cấu hình server production (3 node)

Tạo file /etc/nomad.d/server.hcl cho node server:

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

server {
  enabled          = true
  bootstrap_expect = 3  # Cần 3 node server để có quorum
}

client {
  enabled = false
}

Tạo file /etc/nomad.d/client.hcl cho node worker:

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

client {
  enabled = true
  servers = ["192.168.1.10:4647", "192.168.1.11:4647", "192.168.1.12:4647"]
}

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

Viết Job Spec và deploy Docker container

Đây là phần mình thấy Nomad làm tốt hơn hẳn Docker Swarm. Job spec rõ ràng, dễ đọc, dễ version control.

Job spec cơ bản — deploy Nginx

job "nginx-web" {
  datacenters = ["dc1"]
  type        = "service"  # Chạy liên tục, restart khi fail

  group "web" {
    count = 2  # 2 instances

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

    task "nginx" {
      driver = "docker"

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

      resources {
        cpu    = 200  # MHz
        memory = 128  # MB
      }

      # Health check tích hợp
      service {
        name = "nginx-web"
        port = "http"

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

Deploy job lên cluster:

# Kiểm tra plan trước khi apply (giống terraform plan)
nomad job plan nginx.nomad

# Deploy
nomad job run nginx.nomad

# Xem status
nomad job status nginx-web

# Xem logs của 1 allocation cụ thể
nomad alloc logs <alloc-id>

Tip thực chiến: dùng resource constraint để tránh OOM

Hồi gặp lỗi memory leak trong dự án e-commerce, mình học được bài học đắt giá: luôn đặt memory limit. Trên K8s, container bị kill nhưng log lại không rõ. Trên Nomad, khi container vượt memory limit, Nomad sẽ:

  • Ghi rõ lý do vào allocation log: OOM killed
  • Tự restart theo policy đã cấu hình
  • Cập nhật metric để bạn alert
resources {
  cpu        = 500
  memory     = 256
  memory_max = 512  # Cho phép burst nhưng không quá mức này
}

restart {
  attempts = 3
  interval = "5m"
  delay    = "15s"
  mode     = "fail"  # Fail sau 3 lần thay vì restart mãi
}

Deploy ứng dụng có biến môi trường và secret

task "api-server" {
  driver = "docker"

  config {
    image = "myapp/api:v1.2.0"
  }

  env {
    APP_ENV  = "production"
    LOG_LEVEL = "info"
  }

  # Secret từ Vault (nếu dùng HashiCorp Vault)
  template {
    data = <<EOT
{{ with secret "secret/myapp/db" }}
DB_PASSWORD={{ .Data.data.password }}
{{ end }}
EOT
    destination = "secrets/env.txt"
    env         = true
  }

  resources {
    cpu    = 300
    memory = 256
  }
}

Một số tips từ thực tế vận hành

1. Dùng “dead” job type cho batch task

Nếu bạn cần chạy cronjob hoặc script một lần, dùng type = "batch". Nomad sẽ chạy xong rồi cleanup, không giữ process.

job "db-backup" {
  type = "batch"
  # Nomad cũng có periodic block — giống cron
  periodic {
    crons = ["0 3 * * *"]  # Chạy lúc 3am mỗi ngày
  }
  # ... task config
}

2. Canary deployment có sẵn trong Nomad

update {
  canary           = 1       # Deploy 1 instance mới trước
  min_healthy_time = "30s"   # Phải healthy 30s mới tiếp tục
  healthy_deadline = "5m"
  auto_promote     = false   # Cần manual promote — an toàn hơn
  auto_revert      = true    # Tự rollback nếu fail
}

3. Validate JSON response từ Nomad API

Nomad có REST API khá đầy đủ. Khi debug hay viết automation script, mình hay dùng JSON Formatter trên ToolCraft để format output cho dễ đọc. Tool chạy local trên trình duyệt — không lo dữ liệu internal bị gửi đi đâu cả.

# Lấy thông tin job dưới dạng JSON
curl http://localhost:4646/v1/job/nginx-web | python3 -m json.tool

# Hoặc dùng nomad CLI
nomad job inspect nginx-web

4. Chuyển đổi giữa YAML và HCL khi cần

Nếu team bạn quen YAML (từ K8s), Nomad cũng accept JSON job spec. Mình dùng YAML ↔ JSON Converter của ToolCraft khi cần convert config qua lại — tiện hơn viết script tay nhiều.

5. Monitoring với Nomad UI

Nomad có sẵn Web UI tại http://<server-ip>:4646. Không fancy như K8s Dashboard nhưng đủ dùng: xem job status, allocation, logs, resource usage theo realtime.

So sánh nhanh: Nomad vs Docker Swarm vs Kubernetes

Tiêu chí Nomad Docker Swarm Kubernetes
Độ phức tạp setup Thấp Rất thấp Cao
Resource overhead ~100MB RAM ~50MB RAM >1GB RAM
Non-Docker workload ✅ Có ❌ Không ✅ Có
Canary deployment ✅ Built-in ❌ Manual ✅ Có
Hệ sinh thái HashiCorp Docker Inc. CNCF (rất lớn)
Phù hợp với Team 2-10 người Setup đơn giản Enterprise

Kết luận

Nomad không phải công cụ hoàn hảo. Cần ecosystem đồ sộ, cộng đồng lớn, hay đang build SaaS scale thật sự? K8s vẫn đúng chỗ hơn. Nhưng nếu bạn vận hành 3-20 server với team nhỏ, cần orchestration đủ dùng mà không muốn mất cả tuần maintain control plane — Nomad đáng thử nghiêm túc đấy.

Cá nhân mình sau 1 năm chuyển sang Nomad: deployment nhanh hơn, debug dễ hơn, và quan trọng nhất là cả team hiểu được hệ thống đang chạy cái gì — không ai sợ phải mở kubectl ra nữa.

Share: