openstack heat 编排模板指南

    • openstack 版本
    • HOT hello world
    • HOT 指南
      • 模板结构
      • Parameter groups section
      • Parameters section
        • 内置的参数
      • Resources section
        • Resource dependencies
      • Outputs section
      • Conditions section
      • 函数
        • get_attr
        • get_file
        • get_param
        • get_resource
        • list_join
        • digest
        • repeat
        • str_replace
        • str_replace_strict
        • str_replace_vstrict
        • str_split
        • map_merge
        • map_repalce
        • equals
        • if
        • not
        • and
        • or
        • filter
        • make_url
        • list_concat
        • list_concat_unique
        • contains
      • 云主机管理
        • 管理实例
        • 管理网络
        • 管理云盘
        • boot script
        • Signals and wait conditions
      • template 组合
        • 使用文件名作为资源类型
        • 定义资源类型
      • resource type
        • OS::Heat::ResourceGroup
        • OS::Heat::SoftwareConfig
        • OS::Heat::SoftwareDeployment
    • 参考

opentack heat orchestration template (HOT) 可以类比 k8syaml 文件,k8s 通过 yaml 文件实现编排, heat 通过 HOT yaml 文件实现 openstack 上的编排

openstack 版本

train

HOT hello world

heat_template_version: 2015-04-30description: Simple template to deploy a single compute instance # 可选resources:my_instance:type: OS::Nova::Serverproperties:key_name: my_keyimage: F18-x86_64-cfntoolsflavor: m1.small

有效的 HOT 版本参考:Heat template version

resources 段是必须的,并且至少包含一个资源定义,上面就是定义了一个名为my_instance的资源

上面的例子会创建一个镜像为 F18-x86_64-cfntools, 大小为 m1.small, ssh key 指定为 my_key 的一个云主机

通常为了使模板更通用,会使用变量,而不是像上面的例子那样硬编码

heat_template_version: 2015-04-30description: Simple template to deploy a single compute instanceparameters:key_name:type: stringlabel: Key Namedescription: Name of key-pair to be used for compute instanceimage_id:type: stringlabel: Image IDdescription: Image to be used for compute instanceinstance_type:type: stringlabel: Instance Typedescription: Type of instance (flavor) to be useddefault: m1.small # 指定默认值resources:my_instance:type: OS::Nova::Serverproperties: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: stringlabel: Database Passworddescription: Password to be used for databasehidden: true

allowed_valuesallowed_pattern 属性可以限制一个变量的取值

parameters:instance_type:type: stringlabel: Instance Typedescription: Type of instance (flavor) to be usedconstraints:- allowed_values: [ m1.medium, m1.large, m1.xlarge ]description: Value must be one of m1.medium, m1.large or m1.xlarge.database_password:type: stringlabel: Database Passworddescription: Password to be used for databasehidden: trueconstraints:- 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 instancevalue: { get_attr: [my_instance, first_address] }

HOT 指南

模板结构

heat_template_version: 2016-10-14
# heat_template_version: rockydescription:# a description of the templateparameter_groups:# a declaration of input parameter groups and orderparameters:# declaration of input parametersresources:# declaration of template resourcesoutputs:# declaration of output parametersconditions:# declaration of conditions

heat_template_version : 必须,指定模板语法的版本,除了可以指定日期的格式,还可以直接指定 openstack 的版本, 如 rocky

description : 可选,描述信息

parameter_groups : 可选,指明输入参数该如何分组以及参数传入的顺序

parameters : 可选, 定义输入参数

resources : 必须,定义模板资源

outputs : 可选,定义输出参数

conditions : 可选,用来控制一个资源什么情况下可以被创建,或者资源某一属性什么情况下可以被定义

Parameter groups section

parameter_groups:
- label: <human-readable label of parameter group>description: <description of the parameter group>parameters:- <param name>- <param name>

Parameters section

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>tags: <list of parameter categories>

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: stringlabel: User Namedescription: User name to be configured for the applicationport_number:type: numberlabel: Port Numberdescription: Port number to be configured for the web server

