Book Image

DevOps: Puppet, Docker, and Kubernetes

By : Neependra Khare, Ke-Jou Carol Hsu, Hideto Saito, John Arundel, Hui-Chuan Chloe Lee, Thomas Uphill
Book Image

DevOps: Puppet, Docker, and Kubernetes

By: Neependra Khare, Ke-Jou Carol Hsu, Hideto Saito, John Arundel, Hui-Chuan Chloe Lee, Thomas Uphill

Overview of this book

With so many IT management and DevOps tools on the market, both open source and commercial, it’s difficult to know where to start. DevOps is incredibly powerful when implemented correctly, and here’s how to get it done.This Learning Path covers three broad areas: Puppet, Docker, and Kubernetes. This Learning Path is a large resource of recipes to ease your daily DevOps tasks. We begin with recipes that help you develop a complete and expert understanding of Puppet’s latest and most advanced features. Then we provide recipes that help you efficiently work with the Docker environment. Finally, we show you how to better manage containers in different scenarios in production using Kubernetes. This course is based on these books: 1. Puppet Cookbook, Third Edition 2. Docker Cookbook 3. Kubernetes Cookbook
Table of Contents (6 chapters)

When you need to have Puppet change a particular setting in a config file, it's common to simply deploy the whole file with Puppet. This isn't always possible, though; especially if it's a file that several different parts of your Puppet manifest may need to modify.

What would be useful is a simple recipe to add a line to a config file if it's not already present, for example, adding a module name to /etc/modules to tell the kernel to load that module at boot. There are several ways to do this, the simplest is to use the file_line type provided by the puppetlabs-stdlib module. In this example, we install the stdlib module and use this type to append a line to a text file.

We can define more instances of file_line and add more lines to the file; we can have multiple resources modifying a single file.

Modify the oneline.pp file and add another file_line resource:

Now apply the manifest again and verify whether the new line is appended to the file:

The file_line type also supports pattern matching and line removal as we'll show you in the following example:

Verify the contents of /tmp/cookbook before your Puppet run:

Apply the updated manifest:

Verify that the line has been removed and the goodbye line has been replaced:

Editing files with file_line works well if the file is unstructured. Structured files may have similar lines in different sections that have different meanings. In the next section, we'll show you how to deal with one particular type of structured file, a file using INI syntax.

Getting ready

Install the puppetlabs-stdlib module

We can define more instances of file_line and add more lines to the file; we can have multiple resources modifying a single file.

Modify the oneline.pp file and add another file_line resource:

Now apply the manifest again and verify whether the new line is appended to the file:

The file_line type also supports pattern matching and line removal as we'll show you in the following example:

Verify the contents of /tmp/cookbook before your Puppet run:

Apply the updated manifest:

Verify that the line has been removed and the goodbye line has been replaced:

Editing files with file_line works well if the file is unstructured. Structured files may have similar lines in different sections that have different meanings. In the next section, we'll show you how to deal with one particular type of structured file, a file using INI syntax.

How to do it...

Using the file_line resource type, we can ensure that a line exists or is absent in a config file. Using file_line we can quickly make edits to files without controlling the entire file.

Create a manifest named oneline.pp that will use file_line on a file in /tmp:
  file {'/tmp/cookbook':
    ensure => 'file',
  }
  file_line {'cookbook-hello':
    path    => '/tmp/cookbook',
    line    => 'Hello World!',
    require => File['/tmp/cookbook'],
  }
Run puppet apply on the oneline.pp manifest:
t@mylaptop ~/.puppet/manifests $ puppet apply oneline.pp 
Notice: Compiled catalog for mylaptop in environment production in 0.39 seconds
Notice: /Stage[main]/Main/File[/tmp/cookbook]/ensure: created
Notice: /Stage[main]/Main/File_line[cookbook-hello]/ensure: created
Notice: Finished catalog run in 0.02 seconds
Now verify that /tmp/cookbook contains the line we defined:
t@mylaptop ~/.puppet/manifests $ cat /tmp/cookbook
Hello World!

We can define more instances of file_line and add more lines to the file; we can have multiple resources modifying a single file.

Modify the oneline.pp file and add another file_line resource:

Now apply the manifest again and verify whether the new line is appended to the file:

The file_line type also supports pattern matching and line removal as we'll show you in the following example:

Verify the contents of /tmp/cookbook before your Puppet run:

Apply the updated manifest:

Verify that the line has been removed and the goodbye line has been replaced:

Editing files with file_line works well if the file is unstructured. Structured files may have similar lines in different sections that have different meanings. In the next section, we'll show you how to deal with one particular type of structured file, a file using INI syntax.

How it works…

We installed

We can define more instances of file_line and add more lines to the file; we can have multiple resources modifying a single file.

Modify the oneline.pp file and add another file_line resource:

Now apply the manifest again and verify whether the new line is appended to the file:

The file_line type also supports pattern matching and line removal as we'll show you in the following example:

Verify the contents of /tmp/cookbook before your Puppet run:

Apply the updated manifest:

Verify that the line has been removed and the goodbye line has been replaced:

Editing files with file_line works well if the file is unstructured. Structured files may have similar lines in different sections that have different meanings. In the next section, we'll show you how to deal with one particular type of structured file, a file using INI syntax.

There's more…

We can define more instances of file_line and add more lines to the file; we can have multiple resources modifying a single file.

Modify the oneline.pp file

and add another file_line resource:

Now apply the manifest again and verify whether the new line is appended to the file:

The file_line type also supports pattern matching and line removal as we'll show you in the following example:

Verify the contents of /tmp/cookbook before your Puppet run:

Apply the updated manifest:

Verify that the line has been removed and the goodbye line has been replaced:

Editing files with file_line works well if the file is unstructured. Structured files may have similar lines in different sections that have different meanings. In the next section, we'll show you how to deal with one particular type of structured file, a file using INI syntax.

INI files are used throughout many systems, Puppet uses INI syntax for the puppet.conf file. The puppetlabs-inifile module creates two types, ini_setting and ini_subsetting, which can be used to edit INI style files.

Getting ready

Install the
How to do it...

In this example, we will create a /tmp/server.conf file and ensure that the server_true setting is set in that file:

Create an initest.pp manifest with the following contents:
  ini_setting {'server_true':
    path    => '/tmp/server.conf',
    section => 'main',
    setting => 'server',
    value   => 'true',
  }
Apply the manifest:
t@mylaptop ~/.puppet/manifests $ puppet apply initest.pp 
Notice: Compiled catalog for burnaby in environment production in 0.14 seconds
Notice: /Stage[main]/Main/Ini_setting[server_true]/ensure: created
Notice: Finished catalog run in 0.02 seconds
Verify the contents of the /tmp/server.conf file:
t@mylaptop ~/.puppet/manifests $ cat /tmp/server.conf 

[main]
server = true
How it works...

The inifile module defines two types, ini_setting and ini_subsetting. Our manifest defines an ini_setting resource that creates a server = true setting within
There's more...

Using ini_subsetting, you can have

Sometimes it seems like every application has its own subtly different config file format, and writing regular expressions to parse and modify all of them can be a tiresome business.

Thankfully, Augeas is here to help. Augeas is a system that aims to simplify working with different config file formats by presenting them all as a simple tree of values. Puppet's Augeas support allows you to create augeas resources that can make the required config changes intelligently and automatically.

How to do it…

Follow these steps to create an example augeas resource:

Modify your base module as follows:
  class base {
    augeas { 'enable-ip-forwarding':
      incl    => '/etc/sysctl.conf',
      lens    => 'Sysctl.lns',
      changes => ['set net.ipv4.ip_forward 1'],
    }
  }
Run Puppet:
[root@cookbook ~]# puppet agent -t
Info: Applying configuration version '1412130479'
Notice: Augeas[enable-ip-forwarding](provider=augeas): 
--- /etc/sysctl.conf	2014-09-04 03:41:09.000000000 -0400
+++ /etc/sysctl.conf.augnew	2014-09-30 22:28:03.503000039 -0400
@@ -4,7 +4,7 @@
 # sysctl.conf(5) for more details.
 
 # Controls IP packet forwarding
-net.ipv4.ip_forward = 0
+net.ipv4.ip_forward = 1
 
 # Controls source route verification
 net.ipv4.conf.default.rp_filter = 1
Notice: /Stage[main]/Base/Augeas[enable-ip-forwarding]/returns: executed successfully
Notice: Finished catalog run in 2.27 seconds
Check whether the setting has been correctly applied:
[root@cookbook ~]# sysctl -p |grep ip_forward
net.ipv4.ip_forward = 1
How it works…

We declare an augeas
There's more…

I've chosen /etc/sysctl.conf as the example because it can contain a wide variety of kernel settings and you may want to change these settings for all sorts of different purposes and in different Puppet classes. You might want to enable IP forwarding, as in the example, for a router class but you might also want to tune the value of net.core.somaxconn for a load-balancer class.

This means that simply puppetizing the /etc/sysctl.conf file and distributing it as a text file won't work because you might have several different and conflicting versions depending on the setting you want to modify. Augeas is the right solution here because you can define augeas resources in different places, which modify the same file and they won't conflict.

For more information about using Puppet and

Sometimes you can't deploy a whole config file in one piece, yet making line by line edits isn't enough. Often, you need to build a config file from various bits of configuration managed by different classes. You may run into a situation where local information needs to be imported into the file as well. In this example, we'll build a config file using a local file as well as snippets defined in our manifests.

Getting ready

