1
2
3
4
5
6
7
作者:李晓辉

联系方式:

1. 微信:Lxh_Chat

2. 邮箱:939958092@qq.com

你知道 Heat Orchestration Template Syntax(HOT 模板语法)那事儿不?这可是云运维人员和开发人员打交道时用到的“秘密武器”。云运维人员要搞定云开发者的应用,有时候得去调试或者修改那些编排配置,说不定还得解决错误,或者提升应用性能、让应用能更好地扩展。OpenStack 的编排服务(Heat)就是用 YAML 结构来写 HOT 文件的。

YAML 现在超流行,网上能找到超多参考资料。它用空格缩进,绝对不能用制表符,用来表示实体、组件、参数和属性之间的层级关系。它把括号和标签都去掉了,看起来很“人类友好”。

大多数文本编辑器都支持 YAML,还能设置自动用空格。比如 Red Hat Enterprise Linux 自带的 gedit 编辑器,你可以把“用空格代替制表符”这个选项勾上。要是用 vim 编辑器,就得在隐藏的配置文件里设置。要是想让 YAML 文件编辑自动缩进、设置制表符宽度、缩进宽度,还有把制表符展开成空格,就在 ~/.vimrc 文件里加上这行:

1
autocmd FileType yaml setlocal ai ts=2 sw=2 et

HOT 模板在声明版本和描述之后,结构大概就是这样的:一个有效的模板至少得定义一个要创建的资源,其他部分都是可选的。

  • parameter_groups:就是把输入参数分组、排序。

  • parameters:声明创建堆栈时需要提供的输入参数。

  • resources:声明堆栈里要创建的独一无二的资源。

  • outputs:声明创建堆栈后要提供给用户的输出参数。

  • conditions:用来限制在什么情况下创建资源。

模板的版本

对了,还得说说模板版本。每次 OpenStack 大版本更新,HOT 的功能和语法都会升级。你可以用任何一个版本的 HOT 特性来创建和维护模板,但得记住,一个堆栈用的所有模板和环境文件得是同一个版本,还得在模板里声明 heat_template_version。就像下面这样,最近的版本要么用版本字符串里的日期,要么用 OpenStack 发行版本名来指定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(overcloud) [stack@director ~]$ openstack orchestration template version list
+--------------------------------------+------+------------------------------+
| Version | Type | Aliases |
+--------------------------------------+------+------------------------------+
| AWSTemplateFormatVersion.2010-09-09 | cfn | |
| HeatTemplateFormatVersion.2012-12-12 | cfn | |
| heat_template_version.2013-05-23 | hot | |
| heat_template_version.2014-10-16 | hot | |
| heat_template_version.2015-04-30 | hot | |
| heat_template_version.2015-10-15 | hot | |
| heat_template_version.2016-04-08 | hot | |
| heat_template_version.2016-10-14 | hot | heat_template_version.newton |
| heat_template_version.2017-02-24 | hot | heat_template_version.ocata |
| heat_template_version.2017-09-01 | hot | heat_template_version.pike |
| heat_template_version.2018-03-02 | hot | heat_template_version.queens |
| heat_template_version.2018-08-31 | hot | heat_template_version.rocky |
+--------------------------------------+------+------------------------------+

HOT 模板里有个可选的 description 键,可以用它来说明这个模板是干嘛用的。如果描述内容比较多,可以用一个 > 符号放在那一行,这样就能写多行文本了。

比如这样:

1
2
3
4
heat_template_version: queens
description: >
This is multiple line description for the template
or other information about the resulting application stack.

定义输⼊参数

这可是让 HOT 模板变得灵活和可复用的关键一步!在模板开头声明参数,这样就可以在创建具体堆栈的时候,通过一个单独的环境文件传入值,覆盖默认设置。

参数的定义包括一些必要的属性,比如类型、标签、描述、默认值、是否隐藏、约束条件,还有是否不可变。具体长这样:

1
2
3
4
5
6
7
8
9
10
parameters:
<param_name>:
type: <string | number | json | comma_delimited_list | boolean>
label: <human-readable name of the parameter>
description: <description of the parameter>
default: <default value for parameter>
hidden: <true | false>
constraints:
<parameter constraints>
immutable: <true | false>
  • type:支持的数据类型有字符串、数字、JSON、逗号分隔的列表,还有布尔值。

  • default:如果没有传入值覆盖它,就用这个默认值。

  • hidden:如果设置为 true,参数值就不会显示在创建的堆栈信息里。

  • constraints:这个是用来验证输入参数值的,可以设置多种约束条件。

  • immutable:如果设置为 true,一旦声明了参数值,就不能再改了。要是尝试修改,堆栈创建或者更新就会失败。

