In the following quick start guide I will show you the basic usage of Puppet. If you want to follow me along, you’ll only need the text editor of your choice (I’m using Sublime Text here) and a command line. Also you should be using a supported operating system, I’ll describe the installation exemplary on Fedora Linux and Mac OS X Yosemite.
Some theory
What is Puppet?
Puppet is a tool for managing system configurations. Although Puppet can primarily be found in large server environments, where many machines need to be set up the exact same way, it could possibly also be used for setting up your own workstation or configuring a switch.
Written in Ruby, Puppet is generally platform independent but only *NIX alike systems like Linux and BSD are supported officially.
Manifest files
Puppet features a relatively easy configuration syntax. The administrator simply creates a text file containing the desired state for a given system. The state is defined in a declarative manner, like:
The file
/etc/resolv.conf
is owned by userroot
.
When starting the Puppet command line tool, it will double-check all assertions and apply all the changes required to get into the desired state as specified.
By implication this means that if everything is already configured properly, Puppet will do absolutely nothing.
Modules
Puppet maintains a large repository of ready-to-use modules called Puppet Forge. A lot of modules can also be found on GitHub. So for instance if you need a module to configure the Apache web server or a package provider for Homebrew, you’ll likely find one there.
Standalone & Puppetmaster
Generally, Puppet can be operated in two modes: Standalone (agent) or using the
client/server principle. In the larger setups, Puppet is meant to run in, you’ll
find a Puppet server called puppetmaster
which holds all relevant manifests and
modules. Every agent (node) authenticates against the puppetmaster
using public key
authentication.
The agent then runs in the background on every node and periodically checks for changes regarding the manifest files and applies them if necessary.
Using the node
definition, a manifest can contain different instructions for each
client. The nodes themselves are identified by their individual host names.
However, in this guide we’ll focus on the standalone mode only. This mode doesn’t
require a puppetmaster
because the manifest and all modules are available on
the local machine and manually applied by running the puppet apply
command.
Installing Puppet
Mac OS X
Puppet requires Ruby in version 1.8 or higher, which is already the case for
Mavericks and Yosemite. Using Ruby’s package manager gem
, Puppet can be installed
right from the command line:
sudo gem install puppet
Puppet’s own dependencies named facter
and heira
will get installed automatically.
Fedora Linux
Most Linux distributions maintain own packages for Puppet in their respective package management system, which is also the case for Fedora. Therefore Puppet can be installed using the following command:
sudo yum install puppet
On my Fedora 21 box, yum
installed version 3.6.2:
puppet --version
3.6.2
Writing the first manifest file
Example: Creating a user
Below we’ll create a Puppet manifest that creates a system user as well as a file. Afterwards we change the ownership of the create file to the created user. While this has not much in common with usual server administration tasks, it explains the basic syntax usage very well.
Create a file called test.pp
with the following contents:
user { "puppet_test":
ensure => present,
}
All state definitions are done this way in Puppet. user
is a type. Within
the curly brackets, the block first gets a name (puppet_test
) followed by
various key -> value definitions called attributes. There are some attributes that
all types have in common, e.g. ensure
or require
. Other attributes depend
on the type you are using. A complete list can be found in the type reference.
Now we can run puppet apply
to apply our manifest:
puppet apply test.pp
Could not retrieve fact='virtual', resolution='': Permission denied @ rb_sysopen - /sys/firmware/dmi/entries/1-0/raw
Notice: Compiled catalog for rabbithole-local.local in environment production in 0.24 seconds
Error: Could not find a suitable provider for user
Notice: Finished catalog run in 0.14 seconds
As you can see, the execution didn’t work in the above case. This is the case, because we didn’t run Puppet as a privileged user, which is needed to gather all relevant system information.
Running the same command using sudo
solves this problem:
sudo puppet apply test.pp
Notice: Compiled catalog for rabbithole-local.local in environment production in 0.12 seconds
Notice: /Stage[main]/Main/User[puppet_test]/ensure: created
Notice: Finished catalog run in 0.44 seconds
Now the command was executed successfully. The line /Stage[main]/Main/User[puppet-test]/ensure: created
indicates, that the user was created successfully.
If we’d run puppet apply
again, nothing would happen because the user already
exists. Simple, huh?
sudo puppet apply test.pp
Notice: Compiled catalog for rabbithole-local.local in environment production in 0.12 seconds
Notice: Finished catalog run in 0.07 seconds
Dependencies
In the next step, we’ll create a file and fill it with example contents. This
can be accomplished using the type file
. Append the following lines to the
test.pp
file:
file { "fancy file":
ensure => present,
path => "/home/spike/Desktop/puppet_test_file",
content => "Hello World",
owner => "puppet_test",
require => User["puppet_test"],
}
As you can see, we used the same scheme as before. The name “fancy file” does not matter at all in this case and is only used for referencing this block.
The file may have the contents “Hello World” and belong to puppet_test
. Using the
require
attribute we specified a dependency between the file and the user because
it’s ownership cannot be changed, if the user was not created first.
Explicitly defining those requirements is a absolutely essential part in Puppet because the manifest file is not interpreted on a per-line basis but arranged in the right order after resolving all dependencies (if not circular because this doesn’t work).
We’ll now run Puppet again:
sudo puppet apply test.pp
Notice: Compiled catalog for rabbithole-local.local in environment production in 0.20 seconds
Notice: /Stage[main]/Main/File[cool file]/ensure: created
Notice: Finished catalog run in 0.11 seconds
As you see, the file has been created. Due to the fact that the user is already existing, Puppet won’t mention it anymore.
Taking a look into the target directory reveals that the file has been created:
ls -l puppet_test_file
-rw-r--r--. 1 puppet_test root 10 2. Jan 19:29 puppet_test_file
Apply state corrections
Let’s change the file ownerships by hand:
sudo chown spike puppet_test_file
ls -al puppet_test_file
-rw-r--r--. 1 spike root 10 2. Jan 19:29 puppet_test_file
By running Puppet again, only the ownership will be corrected because this is the only thing that differs from the manifests’ state.
sudo puppet apply test.pp
Notice: Compiled catalog for rabbithole-local.local in environment production in 0.19 seconds
Notice: /Stage[main]/Main/File[cool file]/owner: owner changed 'spike' to 'puppet_test'
Notice: Finished catalog run in 0.06 seconds
As you see, Puppet is able to check individual aspects of existing setups and change only those, who are not configured as intended.
Classes: Grouping blocks
As you might have recognized by now, the configuration of a whole server may easily get unclear. This is why Puppet supports grouping blocks to classes in order to structure functional aspects of your manifest.
Let’s create a class called create_user_and_file
that contains both blocks
we created before. The whole file will look like this:
class create_user_and_file {
user { "puppet_test":
ensure => present,
}
file { "fancy file":
ensure => present,
path => "/home/spike/Desktop/puppet_test_file",
content => "Hello World",
owner => "puppet_test",
require => User["puppet_test"],
}
}
Additionally you could outsource this functionality into a separate file, but for now we’ll keep it right where it is.
If you’d run puppet apply
again, even if you changed something in the expected
configuration, nothing would happen. This is because even though the class is
properly defined, it’s not executed in any context. Like in object oriented
programming, defining a class creates a blueprint for a object but without creating
a object, no logic is executed.
So we need to include the class to our root manifest again. Append this to the
test.pp
file:
include create_user_and_file
By running Puppet, our configuration will be applied again:
sudo puppet apply test.pp
Notice: Compiled catalog for rabbithole-local.local in environment production in 0.25 seconds
Notice: /Stage[main]/Create_user_and_file/File[fancy file]/ensure: created
Notice: Finished catalog run in 0.34 seconds
Nodes
Let’s assume, we have multiple machines we want to configure using the same
manifest. In this example, we’ll modify our manifest to only apply our class
create_user_and_file
to the host called rabbithole-local
. So let’s edit
our file:
class create_user_and_file {
user { "puppet_test":
ensure => present,
}
file { "fancy file":
ensure => present,
path => "/home/spike/Desktop/puppet_test_file",
content => "Hello World",
owner => "puppet_test",
require => User["puppet_test"],
}
}
node "rabbithole-local" {
include create_user_and_file
}
When executing the file on rabbithole-local
, our configuration will be applied.
On any other system, nothing would happen. This mechanism is especially useful
when using a puppetmaster
setup in order to serve different configurations
for each registered node.
Outlook
In the next post in this series, I’ll show you how to provision OS X systems using Puppet.