Although it's possible to create our own system to build files from pieces, we'll use the puppetlabs supported concat module. We will start by installing the concat module, in a previous example we installed the module to our local machine. In this example, we'll modify the Puppet server configuration and download the module to the Puppet server.

In your Git repository create an environment.conf file with the following contents:

modulepath = public:modules manifest = manifests/site.pp

Create the public directory and download the module into that directory as follows:

t@mylaptop ~/puppet $ mkdir public && cd public t@mylaptop ~/puppet/public $ puppet module install puppetlabs-concat --modulepath=. Notice: Preparing to install into /home/thomas/puppet/public ... Notice: Downloading from https://forgeapi.puppetlabs.com ... Notice: Installing -- do not interrupt ... /home/thomas/puppet/public └─┬ puppetlabs-concat (v1.1.1) └── puppetlabs-stdlib (v4.3.2)

Now add the new modules to our Git repository:

t@mylaptop ~/puppet/public $ git add . t@mylaptop ~/puppet/public $ git commit -m "adding concat" [production 50c6fca] adding concat 407 files changed, 20089 insertions(+)

Then push to our Git server:

t@mylaptop ~/puppet/public $ git push origin production
How to do it...

Now that we have
How it works...

The concat resource defines

While you can deploy config files easily with Puppet as simple text files, templates are much more powerful. A template file can do calculations, execute Ruby code, or reference the values of variables from your Puppet manifests. Anywhere you might deploy a text file using Puppet, you can use a template instead.

In the simplest case, a template can just be a static text file. More usefully, you can insert variables into it using the ERB (embedded Ruby) syntax. For example:

If the template is used in a context where the variable $name contains Zaphod Beeblebrox, the template will evaluate to:

This simple technique is very useful to generate lots of files that only differ in the values of one or two variables, for example, virtual hosts, and for inserting values into a script such as database names and passwords.

How to do it…

In this example, we'll use an ERB template to insert a password into a backup script:

Create the file modules/admin/templates/backup-mysql.sh.erb with the following contents:
#!/bin/sh
/usr/bin/mysqldump -uroot \ -p<%= @mysql_password %> \ --all-databases | \ /bin/gzip > /backup/mysql/all-databases.sql.gz
Modify your site.pp file as follows:
node 'cookbook' {
  $mysql_password = 'secret'
  file { '/usr/local/bin/backup-mysql':
    content => template('admin/backup-mysql.sh.erb'),
    mode    => '0755',
  }
}
Run Puppet:
[root@cookbook ~]# puppet agent -t
Info: Caching catalog for cookbook.example.com
Info: Applying configuration version '1412140971'
Notice: /Stage[main]/Main/Node[cookbook]/File[/usr/local/bin/backup-mysql]/ensure: defined content as '{md5}c12af56559ef36529975d568ff52dca5'
Notice: Finished catalog run in 0.31 seconds
Check whether Puppet has correctly inserted the password into the template:
[root@cookbook ~]# cat /usr/local/bin/backup-mysql 
#!/bin/sh
/usr/bin/mysqldump -uroot \
  -psecret \
  --all-databases | \
  /bin/gzip > /backup/mysql/all-databases.sql.gz
How it works…

Wherever a variable is There's more…

In the example, we only used one variable in the template, but you can have as many as you like. These can also be facts:

ServerName <%= @fqdn %>

Or Ruby expressions:

MAILTO=<%= @emails.join(',') %>

Or any Ruby code you want:

ServerAdmin <%= @sitedomain == 'coldcomfort.com' ? '[email protected]' : '[email protected]' %> See also

The Using GnuPG to encrypt secrets recipe in this chapter

In the previous example, we saw that you can use Ruby to interpolate different values in templates depending on the result of an expression. But you're not limited to getting one value at a time. You can put lots of them in a Puppet array and then have the template generate some content for each element of the array using a loop.

How to do it…

Follow these steps to build an example of iterating over arrays:

Modify your site.pp file as follows:
  node 'cookbook' {
    $ipaddresses = ['192.168.0.1', '158.43.128.1', '10.0.75.207' ]
    file { '/tmp/addresslist.txt':
      content => template('base/addresslist.erb')
    }
  }
Create the file modules/base/templates/addresslist.erb with the following contents:
<% @ipaddresses.each do |ip| -%>
IP address <%= ip %> is present
<% end -%>
Run Puppet:
[root@cookbook ~]# puppet agent -t
Info: Caching catalog for cookbook.example.com
Info: Applying configuration version '1412141917'
Notice: /Stage[main]/Main/Node[cookbook]/File[/tmp/addresslist.txt]/ensure: defined content as '{md5}073851229d7b2843830024afb2b3902d'
Notice: Finished catalog run in 0.30 seconds
Check the contents of the generated file:
[root@cookbook ~]# cat /tmp/addresslist.txt 
  IP address 192.168.0.1 is present.
  IP address 158.43.128.1 is present.
  IP address 10.0.75.207 is present.
