Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
DevOps: Puppet, Docker, and Kubernetes

You're reading from   DevOps: Puppet, Docker, and Kubernetes Practical recipes to make the most of DevOps with powerful tools

Arrow left icon
Product type Course
Published in Mar 2017
Publisher Packt
ISBN-13 9781788297615
Length 925 pages
Edition 1st Edition
Tools
Concepts
Arrow right icon
Authors (6):
Arrow left icon
Ke-Jou Carol Hsu Ke-Jou Carol Hsu
Author Profile Icon Ke-Jou Carol Hsu
Ke-Jou Carol Hsu
Neependra Khare Neependra Khare
Author Profile Icon Neependra Khare
Neependra Khare
John Arundel John Arundel
Author Profile Icon John Arundel
John Arundel
Hideto Saito Hideto Saito
Author Profile Icon Hideto Saito
Hideto Saito
Thomas Uphill Thomas Uphill
Author Profile Icon Thomas Uphill
Thomas Uphill
Hui-Chuan Chloe Lee Hui-Chuan Chloe Lee
Author Profile Icon Hui-Chuan Chloe Lee
Hui-Chuan Chloe Lee
+2 more Show less
Arrow right icon
View More author details
Toc

Chapter 2. Puppet Infrastructure

 

"Computers in the future may have as few as 1,000 vacuum tubes and weigh only 1.5 tons."

 
 --Popular Mechanics, 1949

In this chapter, we will cover:

  • Installing Puppet
  • Managing your manifests with Git
  • Creating a decentralized Puppet architecture
  • Writing a papply script
  • Running Puppet from cron
  • Bootstrapping Puppet with bash
  • Creating a centralized Puppet infrastructure
  • Creating certificates with multiple DNS names
  • Running Puppet from passenger
  • Setting up the environment
  • Configuring PuppetDB
  • Configuring Hiera
  • Setting-node specific data with Hiera
  • Storing secret data with hiera-gpg
  • Using MessagePack serialization
  • Automatic syntax checking with Git hooks
  • Pushing code around with Git
  • Managing environments with Git

Introduction

In this chapter, we will cover how to deploy Puppet in a centralized and decentralized manner. With each approach, we'll see a combination of best practices, my personal experience, and community solutions.

We'll configure and use both PuppetDB and Hiera. PuppetDB is used with exported resources, which we will cover in Chapter 5, Users and Virtual Resources. Hiera is used to separate variable data from Puppet code.

Finally, I'll introduce Git and see how to use Git to organize our code and our infrastructure.

Because Linux distributions, such as Ubuntu, Red Hat, and CentOS, differ in the specific details of package names, configuration file paths, and many other things, I have decided that for reasons of space and clarity the best approach for this book is to pick one distribution (Debian 7 named as Wheezy) and stick to that. However, Puppet runs on most popular operating systems, so you should have very little trouble adapting the recipes to your own favorite OS and distribution.

At the time of writing, Puppet 3.7.2 is the latest stable version available, this is the version of Puppet used in the book. The syntax of Puppet commands changes often, so be aware that while older versions of Puppet are still perfectly usable, they may not support all of the features and syntax described in this book. As we saw in Chapter 1, Puppet Language and Style, the future parser showcases features of the language scheduled to become default in Version 4 of Puppet.

Installing Puppet

In Chapter 1, Puppet Language and Style, we installed Puppet as a rubygem using the gem install. When deploying to several nodes, this may not be the best approach. Using the package manager of your chosen distribution is the best way to keep your Puppet versions similar on all of the nodes in your deployment. Puppet labs maintain repositories for APT-based and YUM-based distributions.

Getting ready

If your Linux distribution uses APT for package management, go to http://apt.puppetlabs.com/ and download the appropriate Puppet labs release package for your distribution. For our wheezy cookbook node, we will use http://apt.puppetlabs.com/puppetlabs-release-wheezy.deb.

If you are using a Linux distribution that uses YUM for package management, go to http://yum.puppetlabs.com/ and download the appropriate Puppet labs release package for your distribution.

How to do it...

  1. Once you have found the appropriate Puppet labs release package for your distribution, the steps to install Puppet are the same for either APT or YUM:
    • Install Puppet labs release package
    • Install Puppet package
  2. Once you have installed Puppet, verify the version of Puppet as shown in the following example:
    t@ckbk:~ puppet --version 3.7.2
    

Now that we have a method to install Puppet on our nodes, we need to turn our attention to keeping our Puppet manifests organized. In the next section, we will see how to use Git to keep our code organized and consistent.

Getting ready

If your Linux distribution uses APT for package management, go to

http://apt.puppetlabs.com/ and download the appropriate Puppet labs release package for your distribution. For our wheezy cookbook node, we will use http://apt.puppetlabs.com/puppetlabs-release-wheezy.deb.

If you are using a Linux distribution that uses YUM for package management, go to http://yum.puppetlabs.com/ and download the appropriate Puppet labs release package for your distribution.

How to do it...

  1. Once you have found the appropriate Puppet labs release package for your distribution, the steps to install Puppet are the same for either APT or YUM:
    • Install Puppet labs release package
    • Install Puppet package
  2. Once you have installed Puppet, verify the version of Puppet as shown in the following example:
    t@ckbk:~ puppet --version 3.7.2
    

Now that we have a method to install Puppet on our nodes, we need to turn our attention to keeping our Puppet manifests organized. In the next section, we will see how to use Git to keep our code organized and consistent.

How to do it...

Once you have found the appropriate Puppet labs release package for your distribution, the steps to install Puppet are the same for either APT or YUM:
  • Install Puppet labs release package
  • Install Puppet package
Once you have installed Puppet, verify the version of Puppet as shown in the following example:
t@ckbk:~ puppet --version 3.7.2

Now that

we have a method to install Puppet on our nodes, we need to turn our attention to keeping our Puppet manifests organized. In the next section, we will see how to use Git to keep our code organized and consistent.

Managing your manifests with Git

It's a great idea to put your Puppet manifests in a version control system such as Git or Subversion (Git is the de facto standard for Puppet). This gives you several advantages:

  • You can undo changes and revert to any previous version of your manifest
  • You can experiment with new features using a branch
  • If several people need to make changes to the manifests, they can make them independently, in their own working copies, and then merge their changes later
  • You can use the git log feature to see what was changed, and when (and by whom)

Getting ready

In this section, we'll import your existing manifest files into Git. If you have created a Puppet directory in a previous section use that, otherwise, use your existing manifest directory.

In this example, we'll create a new Git repository on a server accessible from all our nodes. There are several steps we need to take to have our code held in a Git repository:

  1. Install Git on a central server.
  2. Create a user to run Git and own the repository.
  3. Create a repository to hold the code.
  4. Create SSH keys to allow key-based access to the repository.
  5. Install Git on a node and download the latest version from our Git repository.

How to do it...

