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...
- 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.
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...
- 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.
- Install Puppet labs release package
- Install Puppet package
t@ckbk:~ puppet --version 3.7.2
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:
- 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 to allow key-based access to the repository.
- Install Git on a node and download the latest version from our Git repository.
How to do it...
Follow these steps:
- 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 itgit.pp
:package {'git': ensure => installed }
- Apply this manifest using
puppet apply git.pp
, this will install Git. - 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'] }
- 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/
- 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.
- 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
- 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
- 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.
- 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
- 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.
- 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"
- 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
- 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 }
- 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
- As a last step, we create a symbolic link between the
thomas-base
directory andbase
. Now to make sure our module does something useful, add the following to the body of thebase
class defined inthomas-base/manifests/init.pp
:class base { file {'/etc/motd': content => "${::fqdn}\nManaged by puppet ${::puppetversion}\n" } }
- Now add the new base module and site manifest to Git using
git add
andgit 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
- 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.
- to allow key-based access to the repository.
- Install Git on a node and download the latest version from our Git repository.
How to do it...
Follow these steps:
- 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 itgit.pp
:package {'git': ensure => installed }
- Apply this manifest using
puppet apply git.pp
, this will install Git. - 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'] }
- 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/
- 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.
- 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
- 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
- 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.
- 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
- 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.
- 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"
- 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
- 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 }
- 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
- As a last step, we create a symbolic link between the
thomas-base
directory andbase
. Now to make sure our module does something useful, add the following to the body of thebase
class defined inthomas-base/manifests/init.pp
:class base { file {'/etc/motd': content => "${::fqdn}\nManaged by puppet ${::puppetversion}\n" } }
- Now add the new base module and site manifest to Git using
git add
andgit 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
- 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.
- 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 itgit.pp
:package {'git': ensure => installed }
- Apply this manifest using
puppet apply git.pp
, this will install Git. - 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'] }
- 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/
- 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.
- 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
- 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
- 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.
- 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
- 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.
- 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"
- 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
- 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 }
- 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
- As a last step, we create a symbolic link between the
thomas-base
directory andbase
. Now to make sure our module does something useful, add the following to the body of thebase
class defined inthomas-base/manifests/init.pp
:class base { file {'/etc/motd': content => "${::fqdn}\nManaged by puppet ${::puppetversion}\n" } }
- Now add the new base module and site manifest to Git using
git add
andgit 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
- 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.
git commit
command and annotate with a message.
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.
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:
- Install Git:
package {'git': ensure => 'installed' }
- Install the
ssh
key to accessgit.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'] }
- Download the
ssh
host key fromgit.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'], }
- Create a directory to contain the Git repository (
/etc/puppet/cookbook
):file {'/etc/puppet/cookbook': ensure => 'directory', }
- 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', }
- 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 ofsudo
. Comment out the lineDefaults 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. - 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
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:
- Install Git:
package {'git': ensure => 'installed' }
- Install the
ssh
key to accessgit.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'] }
- Download the
ssh
host key fromgit.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'], }
- Create a directory to contain the Git repository (
/etc/puppet/cookbook
):file {'/etc/puppet/cookbook': ensure => 'directory', }
- 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', }
- 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 ofsudo
. Comment out the lineDefaults 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. - 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
bootstrap.pp
manifest that will perform the following configuration steps on our new node:
package {'git': ensure => 'installed' }
- the
ssh
key to accessgit.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'] }
- Download the
ssh
host key fromgit.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'], }
- Create a directory to contain the Git repository (
/etc/puppet/cookbook
):file {'/etc/puppet/cookbook': ensure => 'directory', }
- 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', }
- 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 ofsudo
. Comment out the lineDefaults 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. - 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
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:
- 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 your
manifests/site.pp
file as follows:node default { include base include puppet }
- 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
- 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
- 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
- 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
- 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.
t@mylaptop ~$ cd puppet/modules t@mylaptop modules$ mkdir -p puppet/{manifests,files}
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 $*
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', } }
- your
manifests/site.pp
file as follows:node default { include base include puppet }
- 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
- 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
- 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
- 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
- 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.
puppet apply
command:
modulepath
argument:
sudo
before everything:
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:
- Copy the
bootstrap.pp
script to any node you wish to enroll. Thebootstrap.pp
manifest includes the private key used to access the Git repository, it should be protected in a production environment. - 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
- Modify the
modules/puppet/manifests/init.pp
file and add the following snippet after thepapply
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 => '*', }
- 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
- 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
- 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
- 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
- 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.
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:
- Copy the
bootstrap.pp
script to any node you wish to enroll. Thebootstrap.pp
manifest includes the private key used to access the Git repository, it should be protected in a production environment. - 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
- Modify the
modules/puppet/manifests/init.pp
file and add the following snippet after thepapply
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 => '*', }
- 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
- 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
- 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
- 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
- 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.
these steps:
- Copy the
bootstrap.pp
script to any node you wish to enroll. Thebootstrap.pp
manifest includes the private key used to access the Git repository, it should be protected in a production environment. - 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
- Modify the
modules/puppet/manifests/init.pp
file and add the following snippet after thepapply
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 => '*', }
- 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
- 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
- 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
- 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
- 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.
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.
bootstrap.pp
manifest, run Puppet on the repository; the machine will be set up to pull any new changes and apply them automatically.
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:
- 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.
git.example.com
. Start by creating a directory in the root of your web server:
<Location /bootstrap> AuthType basic AuthName "Bootstrap" AuthBasicProvider file AuthUserFile /var/www/puppet.passwd Require valid-user </Location>
[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>
/var/www/puppet.passwd
):root@git# cd /var/www
root@git# htpasswd –cb puppet.passwd bootstrap cookbook
Adding password for user bootstrap
[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.
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.
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.
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...
- 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).
- From another node, run
puppet agent
to start apuppet 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
- 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'
- 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.
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...
- 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).
- From another node, run
puppet agent
to start apuppet 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
- 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'
- 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.
# puppet resource package puppetmaster ensure='installed' Notice: /Package[puppetmaster]/ensure: created package { 'puppetmaster': ensure => '3.7.0-1puppetlabs1', }
# 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).
- From another node, run
puppet agent
to start apuppet 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
- 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'
- 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.
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).
- From another node, run
puppet agent
to start apuppet 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
- 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'
- 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.
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:
- 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 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
- 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'
- 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.
a certificate authority (CA).
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 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
- 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'
- 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.
# service puppetmaster stop [ ok ] Stopping puppet master.
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'
- 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
- 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'
- 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.
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:
- 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 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.
- 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
- Finally, the location of the passenger configuration file
config.ru
is given with theDocumentRoot
location as follows:DocumentRoot /usr/share/puppet/rack/puppetmasterd/public/ RackBaseURI /
- 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
- 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 usingservice 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"
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:
- 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 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.
- 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
- Finally, the location of the passenger configuration file
config.ru
is given with theDocumentRoot
location as follows:DocumentRoot /usr/share/puppet/rack/puppetmasterd/public/ RackBaseURI /
- 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
- 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 usingservice 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"
/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
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.
- 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
- Finally, the location of the passenger configuration file
config.ru
is given with theDocumentRoot
location as follows:DocumentRoot /usr/share/puppet/rack/puppetmasterd/public/ RackBaseURI /
- 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
- 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 usingservice 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"
config.ru
file.
$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:
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"
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:
- Create a
production
directory at/etc/puppet/environments
that contains both amodules
andmanifests
directory. Then create asite.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 another environment
devel
. Create a new manifest in thedevel
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", } }
- 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.
environmentpath
function of your installation by adding a line to the [main]
section of /etc/puppet/puppet.conf
as follows:
How to do it...
The steps are as follows:
- Create a
production
directory at/etc/puppet/environments
that contains both amodules
andmanifests
directory. Then create asite.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 another environment
devel
. Create a new manifest in thedevel
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", } }
- 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.
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", } }
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
- another environment
devel
. Create a new manifest in thedevel
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", } }
- 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.
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
puppet module install
will install the module to the correct location for your installation with the following command:
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
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
puppetdb
class to our Puppet master node, Puppet installed and configured postgresql
and puppetdb
.
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
.
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
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...
- 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 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/.
/etc/puppet/hiera.yaml
. Create the file and add the following as a minimal configuration:--- :hierarchy: - common :backends: - yaml :yaml: :datadir: '/etc/puppet/hieradata'
common.yaml
file referenced in the hierarchy:root@puppet:/etc/puppet# mkdir hieradata root@puppet:/etc/puppet# vim hieradata/common.yaml --- message: 'Default Message'
site.pp
file and add a notify resource based on the Hiera value:node default { $message = hiera('message','unknown') notify {"Message is $message":} }
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/.
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/.
cookbook::example
with a parameter named publisher
, you can include the following in a Hiera yaml file to automatically set this parameter:
environment
you may reference the environment
of the client node using %{environment}
as shown in the following hierarchy:
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:
- Create a file at
/etc/puppet/hieradata/hosts
that is the hostname of your test node. For example if your host is namedcookbook-test
, then the file would be namedcookbook-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.
hosts/%{hostname}
entry.
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 namedcookbook-test
, then the file would be namedcookbook-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.
/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
.
message: 'This is the test node for the cookbook'
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.
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:
- Install the
ruby-dev
package; it will be required to build thehiera-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:
- Create the
secret.yaml
file at/etc/puppet/secret
with the following contents:top_secret: 'Val Kilmer'
- 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.
- Encrypt the
secret.yaml
file to this key using the following command (replace thepuppet@puppet.example.com
with the e-mail address you specified when creating the key). This will create thesecret.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
- Remove the plaintext
secret.yaml
file:root@puppet:/etc/puppet/secret# rm secret.yaml
- Modify your default node in the
site.pp
file as follows:node default { $message = hiera('top_secret','Deja Vu') notify { "Message is $message": } }
- 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.
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', }
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'], }
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:
- Create the
secret.yaml
file at/etc/puppet/secret
with the following contents:top_secret: 'Val Kilmer'
- 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.
- Encrypt the
secret.yaml
file to this key using the following command (replace thepuppet@puppet.example.com
with the e-mail address you specified when creating the key). This will create thesecret.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
- Remove the plaintext
secret.yaml
file:root@puppet:/etc/puppet/secret# rm secret.yaml
- Modify your default node in the
site.pp
file as follows:node default { $message = hiera('top_secret','Deja Vu') notify { "Message is $message": } }
- 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.
example, we'll create a piece of encrypted data and retrieve it using hiera-gpg
as follows:
- Create the
secret.yaml
file at/etc/puppet/secret
with the following contents:top_secret: 'Val Kilmer'
- 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.
- Encrypt the
secret.yaml
file to this key using the following command (replace thepuppet@puppet.example.com
with the e-mail address you specified when creating the key). This will create thesecret.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
- Remove the plaintext
secret.yaml
file:root@puppet:/etc/puppet/secret# rm secret.yaml
- Modify your default node in the
site.pp
file as follows:node default { $message = hiera('top_secret','Deja Vu') notify { "Message is $message": } }
- 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.
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.
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.
- 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.
ruby-dev
or ruby-devel
package on your nodes/server at this point:
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.
preferred_serialization_format
to msgpack
in the [agent]
section of your nodes puppet.conf
file:
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.
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:
- 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 execute permission for the
hook
script with the following command:t@mylaptop:~/puppet$ chmod a+x hooks/check_syntax.sh
- 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.
hooks
directory:t@mylaptop:~/puppet$ mkdir hooks
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
- execute permission for the
hook
script with the following command:t@mylaptop:~/puppet$ chmod a+x hooks/check_syntax.sh
- 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.
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:
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:
- Create an
ssh
key that can access your Puppet user on your Puppet master and install this key into the Git user's account ongit.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:
- 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 createdssh
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 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.
- 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
- 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
- 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.
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
root@puppet:~# chsh puppet -s /bin/bash
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 createdssh
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 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.
- 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
- 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
- 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.
ssh
configuration to use the newly created ssh
key by default:[git@git ~]$ vim .ssh/config Host puppet.example.com IdentityFile ~/.ssh/puppet_rsa
[git@git puppet.git]$ git remote add puppetmaster puppet@puppet.example.com:/etc/puppet/environments/puppet.git
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.
- 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.
- 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
- 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
- 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.
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:
- 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.
production
directory that was based on the master branch; we'll remove that directory now:
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:
- 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.
post-receive
hook to accept a branch variable. The hook will use this variable to create a directory on the Puppet master as follows:
README
file again and push to the repository on git.example.com
:
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:
- 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.
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:
- 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.
t@mylaptop puppet$ git branch production t@mylaptop puppet$ git checkout production Switched to branch 'production'
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
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.