opentack heat orchestration template (HOT)
可以类比 k8s
的 yaml
文件,k8s
通过 yaml
文件实现编排, heat
通过 HOT
yaml
文件实现 openstack
上的编排
train
版
heat_template_version: 2015-04-30
description: Simple template to deploy a single compute instance # 可选
resources:
my_instance:
type: OS::Nova::Server
properties:
key_name: my_key
image: F18-x86_64-cfntools
flavor: m1.small
有效的 HOT
版本参考:Heat template version
resources
段是必须的,并且至少包含一个资源定义,上面就是定义了一个名为my_instance
的资源
上面的例子会创建一个镜像为 F18-x86_64-cfntools
, 大小为 m1.small
, ssh key
指定为 my_key
的一个云主机
通常为了使模板更通用,会使用变量,而不是像上面的例子那样硬编码
heat_template_version: 2015-04-30
description: Simple template to deploy a single compute instance
parameters:
key_name:
type: string
label: Key Name
description: Name of key-pair to be used for compute instance
image_id:
type: string
label: Image ID
description: Image to be used for compute instance
instance_type:
type: string
label: Instance Type
description: Type of instance (flavor) to be used
default: m1.small # 指定默认值
resources:
my_instance:
type: OS::Nova::Server
properties:
key_name: { get_param: key_name }
image: { get_param: image_id }
flavor: { get_param: instance_type }
这次,定了三个变量,并且my_instance
里面使用变量,而不是硬编码, 并且变量可以指定默认值,这些变量通过 horizon
页面或者 openstack
命令行传进来
变量除了设置默认值,还可以设置 hidden
属性,这样用户在 heat
stack
页面看不到该属性的值,通常用来保护一些私密信息
parameters:
database_password:
type: string
label: Database Password
description: Password to be used for database
hidden: true
allowed_values
和 allowed_pattern
属性可以限制一个变量的取值
parameters:
instance_type:
type: string
label: Instance Type
description: Type of instance (flavor) to be used
constraints:
- allowed_values: [ m1.medium, m1.large, m1.xlarge ]
description: Value must be one of m1.medium, m1.large or m1.xlarge.
database_password:
type: string
label: Database Password
description: Password to be used for database
hidden: true
constraints:
- length: { min: 6, max: 8 }
description: Password length must be between 6 and 8 characters.
- allowed_pattern: "[a-zA-Z0-9]+"
description: Password must consist of characters and numbers only.
- allowed_pattern: "[A-Z]+[a-zA-Z0-9]*"
description: Password must start with an uppercase character.
除了自定义用户的输入参数外,还可以配置输出变量
outputs:
instance_ip:
description: The IP address of the deployed instance
value: { get_attr: [my_instance, first_address] }
heat_template_version: 2016-10-14
# heat_template_version: rocky
description:
# a description of the template
parameter_groups:
# a declaration of input parameter groups and order
parameters:
# declaration of input parameters
resources:
# declaration of template resources
outputs:
# declaration of output parameters
conditions:
# declaration of conditions
heat_template_version
: 必须,指定模板语法的版本,除了可以指定日期的格式,还可以直接指定 openstack
的版本, 如 rocky
description
: 可选,描述信息
parameter_groups
: 可选,指明输入参数该如何分组以及参数传入的顺序
parameters
: 可选, 定义输入参数
resources
: 必须,定义模板资源
outputs
: 可选,定义输出参数
conditions
: 可选,用来控制一个资源什么情况下可以被创建,或者资源某一属性什么情况下可以被定义
parameter_groups:
- label: -readable label of parameter group>
description: >
parameters:
- >
- >
parameters:
>:
type: | number | json | comma_delimited_list | boolean>
label: -readable name of the parameter>
description: >
default: >
hidden: | false>
constraints:
>
immutable: | false>
tags: >
type
示例 :
Type | Description | Examples |
---|---|---|
string | A literal string. | “String param” |
number | An integer or float. | “2”; “0.2” |
comma_delimited_list | An array of literal strings that are separated by commas. The total number of strings should be one more than the total number of commas. | [“one”, “two”]; “one, two”; Note: “one, two” returns [“one”, “two”] |
json | A JSON-formatted map or list. | {“key”: “value”} |
boolean | Boolean type value, which can be equal “t”, “true”, “on”, “y”, “yes”, or “1” for true value and “f”, “false”, “off”, “n”, “no”, or “0” for false value. | “on”; “n” |
定义一个输入参数,至少包含 type
parameters:
user_name:
type: string
label: User Name
description: User name to be configured for the application
port_number:
type: number
label: Port Number
description: Port number to be configured for the web server
label
和 description
是可选的,但是一般为了更好的描述都加上了这两个属性
heat
会为每个 stack
内置三个参数
OS::stack_name
: stack
名称OS::stack_id
: stack
id
OS::project_id
: 项目 id
这三个参数都可以通过 get_param
参数获取
resources:
>:
type: >
properties:
>: >
metadata:
>
depends_on: >
update_policy: >
deletion_policy: >
external_id: >
condition: >
depends_on
属性定义资源之间的依赖关系
resources:
server1:
type: OS::Nova::Server
depends_on: [ server2, server3 ]
server2:
type: OS::Nova::Server
depends_on: server3
server3:
type: OS::Nova::Server
outputs:
>:
description: >
value: >
condition: >
conditions:
>: {expression1}
>: {expression2}
...
示例
conditions:
cd1: True
cd2:
get_param: param1
cd3:
equals:
- get_param: param2
- yes
cd4:
not:
equals:
- get_param: param3
- yes
cd5:
and:
- equals:
- get_param: env_type
- prod
- not:
equals:
- get_param: zone
- beijing
cd6:
or:
- equals:
- get_param: zone
- shanghai
- equals:
- get_param: zone
- beijing
cd7:
not: cd4
cd8:
and:
- cd1
- cd2
cd9:
yaql:
expression: $.data.services.contains('heat')
data:
services:
get_param: ServiceNames
cd10:
contains:
- 'neutron'
- get_param: ServiceNames
condition
与 resource
关联
parameters:
env_type:
default: test
type: string
conditions:
create_prod_res: {equals : [{get_param: env_type}, "prod"]}
resources:
volume:
type: OS::Cinder::Volume
condition: create_prod_res
properties:
size: 1
condition
与 output
关联
outputs:
vol_size:
value: {get_attr: [my_volume, size]}
condition: create_prod_res
获取某一资源的某属性
get_attr:
- >
- >
- > (optional)
- > (optional)
- ...
示例
resources:
my_instance:
type: OS::Nova::Server
# ...
outputs:
instance_ip:
description: IP address of the deployed compute instance
value: { get_attr: [my_instance, first_address] }
instance_private_ip:
description: Private IP address of the deployed compute instance
value: { get_attr: [my_instance, networks, private, 0] }
从网络或者本地文件系统获取文件
resources:
my_instance:
type: OS::Nova::Server
properties:
# general properties ...
user_data:
get_file: my_instance_user_data.sh
my_other_instance:
type: OS::Nova::Server
properties:
# general properties ...
user_data:
get_file: http://example.com/my_other_instance_user_data.sh
get_file
支持两类 url
file:///path/to/my_instance_user_data.sh
http://example.com/my_other_instance_user_data.sh
获取输入参数的值
get_param:
- >
- > (optional)
- > (optional)
- ...
示例
parameters:
instance_type:
type: string
label: Instance Type
description: Instance type to be used.
server_data:
type: json
resources:
my_instance:
type: OS::Nova::Server
properties:
flavor: { get_param: instance_type}
metadata: { get_param: [ server_data, metadata ] }
key_name: { get_param: [ server_data, keys, 0 ] }
如果 instance_type
和 server_data
包含以下数据
{"instance_type": "m1.tiny",
{"server_data": {"metadata": {"foo": "bar"},
"keys": ["a_key","other_key"]}}}
最后 flavor
将被解析为 m1.tiny
metadata
将被解析为 {"foo": "bar"}
引用其他资源,通常会返回该资源对应的 ID
resources:
instance_port:
type: OS::Neutron::Port
properties: ...
instance:
type: OS::Nova::Server
properties:
...
networks:
port: { get_resource: instance_port }
该例子中,port
值最终会被解析为 instance_port
资源对应的 id
以指定分隔符连接一些列字符串
list_join:
- >
- >
- >
- ...
示例
list_join: [', ', ['one', 'two'], ['three', 'four']]
最终返回的值为 "one, two, three, four"
根据指定摘要算法获取给定值的摘要
digest:
- >
- >
示例
# from a user supplied parameter
pwd_hash: { digest: ['sha512', { get_param: raw_password }] }
相当于 for
循环
repeat:
template:
>
for_each:
: >
permutations: >
permutations
指明是否对所给的 list
进行排序之后再循环
示例
parameters:
ports:
type: comma_delimited_list
label: ports
default: "80,443,8080"
protocols:
type: comma_delimited_list
label: protocols
default: "tcp,tcp,tcp"
resources:
security_group:
type: OS::Neutron::SecurityGroup
properties:
name: web_server_security_group
rules:
repeat:
for_each:
<%port%>: { get_param: ports }
<%protocol%>: { get_param: protocols }
template:
protocol: <%protocol%>
port_range_min: <%port%>
permutations: false
字符串替换
str_replace:
template: >
params: >
示例
parameters:
DBRootPassword:
type: string
label: Database Password
description: Root password for MySQL
hidden: true
resources:
my_instance:
type: OS::Nova::Server
properties:
# general properties ...
user_data:
str_replace:
template: |
#!/bin/bash
echo "Hello world"
echo "Setting MySQL root password"
mysqladmin -u root password $db_rootpassword
# do more things ...
params:
$db_rootpassword: { get_param: DBRootPassword }
使用与 str_replace
一样,只是如果指定的 params
不在 template
中是,会报错
使用与 str_replace
一样,只是如果指定的 params
为空时,会报错
以指定分隔符分割字符串
str_split:
- ','
- string,to,split
示例
str_split: [',', 'string,to,split']
将被解析为 ['string', 'to', 'split']
str_split
有个可选索引来获取分割后列表指定索引对应的值
str_split: [',', 'string,to,split', 0]
将被解析为 'string'
合并两个 map
,后面的 map
会覆盖前面 map
中同名的成员
map_merge:
-
示例
map_merge: [{'k1': 'v1', 'k2': 'v2'}, {'k1': 'v2'}]
将被解析为 {'k1': 'v2', 'k2': 'v2'}
替换给定 map
的 key
或者 value
map_replace:
- >
- keys: >
values: >
示例
map_replace:
- k1: v1
k2: v2
- keys:
k1: K1
values:
v2: V2
将被解析为 {'K1': 'v1', 'k2': 'V2'}
比较两个值是否相等,相等就返回 true
,否则返回 false
equals: [value_1, value_2]
示例
equals: [{get_param: env_type}, 'prod']
类似 python
语言的三元表达式
if: [condition_name, value_if_true, value_if_false]
示例
conditions:
create_prod_res: {equals : [{get_param: env_type}, "prod"]}
resources:
test_server:
type: OS::Nova::Server
properties:
name: {if: ["create_prod_res", "s_prod", "s_test"]}
对 condition
结果取反
not: condition
示例
not:
equals:
- get_param: env_type
- prod
多个 condition
and
操作
and: [{condition_1}, {condition_2}, ... {condition_n}]
示例
and:
- equals:
- get_param: env_type
- prod
- not:
equals:
- get_param: zone
- beijing
多个 condition
or
操作
or: [{condition_1}, {condition_2}, ... {condition_n}]
示例
or:
- equals:
- get_param: env_type
- prod
- not:
equals:
- get_param: zone
- beijing
从 list
中过滤掉指定值
filter:
- >
- >
示例
parameters:
list_param:
type: comma_delimited_list
default: [1, 2, 3]
outputs:
output_list:
value:
filter:
- [3]
- {get_param: list_param}
最终结果为 [1, 2]
构造 url
make_url:
scheme: >
username: >
password: >
host: >
port: >
path: >
query:
: >
: >
fragment: >
示例
outputs:
server_url:
value:
make_url:
scheme: http
host: {get_attr: [server, networks, >, 0]}
port: 8080
path: /hello
query:
recipient: world
fragment: greeting
server_url
将被解析为 http://[
合并多个 list
list_concat:
- #1>
- #2>
- ...
示例
list_concat: [['v1', 'v2'], ['v3', 'v4']]
将被解析为 ['v1', 'v2', 'v3', 'v4']
合并多个 list
,并删除重复的元素,只保留一份
示例
list_concat_unique: [['v1', 'v2'], ['v2', 'v3']]
将被解析为 ['v1', 'v2', 'v3']
判断给定值是否在 list
中,如果存在返回 true
,否则返回 false
contains: [>, >]
示例
contains: ['v1', ['v1', 'v2', 'v3']]
结果为 true
在私有网络中创建云主机
resources:
instance:
type: OS::Nova::Server
properties:
flavor: m1.small
image: ubuntu-trusty-x86_64
networks:
- network: private
为云主机绑定指定网络端口
resources:
instance_port:
type: OS::Neutron::Port
properties:
network: private
fixed_ips:
- subnet_id: "private-subnet"
instance1:
type: OS::Nova::Server
properties:
flavor: m1.small
image: ubuntu-trusty-x86_64
networks:
- port: { get_resource: instance_port }
instance2:
type: OS::Nova::Server
properties:
flavor: m1.small
image: ubuntu-trusty-x86_64
networks:
- network: private
为接口绑定安全组
resources:
web_secgroup:
type: OS::Neutron::SecurityGroup
properties:
rules:
- protocol: tcp
remote_ip_prefix: 0.0.0.0/0
port_range_min: 80
port_range_max: 80
- protocol: tcp
remote_ip_prefix: 0.0.0.0/0
port_range_min: 443
port_range_max: 443
instance_port:
type: OS::Neutron::Port
properties:
network: private
security_groups:
- default
- { get_resource: web_secgroup }
fixed_ips:
- subnet_id: private-subnet
instance:
type: OS::Nova::Server
properties:
flavor: m1.small
image: ubuntu-trusty-x86_64
networks:
- port: { get_resource: instance_port }
为云主机绑定浮动ip
, 两种方法,分别通过 OS::Nova::FloatingIP
和OS::Neutron::FloatingIP
resources:
floating_ip:
type: OS::Nova::FloatingIP
properties:
pool: public
inst1:
type: OS::Nova::Server
properties:
flavor: m1.small
image: ubuntu-trusty-x86_64
networks:
- network: private
association:
type: OS::Nova::FloatingIPAssociation
properties:
floating_ip: { get_resource: floating_ip }
server_id: { get_resource: inst1 }
parameters:
net:
description: name of network used to launch instance.
type: string
default: private
resources:
inst1:
type: OS::Nova::Server
properties:
flavor: m1.small
image: ubuntu-trusty-x86_64
networks:
- network: {get_param: net}
floating_ip:
type: OS::Neutron::FloatingIP
properties:
floating_network: public
association:
type: OS::Neutron::FloatingIPAssociation
properties:
floatingip_id: { get_resource: floating_ip }
port_id: {get_attr: [inst1, addresses, {get_param: net}, 0, port]}
创建 ssh key
resources:
my_key:
type: OS::Nova::KeyPair
properties:
save_private_key: true
name: my_key
my_instance:
type: OS::Nova::Server
properties:
flavor: m1.small
image: ubuntu-trusty-x86_64
key_name: { get_resource: my_key }
outputs:
private_key:
description: Private key
value: { get_attr: [ my_key, private_key ] }
创建虚拟网络和子网
resources:
new_net:
type: OS::Neutron::Net
new_subnet:
type: OS::Neutron::Subnet
properties:
network_id: { get_resource: new_net }
cidr: "10.8.1.0/24"
dns_nameservers: [ "8.8.8.8", "8.8.4.4" ]
ip_version: 4
创建路由
resources:
router1:
type: OS::Neutron::Router
properties:
external_gateway_info: { network: public }
将网络加到路由中
resources:
subnet1_interface:
type: OS::Neutron::RouterInterface
properties:
router_id: { get_resource: router1 }
subnet: private-subnet
完整示例
resources:
internal_net:
type: OS::Neutron::Net
internal_subnet:
type: OS::Neutron::Subnet
properties:
network_id: { get_resource: internal_net }
cidr: "10.8.1.0/24"
dns_nameservers: [ "8.8.8.8", "8.8.4.4" ]
ip_version: 4
internal_router:
type: OS::Neutron::Router
properties:
external_gateway_info: { network: public }
internal_interface:
type: OS::Neutron::RouterInterface
properties:
router_id: { get_resource: internal_router }
subnet: { get_resource: internal_subnet }
创建空云盘
resources:
my_new_volume:
type: OS::Cinder::Volume
properties:
size: 10
为云主机挂载云盘
resources:
new_volume:
type: OS::Cinder::Volume
properties:
size: 1
new_instance:
type: OS::Nova::Server
properties:
flavor: m1.small
image: ubuntu-trusty-x86_64
volume_attachment:
type: OS::Cinder::VolumeAttachment
properties:
volume_id: { get_resource: new_volume }
instance_uuid: { get_resource: new_instance }
resources:
server_with_boot_script:
type: OS::Nova::Server
properties:
# flavor, image etc
user_data_format: RAW
user_data: |
#!/bin/bash
echo "Running boot script"
# ...
server_with_cloud_config:
type: OS::Nova::Server
properties:
# flavor, image etc
user_data_format: RAW
user_data: |
#cloud-config
final_message: "The system is finally up, after $UPTIME seconds"
boot script
可以抽象为 OS::Heat::SoftwareConfig
或者 OS::Heat::CloudConfig
类型的资源
resources:
boot_script:
type: OS::Heat::SoftwareConfig
properties:
group: ungrouped
config: |
#!/bin/bash
echo "Running boot script"
# ...
server_with_boot_script:
type: OS::Nova::Server
properties:
# flavor, image etc
user_data_format: SOFTWARE_CONFIG
user_data: {get_resource: boot_script}
parameters:
file_content:
type: string
description: The contents of the file /tmp/file
resources:
boot_config:
type: OS::Heat::CloudConfig
properties:
cloud_config:
write_files:
- path: /tmp/file
content: {get_param: file_content}
server_with_cloud_config:
type: OS::Nova::Server
properties:
# flavor, image etc
user_data_format: SOFTWARE_CONFIG
user_data: {get_resource: boot_config}
实际使用中,通常都会根据 boot script
执行的状态来决定是否继续创建其他的资源,这时候可以在脚本中通过 curl
发送指定 json
格式的数据,OS::Heat::WaitConditionHandle
通过获取该数据判断是否继续创建其他资源
json
格式示例
{
"status": "SUCCESS",
"reason": "The reason which will appear in the 'heat event-list' output",
"data": "Data to be used elsewhere in the template via get_attr",
"id": "Optional unique ID of signal"
}
示例
resources:
wait_condition:
type: OS::Heat::WaitCondition
properties:
handle: {get_resource: wait_handle}
# Note, count of 5 vs 6 is due to duplicate signal ID 5 sent below
count: 5 # 指定获取多少次成功的信号才继续其他操作
timeout: 300
wait_handle:
type: OS::Heat::WaitConditionHandle
the_server:
type: OS::Nova::Server
properties:
# flavor, image etc
user_data_format: RAW
user_data:
str_replace:
template: |
#!/bin/sh
# Below are some examples of the various ways signals
# can be sent to the Handle resource
# Simple success signal
wc_notify --data-binary '{"status": "SUCCESS"}'
# Or you optionally can specify any of the additional fields
wc_notify --data-binary '{"status": "SUCCESS", "reason": "signal2"}'
wc_notify --data-binary '{"status": "SUCCESS", "reason": "signal3", "data": "data3"}'
wc_notify --data-binary '{"status": "SUCCESS", "reason": "signal4", "id": "id4", "data": "data4"}'
# If you require control of the ID, you can pass it.
# The ID should be unique, unless you intend for duplicate
# signals to overwrite each other. The following two calls
# do the exact same thing, and will be treated as one signal
# (You can prove this by changing count above to 7)
wc_notify --data-binary '{"status": "SUCCESS", "id": "id5"}'
wc_notify --data-binary '{"status": "SUCCESS", "id": "id5"}'
# Example of sending a failure signal, optionally
# reason, id, and data can be specified as above
# wc_notify --data-binary '{"status": "FAILURE"}'
params:
wc_notify: { get_attr: [wait_handle, curl_cli] } # curl_cli 是 OS::Heat::WaitConditionHandle 构造的带认证信息的 curl 命令
outputs:
wc_data:
value: { get_attr: [wait_condition, data] }
# this would return the following json
# {"1": null, "2": null, "3": "data3", "id4": "data4", "id5": null}
wc_data_4:
value: { 'Fn::Select': ['id4', { get_attr: [wait_condition, data] }] }
# this would return "data4"
假设 my_nova.yaml
内容如下
heat_template_version: 2015-04-30
parameters:
key_name:
type: string
description: Name of a KeyPair
resources:
server:
type: OS::Nova::Server
properties:
key_name: {get_param: key_name}
flavor: m1.small
image: ubuntu-trusty-x86_64
可以在其他文件中使用该文件名作为资源类型, 并且资源的 properties
就是文件中定义的输入变量
heat_template_version: 2015-04-30
resources:
my_server:
type: my_nova.yaml
properties:
key_name: my_key
使用 resource_registry
来定义资源类型以及覆盖已有的资源类型
resource_registry:
"OS::Nova::Server": my_nova.yaml
上述示例使用 my_nova.yaml
覆盖了已有的 OS::Nova::Server
资源类型
my_nova.yaml
底层也用了OS::Nova::Server
, 那么如何获取到底层嵌套的OS::Nova::Server
的属性呢
heat_template_version: 2015-04-30
resources:
my_server:
type: my_nova.yaml
outputs:
test_out:
value: {get_attr: my_server, resource.server, first_address}
参考:OpenStack Resource Types
下面介绍几个常用的资源类型
并行创建多个同类资源, 并且可以使用 %index%
变量
resources:
my_indexed_group:
type: OS::Heat::ResourceGroup
properties:
count: 3
resource_def:
type: OS::Nova::Server
properties:
# create a unique name for each server
# using its index in the group
name: my_server_%index%
image: CentOS 6.5
flavor: 4GB Performance
该示例将会创建 3 个云主机,使用的镜像和大小都相同,但是云主机名称分别为 my_server_0
, my_server_1
, my_server_2
用来描述一些列配置的资源,之后可以通过 OS::Heat::SoftwareDeployment
调用这些资源
master_config:
type: OS::Heat::SoftwareConfig
properties:
group: script
config:
list_join:
- "\n"
-
- str_replace:
template: {get_file: ../../common/templates/kubernetes/fragments/write-heat-params-master.sh}
params:
"$INSTANCE_NAME": {get_param: name}
- get_file: ../../common/templates/kubernetes/fragments/make-cert.sh
- get_file: ../../common/templates/kubernetes/fragments/configure-etcd.sh
将配置部署到指定云主机,配置通常是一系列脚本,也就是在指定云主机上执行这些脚本
master_config_deployment:
type: OS::Heat::SoftwareDeployment
properties:
signal_transport: HEAT_SIGNAL
config: {get_resource: master_config}
server: {if: ["volume_based", {get_resource: kube-master-bfv}, {get_resource: kube-master}]}
actions: ['CREATE']
config
参数指定配置的 id
, 这里就是上面创建的 master_config
这个 OS::Heat::SoftwareDeployment
server
指定要应用这些配置的云主机
action
参数指定在云主机创建阶段应用这些配置
Template Guide