> For the complete documentation index, see [llms.txt](https://freebsd-journal-cn.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://freebsd-journal-cn.bsdcn.org/20190708-rong-qi-hua/hashicorp-nomad.md).

# HashiCorp Nomad

作者：**Tara Sawyer**

在 BSD 里我们有 RC 脚本，在 Windows 上我们有 Service Manager，在 Linux 上我们有 systemd。它们都缺了些什么，或者把事情弄得比应有的更复杂。它们几乎都不是声明式的，也不能让你轻松地定义（和限制）资源。如果我们能拥有这一切，跨所有平台做这些事，与容器交互，还包含所有热门的新功能呢？

HashiCorp 的 Nomad 可以说用一种合理的声明式格式做到了这一点。Nomad 处理任务的整个生命周期，从分配资源、搭建环境、执行任务、监控任务健康状态，到失败时在任意数量的节点上重启任务、任何依赖任务。这让以下事情变得极其简单：

* 滚动升级
* 蓝/绿（A/B）部署
* 金丝雀部署
* 任务、节点或数据中心的故障恢复

Nomad 是 HashiCorp 套件的官方组成部分，有企业赞助，但也是相当标准的 GitHub 项目，用 Golang 编写，以 MPL-2.0 发布。这让偏好 BSD 许可代码的我们中许多人也能使用它。

HashiCorp 有企业商业版 Nomad，提供一些额外功能，但 MPL 许可版已经包含了几乎所有功能。商业版提供支持、细粒度访问控制、全局资源配额和命名空间等功能。即便在大规模生产环境中，这些功能大多也不是必需的。

Nomad 的架构是 agent 和 server 类型，但两种模式用的是同一个 Golang 二进制，可以完全自包含，没有外部依赖。不过，运行多节点 server 可能还需要运行 Consul——分布式 KV 存储。这里我们不介绍生产环境的安装部署。

Nomad 的 server 模式是 leader/follower 模型，通常每个数据中心运行三个 server，加上每个执行任务的节点上运行 agent。对于较小的部署，可以独立运行 agent，不需要 server 实例。对于更大的部署，Nomad 完全感知数据中心和区域，在生产环境中可以轻松处理 10,000 个节点。

在每个节点上运行的典型 Nomad agent 资源占用不高。在我的生产节点上，agent 占用 58MB RSS、约 2% CPU 和 3MB **/var** 磁盘空间来保存状态（不包括作业任务、日志文件等）。

Nomad 集群通信只需很少的配置即可加密。它有完整的基于能力的访问控制系统，你可以选择配置访问控制。没有企业版的话，访问控制严格基于令牌，不涵盖认证。实践中这不成问题，因为你可以把作业提交通过 CI/CD 系统来把关，或者把令牌生成绑定到 HashiCorp Vault。或者，你可以向 HashiCorp 付费购买企业版的高质量访问控制。

## 任务

每个“任务”或应用可以只是在机器上运行某条命令，也可以是在某种容器里运行命令的结果，比如 QEMU、docker、rkt、Java 等。这些“任务驱动”基于插件，自己写也不难，通常用 Go 编写。任务用 HCL 以非常声明式的方式定义，HCL 是 JSON 的更合理、更友好的版本，并且完全兼容 JSON。

任务可以轻松地在生产环境中跨 10,000 多个节点调度，并可以受给定节点的几乎所有方面约束，包括按区域或数据中心。所以，从节点的小部署到全规模企业系统，Nomad 都能搞定让你的任务跑起来的工作。

任务也有多种调度方式，包括定期（类似 crontab）、批处理（运行一次）、服务（持续运行）和系统（在每个节点上运行）。

任务配置在基于 HCL 的作业文件中完成。作业文件基本上有三部分：

* 给定作业的约束和任务组。
* 你需要的资源（包括运行作业所需的文件和工件，所需的 CPU、内存和网络资源）。
* 运维和故障设置——即运行多少实例、如何处理故障、如何处理作业更新。

让我们逐一看看这三部分：

### 约束和组

每个作业属于一个组，组把需要在同一节点上运行的任务归在一起。每个组可以有一个或多个任务/应用要运行。

约束让你控制作业或任务应该（或不应该）在哪里运行。你可以做各种常规布尔比较，比如等于、大于、小于等，但也可以指定更复杂的东西，比如正则比较、集合比较（如节点列表）和版本比较。完整的约束运算符列表见文档。

### 资源

对于组中的给定任务，你需要定义资源、工件、用什么系统/驱动来执行任务（对我们来说是 Jail）、运行时环境、任何要在启动前渲染进任务的模板化配置。

Nomad 强烈建议你声明式地列出作业所需的每个工件/文件和资源。这包括网络端口、带宽、CPU、内存、任何特殊硬件（比如 GPU）。这也包括 map/reduce 类批处理作业所需的任何动态载荷。

### 运维和故障设置

这包括调度器（批处理、系统、服务、定期）、亲和性、重启、重新调度、分散和服务检查等项目。这也包括运行多少实例。

总的来说，这里的选项相当全面，我不会深入太多细节，但 Nomad 能处理的一些事情包括：

* 如果你有一组 leader/follower 任务，可以把一个任务指定为 leader，当 leader 停止时，它会为你停止所有 follower 任务。
* 滚动升级，一次启动 X 个副本，并确保每个副本都在运行且健康（通过健康检查）后，才继续启动更多副本。
* 蓝/绿部署。从 11.2 升级到 11.3 时，启动所有 11.3 副本，同时不改变任何正在运行的 11.2 版本。确保它们健康后手动提升 11.3 版本。Nomad 然后会自动停止 11.2 版本。这也可以支持金丝雀部署，即先启动新版本实例，验证它正常，然后让所有其他副本滚动推出。
* 故障处理和容错。除了为作业或任务应该部署在哪里指定亲和性外，Nomad 还能处理许多不同的故障。比如，你可以把多个相同任务分散到不同节点，这样在节点故障时没有用户会察觉。这种情况的例子包括你需要的本地资源停止响应，或者应用崩溃需要重启，甚至应用在特定时间内没有响应而你想重新调度任务。Nomad 直接处理所有这些情况。

现在我们已经对 Nomad 能处理的事情有了相当全面的了解，让我们进入有趣的部分：用 Nomad 把非常简单的 Python 应用部署进 Jail 里。全程声明式！但首先，我们需要安装并运行 Nomad。

### 安装和运行

在 FreeBSD 上，`pkg install nomad` 即可直接使用。

在 HardenedBSD 上，目前需要两个补丁，必须从源码构建。

#### git/源码安装

首先安装依赖：

```sh
pkg install git
pkg install go
```

然后以普通用户身份：

```sh
export GOPATH=~/go
mkdir -p $GOPATH/src/github.com/hashicorp && cd $_
git clone https://github.com/hashicorp/nomad.git
cd nomad
```

如果在 HardenedBSD 上，按照这个 issue 和这个 issue 的补丁说明操作。最后构建：

```sh
go install
```

然后复制到 **/usr/local/bin/nomad**，以 root 身份在开发模式下运行 Nomad：

```sh
cp ~/go/bin/nomad /usr/local/bin/nomad
```

运行 Nomad：

```sh
nomad agent -dev
```

这会让 Nomad 跑起来。现在创建一些作业并运行它们。

以多 master 生产模式运行超出了本指南范围。你可以用 `service nomad enable` 然后 `service nomad start`，但你必须编辑配置文件，而且所有日志输出当前都设到了 **/dev/null**，所以尤其是在开始时，就在自己的终端上运行它。

### 运行你的第一个作业

一个获取你的 IP 的非常简单的示例作业：

它应该相当直观，但有几点要说一下。每个作业文件有 `job {}` 定义。在作业内部，你定义任何全局约束，比如这里我们说在 dc1 数据中心运行（这是 Nomad agent 加入的默认 dc）。我们还指定了类型——这里是 `batch` 类型，因为它不是长期运行的服务；它运行一次就完成了。组，我们可能还记得，把任务归到某个节点。Count 是实例数。如果你想运行 100 个 fetch 命令，只需写 `count=100`，Nomad 会处理其余的。Tasks 是细节，我们在其中指定要使用的驱动、要运行的命令，以及运行任务所需的资源。这里没有什么太刺激的。更多细节见 Nomad 文档。

把它保存到 fetch.nomad 文件，然后运行：

Nomad 任务会创建任务分配；这里你可以看到分配“fe19e216”。你的分配 ID 当然会不同。你也可以用 `nomad status <jobname>` 命令查找分配。要查看任务发生了什么，我们可以请求分配状态：

这里我们可以看到分配、它被放在哪个节点、为这个任务分配的资源、所有事件。我们看到它成功运行并以 exit 0 终止，所以它成功运行了。任务的日志和输出可以用 logs 命令获取：

IP 地址已更改，我并不真的为 NSA 工作，又或者我是？ :)