Follow these steps:

  1. First, install Git on your Git server (git.example.com in our example). The easiest way to do this is using Puppet. Create the following manifest, call it git.pp:
      package {'git':
        ensure => installed }
  2. Apply this manifest using puppet apply git.pp, this will install Git.
  3. Next, create a Git user that the nodes will use to log in and retrieve the latest code. Again, we'll do this with puppet. We'll also create a directory to hold our repository (/home/git/repos) as shown in the following code snippet:
    group { 'git': gid => 1111, }
    user {'git': uid => 1111, gid => 1111, comment => 'Git User', home => '/home/git', require => Group['git'], }
    file {'/home/git': ensure => 'directory', owner => 1111, group => 1111, require => User['git'], }
    file {'/home/git/repos': ensure => 'directory', owner => 1111, group => 1111, require => File['/home/git'] }
  4. After applying that manifest, log in as the Git user and create an empty Git repository using the following command:
    # sudo -iu git git@git $ cd repos git@git $ git init --bare puppet.git Initialized empty Git repository in /home/git/repos/puppet.git/
    
  5. Set a password for the Git user, we'll need to log in remotely after the next step:
    [root@git ~]# passwd git
    Changing password for user git.
    New password: 
    Retype new password: 
    passwd: all authentication tokens updated successfully.
    
  6. Now back on your local machine, create an ssh key for our nodes to use to update the repository:
    t@mylaptop ~ $ cd .ssh
    t@mylaptop ~/.ssh $ ssh-keygen -b 4096 -f git_rsa
    Generating public/private rsa key pair.
    Enter passphrase (empty for no passphrase): 
    Enter same passphrase again: 
    Your identification has been saved in git_rsa.
    Your public key has been saved in git_rsa.pub.
    The key fingerprint is:
    87:35:0e:4e:d2:96:5f:e4:ce:64:4a:d5:76:c8:2b:e4 thomas@mylaptop
  7. Now copy the newly created public key to the authorized_keys file. This will allow us to connect to the Git server using this new key:
    t@mylaptop ~/.ssh $ ssh-copy-id -i git_rsa git@git.example.com
    git@git.example.com's password: 
    Number of key(s) added: 1
  8. Now try logging into the machine, with: "ssh 'git@git.example.com'" and check to make sure that only the key(s) you wanted were added.
  9. Next, configure ssh to use your key when accessing the Git server and add the following to your ~/.ssh/config file:
    Host git git.example.com
      User git
      IdentityFile /home/thomas/.ssh/git_rsa
    
  10. Clone the repo onto your machine into a directory named Puppet (substitute your server name if you didn't use git.example.com):
    t@mylaptop ~$ git clone git@git.example.com:repos/puppet.git
    Cloning into 'puppet'...
    warning: You appear to have cloned an empty repository.
    Checking connectivity... done.

    We've created a Git repository; before we commit any changes to the repository, it's a good idea to set your name and e-mail in Git. Your name and e-mail will be appended to each commit you make.

  11. When you are working in a large team, knowing who made a change is very important; for this, use the following code snippet:
    t@mylaptop puppet$ git config --global user.email"thomas@narrabilis.com"
    t@mylaptop puppet$ git config --global user.name "ThomasUphill"
    
  12. You can verify your Git settings using the following snippet:
    t@mylaptop ~$ git config --global --list
    user.name=Thomas Uphill
    user.email=thomas@narrabilis.com
    core.editor=vim
    merge.tool=vimdiff
    color.ui=true
    push.default=simple
    
  13. Now that we have Git configured properly, change directory to your repository directory and create a new site manifest as shown in the following snippet:
    t@mylaptop ~$ cd puppet
    t@mylaptop puppet$ mkdir manifests
    t@mylaptop puppet$ vim manifests/site.pp
    node default {
      include base
    }
    
  14. This site manifest will install our base class on every node; we will create the base class using the Puppet module as we did in Chapter 1, Puppet Language and Style:
    t@mylaptop puppet$ mkdir modules
    t@mylaptop puppet$ cd modules
    t@mylaptop modules$ puppet module generate thomas-base
    Notice: Generating module at /home/tuphill/puppet/modules/thomas-base
    thomas-base
    thomas-base/Modulefile
    thomas-base/README
    thomas-base/manifests
    thomas-base/manifests/init.pp
    thomas-base/spec
    thomas-base/spec/spec_helper.rb
    thomas-base/tests
    thomas-base/tests/init.pp
    t@mylaptop modules$ ln -s thomas-base base
    
  15. As a last step, we create a symbolic link between the thomas-base directory and base. Now to make sure our module does something useful, add the following to the body of the base class defined in thomas-base/manifests/init.pp:
    class base {
      file {'/etc/motd':
        content => "${::fqdn}\nManaged by puppet ${::puppetversion}\n"
      }
    }
    
  16. Now add the new base module and site manifest to Git using git add and git commit as follows:
    t@mylaptop modules$ cd ..
    t@mylaptop puppet$ git add modules manifests
    t@mylaptop puppet$ git status
    On branch master
    Initial commit
    Changes to be committed:
      (use "git rm --cached <file>..." to unstage)
    new file:   manifests/site.pp
    new file:   modules/base
    new file:   modules/thomas-base/Modulefile
    new file:   modules/thomas-base/README
    new file:   modules/thomas-base/manifests/init.pp
    new file:   modules/thomas-base/spec/spec_helper.rb
    new file:   modules/thomas-base/tests/init.pp
    t@mylaptop puppet$ git commit -m "Initial commit with simple base module"
    [master (root-commit) 3e1f837] Initial commit with simple base module
     7 files changed, 102 insertions(+)
     create mode 100644 manifests/site.pp
     create mode 120000 modules/base
     create mode 100644 modules/thomas-base/Modulefile
     create mode 100644 modules/thomas-base/README
     create mode 100644 modules/thomas-base/manifests/init.pp
     create mode 100644 modules/thomas-base/spec/spec_helper.rb
     create mode 100644 modules/thomas-base/tests/init.pp
    
  17. At this point your changes to the Git repository have been committed locally; you now need to push those changes back to git.example.com so that other nodes can retrieve the updated files:
    t@mylaptop puppet$ git push origin master
    Counting objects: 15, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (9/9), done.
    Writing objects: 100% (15/15), 2.15 KiB | 0 bytes/s, done.
    Total 15 (delta 0), reused 0 (delta 0)
    To git@git.example.com:repos/puppet.git
     * [new branch]      master -> master
    

How it works...

Git tracks changes to files, and stores a complete history of all changes. The history of the repo is made up of commits. A commit represents the state of the repo at a particular point in time, which you create with the git commit command and annotate with a message.

You've now added your Puppet manifest files to the repo and created your first commit. This updates the history of the repo, but only in your local working copy. To synchronize the changes with the git.example.com copy, the git push command pushes all changes made since the last sync.

There's more...

Now that you have a central Git repository for your Puppet manifests, you can check out multiple copies of it in different places and work on them before committing your changes. For example, if you're working in a team, each member can have their own local copy of the repo and synchronize changes with the others via the central server. You may also choose to use GitHub as your central Git repository server. GitHub offers free Git repository hosting for public repositories, and you can pay for GitHub's premium service if you don't want your Puppet code to be publicly available.

In the next section, we will use our Git repository for both centralized and decentralized Puppet configurations.

Getting ready

In this section, we'll import your existing manifest files into Git. If you have created a Puppet directory in a previous section use that, otherwise, use your existing manifest directory.

In this example, we'll create a new Git repository on a server accessible from all our nodes. There are several steps we need to take to have our code held in a Git repository:

Install Git on a central server.
Create a user to run Git and own the repository.
Create a repository to hold the code.
Create SSH keys
  1. to allow key-based access to the repository.
  2. Install Git on a node and download the latest version from our Git repository.

How to do it...

Follow these steps:

  1. First, install Git on your Git server (git.example.com in our example). The easiest way to do this is using Puppet. Create the following manifest, call it git.pp:
      package {'git':
        ensure => installed }
  2. Apply this manifest using puppet apply git.pp, this will install Git.
  3. Next, create a Git user that the nodes will use to log in and retrieve the latest code. Again, we'll do this with puppet. We'll also create a directory to hold our repository (/home/git/repos) as shown in the following code snippet:
    group { 'git': gid => 1111, }
    user {'git': uid => 1111, gid => 1111, comment => 'Git User', home => '/home/git', require => Group['git'], }
    file {'/home/git': ensure => 'directory', owner => 1111, group => 1111, require => User['git'], }
    file {'/home/git/repos': ensure => 'directory', owner => 1111, group => 1111, require => File['/home/git'] }
  4. After applying that manifest, log in as the Git user and create an empty Git repository using the following command:
    # sudo -iu git git@git $ cd repos git@git $ git init --bare puppet.git Initialized empty Git repository in /home/git/repos/puppet.git/
    
  5. Set a password for the Git user, we'll need to log in remotely after the next step:
    [root@git ~]# passwd git
    Changing password for user git.
    New password: 
    Retype new password: 
    passwd: all authentication tokens updated successfully.
    
  6. Now back on your local machine, create an ssh key for our nodes to use to update the repository:
    t@mylaptop ~ $ cd .ssh
    t@mylaptop ~/.ssh $ ssh-keygen -b 4096 -f git_rsa
    Generating public/private rsa key pair.
    Enter passphrase (empty for no passphrase): 
    Enter same passphrase again: 
    Your identification has been saved in git_rsa.
    Your public key has been saved in git_rsa.pub.
    The key fingerprint is:
    87:35:0e:4e:d2:96:5f:e4:ce:64:4a:d5:76:c8:2b:e4 thomas@mylaptop
  7. Now copy the newly created public key to the authorized_keys file. This will allow us to connect to the Git server using this new key:
    t@mylaptop ~/.ssh $ ssh-copy-id -i git_rsa git@git.example.com
    git@git.example.com's password: 
    Number of key(s) added: 1
  8. Now try logging into the machine, with: "ssh 'git@git.example.com'" and check to make sure that only the key(s) you wanted were added.
  9. Next, configure ssh to use your key when accessing the Git server and add the following to your ~/.ssh/config file:
    Host git git.example.com
      User git
      IdentityFile /home/thomas/.ssh/git_rsa
    
  10. Clone the repo onto your machine into a directory named Puppet (substitute your server name if you didn't use git.example.com):
    t@mylaptop ~$ git clone git@git.example.com:repos/puppet.git
    Cloning into 'puppet'...
    warning: You appear to have cloned an empty repository.
    Checking connectivity... done.

    We've created a Git repository; before we commit any changes to the repository, it's a good idea to set your name and e-mail in Git. Your name and e-mail will be appended to each commit you make.

  11. When you are working in a large team, knowing who made a change is very important; for this, use the following code snippet:
    t@mylaptop puppet$ git config --global user.email"thomas@narrabilis.com"
    t@mylaptop puppet$ git config --global user.name "ThomasUphill"
    
  12. You can verify your Git settings using the following snippet:
    t@mylaptop ~$ git config --global --list
    user.name=Thomas Uphill
    user.email=thomas@narrabilis.com
    core.editor=vim
    merge.tool=vimdiff
    color.ui=true
    push.default=simple
    
  13. Now that we have Git configured properly, change directory to your repository directory and create a new site manifest as shown in the following snippet:
    t@mylaptop ~$ cd puppet
    t@mylaptop puppet$ mkdir manifests
    t@mylaptop puppet$ vim manifests/site.pp
    node default {
      include base
    }
    
  14. This site manifest will install our base class on every node; we will create the base class using the Puppet module as we did in Chapter 1, Puppet Language and Style:
    t@mylaptop puppet$ mkdir modules
    t@mylaptop puppet$ cd modules
    t@mylaptop modules$ puppet module generate thomas-base
    Notice: Generating module at /home/tuphill/puppet/modules/thomas-base
    thomas-base
    thomas-base/Modulefile
    thomas-base/README
    thomas-base/manifests
    thomas-base/manifests/init.pp
    thomas-base/spec
    thomas-base/spec/spec_helper.rb
    thomas-base/tests
    thomas-base/tests/init.pp
    t@mylaptop modules$ ln -s thomas-base base
    
  15. As a last step, we create a symbolic link between the thomas-base directory and base. Now to make sure our module does something useful, add the following to the body of the base class defined in thomas-base/manifests/init.pp:
    class base {
      file {'/etc/motd':
        content => "${::fqdn}\nManaged by puppet ${::puppetversion}\n"
      }
    }
    
  16. Now add the new base module and site manifest to Git using git add and git commit as follows:
    t@mylaptop modules$ cd ..
    t@mylaptop puppet$ git add modules manifests
    t@mylaptop puppet$ git status
    On branch master
    Initial commit
    Changes to be committed:
      (use "git rm --cached <file>..." to unstage)
    new file:   manifests/site.pp
    new file:   modules/base
    new file:   modules/thomas-base/Modulefile
    new file:   modules/thomas-base/README
    new file:   modules/thomas-base/manifests/init.pp
    new file:   modules/thomas-base/spec/spec_helper.rb
    new file:   modules/thomas-base/tests/init.pp
    t@mylaptop puppet$ git commit -m "Initial commit with simple base module"
    [master (root-commit) 3e1f837] Initial commit with simple base module
     7 files changed, 102 insertions(+)
     create mode 100644 manifests/site.pp
     create mode 120000 modules/base
     create mode 100644 modules/thomas-base/Modulefile
     create mode 100644 modules/thomas-base/README
     create mode 100644 modules/thomas-base/manifests/init.pp
     create mode 100644 modules/thomas-base/spec/spec_helper.rb
     create mode 100644 modules/thomas-base/tests/init.pp
    
  17. At this point your changes to the Git repository have been committed locally; you now need to push those changes back to git.example.com so that other nodes can retrieve the updated files:
    t@mylaptop puppet$ git push origin master
    Counting objects: 15, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (9/9), done.
    Writing objects: 100% (15/15), 2.15 KiB | 0 bytes/s, done.
    Total 15 (delta 0), reused 0 (delta 0)
    To git@git.example.com:repos/puppet.git
     * [new branch]      master -> master
    

How it works...

Git tracks changes to files, and stores a complete history of all changes. The history of the repo is made up of commits. A commit represents the state of the repo at a particular point in time, which you create with the git commit command and annotate with a message.

You've now added your Puppet manifest files to the repo and created your first commit. This updates the history of the repo, but only in your local working copy. To synchronize the changes with the git.example.com copy, the git push command pushes all changes made since the last sync.

There's more...

Now that you have a central Git repository for your Puppet manifests, you can check out multiple copies of it in different places and work on them before committing your changes. For example, if you're working in a team, each member can have their own local copy of the repo and synchronize changes with the others via the central server. You may also choose to use GitHub as your central Git repository server. GitHub offers free Git repository hosting for public repositories, and you can pay for GitHub's premium service if you don't want your Puppet code to be publicly available.

In the next section, we will use our Git repository for both centralized and decentralized Puppet configurations.

How to do it...

Follow these steps:

First, install
  1. Git on your Git server (git.example.com in our example). The easiest way to do this is using Puppet. Create the following manifest, call it git.pp:
      package {'git':
        ensure => installed }
  2. Apply this manifest using puppet apply git.pp, this will install Git.
  3. Next, create a Git user that the nodes will use to log in and retrieve the latest code. Again, we'll do this with puppet. We'll also create a directory to hold our repository (/home/git/repos) as shown in the following code snippet:
    group { 'git': gid => 1111, }
    user {'git': uid => 1111, gid => 1111, comment => 'Git User', home => '/home/git', require => Group['git'], }
    file {'/home/git': ensure => 'directory', owner => 1111, group => 1111, require => User['git'], }
    file {'/home/git/repos': ensure => 'directory', owner => 1111, group => 1111, require => File['/home/git'] }
  4. After applying that manifest, log in as the Git user and create an empty Git repository using the following command:
    # sudo -iu git git@git $ cd repos git@git $ git init --bare puppet.git Initialized empty Git repository in /home/git/repos/puppet.git/
    
  5. Set a password for the Git user, we'll need to log in remotely after the next step:
    [root@git ~]# passwd git
    Changing password for user git.
    New password: 
    Retype new password: 
    passwd: all authentication tokens updated successfully.
    
  6. Now back on your local machine, create an ssh key for our nodes to use to update the repository:
    t@mylaptop ~ $ cd .ssh
    t@mylaptop ~/.ssh $ ssh-keygen -b 4096 -f git_rsa
    Generating public/private rsa key pair.
    Enter passphrase (empty for no passphrase): 
    Enter same passphrase again: 
    Your identification has been saved in git_rsa.
    Your public key has been saved in git_rsa.pub.
    The key fingerprint is:
    87:35:0e:4e:d2:96:5f:e4:ce:64:4a:d5:76:c8:2b:e4 thomas@mylaptop
  7. Now copy the newly created public key to the authorized_keys file. This will allow us to connect to the Git server using this new key:
    t@mylaptop ~/.ssh $ ssh-copy-id -i git_rsa git@git.example.com
    git@git.example.com's password: 
    Number of key(s) added: 1
  8. Now try logging into the machine, with: "ssh 'git@git.example.com'" and check to make sure that only the key(s) you wanted were added.
  9. Next, configure ssh to use your key when accessing the Git server and add the following to your ~/.ssh/config file:
    Host git git.example.com
      User git
      IdentityFile /home/thomas/.ssh/git_rsa
    
  10. Clone the repo onto your machine into a directory named Puppet (substitute your server name if you didn't use git.example.com):
    t@mylaptop ~$ git clone git@git.example.com:repos/puppet.git
    Cloning into 'puppet'...
    warning: You appear to have cloned an empty repository.
    Checking connectivity... done.

    We've created a Git repository; before we commit any changes to the repository, it's a good idea to set your name and e-mail in Git. Your name and e-mail will be appended to each commit you make.

  11. When you are working in a large team, knowing who made a change is very important; for this, use the following code snippet:
    t@mylaptop puppet$ git config --global user.email"thomas@narrabilis.com"
    t@mylaptop puppet$ git config --global user.name "ThomasUphill"
    
  12. You can verify your Git settings using the following snippet:
    t@mylaptop ~$ git config --global --list
    user.name=Thomas Uphill
    user.email=thomas@narrabilis.com
    core.editor=vim
    merge.tool=vimdiff
    color.ui=true
    push.default=simple
    
  13. Now that we have Git configured properly, change directory to your repository directory and create a new site manifest as shown in the following snippet:
    t@mylaptop ~$ cd puppet
    t@mylaptop puppet$ mkdir manifests
    t@mylaptop puppet$ vim manifests/site.pp
    node default {
      include base
    }
    
  14. This site manifest will install our base class on every node; we will create the base class using the Puppet module as we did in Chapter 1, Puppet Language and Style:
    t@mylaptop puppet$ mkdir modules
    t@mylaptop puppet$ cd modules
    t@mylaptop modules$ puppet module generate thomas-base
    Notice: Generating module at /home/tuphill/puppet/modules/thomas-base
    thomas-base
    thomas-base/Modulefile
    thomas-base/README
    thomas-base/manifests
    thomas-base/manifests/init.pp
    thomas-base/spec
    thomas-base/spec/spec_helper.rb
    thomas-base/tests
    thomas-base/tests/init.pp
    t@mylaptop modules$ ln -s thomas-base base
    
  15. As a last step, we create a symbolic link between the thomas-base directory and base. Now to make sure our module does something useful, add the following to the body of the base class defined in thomas-base/manifests/init.pp:
    class base {
      file {'/etc/motd':
        content => "${::fqdn}\nManaged by puppet ${::puppetversion}\n"
      }
    }
    
  16. Now add the new base module and site manifest to Git using git add and git commit as follows:
    t@mylaptop modules$ cd ..
    t@mylaptop puppet$ git add modules manifests
    t@mylaptop puppet$ git status
    On branch master
    Initial commit
    Changes to be committed:
      (use "git rm --cached <file>..." to unstage)
    new file:   manifests/site.pp
    new file:   modules/base
    new file:   modules/thomas-base/Modulefile
    new file:   modules/thomas-base/README
    new file:   modules/thomas-base/manifests/init.pp
    new file:   modules/thomas-base/spec/spec_helper.rb
    new file:   modules/thomas-base/tests/init.pp
    t@mylaptop puppet$ git commit -m "Initial commit with simple base module"
    [master (root-commit) 3e1f837] Initial commit with simple base module
     7 files changed, 102 insertions(+)
     create mode 100644 manifests/site.pp
     create mode 120000 modules/base
     create mode 100644 modules/thomas-base/Modulefile
     create mode 100644 modules/thomas-base/README
     create mode 100644 modules/thomas-base/manifests/init.pp
     create mode 100644 modules/thomas-base/spec/spec_helper.rb
     create mode 100644 modules/thomas-base/tests/init.pp
    
  17. At this point your changes to the Git repository have been committed locally; you now need to push those changes back to git.example.com so that other nodes can retrieve the updated files:
    t@mylaptop puppet$ git push origin master
    Counting objects: 15, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (9/9), done.
    Writing objects: 100% (15/15), 2.15 KiB | 0 bytes/s, done.
    Total 15 (delta 0), reused 0 (delta 0)
    To git@git.example.com:repos/puppet.git
     * [new branch]      master -> master
    

How it works...

Git tracks changes to files, and stores a complete history of all changes. The history of the repo is made up of commits. A commit represents the state of the repo at a particular point in time, which you create with the git commit command and annotate with a message.

You've now added your Puppet manifest files to the repo and created your first commit. This updates the history of the repo, but only in your local working copy. To synchronize the changes with the git.example.com copy, the git push command pushes all changes made since the last sync.

There's more...

Now that you have a central Git repository for your Puppet manifests, you can check out multiple copies of it in different places and work on them before committing your changes. For example, if you're working in a team, each member can have their own local copy of the repo and synchronize changes with the others via the central server. You may also choose to use GitHub as your central Git repository server. GitHub offers free Git repository hosting for public repositories, and you can pay for GitHub's premium service if you don't want your Puppet code to be publicly available.

In the next section, we will use our Git repository for both centralized and decentralized Puppet configurations.

How it works...

Git tracks changes to files, and stores a complete history of all changes. The history of the repo is made up of commits. A commit represents the state of the repo at a particular point in time, which you create with the git commit command and annotate with a message.

You've now added your Puppet manifest files to the repo and created your first commit. This updates the history of the repo, but only in your local working copy. To synchronize the changes with the git.example.com copy, the git push command pushes all changes made since the last sync.

There's more...

Now that you have a central Git repository for your Puppet manifests, you can check out multiple copies of it in different places and work on them before committing your changes. For example, if you're working in a team, each member can have their own local copy of the repo and synchronize changes with the others via the central server. You may also choose to use GitHub as your central Git repository server. GitHub offers free Git repository hosting for public repositories, and you can pay for GitHub's premium service if you don't want your Puppet code to be publicly available.

In the next section, we will use our Git repository for both centralized and decentralized Puppet configurations.

There's more...

Now that you have a central Git repository for your Puppet manifests, you can check out multiple copies of it in different places and work on them before committing your changes. For example, if you're working in a team, each member can have their own local copy of the

repo and synchronize changes with the others via the central server. You may also choose to use GitHub as your central Git repository server. GitHub offers free Git repository hosting for public repositories, and you can pay for GitHub's premium service if you don't want your Puppet code to be publicly available.

In the next section, we will use our Git repository for both centralized and decentralized Puppet configurations.

Creating a decentralized Puppet architecture

Puppet is a configuration management tool. You can use Puppet to configure and prevent configuration drift in a large number of client computers. If all your client computers are easily reached via a central location, you may choose to have a central Puppet server control all the client computers. In the centralized model, the Puppet server is known as the Puppet master. We will cover how to configure a central Puppet master in a few sections.

If your client computers are widely distributed or you cannot guarantee communication between the client computers and a central location, then a decentralized architecture may be a good fit for your deployment. In the next few sections, we will see how to configure a decentralized Puppet architecture.

As we have seen, we can run the puppet apply command directly on a manifest file to have Puppet apply it. The problem with this arrangement is that we need to have the manifests transferred to the client computers.

We can use the Git repository we created in the previous section to transfer our manifests to each new node we create.

Getting ready

Create a new test node, call this new node whatever you wish, I'll use testnode for mine. Install Puppet on the machine as we have previously done.

How to do it...

Create a bootstrap.pp manifest that will perform the following configuration steps on our new node:

  1. Install Git:
    package {'git':
      ensure => 'installed'
    }
  2. Install the ssh key to access git.example.com in the Puppet user's home directory (/var/lib/puppet/.ssh/id_rsa):
    File {
      owner => 'puppet',
      group => 'puppet',
    }
    file {'/var/lib/puppet/.ssh':
      ensure => 'directory',
    }
    file {'/var/lib/puppet/.ssh/id_rsa':
      content => "
    -----BEGIN RSA PRIVATE KEY-----
    …
    NIjTXmZUlOKefh4MBilqUU3KQG8GBHjzYl2TkFVGLNYGNA0U8VG8SUJq
    -----END RSA PRIVATE KEY-----
    ",
      mode    => 0600,
      require => File['/var/lib/puppet/.ssh']
    }
  3. Download the ssh host key from git.example.com (/var/lib/puppet/.ssh/known_hosts):
    exec {'download git.example.com host key': 
      command => 'sudo -u puppet ssh-keyscan git.example.com >> /var/lib/puppet/.ssh/known_hosts',
      path    => '/usr/bin:/usr/sbin:/bin:/sbin',
      unless  => 'grep git.example.com /var/lib/puppet/.ssh/known_hosts',
      require => File['/var/lib/puppet/.ssh'],
    }
  4. Create a directory to contain the Git repository (/etc/puppet/cookbook):
    file {'/etc/puppet/cookbook':
      ensure => 'directory',
    }
  5. Clone the Puppet repository onto the new machine:
    exec {'create cookbook':
      command => 'sudo -u puppet git clone git@git.example.com:repos/puppet.git /etc/puppet/cookbook',
      path    => '/usr/bin:/usr/sbin:/bin:/sbin',
      require => [Package['git'],File['/var/lib/puppet/.ssh/id_rsa'],Exec['download git.example.com host key']],
      unless  => 'test -f /etc/puppet/cookbook/.git/config',
    }
  6. Now when we run Puppet apply on the new machine, the ssh key will be installed for the Puppet user. The Puppet user will then clone the Git repository into /etc/puppet/cookbook:
    root@testnode /tmp# puppet apply bootstrap.pp 
    Notice: Compiled catalog for testnode.example.com in environment production in 0.40 seconds
    Notice: /Stage[main]/Main/File[/etc/puppet/cookbook]/ensure: created
    Notice: /Stage[main]/Main/File[/var/lib/puppet/.ssh]/ensure: created
    Notice: /Stage[main]/Main/Exec[download git.example.com host key]/returns: executed successfully
    Notice: /Stage[main]/Main/File[/var/lib/puppet/.ssh/id_rsa]/ensure: defined content as '{md5}da61ce6ccc79bc6937bd98c798bc9fd3'
    Notice: /Stage[main]/Main/Exec[create cookbook]/returns: executed successfully
    Notice: Finished catalog run in 0.82 seconds
    

    Note

    You may have to disable the tty requirement of sudo. Comment out the line Defaults requiretty at /etc/sudoers if you have this line.

    Alternatively, you can set user => Puppet within the 'create cookbook' exec type. Beware that using the user attribute will cause any error messages from the command to be lost.

  7. Now that your Puppet code is available on the new node, you can apply it using puppet apply, specifying that /etc/puppet/cookbook/modules will contain the modules:
    root@testnode ~# puppet apply --modulepath=/etc/puppet/cookbook/modules /etc/puppet/cookbook/manifests/site.pp 
    Notice: Compiled catalog for testnode.example.com in environment production in 0.12 seconds
    Notice: /Stage[main]/Base/File[/etc/motd]/content: content changed '{md5}86d28ff83a8d49d349ba56b5c64b79ee' to '{md5}4c4c3ab7591d940318279d78b9c51d4f'
    Notice: Finished catalog run in 0.11 seconds
    root@testnode /tmp# cat /etc/motd
    testnode.example.com
    Managed by puppet 3.6.2
    

How it works...

First, our bootstrap.pp manifest ensures that Git is installed. The manifest then goes on to ensure that the ssh key for the Git user on git.example.com is installed into the Puppet user's home directory (/var/lib/puppet by default). The manifest then ensures that the host key for git.example.com is trusted by the Puppet user. With ssh configured, the bootstrap ensures that /etc/puppet/cookbook exists and is a directory.

We then use an exec to have Git clone the repository into /etc/puppet/cookbook. With all the code in place, we then call puppet apply a final time to deploy the code from the repository. In a production setting, you would distribute the bootstrap.pp manifest to all your nodes, possibly via an internal web server, using a method similar to curl http://puppet/bootstrap.pp >bootstrap.pp && puppet apply bootstrap.pp

Getting ready

Create a new test node, call this new node whatever you wish, I'll use testnode for mine. Install Puppet on the machine as we have previously done.

How to do it...

Create a bootstrap.pp manifest that will perform the following configuration steps on our new node:

  1. Install Git:
    package {'git':
      ensure => 'installed'
    }
  2. Install the ssh key to access git.example.com in the Puppet user's home directory (/var/lib/puppet/.ssh/id_rsa):
    File {
      owner => 'puppet',
      group => 'puppet',
    }
    file {'/var/lib/puppet/.ssh':
      ensure => 'directory',
    }
    file {'/var/lib/puppet/.ssh/id_rsa':
      content => "
    -----BEGIN RSA PRIVATE KEY-----
    …
    NIjTXmZUlOKefh4MBilqUU3KQG8GBHjzYl2TkFVGLNYGNA0U8VG8SUJq
    -----END RSA PRIVATE KEY-----
    ",
      mode    => 0600,
      require => File['/var/lib/puppet/.ssh']
    }
  3. Download the ssh host key from git.example.com (/var/lib/puppet/.ssh/known_hosts):
    exec {'download git.example.com host key': 
      command => 'sudo -u puppet ssh-keyscan git.example.com >> /var/lib/puppet/.ssh/known_hosts',
      path    => '/usr/bin:/usr/sbin:/bin:/sbin',
      unless  => 'grep git.example.com /var/lib/puppet/.ssh/known_hosts',
      require => File['/var/lib/puppet/.ssh'],
    }
  4. Create a directory to contain the Git repository (/etc/puppet/cookbook):
    file {'/etc/puppet/cookbook':
      ensure => 'directory',
    }
  5. Clone the Puppet repository onto the new machine:
    exec {'create cookbook':
      command => 'sudo -u puppet git clone git@git.example.com:repos/puppet.git /etc/puppet/cookbook',
      path    => '/usr/bin:/usr/sbin:/bin:/sbin',
      require => [Package['git'],File['/var/lib/puppet/.ssh/id_rsa'],Exec['download git.example.com host key']],
      unless  => 'test -f /etc/puppet/cookbook/.git/config',
    }
  6. Now when we run Puppet apply on the new machine, the ssh key will be installed for the Puppet user. The Puppet user will then clone the Git repository into /etc/puppet/cookbook:
    root@testnode /tmp# puppet apply bootstrap.pp 
    Notice: Compiled catalog for testnode.example.com in environment production in 0.40 seconds
    Notice: /Stage[main]/Main/File[/etc/puppet/cookbook]/ensure: created
    Notice: /Stage[main]/Main/File[/var/lib/puppet/.ssh]/ensure: created
    Notice: /Stage[main]/Main/Exec[download git.example.com host key]/returns: executed successfully
    Notice: /Stage[main]/Main/File[/var/lib/puppet/.ssh/id_rsa]/ensure: defined content as '{md5}da61ce6ccc79bc6937bd98c798bc9fd3'
    Notice: /Stage[main]/Main/Exec[create cookbook]/returns: executed successfully
    Notice: Finished catalog run in 0.82 seconds
    

    Note

    You may have to disable the tty requirement of sudo. Comment out the line Defaults requiretty at /etc/sudoers if you have this line.

    Alternatively, you can set user => Puppet within the 'create cookbook' exec type. Beware that using the user attribute will cause any error messages from the command to be lost.

  7. Now that your Puppet code is available on the new node, you can apply it using puppet apply, specifying that /etc/puppet/cookbook/modules will contain the modules:
    root@testnode ~# puppet apply --modulepath=/etc/puppet/cookbook/modules /etc/puppet/cookbook/manifests/site.pp 
    Notice: Compiled catalog for testnode.example.com in environment production in 0.12 seconds
    Notice: /Stage[main]/Base/File[/etc/motd]/content: content changed '{md5}86d28ff83a8d49d349ba56b5c64b79ee' to '{md5}4c4c3ab7591d940318279d78b9c51d4f'
    Notice: Finished catalog run in 0.11 seconds
    root@testnode /tmp# cat /etc/motd
    testnode.example.com
    Managed by puppet 3.6.2
    

How it works...

First, our bootstrap.pp manifest ensures that Git is installed. The manifest then goes on to ensure that the ssh key for the Git user on git.example.com is installed into the Puppet user's home directory (/var/lib/puppet by default). The manifest then ensures that the host key for git.example.com is trusted by the Puppet user. With ssh configured, the bootstrap ensures that /etc/puppet/cookbook exists and is a directory.

We then use an exec to have Git clone the repository into /etc/puppet/cookbook. With all the code in place, we then call puppet apply a final time to deploy the code from the repository. In a production setting, you would distribute the bootstrap.pp manifest to all your nodes, possibly via an internal web server, using a method similar to curl http://puppet/bootstrap.pp >bootstrap.pp && puppet apply bootstrap.pp

How to do it...

Create a bootstrap.pp manifest that will perform the following configuration steps on our new node:

Install Git:
package {'git':
  ensure => 'installed'
}
Install
  1. the ssh key to access git.example.com in the Puppet user's home directory (/var/lib/puppet/.ssh/id_rsa):
    File {
      owner => 'puppet',
      group => 'puppet',
    }
    file {'/var/lib/puppet/.ssh':
      ensure => 'directory',
    }
    file {'/var/lib/puppet/.ssh/id_rsa':
      content => "
    -----BEGIN RSA PRIVATE KEY-----
    …
    NIjTXmZUlOKefh4MBilqUU3KQG8GBHjzYl2TkFVGLNYGNA0U8VG8SUJq
    -----END RSA PRIVATE KEY-----
    ",
      mode    => 0600,
      require => File['/var/lib/puppet/.ssh']
    }
  2. Download the ssh host key from git.example.com (/var/lib/puppet/.ssh/known_hosts):
    exec {'download git.example.com host key': 
      command => 'sudo -u puppet ssh-keyscan git.example.com >> /var/lib/puppet/.ssh/known_hosts',
      path    => '/usr/bin:/usr/sbin:/bin:/sbin',
      unless  => 'grep git.example.com /var/lib/puppet/.ssh/known_hosts',
      require => File['/var/lib/puppet/.ssh'],
    }
  3. Create a directory to contain the Git repository (/etc/puppet/cookbook):
    file {'/etc/puppet/cookbook':
      ensure => 'directory',
    }
  4. Clone the Puppet repository onto the new machine:
    exec {'create cookbook':
      command => 'sudo -u puppet git clone git@git.example.com:repos/puppet.git /etc/puppet/cookbook',
      path    => '/usr/bin:/usr/sbin:/bin:/sbin',
      require => [Package['git'],File['/var/lib/puppet/.ssh/id_rsa'],Exec['download git.example.com host key']],
      unless  => 'test -f /etc/puppet/cookbook/.git/config',
    }
  5. Now when we run Puppet apply on the new machine, the ssh key will be installed for the Puppet user. The Puppet user will then clone the Git repository into /etc/puppet/cookbook:
    root@testnode /tmp# puppet apply bootstrap.pp 
    Notice: Compiled catalog for testnode.example.com in environment production in 0.40 seconds
    Notice: /Stage[main]/Main/File[/etc/puppet/cookbook]/ensure: created
    Notice: /Stage[main]/Main/File[/var/lib/puppet/.ssh]/ensure: created
    Notice: /Stage[main]/Main/Exec[download git.example.com host key]/returns: executed successfully
    Notice: /Stage[main]/Main/File[/var/lib/puppet/.ssh/id_rsa]/ensure: defined content as '{md5}da61ce6ccc79bc6937bd98c798bc9fd3'
    Notice: /Stage[main]/Main/Exec[create cookbook]/returns: executed successfully
    Notice: Finished catalog run in 0.82 seconds
    

    Note

    You may have to disable the tty requirement of sudo. Comment out the line Defaults requiretty at /etc/sudoers if you have this line.

    Alternatively, you can set user => Puppet within the 'create cookbook' exec type. Beware that using the user attribute will cause any error messages from the command to be lost.

  6. Now that your Puppet code is available on the new node, you can apply it using puppet apply, specifying that /etc/puppet/cookbook/modules will contain the modules:
    root@testnode ~# puppet apply --modulepath=/etc/puppet/cookbook/modules /etc/puppet/cookbook/manifests/site.pp 
    Notice: Compiled catalog for testnode.example.com in environment production in 0.12 seconds
    Notice: /Stage[main]/Base/File[/etc/motd]/content: content changed '{md5}86d28ff83a8d49d349ba56b5c64b79ee' to '{md5}4c4c3ab7591d940318279d78b9c51d4f'
    Notice: Finished catalog run in 0.11 seconds
    root@testnode /tmp# cat /etc/motd
    testnode.example.com
    Managed by puppet 3.6.2
    

How it works...

First, our bootstrap.pp manifest ensures that Git is installed. The manifest then goes on to ensure that the ssh key for the Git user on git.example.com is installed into the Puppet user's home directory (/var/lib/puppet by default). The manifest then ensures that the host key for git.example.com is trusted by the Puppet user. With ssh configured, the bootstrap ensures that /etc/puppet/cookbook exists and is a directory.

We then use an exec to have Git clone the repository into /etc/puppet/cookbook. With all the code in place, we then call puppet apply a final time to deploy the code from the repository. In a production setting, you would distribute the bootstrap.pp manifest to all your nodes, possibly via an internal web server, using a method similar to curl http://puppet/bootstrap.pp >bootstrap.pp && puppet apply bootstrap.pp

How it works...

First, our bootstrap.pp manifest ensures that Git is installed. The manifest then goes on to ensure

that the ssh key for the Git user on git.example.com is installed into the Puppet user's home directory (/var/lib/puppet by default). The manifest then ensures that the host key for git.example.com is trusted by the Puppet user. With ssh configured, the bootstrap ensures that /etc/puppet/cookbook exists and is a directory.

We then use an exec to have Git clone the repository into /etc/puppet/cookbook. With all the code in place, we then call puppet apply a final time to deploy the code from the repository. In a production setting, you would distribute the bootstrap.pp manifest to all your nodes, possibly via an internal web server, using a method similar to curl http://puppet/bootstrap.pp >bootstrap.pp && puppet apply bootstrap.pp

Writing a papply script

We'd like to make it as quick and easy as possible to apply Puppet on a machine; for this we'll write a little script that wraps the puppet apply command with the parameters it needs. We'll deploy the script where it's needed with Puppet itself.

How to do it...

Follow these steps:

  1. In your Puppet repo, create the directories needed for a Puppet module:
    t@mylaptop ~$ cd puppet/modules
    t@mylaptop modules$ mkdir -p puppet/{manifests,files}
    
  2. Create the modules/puppet/files/papply.sh file with the following contents:
    #!/bin/sh sudo puppet apply /etc/puppet/cookbook/manifests/site.pp \--modulepath=/etc/puppet/cookbook/modules $*
  3. Create the modules/puppet/manifests/init.pp file with the following contents:
    class puppet {
      file { '/usr/local/bin/papply':
        source => 'puppet:///modules/puppet/papply.sh',
        mode   => '0755',
      }
    }
  4. Modify your manifests/site.pp file as follows:
    node default {
      include base
      include puppet
    }
  5. Add the Puppet module to the Git repository and commit the change as follows:
    t@mylaptop puppet$ git add manifests/site.pp modules/puppet
    t@mylaptop puppet$ git status
    On branch master
    Your branch is up-to-date with 'origin/master'.
    Changes to be committed:
      (use "git reset HEAD <file>..." to unstage)
    modified:   manifests/site.pp
    new file:   modules/puppet/files/papply.sh
    new file:   modules/puppet/manifests/init.pp
    t@mylaptop puppet$ git commit -m "adding puppet module to include papply"
    [master 7c2e3d5] adding puppet module to include papply
     3 files changed, 11 insertions(+)
     create mode 100644 modules/puppet/files/papply.sh
     create mode 100644 modules/puppet/manifests/init.pp
    
  6. Now remember to push the changes to the Git repository on git.example.com:
    t@mylaptop puppet$ git push origin master Counting objects: 14, done. Delta compression using up to 4 threads. Compressing objects: 100% (7/7), done. Writing objects: 100% (10/10), 894 bytes | 0 bytes/s, done. Total 10 (delta 0), reused 0 (delta 0) To git@git.example.com:repos/puppet.git 23e887c..7c2e3d5  master -> master
    
  7. Pull the latest version of the Git repository to your new node (testnode for me) as shown in the following command line:
    root@testnode ~# sudo -iu puppet
    puppet@testnode ~$ cd /etc/puppet/cookbook/puppet@testnode /etc/puppet/cookbook$ git pull origin master remote: Counting objects: 14, done. remote: Compressing objects: 100% (7/7), done. remote: Total 10 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (10/10), done. From git.example.com:repos/puppet * branch            master     -> FETCH_HEAD Updating 23e887c..7c2e3d5 Fast-forward manifests/site.pp                |    1 + modules/puppet/files/papply.sh   |    4 ++++ modules/puppet/manifests/init.pp |    6 ++++++ 3 files changed, 11 insertions(+), 0 deletions(-) create mode 100644 modules/puppet/files/papply.sh create mode 100644 modules/puppet/manifests/init.pp
    
  8. Apply the manifest manually once to install the papply script:
    root@testnode ~# puppet apply /etc/puppet/cookbook/manifests/site.pp --modulepath /etc/puppet/cookbook/modules
    Notice: Compiled catalog for testnode.example.com in environment production in 0.13 seconds
    Notice: /Stage[main]/Puppet/File[/usr/local/bin/papply]/ensure: defined content as '{md5}d5c2cdd359306dd6e6441e6fb96e5ef7'
    Notice: Finished catalog run in 0.13 seconds
    
  9. Finally, test the script:
    root@testnode ~# papply
    Notice: Compiled catalog for testnode.example.com in environment production in 0.13 seconds
    Notice: Finished catalog run in 0.09 seconds
    

Now, whenever you need to run Puppet, you can simply run papply. In future, when we apply Puppet changes, I'll ask you to run papply instead of the full puppet apply command.

How it works...

As you've seen, to run Puppet on a machine and apply a specified manifest file, we use the puppet apply command:

puppet apply manifests/site.pp

When you're using modules (such as the Puppet module we just created), you also need to tell Puppet where to search for modules, using the modulepath argument:

puppet apply manifests/nodes.pp \--modulepath=/home/ubuntu/puppet/modules

In order to run Puppet with the root privileges it needs, we have to put sudo before everything:

sudo puppet apply manifests/nodes.pp \--modulepath=/home/ubuntu/puppet/modules

Finally, any additional arguments passed to papply will be passed through to Puppet itself, by adding the $* parameter:

sudo puppet apply manifests/nodes.pp \--modulepath=/home/ubuntu/puppet/modules $*

That's a lot of typing, so putting this in a script makes sense. We've added a Puppet file resource that will deploy the script to /usr/local/bin and make it executable:

file { '/usr/local/bin/papply': source => 'puppet:///modules/puppet/papply.sh', mode   => '0755',}

Finally, we include the Puppet module in our default node declaration:

node default {
  include base
  include puppet
}

You can do the same for any other nodes managed by Puppet.

How to do it...

Follow these steps:

In your Puppet repo, create the directories needed for a Puppet module:
t@mylaptop ~$ cd puppet/modules
t@mylaptop modules$ mkdir -p puppet/{manifests,files}
Create the modules/puppet/files/papply.sh file with the following contents:
#!/bin/sh sudo puppet apply /etc/puppet/cookbook/manifests/site.pp \--modulepath=/etc/puppet/cookbook/modules $*
Create the modules/puppet/manifests/init.pp file with the following contents:
class puppet {
  file { '/usr/local/bin/papply':
    source => 'puppet:///modules/puppet/papply.sh',
    mode   => '0755',
  }
}
Modify
  1. your manifests/site.pp file as follows:
    node default {
      include base
      include puppet
    }
  2. Add the Puppet module to the Git repository and commit the change as follows:
    t@mylaptop puppet$ git add manifests/site.pp modules/puppet
    t@mylaptop puppet$ git status
    On branch master
    Your branch is up-to-date with 'origin/master'.
    Changes to be committed:
      (use "git reset HEAD <file>..." to unstage)
    modified:   manifests/site.pp
    new file:   modules/puppet/files/papply.sh
    new file:   modules/puppet/manifests/init.pp
    t@mylaptop puppet$ git commit -m "adding puppet module to include papply"
    [master 7c2e3d5] adding puppet module to include papply
     3 files changed, 11 insertions(+)
     create mode 100644 modules/puppet/files/papply.sh
     create mode 100644 modules/puppet/manifests/init.pp
    
  3. Now remember to push the changes to the Git repository on git.example.com:
    t@mylaptop puppet$ git push origin master Counting objects: 14, done. Delta compression using up to 4 threads. Compressing objects: 100% (7/7), done. Writing objects: 100% (10/10), 894 bytes | 0 bytes/s, done. Total 10 (delta 0), reused 0 (delta 0) To git@git.example.com:repos/puppet.git 23e887c..7c2e3d5  master -> master
    
  4. Pull the latest version of the Git repository to your new node (testnode for me) as shown in the following command line:
    root@testnode ~# sudo -iu puppet
    puppet@testnode ~$ cd /etc/puppet/cookbook/puppet@testnode /etc/puppet/cookbook$ git pull origin master remote: Counting objects: 14, done. remote: Compressing objects: 100% (7/7), done. remote: Total 10 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (10/10), done. From git.example.com:repos/puppet * branch            master     -> FETCH_HEAD Updating 23e887c..7c2e3d5 Fast-forward manifests/site.pp                |    1 + modules/puppet/files/papply.sh   |    4 ++++ modules/puppet/manifests/init.pp |    6 ++++++ 3 files changed, 11 insertions(+), 0 deletions(-) create mode 100644 modules/puppet/files/papply.sh create mode 100644 modules/puppet/manifests/init.pp
    
  5. Apply the manifest manually once to install the papply script:
    root@testnode ~# puppet apply /etc/puppet/cookbook/manifests/site.pp --modulepath /etc/puppet/cookbook/modules
    Notice: Compiled catalog for testnode.example.com in environment production in 0.13 seconds
    Notice: /Stage[main]/Puppet/File[/usr/local/bin/papply]/ensure: defined content as '{md5}d5c2cdd359306dd6e6441e6fb96e5ef7'
    Notice: Finished catalog run in 0.13 seconds
    
  6. Finally, test the script:
    root@testnode ~# papply
    Notice: Compiled catalog for testnode.example.com in environment production in 0.13 seconds
    Notice: Finished catalog run in 0.09 seconds
    

Now, whenever you need to run Puppet, you can simply run papply. In future, when we apply Puppet changes, I'll ask you to run papply instead of the full puppet apply command.

How it works...

As you've seen, to run Puppet on a machine and apply a specified manifest file, we use the puppet apply command:

puppet apply manifests/site.pp

When you're using modules (such as the Puppet module we just created), you also need to tell Puppet where to search for modules, using the modulepath argument:

puppet apply manifests/nodes.pp \--modulepath=/home/ubuntu/puppet/modules

In order to run Puppet with the root privileges it needs, we have to put sudo before everything:

sudo puppet apply manifests/nodes.pp \--modulepath=/home/ubuntu/puppet/modules

Finally, any additional arguments passed to papply will be passed through to Puppet itself, by adding the $* parameter:

sudo puppet apply manifests/nodes.pp \--modulepath=/home/ubuntu/puppet/modules $*

That's a lot of typing, so putting this in a script makes sense. We've added a Puppet file resource that will deploy the script to /usr/local/bin and make it executable:

file { '/usr/local/bin/papply': source => 'puppet:///modules/puppet/papply.sh', mode   => '0755',}

Finally, we include the Puppet module in our default node declaration:

node default {
  include base
  include puppet
}

You can do the same for any other nodes managed by Puppet.

How it works...

As you've seen, to run Puppet on a machine and apply a specified manifest file, we use the puppet apply command:

puppet apply manifests/site.pp

When you're using modules (such as the Puppet module we just created), you also need to tell Puppet where to search for modules, using the modulepath argument:

puppet apply manifests/nodes.pp \--modulepath=/home/ubuntu/puppet/modules

In order to run Puppet with the root privileges it needs, we have to put sudo before everything:

sudo puppet apply manifests/nodes.pp \--modulepath=/home/ubuntu/puppet/modules

Finally, any

additional arguments passed to papply will be passed through to Puppet itself, by adding the $* parameter:

sudo puppet apply manifests/nodes.pp \--modulepath=/home/ubuntu/puppet/modules $*

That's a lot of typing, so putting this in a script makes sense. We've added a Puppet file resource that will deploy the script to /usr/local/bin and make it executable:

file { '/usr/local/bin/papply': source => 'puppet:///modules/puppet/papply.sh', mode   => '0755',}

Finally, we include the Puppet module in our default node declaration:

node default {
  include base
  include puppet
}

You can do the same for any other nodes managed by Puppet.

Running Puppet from cron

You can do a lot with the setup you already have: work on your Puppet manifests as a team, communicate changes via a central Git repository, and manually apply them on a machine using the papply script.

However, you still have to log into each machine to update the Git repo and rerun Puppet. It would be helpful to have each machine update itself and apply any changes automatically. Then all you need to do is to push a change to the repo, and it will go out to all your machines within a certain time.

The simplest way to do this is with a cron job that pulls updates from the repo at regular intervals and then runs Puppet if anything has changed.

Getting ready

You'll need the Git repo we set up in the Managing your manifests with Git and Creating a decentralized Puppet architecture recipes, and the papply script from the Writing a papply script recipe. You'll need to apply the bootstrap.pp manifest we created to install ssh keys to download the latest repository.

How to do it...

Follow these steps:

  1. Copy the bootstrap.pp script to any node you wish to enroll. The bootstrap.pp manifest includes the private key used to access the Git repository, it should be protected in a production environment.
  2. Create the modules/puppet/files/pull-updates.sh file with the following contents:
    #!/bin/sh
    cd /etc/puppet/cookbook
    sudo –u puppet git pull && /usr/local/bin/papply
  3. Modify the modules/puppet/manifests/init.pp file and add the following snippet after the papply file definition:
    file { '/usr/local/bin/pull-updates':
      source => 'puppet:///modules/puppet/pull-updates.sh',
      mode   => '0755',
    }
    cron { 'run-puppet':
      ensure  => 'present',
      user    => 'puppet',
      command => '/usr/local/bin/pull-updates',
      minute  => '*/10',
      hour    => '*',
    }
  4. Commit the changes as before and push to the Git server as shown in the following command line:
    t@mylaptop puppet$ git add modules/puppet
    t@mylaptop puppet$ git commit -m "adding pull-updates"
    [master 7e9bac3] adding pull-updates
     2 files changed, 14 insertions(+)
     create mode 100644 modules/puppet/files/pull-updates.sh
    t@mylaptop puppet$ git push
    Counting objects: 14, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (7/7), done.
    Writing objects: 100% (8/8), 839 bytes | 0 bytes/s, done.
    Total 8 (delta 0), reused 0 (delta 0)
    To git@git.example.com:repos/puppet.git
       7c2e3d5..7e9bac3  master -> master
    
  5. Issue a Git pull on the test node:
    root@testnode ~# cd /etc/puppet/cookbook/
    root@testnode /etc/puppet/cookbook# sudo –u puppet git pull
    remote: Counting objects: 14, done.
    remote: Compressing objects: 100% (7/7), done.
    remote: Total 8 (delta 0), reused 0 (delta 0)
    Unpacking objects: 100% (8/8), done.
    From git.example.com:repos/puppet
       23e887c..7e9bac3  master     -> origin/master
    Updating 7c2e3d5..7e9bac3
    Fast-forward
     modules/puppet/files/pull-updates.sh |    3 +++
     modules/puppet/manifests/init.pp     |   11 +++++++++++
     2 files changed, 14 insertions(+), 0 deletions(-)
     create mode 100644 modules/puppet/files/pull-updates.sh
    
  6. Run Puppet on the test node:
    root@testnode /etc/puppet/cookbook# papply
    Notice: Compiled catalog for testnode.example.com in environment production in 0.17 seconds
    Notice: /Stage[main]/Puppet/Cron[run-puppet]/ensure: created
    Notice: /Stage[main]/Puppet/File[/usr/local/bin/pull-updates]/ensure: defined content as '{md5}04c023feb5d566a417b519ea51586398'
    Notice: Finished catalog run in 0.16 seconds
    
  7. Check that the pull-updates script works properly:
    root@testnode /etc/puppet/cookbook# pull-updates
    Already up-to-date.
    Notice: Compiled catalog for testnode.example.com in environment production in 0.15 seconds
    Notice: Finished catalog run in 0.14 seconds
    
  8. Verify the cron job was created successfully:
    root@testnode /etc/puppet/cookbook# crontab -l -u puppet
    # HEADER: This file was autogenerated at Tue Sep 09 02:31:00 -0400 2014 by puppet.
    # HEADER: While it can still be managed manually, it is definitely not recommended.
    # HEADER: Note particularly that the comments starting with 'Puppet Name' should
    # HEADER: not be deleted, as doing so could cause duplicate cron jobs.
    # Puppet Name: run-puppet
    */10 * * * * /usr/local/bin/pull-updates
    

How it works...

When we created the bootstrap.pp manifest, we made sure that the Puppet user can checkout the Git repository using an ssh key. This enables the Puppet user to run the Git pull in the cookbook directory unattended. We've also added the pull-updates script, which does this and runs Puppet if any changes are pulled:

#!/bin/sh
cd /etc/puppet/cookbook
sudo –u puppet git pull && papply

We deploy this script to the node with Puppet:

file { '/usr/local/bin/pull-updates':
  source => 'puppet:///modules/puppet/pull-updates.sh',
  mode   => '0755',
}

Finally, we've created a cron job that runs pull-updates at regular intervals (every 10 minutes, but feel free to change this if you need to):

cron { 'run-puppet':
  ensure  => 'present',
  command => '/usr/local/bin/pull-updates',
  minute  => '*/10',
  hour    => '*',
}

There's more...

Congratulations, you now have a fully-automated Puppet infrastructure! Once you have applied the bootstrap.pp manifest, run Puppet on the repository; the machine will be set up to pull any new changes and apply them automatically.

So, for example, if you wanted to add a new user account to all your machines, all you have to do is add the account in your working copy of the manifest, and commit and push the changes to the central Git repository. Within 10 minutes, it will automatically be applied to every machine that's running Puppet.

Getting ready

You'll need the Git repo we set up in the Managing your manifests with Git and Creating a decentralized Puppet architecture recipes, and the papply script from the Writing a papply script recipe. You'll need to apply the bootstrap.pp manifest we created to install ssh keys to download the latest repository.

How to do it...

Follow these steps:

  1. Copy the bootstrap.pp script to any node you wish to enroll. The bootstrap.pp manifest includes the private key used to access the Git repository, it should be protected in a production environment.
  2. Create the modules/puppet/files/pull-updates.sh file with the following contents:
    #!/bin/sh
    cd /etc/puppet/cookbook
    sudo –u puppet git pull && /usr/local/bin/papply
  3. Modify the modules/puppet/manifests/init.pp file and add the following snippet after the papply file definition:
    file { '/usr/local/bin/pull-updates':
      source => 'puppet:///modules/puppet/pull-updates.sh',
      mode   => '0755',
    }
    cron { 'run-puppet':
      ensure  => 'present',
      user    => 'puppet',
      command => '/usr/local/bin/pull-updates',
      minute  => '*/10',
      hour    => '*',
    }
  4. Commit the changes as before and push to the Git server as shown in the following command line:
    t@mylaptop puppet$ git add modules/puppet
    t@mylaptop puppet$ git commit -m "adding pull-updates"
    [master 7e9bac3] adding pull-updates
     2 files changed, 14 insertions(+)
     create mode 100644 modules/puppet/files/pull-updates.sh
    t@mylaptop puppet$ git push
    Counting objects: 14, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (7/7), done.
    Writing objects: 100% (8/8), 839 bytes | 0 bytes/s, done.
    Total 8 (delta 0), reused 0 (delta 0)
    To git@git.example.com:repos/puppet.git
       7c2e3d5..7e9bac3  master -> master
    
  5. Issue a Git pull on the test node:
    root@testnode ~# cd /etc/puppet/cookbook/
    root@testnode /etc/puppet/cookbook# sudo –u puppet git pull
    remote: Counting objects: 14, done.
    remote: Compressing objects: 100% (7/7), done.
    remote: Total 8 (delta 0), reused 0 (delta 0)
    Unpacking objects: 100% (8/8), done.
    From git.example.com:repos/puppet
       23e887c..7e9bac3  master     -> origin/master
    Updating 7c2e3d5..7e9bac3
    Fast-forward
     modules/puppet/files/pull-updates.sh |    3 +++
     modules/puppet/manifests/init.pp     |   11 +++++++++++
     2 files changed, 14 insertions(+), 0 deletions(-)
     create mode 100644 modules/puppet/files/pull-updates.sh
    
  6. Run Puppet on the test node:
    root@testnode /etc/puppet/cookbook# papply
    Notice: Compiled catalog for testnode.example.com in environment production in 0.17 seconds
    Notice: /Stage[main]/Puppet/Cron[run-puppet]/ensure: created
    Notice: /Stage[main]/Puppet/File[/usr/local/bin/pull-updates]/ensure: defined content as '{md5}04c023feb5d566a417b519ea51586398'
    Notice: Finished catalog run in 0.16 seconds
    
  7. Check that the pull-updates script works properly:
    root@testnode /etc/puppet/cookbook# pull-updates
    Already up-to-date.
    Notice: Compiled catalog for testnode.example.com in environment production in 0.15 seconds
    Notice: Finished catalog run in 0.14 seconds
    
  8. Verify the cron job was created successfully:
    root@testnode /etc/puppet/cookbook# crontab -l -u puppet
    # HEADER: This file was autogenerated at Tue Sep 09 02:31:00 -0400 2014 by puppet.
    # HEADER: While it can still be managed manually, it is definitely not recommended.
    # HEADER: Note particularly that the comments starting with 'Puppet Name' should
    # HEADER: not be deleted, as doing so could cause duplicate cron jobs.
    # Puppet Name: run-puppet
    */10 * * * * /usr/local/bin/pull-updates
    

How it works...

When we created the bootstrap.pp manifest, we made sure that the Puppet user can checkout the Git repository using an ssh key. This enables the Puppet user to run the Git pull in the cookbook directory unattended. We've also added the pull-updates script, which does this and runs Puppet if any changes are pulled:

#!/bin/sh
cd /etc/puppet/cookbook
sudo –u puppet git pull && papply

We deploy this script to the node with Puppet:

file { '/usr/local/bin/pull-updates':
  source => 'puppet:///modules/puppet/pull-updates.sh',
  mode   => '0755',
}

Finally, we've created a cron job that runs pull-updates at regular intervals (every 10 minutes, but feel free to change this if you need to):

cron { 'run-puppet':
  ensure  => 'present',
  command => '/usr/local/bin/pull-updates',
  minute  => '*/10',
  hour    => '*',
}

There's more...

Congratulations, you now have a fully-automated Puppet infrastructure! Once you have applied the bootstrap.pp manifest, run Puppet on the repository; the machine will be set up to pull any new changes and apply them automatically.

So, for example, if you wanted to add a new user account to all your machines, all you have to do is add the account in your working copy of the manifest, and commit and push the changes to the central Git repository. Within 10 minutes, it will automatically be applied to every machine that's running Puppet.

How to do it...

Follow

these steps:

  1. Copy the bootstrap.pp script to any node you wish to enroll. The bootstrap.pp manifest includes the private key used to access the Git repository, it should be protected in a production environment.
  2. Create the modules/puppet/files/pull-updates.sh file with the following contents:
    #!/bin/sh
    cd /etc/puppet/cookbook
    sudo –u puppet git pull && /usr/local/bin/papply
  3. Modify the modules/puppet/manifests/init.pp file and add the following snippet after the papply file definition:
    file { '/usr/local/bin/pull-updates':
      source => 'puppet:///modules/puppet/pull-updates.sh',
      mode   => '0755',
    }
    cron { 'run-puppet':
      ensure  => 'present',
      user    => 'puppet',
      command => '/usr/local/bin/pull-updates',
      minute  => '*/10',
      hour    => '*',
    }
  4. Commit the changes as before and push to the Git server as shown in the following command line:
    t@mylaptop puppet$ git add modules/puppet
    t@mylaptop puppet$ git commit -m "adding pull-updates"
    [master 7e9bac3] adding pull-updates
     2 files changed, 14 insertions(+)
     create mode 100644 modules/puppet/files/pull-updates.sh
    t@mylaptop puppet$ git push
    Counting objects: 14, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (7/7), done.
    Writing objects: 100% (8/8), 839 bytes | 0 bytes/s, done.
    Total 8 (delta 0), reused 0 (delta 0)
    To git@git.example.com:repos/puppet.git
       7c2e3d5..7e9bac3  master -> master
    
  5. Issue a Git pull on the test node:
    root@testnode ~# cd /etc/puppet/cookbook/
    root@testnode /etc/puppet/cookbook# sudo –u puppet git pull
    remote: Counting objects: 14, done.
    remote: Compressing objects: 100% (7/7), done.
    remote: Total 8 (delta 0), reused 0 (delta 0)
    Unpacking objects: 100% (8/8), done.
    From git.example.com:repos/puppet
       23e887c..7e9bac3  master     -> origin/master
    Updating 7c2e3d5..7e9bac3
    Fast-forward
     modules/puppet/files/pull-updates.sh |    3 +++
     modules/puppet/manifests/init.pp     |   11 +++++++++++
     2 files changed, 14 insertions(+), 0 deletions(-)
     create mode 100644 modules/puppet/files/pull-updates.sh
    
  6. Run Puppet on the test node:
    root@testnode /etc/puppet/cookbook# papply
    Notice: Compiled catalog for testnode.example.com in environment production in 0.17 seconds
    Notice: /Stage[main]/Puppet/Cron[run-puppet]/ensure: created
    Notice: /Stage[main]/Puppet/File[/usr/local/bin/pull-updates]/ensure: defined content as '{md5}04c023feb5d566a417b519ea51586398'
    Notice: Finished catalog run in 0.16 seconds
    
  7. Check that the pull-updates script works properly:
    root@testnode /etc/puppet/cookbook# pull-updates
    Already up-to-date.
    Notice: Compiled catalog for testnode.example.com in environment production in 0.15 seconds
    Notice: Finished catalog run in 0.14 seconds
    
  8. Verify the cron job was created successfully:
    root@testnode /etc/puppet/cookbook# crontab -l -u puppet
    # HEADER: This file was autogenerated at Tue Sep 09 02:31:00 -0400 2014 by puppet.
    # HEADER: While it can still be managed manually, it is definitely not recommended.
    # HEADER: Note particularly that the comments starting with 'Puppet Name' should
    # HEADER: not be deleted, as doing so could cause duplicate cron jobs.
    # Puppet Name: run-puppet
    */10 * * * * /usr/local/bin/pull-updates
    

How it works...

When we created the bootstrap.pp manifest, we made sure that the Puppet user can checkout the Git repository using an ssh key. This enables the Puppet user to run the Git pull in the cookbook directory unattended. We've also added the pull-updates script, which does this and runs Puppet if any changes are pulled:

#!/bin/sh
cd /etc/puppet/cookbook
sudo –u puppet git pull && papply

We deploy this script to the node with Puppet:

file { '/usr/local/bin/pull-updates':
  source => 'puppet:///modules/puppet/pull-updates.sh',
  mode   => '0755',
}

Finally, we've created a cron job that runs pull-updates at regular intervals (every 10 minutes, but feel free to change this if you need to):

cron { 'run-puppet':
  ensure  => 'present',
  command => '/usr/local/bin/pull-updates',
  minute  => '*/10',
  hour    => '*',
}

There's more...

Congratulations, you now have a fully-automated Puppet infrastructure! Once you have applied the bootstrap.pp manifest, run Puppet on the repository; the machine will be set up to pull any new changes and apply them automatically.

So, for example, if you wanted to add a new user account to all your machines, all you have to do is add the account in your working copy of the manifest, and commit and push the changes to the central Git repository. Within 10 minutes, it will automatically be applied to every machine that's running Puppet.

How it works...

When we created the bootstrap.pp manifest, we made sure that the Puppet user can checkout

the Git repository using an ssh key. This enables the Puppet user to run the Git pull in the cookbook directory unattended. We've also added the pull-updates script, which does this and runs Puppet if any changes are pulled:

#!/bin/sh
cd /etc/puppet/cookbook
sudo –u puppet git pull && papply

We deploy this script to the node with Puppet:

file { '/usr/local/bin/pull-updates':
  source => 'puppet:///modules/puppet/pull-updates.sh',
  mode   => '0755',
}

Finally, we've created a cron job that runs pull-updates at regular intervals (every 10 minutes, but feel free to change this if you need to):

cron { 'run-puppet':
  ensure  => 'present',
  command => '/usr/local/bin/pull-updates',
  minute  => '*/10',
  hour    => '*',
}

There's more...

Congratulations, you now have a fully-automated Puppet infrastructure! Once you have applied the bootstrap.pp manifest, run Puppet on the repository; the machine will be set up to pull any new changes and apply them automatically.

So, for example, if you wanted to add a new user account to all your machines, all you have to do is add the account in your working copy of the manifest, and commit and push the changes to the central Git repository. Within 10 minutes, it will automatically be applied to every machine that's running Puppet.

There's more...

Congratulations, you now have a fully-automated Puppet infrastructure! Once you have applied the bootstrap.pp manifest, run Puppet on the repository; the machine will be set up to pull any new changes and apply them automatically.

So, for example, if you wanted to add a new user account to all your machines, all you have to do is add the account in your working copy of the manifest, and commit and push the changes to the central Git repository. Within 10 minutes, it will automatically be applied to every machine that's running Puppet.

Bootstrapping Puppet with bash

Previous versions of this book used Rakefiles to bootstrap Puppet. The problem with using Rake to configure a node is that you are running the commands from your laptop; you assume you already have ssh access to the machine. Most bootstrap processes work by issuing an easy to remember command from a node once it has been provisioned. In this section, we'll show how to use bash to bootstrap Puppet with a web server and a bootstrap script.

Getting ready

Install httpd on a centrally accessible server and create a password protected area to store the bootstrap script. In my example, I'll use the Git server I set up previously, git.example.com. Start by creating a directory in the root of your web server:

# cd /var/www/html
# mkdir bootstrap

Now perform the following steps:

  1. Add the following location definition to your apache configuration:
    <Location /bootstrap>
    AuthType basic
    AuthName "Bootstrap"
    AuthBasicProvider file
    AuthUserFile /var/www/puppet.passwd
    Require valid-user
    </Location>
  2. Reload your web server to ensure the location configuration is operating. Verify with curl that you cannot download from the bootstrap directory without authentication:
    [root@bootstrap-test tmp]# curl http://git.example.com/bootstrap/
    <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
    <html><head>
    <title>401 Authorization Required</title>
    </head><body>
    <h1>Authorization Required</h1>
    
  3. Create the password file you referenced in the apache configuration (/var/www/puppet.passwd):
    root@git# cd /var/www
    root@git# htpasswd –cb puppet.passwd bootstrap cookbook
    Adding password for user bootstrap
    
    
  4. Verify that the username and password permit access to the bootstrap directory as follows:
    [root@node1 tmp]# curl --user bootstrap:cookbook http://git.example.com/bootstrap/
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
    <html>
     <head>
      <title>Index of /bootstrap</title>
    

How to do it...

Now that you have a safe location to store the bootstrap script, create a bootstrap script for each OS you support in the bootstrap directory. In this example, I'll show you how to do this for a Red Hat Enterprise Linux 6-based distribution.

Tip

Although the bootstrap location requires a password, there is no encryption since we haven't configured SSL on our server. Without encryption, the location is not very safe.

Create a script named el6.sh in the bootstrap directory with the following contents:

#!/bin/bash

# bootstrap for EL6 distributions
SERVER=git.example.com
LOCATION=/bootstrap
BOOTSTRAP=bootstrap.pp
USER=bootstrap
PASS=cookbook

# install puppet
curl http://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs >/etc/pki/rpm-gpg/RPM-GPG-KEY-puppetlabs
rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-puppetlabs
yum -y install http://yum.puppetlabs.com/puppetlabs-release-el-6.noarch.rpm
yum -y install puppet
# download bootstrap
curl --user $USER:$PASS http://$SERVER/$LOCATION/$BOOTSTRAP >/tmp/$BOOTSTRAP
# apply bootstrap
cd /tmp
puppet apply /tmp/$BOOTSTRAP
# apply puppet
puppet apply --modulepath /etc/puppet/cookbook/modules /etc/puppet/cookbook/manifests/site.pp

How it works...

The apache configuration only permits access to the bootstrap directory with a username and password combination. We supply these with the --user argument to curl, thereby getting access to the file. We use a pipe (|) to redirect the output of curl into bash. This causes bash to execute the script. We write our bash script like we would any other bash script. The bash script downloads our bootstrap.pp manifest and applies it. Finally, we apply the Puppet manifest from the Git repository and the machine is configured as a member of our decentralized infrastructure.

There's more...

To support another operating system, we only need to create a new bash script. All Linux distributions will support bash scripting, Mac OS X does as well. Since we placed much of our logic into the bootstrap.pp manifest, the bootstrap script is quite minimal and easy to port to new operating systems.

Getting ready

Install httpd on a centrally accessible server and create a password protected area to store the bootstrap script. In my example, I'll use the Git server I set up previously, git.example.com. Start by creating a directory in the root of your web server:

# cd /var/www/html # mkdir bootstrap

Now perform the following steps:

Add the following location definition to your apache configuration:
<Location /bootstrap>
AuthType basic
AuthName "Bootstrap"
AuthBasicProvider file
AuthUserFile /var/www/puppet.passwd
Require valid-user
</Location>
Reload your web server to ensure the location configuration is operating. Verify with curl that you cannot download from the bootstrap directory without authentication:
[root@bootstrap-test tmp]# curl http://git.example.com/bootstrap/
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>401 Authorization Required</title>
</head><body>
<h1>Authorization Required</h1>
Create the password file you referenced in the apache configuration (/var/www/puppet.passwd):
root@git# cd /var/www
root@git# htpasswd –cb puppet.passwd bootstrap cookbook
Adding password for user bootstrap

Verify that the username and password permit access to the bootstrap directory as follows:
[root@node1 tmp]# curl --user bootstrap:cookbook http://git.example.com/bootstrap/
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
 <head>
  <title>Index of /bootstrap</title>

How to do it...

Now that you have a safe location to store the bootstrap script, create a bootstrap script for each OS you support in the bootstrap directory. In this example, I'll show you how to do this for a Red Hat Enterprise Linux 6-based distribution.

Tip

Although the bootstrap location requires a password, there is no encryption since we haven't configured SSL on our server. Without encryption, the location is not very safe.

Create a script named el6.sh in the bootstrap directory with the following contents:

#!/bin/bash

# bootstrap for EL6 distributions
SERVER=git.example.com
LOCATION=/bootstrap
BOOTSTRAP=bootstrap.pp
USER=bootstrap
PASS=cookbook

# install puppet
curl http://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs >/etc/pki/rpm-gpg/RPM-GPG-KEY-puppetlabs
rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-puppetlabs
yum -y install http://yum.puppetlabs.com/puppetlabs-release-el-6.noarch.rpm
yum -y install puppet
# download bootstrap
curl --user $USER:$PASS http://$SERVER/$LOCATION/$BOOTSTRAP >/tmp/$BOOTSTRAP
# apply bootstrap
cd /tmp
puppet apply /tmp/$BOOTSTRAP
# apply puppet
puppet apply --modulepath /etc/puppet/cookbook/modules /etc/puppet/cookbook/manifests/site.pp

How it works...

The apache configuration only permits access to the bootstrap directory with a username and password combination. We supply these with the --user argument to curl, thereby getting access to the file. We use a pipe (|) to redirect the output of curl into bash. This causes bash to execute the script. We write our bash script like we would any other bash script. The bash script downloads our bootstrap.pp manifest and applies it. Finally, we apply the Puppet manifest from the Git repository and the machine is configured as a member of our decentralized infrastructure.

There's more...

To support another operating system, we only need to create a new bash script. All Linux distributions will support bash scripting, Mac OS X does as well. Since we placed much of our logic into the bootstrap.pp manifest, the bootstrap script is quite minimal and easy to port to new operating systems.

How to do it...

Now

that you have a safe location to store the bootstrap script, create a bootstrap script for each OS you support in the bootstrap directory. In this example, I'll show you how to do this for a Red Hat Enterprise Linux 6-based distribution.

Tip

Although the bootstrap location requires a password, there is no encryption since we haven't configured SSL on our server. Without encryption, the location is not very safe.

Create a script named el6.sh in the bootstrap directory with the following contents:

#!/bin/bash

# bootstrap for EL6 distributions
SERVER=git.example.com
LOCATION=/bootstrap
BOOTSTRAP=bootstrap.pp
USER=bootstrap
PASS=cookbook

# install puppet
curl http://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs >/etc/pki/rpm-gpg/RPM-GPG-KEY-puppetlabs
rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-puppetlabs
yum -y install http://yum.puppetlabs.com/puppetlabs-release-el-6.noarch.rpm
yum -y install puppet
# download bootstrap
curl --user $USER:$PASS http://$SERVER/$LOCATION/$BOOTSTRAP >/tmp/$BOOTSTRAP
# apply bootstrap
cd /tmp
puppet apply /tmp/$BOOTSTRAP
# apply puppet
puppet apply --modulepath /etc/puppet/cookbook/modules /etc/puppet/cookbook/manifests/site.pp

How it works...

The apache configuration only permits access to the bootstrap directory with a username and password combination. We supply these with the --user argument to curl, thereby getting access to the file. We use a pipe (|) to redirect the output of curl into bash. This causes bash to execute the script. We write our bash script like we would any other bash script. The bash script downloads our bootstrap.pp manifest and applies it. Finally, we apply the Puppet manifest from the Git repository and the machine is configured as a member of our decentralized infrastructure.

There's more...

To support another operating system, we only need to create a new bash script. All Linux distributions will support bash scripting, Mac OS X does as well. Since we placed much of our logic into the bootstrap.pp manifest, the bootstrap script is quite minimal and easy to port to new operating systems.

How it works...

The

apache configuration only permits access to the bootstrap directory with a username and password combination. We supply these with the --user argument to curl, thereby getting access to the file. We use a pipe (|) to redirect the output of curl into bash. This causes bash to execute the script. We write our bash script like we would any other bash script. The bash script downloads our bootstrap.pp manifest and applies it. Finally, we apply the Puppet manifest from the Git repository and the machine is configured as a member of our decentralized infrastructure.

There's more...

To support another operating system, we only need to create a new bash script. All Linux distributions will support bash scripting, Mac OS X does as well. Since we placed much of our logic into the bootstrap.pp manifest, the bootstrap script is quite minimal and easy to port to new operating systems.

There's more...

To support another operating system, we only need to create a new bash script. All Linux distributions will support bash scripting, Mac OS X does as well. Since we placed much of our logic into the bootstrap.pp manifest, the bootstrap script is quite minimal and easy to port to new operating systems.

Creating a centralized Puppet infrastructure

A configuration management tool such as Puppet is best used when you have many machines to manage. If all the machines can reach a central location, using a centralized Puppet infrastructure might be a good solution. Unfortunately, Puppet doesn't scale well with a large number of nodes. If your deployment has less than 800 servers, a single Puppet master should be able to handle the load, assuming your catalogs are not complex (take less than 10 seconds to compile each catalog). If you have a larger number of nodes, I suggest a load balancing configuration described in Mastering Puppet, Thomas Uphill, Packt Publishing.

A Puppet master is a Puppet server that acts as an X509 certificate authority for Puppet and distributes catalogs (compiled manifests) to client nodes. Puppet ships with a built-in web server called WEBrick, which can handle a very small number of nodes. In this section, we will see how to use that built-in server to control a very small (less than 10) number of nodes.

Getting ready

The Puppet master process is started by running puppet master; most Linux distributions have start and stop scripts for the Puppet master in a separate package. To get started, we'll create a new debian server named puppet.example.com.

How to do it...

  1. Install Puppet on the new server and then use Puppet to install the Puppet master package:
    # puppet resource package puppetmaster ensure='installed' Notice: /Package[puppetmaster]/ensure: created package { 'puppetmaster': ensure => '3.7.0-1puppetlabs1', }
    
  2. Now start the Puppet master service and ensure it will start at boot:
    # puppet resource service puppetmaster ensure=true enable=true service { 'puppetmaster': ensure => 'running', enable => 'true', }
    

How it works...

The Puppet master package includes the start and stop scripts for the Puppet master service. We use Puppet to install the package and start the service. Once the service is started, we can point another node at the Puppet master (you might need to disable the host-based firewall on your machine).

  1. From another node, run puppet agent to start a puppet agent, which will contact the server and request a new certificate:
    t@ckbk:~$ sudo puppet agent -t
    Info: Creating a new SSL key for cookbook.example.com
    Info: Caching certificate for ca
    Info: Creating a new SSL certificate request for cookbook.example.com
    Info: Certificate Request fingerprint (SHA256): 06:C6:2B:C4:97:5D:16:F2:73:82:C4:A9:A7:B1:D0:95:AC:69:7B:27:13:A9:1A:4C:98:20:21:C2:50:48:66:A2
    Info: Caching certificate for ca
    Exiting; no certificate found and waitforcert is disabled
    
  2. Now on the Puppet server, sign the new key:
    root@puppet:~# puppet cert list
    pu  "cookbook.example.com" (SHA256) 06:C6:2B:C4:97:5D:16:F2:73:82:C4:A9:A7:B1:D0:95:AC:69:7B:27:13:A9:1A:4C:98:20:21:C2:50:48:66:A2
    root@puppet:~# puppet cert sign cookbook.example.com
    Notice: Signed certificate request for cookbook.example.com
    Notice: Removing file Puppet::SSL::CertificateRequestcookbook.example.com at'/var/lib/puppet/ssl/ca/requests/cookbook.example.com.pem'
    
  3. Return to the cookbook node and run Puppet again:
    t@ckbk:~$ sudo puppet agent –vt
    Info: Caching certificate for cookbook.example.com
    Info: Caching certificate_revocation_list for ca
    Info: Caching certificate for cookbook.example.comInfo: Retrieving pluginfacts Info: Retrieving plugin Info: Caching catalog for cookbook Info: Applying configuration version '1410401823' Notice: Finished catalog run in 0.04 seconds
    

There's more...

When we ran puppet agent, Puppet looked for a host named puppet.example.com (since our test node is in the example.com domain); if it couldn't find that host, it would then look for a host named Puppet. We can specify the server to contact with the --server option to puppet agent. When we installed the Puppet master package and started the Puppet master service, Puppet created default SSL certificates based on our hostname. In the next section, we'll see how to create an SSL certificate that has multiple DNS names for our Puppet server.

Getting ready

The Puppet master process is started by running puppet master; most Linux distributions have start and stop scripts for the Puppet master in a separate package. To get started, we'll create a new debian server named puppet.example.com.

How to do it...

  1. Install Puppet on the new server and then use Puppet to install the Puppet master package:
    # puppet resource package puppetmaster ensure='installed' Notice: /Package[puppetmaster]/ensure: created package { 'puppetmaster': ensure => '3.7.0-1puppetlabs1', }
    
  2. Now start the Puppet master service and ensure it will start at boot:
    # puppet resource service puppetmaster ensure=true enable=true service { 'puppetmaster': ensure => 'running', enable => 'true', }
    

How it works...

The Puppet master package includes the start and stop scripts for the Puppet master service. We use Puppet to install the package and start the service. Once the service is started, we can point another node at the Puppet master (you might need to disable the host-based firewall on your machine).

  1. From another node, run puppet agent to start a puppet agent, which will contact the server and request a new certificate:
    t@ckbk:~$ sudo puppet agent -t
    Info: Creating a new SSL key for cookbook.example.com
    Info: Caching certificate for ca
    Info: Creating a new SSL certificate request for cookbook.example.com
    Info: Certificate Request fingerprint (SHA256): 06:C6:2B:C4:97:5D:16:F2:73:82:C4:A9:A7:B1:D0:95:AC:69:7B:27:13:A9:1A:4C:98:20:21:C2:50:48:66:A2
    Info: Caching certificate for ca
    Exiting; no certificate found and waitforcert is disabled
    
  2. Now on the Puppet server, sign the new key:
    root@puppet:~# puppet cert list
    pu  "cookbook.example.com" (SHA256) 06:C6:2B:C4:97:5D:16:F2:73:82:C4:A9:A7:B1:D0:95:AC:69:7B:27:13:A9:1A:4C:98:20:21:C2:50:48:66:A2
    root@puppet:~# puppet cert sign cookbook.example.com
    Notice: Signed certificate request for cookbook.example.com
    Notice: Removing file Puppet::SSL::CertificateRequestcookbook.example.com at'/var/lib/puppet/ssl/ca/requests/cookbook.example.com.pem'
    
  3. Return to the cookbook node and run Puppet again:
    t@ckbk:~$ sudo puppet agent –vt
    Info: Caching certificate for cookbook.example.com
    Info: Caching certificate_revocation_list for ca
    Info: Caching certificate for cookbook.example.comInfo: Retrieving pluginfacts Info: Retrieving plugin Info: Caching catalog for cookbook Info: Applying configuration version '1410401823' Notice: Finished catalog run in 0.04 seconds
    

There's more...

When we ran puppet agent, Puppet looked for a host named puppet.example.com (since our test node is in the example.com domain); if it couldn't find that host, it would then look for a host named Puppet. We can specify the server to contact with the --server option to puppet agent. When we installed the Puppet master package and started the Puppet master service, Puppet created default SSL certificates based on our hostname. In the next section, we'll see how to create an SSL certificate that has multiple DNS names for our Puppet server.

How to do it...

Install Puppet on the new server and then use Puppet to install the Puppet master package:
# puppet resource package puppetmaster ensure='installed' Notice: /Package[puppetmaster]/ensure: created package { 'puppetmaster': ensure => '3.7.0-1puppetlabs1', }
Now start the Puppet master service and ensure it will start at boot:
# puppet resource service puppetmaster ensure=true enable=true service { 'puppetmaster': ensure => 'running', enable => 'true', }

How it works...

The Puppet master package includes the start and stop scripts for the Puppet master service. We use Puppet to install the package and start the service. Once the service is started, we can point another node at the Puppet master (you might need to disable the host-based firewall on your machine).

  1. From another node, run puppet agent to start a puppet agent, which will contact the server and request a new certificate:
    t@ckbk:~$ sudo puppet agent -t
    Info: Creating a new SSL key for cookbook.example.com
    Info: Caching certificate for ca
    Info: Creating a new SSL certificate request for cookbook.example.com
    Info: Certificate Request fingerprint (SHA256): 06:C6:2B:C4:97:5D:16:F2:73:82:C4:A9:A7:B1:D0:95:AC:69:7B:27:13:A9:1A:4C:98:20:21:C2:50:48:66:A2
    Info: Caching certificate for ca
    Exiting; no certificate found and waitforcert is disabled
    
  2. Now on the Puppet server, sign the new key:
    root@puppet:~# puppet cert list
    pu  "cookbook.example.com" (SHA256) 06:C6:2B:C4:97:5D:16:F2:73:82:C4:A9:A7:B1:D0:95:AC:69:7B:27:13:A9:1A:4C:98:20:21:C2:50:48:66:A2
    root@puppet:~# puppet cert sign cookbook.example.com
    Notice: Signed certificate request for cookbook.example.com
    Notice: Removing file Puppet::SSL::CertificateRequestcookbook.example.com at'/var/lib/puppet/ssl/ca/requests/cookbook.example.com.pem'
    
  3. Return to the cookbook node and run Puppet again:
    t@ckbk:~$ sudo puppet agent –vt
    Info: Caching certificate for cookbook.example.com
    Info: Caching certificate_revocation_list for ca
    Info: Caching certificate for cookbook.example.comInfo: Retrieving pluginfacts Info: Retrieving plugin Info: Caching catalog for cookbook Info: Applying configuration version '1410401823' Notice: Finished catalog run in 0.04 seconds
    

There's more...

When we ran puppet agent, Puppet looked for a host named puppet.example.com (since our test node is in the example.com domain); if it couldn't find that host, it would then look for a host named Puppet. We can specify the server to contact with the --server option to puppet agent. When we installed the Puppet master package and started the Puppet master service, Puppet created default SSL certificates based on our hostname. In the next section, we'll see how to create an SSL certificate that has multiple DNS names for our Puppet server.

How it works...

The

Puppet master package includes the start and stop scripts for the Puppet master service. We use Puppet to install the package and start the service. Once the service is started, we can point another node at the Puppet master (you might need to disable the host-based firewall on your machine).

  1. From another node, run puppet agent to start a puppet agent, which will contact the server and request a new certificate:
    t@ckbk:~$ sudo puppet agent -t
    Info: Creating a new SSL key for cookbook.example.com
    Info: Caching certificate for ca
    Info: Creating a new SSL certificate request for cookbook.example.com
    Info: Certificate Request fingerprint (SHA256): 06:C6:2B:C4:97:5D:16:F2:73:82:C4:A9:A7:B1:D0:95:AC:69:7B:27:13:A9:1A:4C:98:20:21:C2:50:48:66:A2
    Info: Caching certificate for ca
    Exiting; no certificate found and waitforcert is disabled
    
  2. Now on the Puppet server, sign the new key:
    root@puppet:~# puppet cert list
    pu  "cookbook.example.com" (SHA256) 06:C6:2B:C4:97:5D:16:F2:73:82:C4:A9:A7:B1:D0:95:AC:69:7B:27:13:A9:1A:4C:98:20:21:C2:50:48:66:A2
    root@puppet:~# puppet cert sign cookbook.example.com
    Notice: Signed certificate request for cookbook.example.com
    Notice: Removing file Puppet::SSL::CertificateRequestcookbook.example.com at'/var/lib/puppet/ssl/ca/requests/cookbook.example.com.pem'
    
  3. Return to the cookbook node and run Puppet again:
    t@ckbk:~$ sudo puppet agent –vt
    Info: Caching certificate for cookbook.example.com
    Info: Caching certificate_revocation_list for ca
    Info: Caching certificate for cookbook.example.comInfo: Retrieving pluginfacts Info: Retrieving plugin Info: Caching catalog for cookbook Info: Applying configuration version '1410401823' Notice: Finished catalog run in 0.04 seconds
    

There's more...

When we ran puppet agent, Puppet looked for a host named puppet.example.com (since our test node is in the example.com domain); if it couldn't find that host, it would then look for a host named Puppet. We can specify the server to contact with the --server option to puppet agent. When we installed the Puppet master package and started the Puppet master service, Puppet created default SSL certificates based on our hostname. In the next section, we'll see how to create an SSL certificate that has multiple DNS names for our Puppet server.

There's more...

When we ran puppet agent, Puppet looked for a host named puppet.example.com (since our test node is in the example.com domain); if it couldn't find that host, it would then look for a host named Puppet. We can specify the server to contact with the --server option to puppet agent. When we installed the Puppet master package and started the Puppet master service, Puppet created default SSL certificates based on our hostname. In the next section, we'll see how to create an SSL certificate that has multiple DNS names for our Puppet server.

Creating certificates with multiple DNS names

By default, Puppet will create an SSL certificate for your Puppet master that contains the fully qualified domain name of the server only. Depending on how your network is configured, it can be useful for the server to be known by other names. In this recipe, we'll make a new certificate for our Puppet master that has multiple DNS names.

Getting ready

Install the Puppet master package if you haven't already done so. You will then need to start the Puppet master service at least once to create a certificate authority (CA).

How to do it...

The steps are as follows:

  1. Stop the running Puppet master process with the following command:
    # service puppetmaster stop
    [ ok ] Stopping puppet master.
    
  2. Delete (clean) the current server certificate:
    # puppet cert clean puppet
    Notice: Revoked certificate with serial 6
    Notice: Removing file Puppet::SSL::Certificate puppet at '/var/lib/puppet/ssl/ca/signed/puppet.pem'
    Notice: Removing file Puppet::SSL::Certificate puppet at '/var/lib/puppet/ssl/certs/puppet.pem'
    Notice: Removing file Puppet::SSL::Key puppet at '/var/lib/puppet/ssl/private_keys/puppet.pem'
    
  3. Create a new Puppet certificate using Puppet certificate generate with the --dns-alt-names option:
    root@puppet:~# puppet certificate generate puppet --dns-alt-names puppet.example.com,puppet.example.org,puppet.example.net --ca-location local
    Notice: puppet has a waiting certificate request
    true
    
  4. Sign the new certificate:
    root@puppet:~# puppet cert --allow-dns-alt-names sign puppet
    Notice: Signed certificate request for puppet
    Notice: Removing file Puppet::SSL::CertificateRequest puppet at '/var/lib/puppet/ssl/ca/requests/puppet.pem'
    
  5. Restart the Puppet master process:
    root@puppet:~# service puppetmaster restart
    [ ok ] Restarting puppet master.
    

How it works...

When your puppet agents connect to the Puppet server, they look for a host called Puppet, they then look for a host called Puppet.[your domain]. If your clients are in different domains, then you need your Puppet master to reply to all the names correctly. By removing the existing certificate and generating a new one, you can have your Puppet master reply to multiple DNS names.

Getting ready

Install the Puppet master package if you haven't already done so. You will then need to start the Puppet master service at least once to create

a certificate authority (CA).

How to do it...

The steps are as follows:

  1. Stop the running Puppet master process with the following command:
    # service puppetmaster stop
    [ ok ] Stopping puppet master.
    
  2. Delete (clean) the current server certificate:
    # puppet cert clean puppet
    Notice: Revoked certificate with serial 6
    Notice: Removing file Puppet::SSL::Certificate puppet at '/var/lib/puppet/ssl/ca/signed/puppet.pem'
    Notice: Removing file Puppet::SSL::Certificate puppet at '/var/lib/puppet/ssl/certs/puppet.pem'
    Notice: Removing file Puppet::SSL::Key puppet at '/var/lib/puppet/ssl/private_keys/puppet.pem'
    
  3. Create a new Puppet certificate using Puppet certificate generate with the --dns-alt-names option:
    root@puppet:~# puppet certificate generate puppet --dns-alt-names puppet.example.com,puppet.example.org,puppet.example.net --ca-location local
    Notice: puppet has a waiting certificate request
    true
    
  4. Sign the new certificate:
    root@puppet:~# puppet cert --allow-dns-alt-names sign puppet
    Notice: Signed certificate request for puppet
    Notice: Removing file Puppet::SSL::CertificateRequest puppet at '/var/lib/puppet/ssl/ca/requests/puppet.pem'
    
  5. Restart the Puppet master process:
    root@puppet:~# service puppetmaster restart
    [ ok ] Restarting puppet master.
    

How it works...

When your puppet agents connect to the Puppet server, they look for a host called Puppet, they then look for a host called Puppet.[your domain]. If your clients are in different domains, then you need your Puppet master to reply to all the names correctly. By removing the existing certificate and generating a new one, you can have your Puppet master reply to multiple DNS names.

How to do it...

The steps are as follows:

Stop the running Puppet master process with the following command:
# service puppetmaster stop
[ ok ] Stopping puppet master.
Delete (clean) the current server certificate:
# puppet cert clean puppet
Notice: Revoked certificate with serial 6
Notice: Removing file Puppet::SSL::Certificate puppet at '/var/lib/puppet/ssl/ca/signed/puppet.pem'
Notice: Removing file Puppet::SSL::Certificate puppet at '/var/lib/puppet/ssl/certs/puppet.pem'
Notice: Removing file Puppet::SSL::Key puppet at '/var/lib/puppet/ssl/private_keys/puppet.pem'
Create
  1. a new Puppet certificate using Puppet certificate generate with the --dns-alt-names option:
    root@puppet:~# puppet certificate generate puppet --dns-alt-names puppet.example.com,puppet.example.org,puppet.example.net --ca-location local
    Notice: puppet has a waiting certificate request
    true
    
  2. Sign the new certificate:
    root@puppet:~# puppet cert --allow-dns-alt-names sign puppet
    Notice: Signed certificate request for puppet
    Notice: Removing file Puppet::SSL::CertificateRequest puppet at '/var/lib/puppet/ssl/ca/requests/puppet.pem'
    
  3. Restart the Puppet master process:
    root@puppet:~# service puppetmaster restart
    [ ok ] Restarting puppet master.
    

How it works...

When your puppet agents connect to the Puppet server, they look for a host called Puppet, they then look for a host called Puppet.[your domain]. If your clients are in different domains, then you need your Puppet master to reply to all the names correctly. By removing the existing certificate and generating a new one, you can have your Puppet master reply to multiple DNS names.

How it works...

When your puppet agents connect to the Puppet server, they look for a host called Puppet, they then look for a host called Puppet.[your domain]. If your clients are in different domains, then you need your Puppet master to reply to all the names correctly. By removing the existing certificate and generating a new one, you can have your Puppet master reply to multiple DNS names.

Running Puppet from passenger

The WEBrick server we configured in the previous section is not capable of handling a large number of nodes. To deal with a large number of nodes, a scalable web server is required. Puppet is a ruby process, so we need a way to run a ruby process within a web server. Passenger is the solution to this problem. It allows us to run the Puppet master process within a web server (apache by default). Many distributions ship with a puppetmaster-passenger package that configures this for you. In this section, we'll use the package to configure Puppet to run within passenger.

Getting ready

Install the puppetmaster-passenger package:

# puppet resource package puppetmaster-passenger ensure=installed
Notice: /Package[puppetmaster-passenger]/ensure: ensure changed 'purged'
 to 'present'
package { 'puppetmaster-passenger':
  ensure => '3.7.0-1puppetlabs1',
}

Note

Using puppet resource to install packages ensures the same command will work on multiple distributions (provided the package names are the same).

How to do it...

The steps are as follows:

  1. Ensure the Puppet master site is enabled in your apache configuration. Depending on your distribution this may be at /etc/httpd/conf.d or /etc/apache2/sites-enabled. The configuration file should be created for you and contain the following information:
    PassengerHighPerformance on
    PassengerMaxPoolSize 12
    PassengerPoolIdleTime 1500
    # PassengerMaxRequests 1000
    PassengerStatThrottleRate 120
    RackAutoDetect Off
    RailsAutoDetect Off
    Listen 8140
    
  2. These lines are tuning settings for passenger. The file then instructs apache to listen on port 8140, the Puppet master port. Next a VirtualHost definition is created that loads the Puppet CA certificates and the Puppet master's certificate:
    <VirtualHost *:8140>
            SSLEngine on
            SSLProtocol             ALL -SSLv2 -SSLv3
            SSLCertificateFile      /var/lib/puppet/ssl/certs/puppet.pem
            SSLCertificateKeyFile   /var/lib/puppet/ssl/private_keys/puppet.pem
            SSLCertificateChainFile /var/lib/puppet/ssl/certs/ca.pem
            SSLCACertificateFile    /var/lib/puppet/ssl/certs/ca.pem
            SSLCARevocationFile     /var/lib/puppet/ssl/ca/ca_crl.pem
            SSLVerifyClient optional
            SSLVerifyDepth  1
            SSLOptions +StdEnvVars +ExportCertData
    

    Tip

    You may have more or less lines of SSL configuration here depending on your version of the puppetmaster-passenger package.

  3. Next, a few important headers are set so that the passenger process has access to the SSL information sent by the client node:
    RequestHeader unset X-Forwarded-For
    RequestHeader set X-SSL-Subject %{SSL_CLIENT_S_DN}e
    RequestHeader set X-Client-DN %{SSL_CLIENT_S_DN}e
    RequestHeader set X-Client-Verify %{SSL_CLIENT_VERIFY}e
    
  4. Finally, the location of the passenger configuration file config.ru is given with the DocumentRoot location as follows:
           DocumentRoot /usr/share/puppet/rack/puppetmasterd/public/
            RackBaseURI /
    
  5. The config.ru file should exist at /usr/share/puppet/rack/puppetmasterd/ and should have the following content:
    $0 = "master"
    ARGV << "--rack"
    ARGV << "--confdir" << "/etc/puppet"
    ARGV << "--vardir"  << "/var/lib/puppet"
    require 'puppet/util/command_line'
    run Puppet::Util::CommandLine.new.execute
    
  6. With the passenger apache configuration file in place and the config.ru file correctly configured, start the apache server and verify that apache is listening on the Puppet master port (if you configured the standalone Puppet master previously, you must stop that process now using service puppetmaster stop):
    root@puppet:~ # service apache2 start
    [ ok ] Starting web server: apache2
    root@puppet:~ # lsof -i :8140
    COMMAND  PID     USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
    apache2 9048     root    8u  IPv6  16842      0t0  TCP *:8140 (LISTEN)
    apache2 9069 www-data    8u  IPv6  16842      0t0  TCP *:8140 (LISTEN)
    apache2 9070 www-data    8u  IPv6  16842      0t0  TCP *:8140 (LISTEN)
    

How it works...

The passenger configuration file uses the existing Puppet master certificates to listen on port 8140 and handles all the SSL communication between the server and the client. Once the certificate information has been dealt with, the connection is handed off to a ruby process started from passenger using the command line arguments from the config.ru file.

In this case, the $0 variable is set to master and the arguments variable is set to --rack --confdir /etc/puppet --vardir /var/lib/puppet; this is equivalent to running the following from the command line:

puppet master --rack --confdir /etc/puppet --vardir /var/lib/puppet

There's more...

You can add additional configuration parameters to the config.ru file to further alter how Puppet runs when it's running through passenger. For instance, to enable debugging on the passenger Puppet master, add the following line to config.ru before the run Puppet::Util::CommandLine.new.execute line:

ARGV << "--debug"
Getting ready

Install the puppetmaster-passenger package:

# puppet resource package puppetmaster-passenger ensure=installed Notice: /Package[puppetmaster-passenger]/ensure: ensure changed 'purged' to 'present' package { 'puppetmaster-passenger': ensure => '3.7.0-1puppetlabs1', }

Note

Using puppet resource to install packages ensures the same command will work on multiple distributions (provided the package names are the same).

How to do it...

The steps are as follows:

  1. Ensure the Puppet master site is enabled in your apache configuration. Depending on your distribution this may be at /etc/httpd/conf.d or /etc/apache2/sites-enabled. The configuration file should be created for you and contain the following information:
    PassengerHighPerformance on
    PassengerMaxPoolSize 12
    PassengerPoolIdleTime 1500
    # PassengerMaxRequests 1000
    PassengerStatThrottleRate 120
    RackAutoDetect Off
    RailsAutoDetect Off
    Listen 8140
    
  2. These lines are tuning settings for passenger. The file then instructs apache to listen on port 8140, the Puppet master port. Next a VirtualHost definition is created that loads the Puppet CA certificates and the Puppet master's certificate:
    <VirtualHost *:8140>
            SSLEngine on
            SSLProtocol             ALL -SSLv2 -SSLv3
            SSLCertificateFile      /var/lib/puppet/ssl/certs/puppet.pem
            SSLCertificateKeyFile   /var/lib/puppet/ssl/private_keys/puppet.pem
            SSLCertificateChainFile /var/lib/puppet/ssl/certs/ca.pem
            SSLCACertificateFile    /var/lib/puppet/ssl/certs/ca.pem
            SSLCARevocationFile     /var/lib/puppet/ssl/ca/ca_crl.pem
            SSLVerifyClient optional
            SSLVerifyDepth  1
            SSLOptions +StdEnvVars +ExportCertData
    

    Tip

    You may have more or less lines of SSL configuration here depending on your version of the puppetmaster-passenger package.

  3. Next, a few important headers are set so that the passenger process has access to the SSL information sent by the client node:
    RequestHeader unset X-Forwarded-For
    RequestHeader set X-SSL-Subject %{SSL_CLIENT_S_DN}e
    RequestHeader set X-Client-DN %{SSL_CLIENT_S_DN}e
    RequestHeader set X-Client-Verify %{SSL_CLIENT_VERIFY}e
    
  4. Finally, the location of the passenger configuration file config.ru is given with the DocumentRoot location as follows:
           DocumentRoot /usr/share/puppet/rack/puppetmasterd/public/
            RackBaseURI /
    
  5. The config.ru file should exist at /usr/share/puppet/rack/puppetmasterd/ and should have the following content:
    $0 = "master"
    ARGV << "--rack"
    ARGV << "--confdir" << "/etc/puppet"
    ARGV << "--vardir"  << "/var/lib/puppet"
    require 'puppet/util/command_line'
    run Puppet::Util::CommandLine.new.execute
    
  6. With the passenger apache configuration file in place and the config.ru file correctly configured, start the apache server and verify that apache is listening on the Puppet master port (if you configured the standalone Puppet master previously, you must stop that process now using service puppetmaster stop):
    root@puppet:~ # service apache2 start
    [ ok ] Starting web server: apache2
    root@puppet:~ # lsof -i :8140
    COMMAND  PID     USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
    apache2 9048     root    8u  IPv6  16842      0t0  TCP *:8140 (LISTEN)
    apache2 9069 www-data    8u  IPv6  16842      0t0  TCP *:8140 (LISTEN)
    apache2 9070 www-data    8u  IPv6  16842      0t0  TCP *:8140 (LISTEN)
    

How it works...

The passenger configuration file uses the existing Puppet master certificates to listen on port 8140 and handles all the SSL communication between the server and the client. Once the certificate information has been dealt with, the connection is handed off to a ruby process started from passenger using the command line arguments from the config.ru file.

In this case, the $0 variable is set to master and the arguments variable is set to --rack --confdir /etc/puppet --vardir /var/lib/puppet; this is equivalent to running the following from the command line:

puppet master --rack --confdir /etc/puppet --vardir /var/lib/puppet

There's more...

You can add additional configuration parameters to the config.ru file to further alter how Puppet runs when it's running through passenger. For instance, to enable debugging on the passenger Puppet master, add the following line to config.ru before the run Puppet::Util::CommandLine.new.execute line:

ARGV << "--debug"
How to do it...

The steps are as follows:

Ensure the Puppet master site is enabled in your apache configuration. Depending on your distribution this may be at /etc/httpd/conf.d or /etc/apache2/sites-enabled. The configuration file should be created for you and contain the following information:
PassengerHighPerformance on
PassengerMaxPoolSize 12
PassengerPoolIdleTime 1500
# PassengerMaxRequests 1000
PassengerStatThrottleRate 120
RackAutoDetect Off
RailsAutoDetect Off
Listen 8140
These lines are tuning settings for passenger. The file then instructs apache to listen on port 8140, the Puppet master port. Next a VirtualHost definition is created
  1. that loads the Puppet CA certificates and the Puppet master's certificate:
    <VirtualHost *:8140>
            SSLEngine on
            SSLProtocol             ALL -SSLv2 -SSLv3
            SSLCertificateFile      /var/lib/puppet/ssl/certs/puppet.pem
            SSLCertificateKeyFile   /var/lib/puppet/ssl/private_keys/puppet.pem
            SSLCertificateChainFile /var/lib/puppet/ssl/certs/ca.pem
            SSLCACertificateFile    /var/lib/puppet/ssl/certs/ca.pem
            SSLCARevocationFile     /var/lib/puppet/ssl/ca/ca_crl.pem
            SSLVerifyClient optional
            SSLVerifyDepth  1
            SSLOptions +StdEnvVars +ExportCertData
    

    Tip

    You may have more or less lines of SSL configuration here depending on your version of the puppetmaster-passenger package.

  2. Next, a few important headers are set so that the passenger process has access to the SSL information sent by the client node:
    RequestHeader unset X-Forwarded-For
    RequestHeader set X-SSL-Subject %{SSL_CLIENT_S_DN}e
    RequestHeader set X-Client-DN %{SSL_CLIENT_S_DN}e
    RequestHeader set X-Client-Verify %{SSL_CLIENT_VERIFY}e
    
  3. Finally, the location of the passenger configuration file config.ru is given with the DocumentRoot location as follows:
           DocumentRoot /usr/share/puppet/rack/puppetmasterd/public/
            RackBaseURI /
    
  4. The config.ru file should exist at /usr/share/puppet/rack/puppetmasterd/ and should have the following content:
    $0 = "master"
    ARGV << "--rack"
    ARGV << "--confdir" << "/etc/puppet"
    ARGV << "--vardir"  << "/var/lib/puppet"
    require 'puppet/util/command_line'
    run Puppet::Util::CommandLine.new.execute
    
  5. With the passenger apache configuration file in place and the config.ru file correctly configured, start the apache server and verify that apache is listening on the Puppet master port (if you configured the standalone Puppet master previously, you must stop that process now using service puppetmaster stop):
    root@puppet:~ # service apache2 start
    [ ok ] Starting web server: apache2
    root@puppet:~ # lsof -i :8140
    COMMAND  PID     USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
    apache2 9048     root    8u  IPv6  16842      0t0  TCP *:8140 (LISTEN)
    apache2 9069 www-data    8u  IPv6  16842      0t0  TCP *:8140 (LISTEN)
    apache2 9070 www-data    8u  IPv6  16842      0t0  TCP *:8140 (LISTEN)
    

How it works...

The passenger configuration file uses the existing Puppet master certificates to listen on port 8140 and handles all the SSL communication between the server and the client. Once the certificate information has been dealt with, the connection is handed off to a ruby process started from passenger using the command line arguments from the config.ru file.

In this case, the $0 variable is set to master and the arguments variable is set to --rack --confdir /etc/puppet --vardir /var/lib/puppet; this is equivalent to running the following from the command line:

puppet master --rack --confdir /etc/puppet --vardir /var/lib/puppet

There's more...

You can add additional configuration parameters to the config.ru file to further alter how Puppet runs when it's running through passenger. For instance, to enable debugging on the passenger Puppet master, add the following line to config.ru before the run Puppet::Util::CommandLine.new.execute line:

ARGV << "--debug"
How it works...

The passenger configuration file uses the existing Puppet master certificates to listen on port 8140 and handles all the SSL communication between the server and the client. Once the certificate information has been dealt with, the connection is handed off to a ruby process started from passenger using the command line arguments from the config.ru file.

In this case, the $0 variable is set to master and the arguments variable is set to --rack --confdir /etc/puppet --vardir /var/lib/puppet; this is equivalent to running the following from the command line:

puppet master --rack --confdir /etc/puppet --vardir /var/lib/puppet

There's more...

You can add additional configuration parameters to the config.ru file to further alter how Puppet runs when it's running through passenger. For instance, to enable debugging on the passenger Puppet master, add the following line to config.ru before the run Puppet::Util::CommandLine.new.execute line:

ARGV << "--debug"
There's more...

You can add additional configuration parameters to the config.ru file to further alter how Puppet

runs when it's running through passenger. For instance, to enable debugging on the passenger Puppet master, add the following line to config.ru before the run Puppet::Util::CommandLine.new.execute line:

ARGV << "--debug"

Setting up the environment

Environments in Puppet are directories holding different versions of your Puppet manifests. Environments prior to Version 3.6 of Puppet were not a default configuration for Puppet. In newer versions of Puppet, environments are configured by default.

Whenever a node connects to a Puppet master, it informs the Puppet master of its environment. By default, all nodes report to the production environment. This causes the Puppet master to look in the production environment for manifests. You may specify an alternate environment with the --environment setting when running puppet agent or by setting environment = newenvironment in /etc/puppet/puppet.conf in the [agent] section.

Getting ready

Set the environmentpath function of your installation by adding a line to the [main] section of /etc/puppet/puppet.conf as follows:

[main]
...
environmentpath=/etc/puppet/environments

How to do it...

The steps are as follows:

  1. Create a production directory at /etc/puppet/environments that contains both a modules and manifests directory. Then create a site.pp which creates a file in /tmp as follows:
    root@puppet:~# cd /etc/puppet/environments/
    root@puppet:/etc/puppet/environments# mkdir -p production/{manifests,modules}
    root@puppet:/etc/puppet/environments# vim production/manifests/site.pp
    node default {
      file {'/tmp/production':
        content => "Hello World!\nThis is production\n",
      }
    }
    
  2. Run puppet agent on the master to connect to it and verify that the production code was delivered:
    root@puppet:~# puppet agent -vt
    Info: Retrieving pluginfacts
    Info: Retrieving plugin
    Info: Caching catalog for puppet
    Info: Applying configuration version '1410415538'
    Notice: /Stage[main]/Main/Node[default]/File[/tmp/production]/ensure: defined content as '{md5}f7ad9261670b9da33a67a5126933044c'
    Notice: Finished catalog run in 0.04 seconds
    # cat /tmp/production
    Hello World!
    This is production
    
  3. Configure another environment devel. Create a new manifest in the devel environment:
    root@puppet:/etc/puppet/environments# mkdir -p devel/{manifests,modules}
    root@puppet:/etc/puppet/environments# vim devel/manifests/site.pp
    node default {
      file {'/tmp/devel':
        content => "Good-bye! Development\n",
      }
    }
    
  4. Apply the new environment by running the --environment devel puppet agent using the following command:
    root@puppet:/etc/puppet/environments# puppet agent -vt --environment devel
    Info: Retrieving pluginfacts
    Info: Retrieving plugin
    Info: Caching catalog for puppet
    Info: Applying configuration version '1410415890'
    Notice: /Stage[main]/Main/Node[default]/File[/tmp/devel]/ensure: defined content as '{md5}b6313bb89bc1b7d97eae5aa94588eb68'
    Notice: Finished catalog run in 0.04 seconds
    root@puppet:/etc/puppet/environments# cat /tmp/devel
    Good-bye! Development
    

Tip

You may need to restart apache2 to enable your new environment, this depends on your version of Puppet and the environment_timeout parameter of puppet.conf.

There's more...

Each environment can have its own modulepath if you create an environment.conf file within the environment directory. More information on environments can be found on the Puppet labs website at https://docs.puppetlabs.com/puppet/latest/reference/environments.html.

Getting ready

Set the environmentpath function of your installation by adding a line to the [main] section of /etc/puppet/puppet.conf as follows:

[main] ... environmentpath=/etc/puppet/environments

How to do it...

The steps are as follows:

  1. Create a production directory at /etc/puppet/environments that contains both a modules and manifests directory. Then create a site.pp which creates a file in /tmp as follows:
    root@puppet:~# cd /etc/puppet/environments/
    root@puppet:/etc/puppet/environments# mkdir -p production/{manifests,modules}
    root@puppet:/etc/puppet/environments# vim production/manifests/site.pp
    node default {
      file {'/tmp/production':
        content => "Hello World!\nThis is production\n",
      }
    }
    
  2. Run puppet agent on the master to connect to it and verify that the production code was delivered:
    root@puppet:~# puppet agent -vt
    Info: Retrieving pluginfacts
    Info: Retrieving plugin
    Info: Caching catalog for puppet
    Info: Applying configuration version '1410415538'
    Notice: /Stage[main]/Main/Node[default]/File[/tmp/production]/ensure: defined content as '{md5}f7ad9261670b9da33a67a5126933044c'
    Notice: Finished catalog run in 0.04 seconds
    # cat /tmp/production
    Hello World!
    This is production
    
  3. Configure another environment devel. Create a new manifest in the devel environment:
    root@puppet:/etc/puppet/environments# mkdir -p devel/{manifests,modules}
    root@puppet:/etc/puppet/environments# vim devel/manifests/site.pp
    node default {
      file {'/tmp/devel':
        content => "Good-bye! Development\n",
      }
    }
    
  4. Apply the new environment by running the --environment devel puppet agent using the following command:
    root@puppet:/etc/puppet/environments# puppet agent -vt --environment devel
    Info: Retrieving pluginfacts
    Info: Retrieving plugin
    Info: Caching catalog for puppet
    Info: Applying configuration version '1410415890'
    Notice: /Stage[main]/Main/Node[default]/File[/tmp/devel]/ensure: defined content as '{md5}b6313bb89bc1b7d97eae5aa94588eb68'
    Notice: Finished catalog run in 0.04 seconds
    root@puppet:/etc/puppet/environments# cat /tmp/devel
    Good-bye! Development
    

Tip

You may need to restart apache2 to enable your new environment, this depends on your version of Puppet and the environment_timeout parameter of puppet.conf.

There's more...

Each environment can have its own modulepath if you create an environment.conf file within the environment directory. More information on environments can be found on the Puppet labs website at https://docs.puppetlabs.com/puppet/latest/reference/environments.html.

How to do it...

The steps are as follows:

Create a production directory at /etc/puppet/environments that contains both a modules and manifests directory. Then create a site.pp which creates a file in /tmp as follows:
root@puppet:~# cd /etc/puppet/environments/
root@puppet:/etc/puppet/environments# mkdir -p production/{manifests,modules}
root@puppet:/etc/puppet/environments# vim production/manifests/site.pp
node default {
  file {'/tmp/production':
    content => "Hello World!\nThis is production\n",
  }
}
Run puppet agent on the master to connect to it and verify that the production code was delivered:
root@puppet:~# puppet agent -vt
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Caching catalog for puppet
Info: Applying configuration version '1410415538'
Notice: /Stage[main]/Main/Node[default]/File[/tmp/production]/ensure: defined content as '{md5}f7ad9261670b9da33a67a5126933044c'
Notice: Finished catalog run in 0.04 seconds
# cat /tmp/production
Hello World!
This is production
Configure
  1. another environment devel. Create a new manifest in the devel environment:
    root@puppet:/etc/puppet/environments# mkdir -p devel/{manifests,modules}
    root@puppet:/etc/puppet/environments# vim devel/manifests/site.pp
    node default {
      file {'/tmp/devel':
        content => "Good-bye! Development\n",
      }
    }
    
  2. Apply the new environment by running the --environment devel puppet agent using the following command:
    root@puppet:/etc/puppet/environments# puppet agent -vt --environment devel
    Info: Retrieving pluginfacts
    Info: Retrieving plugin
    Info: Caching catalog for puppet
    Info: Applying configuration version '1410415890'
    Notice: /Stage[main]/Main/Node[default]/File[/tmp/devel]/ensure: defined content as '{md5}b6313bb89bc1b7d97eae5aa94588eb68'
    Notice: Finished catalog run in 0.04 seconds
    root@puppet:/etc/puppet/environments# cat /tmp/devel
    Good-bye! Development
    

Tip

You may need to restart apache2 to enable your new environment, this depends on your version of Puppet and the environment_timeout parameter of puppet.conf.

There's more...

Each environment can have its own modulepath if you create an environment.conf file within the environment directory. More information on environments can be found on the Puppet labs website at https://docs.puppetlabs.com/puppet/latest/reference/environments.html.

There's more...

Each

environment can have its own modulepath if you create an environment.conf file within the environment directory. More information on environments can be found on the Puppet labs website at https://docs.puppetlabs.com/puppet/latest/reference/environments.html.

Configuring PuppetDB

PuppetDB is a database for Puppet that is used to store information about nodes connected to a Puppet master. PuppetDB is also a storage area for exported resources. Exported resources are resources that are defined on nodes but applied to other nodes. The simplest way to install PuppetDB is to use the PuppetDB module from Puppet labs. From this point on, we'll assume you are using the puppet.example.com machine and have a passenger-based configuration of Puppet.

Getting ready

Install the PuppetDB module in the production environment you created in the previous recipe. If you didn't create directory environments, don't worry, using puppet module install will install the module to the correct location for your installation with the following command:

root@puppet:~# puppet module install puppetlabs-puppetdb
Notice: Preparing to install into /etc/puppet/environments/production/modules ...
Notice: Downloading from https://forgeapi.puppetlabs.com ...
Notice: Installing -- do not interrupt ...
/etc/puppet/environments/production/modules
└─┬ puppetlabs-puppetdb (v3.0.1)
  ├── puppetlabs-firewall (v1.1.3)
  ├── puppetlabs-inifile (v1.1.3)
  └─┬ puppetlabs-postgresql (v3.4.2)
    ├─┬ puppetlabs-apt (v1.6.0)
    │ └── puppetlabs-stdlib (v4.3.2)
    └── puppetlabs-concat (v1.1.0)

How to do it...

Now that our Puppet master has the PuppetDB module installed, we need to apply the PuppetDB module to our Puppet master, we can do this in the site manifest. Add the following to your (production) site.pp:

node puppet {
  class { 'puppetdb': }
  class { 'puppetdb::master::config': 
    puppet_service_name => 'apache2',
  }
}

Run puppet agent to apply the puppetdb class and the puppetdb::master::config class:

root@puppet:~# puppet agent -t
Info: Caching catalog for puppet
Info: Applying configuration version '1410416952'
...
Info: Class[Puppetdb::Server::Jetty_ini]: Scheduling refresh of Service[puppetdb]
Notice: Finished catalog run in 160.78 seconds

How it works...

The PuppetDB module is a great example of how a complex configuration task can be puppetized. Simply by adding the puppetdb class to our Puppet master node, Puppet installed and configured postgresql and puppetdb.

When we called the puppetdb::master::config class, we set the puppet_service_name variable to apache2, this is because we are running Puppet through passenger. Without this line our agent would try to start the puppetmaster process instead of apache2.

The agent then set up the configuration files for PuppetDB and configured Puppet to use PuppetDB. If you look at /etc/puppet/puppet.conf, you'll see the following two new lines:

storeconfigs = true
storeconfigs_backend = puppetdb

There's more...

Now that PuppetDB is configured and we've had a successful agent run, PuppetDB will have data we can query:

root@puppet:~# puppet node status puppet
puppet
Currently active
Last catalog: 2014-09-11T06:45:25.267Z
Last facts: 2014-09-11T06:45:22.351Z
Getting ready

Install the PuppetDB module in the production environment you created in the previous recipe. If you didn't create directory environments, don't worry, using puppet module install will install the module to the correct location for your installation with the following command:

root@puppet:~# puppet module install puppetlabs-puppetdb Notice: Preparing to install into /etc/puppet/environments/production/modules ... Notice: Downloading from https://forgeapi.puppetlabs.com ... Notice: Installing -- do not interrupt ... /etc/puppet/environments/production/modules └─┬ puppetlabs-puppetdb (v3.0.1) ├── puppetlabs-firewall (v1.1.3) ├── puppetlabs-inifile (v1.1.3) └─┬ puppetlabs-postgresql (v3.4.2) ├─┬ puppetlabs-apt (v1.6.0) │ └── puppetlabs-stdlib (v4.3.2) └── puppetlabs-concat (v1.1.0)

How to do it...

Now that our Puppet master has the PuppetDB module installed, we need to apply the PuppetDB module to our Puppet master, we can do this in the site manifest. Add the following to your (production) site.pp:

node puppet {
  class { 'puppetdb': }
  class { 'puppetdb::master::config': 
    puppet_service_name => 'apache2',
  }
}

Run puppet agent to apply the puppetdb class and the puppetdb::master::config class:

root@puppet:~# puppet agent -t
Info: Caching catalog for puppet
Info: Applying configuration version '1410416952'
...
Info: Class[Puppetdb::Server::Jetty_ini]: Scheduling refresh of Service[puppetdb]
Notice: Finished catalog run in 160.78 seconds

How it works...

The PuppetDB module is a great example of how a complex configuration task can be puppetized. Simply by adding the puppetdb class to our Puppet master node, Puppet installed and configured postgresql and puppetdb.

When we called the puppetdb::master::config class, we set the puppet_service_name variable to apache2, this is because we are running Puppet through passenger. Without this line our agent would try to start the puppetmaster process instead of apache2.

The agent then set up the configuration files for PuppetDB and configured Puppet to use PuppetDB. If you look at /etc/puppet/puppet.conf, you'll see the following two new lines:

storeconfigs = true
storeconfigs_backend = puppetdb

There's more...

Now that PuppetDB is configured and we've had a successful agent run, PuppetDB will have data we can query:

root@puppet:~# puppet node status puppet
puppet
Currently active
Last catalog: 2014-09-11T06:45:25.267Z
Last facts: 2014-09-11T06:45:22.351Z
How to do it...

Now that our Puppet master has the PuppetDB module installed, we need to apply the PuppetDB module to our Puppet master, we can do this in the site manifest. Add the following to

your (production) site.pp:

node puppet {
  class { 'puppetdb': }
  class { 'puppetdb::master::config': 
    puppet_service_name => 'apache2',
  }
}

Run puppet agent to apply the puppetdb class and the puppetdb::master::config class:

root@puppet:~# puppet agent -t
Info: Caching catalog for puppet
Info: Applying configuration version '1410416952'
...
Info: Class[Puppetdb::Server::Jetty_ini]: Scheduling refresh of Service[puppetdb]
Notice: Finished catalog run in 160.78 seconds

How it works...

The PuppetDB module is a great example of how a complex configuration task can be puppetized. Simply by adding the puppetdb class to our Puppet master node, Puppet installed and configured postgresql and puppetdb.

When we called the puppetdb::master::config class, we set the puppet_service_name variable to apache2, this is because we are running Puppet through passenger. Without this line our agent would try to start the puppetmaster process instead of apache2.

The agent then set up the configuration files for PuppetDB and configured Puppet to use PuppetDB. If you look at /etc/puppet/puppet.conf, you'll see the following two new lines:

storeconfigs = true
storeconfigs_backend = puppetdb

There's more...

Now that PuppetDB is configured and we've had a successful agent run, PuppetDB will have data we can query:

root@puppet:~# puppet node status puppet
puppet
Currently active
Last catalog: 2014-09-11T06:45:25.267Z
Last facts: 2014-09-11T06:45:22.351Z
How it works...

The PuppetDB module is a great example of how a complex configuration task can be puppetized. Simply by adding the puppetdb class to our Puppet master node, Puppet installed and configured postgresql and puppetdb.

When we called the puppetdb::master::config class, we set the puppet_service_name variable to apache2, this is because we are running Puppet through passenger. Without this line our agent would try to start the puppetmaster process instead of apache2.

The agent then set up the configuration files for PuppetDB and configured Puppet to use PuppetDB. If

you look at /etc/puppet/puppet.conf, you'll see the following two new lines:

storeconfigs = true
storeconfigs_backend = puppetdb

There's more...

Now that PuppetDB is configured and we've had a successful agent run, PuppetDB will have data we can query:

root@puppet:~# puppet node status puppet
puppet
Currently active
Last catalog: 2014-09-11T06:45:25.267Z
Last facts: 2014-09-11T06:45:22.351Z
There's more...

Now that PuppetDB is configured and we've had a successful agent run, PuppetDB will have data we can query:

root@puppet:~# puppet node status puppet puppet Currently active Last catalog: 2014-09-11T06:45:25.267Z Last facts: 2014-09-11T06:45:22.351Z

Configuring Hiera

Hiera is an information repository for Puppet. Using Hiera you can have a hierarchical categorization of data about your nodes that is maintained outside of your manifests. This is very useful for sharing code and dealing with exceptions that will creep into any Puppet deployment.

Getting ready

Hiera should have already been installed as a dependency on your Puppet master. If it has not already, install it using Puppet:

root@puppet:~# puppet resource package hiera ensure=installed
package { 'hiera':
  ensure => '1.3.4-1puppetlabs1',
}

How to do it...

  1. Hiera is configured from a yaml file, /etc/puppet/hiera.yaml. Create the file and add the following as a minimal configuration:
    ---
    :hierarchy:
      - common
    :backends:
      - yaml
    :yaml:
      :datadir: '/etc/puppet/hieradata'
    
  2. Create the common.yaml file referenced in the hierarchy:
    root@puppet:/etc/puppet# mkdir hieradata
    root@puppet:/etc/puppet# vim hieradata/common.yaml
    ---
    message: 'Default Message'
    
  3. Edit the site.pp file and add a notify resource based on the Hiera value:
    node default {
      $message = hiera('message','unknown')
      notify {"Message is $message":}
    }
    
  4. Apply the manifest to a test node:
    t@ckbk:~$ sudo puppet agent -t
    Info: Retrieving pluginfacts
    Info: Retrieving plugin
    ...
    Info: Caching catalog for cookbook-test
    Info: Applying configuration version '1410504848'
    Notice: Message is Default Message
    Notice: /Stage[main]/Main/Node[default]/Notify[Message is Default Message]/message: defined 'message' as 'Message is Default Message'
    Notice: Finished catalog run in 0.06 seconds
    

How it works...

Hiera uses a hierarchy to search through a set of yaml files to find the appropriate values. We defined this hierarchy in hiera.yaml with the single entry for common.yaml. We used the hiera function in site.pp to lookup the value for message and store that value in the variable $message. The values used for the definition of the hierarchy can be any facter facts defined about the system. A common hierarchy is shown as:

:hierarchy:
  - hosts/%{hostname}
  - os/%{operatingsystem}
  - network/%{network_eth0}
  - common

There's more...

Hiera can be used for automatic parameter lookup with parameterized classes. For example, if you have a class named cookbook::example with a parameter named publisher, you can include the following in a Hiera yaml file to automatically set this parameter:

cookbook::example::publisher: 'PacktPub'

Another often used fact is environment you may reference the environment of the client node using %{environment} as shown in the following hierarchy:

:hierarchy:
hosts/%{hostname}
os/%{operatingsystem}
environment/%{environment}
common

Tip

A good rule of thumb is to limit the hierarchy to 8 levels or less. Keep in mind that each time a parameter is searched with Hiera, all the levels are searched until a match is found.

The default Hiera function returns the first match to the search key, you can also use hiera_array and hiera_hash to search and return all values stored in Hiera.

Hiera can also be searched from the command line as shown in the following command line (note that currently the command line Hiera utility uses /etc/hiera.yaml as its configuration file whereas the Puppet master uses /etc/puppet/hiera.yaml):

root@puppet:/etc/puppet# rm /etc/hiera.yaml 
root@puppet:/etc/puppet# ln -s /etc/puppet/hiera.yaml /etc/
root@puppet:/etc/puppet# hiera message
Default Message

Note

For more information, consult the Puppet labs website at https://docs.puppetlabs.com/hiera/1/.

Getting ready

Hiera should have already been installed as a dependency on your Puppet master. If it has not already, install it using Puppet:

root@puppet:~# puppet resource package hiera ensure=installed package { 'hiera': ensure => '1.3.4-1puppetlabs1', }

How to do it...

  1. Hiera is configured from a yaml file, /etc/puppet/hiera.yaml. Create the file and add the following as a minimal configuration:
    ---
    :hierarchy:
      - common
    :backends:
      - yaml
    :yaml:
      :datadir: '/etc/puppet/hieradata'
    
  2. Create the common.yaml file referenced in the hierarchy:
    root@puppet:/etc/puppet# mkdir hieradata
    root@puppet:/etc/puppet# vim hieradata/common.yaml
    ---
    message: 'Default Message'
    
  3. Edit the site.pp file and add a notify resource based on the Hiera value:
    node default {
      $message = hiera('message','unknown')
      notify {"Message is $message":}
    }
    
  4. Apply the manifest to a test node:
    t@ckbk:~$ sudo puppet agent -t
    Info: Retrieving pluginfacts
    Info: Retrieving plugin
    ...
    Info: Caching catalog for cookbook-test
    Info: Applying configuration version '1410504848'
    Notice: Message is Default Message
    Notice: /Stage[main]/Main/Node[default]/Notify[Message is Default Message]/message: defined 'message' as 'Message is Default Message'
    Notice: Finished catalog run in 0.06 seconds
    

How it works...

Hiera uses a hierarchy to search through a set of yaml files to find the appropriate values. We defined this hierarchy in hiera.yaml with the single entry for common.yaml. We used the hiera function in site.pp to lookup the value for message and store that value in the variable $message. The values used for the definition of the hierarchy can be any facter facts defined about the system. A common hierarchy is shown as:

:hierarchy:
  - hosts/%{hostname}
  - os/%{operatingsystem}
  - network/%{network_eth0}
  - common

There's more...

Hiera can be used for automatic parameter lookup with parameterized classes. For example, if you have a class named cookbook::example with a parameter named publisher, you can include the following in a Hiera yaml file to automatically set this parameter:

cookbook::example::publisher: 'PacktPub'

Another often used fact is environment you may reference the environment of the client node using %{environment} as shown in the following hierarchy:

:hierarchy:
hosts/%{hostname}
os/%{operatingsystem}
environment/%{environment}
common

Tip

A good rule of thumb is to limit the hierarchy to 8 levels or less. Keep in mind that each time a parameter is searched with Hiera, all the levels are searched until a match is found.

The default Hiera function returns the first match to the search key, you can also use hiera_array and hiera_hash to search and return all values stored in Hiera.

Hiera can also be searched from the command line as shown in the following command line (note that currently the command line Hiera utility uses /etc/hiera.yaml as its configuration file whereas the Puppet master uses /etc/puppet/hiera.yaml):

root@puppet:/etc/puppet# rm /etc/hiera.yaml 
root@puppet:/etc/puppet# ln -s /etc/puppet/hiera.yaml /etc/
root@puppet:/etc/puppet# hiera message
Default Message

Note

For more information, consult the Puppet labs website at https://docs.puppetlabs.com/hiera/1/.

How to do it...

Hiera is configured from a yaml file, /etc/puppet/hiera.yaml. Create the file and add the following as a minimal configuration:
---
:hierarchy:
  - common
:backends:
  - yaml
:yaml:
  :datadir: '/etc/puppet/hieradata'
Create the common.yaml file referenced in the hierarchy:
root@puppet:/etc/puppet# mkdir hieradata
root@puppet:/etc/puppet# vim hieradata/common.yaml
---
message: 'Default Message'
Edit the site.pp file and add a notify resource based on the Hiera value:
node default {
  $message = hiera('message','unknown')
  notify {"Message is $message":}
}
Apply the manifest to a test node:
t@ckbk:~$ sudo puppet agent -t
Info: Retrieving pluginfacts
Info: Retrieving plugin
...
Info: Caching catalog for cookbook-test
Info: Applying configuration version '1410504848'
Notice: Message is Default Message
Notice: /Stage[main]/Main/Node[default]/Notify[Message is Default Message]/message: defined 'message' as 'Message is Default Message'
Notice: Finished catalog run in 0.06 seconds

How it works...

Hiera uses a hierarchy to search through a set of yaml files to find the appropriate values. We defined this hierarchy in hiera.yaml with the single entry for common.yaml. We used the hiera function in site.pp to lookup the value for message and store that value in the variable $message. The values used for the definition of the hierarchy can be any facter facts defined about the system. A common hierarchy is shown as:

:hierarchy:
  - hosts/%{hostname}
  - os/%{operatingsystem}
  - network/%{network_eth0}
  - common

There's more...

Hiera can be used for automatic parameter lookup with parameterized classes. For example, if you have a class named cookbook::example with a parameter named publisher, you can include the following in a Hiera yaml file to automatically set this parameter:

cookbook::example::publisher: 'PacktPub'

Another often used fact is environment you may reference the environment of the client node using %{environment} as shown in the following hierarchy:

:hierarchy:
hosts/%{hostname}
os/%{operatingsystem}
environment/%{environment}
common

Tip

A good rule of thumb is to limit the hierarchy to 8 levels or less. Keep in mind that each time a parameter is searched with Hiera, all the levels are searched until a match is found.

The default Hiera function returns the first match to the search key, you can also use hiera_array and hiera_hash to search and return all values stored in Hiera.

Hiera can also be searched from the command line as shown in the following command line (note that currently the command line Hiera utility uses /etc/hiera.yaml as its configuration file whereas the Puppet master uses /etc/puppet/hiera.yaml):

root@puppet:/etc/puppet# rm /etc/hiera.yaml 
root@puppet:/etc/puppet# ln -s /etc/puppet/hiera.yaml /etc/
root@puppet:/etc/puppet# hiera message
Default Message

Note

For more information, consult the Puppet labs website at https://docs.puppetlabs.com/hiera/1/.

How it works...

Hiera uses

a hierarchy to search through a set of yaml files to find the appropriate values. We defined this hierarchy in hiera.yaml with the single entry for common.yaml. We used the hiera function in site.pp to lookup the value for message and store that value in the variable $message. The values used for the definition of the hierarchy can be any facter facts defined about the system. A common hierarchy is shown as:

:hierarchy:
  - hosts/%{hostname}
  - os/%{operatingsystem}
  - network/%{network_eth0}
  - common

There's more...

Hiera can be used for automatic parameter lookup with parameterized classes. For example, if you have a class named cookbook::example with a parameter named publisher, you can include the following in a Hiera yaml file to automatically set this parameter:

cookbook::example::publisher: 'PacktPub'

Another often used fact is environment you may reference the environment of the client node using %{environment} as shown in the following hierarchy:

:hierarchy:
hosts/%{hostname}
os/%{operatingsystem}
environment/%{environment}
common

Tip

A good rule of thumb is to limit the hierarchy to 8 levels or less. Keep in mind that each time a parameter is searched with Hiera, all the levels are searched until a match is found.

The default Hiera function returns the first match to the search key, you can also use hiera_array and hiera_hash to search and return all values stored in Hiera.

Hiera can also be searched from the command line as shown in the following command line (note that currently the command line Hiera utility uses /etc/hiera.yaml as its configuration file whereas the Puppet master uses /etc/puppet/hiera.yaml):

root@puppet:/etc/puppet# rm /etc/hiera.yaml 
root@puppet:/etc/puppet# ln -s /etc/puppet/hiera.yaml /etc/
root@puppet:/etc/puppet# hiera message
Default Message

Note

For more information, consult the Puppet labs website at https://docs.puppetlabs.com/hiera/1/.

There's more...

Hiera can be used for automatic parameter lookup with parameterized classes. For example, if you have a class named cookbook::example with a parameter named publisher, you can include the following in a Hiera yaml file to automatically set this parameter:

cookbook::example::publisher: 'PacktPub'

Another often used fact is environment you may reference the environment of the client node using %{environment} as shown in the following hierarchy:

:hierarchy: hosts/%{hostname} os/%{operatingsystem} environment/%{environment} common

Tip

A good rule of thumb is to limit the hierarchy to 8 levels or less. Keep in mind that each time a parameter is searched with Hiera, all the levels are searched until a match is found.

The default Hiera function returns the first match to the search key, you can also use hiera_array and hiera_hash to search and return all values stored in Hiera.

Hiera can also be searched from the command line as shown in the following command line (note that currently the command line Hiera utility uses /etc/hiera.yaml as its configuration file whereas the Puppet master uses /etc/puppet/hiera.yaml):

root@puppet:/etc/puppet# rm /etc/hiera.yaml 
root@puppet:/etc/puppet# ln -s /etc/puppet/hiera.yaml /etc/
root@puppet:/etc/puppet# hiera message
Default Message

Note

For more information, consult the Puppet labs website at https://docs.puppetlabs.com/hiera/1/.

Setting node-specific data with Hiera

In our hierarchy defined in hiera.yaml, we created an entry based on the hostname fact; in this section, we'll create yaml files in the hosts subdirectory of Hiera data with information specific to a particular host.

Getting ready

Install and configure Hiera as in the last section and use the hierarchy defined in the previous recipe that includes a hosts/%{hostname} entry.

How to do it...

The following are the steps:

  1. Create a file at /etc/puppet/hieradata/hosts that is the hostname of your test node. For example if your host is named cookbook-test, then the file would be named cookbook-test.yaml.
  2. Insert a specific message in this file:
    message: 'This is the test node for the cookbook'
  3. Run Puppet on two different test nodes to note the difference:
    t@ckbk:~$ sudo puppet agent -t
    Info: Caching catalog for cookbook-test
    Notice: Message is This is the test node for the cookbook
    [root@hiera-test ~]# puppet agent -t
    Info: Caching catalog for hiera-test.example.com
    Notice: Message is Default Message
    

How it works...

Hiera searches the hierarchy for files that match the values returned by facter. In this case, the cookbook-test.yaml file is found by substituting the hostname of the node into the search path /etc/puppet/hieradata/hosts/%{hostname}.yaml.

Using Hiera, it is possible to greatly reduce the complexity of your Puppet code. We will use yaml files for separate values, where previously you had large case statements or nested if statements.

Getting ready

Install and configure Hiera as in the last section and use the hierarchy defined in the previous recipe that includes a hosts/%{hostname} entry.

How to do it...

The following are the steps:

  1. Create a file at /etc/puppet/hieradata/hosts that is the hostname of your test node. For example if your host is named cookbook-test, then the file would be named cookbook-test.yaml.
  2. Insert a specific message in this file:
    message: 'This is the test node for the cookbook'
  3. Run Puppet on two different test nodes to note the difference:
    t@ckbk:~$ sudo puppet agent -t
    Info: Caching catalog for cookbook-test
    Notice: Message is This is the test node for the cookbook
    [root@hiera-test ~]# puppet agent -t
    Info: Caching catalog for hiera-test.example.com
    Notice: Message is Default Message
    

How it works...

Hiera searches the hierarchy for files that match the values returned by facter. In this case, the cookbook-test.yaml file is found by substituting the hostname of the node into the search path /etc/puppet/hieradata/hosts/%{hostname}.yaml.

Using Hiera, it is possible to greatly reduce the complexity of your Puppet code. We will use yaml files for separate values, where previously you had large case statements or nested if statements.

How to do it...

The following are the steps:

Create a file at /etc/puppet/hieradata/hosts that is the hostname of your test node. For example if your host is named cookbook-test, then the file would be named cookbook-test.yaml.
Insert a specific message in this file:
message: 'This is the test node for the cookbook'
Run Puppet on two different test nodes to note the difference:
t@ckbk:~$ sudo puppet agent -t
Info: Caching catalog for cookbook-test
Notice: Message is This is the test node for the cookbook
[root@hiera-test ~]# puppet agent -t
Info: Caching catalog for hiera-test.example.com
Notice: Message is Default Message

How it works...

Hiera searches the hierarchy for files that match the values returned by facter. In this case, the cookbook-test.yaml file is found by substituting the hostname of the node into the search path /etc/puppet/hieradata/hosts/%{hostname}.yaml.

Using Hiera, it is possible to greatly reduce the complexity of your Puppet code. We will use yaml files for separate values, where previously you had large case statements or nested if statements.

How it works...

Hiera

searches the hierarchy for files that match the values returned by facter. In this case, the cookbook-test.yaml file is found by substituting the hostname of the node into the search path /etc/puppet/hieradata/hosts/%{hostname}.yaml.

Using Hiera, it is possible to greatly reduce the complexity of your Puppet code. We will use yaml files for separate values, where previously you had large case statements or nested if statements.

Storing secret data with hiera-gpg

If you're using Hiera to store your configuration data, there's a gem available called hiera-gpg that adds an encryption backend to Hiera to allow you to protect values stored in Hiera.

Getting ready

To set up hiera-gpg, follow these steps:

  1. Install the ruby-dev package; it will be required to build the hiera-gpg gem as follows:
    root@puppet:~# puppet resource package ruby-dev ensure=installed
    Notice: /Package[ruby-dev]/ensure: ensure changed 'purged' to 'present'
    package { 'ruby-dev':
      ensure => '1:1.9.3',
    }
    
  2. Install the hiera-gpg gem using the gem provider:
    root@puppet:~# puppet resource package hiera-gpg ensure=installed provider=gem
    Notice: /Package[hiera-gpg]/ensure: created
    package { 'hiera-gpg':
      ensure => ['1.1.0'],
    }
    
  3. Modify your hiera.yaml file as follows:
        :hierarchy:
            - secret
            - common
        :backends:
            - yaml
            - gpg
        :yaml:
            :datadir: '/etc/puppet/hieradata'
        :gpg:
            :datadir: '/etc/puppet/secret'

How to do it...

In this example, we'll create a piece of encrypted data and retrieve it using hiera-gpg as follows:

  1. Create the secret.yaml file at /etc/puppet/secret with the following contents:
    top_secret: 'Val Kilmer'
    
  2. If you don't already have a GnuPG encryption key, follow the steps in the Using GnuPG to encrypt secrets recipe in Chapter 4, Working with Files and Packages.
  3. Encrypt the secret.yaml file to this key using the following command (replace the puppet@puppet.example.com with the e-mail address you specified when creating the key). This will create the secret.gpg file:
    root@puppet:/etc/puppet/secret# gpg -e -o secret.gpg -r puppet@puppet.example.com secret.yaml 
    root@puppet:/etc/puppet/secret# file secret.gpg
    secret.gpg: GPG encrypted data
    
  4. Remove the plaintext secret.yaml file:
    root@puppet:/etc/puppet/secret# rm secret.yaml
    
  5. Modify your default node in the site.pp file as follows:
    node default {
      $message = hiera('top_secret','Deja Vu')
      notify { "Message is $message": }
    }
    
  6. Now run Puppet on a node:
    [root@hiera-test ~]# puppet agent -t
    Info: Caching catalog for hiera-test.example.com
    Info: Applying configuration version '1410508276'
    Notice: Message is Deja Vu
    Notice: /Stage[main]/Main/Node[default]/Notify[Message is Deja Vu]/message: defined 'message' as 'Message is Deja Vu'
    Notice: Finished catalog run in 0.08 seconds
    

How it works...

When you install hiera-gpg, it adds to Hiera, the ability to decrypt .gpg files. So you can put any secret data into a .yaml file that you then encrypt to the appropriate key with GnuPG. Only machines that have the right secret key will be able to access this data.

For example, you might encrypt the MySQL root password using hiera-gpg and install the corresponding key only on your database servers. Although other machines may also have a copy of the secret.gpg file, it's not readable to them unless they have the decryption key.

There's more...

You might also like to know about hiera-eyaml, another secret-data backend for Hiera that supports encryption of individual values within a Hiera data file. This could be handy if you need to mix encrypted and unencrypted facts within a single file. Find out more about hiera-eyaml at https://github.com/TomPoulton/hiera-eyaml.

See also

  • The Using GnuPG to encrypt secrets recipe in Chapter 4, Working with Files and Packages.
Getting ready

To set up hiera-gpg, follow these steps:

Install the ruby-dev package; it will be required to build the hiera-gpg gem as follows:
root@puppet:~# puppet resource package ruby-dev ensure=installed
Notice: /Package[ruby-dev]/ensure: ensure changed 'purged' to 'present'
package { 'ruby-dev':
  ensure => '1:1.9.3',
}
Install the hiera-gpg gem using the gem provider:
root@puppet:~# puppet resource package hiera-gpg ensure=installed provider=gem
Notice: /Package[hiera-gpg]/ensure: created
package { 'hiera-gpg':
  ensure => ['1.1.0'],
}
Modify your hiera.yaml file as follows:
    :hierarchy:
        - secret
        - common
    :backends:
        - yaml
        - gpg
    :yaml:
        :datadir: '/etc/puppet/hieradata'
    :gpg:
        :datadir: '/etc/puppet/secret'

How to do it...

In this example, we'll create a piece of encrypted data and retrieve it using hiera-gpg as follows:

  1. Create the secret.yaml file at /etc/puppet/secret with the following contents:
    top_secret: 'Val Kilmer'
    
  2. If you don't already have a GnuPG encryption key, follow the steps in the Using GnuPG to encrypt secrets recipe in Chapter 4, Working with Files and Packages.
  3. Encrypt the secret.yaml file to this key using the following command (replace the puppet@puppet.example.com with the e-mail address you specified when creating the key). This will create the secret.gpg file:
    root@puppet:/etc/puppet/secret# gpg -e -o secret.gpg -r puppet@puppet.example.com secret.yaml 
    root@puppet:/etc/puppet/secret# file secret.gpg
    secret.gpg: GPG encrypted data
    
  4. Remove the plaintext secret.yaml file:
    root@puppet:/etc/puppet/secret# rm secret.yaml
    
  5. Modify your default node in the site.pp file as follows:
    node default {
      $message = hiera('top_secret','Deja Vu')
      notify { "Message is $message": }
    }
    
  6. Now run Puppet on a node:
    [root@hiera-test ~]# puppet agent -t
    Info: Caching catalog for hiera-test.example.com
    Info: Applying configuration version '1410508276'
    Notice: Message is Deja Vu
    Notice: /Stage[main]/Main/Node[default]/Notify[Message is Deja Vu]/message: defined 'message' as 'Message is Deja Vu'
    Notice: Finished catalog run in 0.08 seconds
    

How it works...

When you install hiera-gpg, it adds to Hiera, the ability to decrypt .gpg files. So you can put any secret data into a .yaml file that you then encrypt to the appropriate key with GnuPG. Only machines that have the right secret key will be able to access this data.

For example, you might encrypt the MySQL root password using hiera-gpg and install the corresponding key only on your database servers. Although other machines may also have a copy of the secret.gpg file, it's not readable to them unless they have the decryption key.

There's more...

You might also like to know about hiera-eyaml, another secret-data backend for Hiera that supports encryption of individual values within a Hiera data file. This could be handy if you need to mix encrypted and unencrypted facts within a single file. Find out more about hiera-eyaml at https://github.com/TomPoulton/hiera-eyaml.

See also

  • The Using GnuPG to encrypt secrets recipe in Chapter 4, Working with Files and Packages.
How to do it...

In this

example, we'll create a piece of encrypted data and retrieve it using hiera-gpg as follows:

  1. Create the secret.yaml file at /etc/puppet/secret with the following contents:
    top_secret: 'Val Kilmer'
    
  2. If you don't already have a GnuPG encryption key, follow the steps in the Using GnuPG to encrypt secrets recipe in Chapter 4, Working with Files and Packages.
  3. Encrypt the secret.yaml file to this key using the following command (replace the puppet@puppet.example.com with the e-mail address you specified when creating the key). This will create the secret.gpg file:
    root@puppet:/etc/puppet/secret# gpg -e -o secret.gpg -r puppet@puppet.example.com secret.yaml 
    root@puppet:/etc/puppet/secret# file secret.gpg
    secret.gpg: GPG encrypted data
    
  4. Remove the plaintext secret.yaml file:
    root@puppet:/etc/puppet/secret# rm secret.yaml
    
  5. Modify your default node in the site.pp file as follows:
    node default {
      $message = hiera('top_secret','Deja Vu')
      notify { "Message is $message": }
    }
    
  6. Now run Puppet on a node:
    [root@hiera-test ~]# puppet agent -t
    Info: Caching catalog for hiera-test.example.com
    Info: Applying configuration version '1410508276'
    Notice: Message is Deja Vu
    Notice: /Stage[main]/Main/Node[default]/Notify[Message is Deja Vu]/message: defined 'message' as 'Message is Deja Vu'
    Notice: Finished catalog run in 0.08 seconds
    

How it works...

When you install hiera-gpg, it adds to Hiera, the ability to decrypt .gpg files. So you can put any secret data into a .yaml file that you then encrypt to the appropriate key with GnuPG. Only machines that have the right secret key will be able to access this data.

For example, you might encrypt the MySQL root password using hiera-gpg and install the corresponding key only on your database servers. Although other machines may also have a copy of the secret.gpg file, it's not readable to them unless they have the decryption key.

There's more...

You might also like to know about hiera-eyaml, another secret-data backend for Hiera that supports encryption of individual values within a Hiera data file. This could be handy if you need to mix encrypted and unencrypted facts within a single file. Find out more about hiera-eyaml at https://github.com/TomPoulton/hiera-eyaml.

See also

  • The Using GnuPG to encrypt secrets recipe in Chapter 4, Working with Files and Packages.
How it works...

When

you install hiera-gpg, it adds to Hiera, the ability to decrypt .gpg files. So you can put any secret data into a .yaml file that you then encrypt to the appropriate key with GnuPG. Only machines that have the right secret key will be able to access this data.

For example, you might encrypt the MySQL root password using hiera-gpg and install the corresponding key only on your database servers. Although other machines may also have a copy of the secret.gpg file, it's not readable to them unless they have the decryption key.

There's more...

You might also like to know about hiera-eyaml, another secret-data backend for Hiera that supports encryption of individual values within a Hiera data file. This could be handy if you need to mix encrypted and unencrypted facts within a single file. Find out more about hiera-eyaml at https://github.com/TomPoulton/hiera-eyaml.

See also

  • The Using GnuPG to encrypt secrets recipe in Chapter 4, Working with Files and Packages.
There's more...

You might also like to know about hiera-eyaml, another secret-data backend for Hiera that supports encryption of individual values within a Hiera data file. This could be handy if you need

to mix encrypted and unencrypted facts within a single file. Find out more about hiera-eyaml at https://github.com/TomPoulton/hiera-eyaml.

See also

  • The Using GnuPG to encrypt secrets recipe in Chapter 4, Working with Files and Packages.
See also

The Using GnuPG to encrypt secrets recipe in
  • Chapter 4, Working with Files and Packages.

Using MessagePack serialization

Running Puppet in a centralized architecture creates a lot of traffic between nodes. The bulk of this traffic is JSON and yaml data. An experimental feature of the latest releases of Puppet allow for the serialization of this data using MessagePack (msgpack).

Getting ready

Install the msgpack gem onto your Puppet master and your nodes. Use Puppet to do the work for you with Puppet resource. You may need to install the ruby-dev or ruby-devel package on your nodes/server at this point:

t@ckbk:~$ sudo puppet resource package msgpack ensure=installedprovider=gem
Notice: /Package[msgpack]/ensure: created
package { 'msgpack':
  ensure => ['0.5.8'],
}

How to do it...

Set the preferred_serialization_format to msgpack in the [agent] section of your nodes puppet.conf file:

[agent]
preferred_serialization_format=msgpack

How it works...

The master will be sent this option when the node begins communicating with the master. Any classes that support serialization with msgpack will be transmitted with msgpack.Serialization of the data between nodes and the master will in theory increase the speed at which nodes communicate by optimizing the data that is travelling between them. This feature is still experimental.

Getting ready

Install the msgpack gem onto your Puppet master and your nodes. Use Puppet to do the work for you with Puppet resource. You may need to install the ruby-dev or ruby-devel package on your nodes/server at this point:

t@ckbk:~$ sudo puppet resource package msgpack ensure=installedprovider=gem Notice: /Package[msgpack]/ensure: created package { 'msgpack': ensure => ['0.5.8'], }

How to do it...

Set the preferred_serialization_format to msgpack in the [agent] section of your nodes puppet.conf file:

[agent]
preferred_serialization_format=msgpack

How it works...

The master will be sent this option when the node begins communicating with the master. Any classes that support serialization with msgpack will be transmitted with msgpack.Serialization of the data between nodes and the master will in theory increase the speed at which nodes communicate by optimizing the data that is travelling between them. This feature is still experimental.

How to do it...

Set the preferred_serialization_format to msgpack in the [agent] section of your nodes puppet.conf file:

[agent] preferred_serialization_format=msgpack

How it works...

The master will be sent this option when the node begins communicating with the master. Any classes that support serialization with msgpack will be transmitted with msgpack.Serialization of the data between nodes and the master will in theory increase the speed at which nodes communicate by optimizing the data that is travelling between them. This feature is still experimental.

How it works...

The master will be sent this option when the node begins communicating with the master. Any classes that support serialization with msgpack will be transmitted with msgpack.Serialization of the data between nodes and the master will in theory increase the speed at which nodes communicate by optimizing the data that is travelling between

them. This feature is still experimental.

Automatic syntax checking with Git hooks

It would be nice if we knew there was a syntax error in the manifest before we even committed it. You can have Puppet check the manifest using the puppet parser validate command:

t@ckbk:~$ puppet parser validate bootstrap.pp
Error: Could not parse for environment production: Syntax error at
 'File'; expected '}' at /home/thomas/bootstrap.pp:3

This is especially useful because a mistake anywhere in the manifest will stop Puppet from running on any node, even on nodes that don't use that particular part of the manifest. So checking in a bad manifest can cause Puppet to stop applying updates to production for some time, until the problem is discovered, and this could potentially have serious consequences. The best way to avoid this is to automate the syntax check, by using a precommit hook in your version control repo.

How to do it...

Follow these steps:

  1. In your Puppet repo, create a new hooks directory:
    t@mylaptop:~/puppet$ mkdir hooks
    
  2. Create the file hooks/check_syntax.sh with the following contents (based on a script by Puppet Labs):
    #!/bin/sh
    
    syntax_errors=0
    error_msg=$(mktemp /tmp/error_msg.XXXXXX)
    
    if git rev-parse --quiet --verify HEAD > /dev/null
    then
        against=HEAD
    else
        # Initial commit: diff against an empty tree object
        against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
    fi
    
    # Get list of new/modified manifest and template files
      to check (in git index)
    for indexfile in 'git diff-index --diff-filter=AM --
      name-only --cached $against | egrep '\.(pp|erb)''
    do
        # Don't check empty files
        if [ 'git cat-file -s :0:$indexfile' -gt 0 ]
        then
            case $indexfile in
                *.pp )
                    # Check puppet manifest syntax
                    git cat-file blob :0:$indexfile | 
                      puppet parser validate > $error_msg ;;
                *.erb )
                    # Check ERB template syntax
                    git cat-file blob :0:$indexfile | 
                      erb -x -T - | ruby -c 2> $error_msg >
                        /dev/null ;;
            esac
            if [ "$?" -ne 0 ]
            then
                echo -n "$indexfile: "
                cat $error_msg
                syntax_errors='expr $syntax_errors + 1'
            fi
        fi
    done
    
    rm -f $error_msg
    
    if [ "$syntax_errors" -ne 0 ]
    then
        echo "Error: $syntax_errors syntax errors found,
          aborting commit."
        exit 1
    fi
  3. Set execute permission for the hook script with the following command:
    t@mylaptop:~/puppet$ chmod a+x hooks/check_syntax.sh
    
  4. Now either symlink or copy the script to the precommit hook in your hooks directory. If your Git repo is checked out in ~/puppet, then create the symlink at ~/puppet/hooks/pre-commit as follows:
    t@mylaptop:~/puppet$ ln -s ~/puppet/hooks/check_syntax.sh.git/hooks/pre-commit
    

How it works...

The check_syntax.sh script will prevent you from committing any files with syntax errors when it is used as the pre-commit hook for Git:

t@mylaptop:~/puppet$ git commit -m "test commit"
Error: Could not parse for environment production: Syntax error at
  '}' at line 3
Error: Try 'puppet help parser validate' for usage
manifests/nodes.pp: Error: 1 syntax errors found, aborting commit.

If you add the hooks directory to your Git repo, anyone who has a checkout can copy the script into their local hooks directory to get this syntax checking behavior.

How to do it...

Follow these steps:

In your Puppet repo, create a new hooks directory:
t@mylaptop:~/puppet$ mkdir hooks
Create the file hooks/check_syntax.sh with the following contents (based on a script by Puppet Labs):
#!/bin/sh

syntax_errors=0
error_msg=$(mktemp /tmp/error_msg.XXXXXX)

if git rev-parse --quiet --verify HEAD > /dev/null
then
    against=HEAD
else
    # Initial commit: diff against an empty tree object
    against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

# Get list of new/modified manifest and template files
  to check (in git index)
for indexfile in 'git diff-index --diff-filter=AM --
  name-only --cached $against | egrep '\.(pp|erb)''
do
    # Don't check empty files
    if [ 'git cat-file -s :0:$indexfile' -gt 0 ]
    then
        case $indexfile in
            *.pp )
                # Check puppet manifest syntax
                git cat-file blob :0:$indexfile | 
                  puppet parser validate > $error_msg ;;
            *.erb )
                # Check ERB template syntax
                git cat-file blob :0:$indexfile | 
                  erb -x -T - | ruby -c 2> $error_msg >
                    /dev/null ;;
        esac
        if [ "$?" -ne 0 ]
        then
            echo -n "$indexfile: "
            cat $error_msg
            syntax_errors='expr $syntax_errors + 1'
        fi
    fi