参数的名字和类型是必须的,其他的属性都是可选的。hiddenimmutable 默认都是 false。还有一个 custom_constraints 属性,它可以通过编排插件来增加资源验证。比如在这个例子中,约束条件用了一个叫 cinder.volume 的插件,来验证块存储卷是否存在:

1
2
3
4
5
6
parameters:
volume_name:
type: string
description: volume name
constraints:
- custom_constraints: cinder.volume

定义资源

接下来是定义资源的部分。每个要创建的资源都是一个独立的嵌套块,包含它的必要属性和属性值,结构大概是这样:

1
2
3
4
5
resources:
<resource ID>:
type: <resource type>
properties:
<property name>: <property value>
  • resource ID:这是用户定义的、在堆栈中唯一的资源名称。

  • type:OpenStack 的核心资源类型都包含在编排引擎里。编排还支持资源插件,可以用来提供自定义的资源处理,或者覆盖内置的资源实现。

  • properties:这个属性用来指定与资源类型相关的属性。属性值可以是硬编码的,也可以用内置函数来获取。

如果你想查看你的 overcloud 上可用的资源类型,可以用下面的命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(overcloud) [stack@director ~]$ openstack orchestration resource type list
+----------------------------------------------+
| Resource Type |
+----------------------------------------------+
| AWS::AutoScaling::AutoScalingGroup |
| AWS::AutoScaling::LaunchConfiguration |
| AWS::AutoScaling::ScalingPolicy |
| AWS::CloudFormation::Stack |
| AWS::CloudFormation::WaitCondition |
| AWS::CloudFormation::WaitConditionHandle |
| AWS::CloudWatch::Alarm |
| AWS::EC2::EIP |
| AWS::EC2::EIPAssociation |
...

如果你想看看某个资源类型的结构,可以用 openstack orchestration resource type show 命令。比如,你想看看负载均衡器成员资源的属性,可以这样操作:

1
2
3
4
5
6
7
8
9
10
11
(overcloud) [stack@director ~]$ openstack orchestration resource type show OS::Octavia::PoolMember
resource_type: OS::Octavia::PoolMember
properties:
address:
constraints:
- custom_constraint: ip_addr
description: IP address of the pool member on the pool network.
immutable: false
required: true
type: string
update_allowed: false

输出会显示资源的属性,比如 address 属性可能有一个 custom_constraint,要求它必须是一个有效的 IP 地址。

如果想创建多个相同类型的资源,可以用 OS::Heat::ResourceGroup 资源类型。比如,你想创建两个相同的虚拟机实例,可以这样定义:

在这个例子中,count 设置为 2,表示创建两个实例。resource_def 定义了要创建的资源类型和属性。

1
2
3
4
5
6
7
8
9
10
resources:
my_group:
type: OS::Heat::ResourceGroup
properties:
count: 2
resource_def:
type: OS::Nova::Server
properties:
name: { get_param: instance_name }
image: { get_param: instance_image }

如果需要为每个实例生成唯一名称,可以用 %index% 变量。比如,你想创建 4 个 Web 服务器实例,每个实例的名称都带有编号,可以这样定义:

1
2
3
4
5
6
7
8
9
10
11
resources:
my_group:
type: OS::Heat::ResourceGroup
properties:
count: 4
resource_def:
type: OS::Nova::Server
properties:
name:
list_join: [ "%index%", [ "webserver", ".overcloud.example.com" ] ]
image: { get_param: instance_image }

这样,创建的实例名称会是:

  • webserver0.overcloud.example.com

  • webserver1.overcloud.example.com

  • webserver2.overcloud.example.com

  • webserver3.overcloud.example.com

内置函数

在编排模板中,内置函数是用来在堆栈创建时为定义的属性赋值的。

get_attr

get_attr 函数用来获取资源的属性值。比如,你想获取某个实例的 IP 地址:

1
2
3
4
5
6
7
8
resources:
appserver:
type: OS::Nova::Server
...output omitted...
outputs:
instance_ip:
description: IP address of the instance
value: { get_attr: [appserver, first_address] }

get_param

get_param 函数用来获取输入参数的值。比如,你想用输入参数设置虚拟机的规格:

1
2
3
4
5
6
7
8
9
parameters:
appserver_flavor:
type: string
description: Flavor to be used by the appserver.
resources:
appserver:
type: OS::Nova::Server
properties:
flavor: { get_param: appserver_flavor }

get_resource

get_resource 函数用来获取模板中的资源。比如,你想把一个端口资源的 ID 设置为虚拟机的网络端口:

1
2
3
4
5
6
7
8
9
resources:
appserver_port:
type: OS::Neutron::Port
...output omitted...
appserver:
type: OS::Nova::Server
properties:
networks:
port: { get_resource: appserver_port }

str_replace

str_replace 函数用来替换字符串。比如,你想用实例的 IP 地址生成一个 URL:

1
2
3
4
5
6
7
8
outputs:
website_url:
description: The website URL of the application.
value:
str_replace:
template: http://varname/MyApps
params:
varname: { get_attr: [ appserver, first_address ] }

list_join

list_join 函数用来把多个字符串拼接成一个字符串,中间可以用指定的分隔符分隔。比如,你想把实例名称和随机字符串拼接起来:

1
2
3
4
5
6
7
8
9
resources:
random:
type: OS::Heat::RandomString
properties:
length: 2
appserver:
type: OS::Nova::Server
properties:
instance_name: { list_join: [ '-', [ {get_param: appserver_name}, {get_attr: [random, value]} ] ] }

定义等待条件

在创建堆栈时,有些资源可能需要在某些条件满足后才真正算创建完成。比如,你可能希望在虚拟机上下载并配置好应用程序之后,才认为虚拟机创建完成。这时候,等待条件(Wait Conditions)就派上用场了。

等待条件的作用是暂停堆栈创建过程,直到某个条件完成并发出信号。模板中定义等待条件的任务,以及一个自动生成的 URL(称为等待条件句柄),任务可以通过这个 URL 发送状态。

等待条件的处理过程大概是这样的:

  1. 创建等待条件和句柄:在堆栈创建时,等待条件和句柄会被创建,堆栈状态会显示为“创建中”(CREATE_IN_PROGRESS)。堆栈会等待收到成功信号(或者超时)。

  2. 执行任务并发送状态:任务会按照定义的流程执行,并通过句柄发送“成功”(SUCCESS)或“失败”(FAILURE)状态。

  3. 判断状态

    • 如果在超时之前收到足够的“成功”信号,等待条件状态变为“创建完成”(CREATE_COMPLETE),堆栈创建继续。

    • 如果超时或者收到“失败”状态,等待条件状态变为“创建失败”(CREATE_FAILED),堆栈创建终止。

示例

定义一个等待条件句柄(OS::Heat::WaitConditionHandle),它没有可配置的属性:

1
2
example_wait_handle:
type: OS::Heat::WaitConditionHandle

然后定义一个等待条件(OS::Heat::WaitCondition),指定句柄、超时时间和需要接收的成功信号数量:

1
2
3
4
5
6
example_wait_condition:
type: OS::Heat::WaitCondition
properties:
handle: { get_resource: example_wait_handle }
count: 2
timeout: 600

最后,在虚拟机的 user_data 脚本中,通过 curl 向句柄发送状态信号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
example_server:
type: OS::Nova::Server
properties:
...
user_data:
str_replace:
template: |
#!/bin/bash
echo $(hostname) > /tmp/test1
[[ -f /tmp/test1 ]] && example_notify --data-binary \
'{"status": "SUCCESS", "reason": "signal1"}'
echo $(whoami) > /tmp/test2
[[ -f /tmp/test2 ]] && example_notify --data-binary \
'{"status": "SUCCESS", "reason": "signal2"}'
params:
example_notify: { get_attr: [example_wait_handle, curl_cli] }

实例配置方法

在部署实例时,有三种常见的方法可以用来配置软件:

1. 自定义 Glance 镜像

如果你在整个实例生命周期中不需要更改软件配置,可以直接使用一个已经安装并配置好软件的自定义镜像。这种方法最简单,但灵活性最低。

2. user_data 脚本

使用 user_data 脚本和 cloud-init 来配置实例中的预安装软件。这种方法适用于在实例启动时进行一次性的软件配置更改。如果需要更改配置,必须重新创建实例。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
resources:
appserver:
type: OS::Nova::Server
properties:
...
user_data_format: RAW
user_data:
str_replace:
template: |
#!/bin/bash
echo "Hello World" > /tmp/$demo
params:
$demo: demofile

如果需要更复杂的脚本,可以用 get_file 函数从文件中获取脚本内容:

1
2
3
4
5
6
7
8
9
10
11
resources:
appserver:
type: OS::Nova::Server
properties:
...
user_data_format: RAW
user_data:
str_replace:
template: { get_file: demoscript.sh }
params:
$demo: demofile

3. SoftwareDeployment 资源