How it works…

In the first line of the There's more…

Templates can also iterate See also

The Using ERB templates recipe in this chapter

EPP templates are a new feature in Puppet 3.5 and newer versions. EPP templates use a syntax similar to ERB templates but are not compiled through Ruby. Two new functions are defined to call EPP templates, epp, and inline_epp. These functions are the EPP equivalents of the ERB functions template and inline_template, respectively. The main difference with EPP templates is that variables are referenced using the Puppet notation, $variable instead of @variable.

How to do it...

Create an EPP template in ~/puppet/epp-test.epp with the following content:
This is <%= $message %>.
Create an epp.pp manifest, which uses the epp and inline_epp functions:
$message = "the message"
file {'/tmp/epp-test':
  content => epp('/home/thomas/puppet/epp-test.epp')
}
notify {inline_epp('Also prints <%= $message %>'):}
Apply the manifest making sure to use the future parser (the future parser is required for the epp and inline_epp functions to be defined):
t@mylaptop ~/puppet $ puppet apply epp.pp --parser=future
Notice: Compiled catalog for mylaptop in environment production in 1.03 seconds
Notice: /Stage[main]/Main/File[/tmp/epp-test]/ensure: defined content as '{md5}999ccc2507d79d50fae0775d69b63b8c'
Notice: Also prints the message
Verify that the template worked as intended:
t@mylaptop ~/puppet $ cat /tmp/epp-test 
This is the message.
How it works...

Using the future parser, the epp and inline_epp functions are defined. The main difference between EPP templates and ERB templates is that variables are referenced in the same way they are within Puppet manifests. There's more...

Both epp and inline_epp allow

We often need Puppet to have access to secret information, such as passwords or crypto keys, for it to configure systems properly. But how do you avoid putting such secrets directly into your Puppet code, where they're visible to anyone who has read access to your repository?

It's a common requirement for third-party developers and contractors to be able to make changes via Puppet, but they definitely shouldn't see any confidential information. Similarly, if you're using a distributed Puppet setup like that described in Chapter 2, Puppet Infrastructure, every machine has a copy of the whole repo, including secrets for other machines that it doesn't need and shouldn't have. How can we prevent this?

One answer is to encrypt the secrets using the GnuPG tool, so that any secret information in the Puppet repo is undecipherable (for all practical purposes) without the appropriate key. Then we distribute the key securely to the people or machines that need it.

First you'll need an encryption key, so follow these steps to generate one. If you already have a GnuPG key that you'd like to use, go on to the next section. To complete this section, you will need to install the gpg command:

  1. Use puppet resource to install gpg:
    # puppet resource package gnupg ensure=installed
    
  2. Run the following command. Answer the prompts as shown, except to substitute your name and e-mail address for mine. When prompted for a passphrase, just hit Enter:
    t@mylaptop ~/puppet $ gpg --gen-key
    gpg (GnuPG) 1.4.18; Copyright (C) 2014 Free Software Foundation, Inc.
    This is free software: you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law.
    Please select what kind of key you want:
       (1) RSA and RSA (default)
       (2) DSA and Elgamal
       (3) DSA (sign only)
       (4) RSA (sign only)
    Your selection? 1
    RSA keys may be between 1024 and 4096 bits long.
    What keysize do you want? (2048) 2048
    Requested keysize is 2048 bits
    Please specify how long the key should be valid.
             0 = key does not expire
          <n>  = key expires in n days
          <n>w = key expires in n weeks
          <n>m = key expires in n months
          <n>y = key expires in n years
    Key is valid for? (0) 0
    Key does not expire at all
    Is this correct? (y/N) y
    You need a user ID to identify your key; the software constructs the user ID
    from the Real Name, Comment and Email Address in this form:
        "Heinrich Heine (Der Dichter) <[email protected]>"
    
    Real name: Thomas Uphill
    Email address: [email protected]
    Comment: <enter>
    You selected this USER-ID:
        "Thomas Uphill <[email protected]>"
    
    Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
    You need a Passphrase to protect your secret key.
    

    Hit enter twice here to have an empty passphrase

    You don't want a passphrase - this is probably a *bad* idea!
    I will do it anyway.  You can change your passphrase at any time,
    using this program with the option "--edit-key".
    
    gpg: key F1C1EE49 marked as ultimately trusted
    public and secret key created and signed.
    
    gpg: checking the trustdb
    gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
    gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
    pub   2048R/F1C1EE49 2014-10-01
          Key fingerprint = 461A CB4C 397F 06A7 FB82  3BAD 63CF 50D8 F1C1 EE49
    uid                  Thomas Uphill <[email protected]>
    sub   2048R/E2440023 2014-10-01
    
  3. You may see a message like this if your system is not configured with a source of randomness:
    We need to generate a lot of random bytes. It is a good idea to perform
    some other action (type on the keyboard, move the mouse, utilize the
    disks) during the prime generation; this gives the random number
    generator a better chance to gain enough entropy.
    
  4. In this case, install and start a random number generator daemon such as haveged or rng-tools. Copy the gpg key you just created into the puppet user's account on your Puppet master:
    t@mylaptop ~ $ scp -r .gnupg [email protected]:
    gpg.conf                                      100% 7680     7.5KB/s   00:00    
    random_seed                                   100%  600     0.6KB/s   00:00    
    pubring.gpg                                   100% 1196     1.2KB/s   00:00    
    secring.gpg                                   100% 2498     2.4KB/s   00:00    
    trustdb.gpg                                   100% 1280     1.3KB/s   00:00
    