labeldescription 是可选的,但是一般为了更好的描述都加上了这两个属性

内置的参数

heat 会为每个 stack 内置三个参数

  1. OS::stack_name : stack 名称
  2. OS::stack_id : stack id
  3. OS::project_id : 项目 id

这三个参数都可以通过 get_param 参数获取

Resources section

resources:<resource ID>:type: <resource type>properties:<property name>: <property value>metadata:<resource specific metadata>depends_on: <resource ID or list of ID>update_policy: <update policy>deletion_policy: <deletion policy>external_id: <external resource ID>condition: <condition name or expression or boolean>

Resource dependencies

depends_on 属性定义资源之间的依赖关系

resources:server1:type: OS::Nova::Serverdepends_on: [ server2, server3 ]server2:type: OS::Nova::Serverdepends_on: server3server3:type: OS::Nova::Server

Outputs section

outputs:<parameter name>:description: <description>value: <parameter value>condition: <condition name or expression or boolean>

Conditions section

conditions:<condition name1>: {expression1}<condition name2>: {expression2}...

示例

conditions:cd1: Truecd2:get_param: param1cd3:equals:- get_param: param2- yescd4:not:equals:- get_param: param3- yescd5:and:- equals:- get_param: env_type- prod- not:equals:- get_param: zone- beijingcd6:or:- equals:- get_param: zone- shanghai- equals:- get_param: zone- beijingcd7:not: cd4cd8:and:- cd1- cd2cd9:yaql:expression: $.data.services.contains('heat')data:services:get_param: ServiceNamescd10:contains:- 'neutron'- get_param: ServiceNames

conditionresource 关联

parameters:env_type:default: testtype: string
conditions:create_prod_res: {equals : [{get_param: env_type}, "prod"]}
resources:volume:type: OS::Cinder::Volumecondition: create_prod_resproperties:size: 1

conditionoutput 关联

outputs:vol_size:value: {get_attr: [my_volume, size]}condition: create_prod_res

函数

get_attr

获取某一资源的某属性

get_attr:- <resource name>- <attribute name>- <key/index 1> (optional)- <key/index 2> (optional)- ...

示例

resources:my_instance:type: OS::Nova::Server# ...outputs:instance_ip:description: IP address of the deployed compute instancevalue: { get_attr: [my_instance, first_address] }instance_private_ip:description: Private IP address of the deployed compute instancevalue: { get_attr: [my_instance, networks, private, 0] }

get_file

从网络或者本地文件系统获取文件

resources:my_instance:type: OS::Nova::Serverproperties:# general properties ...user_data:get_file: my_instance_user_data.shmy_other_instance:type: OS::Nova::Serverproperties:# 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

获取输入参数的值

get_param:- <parameter name>- <key/index 1> (optional)- <key/index 2> (optional)- ...

示例

parameters:instance_type:type: stringlabel: Instance Typedescription: Instance type to be used.server_data:type: jsonresources:my_instance:type: OS::Nova::Serverproperties:flavor: { get_param: instance_type}metadata: { get_param: [ server_data, metadata ] }key_name: { get_param: [ server_data, keys, 0 ] }

如果 instance_typeserver_data 包含以下数据

{"instance_type": "m1.tiny",
{"server_data": {"metadata": {"foo": "bar"},"keys": ["a_key","other_key"]}}}

最后 flavor 将被解析为 m1.tiny

metadata 将被解析为 {"foo": "bar"}

get_resource

引用其他资源,通常会返回该资源对应的 ID

resources:instance_port:type: OS::Neutron::Portproperties: ...instance:type: OS::Nova::Serverproperties:...networks:port: { get_resource: instance_port }

该例子中,port 值最终会被解析为 instance_port 资源对应的 id

list_join

以指定分隔符连接一些列字符串

list_join:
- <delimiter>
- <list to join>
- <list to join>
- ...

示例

list_join: [', ', ['one', 'two'], ['three', 'four']]

最终返回的值为 "one, two, three, four"

digest

根据指定摘要算法获取给定值的摘要

digest:- <algorithm>- <value>

示例

