An introduction to OpenStack Heat
Heat is a template-based orchestration mechanism for use with OpenStack. With Heat, you can deploy collections of resources – networks, servers, storage, and more – all from a single, parameterized template.
In this article I will introduce Heat templates and the heat
command
line client.
Writing templates⌗
Because Heat began life as an analog of AWS CloudFormation, it supports the template formats used by the CloudFormation (CFN) tools. It also supports its own native template format, called HOT (“Heat Orchestration Templates”). In this article I will be using the HOT template syntax, which is fully specified on the OpenStack website.
NB: Heat is under active development, and there are a variety of discussions going on right now regarding the HOT specification. I will try to keep this post up-to-date as the spec evolves.
A HOT template is written using YAML syntax and has three major sections:
- parameters – these are input parameters that you provide when you deploy from the template.
- resources – these are things created by the template.
- outputs – these are output parameters generated by Heat and available to you via the API.
Parameters⌗
The parameters
section defines the list of available parameters.
For each parameter, you define a data type, an optional default value
(that will be used if you do not otherwise specify a value for the
parameter), an optional description, constraints to validate the data,
and so forth. The definition from the spec looks like this:
parameters:
<param name>:
type: <string | number | json | comma_delimited_list>
description: <description of the parameter>
default: <default value for parameter>
hidden: <true | false>
constraints:
<parameter constraints>
A simple example might look like this:
parameters:
flavor:
type: string
default: m1.small
constraints:
- allowed_values: [m1.nano, m1.tiny, m1.small, m1.large]
description: Value must be one of 'm1.tiny', 'm1.small' or 'm1.large'
This defines one parameter named flavor
with a default value of
m1.small
. Any value passed in when you deploy from this template
must match of one the values in the allowed_values
constraint.
Resources⌗
The resources
section of your template defines the items that will
be created by Heat when you deploy from your template. This may
include storage, networks, ports, routers, security groups, firewall
rules, or any other of the many available resources.
The definition of this section from the spec looks like this:
resources:
<resource ID>:
type: <resource type>
properties:
<property name>: <property value>
# more resource specific metadata
Here’s a simple example that would create a single server:
resources:
instance0:
type: OS::Nova::Server
properties:
name: instance0
image: cirros
flavor: m1.tiny
key_name: mykey
The complete list of resources and their available properties can be found in the documentation.
You’ll notice that the above example is static: it will always result
in an instance using the cirros
image and the m1.tiny
flavor.
This isn’t terribly useful, so let’s redefine this example assuming
that we have available the parameter
section from the previous
example:
resources:
instance0:
type: OS::Nova::Server
properties:
name: instance0
image: cirros
flavor: {get_param: flavor}
key_name: mykey
Here we are using the get_param intrinsic function to
retrieve an insert the value of the flavor
parameter.
Outputs⌗
The outputs
section of your template defines parameters that will be
available to you (via the API or command line client) after your stack
has been deployed. This may include things like this ip addresses
assigned to your instances. The outputs
section definition is:
outputs:
<parameter name>:
description: <description>
value: <parameter value>
In order to make the outputs
section useful, we’ll need another
template function, get_attr. Where get_param
accesses values from
your parameters
section, get_attr
accesses attributes of your
resources. For example:
outputs:
instance_ip:
value: {get_attr: [instance0, first_address]}
You will again want to refer to the list of resource types for a list of available attributes.
Putting it all together⌗
Using the above information, let’s put together a slightly more complete template. This example will:
- Deploy a single instance
- Assign it a floating ip address
- Ensure ssh access via an ssh key published to Nova
We’ll get the flavor name, image name, key name, and network information from user-provided parameters.
Since this is a complete example, we need to add the
heat_template_version
key to our template:
heat_template_version: 2013-05-23
And a description provides useful documentation:
description: >
A simple HOT template for demonstrating Heat.
We define parameters for the key name, flavor, and image, as well as network ids for address provisioning:
parameters:
key_name:
type: string
default: lars
description: Name of an existing key pair to use for the instance
flavor:
type: string
description: Instance type for the instance to be created
default: m1.small
constraints:
- allowed_values: [m1.nano, m1.tiny, m1.small, m1.large]
description: Value must be one of 'm1.tiny', 'm1.small' or 'm1.large'
image:
type: string
default: cirros
description: ID or name of the image to use for the instance
private_net_id:
type: string
description: Private network id
private_subnet_id:
type: string
description: Private subnet id
public_net_id:
type: string
description: Public network id
In the resources
section, we define a single instance of
OS::Nova::Server
, attach to it an instance of OS::Neutron::Port
,
and attach to that port an instance of OS::Neutron::FloatingIP
.
Note that use of the get_resource function here to refer to a
resource defined elsewhere in the template:
resources:
instance0:
type: OS::Nova::Server
properties:
name: instance0
image: { get_param: image }
flavor: { get_param: flavor }
key_name: { get_param: key_name }
networks:
- port: { get_resource: instance0_port0 }
instance0_port0:
type: OS::Neutron::Port
properties:
network_id: { get_param: private_net_id }
security_groups:
- default
fixed_ips:
- subnet_id: { get_param: private_subnet_id }
instance0_public:
type: OS::Neutron::FloatingIP
properties:
floating_network_id: { get_param: public_net_id }
port_id: { get_resource: instance0_port0 }
As outputs we provide the fixed and floating ip addresses assigned to our instance:
outputs:
instance0_private_ip:
description: IP address of instance0 in private network
value: { get_attr: [ instance0, first_address ] }
instance0_public_ip:
description: Floating IP address of instance0 in public network
value: { get_attr: [ instance0_public, floating_ip_address ] }
Filling in the blanks⌗
Now that we have a complete template, what do we do with it?
When you deploy from a template, you need to provide values for any
parameters required by the template (and you may also want to override
default values). You can do this using the -P
(aka --parameter
)
command line option, which takes a semicolon-delimited list of
name=value
pairs:
heat stack-create -P 'param1=value1;param2=value2' ...
While this works, it’s not terribly useful, especially as the
parameter list grows long. You can also provide parameters via an
environment file, which a YAML configuration file containing a
parameters
key (you can use environment files for other things, too,
but here we’re going to focus on their use for template parameters).
A sample file, equivalent to arguments to -P
in the above command
line, might look like:
parameters:
param1: value1
param2: value2
An environment file appropriate to our example template from the previous section might look like this:
parameters:
image: fedora-19-x86_64
flavor: m1.small
private_net_id: 99ab8ebf-ad2f-4a4b-9890-fee37cea4254
private_subnet_id: ed8ad5f5-4c47-4204-9ca3-1b3bc4de286d
public_net_id: 7e687cc3-8155-4ec2-bd11-ba741ecbf4f0
You would, of course, need to replace the network ids with ones appropriate to your environment.
Command line client⌗
With the template in a file called template.yml
and the parameters
in a file called environment.yml
, we could deploy an instance like
this:
heat stack-create -f template.yml \
-e environment.yml mystack
This would, assuming no errors, create a stack called mystack
. You
can view the status of your stacks with the stack-list
subcommand:
$ heat stack-list
+-------+------------+-----------------+----------------------+
| id | stack_name | stack_status | creation_time |
+-------+------------+-----------------+----------------------+
| 0...6 | mystack | CREATE_COMPLETE | 2013-12-06T21:37:32Z |
+-------+------------+-----------------+----------------------+
You can view detailed information about your stack – including the
values of your outputs – using the stack-show
subcommand:
$ heat stack-show mystack
(The output is a little too verbose to include here.)
If you want more convenient access to the values of your outputs,
you’re going to have to either make direct use of the Heat REST
API, or wait for my proposed change to the
python-heatclient package, which will add the output-list
and
output-get
subcommands:
$ heat output-list mystack
instance0_private_ip
instance0_public_ip
$ heat output-get mystack instance0_public_ip
192.168.122.203
Working with Neutron⌗
If you are using Heat in an environment that uses Neutron for
networking you may need to take a few additional steps. By default,
your virtual instances will not be associated with any security
groups, which means that they will have neither outbound or inbound
network connectivity. This is in contrast to instances started using
the nova boot
command, which will automatically be members of the
default
security group.
In order to provide your instances with appropriate network
connectivity, you will need to associate each OS::Neutron::Port
resource in your template with one or more security groups. For
example, the following configuration snippet would create a port
instance0_port0
and assign it to the default
and webserver
security groups:
instance0_port0:
type: OS::Neutron::Port
properties:
network_id: { get_param: private_net_id }
security_groups:
- default
- webserver
fixed_ips:
- subnet_id: { get_param: private_subnet_id }
For this to work, you will need to be running a (very) recent version of Heat. Until commit 902154c, Heat was unable to look up Neutron security groups by name. It worked fine if you specified security groups by UUID:
security_groups:
- 4296f3ff-9dc0-4b0b-a633-c30eacc8493d
- 8c49cd42-7c42-4a1f-af1d-492a0687fc12
See also⌗
- The Heat project maintains a repository of example templates.