With your encryption key installed on the puppet user's keyring (the key generation process described in the previous section will do this for you), you're ready to set up Puppet to decrypt secrets.

You should use the secret function, or something like it, to protect any confidential data in your Puppet repo: passwords, AWS credentials, license keys, even other secret keys such as SSL host keys.

You may decide to use a single key, which you push to machines as they're built, perhaps as part of a bootstrap process like that described in the Bootstrapping Puppet with Bash recipe in Chapter 2, Puppet Infrastructure. For even greater security, you might like to create a new key for each machine, or group of machines, and encrypt a given secret only for the machines that need it.

For example, your web servers might need a certain secret that you don't want to be accessible on any other machine. You could create a key for web servers, and encrypt the data only for this key.

If you want to use encrypted data with Hiera, there is a GnuPG backend for Hiera available at http://www.craigdunn.org/2011/10/secret-variables-in-puppet-with-hiera-and-gpg/.

Getting ready

First you'll need an

encryption key, so follow these steps to generate one. If you already have a GnuPG key that you'd like to use, go on to the next section. To complete this section, you will need to install the gpg command:

  1. Use puppet resource to install gpg:
    # puppet resource package gnupg ensure=installed
    
  2. Run the following command. Answer the prompts as shown, except to substitute your name and e-mail address for mine. When prompted for a passphrase, just hit Enter:
    t@mylaptop ~/puppet $ gpg --gen-key
    gpg (GnuPG) 1.4.18; Copyright (C) 2014 Free Software Foundation, Inc.
    This is free software: you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law.
    Please select what kind of key you want:
       (1) RSA and RSA (default)
       (2) DSA and Elgamal
       (3) DSA (sign only)
       (4) RSA (sign only)
    Your selection? 1
    RSA keys may be between 1024 and 4096 bits long.
    What keysize do you want? (2048) 2048
    Requested keysize is 2048 bits
    Please specify how long the key should be valid.
             0 = key does not expire
          <n>  = key expires in n days
          <n>w = key expires in n weeks
          <n>m = key expires in n months
          <n>y = key expires in n years
    Key is valid for? (0) 0
    Key does not expire at all
    Is this correct? (y/N) y
    You need a user ID to identify your key; the software constructs the user ID
    from the Real Name, Comment and Email Address in this form:
        "Heinrich Heine (Der Dichter) <[email protected]>"
    
    Real name: Thomas Uphill
    Email address: [email protected]
    Comment: <enter>
    You selected this USER-ID:
        "Thomas Uphill <[email protected]>"
    
    Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
    You need a Passphrase to protect your secret key.
    

    Hit enter twice here to have an empty passphrase

    You don't want a passphrase - this is probably a *bad* idea!
    I will do it anyway.  You can change your passphrase at any time,
    using this program with the option "--edit-key".
    
    gpg: key F1C1EE49 marked as ultimately trusted
    public and secret key created and signed.
    
    gpg: checking the trustdb
    gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
    gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
    pub   2048R/F1C1EE49 2014-10-01
          Key fingerprint = 461A CB4C 397F 06A7 FB82  3BAD 63CF 50D8 F1C1 EE49
    uid                  Thomas Uphill <[email protected]>
    sub   2048R/E2440023 2014-10-01
    
  3. You may see a message like this if your system is not configured with a source of randomness:
    We need to generate a lot of random bytes. It is a good idea to perform
    some other action (type on the keyboard, move the mouse, utilize the
    disks) during the prime generation; this gives the random number
    generator a better chance to gain enough entropy.
    
  4. In this case, install and start a random number generator daemon such as haveged or rng-tools. Copy the gpg key you just created into the puppet user's account on your Puppet master:
    t@mylaptop ~ $ scp -r .gnupg [email protected]:
    gpg.conf                                      100% 7680     7.5KB/s   00:00    
    random_seed                                   100%  600     0.6KB/s   00:00    
    pubring.gpg                                   100% 1196     1.2KB/s   00:00    
    secring.gpg                                   100% 2498     2.4KB/s   00:00    
    trustdb.gpg                                   100% 1280     1.3KB/s   00:00
    