# from a user supplied parameter
pwd_hash: { digest: ['sha512', { get_param: raw_password }] }

repeat

相当于 for 循环

repeat:template:<template>for_each:<var>: <list>permutations: <bool>

permutations 指明是否对所给的 list 进行排序之后再循环

示例

parameters:ports:type: comma_delimited_listlabel: portsdefault: "80,443,8080"protocols:type: comma_delimited_listlabel: protocolsdefault: "tcp,tcp,tcp"resources:security_group:type: OS::Neutron::SecurityGroupproperties:name: web_server_security_grouprules:repeat:for_each:<%port%>: { get_param: ports }<%protocol%>: { get_param: protocols }template:protocol: <%protocol%>port_range_min: <%port%>permutations: false

str_replace

字符串替换

str_replace:template: <template string>params: <parameter mappings>

示例

parameters:DBRootPassword:type: stringlabel: Database Passworddescription: Root password for MySQLhidden: trueresources:my_instance:type: OS::Nova::Serverproperties:# general properties ...user_data:str_replace:template: |#!/bin/bashecho "Hello world"echo "Setting MySQL root password"mysqladmin -u root password $db_rootpassword# do more things ...params:$db_rootpassword: { get_param: DBRootPassword }

str_replace_strict

使用与 str_replace 一样,只是如果指定的 params 不在 template 中是,会报错

str_replace_vstrict

使用与 str_replace 一样,只是如果指定的 params 为空时,会报错

str_split

以指定分隔符分割字符串

str_split:- ','- string,to,split

示例

str_split: [',', 'string,to,split']

将被解析为 ['string', 'to', 'split']

str_split 有个可选索引来获取分割后列表指定索引对应的值

str_split: [',', 'string,to,split', 0]

将被解析为 'string'

map_merge

合并两个 map,后面的 map 会覆盖前面 map 中同名的成员

map_merge:
- <map 1>
- <map 2>
- ...

示例

map_merge: [{'k1': 'v1', 'k2': 'v2'}, {'k1': 'v2'}]

将被解析为 {'k1': 'v2', 'k2': 'v2'}

map_repalce

替换给定 mapkey 或者 value

map_replace:
- <input map>
- keys: <map of key replacements>values: <map of value replacements>

示例

map_replace:
- k1: v1k2: v2
- keys:k1: K1values:v2: V2

将被解析为 {'K1': 'v1', 'k2': 'V2'}

equals

比较两个值是否相等,相等就返回 true,否则返回 false

equals: [value_1, value_2]

示例

equals: [{get_param: env_type}, 'prod']

if

类似 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::Serverproperties:name: {if: ["create_prod_res", "s_prod", "s_test"]}

not

condition 结果取反

not: condition

示例

not:equals:- get_param: env_type- prod

and

多个 condition and 操作

and: [{condition_1}, {condition_2}, ... {condition_n}]

示例

and:
- equals:- get_param: env_type- prod
- not:equals:- get_param: zone- beijing

or

多个 condition or 操作

or: [{condition_1}, {condition_2}, ... {condition_n}]

示例

or:
- equals:- get_param: env_type- prod
- not:equals:- get_param: zone- beijing

filter

list 中过滤掉指定值

filter:- <values>- <list>

示例

parameters:list_param:type: comma_delimited_listdefault: [1, 2, 3]outputs:output_list:value:filter:- [3]- {get_param: list_param}

最终结果为 [1, 2]

make_url

构造 url

make_url:scheme: <protocol>username: <username>password: <password>host: <hostname or IP>port: <port>path: <path>query:<key1>: <value1><key2>: <value2>fragment: <fragment>

示例

outputs:server_url:value:make_url:scheme: httphost: {get_attr: [server, networks, <network_name>, 0]}port: 8080path: /helloquery:recipient: worldfragment: greeting

server_url 将被解析为 http://[<server IP>]:8080/hello?recipient=world#greeting

list_concat

合并多个 list

list_concat:- <list #1>- <list #2>- ...

示例

list_concat: [['v1', 'v2'], ['v3', 'v4']]

