一、Mistral的背景
在管理类似云平台或数据中心等IT基础设施时,管理员通常需要完成一系列的任务,如:
- 在所有的或部分服务器上更新Linux内核或特定的软件
- 在部分服务器上重新配置软件
- 从服务器抓取数据并根据这些数据构建报告
- 检查服务器的软件的运行情况或服务器本身的健康信息
值得补充的是,这些列举的任务可能都需要按照指定的时间表定期进行。如果不使用任何可以使其自动化的特殊软件,那么处理它们就需要花费很多的精力。
在这篇文章中,我们把openstack云租户当做一个IT基础设施的例子,看看Mistral工作流服务如何为管理员提供帮助。
二、重要内容
就以在单个服务器上更新Linux内核为例,有一下几个步骤:
- 下载新的Linux内核源码包
- 安装源码包
- 重启服务器
看起来很简单,但是当我们做下面的任务时就会变得很难:
- 在多个服务器上完成上述任务
- 在上述步骤完成后检查哪些服务器成功更新了,哪些没有成功
- 周期性的完成上述任务
举例来说,如果我们想做这种自动化通过编写一个脚本,无论是shell或Python,我们会很快看到,照顾这些方面非常具有挑战性,因为为了高效的完成任务,并行处理所有服务器就很有意义了,一旦所有的服务器都处理发送通知的信息显示所有是好的或有问题是否发生在一些操作。此外,如果一个脚本运行在一个负责解决这个任务的机器上,不管出于什么原因这个机器故障了,那么整个更新100台服务器的过程将不会完成,最终处于一个未知的状态
我们需要关注的至少有:
- 并行执行
- 持续的发送每个服务器的状态信息(至少是成功还是失败)
- 搞可靠性,保证整个任务的完成
- 消息通知机制,而不用我们自己去查看过程的状态
事实上,每次我们需要做类似的事情的时候这个过程都应该重复。如果我们总是想手动地运行这个升级,通知机制不是必须的,而且它不会花很长时间。如果一个人在启动时没有控制,或者需要很长时间,通知就变得非常重要。这实际上意味着我们很可能需要使用一个外部工具来解决这些问题。像Mistral工作流服务这样的工作流技术正是能够帮助解决这些问题的工具。
三、基于Mistral的解决方案
在所有的VMs上更新Linux内核
作为一个例子,假设所有的虚拟机都已经安装了Ubuntu系统,让我们来看看怎么在这些VMs上更新内核。
这个使用案例相当地简单,但是它能展示使用工作流服务的必要的优点。
1、初始化workflow
workflow是Mistral的核心。所以我们首先要创建一个包含更新内核功能的Mistral workflow。先创建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
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"
看这个文件我们可以看到它定义了两个task:get_hosts和upgrade。
get_hosts调用nova action “nova.servers_list”以JSON list的形式返回租户项目中所有VM的信息。我们需要做的是解析他们的IP地址。所以我们声明了一个publish字句去产生hosts变量,这个变量会包含一系列IPs。这里使用的解析IP地址的YAQL表达很微妙,只是因为nova建造网络信息的方式。
注:运行一下命令可以很容易查看nova返回VM信息的格式:
$ mistral run-action nova.servers_get '{"server": "<server-id>"}'
值得注意的是,由于在Mistral中,任务的结果是它的action(或workflow)的结果,我们使用“false”分配的特殊任务属性“keep - result”,这样结果就不会被存储在工作流上下文中。我们这样做只是因为我们对Nova返回的所有信息不感兴趣,只有IP地址是相关的。这样做是有意义的,因为即使我们有一个拥有30个虚拟服务器的租户,那么由Nova返回的所有信息都将花费约100 KB的磁盘空间。
upgrade task是最重要的部分。它利用“with- items”功能遍历服务器IP并ssh到每个服务器,以升级内核。在这里“iterate”一词并不意味着处理是连续的。相反,这里正是Mistral并行运行内核升级独特的地方。“std.sh_proxied”的每个动作执行对象都存储在数据库中,并保存在某个虚拟服务器上的升级操作的状态和结果。
细心的读者可能会注意到action “std.ssh_proxied” 名字中的后缀”proxied” 并且疑惑这个是什么意思?为什么不是Mistral标准action库中的std.ssh?现在我们回到关于如何访问客户操作系统的假设。默认情况下,Mistral无法真正为云隔离管理网络提供安全的shell访问,因为所有的OpenStack服务都来自于客户网络。事实上,如果一个服务器没有浮动IP,那么任何运行在管理网络中的服务都无法访问该服务器的网络,因为它在一个不同的网络中。在我们的特定示例中,我们假设租户中至少有一个VM有一个浮动的IP地址,这样它就可以作为一个ssh-gateway,通过它我们实际上可以ssh其他VM。这就是为什么我们使用名为“std . ssh_proxied”的特殊操作,其中“proxied”意味着我们有一个代理VM来访问所有租户VM。
Mistral是一个分布式的高可用系统,它不仅能够在基础设施的故障中存活,而且还能保持其工作流程的运行。这就是为什么我们可以确保这样一个流程自动化的流程服务,就像Mistral将完成,即使在控制系统组件失败的情况下,在我们的例子中的Mistral engine和executor。
2、添加提醒
我们的工作流缺少的是当内核升级在所有服务器上完成时,通知云操作员的能力。我们需要增加一个task来完成通知功能,称之为“send_success_email”。完整地workflow描述文件update_kernel.yaml变为:
---
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” task的转换。这个转换是有条件的:只有当我们通过需要发送电子邮件作为输入参数的数据时,它才会起作用。这也是这个新版本的workflow有一个新输入参数 “email_info” 的原因。“email_info”是一个数据结构,包括 “from_email” ,“to_email”,“smtp_server”,“smtp_password”。
3、上传workflow到Mistral
假设我们已经安装好Mistral client,我们可以使用一下命令上传workflow:
$ mistral workflow-create update_kernel.yaml
这个命令通常的输出结果是一个表格:
Name | Tags | Input | Created at | Updated at |
---|---|---|---|---|
upgrade_kernel | username=ubuntu, private_… | 2015-10-19 10:32:27 | None |
注: 打印所有可用的workflow,运行:
$ mistral workflow-list
4、运行workflow
上传workflow后Mistral就记录 “upgrade_kernel” workflow,运行workflow:
$ mistral execution-create upgrade_kernel input.json
JSON格式的input.json 文件包含了workflow的输入,如下:
{
“private_key_filename”: “my_key.pem”,
“gateway_host”: “172.16.74.8”
}
5、配置周期运行
我们需要配置一个计划任务触发器,以使workflow周期性的执行:
$ mistral cron-trigger-create update_kernel_weekly update_kernel --pattern “0 2 * * mon”
查看所有激活的计划任务触发器,运行:
$ mistral cron-trigger-list
这样,我们创建的workflow会在每周一2:00 am运行,在我们登录的租户中的所有服务器上更新Linux内核。
对Mistral Cron trigger来说,重要的是它也是一个分布式的容错机制。这意味着,如果一些Mistral engine崩溃了,那Cron trigger将会继续工作,因为它们没有单一的故障点。
如果我们不再需要周期性的更新内核,我们只需要删除这个触发器:
$ mistral cron-trigger-delete update_kernel_weekly