当你需要一个能同时跑 Docker 容器、原生二进制和定时批处理任务的调度器时,Nomad 是值得认真考虑的选择。作为 HashiCorp 出品的轻量级集群调度器,Nomad 用一个二进制、极低的运维负担,把容器编排、服务调度与批处理统一到一套 Job 模型里。本文用实战带你从单节点上手到生产集群落地。
一、Nomad 是什么:和 Kubernetes 不一样的工作方式
Nomad 的定位是”调度器”而非”全家桶”。它只负责把任务放到合适的节点上运行,并维持你声明的期望状态;网络、存储、密钥等能力则交给 Consul、Vault 等”积木”按需拼装。这种克制让它的部署与心智负担都远小于 Kubernetes,却依然能覆盖绝大多数混合负载场景。
1.1 三个核心概念
- Job(作业):你提交的声明式描述,定义要跑什么、怎么跑、跑几份。
- Driver(驱动):决定任务以何种方式运行——Docker、exec、raw_exec、java、batch 等。
- Task Group(任务组):一组共享资源与网络的任务,是调度的最小单位。
# 一个最小 Job:用 Docker 跑 nginx
job "hello" {
datacenters = ["dc1"]
type = "service"
group "g" {
task "t" {
driver = "docker"
config {
image = "nginx:alpine"
}
}
}
}
二、快速上手:单节点 dev 模式
Nomad 是单个静态二进制,下载解压即可运行。dev 模式会在本地拉起一个单节点集群,数据不持久,非常适合本地验证 Job 写法。
# 下载并启动单节点 dev 集群(仅用于本地验证)
wget https://releases.hashicorp.com/nomad/1.8.4/nomad_1.8.4_linux_amd64.zip
unzip nomad_1.8.4_linux_amd64.zip
./nomad agent -dev -bind 0.0.0.0 -log-level INFO
另开一个终端,用 CLI 查看节点与作业状态:
nomad node status # 查看客户端节点
nomad job status # 查看已提交作业
nomad job run hello.nomad # 提交作业
三、第一个 Job:用 Docker 跑一个 Web 服务
下面这个 Job 会启动 2 个 nginx 实例,并把容器 80 端口映射到 Nomad 分配的 http 端口。通过 count 字段即可做水平扩容,Nomad 会自动把任务分散到可用节点。
job "web" {
datacenters = ["dc1"]
type = "service"
group "web" {
count = 2
network {
port "http" { to = 80 }
}
task "nginx" {
driver = "docker"
config {
image = "nginx:alpine"
ports = ["http"]
}
resources {
cpu = 256
memory = 256
}
}
}
}
提交后查看运行状态,nomad job status web 会列出 Allocation(分配)及其所在节点、健康情况:
nomad job run web.nomad
nomad job status web
nomad alloc status <alloc-id> # 查看某个分配详情
四、不止 Docker:二进制、Java 与批处理
Nomad 真正的优势在于”统一调度”——同一套 Job 语法可以驱动容器、原生进程、JVM 应用和一次性批处理,而不必为不同负载维护多套平台。
4.1 exec 驱动跑原生二进制
task "sleep" {
driver = "exec"
config {
command = "/bin/sleep"
args = ["3600"]
}
}
4.2 raw_exec 与 Java 任务
task "java-app" {
driver = "raw_exec"
config {
command = "/usr/bin/java"
args = ["-jar", "app.jar"]
}
}
4.3 batch 类型跑一次性批处理
把 type 设为 batch,任务跑完即止、不维持常驻,非常适合 ETL、报表生成、模型训练等离线计算。
job "etl" {
datacenters = ["dc1"]
type = "batch" # 跑完即止,不维持常驻
group "etl" {
task "spark" {
driver = "docker"
config {
image = "bitnami/spark:latest"
args = ["spark-submit", "--master", "local", "/jobs/etl.py"]
}
}
}
}
| 任务类型 | 行为 | 典型场景 |
|---|---|---|
| service | 长期运行、维持期望副本数 | Web 服务、API、长连接网关 |
| batch | 运行一次、成功即退出 | ETL、报表、模型训练 |
| system | 每个符合条件的节点都跑一份 | 日志采集、节点监控 Agent |
4.4 用 constraint 做节点亲和与分散
你可以用 constraint 把任务固定到带特定标签的节点,例如只跑在有 GPU 的机器上;也可以用 spread 让同一组的多个实例尽量分散到不同主机,避免”鸡蛋放一个篮子”。
# 只调度到带 gpu=true 标签的节点
constraint {
attribute = "${meta.gpu}"
value = "true"
}
# 同一 group 的多个实例分散到不同主机
spread {
attribute = "${node.unique.id}"
}
相比”写一堆 shell 脚本去 scp 启进程”,Nomad 把这些调度语义收敛成声明式配置:你描述期望状态,调度器负责把现实拉回到期望。这种”声明式”思维,正是它和 Ansible/supervisord 这类命令式工具的本质区别。
五、生产部署:集群、服务发现与密钥
5.1 Server / Client 架构
生产环境建议部署 3 或 5 个 Server 节点组成 Raft 共识组,Client 节点负责真正执行任务。下面是两个最小配置片段。为什么是 3 或 5 个?因为 Raft 需要多数派存活才能选出 Leader:3 节点允许 1 台宕机、5 节点允许 2 台宕机,而奇数能在相同容错能力下少花一台机器。
# server.hcl
server {
enabled = true
bootstrap_expect = 3
}
# client.hcl
client {
enabled = true
servers = ["10.0.0.10:4647", "10.0.0.11:4647", "10.0.0.12:4647"]
}
5.2 与 Consul 集成做服务发现
Nomad 原生支持把服务注册到 Consul 服务发现,配合 Nginx 反向代理 即可把内部服务安全暴露出去;敏感配置与证书则交给 Vault 动态密钥 注入,避免明文落盘。这正是 Nomad”调度器 + 生态积木”协作的典型范式。
六、Nomad vs Kubernetes:怎么选
两者并非非此即彼。理解定位差异,才能避免”用牛刀杀鸡”或”用小刀砍大树”。
| 维度 | Nomad | Kubernetes |
|---|---|---|
| 核心定位 | 轻量集群调度器 | 完整容器编排平台 |
| 部署复杂度 | 单二进制,分钟级 | 多组件,较重 |
| 任务类型 | 容器 + 二进制 + 批处理 | 主要容器 |
| 学习曲线 | 平缓 | 陡峭 |
| 适用团队 | 中小团队 / 混合负载 | 大规模容器原生组织 |
若你的团队已经深度使用 Kubernetes,可参考 Kubernetes 入门 巩固基础;若只是想把容器和脚本统一调度起来,Nomad 往往更快见效。两者也能共存:用 Nomad 跑批处理、用 K8s 跑在线服务。
七、常见排障与误区
| 现象 | 常见原因 | 处理方式 |
|---|---|---|
| 任务一直 Pending | 资源不足或约束不满足 | 调小 resources / 放宽 constraint |
| 节点 Not Ready | Client 未连上 Server | 检查 servers 配置与 4647 端口连通 |
| 镜像拉取慢 | 无本地缓存 | 配置镜像加速器或预拉取 |
| 服务发现缺失 | 未集成 Consul | 启用 consul 块并确认 Agent 联通 |
遇到问题时,下面几条命令是排查入口:先看作业事件,再钻到具体分配看日志。
nomad job status -verbose web # 查看作业详细事件与失败原因
nomad alloc logs <alloc-id> # 查看任务 stdout
nomad alloc logs -stderr <alloc-id> # 查看 stderr
nomad node status <node-id> # 查看节点资源与心跳
八、延伸阅读与落地建议
把 Nomad 接入你的交付链也很简单:用 GitHub Actions 在 CI 结束后自动 nomad job run,配合 Docker 镜像构建,即可实现”提交即部署”。一个典型的流水线步骤是:构建并推送镜像后,运行 nomad job run -check web.nomad 先做语法与约束预检,再用 -detach 异步提交并等待分配落地;对于需要常驻的 Web 服务,记得用 Nginx 反向代理 做统一入口与 TLS 终止。
总结一句:Nomad 的价值不在于”比 K8s 更强”,而在于”足够轻、足够专”。当你需要一个能统一调度容器、二进制与批处理,又不想背负整支 SRE 团队来运维编排平台时,它是最省心的那块拼图。




