Mistral for Administration (aka Cloud Cron)¶
先决条件¶
读者应该熟悉 Mistral 的基本概念,例如工作流、任务、动作、cron 触发器和 YAQL 表达式语言。请参阅 用户文档 的相应部分以获取更多信息。
背景¶
在管理 IT 基础设施(例如云或数据中心)时,系统管理员通常需要解决很多任务。仅举几例
更新所有或部分服务器上的 Linux 内核或特定软件
重新配置部分服务器上的某些软件
从部分服务器上抓取数据并基于此数据生成报告
检查部分服务器上某些软件的运行状况或服务器本身的运行状况
值得补充的是,上述任何任务可能都需要根据指定的时间表定期执行。如果没有使用任何能够自动执行它的特殊软件,处理这些任务将需要大量的人工关注。
在本文中,我们将 OpenStack 云租户作为系统管理员需要管理的 IT 基础设施的一个例子,并了解 Mistral 工作流服务如何帮助解决这些问题以及为什么使用工作流技术是值得的。
重要方面¶
那么,解决上述任何问题需要什么?让我们看一个非常简单的任务,例如在单个服务器上升级 Linux 内核。它需要以下操作
下载新的 Linux 内核包
安装包
重新启动服务器
看起来很简单。但是,当
我们想对多个服务器执行此操作时
在所有服务器上完成此序列后,我们需要清楚地知道哪些服务器已成功更新,哪些服务器没有
我们需要定期自动运行此序列
例如,如果我们只想通过编写脚本(管理员通常会这样做,无论是 Shell 还是 Python)来完成这种自动化,我们很快就会发现处理这些方面非常具有挑战性,因为为了有效地做到这一点,有必要并行处理所有服务器,并在处理完所有服务器后发送通知,其中包含显示一切正常或操作过程中出现问题的相关信息。此外,如果负责解决此任务的单个机器上运行的脚本由于任何原因而失败,那么更新一百台服务器的整个过程将无法完成,并最终陷入未知状态。
图 1. 更新多个租户服务器¶
因此,这表明我们需要处理至少
并行执行
持久状态,提供有关每个服务器发生情况的信息(至少,成功或失败)
高可用性,以确保一切顺利完成
通知机制,以便我们不必手动检查流程状态
实际上,每当我们需要执行类似操作时,都应该重复这些操作。如果总是想手动运行此升级并且不需要很长时间,则通知机制不是必需的。如果人类不控制何时启动和/或需要很长时间,那么通知就变得非常重要。所有这些实际上意味着我们很可能需要使用外部工具来处理这些问题。像 Mistral 工作流服务这样的工作流技术正是可以帮助处理这些问题的工具。
基于 Mistral 的解决方案¶
现在让我们展示如何使用 Mistral 解决这类任务,并详细探讨 Mistral 如何解决上述问题。
在所有租户 VM 上更新 Linux 内核¶
例如,让我们看看如何升级所有云租户服务器(虚拟机或 VM)上的 Linux 内核版本,假设它们都安装了 Ubuntu。我们还将提出一些关于如何访问客户操作系统(我们将在单独的地方提及)的假设。事实上,这些假设并没有从整体方法论的角度改变太多,因此即使我们更改了一些细节,例如使用不同的操作系统而不是 Ubuntu,它仍然适用。
这个用例非常简单,但它展示了使用工作流技术的基本优势。
初始工作流¶
Mistral 的核心概念是工作流,因此首先,我们需要创建一个 Mistral 工作流,其中包含在多个租户服务器上更新 Linux 内核的逻辑。让我们在任何方便的文本编辑器中创建一个名为 update_kernel.yaml 的文本文件
---
version: '2.0'
upgrade_kernel:
input:
- username: ubuntu
- private_key_filename
- gateway_host
tasks:
get_hosts:
action: nova.servers_list
publish:
hosts: <% task(get_hosts).result.select({ip => $.addresses.get($.addresses.keys().first()).where($.get("OS-EXT-IPS:type") = fixed).first().addr}).ip %>
keep-result: false
on-success: upgrade
upgrade:
with-items: host in <% $.hosts %>
action: std.ssh_proxied host=<% $.host %>
input:
host: <% $.host %>
gateway_host: <% $.gateway_host %>
username: <% $.username %>
private_key_filename: <% $.private_key_filename %>
cmd: "sudo apt-get update && sudo apt-get install linux-image-generic-lts-$(lsb_release -sc) -y && sudo reboot"
这是执行我们需要做的 Mistral 工作流的最简单版本。让我们看看它包含什么。它有两个任务定义:“get_hosts”和“upgrade”。
“get_hosts” 调用 Nova 动作 “nova.servers_list”,该动作以 JSON 列表的形式返回有关租户中所有服务器的信息。我们真正需要的是提取它们的 IP 地址。为了做到这一点,我们声明了一个“publish”子句,该子句在工作流上下文中引入了一个名为“hosts”的新变量,该变量将包含 IP 地址列表。用于提取 IP 地址的 YAQL 表达式在这里有点棘手,只是因为 Nova 结构化网络信息的方式。
注意:只需运行即可轻松查看 Nova 以何种形式返回服务器信息
$ mistral run-action nova.servers_get '{"server": "<server-id>"}'
值得注意的是,由于在 Mistral 中,任务的结果是其动作(或工作流)的结果,我们使用特殊的任务属性“keep-result”分配了“false”,以便结果不会存储在工作流上下文中。我们这样做只是因为我们对 Nova 返回的所有信息不感兴趣,只有 IP 地址是相关的。这样做是有意义的,因为即使我们有一个包含 30 个虚拟服务器的租户,Nova 返回的有关它们的所有信息也将占用大约 100 KB 的磁盘空间。
任务“upgrade” 是最有趣的地方。它利用“with-items”功能来迭代服务器 IP 地址列表,并 ssh 到每个服务器以升级内核。这里的“迭代”并不意味着处理是顺序的。相反,这里是 Mistral 并行运行内核升级的地方。每个“std.ssh_proxied”动作执行对象都存储在数据库中,并保留对特定虚拟服务器的升级操作的状态和结果。
细心的读者可能注意到动作名称“std.ssh_proxied”中的“proxied”后缀,并问“这意味着什么?为什么不直接使用 Mistral 也包含在其标准动作包中的“std.ssh”?”所以现在我们回到关于如何访问客户操作系统的假设。默认情况下,Mistral 实际上无法获得对客户 VM 的安全 shell 访问权限,因为云将所有 OpenStack 服务所在的管理网络与客户网络隔离。事实上,如果服务器没有浮动 IP,则管理网络中运行的任何服务都无法获得对该服务器的网络访问权限,它只是位于不同的网络中。在我们的具体例子中,我们假设租户中至少有一个 VM 具有浮动 IP 地址,以便可以用作 ssh 网关,通过该网关我们可以实际 ssh 其他 VM。这就是我们使用名为“std.ssh_proxied”的特殊动作的原因,其中“proxied”意味着我们有一个代理 VM 来访问所有租户 VM。
图 2. 通过网关 VM 进行 ssh 访问。¶
Mistral 是一个分布式高可用系统,它不仅设计用于在基础设施故障中生存,而且还能保持其工作流的运行。因此,我们可以确保使用 Mistral 等工作流服务自动化的过程即使在控制系统组件(在本例中为 Mistral 引擎和执行器)发生故障时也能完成。
添加通知¶
我们的工作流缺少的是在所有服务器上完成内核升级时通知云操作员的能力。为了做到这一点,我们只需要添加一个任务,我们称之为“send_success_email”。现在完整的工作流将如下所示
---
version: '2.0'
upgrade_kernel:
input:
- username: ubuntu
- private_key_filename
- gateway_host
- email_info: null # [to_email, from_email, smtp_server, smtp_password]
tasks:
get_hosts:
action: nova.servers_list
publish:
hosts: <% task(get_hosts).result.select({ip => $.addresses.get($.addresses.keys().first()).where($.get("OS-EXT-IPS:type") = fixed).first().addr}).ip %>
keep-result: false
on-success: upgrade
upgrade:
with-items: host in <% $.hosts %>
action: std.ssh_proxied
input:
host: <% $.host %>
gateway_host: <% $.gateway_host %>
username: <% $.username %>
private_key_filename: <% $.private_key_filename %>
cmd: "sudo apt-get update && sudo apt-get install linux-image-generic-lts-$(lsb_release -sc) -y && sudo reboot"
on-success:
- send_success_email: <% $.email_info != null %>
send_success_email:
action: std.email
input:
subject: Linux kernel on tenant VMs successfully updated
body: |
Number of updated VMs: <% $.hosts.len() %>
-- Thanks
from_addr: <% $.email_info.from_email %>
to_addrs: [<% $.email_info.to_email %>]
smtp_server: <% $.email_info.smtp_server %>
smtp_password: <% $.email_info.smtp_password %>
请注意,除了任务之外,我们还为“upgrade”任务添加了一个“on-success”子句,该子句定义了在“upgrade”成功完成后到“send_success_email”任务的转换。此转换是条件性的:它仅在我们作为输入参数传递了发送电子邮件所需的数据时才有效。这就是此新版本的工作流具有新的输入参数“email_info”的原因。预计“email_info”是一个数据结构,包含字段“from_email”、“to_email”、“smtp_server”和“smtp_password”。
将工作流上传到 Mistral¶
假设我们已安装 Mistral 客户端,我们可以使用以下命令将此工作流上传到 Mistral
$ mistral workflow-create update_kernel.yaml
此命令的正常输出(以及大多数其他命令)显示一个包含新上传的工作流的表。它可能如下所示
+----------------+--------+------------------------------+----------------------------+------------+
| Name | Tags | Input | Created at | Updated at |
+----------------+--------+------------------------------+----------------------------+------------+
| upgrade_kernel | <none> | username=ubuntu, private_... | 2015-10-19 10:32:27 | None |
+----------------+--------+------------------------------+----------------------------+------------+
注意:要打印所有可用工作流,请运行
$ mistral workflow-list
运行工作流¶
现在,一旦 Mistral 知道工作流“upgrade_kernel”,我们就可以通过运行来启动它
$ mistral execution-create upgrade_kernel input.json
文件 input.json 应包含工作流输入数据,例如
{
“private_key_filename”: “my_key.pem”,
“gateway_host”: “172.16.74.8”
}
配置 Cron 触发器¶
为了使此工作流定期运行,我们需要创建一个 cron 触发器
$ mistral cron-trigger-create update_kernel_weekly update_kernel --pattern “0 2 * * mon”
要打印所有活动的 cron 触发器,请运行
$ mistral cron-trigger-list
从现在开始,我们创建的工作流将在每周一凌晨 2:00 启动,它将在我们登录的租户中的所有服务器上更新 Linux 内核。
Mistral Cron 触发器的重要之处在于,它也是一种分布式容错机制。这意味着即使多个 Mistral 引擎崩溃,cron 触发器仍将继续工作,因为它们没有单点故障。
如果我们不再需要定期升级内核,我们可以删除触发器
$ mistral cron-trigger-delete update_kernel_weekly