将被解析为 ['v1', 'v2', 'v3', 'v4']

list_concat_unique

合并多个 list,并删除重复的元素,只保留一份

示例

list_concat_unique: [['v1', 'v2'], ['v2', 'v3']]

将被解析为 ['v1', 'v2', 'v3']

contains

判断给定值是否在 list 中,如果存在返回 true,否则返回 false

contains: [<value>, <sequence>]

示例

contains: ['v1', ['v1', 'v2', 'v3']]

结果为 true

云主机管理

管理实例

在私有网络中创建云主机

resources:instance:type: OS::Nova::Serverproperties:flavor: m1.smallimage: ubuntu-trusty-x86_64networks:- network: private

为云主机绑定指定网络端口

resources:instance_port:type: OS::Neutron::Portproperties:network: privatefixed_ips:- subnet_id: "private-subnet"instance1:type: OS::Nova::Serverproperties:flavor: m1.smallimage: ubuntu-trusty-x86_64networks:- port: { get_resource: instance_port }instance2:type: OS::Nova::Serverproperties:flavor: m1.smallimage: ubuntu-trusty-x86_64networks:- network: private

为接口绑定安全组

resources:web_secgroup:type: OS::Neutron::SecurityGroupproperties:rules:- protocol: tcpremote_ip_prefix: 0.0.0.0/0port_range_min: 80port_range_max: 80- protocol: tcpremote_ip_prefix: 0.0.0.0/0port_range_min: 443port_range_max: 443instance_port:type: OS::Neutron::Portproperties:network: privatesecurity_groups:- default- { get_resource: web_secgroup }fixed_ips:- subnet_id: private-subnetinstance:type: OS::Nova::Serverproperties:flavor: m1.smallimage: ubuntu-trusty-x86_64networks:- port: { get_resource: instance_port }

为云主机绑定浮动ip, 两种方法,分别通过 OS::Nova::FloatingIPOS::Neutron::FloatingIP

resources:floating_ip:type: OS::Nova::FloatingIPproperties:pool: publicinst1:type: OS::Nova::Serverproperties:flavor: m1.smallimage: ubuntu-trusty-x86_64networks:- network: privateassociation:type: OS::Nova::FloatingIPAssociationproperties:floating_ip: { get_resource: floating_ip }server_id: { get_resource: inst1 }
parameters:net:description: name of network used to launch instance.type: stringdefault: privateresources:inst1:type: OS::Nova::Serverproperties:flavor: m1.smallimage: ubuntu-trusty-x86_64networks:- network: {get_param: net}floating_ip:type: OS::Neutron::FloatingIPproperties:floating_network: publicassociation:type: OS::Neutron::FloatingIPAssociationproperties: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::KeyPairproperties:save_private_key: truename: my_keymy_instance:type: OS::Nova::Serverproperties:flavor: m1.smallimage: ubuntu-trusty-x86_64key_name: { get_resource: my_key }outputs:private_key:description: Private keyvalue: { get_attr: [ my_key, private_key ] }

管理网络

创建虚拟网络和子网

resources:new_net:type: OS::Neutron::Netnew_subnet:type: OS::Neutron::Subnetproperties: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::Routerproperties:external_gateway_info: { network: public }

将网络加到路由中

resources:subnet1_interface:type: OS::Neutron::RouterInterfaceproperties:router_id: { get_resource: router1 }subnet: private-subnet

完整示例

resources:internal_net:type: OS::Neutron::Netinternal_subnet:type: OS::Neutron::Subnetproperties:network_id: { get_resource: internal_net }cidr: "10.8.1.0/24"dns_nameservers: [ "8.8.8.8", "8.8.4.4" ]ip_version: 4internal_router:type: OS::Neutron::Routerproperties:external_gateway_info: { network: public }internal_interface:type: OS::Neutron::RouterInterfaceproperties:router_id: { get_resource: internal_router }subnet: { get_resource: internal_subnet }

管理云盘

创建空云盘

resources:my_new_volume:type: OS::Cinder::Volumeproperties:size: 10

为云主机挂载云盘

resources:new_volume:type: OS::Cinder::Volumeproperties:size: 1new_instance:type: OS::Nova::Serverproperties:flavor: m1.smallimage: ubuntu-trusty-x86_64volume_attachment:type: OS::Cinder::VolumeAttachmentproperties:volume_id: { get_resource: new_volume }instance_uuid: { get_resource: new_instance }

boot script

resources:server_with_boot_script:type: OS::Nova::Serverproperties:# flavor, image etcuser_data_format: RAWuser_data: |#!/bin/bashecho "Running boot script"# ...server_with_cloud_config:type: OS::Nova::Serverproperties:# flavor, image etcuser_data_format: RAWuser_data: |#cloud-configfinal_message: "The system is finally up, after $UPTIME seconds"

boot script 可以抽象为 OS::Heat::SoftwareConfig 或者 OS::Heat::CloudConfig 类型的资源

resources:boot_script:type: OS::Heat::SoftwareConfigproperties:group: ungroupedconfig: |#!/bin/bashecho "Running boot script"# ...server_with_boot_script:type: OS::Nova::Serverproperties:# flavor, image etcuser_data_format: SOFTWARE_CONFIGuser_data: {get_resource: boot_script}
parameters:file_content:type: stringdescription: The contents of the file /tmp/fileresources:boot_config:type: OS::Heat::CloudConfigproperties:cloud_config:write_files:- path: /tmp/filecontent: {get_param: file_content}server_with_cloud_config:type: OS::Nova::Serverproperties:# flavor, image etcuser_data_format: SOFTWARE_CONFIGuser_data: {get_resource: boot_config}

Signals and wait conditions

实际使用中,通常都会根据 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::WaitConditionproperties:handle: {get_resource: wait_handle}# Note, count of 5 vs 6 is due to duplicate signal ID 5 sent belowcount: 5 # 指定获取多少次成功的信号才继续其他操作timeout: 300wait_handle:type: OS::Heat::WaitConditionHandlethe_server:type: OS::Nova::Serverproperties:# flavor, image etcuser_data_format: RAWuser_data:str_replace:template: |#!/bin/sh# Below are some examples of the various ways signals# can be sent to the Handle resource# Simple success signalwc_notify --data-binary '{"status": "SUCCESS"}'# Or you optionally can specify any of the additional fieldswc_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"

template 组合

使用文件名作为资源类型

假设 my_nova.yaml 内容如下

heat_template_version: 2015-04-30parameters:key_name:type: stringdescription: Name of a KeyPairresources:server:type: OS::Nova::Serverproperties:key_name: {get_param: key_name}flavor: m1.smallimage: ubuntu-trusty-x86_64

可以在其他文件中使用该文件名作为资源类型, 并且资源的 properties 就是文件中定义的输入变量

heat_template_version: 2015-04-30resources:my_server:type: my_nova.yamlproperties: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-30resources:my_server:type: my_nova.yamloutputs:test_out:value: {get_attr: my_server, resource.server, first_address}

resource type

参考:OpenStack Resource Types

下面介绍几个常用的资源类型

OS::Heat::ResourceGroup

并行创建多个同类资源, 并且可以使用 %index% 变量

resources:my_indexed_group:type: OS::Heat::ResourceGroupproperties:count: 3resource_def:type: OS::Nova::Serverproperties:# create a unique name for each server# using its index in the groupname: my_server_%index%image: CentOS 6.5flavor: 4GB Performance

该示例将会创建 3 个云主机,使用的镜像和大小都相同,但是云主机名称分别为 my_server_0, my_server_1, my_server_2

OS::Heat::SoftwareConfig

用来描述一些列配置的资源,之后可以通过 OS::Heat::SoftwareDeployment 调用这些资源

master_config:type: OS::Heat::SoftwareConfigproperties:group: scriptconfig: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

OS::Heat::SoftwareDeployment

将配置部署到指定云主机,配置通常是一系列脚本,也就是在指定云主机上执行这些脚本

master_config_deployment:type: OS::Heat::SoftwareDeploymentproperties:signal_transport: HEAT_SIGNALconfig: {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