OS::Heat::SoftwareDeployment 资源类型允许在实例生命周期内多次更改软件配置,而无需重新部署实例。这种方法适用于需要频繁更改软件配置的场景。

要使用 SoftwareDeployment,需要定义以下三种资源:

  • OS::Heat::SoftwareConfig:定义软件配置,可以与多种配置管理工具(如脚本、Ansible、Puppet)配合使用。每次更改配置时,会用新的配置替换旧的配置。
1
2
3
4
5
6
7
8
9
10
11
resources:
the_config:
type: OS::Heat::SoftwareConfig
properties:
group: script
inputs:
- name: filename
- name: content
outputs:
- name: result
config: { get_file: demo-script.sh }
  • OS::Nova::Server:定义要应用软件配置更改的实例。user_data_format 属性需要设置为 SOFTWARE_CONFIG
1
2
3
4
5
6
resources:
the_server:
type: OS::Nova::Server
properties:
...
user_data_format: SOFTWARE_CONFIG
  • OS::Heat::SoftwareDeployment:将定义的 SoftwareConfig 应用到实例上。可以通过 input_values 设置输入变量的值。
1
2
3
4
5
6
7
8
9
10
11
12
resources:
the_deployment:
type: OS::Heat::SoftwareDeployment
properties:
server: { get_resource: the_server }
actions:
- CREATE
- UPDATE
config: { get_resource: the_config }
input_values:
filename: demofile
content: 'Hello World'

软件部署代理

使用 SoftwareDeployment 方法时,需要在实例上安装编排代理(orchestration agents),以便收集和处理配置更改。通常,这些代理会安装在实例镜像中。根据需要,可以安装以下代理包:

  • python-heat-agent:支持 Shell 脚本。

  • python-heat-agent-ansible:支持 Ansible Playbooks。

  • python-heat-agent-puppet:支持 Puppet 清单。

下面这些代理工具的作用就是帮助在实例上应用软件配置更改。

1. os-collect-config

这个工具的作用是定期轮询编排(Orchestration)API,查看是否有针对服务器资源类型的更新后的资源元数据。简单来说,它就像是一个“侦查员”,不停地去检查有没有新的配置指令发过来。

  • 工作原理:它会定期检查,看看是否有新的配置需要应用到实例上。

  • 应用场景:当你通过 Heat(OpenStack 的编排服务)更新了某个实例的配置时,os-collect-config 会检测到这些变化。

2. os-refresh-config

这个工具的作用是刷新实例的配置。它会删除旧的配置,并用新的配置替换掉。你可以把它想象成一个“更新管理员”,负责把旧的东西清理掉,然后换上新的。

  • 工作原理:它会清理掉实例上旧的配置文件或设置,然后根据最新的元数据应用新的配置。

  • 应用场景:当你需要对实例进行配置更新时,os-refresh-config 会确保实例上的配置是最新的。

3. heat-config

这个工具的作用是安装一个 os-refresh-config 脚本,并调用适当的钩子(hooks)来执行配置。它就像是一个“启动器”,负责启动整个配置更新的过程。

  • 工作原理:它会安装必要的脚本,并确保这些脚本能够正确地触发配置更新。

  • 应用场景:当你需要在实例上启动一个配置更新流程时,heat-config 会负责初始化这个过程。

4. heat-config-hook

这个工具的作用是根据关联的组(group)使用正确的钩子来应用配置更改。它就像是一个“执行者”,负责具体实施配置更改。

  • 工作原理:它会根据配置中定义的组(比如使用 Shell 脚本、Ansible 或 Puppet)选择正确的钩子来应用配置。

  • 应用场景:当你定义了一个配置任务(比如运行一个 Shell 脚本),heat-config-hook 会确保这个任务被正确执行。

5. heat-config-notify

这个工具的作用是通知编排 API 配置部署是否成功。它就像是一个“汇报员”,负责把配置的结果反馈回去。

  • 工作原理:它会向 Heat API 发送一个信号,告知配置是否成功应用。

  • 应用场景:当配置更新完成后,heat-config-notify 会告诉 Heat:“嘿,配置已经完成了,这是结果!”

它们是如何协同工作的

想象一下,你有一个虚拟机实例,需要更新它的软件配置。以下是这些工具协同工作的过程:

  1. os-collect-config 检测到 Heat API 发来的更新指令。

  2. os-refresh-config 开始清理旧的配置,并准备应用新的配置。

  3. heat-config 启动整个更新流程,安装必要的脚本。

  4. heat-config-hook 根据配置类型(比如 Shell 脚本),选择正确的钩子来执行配置任务。

  5. heat-config-notify 在配置完成后,向 Heat API 发送信号,告知结果。