An introduction to OpenStack Heat

Fri 06 December 2013 by Lars Kellogg-Stedman Tags openstack heat neutron

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


Comments