Nomad 集群部署指南

在 3 台 Server + 2 台 Client 的真实服务器上从 0 到 1 搭建 Nomad 2.0.7 集群,覆盖 retry_join 自动组网、Raft 选主、节点注册,并创建一个实际运行的服务型 job 验证端到端调度。

Nomad容器编排容器编排Nomad2026/10/09

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 不带开关参数。

八、参考链接