With your encryption key installed on the puppet user's keyring (the key generation process described in the previous section will do this for you), you're ready to set up Puppet to decrypt secrets.

You should use the secret function, or something like it, to protect any confidential data in your Puppet repo: passwords, AWS credentials, license keys, even other secret keys such as SSL host keys.

You may decide to use a single key, which you push to machines as they're built, perhaps as part of a bootstrap process like that described in the Bootstrapping Puppet with Bash recipe in Chapter 2, Puppet Infrastructure. For even greater security, you might like to create a new key for each machine, or group of machines, and encrypt a given secret only for the machines that need it.

For example, your web servers might need a certain secret that you don't want to be accessible on any other machine. You could create a key for web servers, and encrypt the data only for this key.

If you want to use encrypted data with Hiera, there is a GnuPG backend for Hiera available at http://www.craigdunn.org/2011/10/secret-variables-in-puppet-with-hiera-and-gpg/.

How to do it...

With your encryption key

installed on the puppet user's keyring (the key generation process described in the previous section will do this for you), you're ready to set up Puppet to decrypt secrets.

You should use the secret function, or something like it, to protect any confidential data in your Puppet repo: passwords, AWS credentials, license keys, even other secret keys such as SSL host keys.

You may decide to use a single key, which you push to machines as they're built, perhaps as part of a bootstrap process like that described in the Bootstrapping Puppet with Bash recipe in Chapter 2, Puppet Infrastructure. For even greater security, you might like to create a new key for each machine, or group of machines, and encrypt a given secret only for the machines that need it.

For example, your web servers might need a certain secret that you don't want to be accessible on any other machine. You could create a key for web servers, and encrypt the data only for this key.

If you want to use encrypted data with Hiera, there is a GnuPG backend for Hiera available at http://www.craigdunn.org/2011/10/secret-variables-in-puppet-with-hiera-and-gpg/.

How it works...

First, we've created a

You should use the secret function, or something like it, to protect any confidential data in your Puppet repo: passwords, AWS credentials, license keys, even other secret keys such as SSL host keys.

You may decide to use a single key, which you push to machines as they're built, perhaps as part of a bootstrap process like that described in the Bootstrapping Puppet with Bash recipe in Chapter 2, Puppet Infrastructure. For even greater security, you might like to create a new key for each machine, or group of machines, and encrypt a given secret only for the machines that need it.

For example, your web servers might need a certain secret that you don't want to be accessible on any other machine. You could create a key for web servers, and encrypt the data only for this key.

If you want to use encrypted data with Hiera, there is a GnuPG backend for Hiera available at http://www.craigdunn.org/2011/10/secret-variables-in-puppet-with-hiera-and-gpg/.

There's more...

You should use the

secret function, or something like it, to protect any confidential data in your Puppet repo: passwords, AWS credentials, license keys, even other secret keys such as SSL host keys.

You may decide to use a single key, which you push to machines as they're built, perhaps as part of a bootstrap process like that described in the Bootstrapping Puppet with Bash recipe in Chapter 2, Puppet Infrastructure. For even greater security, you might like to create a new key for each machine, or group of machines, and encrypt a given secret only for the machines that need it.

For example, your web servers might need a certain secret that you don't want to be accessible on any other machine. You could create a key for web servers, and encrypt the data only for this key.

If you want to use encrypted data with Hiera, there is a GnuPG backend for Hiera available at http://www.craigdunn.org/2011/10/secret-variables-in-puppet-with-hiera-and-gpg/.

See also

The Configuring Hiera recipe in
  • Chapter 2, Puppet Infrastructure
  • The Storing secret data with hiera-gpg recipe in Chapter 2, Puppet Infrastructure

Most often you will want to install packages from the main distribution repo, so a simple package resource will do:

Sometimes, you need a package that is only found in a third-party repository (an Ubuntu PPA, for example), or it might be that you need a more recent version of a package than that provided by the distribution, which is available from a third party.

On a manually-administered machine, you would normally do this by adding the repo source configuration to /etc/apt/sources.list.d (and, if necessary, a gpg key for the repo) before installing the package. We can automate this process easily with Puppet.