done

rm -f $error_msg

if [ "$syntax_errors" -ne 0 ]
then
    echo "Error: $syntax_errors syntax errors found,
      aborting commit."
    exit 1
fi
Set
  1. execute permission for the hook script with the following command:
    t@mylaptop:~/puppet$ chmod a+x hooks/check_syntax.sh
    
  2. Now either symlink or copy the script to the precommit hook in your hooks directory. If your Git repo is checked out in ~/puppet, then create the symlink at ~/puppet/hooks/pre-commit as follows:
    t@mylaptop:~/puppet$ ln -s ~/puppet/hooks/check_syntax.sh.git/hooks/pre-commit
    

How it works...

The check_syntax.sh script will prevent you from committing any files with syntax errors when it is used as the pre-commit hook for Git:

t@mylaptop:~/puppet$ git commit -m "test commit"
Error: Could not parse for environment production: Syntax error at
  '}' at line 3
Error: Try 'puppet help parser validate' for usage
manifests/nodes.pp: Error: 1 syntax errors found, aborting commit.

If you add the hooks directory to your Git repo, anyone who has a checkout can copy the script into their local hooks directory to get this syntax checking behavior.

How it works...

The check_syntax.sh script will prevent you from committing any files with syntax errors when it is used as the pre-commit hook for Git:

t@mylaptop:~/puppet$ git commit -m "test commit" Error: Could not parse for environment production: Syntax error at '}' at line 3 Error: Try 'puppet help parser validate' for usage manifests/nodes.pp: Error: 1 syntax errors found, aborting commit.

If you

add the hooks directory to your Git repo, anyone who has a checkout can copy the script into their local hooks directory to get this syntax checking behavior.

Pushing code around with Git

As we have already seen in the decentralized model, Git can be used to transfer files between machines using a combination of ssh and ssh keys. It can also be useful to have a Git hook do the same on each successful commit to the repository.

There exists a hook called post-commit that can be run after a successful commit to the repository. In this recipe, we'll create a hook that updates the code on our Puppet master with code from our Git repository on the Git server.

Getting ready

Follow these steps to get started:

  1. Create an ssh key that can access your Puppet user on your Puppet master and install this key into the Git user's account on git.example.com:
    [git@git ~]$ ssh-keygen -f ~/.ssh/puppet_rsa
    Generating public/private rsa key pair.
    Your identification has been saved in /home/git/.ssh/puppet_rsa.
    Your public key has been saved in /home/git/.ssh/puppet_rsa.pub.
    Copy the public key into the authorized_keys file of the puppet user on your puppetmaster
    puppet@puppet:~/.ssh$ cat puppet_rsa.pub >>authorized_keys
    
  2. Modify the Puppet account to allow the Git user to log in as follows:
    root@puppet:~# chsh puppet -s /bin/bash
    

