Nomad 集群部署指南
在 3 台 Server + 2 台 Client 的真实服务器上从 0 到 1 搭建 Nomad 2.0.7 集群,覆盖 retry_join 自动组网、Raft 选主、节点注册,并创建一个实际运行的服务型 job 验证端到端调度。
Nomad 集群部署指南
目标:在 3 台 Server + 2 台 Client 的真实服务器上从 0 到 1 搭建 Nomad 多节点集群,覆盖 retry_join 自动组网、Raft 选主、节点注册,并创建一个可实际运行的 job 验证端到端调度。所有命令与回显均以 5 节点(3 server + 2 client)+ Nomad 2.0.7 实测为准。
一、环境信息
- 操作系统:5 台 Ubuntu 22.04 LTS(x86_64)
- Nomad 版本:
2.0.7(client/server 同版本) - 集群拓扑:3 台 Server / 2 台 Client
server1.example.com—— Server 节点server2.example.com—— Server 节点server3.example.com—— Server 节点client1.example.com—— Client 节点(node_class = linux-docker)client2.example.com—— Client 节点(node_class = linux-docker)
- 数据中心:
dc1;区域:global - 数据目录:
/opt/data/nomad - 服务端口:HTTP 4646 / RPC 4647 / Serf(gossip) 4648
- 运行账号:
root(配合密码q1w2e3r4便于演示)
💡 拓扑规模只需 3 台 Server(形成 Raft quorum),Client 数量按业务弹性扩缩。本集群实际为 3+2 的 5 节点架构。五台机器处于同一内网,已通过
/etc/hosts(或内网 DNS)配置主机名互认:<IP> server1.example.com等,Server 间用主机名互相发现。
二、集群原理简介
- Server:负责 Raft 日志复制、leader 选举、调度评估(evaluation)、维护集群状态。
- Client:负责注册到 Server、接收并执行被调度下来的任务(allocations),向 Server 上报心跳与资源指纹。
- Raft quorum:3 台 Server 可容忍 1 台故障;
bootstrap_expect=3表示 3 台 Server 之间networking 起来后自动初始化 Raft 集群并选举 leader。 - retry_join:Server 启动时轮询指定地址加入集群;Client 通过
servers列表发现 Server。
三、集群节点安装(0 → 1 安装主线)
3.1 每台节点安装 Nomad
三个 Server 与两个 Client 的二进制来源一致,以 server1 为例执行(方案 B 官方 apt 仓库),其余 4 台执行一致步骤:
sudo apt-get update
sudo apt-get install -y curl gnupg ca-certificates
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo gpg --batch --yes --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" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt-get update && sudo apt-get install -y nomad iproute2
nomad version
执行结果(5 台一致):
Nomad v2.0.7
BuildDate 2026-09-17T17:24:03Z
Revision 9dcbdc5e64ecc4ec63e42b225af729c87c87cf83
⚠️ 排障:client 角色依赖
ip(iproute2)探测网卡,务必安装iproute2,否则报fingerprinting failed … /sbin/ip: no such file or directory。详见《Nomad 安装指南》常见问题。
3.2 配置 Server 节点(server1 / server2 / server3)
三台 Server 使用相同的 /etc/nomad.d/server.hcl,差别仅在主机名。以 server1 的配置为例:
data_dir = "/opt/data/nomad"
bind_addr = "0.0.0.0"
datacenter = "dc1"
server {
enabled = true
bootstrap_expect = 3
server_join {
retry_join = ["server1.example.com", "server2.example.com", "server3.example.com"]
retry_interval = "30s"
retry_max = 3
}
}
💡 三台 Server 的
retry_join都指向彼此主机名,任一节点启动后不断重试直到凑齐 3 台,随后自动引导(bootstrap)并选举 leader。retry_interval="30s"是重试间隔,retry_max=3为最大重试次数。
⚠️ 注意:三台 Server 都禁止在配置中启用 client 角色(本集群 Client 是独立节点),也不要在同一台机器上同时
server.enabled与client.enabled,否则角色职责混淆。
3.3 配置 Client 节点(client1 / client2)
两台 Client 使用相同的 /etc/nomad.d/client.hcl:
data_dir = "/opt/data/nomad"
bind_addr = "0.0.0.0"
datacenter = "dc1"
client {
enabled = true
servers = ["server1.example.com", "server2.example.com", "server3.example.com"]
node_class = "linux-docker"
}
💡
servers是 Client 连接的 Server 地址列表(写成主机名单一次提供高可用);node_class="linux-docker"是给本节点打的调度标签,job 可通过约束按 class 选择目标节点。
3.4 逐台启动 Server 并确认组网
三台 Server 逐个启动(禁止批量脚本循环,每台独立执行):
server1:
sudo systemctl daemon-reload
sudo systemctl enable nomad
sudo systemctl start nomad
server2(引导对等 Peer,同 server1 步骤):
sudo systemctl start nomad
server3:
sudo systemctl start nomad
等待几秒后,在 server1 上查看集群成员:
nomad server members
执行结果:
Name Address Port Status Leader Raft Version Build Datacenter Region
server1.example.com.global 172.30.0.2 4648 alive false 3 2.0.7 dc1 global
server2.example.com.global 172.30.0.3 4648 alive false 3 2.0.7 dc1 global
server3.example.com.global 172.30.0.4 4648 alive true 3 2.0.7 dc1 global
💡 三台 Server 全部
alive;Leader=true的那一行(此处 server3)是当前 Raft leader。Address:Port中 4648 是 gossip(Serf)端口。serf: EventMemberJoin与entering leader state可在各自 agent 日志看到。
3.5 启动 Client 并确认注册
两台 Client 逐个启动(每台独立执行):
client1:
sudo systemctl enable nomad
sudo systemctl start nomad
client2:
sudo systemctl start nomad
在 server1 上验证所有节点(客户端视角):
nomad node status
执行结果(完整集群 5 节点中 Client 部分):
ID Node Pool DC Name Class Drain Eligibility Status
0c531868 default dc1 client1.example.com linux-docker false eligible ready
b9b8a77e default dc1 client2.example.com linux-docker false eligible ready
💡
Status=ready表示该 Client 已成功注册并向 Server 上报可用性;Drain=false表示未在排空,Eligibility=eligible表示可被调度放置新任务。在 server 上执行nomad node status会列出全部已注册 Client(Server 节点本身不作为调度目标显示在这里)。
3.6 查看单个节点的详细资源信息
nomad node status -verbose
执行结果:
ID Node Pool DC Name Class Address Version Drain Eligibility Status
0c531868-e0c2-7aac-061d-f989e07cd873 default dc1 client1.example.com linux-docker 172.30.0.5 2.0.7 false eligible ready
b9b8a77e-5f60-5e9f-2c66-83bb2486482a default dc1 client2.example.com linux-docker 172.30.0.6 2.0.7 false eligible ready
四、创建一个可实际运行的 Job(端到端验证)
4.1 编写 Job 规范文件
在任意节点(此处 server1)创建 /etc/nomad/web-demo.nomad:
job "web-demo" {
datacenters = ["dc1"]
type = "service"
group "web" {
count = 2
task "echo" {
driver = "exec"
config {
command = "/bin/sh"
args = ["-c", "while true; do echo hello-nomad $(date); sleep 2; done"]
}
resources {
cpu = 100
memory = 64
}
}
}
}
💡
datacenters = ["dc1"]指定可运行的机房;type="service"是常驻服务类任务;count=2表示该任务组要放 2 份分配(会分布到两台 Client);driver="exec"用 Nomad 内置的 exec 驱动直接启动进程(无需额外驱动依赖);resources声明每个分配占用的 CPU(MHz)与内存(MiB)。
4.2 预演(plan,不实际变更集群)
nomad job plan /etc/nomad/web-demo.nomad
执行结果:
+ Job: "web-demo"
+ Task Group: "web" (2 create)
+ Task: "echo" (forces create)
Scheduler dry-run:
- All tasks successfully allocated.
Job Modify Index: 0
To submit the job with version verification run:
nomad job run -check-index 0 /etc/nomad/web-demo.nomad
💡
plan是干跑(dry-run),不产生任何实际分配,仅预演调度结果并给出 Job Modify Index 供run -check-index做并发安全的版本校验。
4.3 正式运行 Job
nomad job run /etc/nomad/web-demo.nomad
执行结果:
==> 2026-10-09T09:17:58Z: Monitoring evaluation "fe23c021"
2026-10-09T09:17:58Z: Evaluation triggered by job "web-demo"
2026-10-09T09:17:59Z: Evaluation within deployment: "6c9e15a3"
2026-10-09T09:17:59Z: Allocation "24e22a20" created: node "b9b8a77e", group "web"
2026-10-09T09:17:59Z: Allocation "32bc310a" created: node "0c531868", group "web"
2026-10-09T09:17:59Z: Evaluation status changed: "pending" -> "complete"
==> 2026-10-09T09:17:59Z: Evaluation "fe23c021" finished with status "complete"
==> 2026-10-09T09:17:59Z: Monitoring deployment "6c9e15a3"
Deployed
Task Group Desired Placed Healthy Unhealthy Progress Deadline
web 2 2 2 0 2026-10-09T09:28:35Z
… Deployment completed successfully
💡 两个分配(alloc):
24e22a20落在 client2,32bc310a落在 client1,说明 2 份任务被调度到两台 Client 上,实现了负载分布。deployment 显示Placed=2 Healthy=2,部署成功。
4.4 查看 Job 状态
nomad job status web-demo
执行结果:
ID = web-demo
Name = web-demo
Submit Date = 2026-10-09T09:17:58Z
Type = service
Priority = 50
Datacenters = dc1
Namespace = default
Node Pool = default
Status = running
Periodic = false
Parameterized = false
Summary
Task Group Queued Starting Running Failed Complete Lost Unknown
web 0 0 2 0 0 0 0
Latest Deployment
ID = 6c9e15a3
Status = successful
Description = Deployment completed successfully
Deployed
Task Group Desired Placed Healthy Unhealthy Progress Deadline
web 2 2 2 0 2026-10-09T09:28:35Z
Allocations
ID Node ID Task Group Version Desired Status Created Modified
24e22a20 b9b8a77e web 0 run running 44s ago 7s ago
32bc310a 0c531868 web 0 run running 44s ago 7s ago
4.5 列出 Job 的所有分配
nomad job allocs web-demo
执行结果:
ID Node ID Task Group Version Desired Status Created Modified
24e22a20 b9b8a77e web 0 run running 57s ago 20s ago
32bc310a 0c531868 web 0 run running 57s ago 20s ago
4.6 查看单个分配详情
nomad alloc status 24e22a20
执行结果:
ID = 24e22a20-9c7e-3b0e-42cf-408f0c54a3f5
Eval ID = fe23c021
Name = web-demo.web[1]
Node ID = b9b8a77e
Node Name = client2.example.com
Job ID = web-demo
Job Version = 0
Client Status = running
Client Description = Tasks are running
Desired Status = run
Desired Description = <none>
Created = 1m2s ago
Modified = 25s ago
Deployment ID = 6c9e15a3
Deployment Health = healthy
Task "echo" is "running"
Task Resources:
CPU Memory Disk Addresses
100 MHz 64 MiB 300 MiB
Task Events:
Started At = 2026-10-09T09:18:25Z
Finished At = N/A
Total Restarts = 0
Last Restart = N/A
Recent Events:
Time Type Description
2026-10-09T09:18:25Z Started Task started by client
2026-10-09T09:17:58Z Task Setup Building Task Directory
2026-10-09T09:17:58Z Received Task received by client
4.7 查看任务实时日志(验证任务真实在跑)
nomad alloc logs -tail 24e22a20 echo
执行结果(片段):
hello-nomad Fri Oct 9 09:18:25 UTC 2026
hello-nomad Fri Oct 9 09:18:27 UTC 2026
hello-nomad Fri Oct 9 09:18:29 UTC 2026
hello-nomad Fri Oct 9 09:18:31 UTC 2026
hello-nomad Fri Oct 9 09:18:33 UTC 2026
💡 日志里每 2 秒输出一行
hello-nomad <时间>,证明被调度的任务确实在对应 Client 的真实进程中运行,集群端到端(调度 → 放置 → 执行 → 日志回传)全部打通。
五、节点运维:排空与调度开关(集群级操作)
5.1 节点排空(drain)——迁移该节点全部任务
对 client1(node id 0c531868)启用排空,把其上的分配迁走:
nomad node drain -enable -deadline 10m -yes 0c531868
执行结果:
2026-10-09T09:20:19Z: Ctrl-C to stop monitoring: will not cancel the node drain
2026-10-09T09:20:19Z: Node "0c531868-e0c2-7aac-061d-f989e07cd873" drain strategy set
2026-10-09T09:20:20Z: Alloc "32bc310a-be91-dfa5-f68f-f3122772d688" marked for migration
2026-10-09T09:20:20Z: Alloc "32bc310a-be91-dfa5-f68f-f3122772d688" draining
2026-10-09T09:20:22Z: Alloc "32bc310a-be91-dfa5-f68f-f3122772d688" status running -> complete
2026-10-09T09:20:22Z: All allocations on node "0c531868-e0c2-7aac-061d-f989e07cd873" have stopped
2026-10-09T09:20:22Z: Drain complete for node 0c531868-e0c2-7aac-061d-f989e07cd873
取消排空(恢复正常调度):
nomad node drain -disable -yes 0c531868
执行结果:
Node "0c531868-e0c2-7aac-061d-f989e07cd873" drain strategy unset
💡
-deadline 10m给任务 10 分钟优雅退出宽限,超时强制执行;排空期间新分配不再放置到该节点,存量分配被迁移到其它可用 Client(此例中原本在 client1 的32bc310a迁移并 complete)。
5.2 调度开关(eligibility)
把 client2(b9b8a77e)标记为不可调度(存量任务保留但不再放新任务):
nomad node eligibility -disable -self --address=http://client2.example.com:4646
💡
-self在 Agent 本机执行时查询本节点;也可用<node-id>指定远程节点。-disable设不可调度,-enable恢复。
针对具体 Node 在 server 上执行:
nomad node eligibility -disable b9b8a77e
nomad node eligibility -enable b9b8a77e
六、Server 集群运维命令
6.1 强制某 Server 离开集群
nomad server members
nomad server force-leave server2.example.com.global
💡
force-leave把健康检查判定失效的成员直接置为left状态,常用于脑裂或宕机节点回收。
6.2 查看成员详细标签
nomad server members -verbose
执行结果(单行标签节选):
server1.example.com.global 172.30.0.2 4648 alive false … dc=dc1,expect=3,port=4647,role=nomad,region=global,revision=9dcbdc5e…,raft_vsn=3,build=2.0.7,rpc_addr=172.30.0.2
💡 标签里
expect=3(bootstrap_expect)、role=nomad、raft_vsn=3是运营排障时的关键信息。
七、常见问题
Q1:nomad server members 只有本机,其它 Server 没 join 上
原因:retry_join 里的主机名无法解析,或 bootstrap_expect 未达成。排查:getent hosts server2.example.com 确认 DNS//etc/hosts,确认三台 bootstrap_expect=3 且都已启动。
Q2:Client node status 一直不出现或 Status=down
原因:Client 连不上任一 Server(servers 列表写错/IP 不通),或 Server 未启动。在 Client 看 /opt/data/nomad/nomad.log 的 node registration 是否 complete。
Q3:job 放置失败 Constraint "missing drivers"/Class ->N nodes excluded
原因:任务指定了该 Client 未启用/不存在的驱动或约束。本集群用内置 exec 驱动即无需额外配置;若要用 raw_exec 需在 Client 配置显式 allowlist。
Q4:-monitor 与 -enable/-disable 同时使用报错
nomad node drain 的 -monitor 只能单独用,不能与 -enable/-disable 同用。排空监控用 -enable 后默认即进入监控态,或用 -monitor 不带开关参数。
八、参考链接
- Nomad 集群配置参考:https://developer.hashicorp.com/nomad/docs/configuration/server
- retry_join 说明:https://developer.hashicorp.com/nomad/docs/configuration/server/server_join
- Job 规范文档:https://developer.hashicorp.com/nomad/docs/job-specification
- exec 驱动:https://developer.hashicorp.com/nomad/docs/drivers/exec
- 节点运维命令:https://developer.hashicorp.com/nomad/docs/commands/node/drain