我们有了 fetch 命令的输出。要获取标准错误输出，只需在 logs 命令后加 `-stderr`，但如果我们做对了，这条命令不会有任何 stderr 输出。当然，这一切都能跨节点和数据中心工作。

让我们运行简单的 go 二进制 http-echo，它基本上就是带 HTTP 的 hello-world。但让我们构建它和为它构建 Jail，然后在 Jail 里运行它。我想那会更有趣。

首先，让我们搭建用于部署工件的 Web 服务器。

这个作业文件有两个任务，第一个通过编译源码创建 basejail，第二个任务构建 http-echo 程序。把它保存为类似 build-hello-jail.nomad 的文件并运行。由于有大量编译，会花一些时间——在我的笔记本上大约两小时。`nomad alloc status` 是你的好帮手。它最终应该会完成，nginx 现在应该同时在提供 basejail tarball 和 http-echo tarball。

我们这里用了 `template {}`，它使用 Golang 模板系统，但我没有用任何模板；这只是包含要在作业文件里运行的 shell 脚本的方便方式。

现在我们需要作业来真正在 Jail 里运行 hello 示例：

这里引入了 `artifact {}` 部分，你可能需要稍作修改，因为你的 tarball 的 SHA256 校验和可能不同。幸运的是 Nomad 已经为你算好了，可以在 .sum 文件里找到，比如：