How to do it...

Perform the following steps:

  1. Now that the Git user can log in to the Puppet master as the Puppet user, modify the Git user's ssh configuration to use the newly created ssh key by default:
    [git@git ~]$ vim .ssh/config
    Host puppet.example.com
      IdentityFile ~/.ssh/puppet_rsa
    
  2. Add the Puppet master as a remote location for the Puppet repository on the Git server with the following command:
    [git@git puppet.git]$ git remote add puppetmaster puppet@puppet.example.com:/etc/puppet/environments/puppet.git
    
  3. On the Puppet master, move the production directory out of the way and check out your Puppet repository:
    root@puppet:~# chown -R puppet:puppet /etc/puppet/environments
    root@puppet:~# sudo -iu puppet
    puppet@puppet:~$ cd /etc/puppet/environments/
    puppet@puppet:/etc/puppet/environments$ mv production production.orig
    puppet@puppet:/etc/puppet/environments$ git clone git@git.example.com:repos/puppet.git
    Cloning into 'puppet.git'...
    remote: Counting objects: 63, done.
    remote: Compressing objects: 100% (52/52), done.
    remote: Total 63 (delta 10), reused 0 (delta 0)
    Receiving objects: 100% (63/63), 9.51 KiB, done.
    Resolving deltas: 100% (10/10), done.
    
  4. Now we have a local bare repository on the Puppet server that we can push to, to remotely clone this into the production directory:
    puppet@puppet:/etc/puppet/environments$ git clone puppet.git production
    Cloning into 'production'...
    done.
    
  5. Now perform a Git push from the Git server to the Puppet master:
    [git@git ~]$ cd repos/puppet.git/
    [git@git puppet.git]$ git push puppetmaster
    Everything up-to-date
    
  6. Create a post-commit file in the hooks directory of the repository on the Git server with the following contents:
    [git@git puppet.git]$ vim hooks/post-commit
    #!/bin/sh
    git push puppetmaster
    ssh puppet@puppet.example.com "cd /etc/puppet/environments/production && git pull"
    [git@git puppet.git]$ chmod 755 hooks/post-commit
    
  7. Commit a change to the repository from your laptop and verify that the change is propagated to the Puppet master as follows:
    t@mylaptop puppet$ vim README
    t@mylaptop puppet$ git add README
    t@mylaptop puppet$ git commit -m "Adding README"
    [master 8148902] Adding README
     1 file changed, 4 deletions(-)
    t@mylaptop puppet$ git push
    X11 forwarding request failed on channel 0
    Counting objects: 5, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (3/3), done.
    Writing objects: 100% (3/3), 371 bytes | 0 bytes/s, done.
    Total 3 (delta 1), reused 0 (delta 0)
    remote: To puppet@puppet.example.com:/etc/puppet/environments/puppet.git
    remote:    377ed44..8148902  master -> master
    remote: From /etc/puppet/environments/puppet
    remote:    377ed44..8148902  master     -> origin/master
    remote: Updating 377ed44..8148902
    remote: Fast-forward
    remote:  README |    4 ----
    remote:  1 file changed, 4 deletions(-)
    To git@git.example.com:repos/puppet.git
       377ed44..8148902  master -> master
    