In this example, we'll use the popular Percona APT repo (Percona is a MySQL consulting firm who maintain and release their own specialized version of MySQL, more information is available at http://www.percona.com/software/repositories):

  1. Create the file modules/admin/manifests/percona_repo.pp with the following contents:
    # Install Percona APT repo
    class admin::percona_repo {
      exec { 'add-percona-apt-key':
        unless  => '/usr/bin/apt-key list |grep percona',
        command => '/usr/bin/gpg --keyserver hkp://keys.gnupg.net --recv-keys 1C4CBDCDCD2EFD2A && /usr/bin/gpg -a --export CD2EFD2A | apt-key add -',
        notify  => Exec['percona-apt-update'],
      }
    
      exec { 'percona-apt-update':
        command     => '/usr/bin/apt-get update',
        require     => [File['/etc/apt/sources.list.d/percona.list'],
    File['/etc/apt/preferences.d/00percona.pref']],
        refreshonly => true,
      }
    
      file { '/etc/apt/sources.list.d/percona.list':
        content => 'deb http://repo.percona.com/apt wheezy main',
        notify  => Exec['percona-apt-update'],
      }
    
      file { '/etc/apt/preferences.d/00percona.pref':
        content => "Package: *\nPin: release o=Percona
        Development Team\nPin-Priority: 1001",
        notify  => Exec['percona-apt-update'],
      }
    }
  2. Modify your site.pp file as follows:
    node 'cookbook' {
      include admin::percona_repo
    
      package { 'percona-server-server-5.5':
        ensure  => installed,
        require => Class['admin::percona_repo'],
      }
    }
  3. Run Puppet:
    root@cookbook-deb:~# puppet agent -t
    Info: Caching catalog for cookbook-deb
    Notice: /Stage[main]/Admin::Percona_repo/Exec[add-percona-apt-key]/returns: executed successfully
    Info: /Stage[main]/Admin::Percona_repo/Exec[add-percona-apt-key]: Scheduling refresh of Exec[percona-apt-update]
    Notice: /Stage[main]/Admin::Percona_repo/File[/etc/apt/sources.list.d/percona.list]/ensure: defined content as '{md5}b8d479374497255804ffbf0a7bcdf6c2'
    Info: /Stage[main]/Admin::Percona_repo/File[/etc/apt/sources.list.d/percona.list]: Scheduling refresh of Exec[percona-apt-update]
    Notice: /Stage[main]/Admin::Percona_repo/File[/etc/apt/preferences.d/00percona.pref]/ensure: defined content as '{md5}1d8ca6c1e752308a9bd3018713e2d1ad'
    Info: /Stage[main]/Admin::Percona_repo/File[/etc/apt/preferences.d/00percona.pref]: Scheduling refresh of Exec[percona-apt-update]
    Notice: /Stage[main]/Admin::Percona_repo/Exec[percona-apt-update]: Triggered 'refresh' from 3 events
    

