DevOps:Puppet,Docker,and Kubernetes
上QQ阅读APP看书,第一时间看更新

Using run stages

A common requirement is to apply a certain group of resources before other groups (for example, installing a package repository or a custom Ruby version), or after others (for example, deploying an application once its dependencies are installed). Puppet's run stages feature allows you to do this.

By default, all resources in your manifest are applied in a single stage named main. If you need a resource to be applied before all others, you can assign it to a new run stage that is specified to come before main. Similarly, you could define a run stage that comes after main. In fact, you can define as many run stages as you need and tell Puppet which order they should be applied in.

In this example, we'll use stages to ensure one class is applied first and another last.

How to do it…

Here are the steps to create an example of using run stages:

  1. Create the file modules/admin/manifests/stages.pp with the following contents:
      class admin::stages {
        stage { 'first': before => Stage['main'] }
        stage { 'last': require => Stage['main'] }
        class me_first {
          notify { 'This will be done first': }
        }
        class me_last {
          notify { 'This will be done last': }
        }
        class { 'me_first':
          stage => 'first',
        }
        class { 'me_last':
          stage => 'last',
        }
      }
  2. Modify your site.pp file as follows:
      node 'cookbook' {
        class {'first_class': }
        class {'second_class': }
        include admin::stages
      }
  3. Run Puppet:
    root@cookbook:~# puppet agent -t
    Info: Applying configuration version '1411019225'
    Notice: This will be done first
    Notice: Second Class
    Notice: First Class
    Notice: This will be done last
    Notice: Finished catalog run in 0.43 seconds
    

How it works…

Let's examine this code in detail to see what's happening. First, we declare the run stages first and last, as follows:

  stage { 'first': before => Stage['main'] }
  stage { 'last': require => Stage['main'] }

For the first stage, we've specified that it should come before main. That is, every resource marked as being in the first stage will be applied before any resource in the main stage (the default stage).

The last stage requires the main stage, so no resource in the last stage can be applied until after every resource in the main stage.

We then declare some classes that we'll later assign to these run stages:

  class me_first {
    notify { 'This will be done first': }
  }
  class me_last {
    notify { 'This will be done last': }
  }

We can now put it all together and include these classes on the node, specifying the run stages for each as we do so:

  class { 'me_first': stage => 'first',
  }
  class { 'me_last': stage => 'last',
  }

Note that in the class declarations for me_first and me_last, we didn't have to specify that they take a stage parameter. The stage parameter is another metaparameter, which means it can be applied to any class or resource without having to be explicitly declared. When we ran puppet agent on our Puppet node, the notify from the me_first class was applied before the notifies from first_class and second_class. The notify from me_last was applied after the main stage, so it comes after the two notifies from first_class and second_class. If you run puppet agent multiple times, you will see that the notifies from first_class and second_class may not always appear in the same order but the me_first class will always come first and the me_last class will always come last.

There's more…

You can define as many run stages as you like, and set up any ordering for them. This can greatly simplify a complicated manifest that would otherwise require lots of explicit dependencies between resources. Beware of accidentally introducing dependency cycles, though; when you assign something to a run stage you're automatically making it dependent on everything in prior stages.

You may like to define your stages in the site.pp file instead, so that at the top level of the manifest, it's easy to see what stages are available.

Gary Larizza has written a helpful introduction to using run stages, with some real-world examples, on his website:

http://garylarizza.com/blog/2011/03/11/using-run-stages-with-puppet/

A caveat: many people don't like to use run stages, feeling that Puppet already provides sufficient resource ordering control, and that using run stages indiscriminately can make your code very hard to follow. The use of run stages should be kept to a minimum wherever possible. There are a few key examples where the use of stages creates less complexity. The most notable is when a resource modifies the system used to install packages on the system. It helps to have a package management stage that comes before the main stage. When packages are defined in the main (default) stage, your manifests can count on the updated package management configuration information being present. For instance, for a Yum-based system, you would create a yumrepos stage that comes before main. You can specify this dependency using chaining arrows as shown in the following code snippet:

  stage {'yumrepos': }
  Stage['yumrepos'] -> Stage['main']

We can then create a class that creates a Yum repository (yumrepo) resource and assign it to the yumrepos stage as follows:

  class {'yums': stage => 'yumrepos',
  }
  class yums {
    notify {'always before the rest': }
    yumrepo {'testrepo': baseurl => 'file:///var/yum', ensure  => 'present',
    }
  }

For Apt-based systems, the same example would be a stage where Apt sources are defined. The key with stages is to keep their definitions in your site.pp file where they are highly visible and to only use them sparingly where you can guarantee that you will not introduce dependency cycles.

See also

  • The Using tags recipe, in this chapter
  • The Drawing dependency graphs recipe in Chapter 10, Monitoring, Reporting, and Troubleshooting