How it works...

We created a bare repository on the Puppet master that we then use as a remote for the repository on git.example.com (remote repositories must be bare). We then clone that bare repository into the production directory. We add the bare repository on puppet.example.com as a remote to the bare repository on git.example.com. We then create a post-receive hook in the repository on git.example.com.

The hook issues a Git push to the Puppet master bare repository. We then update the production directory from the updated bare repository on the Puppet master. In the next section, we'll modify the hook to use branches.

Getting ready

Follow these steps to get started:

Create an ssh key that can access your Puppet user on your Puppet master and install this key into the Git user's account on git.example.com:
[git@git ~]$ ssh-keygen -f ~/.ssh/puppet_rsa
Generating public/private rsa key pair.
Your identification has been saved in /home/git/.ssh/puppet_rsa.
Your public key has been saved in /home/git/.ssh/puppet_rsa.pub.
Copy the public key into the authorized_keys file of the puppet user on your puppetmaster
puppet@puppet:~/.ssh$ cat puppet_rsa.pub >>authorized_keys
Modify the Puppet account to allow the Git user to log in as follows:
root@puppet:~# chsh puppet -s /bin/bash

How to do it...

Perform the following steps:

  1. Now that the Git user can log in to the Puppet master as the Puppet user, modify the Git user's ssh configuration to use the newly created ssh key by default:
    [git@git ~]$ vim .ssh/config
    Host puppet.example.com
      IdentityFile ~/.ssh/puppet_rsa
    
  2. Add the Puppet master as a remote location for the Puppet repository on the Git server with the following command:
    [git@git puppet.git]$ git remote add puppetmaster puppet@puppet.example.com:/etc/puppet/environments/puppet.git
    
  3. On the Puppet master, move the production directory out of the way and check out your Puppet repository:
    root@puppet:~# chown -R puppet:puppet /etc/puppet/environments
    root@puppet:~# sudo -iu puppet
    puppet@puppet:~$ cd /etc/puppet/environments/
    puppet@puppet:/etc/puppet/environments$ mv production production.orig
    puppet@puppet:/etc/puppet/environments$ git clone git@git.example.com:repos/puppet.git
    Cloning into 'puppet.git'...
    remote: Counting objects: 63, done.
    remote: Compressing objects: 100% (52/52), done.
    remote: Total 63 (delta 10), reused 0 (delta 0)
    Receiving objects: 100% (63/63), 9.51 KiB, done.
    Resolving deltas: 100% (10/10), done.
    
  4. Now we have a local bare repository on the Puppet server that we can push to, to remotely clone this into the production directory:
    puppet@puppet:/etc/puppet/environments$ git clone puppet.git production
    Cloning into 'production'...
    done.
    
  5. Now perform a Git push from the Git server to the Puppet master:
    [git@git ~]$ cd repos/puppet.git/
    [git@git puppet.git]$ git push puppetmaster
    Everything up-to-date
    
  6. Create a post-commit file in the hooks directory of the repository on the Git server with the following contents:
    [git@git puppet.git]$ vim hooks/post-commit
    #!/bin/sh
    git push puppetmaster
    ssh puppet@puppet.example.com "cd /etc/puppet/environments/production && git pull"
    [git@git puppet.git]$ chmod 755 hooks/post-commit
    
  7. Commit a change to the repository from your laptop and verify that the change is propagated to the Puppet master as follows:
    t@mylaptop puppet$ vim README
    t@mylaptop puppet$ git add README
    t@mylaptop puppet$ git commit -m "Adding README"
    [master 8148902] Adding README
     1 file changed, 4 deletions(-)
    t@mylaptop puppet$ git push
    X11 forwarding request failed on channel 0
    Counting objects: 5, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (3/3), done.
    Writing objects: 100% (3/3), 371 bytes | 0 bytes/s, done.
    Total 3 (delta 1), reused 0 (delta 0)
    remote: To puppet@puppet.example.com:/etc/puppet/environments/puppet.git
    remote:    377ed44..8148902  master -> master
    remote: From /etc/puppet/environments/puppet
    remote:    377ed44..8148902  master     -> origin/master
    remote: Updating 377ed44..8148902
    remote: Fast-forward
    remote:  README |    4 ----
    remote:  1 file changed, 4 deletions(-)
    To git@git.example.com:repos/puppet.git
       377ed44..8148902  master -> master
    