In order to install any Percona package, we first need to have the repository configuration installed on the machine. This is why the percona-server-server-5.5 package (Percona's version of the standard MySQL server) requires the admin::percona_repo class:

So, what does the admin::percona_repo class do? It:

First of all, we install the APT key:

The unless parameter checks the output of apt-key list to make sure that the Percona key is not already installed, in which case we need not do anything. Assuming it isn't, the command runs:

This command retrieves the key from the GnuPG keyserver, exports it in the ASCII format, and pipes this into the apt-key add command, which adds it to the system keyring. You can use a similar pattern for most third-party repos that require an APT signing key.

Having installed the key, we add the repo configuration:

Then run apt-get update to update the system's APT cache with the metadata from the new repo:

Finally, we configure the APT pin priority for the repo:

This ensures that packages installed from the Percona repo will never be superseded by packages from somewhere else (the main Ubuntu distro, for example). Otherwise, you could end up with broken dependencies and be unable to install the Percona packages automatically.

How to do it…

In this example, we'll use

the popular Percona APT repo (Percona is a MySQL consulting firm who maintain and release their own specialized version of MySQL, more information is available at http://www.percona.com/software/repositories):

  1. Create the file modules/admin/manifests/percona_repo.pp with the following contents:
    # Install Percona APT repo
    class admin::percona_repo {
      exec { 'add-percona-apt-key':
        unless  => '/usr/bin/apt-key list |grep percona',
        command => '/usr/bin/gpg --keyserver hkp://keys.gnupg.net --recv-keys 1C4CBDCDCD2EFD2A && /usr/bin/gpg -a --export CD2EFD2A | apt-key add -',
        notify  => Exec['percona-apt-update'],
      }
    
      exec { 'percona-apt-update':
        command     => '/usr/bin/apt-get update',
        require     => [File['/etc/apt/sources.list.d/percona.list'],
    File['/etc/apt/preferences.d/00percona.pref']],
        refreshonly => true,
      }
    
      file { '/etc/apt/sources.list.d/percona.list':
        content => 'deb http://repo.percona.com/apt wheezy main',
        notify  => Exec['percona-apt-update'],
      }
    
      file { '/etc/apt/preferences.d/00percona.pref':
        content => "Package: *\nPin: release o=Percona
        Development Team\nPin-Priority: 1001",
        notify  => Exec['percona-apt-update'],
      }
    }
  2. Modify your site.pp file as follows:
    node 'cookbook' {
      include admin::percona_repo
    
      package { 'percona-server-server-5.5':
        ensure  => installed,
        require => Class['admin::percona_repo'],
      }
    }
  3. Run Puppet:
    root@cookbook-deb:~# puppet agent -t
    Info: Caching catalog for cookbook-deb
    Notice: /Stage[main]/Admin::Percona_repo/Exec[add-percona-apt-key]/returns: executed successfully
    Info: /Stage[main]/Admin::Percona_repo/Exec[add-percona-apt-key]: Scheduling refresh of Exec[percona-apt-update]
    Notice: /Stage[main]/Admin::Percona_repo/File[/etc/apt/sources.list.d/percona.list]/ensure: defined content as '{md5}b8d479374497255804ffbf0a7bcdf6c2'
    Info: /Stage[main]/Admin::Percona_repo/File[/etc/apt/sources.list.d/percona.list]: Scheduling refresh of Exec[percona-apt-update]
    Notice: /Stage[main]/Admin::Percona_repo/File[/etc/apt/preferences.d/00percona.pref]/ensure: defined content as '{md5}1d8ca6c1e752308a9bd3018713e2d1ad'
    Info: /Stage[main]/Admin::Percona_repo/File[/etc/apt/preferences.d/00percona.pref]: Scheduling refresh of Exec[percona-apt-update]
    Notice: /Stage[main]/Admin::Percona_repo/Exec[percona-apt-update]: Triggered 'refresh' from 3 events
    

In order to install any Percona package, we first need to have the repository configuration installed on the machine. This is why the percona-server-server-5.5 package (Percona's version of the standard MySQL server) requires the admin::percona_repo class:

So, what does the admin::percona_repo class do? It:

First of all, we install the APT key:

The unless parameter checks the output of apt-key list to make sure that the Percona key is not already installed, in which case we need not do anything. Assuming it isn't, the command runs:

This command retrieves the key from the GnuPG keyserver, exports it in the ASCII format, and pipes this into the apt-key add command, which adds it to the system keyring. You can use a similar pattern for most third-party repos that require an APT signing key.

Having installed the key, we add the repo configuration:

Then run apt-get update to update the system's APT cache with the metadata from the new repo:

Finally, we configure the APT pin priority for the repo:

This ensures that packages installed from the Percona repo will never be superseded by packages from somewhere else (the main Ubuntu distro, for example). Otherwise, you could end up with broken dependencies and be unable to install the Percona packages automatically.

How it works…

In order to install any

Percona package, we first need to have the repository configuration installed on the machine. This is why the percona-server-server-5.5 package (Percona's version of the standard MySQL server) requires the admin::percona_repo class:

So, what does the admin::percona_repo class do? It:

First of all, we install the APT key:

The unless parameter checks the output of apt-key list to make sure that the Percona key is not already installed, in which case we need not do anything. Assuming it isn't, the command runs:

This command retrieves the key from the GnuPG keyserver, exports it in the ASCII format, and pipes this into the apt-key add command, which adds it to the system keyring. You can use a similar pattern for most third-party repos that require an APT signing key.

Having installed the key, we add the repo configuration:

Then run apt-get update to update the system's APT cache with the metadata from the new repo:

Finally, we configure the APT pin priority for the repo:

This ensures that packages installed from the Percona repo will never be superseded by packages from somewhere else (the main Ubuntu distro, for example). Otherwise, you could end up with broken dependencies and be unable to install the Percona packages automatically.

There's more...

The APT package framework is specific to the Debian and Ubuntu systems. There is a forge module

Package version numbers are odd things. They look like decimal numbers, but they're not: a version number is often in the form of 2.6.4, for example. If you need to compare one version number with another, you can't do a straightforward string comparison: 2.6.4 would be interpreted as greater than 2.6.12. And a numeric comparison won't work because they're not valid numbers.

Puppet's versioncmp function comes to the rescue. If you pass two things that look like version numbers, it will compare them and return a value indicating which is greater:

returns:

How to do it…

Here's an example using the versioncmp function:

Modify your site.pp file as follows:
node 'cookbook' {
  $app_version = '1.2.2'
  $min_version = '1.2.10'
	
  if versioncmp($app_version, $min_version) >= 0 {
    notify { 'Version OK': }
  } else {
    notify { 'Upgrade needed': }
  }
}
Run Puppet:
[root@cookbook ~]# puppet agent -t
Info: Caching catalog for cookbook.example.com
Notice: Upgrade needed
Now change the value of $app_version:
$app_version = '1.2.14'
Run How it works…

We've specified that the minimum acceptable version ($min_version) is 1.2.10. So, in the first example, we want to compare it with $app_version of 1.2.2. A simple alphabetic comparison of these two strings (in Ruby, for example) would give the wrong result, but versioncmp correctly determines that 1.2.2 is less than 1.2.10 and alerts us that we need to upgrade.

In the second example, $app_version is now 1.2.14, which versioncmp correctly recognizes as greater than $min_version and so we get the message Version OK.