Bootstrapping software config

Brief summary

In the Generic software config tutorial, you learned how to use Heat’s generic software configuration mechanism to treat the configuration of compute instances the same way you treat any other resource in your template. This tutorial goes into more detail about bootstrapping a pristine image for use with software config as well as show how you can then create your own image with the necessary tools pre-installed for easier use in future stacks.

Pre-reading

Make sure you completed the previous tutorial, Generic software config first as we will be using that example template as a basis for this tutorial. You will also need a very basic understanding of Heat template composition and Environments.

Following along

You will probably want to clone this repository (https://github.com/rackerlabs/rs-heat-docs/) in order to easily follow along. Otherwise, you may need to modify some of the commands to point to the correct locations of various templates and environments. You may also have to modify the environment file to point to the correct bootconfig_all.yaml.

Modifying the example template

We have started by making a copy of the original example template and saving it as software_config_custom_image.yaml. We then removed resources from the resource section except for the parts that bootstrap the instance as well as the server itself. The following resources were removed from the template:

  • config

  • deployment

  • other_deployment

We revised the outputs section so that we can easily access the server’s IP address and root credentials (we will explain a little more in the next section):

outputs:

  server_ip:
    value: { get_attr: [ server, addresses, public, 0, addr ] }
    description: IP address of the server

  admin_password:
    value: { get_attr: [ admin_password, value ] }
    description: Root password to the server

We left the parameters, description, and heat_template_version sections as-is.

Modify the server

We added an OS::Heat::RandomString resource to generate a random root password for the instance so that we can log into the instance after the stack is complete. This is so that we can make some small modifications later if we want to create an image we can reuse the next time we want to apply software config to a server.

admin_password:
  type: OS::Heat::RandomString

Since we are not actually deploying any software config to the instance, we can just use cloud-init to do our installation. To do this, we will clean up some of this from the server resource by removing the software_config_transport property and changing the user_data_format to RAW. We will also pass in the generated password to the instance:

server:
  type: OS::Nova::Server
  properties:
    name: { get_param: "OS::stack_name" }
    admin_pass: { get_attr: [ admin_password, value ] }
    image: { get_param: image }
    flavor: 2 GB Performance
    user_data_format: RAW
    user_data: {get_attr: [boot_config, config]}

Your template should now look like:

heat_template_version: 2014-10-16
description: |
  A template that creates a server bootstrapped for use
  with Heat Software Config

parameters:

  image:
    type: string

resources:

  boot_config:
    type: Heat::InstallConfigAgent

  admin_password:
    type: OS::Heat::RandomString

  server:
    type: OS::Nova::Server
    properties:
      name: { get_param: "OS::stack_name" }
      admin_pass: { get_attr: [ admin_password, value ] }
      image: { get_param: image }
      flavor: 2 GB Performance
      user_data_format: RAW
      user_data: {get_attr: [boot_config, config]}

outputs:

  server_ip:
    value: { get_attr: [ server, addresses, public, 0, addr ] }
    description: IP address of the server

  admin_password:
    value: { get_attr: [ admin_password, value ] }
    description: Root password to the server

The Heat::InstallConfigAgent resource

You will notice that this resource has no real properties or other configuration. That is because we use the Environment and Template Resource features of Heat so that we can create several bootstrap configurations and use them for different base images as required.

The configuration template

First, look at the template that we will use to provide the underlying definition for the boot_config resource. Since this template is a bit large, it will not be included in its entirety here, but it can always be found in the templates directory of this repository as bootconfig_all.yaml.

In Generic Software Config, we used the same mechanism to bootstrap our clean instance using a template provided by the OpenStack Heat project. While that works well, the repository used is laid out for maximum reusability, so it can be hard to follow what is actually going on in the template. For this tutorial, we’ve “de-normalized” the bootstrap template to more easily explain the different sections and what they do.

Before we dive in, also note that there is nothing special about this template. Heat allows for and encourages template composition so that you can abstract and reuse parts of your application architecture. Having said that, we will not talk at all about basic things like descriptions or versions, but rather go over the resources and how they prepare the instance for use with Heat Software Config.

Install the basics

The first resource is the most complex and uses cloud-init to lay down the needed software, scripts, and configuration needed. Since there is a lot going on here, we will break down the actual cloud-config rather than the resource wrapping it.

First, we install the supporting software packages:

apt_upgrade: true
apt-sources:
- source: "ppa:ansible/ansible"
packages:
- python-pip
- git
- gcc
- python-dev
- libyaml-dev
- libssl-dev
- libffi-dev
- libxml2-dev
- libxslt1-dev
- python-apt
- ansible
- salt-minion

The next section writes several files. The first four are fairly generic and are to configure the base OpenStack agents os-collect-config, os-apply-config, and os-refresh-config. Note that these agents are actually installed in a separate section described later. You can read more about these agents in the reference sections. Their job is to coordinate the reading, running, and updating of the software configuration that will be sent via Heat.

Following are a few files that tell the generic OpenStack agents how to handle configurations received from Heat. The script written to /opt/stack/os-config-refresh/configure.d/55-heat-config is executed when a config is to be applied or refreshed. It is this script that decides which config handler agent to call to apply the configuration (shell script, Ansible, Puppet, Salt, and so forth).

The script written to /var/lib/heat-config/hooks/script is the default config handler agent that executes the configuration in the default group and assumes the configuration is a shell script.

The other available agent handlers are written similarly using the same root hooks directory (/var/lib/heat-config/hooks) and using the name of the config group handled as the file name. In our example, we have included handlers for using configurations in the default, Ansible, Salt, and Puppet config groups. You can customize this for your needs by removing handlers you do not want or adding additional ones from https://github.com/openstack/heat-templates/tree/master/hot/software-config/elements. Note that you may also need to add required packages to the packages or runcmd sections of the cloud-config if you add additional handlers.

The final section installs puppet for the puppet group handler and then runs the commands that bootstrap the generic OpenStack agents.

runcmd:
- wget https://apt.puppetlabs.com/puppetlabs-release-trusty.deb
- dpkg -i puppetlabs-release-trusty.deb
- apt-get update
- apt-get install puppet
- os-collect-config --one-time --debug
- cat /etc/os-collect-config.conf
- os-collect-config --one-time --debug

Install the generic agents

The actual generic OpenStack agents are installed using Python pip since there aren’t any reliable packages for them on the Ubuntu operating system.

install_agents:
  type: "OS::Heat::SoftwareConfig"
  properties:
    group: ungrouped
    config: |
      #!/bin/bash
      set -eux
      pip install os-collect-config os-apply-config os-refresh-config dib-utils

Configure the agents service

Next, we declare a config resource to create the service configuration (upstart or systemd) that will start the collection agent and ensure that it runs on boot:

start:
  type: "OS::Heat::SoftwareConfig"
  properties:
    group: ungrouped
    config: |
      #!/bin/bash
      set -eux

      if [[ `systemctl` =~ -\.mount ]]; then

          # if there is no system unit file, install a local unit
          if [ ! -f /usr/lib/systemd/system/os-collect-config.service ]; then

              cat <<EOF >/etc/systemd/system/os-collect-config.service
      [Unit]
      Description=Collect metadata and run hook commands.

      [Service]
      ExecStart=/usr/bin/os-collect-config
      Restart=on-failure

      [Install]
      WantedBy=multi-user.target
      EOF

      cat <<EOF >/etc/os-collect-config.conf
      [DEFAULT]
      command=os-refresh-config
      EOF
          fi

          # enable and start service to poll for deployment changes
          systemctl enable os-collect-config
          systemctl start --no-block os-collect-config
      elif [[ `/sbin/init --version` =~ upstart ]]; then
          if [ ! -f /etc/init/os-collect-config.conf ]; then

              cat <<EOF >/etc/init/os-collect-config.conf
      start on runlevel [2345]
      stop on runlevel [016]
      respawn

      # We're logging to syslog
      console none

      exec os-collect-config  2>&1 | logger -t os-collect-config
      EOF
          fi
          initctl reload-configuration
          service os-collect-config start
      else
          echo "ERROR: only systemd or upstart supported" 1>&2
          exit 1
      fi

Combine and expose the configs

Finally, the configurations are all combined into a single multi-part-mime so that they can be output as a single file for use in user-data:

install_config_agent:
  type: "OS::Heat::MultipartMime"
  properties:
    parts:
    - config: { get_resource: configure }
    - config: { get_resource: install_agents }
    - config: { get_resource: start }
outputs:
  config:
    value: { get_resource: install_config_agent }

The environment file

The environment file that we will send as part of our stack-create call is quite simple:

# Installs software-config agents for the Ubuntu operating system with pip install

parameters:
  image: Ubuntu 14.04 LTS (Trusty Tahr) (PVHVM)

resource_registry:
  "Heat::InstallConfigAgent": bootconfig_all.yaml

This sets the image parameter value to “Ubuntu 14.04 LTS (Trusty Tahr) (PVHVM)” and maps the resource namespace Heat::InstallConfigAgent to the template resource we created in the previous section. If you have used another file name or want to use the one included in this repository, be sure to change this mapping to point to the appropriate location.

Deploy the bootstrapped instance

All that is left to do is to deploy the template:

heat stack-create -f templates/software_config_custom_image.yaml -e templates/bootconfig.all.env.yaml sw_config_base

Wait for the stack to be CREATE_COMPLETE and you have a basic vm configured for use with Heat software config. You can stop here and modify this template to actually deploy software configurations to your server using OS::Heat::SoftwareConfig and OS::Heat::SoftwareDeployment using “clean” images. However you may prefer to continue directly to the next section, since it explains how you can use this bootstrapped instance to create your own image pre-configured for use with Heat software config. Also, future advanced tutorials, such as Using Ansible with Heat later in this guide, will make use of this pre-bootstrapped image, so that is another reason you may want to continue directly to the next section.

Custom Image

Remove cloud-init artifacts

In order for cloud-init to run on machines booted from the new image, we will need to remove some artifacts from the current vm left over from the initial bootstrapping. First, retrieve the root password from the stack:

heat output-show sw_config_base admin_password

Now, log into the server via ssh by issuing the following command:

ssh root@$(heat output-show sw_config_base server_ip)

Enter the password you retrieved previously.

Once logged into the server, run the following commands to remove the artifacts created by cloud-init when it bootstrapped this server:

  • rm /var/lib/cloud/instance

  • rm -rf /var/lib/cloud/instances/*

  • rm -rf /var/lib/cloud/data/*

  • rm /var/lib/cloud/sem/config_scripts_per_once.once

  • rm /var/log/cloud-init.log

  • rm /var/log/cloud-init-output.log

Snapshot your bootstrapped server

Now we can create an image of our server. First, log into the Rackspace Cloud control panel and under Orchestration, find the sw_config_base stack. Viewing the details, you should see the server listed in the Infrastructure section. Select that server to view its details. Under the Actions button, select Create an Image and name it “Ubuntu 14.04 LTS (HEAT)”.

Once this process is complete, you are all done!

Using your new image

We will make use of this new image in our future tutorials on using Heat software config, but in summary, you can omit using the Heat::InstallConfigAgent resource once you have this image. Instead, set the image property of any servers you want to configure this way to “Ubuntu 14.04 LTS (HEAT)” and the user_data_format property to “SOFTWARE_CONFIG” and it should just work!

Reference documentation