How it works...

We created a bare repository on the Puppet master that we then use as a remote for the repository on git.example.com (remote repositories must be bare). We then clone that bare repository into the production directory. We add the bare repository on puppet.example.com as a remote to the bare repository on git.example.com. We then create a post-receive hook in the repository on git.example.com.

The hook issues a Git push to the Puppet master bare repository. We then update the production directory from the updated bare repository on the Puppet master. In the next section, we'll modify the hook to use branches.

How to do it...

Perform the following steps:

Now that the Git user can log in to the Puppet master as the Puppet user, modify the Git user's ssh configuration to use the newly created ssh key by default:
[git@git ~]$ vim .ssh/config
Host puppet.example.com
  IdentityFile ~/.ssh/puppet_rsa
Add the Puppet master as a remote location for the Puppet repository on the Git server with the following command:
[git@git puppet.git]$ git remote add puppetmaster puppet@puppet.example.com:/etc/puppet/environments/puppet.git
On the Puppet master, move the production directory out of the way and check out your Puppet repository:
root@puppet:~# chown -R puppet:puppet /etc/puppet/environments
root@puppet:~# sudo -iu puppet
puppet@puppet:~$ cd /etc/puppet/environments/
puppet@puppet:/etc/puppet/environments$ mv production production.orig
puppet@puppet:/etc/puppet/environments$ git clone git@git.example.com:repos/puppet.git
Cloning into 'puppet.git'...
remote: Counting objects: 63, done.
remote: Compressing objects: 100% (52/52), done.
remote: Total 63 (delta 10), reused 0 (delta 0)
Receiving objects: 100% (63/63), 9.51 KiB, done.
Resolving deltas: 100% (10/10), done.
Now
  1. we have a local bare repository on the Puppet server that we can push to, to remotely clone this into the production directory:
    puppet@puppet:/etc/puppet/environments$ git clone puppet.git production
    Cloning into 'production'...
    done.
    
  2. Now perform a Git push from the Git server to the Puppet master:
    [git@git ~]$ cd repos/puppet.git/
    [git@git puppet.git]$ git push puppetmaster
    Everything up-to-date
    
  3. Create a post-commit file in the hooks directory of the repository on the Git server with the following contents:
    [git@git puppet.git]$ vim hooks/post-commit
    #!/bin/sh
    git push puppetmaster
    ssh puppet@puppet.example.com "cd /etc/puppet/environments/production && git pull"
    [git@git puppet.git]$ chmod 755 hooks/post-commit
    
  4. Commit a change to the repository from your laptop and verify that the change is propagated to the Puppet master as follows:
    t@mylaptop puppet$ vim README
    t@mylaptop puppet$ git add README
    t@mylaptop puppet$ git commit -m "Adding README"
    [master 8148902] Adding README
     1 file changed, 4 deletions(-)
    t@mylaptop puppet$ git push
    X11 forwarding request failed on channel 0
    Counting objects: 5, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (3/3), done.
    Writing objects: 100% (3/3), 371 bytes | 0 bytes/s, done.
    Total 3 (delta 1), reused 0 (delta 0)
    remote: To puppet@puppet.example.com:/etc/puppet/environments/puppet.git
    remote:    377ed44..8148902  master -> master
    remote: From /etc/puppet/environments/puppet
    remote:    377ed44..8148902  master     -> origin/master
    remote: Updating 377ed44..8148902
    remote: Fast-forward
    remote:  README |    4 ----
    remote:  1 file changed, 4 deletions(-)
    To git@git.example.com:repos/puppet.git
       377ed44..8148902  master -> master
    