```sh
cat /usr/local/www/nginx/nomad/*.sum
```

如果需要，用正确的校验和更新 .nomad 文件。

保存文件并运行它（`nomad run <FILENAME>`），我们应该在分配状态里看到一些有趣的东西：

任务“正在运行”！因为这是“service”类型。Nomad 会尽全力让它一直运行，直到你执行 `nomad stop <jobname>`，对我们来说是 `nomad stop hello`。但停止作业之前，让我们看几样东西：

我们可以看到 Jail 已经起来了！Path 显示了完整的根 Jail，来自我们要求的工件。而且，无论何时我们停止这个作业，都不用担心清理那些 **/tmp/Nomad\*** 目录；Nomad 会自己垃圾回收和清理。

Nomad 创建了全新的 Jail 并运行了 http-echo 服务器。事实上，如果你去探索，你会看到父目录也有 alloc/ 目录，在里面你会找到日志。当然，如果跨多个节点运行，这样访问会很困难，因此有 `nomad logs` 命令，你还可以用 `nomad fs` 命令查看 nomad 作业的整个文件系统。

注意，我的 trap 命令不能可靠工作，但我懒得调试，因为有更好的方式（继续读！）。现在，如果 trap 没有为你捕获并停止 Jail（可以用 jls 命令判断），你可以用 `jail -r <JID>` 删除它们，其中 JID 在 jls 命令的输出里。抱歉 shell 功夫不到家！

现在让我们做一个 python3 http.server Jail：

这是一个更复杂的作业文件，但我会解释发生了什么。首先，我们下载 basejail tarball 并解开，然后我们创建两个 shell 脚本，一个调用另一个。第一个发生在 Nomad Jail 根目录里，但在 Jail 之外，即 start.sh，它负责做任何启动工作、创建 run.sh 脚本，然后启动 Jail，由 Jail 运行 run.sh 脚本。

我们的设置只是创建非常简单的 HTML 文件，然后通过 python3 内置的 HTTP 服务器提供。`nomad run python.nomad` 应该能让你全部就绪并运行最简单的 python HTTP 服务器。

这应该能让你入门，但在 Nomad 里运行 Jail 实际上比上面容易得多，通过使用全新的 Nomad Jail 驱动，我们接下来会谈到。

### 在 Nomad 下管理 Jail 的合理方式

所以，让我们运行 Jail 更轻松些。为什么用所有这些 shell 脚本来构建 Jail 等等，用困难的方式做呢？让我们用 Nomad 任务驱动，它会为我们做所有 Jail 的搭建、拆卸和管理：

<https://github.com/cneira/jail-task-driver>

安装：

这应该能让插件安装好，并让 Nomad agent 用配置加载插件运行。

现在让我们在 Nomad 驱动下运行同样的 http-echo 作业：

运行它，好事应该会发生：

好耶！你可以看到有了 jail-task-driver 让配置更容易理解和推断！

这些示例可能不是你真正想运行和构建 Nomad 作业的方式。理想情况下，你会用 Nomad 批处理作业（或 CI/CD 系统）来创建 Jail tarball，里面装好了你的应用。给结果 tarball 打版本号并部署到你的 Web 服务器，让 Nomad 然后在同一个批处理作业里或经过一些测试后部署。参见 makebasejail 示例，那是会做这第一部分的 Nomad 批处理作业。

然后你会有 Nomad 作业来获取 tarball、解包并作为服务运行。这主要取决于你，但本文中的 Nomad 作业向你展示了所有不同的组成部分。这应该能让你开始用 Nomad 和 Jail。更多信息可以通过 Nomad 社区和他们的文档找到。或者联系我。我很乐意帮忙。 •

***

**TARA SAWYER** 跑过别人的软件一阵子，然后写了些软件，最后基本安身于运维，这几年一直在运行一个生产 Nomad 集群。她在其他各种操作系统里中断了一阵后最近回到了 BSD。目前喜欢杏仁和同情心。Tara 至今未能完成正规教育。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://freebsd-journal-cn.bsdcn.org/20190708-rong-qi-hua/hashicorp-nomad.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
