www.howardism.org
Babblings of an aging geek in love with the Absurd, his family, and his own hubris.... oh, and Lisp.

Using Heat Templates

The following are some notes I use to introduce myself to OpenStack’s Heat project, and how to get things done with its Templates.

What is Heat?

According to their web page, Heat is the main project in the OpenStack Orchestration program, which attempts to create an interface, for managing the entire life-cycle of infrastructure resources within OpenStack clouds.

Heat is a way to automate (and orchestrate) the creation of cloud components. In other words, instead of calling the actions associated with OpenStack's CLI, you would define the things you want to be, and Heat makes it so.

Like a typical four-letter technology, Heat has lot of esoteric terms, and a reader may appreciate a mini-glossary before the deeper dives on this page:

Resource
A resource is any cloud object that Heat either creates, modifies or removes, for instance:
  • Simple things like flavors and volumes
  • Virtual machine instances
  • Complex networks, routers, subnets, security groups, etc.
Stack
collection of resources
Template
defines a Stack, e.g. a bunch of resources
Parameters
input to the template during deployment
Output
provides information back to user

Heat Templates

Heat Orchestration templates (HOT) are declarative configurations for the Heat orchestration tool, used to automate the creation of many OpenStack resources. These are described on the Heat Wiki page as:

templates in the form of text files that can be treated like code.

I don’t know about you, but statements of instructional code promiscuously covorting with declarative definitions, makes me nervous, but let’s timidly press forward and demonstrate templates with a simple example.

Create a Flavor Resource

A HOT is defined in YAML. A basic example to define a Nova flavor might look like this:

heat_template_version: 2016-04-08

description: Template to create a Nova custom flavor.

resources:
  nova_flavor:
    type: OS::Nova::Flavor
    properties:
      ephemeral: 1
      is_public: true
      name: xp.m1.tiny
      ram: 512
      vcpus: 1

Fairly obvious when you compare this to the parameters you’d give the openstack flavor create command line call.

Note that each HOT template must include the heat_template_version key with a valid version, e.g. 2015-10-15 (see Heat template version for a list of all versions).

Try it out:

openstack stack create -t xp-flavor.yaml my-flavor

The -t specifies the template, and you need to name the collection of resources you create with this call. This returns:

+---------------------+------------------------------------------+
| Field               | Value                                    |
+---------------------+------------------------------------------+
| id                  | 010a2f59-6064-4d59-86c1-8564adc348e6     |
| stack_name          | my-flavor                                |
| description         | Template to create a Nova custom flavor. |
| creation_time       | 2017-06-27T20:39:16                      |
| updated_time        | None                                     |
| stack_status        | CREATE_IN_PROGRESS                       |
| stack_status_reason | Stack CREATE started                     |
+---------------------+------------------------------------------+

Calling:

openstack stack show my-flavor

(or even just a openstack stack list) will probably show the status_status in a complete (or failed) state.

The nature of declarative definitions means that we are specifying what should be, not what should be done. If we were to re-do our request, nothing should be done if the flavor already exists, but if you request another create, we’ll get this error:

ERROR: The Stack (my-flavor) already exists.

However, in this case, you can re-issue all the update commands you want:

openstack stack update -t xp-flavor.yaml my-flavor

Which goes through any changes in the YAML file and updates the resource if anything changed, or just leaves it alone if it is the same:

+---------------------+-----------------------------------------------------+
| Field               | Value                                               |
+---------------------+-----------------------------------------------------+
| id                  | c2a2c1f4-9107-4c6f-b6b9-1d3a6a1d6a11                |
| stack_name          | my-flavor                                           |
| description         | Simple template to deploy a single compute instance |
| creation_time       | 2017-07-17T10:51:30                                 |
| updated_time        | 2017-07-17T10:52:18                                 |
| stack_status        | UPDATE_IN_PROGRESS                                  |
| stack_status_reason | Stack UPDATE started                                |
+---------------------+-----------------------------------------------------+

After running this update command a few times, I still only have a single flavor.

Create an Instance

Heat allows you to create an instance, and with the knowledge gained in the previous section, we should be able to not only define the instance, but also define each of the parts it may depend on:

heat_template_version: 2016-04-08

description: Template to create an tiny Instance

resources:
  my_flavor:
    type: OS::Nova::Flavor
    properties:
      ephemeral: 1
      is_public: true
      name: my-tiny.m1
      ram: 512
      vcpus: 1
  my_image:
    type: OS::Glance::Image
    properties:
      name: cirros
      container_format: bare
      disk_format: qcow2
      min_disk: 0
      min_ram: 0
      location: https://download.cirros-cloud.net/0.3.5/cirros-0.3.5-x86_64-disk.img
  my_key:
    type: OS::Nova::KeyPair
    properties:
      name: heat_key
      save_private_key: true
  my_instance:
    type: OS::Nova::Server
    properties:
      image: cirros
      flavor: xp.m1.tiny
      key_name: heat_key

How do I know things like OS::Glance::Image is what we need to specify the image? Well, you can query your system like:

openstack orchestration resource type list | grep Image

Next, how do you know what properties are available for a particular type? Let’s query the type with:

openstack orchestration resource type show OS::Glance::Image

As expected, the available properties are similar to if you were to run the image create command:

glance --os-image-api-version 2 image-create --architecture x86_64 \
 --protected False --min-disk 0 --disk-format qcow2 --min-ram 0 \
 --file ./cirros-0.3.5-x86_64-disk.img

Note: I still haven’t figured out the trick to get Heat to generate the key, and needed to create the key manually on the command line:

nova keypair-add heat_key > heat_key.priv

When you kick off this Heat template:

openstack stack create -t xp-instance.yaml my-instance

We get a table giving us the current status:

+---------------------+--------------------------------------+
| Field               | Value                                |
+---------------------+--------------------------------------+
| id                  | 3d209955-e2d5-4df9-95ac-a34971f129fb |
| stack_name          | my-instance                          |
| description         | Template to create an tiny Instance  |
| creation_time       | 2017-07-19T09:53:47                  |
| updated_time        | None                                 |
| stack_status        | CREATE_IN_PROGRESS                   |
| stack_status_reason |                                      |
+---------------------+--------------------------------------+

Check on the resulting build status with:

openstack stack list

Oh noes, something went wrong:

+--------------------------------------+-------------+-----------------+---------------------+---------------------+
| ID                                   | Stack Name  | Stack Status    | Creation Time       | Updated Time        |
+--------------------------------------+-------------+-----------------+---------------------+---------------------+
| 3d209955-e2d5-4df9-95ac-a34971f129fb | my-instance | CREATE_FAILED   | 2017-07-19T09:53:47 | None                |
| c2a2c1f4-9107-4c6f-b6b9-1d3a6a1d6a11 | my-flavor-1 | UPDATE_COMPLETE | 2017-07-17T10:51:30 | 2017-07-17T10:52:18 |
+--------------------------------------+-------------+-----------------+---------------------+---------------------+

Let’s investigate:

openstack stack show my-instance

Begins with these results:

+-----------------------+-------------------------------------------------------------------------------------------------+
| Field                 | Value                                                                                           |
+-----------------------+-------------------------------------------------------------------------------------------------+
| id                    | 5ba62fde-1b75-477e-ac47-59591abcdf39                                                            |
| stack_name            | my-instance                                                                                     |
| description           | Template to create an tiny Instance                                                             |
| creation_time         | 2017-07-19T10:05:59                                                                             |
| updated_time          | None                                                                                            |
| stack_status          | CREATE_FAILED                                                                                   |
| stack_status_reason   | Resource CREATE failed: HTTPBadRequest: resources.my_image: 400 Bad Request: External sources   |
|                       | are not supported: 'cirros-0.3.5-x86_64-disk.img' (HTTP 400)                                    |
| parameters            | OS::project_id: 5e3b4f4b513545dc80c0c63c0a85439e                                                |
|                       | OS::stack_id: 5ba62fde-1b75-477e-ac47-59591abcdf39                                              |
|                       | OS::stack_name: my-instance                                                                     |
...