How it works...

We created a bare repository on the Puppet master that we then use as a remote for the repository on git.example.com (remote repositories must be bare). We then clone that bare repository into the production directory. We add the bare repository on puppet.example.com as a remote to the bare repository on git.example.com. We then create a post-receive hook in the repository on git.example.com.

The hook issues a Git push to the Puppet master bare repository. We then update the production directory from the updated bare repository on the Puppet master. In the next section, we'll modify the hook to use branches.

How it works...

We

created a bare repository on the Puppet master that we then use as a remote for the repository on git.example.com (remote repositories must be bare). We then clone that bare repository into the production directory. We add the bare repository on puppet.example.com as a remote to the bare repository on git.example.com. We then create a post-receive hook in the repository on git.example.com.

The hook issues a Git push to the Puppet master bare repository. We then update the production directory from the updated bare repository on the Puppet master. In the next section, we'll modify the hook to use branches.

Managing Environments with Git

Branches are a way of keeping several different tracks of development within a single source repository. Puppet environments are a lot like Git branches. You can have the same code with slight variations between branches, just as you can have different modules for different environments. In this section, we'll show how to use Git branches to define environments on the Puppet master.

Getting ready

In the previous section, we created a production directory that was based on the master branch; we'll remove that directory now:

puppet@puppet:/etc/puppet/environments$ mv production production.master

How to do it...

Modify the post-receive hook to accept a branch variable. The hook will use this variable to create a directory on the Puppet master as follows:

#!/bin/sh

read oldrev newrev refname
branch=${refname#*\/*\/}

git push puppetmaster $branch
ssh puppet@puppet.example.com "if [ ! -d 
/etc/puppet/environments/$branch ]; then git clone
 /etc/puppet/environments/puppet.git
 /etc/puppet/environments/$branch; fi; cd
 /etc/puppet/environments/$branch; git checkout $branch; git pull"

Modify your README file again and push to the repository on git.example.com:

t@mylaptop puppet$ git add README
t@mylaptop puppet$ git commit -m "Adding README"
[master 539d9f8] Adding README
 1 file changed, 1 insertion(+)
t@mylaptop puppet$ git push
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 374 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: To puppet@puppet.example.com:/etc/puppet/environments/puppet.git
remote:    0d6b49f..539d9f8  master -> master
remote: Cloning into '/etc/puppet/environments/master'...
remote: done.
remote: Already on 'master'
remote: Already up-to-date.
To git@git.example.com:repos/puppet.git
   0d6b49f..539d9f8  master -> master

How it works...

The hook now reads in the refname and parses out the branch that is being updated. We use that branch variable to clone the repository into a new directory and check out the branch.

There's more...

Now when we want to create a new environment, we can create a new branch in the Git repository. The branch will create a directory on the Puppet master. Each branch of the Git repository represents an environment on the Puppet master:

  1. Create the production branch as shown in the following command line:
    t@mylaptop puppet$ git branch production
    t@mylaptop puppet$ git checkout production
    Switched to branch 'production'
    
  2. Update the production branch and push to the Git server as follows:
    t@mylaptop puppet$ vim README
    t@mylaptop puppet$ git add README
    t@mylaptop puppet$ git commit -m "Production Branch"
    t@mylaptop puppet$ git push origin production
    Counting objects: 7, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (3/3), done.
    Writing objects: 100% (3/3), 372 bytes | 0 bytes/s, done.
    Total 3 (delta 1), reused 0 (delta 0)
    remote: To puppet@puppet.example.com:/etc/puppet/environments/puppet.git
    remote:    11db6e5..832f6a9  production -> production
    remote: Cloning into '/etc/puppet/environments/production'...
    remote: done.
    remote: Switched to a new branch 'production'
    remote: Branch production set up to track remote branch production from origin.
    remote: Already up-to-date.
    To git@git.example.com:repos/puppet.git
       11db6e5..832f6a9  production -> production
    

Now whenever we create a new branch, a corresponding directory is created in our environment's directory. A one-to-one mapping is established between environments and branches.

Getting ready

In the previous section, we created a production directory that was based on the master branch; we'll remove that directory now:

puppet@puppet:/etc/puppet/environments$ mv production production.master

How to do it...

Modify the post-receive hook to accept a branch variable. The hook will use this variable to create a directory on the Puppet master as follows:

#!/bin/sh

read oldrev newrev refname
branch=${refname#*\/*\/}

git push puppetmaster $branch
ssh puppet@puppet.example.com "if [ ! -d 
/etc/puppet/environments/$branch ]; then git clone
 /etc/puppet/environments/puppet.git
 /etc/puppet/environments/$branch; fi; cd
 /etc/puppet/environments/$branch; git checkout $branch; git pull"

Modify your README file again and push to the repository on git.example.com:

t@mylaptop puppet$ git add README
t@mylaptop puppet$ git commit -m "Adding README"
[master 539d9f8] Adding README
 1 file changed, 1 insertion(+)
t@mylaptop puppet$ git push
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 374 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: To puppet@puppet.example.com:/etc/puppet/environments/puppet.git
remote:    0d6b49f..539d9f8  master -> master
remote: Cloning into '/etc/puppet/environments/master'...
remote: done.
remote: Already on 'master'
remote: Already up-to-date.
To git@git.example.com:repos/puppet.git
   0d6b49f..539d9f8  master -> master

How it works...

The hook now reads in the refname and parses out the branch that is being updated. We use that branch variable to clone the repository into a new directory and check out the branch.

There's more...

Now when we want to create a new environment, we can create a new branch in the Git repository. The branch will create a directory on the Puppet master. Each branch of the Git repository represents an environment on the Puppet master:

  1. Create the production branch as shown in the following command line:
    t@mylaptop puppet$ git branch production
    t@mylaptop puppet$ git checkout production
    Switched to branch 'production'
    
  2. Update the production branch and push to the Git server as follows:
    t@mylaptop puppet$ vim README
    t@mylaptop puppet$ git add README
    t@mylaptop puppet$ git commit -m "Production Branch"
    t@mylaptop puppet$ git push origin production
    Counting objects: 7, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (3/3), done.
    Writing objects: 100% (3/3), 372 bytes | 0 bytes/s, done.
    Total 3 (delta 1), reused 0 (delta 0)
    remote: To puppet@puppet.example.com:/etc/puppet/environments/puppet.git
    remote:    11db6e5..832f6a9  production -> production
    remote: Cloning into '/etc/puppet/environments/production'...
    remote: done.
    remote: Switched to a new branch 'production'
    remote: Branch production set up to track remote branch production from origin.
    remote: Already up-to-date.
    To git@git.example.com:repos/puppet.git
       11db6e5..832f6a9  production -> production
    

Now whenever we create a new branch, a corresponding directory is created in our environment's directory. A one-to-one mapping is established between environments and branches.

How to do it...

Modify the post-receive hook to accept a branch variable. The hook will use this variable to create a directory on the Puppet master as follows:

#!/bin/sh read oldrev newrev refname branch=${refname#*\/*\/} git push puppetmaster $branch ssh puppet@puppet.example.com "if [ ! -d /etc/puppet/environments/$branch ]; then git clone /etc/puppet/environments/puppet.git /etc/puppet/environments/$branch; fi; cd /etc/puppet/environments/$branch; git checkout $branch; git pull"

Modify your README file again and push to the repository on git.example.com:

t@mylaptop puppet$ git add README t@mylaptop puppet$ git commit -m "Adding README" [master 539d9f8] Adding README 1 file changed, 1 insertion(+) t@mylaptop puppet$ git push Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 374 bytes | 0 bytes/s, done. Total 3 (delta 1), reused 0 (delta 0) remote: To puppet@puppet.example.com:/etc/puppet/environments/puppet.git remote: 0d6b49f..539d9f8 master -> master remote: Cloning into '/etc/puppet/environments/master'... remote: done. remote: Already on 'master' remote: Already up-to-date. To git@git.example.com:repos/puppet.git 0d6b49f..539d9f8 master -> master

How it works...

The hook now reads in the refname and parses out the branch that is being updated. We use that branch variable to clone the repository into a new directory and check out the branch.

There's more...

Now when we want to create a new environment, we can create a new branch in the Git repository. The branch will create a directory on the Puppet master. Each branch of the Git repository represents an environment on the Puppet master:

  1. Create the production branch as shown in the following command line:
    t@mylaptop puppet$ git branch production
    t@mylaptop puppet$ git checkout production
    Switched to branch 'production'
    
  2. Update the production branch and push to the Git server as follows:
    t@mylaptop puppet$ vim README
    t@mylaptop puppet$ git add README
    t@mylaptop puppet$ git commit -m "Production Branch"
    t@mylaptop puppet$ git push origin production
    Counting objects: 7, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (3/3), done.
    Writing objects: 100% (3/3), 372 bytes | 0 bytes/s, done.
    Total 3 (delta 1), reused 0 (delta 0)
    remote: To puppet@puppet.example.com:/etc/puppet/environments/puppet.git
    remote:    11db6e5..832f6a9  production -> production
    remote: Cloning into '/etc/puppet/environments/production'...
    remote: done.
    remote: Switched to a new branch 'production'
    remote: Branch production set up to track remote branch production from origin.
    remote: Already up-to-date.
    To git@git.example.com:repos/puppet.git
       11db6e5..832f6a9  production -> production
    

Now whenever we create a new branch, a corresponding directory is created in our environment's directory. A one-to-one mapping is established between environments and branches.

How it works...

The

hook now reads in the refname and parses out the branch that is being updated. We use that branch variable to clone the repository into a new directory and check out the branch.

There's more...

Now when we want to create a new environment, we can create a new branch in the Git repository. The branch will create a directory on the Puppet master. Each branch of the Git repository represents an environment on the Puppet master:

  1. Create the production branch as shown in the following command line:
    t@mylaptop puppet$ git branch production
    t@mylaptop puppet$ git checkout production
    Switched to branch 'production'
    
  2. Update the production branch and push to the Git server as follows:
    t@mylaptop puppet$ vim README
    t@mylaptop puppet$ git add README
    t@mylaptop puppet$ git commit -m "Production Branch"
    t@mylaptop puppet$ git push origin production
    Counting objects: 7, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (3/3), done.
    Writing objects: 100% (3/3), 372 bytes | 0 bytes/s, done.
    Total 3 (delta 1), reused 0 (delta 0)
    remote: To puppet@puppet.example.com:/etc/puppet/environments/puppet.git
    remote:    11db6e5..832f6a9  production -> production
    remote: Cloning into '/etc/puppet/environments/production'...
    remote: done.
    remote: Switched to a new branch 'production'
    remote: Branch production set up to track remote branch production from origin.
    remote: Already up-to-date.
    To git@git.example.com:repos/puppet.git
       11db6e5..832f6a9  production -> production
    

Now whenever we create a new branch, a corresponding directory is created in our environment's directory. A one-to-one mapping is established between environments and branches.

There's more...

Now when we want to create a new environment, we can create a new branch in the Git repository. The branch will create a directory on the Puppet master. Each branch of the Git repository represents an environment on the Puppet master:

Create the production branch as shown in the following command line:
t@mylaptop puppet$ git branch production
t@mylaptop puppet$ git checkout production
Switched to branch 'production'
Update the production branch and push to the Git server as follows:
t@mylaptop puppet$ vim README
t@mylaptop puppet$ git add README
t@mylaptop puppet$ git commit -m "Production Branch"
t@mylaptop puppet$ git push origin production
Counting objects: 7, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 372 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: To puppet@puppet.example.com:/etc/puppet/environments/puppet.git
remote:    11db6e5..832f6a9  production -> production
remote: Cloning into '/etc/puppet/environments/production'...
remote: done.
remote: Switched to a new branch 'production'
remote: Branch production set up to track remote branch production from origin.
remote: Already up-to-date.
To git@git.example.com:repos/puppet.git
   11db6e5..832f6a9  production -> production

Now

whenever we create a new branch, a corresponding directory is created in our environment's directory. A one-to-one mapping is established between environments and branches.

You have been reading a chapter from
DevOps: Puppet, Docker, and Kubernetes
Published in: Mar 2017
Publisher: Packt
ISBN-13: 9781788297615
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime