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)
可以类比 k8s
的 yaml
文件,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_values
和 allowed_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
label
和 description
是可选的,但是一般为了更好的描述都加上了这两个属性
内置的参数
heat
会为每个 stack
内置三个参数
OS::stack_name
:stack
名称OS::stack_id
:stack
id
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
condition
与 resource
关联
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
condition
与 output
关联
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_type
和 server_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
替换给定 map
的 key
或者 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::FloatingIP
和OS::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