Updates versus Create

One feature touched on above that I don’t like is that Heat isn’t completely declarative.

If it is the first time you are create all resources, things work fine, but if you are creating some of the resources in the template (like an image), but reusing other resources (like a flavor), you might get errors like:

Resource CREATE failed: Conflict: resources.my_flavor: Flavor with name my-tiny.m1 already
exists. (HTTP 409) (Request-ID: req-79250c9b-bb41-48d0-8eb4-65eb223b81b0)

How to work around this? One approach is to break up your template into parts for initially creating your reusable resources, like flavors and images, and then others for regular requestions, like instances and new users with their tenant projects.

Still more to come in this space.

Add a Name Parameter

Writing everything in the template limits its re-usability. Let’s suppose we wanted to create many similar instances, each with a different name.

Heat allows some values to be replaced by a parameter:

heat_template_version: 2016-04-08

description: Template to create an tiny Instance

parameters:
  name:
    type: string
    description: Name for the tiny instance

resources:
  my_instance:
    type: OS::Nova::Server
    properties:
      image: cirros
      flavor: xp.m1.tiny
      key_name: heat_key
      name:
        get_param: name

Try it out and let’s start two instances with the same template:

openstack stack create --parameter name=foobar -t xp-instance2.yaml my-instance-2
openstack stack create --parameter name=flingbaz -t xp-instance2.yaml my-instance-2

We can see the stacks we’ve created:

openstack stack list

Returns a table of information:

+--------------------------------------+---------------+-----------------+---------------------+---------------------+
| ID                                   | Stack Name    | Stack Status    | Creation Time       | Updated Time        |
+--------------------------------------+---------------+-----------------+---------------------+---------------------+
| c33baf4a-c5ac-4fd3-8219-f45c53fc833a | my-instance-3 | CREATE_COMPLETE | 2017-07-19T12:50:08 | None                |
| 98f15ca3-ac22-415e-931f-996282023b59 | my-instance-2 | CREATE_COMPLETE | 2017-07-19T10:43:16 | None                |
+--------------------------------------+---------------+-----------------+---------------------+---------------------+

Let’s see if they are actually created:

openstack server-list

Returns:

+--------------------------------------+----------+--------+----------+------------+
| ID                                   | Name     | Status | Networks | Image Name |
+--------------------------------------+----------+--------+----------+------------+
| c231fa58-4623-46a4-90bb-0fda05c06a2d | flingbaz | ACTIVE |          | cirros     |
| 059a54a2-8914-406b-bdb1-a77fd37f7e96 | foobar   | ACTIVE |          | cirros     |
+--------------------------------------+----------+--------+----------+------------+

Parameter Files

You can imagine a template having many substituted parameters. For instance, to create a virtual DNS, we may need to call our template with the following parameters:

openstack stack create --parameter domain=default-domain --parameter virtual_DNS_data_domain_name=cedev13.d001.pdx.eng --parameter virtual_DNS_data_record_order=round-robin --parameter fq_name=default-domain:vdns-cedev1-d001-pdx-eng --parameter virtual_DNS_data_default_ttl_seconds=5 --parameter name=vdns-demo -t virtual-vdns.yaml vdns-13

A bit wordy, eh?

Parameters like these could also be seen as a part of the definition, and instead of entering multiple --parameter entries, we could specify a single file that contains all the needed parameters in an environment file:

parameters:
  name: vdns-13
  domain: default-domain
  virtual_DNS_data_domain_name: cedev13.d001.pdx.eng
  virtual_DNS_data_record_order: round-robin
  fq_name: default-domain:vdns-cedev1-d001-pdx-eng
  virtual_DNS_data_default_ttl_seconds: 5

We would then combine that file with our template this way:

openstack stack create -e vdns-13.env -t virtual_dns.yaml vdns-13

So far, our example really demonstrates the purpose in wanting to use Heat, however, in Part two, I’ll demonstrate ways to get burned by Heat and minimizing the pain by testing the templates.