Tasks and templates

Exclusive offer: get 50% off this eBook here
Puppet 3 Beginner’s Guide

Puppet 3 Beginner’s Guide — Save 50%

Start from scratch with the Puppet configuration management system, and learn how to fully utilize Puppet through simple, practical examples with this book and ebook

$26.99    $13.50
by John Arundel | July 2013 | Beginner's Guides Networking & Telephony Open Source

You can tell whether a man is clever by his answers. You can tell whether a man is wise by his questions.— Naguib Mahfouz

In this article created by John Arundel,author of Puppet 3 Beginner's Guide, you'll learn how to use Puppet's resource types to run commands, schedule regular tasks, and distribute large trees of files. You'll also find out how to insert values dynamically into files using templates.

(For more resources related to this topic, see here.)

Running commands with exec resources

We've seen that Puppet lets you model various aspects of a system using resources, such as user or file resources. You describe how the system should be configured, and Puppet will run appropriate commands behind the scenes to bring about the desired state.

But what if you want Puppet to run a certain command directly? You can do this using an exec resource. This is a very flexible and powerful resource, and you can use it to implement almost anything in Puppet. In this section we'll see how to get the most from exec resources.

Time for action – running an arbitrary command

  1. Modify your manifests/nodes.pp file as follows:

    node 'demo' {
    exec { 'Run my arbitrary command':
    command => '/bin/echo I ran this command on `/bin/date` >/tmp/
    command.output.txt',
    }
    }

  2. Run Puppet:

    ubuntu@demo:~/puppet$ papply
    Notice: /Stage[main]//Node[demo]/Exec[Run my arbitrary command]/
    returns: executed successfully
    Notice: Finished catalog run in 0.14 seconds

  3. Check the output produced (you won't see exactly the same date and time shown here, unless you're a Time Lord):

    ubuntu@demo:~/puppet$ cat /tmp/command.output.txt
    I ran this command on Mon Dec 17 16:14:04 UTC 2012

What just happened?

The line exec { 'Run my arbitrary command': declares an exec resource with the name Run my arbitrary command. The name can be anything; it's not otherwise used by Puppet, except that like all resource names it can't be the same as another instance of the same resource type.

The command to run is specified by the following line:

command => '/bin/echo I ran this command on `/bin/date` >/tmp/command.
output.txt',

Note that the UNIX commands, echo and date, are specified with their full path. This is because Puppet wants to be sure exactly which command you mean.

When Puppet runs, it applies the exec resource by running the command:

/bin/echo I ran this command on `/bin/date` >/tmp/command.output.txt

This command will write the following text to /tmp/command.output.txt:

I ran this command on Mon Dec 17 16:14:04 UTC 2012

Running commands selectively

The exec resource we've created will be applied every time Puppet runs, but that's not always what we want. Say we are using an exec resource to download a file, for example. Once the file is downloaded the first time we don't need to do it again. Here's an example:

exec { 'Download public key for John':
cwd => '/tmp',
command => '/usr/bin/wget http: // bitfieldconsulting.com/files/john.
pub',
creates => '/tmp/john.pub',
}

The creates attribute specifies the full path to a file. Puppet will check to see if this file already exists. If it does, the exec won't be run. This is a neat way to have a command run only if it is needed, and not otherwise.

Did you notice we also added the cwd attribute? This tells Puppet the directory in which to run the command (cwd stands for current working directory), so that any files created by the command, like john.pub in this example, will end up in that directory.

You can also use the unless or onlyif attributes to control when an exec is run. unless or onlyif both specify a command for Puppet to run to test whether the exec needs to be applied.

The exit status of the test command determines what Puppet should do. For example:

exec { 'add-cloudera-apt-key':
command => '/usr/bin/apt-key add /tmp/cloudera.pub',
unless => '/usr/bin/apt-key list |grep Cloudera',
}

Here, we're using an exec to add an APT repository key to the system keyring. This only needs to be done once, so the unless command checks whether the key has already been added. If the grep succeeds, we know the key is already present, so we don't need to do anything. The exit status will be zero, so Puppet won't apply the exec. On the other hand, if the grep fails, the exit status will be non-zero so Puppet will apply the exec.

Using onlyif, the opposite logic applies; the exec will be run only if the test command succeeds (exits with a zero status).

Triggering commands

Another way to control when an exec is run is to use the refreshonly attribute:

exec { 'icinga-config-check':
command => '/usr/sbin/icinga -v /etc/icinga/icinga.cfg && /usr/
sbin/service icinga restart',
refreshonly => true,
subscribe => File['/etc/icinga/icinga.cfg'],
}

When refreshonly is set, Puppet will not apply the exec unless it's triggered by subscribe or notify from some other resource. In this example, the exec subscribes to the file /etc/icinga/icinga.cfg. If this file changes, Puppet will run the exec, but not otherwise.

This is a very useful pattern when you want to take some action if a config file changes,especially if you want to sanity-check the file's contents (as in the example) before restarting the service that reads it.

Chaining commands

Often you have a series of commands that need to run in a particular order (for example, if you're installing sofware from source, you might need to download a file, unpack it, build it, and install it). To do this, for short sequences, you can use the shell && construct as shown in the preceding example:

/usr/sbin/icinga -v /etc/icinga/icinga.cfg && /usr/sbin/service icinga
restart

This will chain the commands together in the order you specify, bailing out if any of the commands fail.

For more complicated sequences, or where you may also need to trigger individual commands from other resources, you can use the require attribute to specify the ordering explicitly:

exec { 'command-1':
command => '/bin/echo Step 1',
}
exec { 'command-2':
command => '/bin/echo Step 2',
require => Exec['command-1'],
}
exec { 'command-3':
command => '/bin/echo Step 3',
require => Exec['command-2'],
}

Command search paths

As we've seen, Puppet requires us to specify the full path to any command referenced in an exec resource. However, if you like, you can provide a list of paths for Puppet to search for commands, using the path attribute. For example:

exec { 'Run my arbitrary command':
command => 'echo I ran this command on `date` >/tmp/command.output.
txt',
path => ['/bin', '/usr/bin'],
}

Now when Puppet sees a command name, it will search the directories you specify looking for the matching commands.

If you want to specify a set of default search paths for all exec resources, you can put this in your site.pp file:

Exec {
path => ['/bin', '/usr/bin'],
}

Note the capital E for Exec. This means "make this the default for all exec resources." Then you can use unqualified commands without an explicit path attribute:

exec { 'Run my arbitrary command':
command => 'echo I ran this command on `date` >/tmp/command.output.
txt',
}

Puppet will use the default paths you specified: /bin and /usr/bin.

Scheduled tasks

Typically, when you want a command to be run at a certain time of day, or at regular intervals, you can use the UNIX cron facility. For example, a backup job might run every night at 4 a.m., or a queue processing task might run every 5 minutes.

Puppet can manage cron jobs directly using the cron resource type. Here's an example.

Time for action – scheduling a backup

  1. Modify your manifests/nodes.pp file as follows:

    node 'demo' {
    cron { 'Back up cat-pictures':
    command => '/usr/bin/rsync -az /var/www/cat-pictures/ /cat-
    pictures-backup/',
    hour => '04',
    minute => '00',
    }
    }

  2. Run Puppet:

    ubuntu@demo:~/puppet$ papply
    Notice: /Stage[main]//Node[demo]/Cron[Back up cat-pictures]/
    ensure: created
    Notice: Finished catalog run in 0.12 seconds

  3. Check that the cron job was correctly configured:

    ubuntu@demo:~/puppet$ sudo crontab -l
    # HEADER: This file was autogenerated on Tue Dec 18 12:50:11 +0000
    2012 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: Back up cat-pictures
    0 4 * * * /usr/bin/rsync -avz /var/www/cat-pictures/ /cat-
    pictures-backup/

What just happened?

The line cron { 'Back up cat-pictures': declares a cron resource named Back up cat-pictures (as with exec resources, the name doesn't mater, but it must be unique).

command => '/usr/bin/rsync -avz /var/www/cat-pictures/ /cat-pictures-
backup/',

The preceding line sets the command to run (in this case, an rsync command to back up all files and directories under /var/www/cat-pictures to /cat-pictures-backup). As with exec resources, commands need to be qualified with their full path.

We now go on to specify the time at which the job should run.

hour => '04',

This is in 24-hour format, with 00 being midnight, and 23 being 11 p.m.

minute => '00',

If minute is not specified, it defaults to *; that is, it runs every minute! So always specify both hour and minute (if there is no hour, the job runs every hour at the minute you specify).

Note that Puppet adds a helpful header to the crontab file, warning you not to meddle in the affairs of Puppet. In fact, you can safely add, remove, and modify any cron jobs not managed by Puppet. Puppet identifies the cron jobs it's managing by the Puppet Name comment above each job. So, as the warning suggests, don't remove or edit these comments or Puppet will think the job is missing and add a new copy of it.

More scheduling options

The cron resources can have several other attributes to set the time for the scheduled job:

  • weekday – the day of the week, for example, Friday
  • month – not often used, but can be used to run jobs only during a specific month, for example, January
  • monthday – the day of the month, for example 1 to run a job on the first day of each month

If any of these attributes are not supplied, they default to *; that is, every weekday, every month, or every day of the month.

Running jobs at regular intervals

If you want to run a job every 5 minutes, say, you can specify an interval such as this:

minute => '*/5',
hour => '*',

You can use the same pattern with the other time attributes, for example, to run a job every 6 hours on the hour:

hour => '*/6',
minute => '00',

Running a job as a specified user

The default user for cron jobs is root, but if you want to run the job as a different user,just give the cron resource a user attribute:

user => 'www-data',

The job will be added to the crontab file for www-data.

Exercise

Use a cron resource to automate the pull-updates job you set up in Managing Puppet with Git, which automatically pulls Git changes and applies Puppet on each machine. Make this part of every machine's base configuration.

Distributing files

Sometimes, though, you need to copy a whole directory tree of files, without having to list each individual file in your Puppet manifest. The recurse attribute allows you to do this. We'll see how to use it in the next example.

Time for action – using a recursive file resource

The cat-pictures application is nearly complete, but it needs some pictures of cats added in time for the launch. The art department has sent over a set of feline stock photos for you to deploy to the website.

  1. Create the directories for a new cat-pictures module:

    ubuntu@demo:~/puppet$ mkdir modules/cat-pictures
    ubuntu@demo:~/puppet$ mkdir modules/cat-pictures/files

  2. Create a directory for the images, and some placeholder image files (for extra credit, download some real pictures of cats from the Internet):

    ubuntu@demo:~/puppet$ mkdir modules/cat-pictures/files/img
    ubuntu@demo:~/puppet$ mkdir modules/cat-pictures/files/img/
    cat_001.jpg
    ubuntu@demo:~/puppet$ mkdir modules/cat-pictures/files/img/
    cat_002.jpg
    ubuntu@demo:~/puppet$ mkdir modules/cat-pictures/files/img/
    cat_003.jpg

  3. Modify your manifests/nodes.pp file as follows:

    node 'demo' {
    file { '/var/www/cat-pictures':
    ensure => directory,
    }
    file { '/var/www/cat-pictures/img':
    source => 'puppet:///modules/cat-pictures/img',
    recurse => true,
    require => File['/var/www/cat-pictures'],
    }
    }

  4. Run Puppet:

    ubuntu@demo:~/puppet$ papply
    Notice: /Stage[main]//Node[demo]/File[/var/www/cat-pictures]/
    ensure: created
    Notice: /Stage[main]//Node[demo]/File[/var/www/cat-pictures/img]/
    ensure: created
    Notice: /File[/var/www/cat-pictures/img/cat_002.jpg]/ensure:
    created
    Notice: /File[/var/www/cat-pictures/img/cat_001.jpg]/ensure:
    created
    Notice: /File[/var/www/cat-pictures/img/cat_003.jpg]/ensure:
    created
    Notice: Finished catalog run in 0.08 seconds

What just happened?

First we created a top-level directory for the site files to live in:

file { '/var/www/cat-pictures':
ensure => directory,
}

We haven't seen a file resource before without either a source or a content attribute. ensure => directory will create a directory, as you might expect. If you said ensure => present instead, with no other attributes, Puppet would create an empty file.

The following code is the part that does the heavy lifting:

file { '/var/www/cat-pictures/img':
source => 'puppet:///modules/cat-pictures/img',
recurse => true,
require => File['/var/www/cat-pictures'],
}

The source attribute is as you've used it before, but the recurse => true attribute tells Puppet to copy all files and directories contained in the source. This includes our handful of cat pictures, but it could be thousands of files in a tree of directories many levels deep.

In practice Puppet is rather slow to manage large file trees, because it has to examine every file in the tree on every run to determine if it is up to date with the source. In this situation, you might be better of using Git, for example, to manage large trees of files.

Using templates

In a previous example we had Puppet deploy an Nginx virtual host file for the cat-pictures application. In this case we simply used a file resource with the cat-pictures.conf file distributed from Puppet.

If we wanted to generalize this solution to manage many different websites, it would quickly become tedious to supply an almost identical virtual host file for each site, altering only the name and domain of the site.

What we would prefer is to give Puppet a template file into which it could just insert these variables for each diferent site. The template function serves just this purpose. Anywhere you have multiple files that differ only slightly, or files that need to contain dynamic information, you can use a template.

Puppet 3 Beginner’s Guide Start from scratch with the Puppet configuration management system, and learn how to fully utilize Puppet through simple, practical examples with this book and ebook
Published: April 2013
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

Time for action – templating an Nginx virtual host

Things are looking up at cat-pictures.com headquarters. They've just got VC funding to build three new sites: dog-pictures.com, hamster-pictures.com, and fish-pictures.com. To prepare for this, your job is to change the Puppet config for cat-pictures.com to use a template, so that you can later use the same template for the new sites.

  1. Modify the modules/nginx/manifests/init.pp file as follows:

    # Manage nginx webserver
    class nginx {
    package { 'nginx':
    ensure => installed,
    }
    service { 'nginx':
    ensure => running,
    enable => true,
    require => Package['nginx'],
    }
    file { '/etc/nginx/sites-enabled/default':
    ensure => absent,
    }
    }

  2. Create a new templates directory in the nginx module:

    ubuntu@demo:~/puppet$ mkdir modules/nginx/templates

  3. Create the file modules/nginx/templates/vhost.conf.erb with the following contents:

    server {
    listen 80;
    root /var/www/<%= @site_name %>;
    server_name <%= @site_domain %>;
    }

  4. Modify your manifests/nodes.pp file as follows:

    node 'demo' {
    include nginx
    $site_name = 'cat-pictures'
    $site_domain = 'cat-pictures.com'
    file { '/etc/nginx/sites-enabled/cat-pictures.conf':
    content => template('nginx/vhost.conf.erb'),
    notify => Service['nginx'],
    }
    }

  5. Run Puppet:

    ubuntu@demo:~/puppet$ papply
    Notice:/Stage[main]//Node[demo]/File[/etc/nginx/sites-enabled/cat-
    pictures.conf]/ensure: defined content as '{md5}0750fd1b8da76b84f2
    597de76c1b9bce'
    Notice: /Stage[main]/Nginx/File[/etc/nginx/sites-enabled/default]/
    ensure: removed
    Notice: /Stage[main]/Nginx/Service[nginx]: Triggered 'refresh'
    from 1 events
    Notice: Finished catalog run in 0.74 seconds

  6. Check the resulting virtual host file:

    ubuntu@demo:~/puppet$ cat /etc/nginx/sites-enabled/cat-pictures.
    conf
    server {
    listen 80;
    root /var/www/cat-pictures;
    server_name cat-pictures.com;
    }

What just happened?

First some housekeeping; we previously used the file /etc/nginx/sites-enabled/default as the virtual host for cat-pictures.com, so we need to remove that:

file { '/etc/nginx/sites-enabled/default':
ensure => absent,
}

We create a template file for the virtual host definition:

server {
listen 80;
root /var/www/<%= @site_name %>;
server_name <%= @site_domain %>;
}

The <%= %> signs mark where parameters will go; we will supply site_name and site_domain later, when we use the template. Puppet will replace <%= @site_name %> with the value of the site_name variable.

Then in the nodes.pp file, we include the nginx module on the node:

node 'demo' {
include nginx

Before using the template, we need to set values for the variables site_name and site_domain:

$site_name = 'cat-pictures'
$site_domain = 'cat-pictures.com'

Note that when we refer to these variables in Puppet code, we use a $ prefix ($site_name), but in the template it's an @ prefix (@site_name). This is because in templates we're actually writing Ruby, not Puppet!

When you use a variable name inside a quoted string, it's a good idea to wrap it in curly brackets as follows:

"The domain is ${site_domain}"

Not:

"The domain is $site_domain"

This helps to distinguish the variable name from the literal string it's used in(and any other variables you might be using in the same string).

Now we can use the template to generate the Nginx virtual host file:

file { '/etc/nginx/sites-enabled/cat-pictures.conf':
content => template('nginx/vhost.conf.erb'),
notify => Service['nginx'],

This looks just like any other file resource, with a content attribute, but we previously gave the contents of the file as a literal string:

content => "Hello, world\n",

Instead, here there is a call to the template function:

content => template('nginx/vhost.conf.erb'),

The argument to template tells Puppet where to find the template file. The path

nginx/vhost.conf.erb

Translates to

modules/nginx/templates/vhost.conf.erb

Puppet now evaluates the template, inserting the values of any variables referenced in <%= %> signs, and generates the final output:

server {
listen 80;
root /var/www/cat-pictures;
server_name cat-pictures.com;
}

You might think this is a lot of trouble to go to just to end up with the same file we had before. Of course, having gone to the trouble of using a template, we can now easily create virtual hosts for other sites using the same template file:

node 'demo2' {
include nginx
$site_name = 'dog-pictures'
$site_domain = 'dog-pictures.com'
file { '/etc/nginx/sites-enabled/dog-pictures.conf':
content => template('nginx/vhost.conf.erb'),
notify => Service['nginx'],
}
}

Inline templates

You don't need to use a separate template file to take advantage of the power of templates. The inline_template function lets you put a template string right in your Puppet code:

file { '/tmp/the_answer.txt':
content => inline_template("What do you get if you multiply six by
nine? <%= 6 * 7 %>.\n")
}

System facts

It's often useful to be able to get information about the system, such as its IP address or operating system version. Puppet's companion tool, Facter, provides this information. To see the list of facts available about your system, run the command:

ubuntu@demo:~/puppet$ facter
architecture => amd64
...
uptime_hours => 2109
uptime_seconds => 7593471
virtual => xenu

You can reference any of these facts in a template (or in your Puppet code) just like a variable:

content => inline_template("My address is <%= @ipaddress %>.\n")

There are a lot of facts. The ones you will most likely use in Puppet manifests are:

  • architecture – reports the system processor architecture and bitness (32- or 64-bit)
  • fqdn – the fully-qualified domain name of the machine; for example, demo.cat-pictures.com
  • hostname – just the hostname part; for example, demo
  • ipaddress – the IP address of the primary or first network interface. If there are multiple interfaces, you can find their addresses with ipaddress_eth0, ipaddress_eth1, and so on
  • memorysize – the amount of physical memory present
  • operatingsystem – the name of the machine's OS (for example, Ubuntu or CentOS)
  • operatingsystemrelease – the specific OS version (for example, 12.04 for Ubuntu Precise)

Doing the math

Actually, you can do more than just insert variables and facts in templates. Puppet's templating engine is called ERB, which uses Ruby, and in fact, everything between the <%= and %> signs is Ruby code. So you can do math:

Two plus two is <%= 2 + 2 %>

Or call Ruby methods:

The time is <%= Time.now %>

Or evaluate Ruby expressions:

$vagrant_vm = inline_template("<%= FileTest.exists?('/tmp/vagrant-
puppet') ? 'true' : 'false' %>")

Putting it all together

You can combine facts, variables, arithmetic, string operations, and Ruby logic to do some quite sophistcated things in templates. Here's an example that uses the memorysize fact to modify a configuration file based on the physical RAM present. Unfortunately for us, memorysize isn't returned as a simple integer representing the number of megabytes,say. It's a string that includes the unit, for example 512.20 MB or 31.40 GB.

So before we can do computations with this figure, we need to normalize it to an integer number of megabytes:

<% raw_memsize = @memorysize
if raw_memsize.include?("GB")
mem_in_mb = raw_memsize.to_f * 1024
else
mem_in_mb = raw_memsize.to_f
end
%>
export HADOOP_DATANODE_OPTS="-XX:MaxDirectMemorySize=<%= ( mem_in_mb *
0.25 ).to_i %>M ${HADOOP_DATANODE_OPTS}"

Having copied this code (of mine) from a production system, I see that it isn't really very good. It assumes the only units returned will be MB or GB, so it will fail on systems with memory measured in terabytes (TB), for example. But you get the idea, and your code will be better.

Summary

A quick rundown of what we've learned in this article.

Exec resources

Anything you can do on the command line, Puppet can do with an exec resource. Specify the command to run using the command attribute:

exec { 'Run my arbitrary command':
command => '/bin/echo I ran this command on `/bin/date` >/tmp/
command.output.txt',
}

By default, an exec resource will always be applied, every time you run Puppet. There are several ways to control whether or when an exec will be applied:

  • creates runs the exec only if a given file doesn't exist
  • onlyif runs the exec only if a given command succeeds
  • unless runs the exec only if a given command fails

To run the command in a specified directory, use the cwd attribute:

exec { 'Download public key for John':
cwd => '/tmp',
command => '/usr/bin/wget http: // bitfieldconsulting.com/files/john.
pub',
creates => '/tmp/john.pub',
}

To apply the command only when triggered by some other resource, use the refreshonly attribute:

exec { 'icinga-config-check':
command => '/usr/sbin/icinga -v /etc/icinga/icinga.cfg && /usr/
sbin/service icinga restart',
refreshonly => true,
subscribe => File['/etc/icinga/icinga.cfg'],
}

This will apply the exec only when the resource it subscribes to (/etc/icinga/icinga.cfg) is changed. You could have the other resource notify the exec instead, which has the same effect.

For short sequences of commands, you can chain them in a single exec using the & shell operator:

/usr/sbin/icinga -v /etc/icinga/icinga.cfg && /usr/sbin/service icinga
restart

For longer sequences using multiple exec resources, you can specify the necessary ordering using require:

exec { 'command-1':
command => '/bin/echo Step 1',
}
exec { 'command-2':
command => '/bin/echo Step 2',
require => Exec['command-1'],
}

Puppet requires you to specify the full path to each command you run in an exec, unless you specify a list of paths to search for commands using the path attribute:

exec { 'Run my arbitrary command':
command => 'echo I ran this command on `date` >/tmp/command.output.
txt',
path => ['/bin', '/usr/bin'],
}

You can set a default list of paths for all exec resources in your site.pp file:

Exec {
path => ['/bin', '/usr/bin'],
}

Scheduled jobs

To run commands at a specified tme of day, or at regular intervals, you can use a cron resource:

cron { 'Back up cat-pictures':
command => '/usr/bin/rsync -az /var/www/cat-pictures/ /cat-pictures-
backup/',
hour => '04',
minute => '00',
}

You can use any combination of these attributes to set the scheduled time for the job: hour,minute, day, weekday, monthday, month.

You can run a job at regular intervals (every 5 minutes, for example) with a setting like this:

minute => '*/5',

Cron jobs default to running as root. To make a job execute as a particular user, specify the user attribute:

user => 'www-data',

Recursive file resources

To have Puppet copy a whole tree of files, use the recurse attribute on a file resource:

file { '/var/www/cat-pictures/img':
source => 'puppet:///modules/cat-pictures/img',
recurse => true,
require => File['/var/www/cat-pictures'],
}

Templates

Templates can be used wherever you need to insert information into a file based on Puppet variables or Facter facts. You can also use Ruby code in templates to do math or string computations, or read and write files, anything, in fact, that Ruby can do. Just specify a template file using the template function:

file { '/etc/nginx/sites-enabled/cat-pictures.conf':
content => template('nginx/vhost.conf.erb'),
notify => Service['nginx'],
}

The most common use for templates is simply inserting the value of a variable:

server_name <%= @site_domain %>;

But you can use any valid Ruby code in a template:

The time is <%= Time.now %>

Inline templates don't require a separate template file; you just supply the template to Puppet as a string in your manifest and call the inline_template function to evaluate it:

file { '/tmp/the_answer.txt':
content => inline_template("What do you get if you multiply six by
nine? <%= 6 * 7 %>.\n")
}

Summary

In this article, we learned how to use Puppet's resource types to run commands, schedule regular tasks, and distribute large trees of files. We also learned how to insert values dynamically into files using templates.

Resources for Article:


Further resources on this subject:


Puppet 3 Beginner’s Guide Start from scratch with the Puppet configuration management system, and learn how to fully utilize Puppet through simple, practical examples with this book and ebook
Published: April 2013
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

About the Author :


John Arundel

John Arundel is a devops consultant, which means he solves difficult problems for a living. (He doesn't get called in for easy problems.)

He has worked in the tech industry for 20 years, and during that time has done wrong (or seen done wrong) almost everything that you can do wrong with computers. That comprehensive knowledge of what not to do, he feels, is one of his greatest assets as a consultant. He is still adding to it.

He likes writing books, especially about Puppet (The Puppet 3 Beginner's Guide is available from the same publisher). It seems that at least some people enjoy reading them. He also provides training and coaching on Puppet, which it turns out is far harder than simply doing the work himself.

Off the clock, he can usually be found driving a Land Rover up some mountain or other. He lives in a small cottage in Cornwall and believes, like Cicero, that if you have a garden and a library, then you have everything you need.

You can follow him on Twitter at @bitfield.

Books From Packt


Puppet 2.7 Cookbook
Puppet 2.7 Cookbook

Apache CloudStack Cloud Computing
Apache CloudStack Cloud Computing

Instant Puppet 3 Starter [Instant]
Instant Puppet 3 Starter [Instant]

OpenNebula 3 Cloud Computing
OpenNebula 3 Cloud Computing

OpenStack Cloud Computing Cookbook
OpenStack Cloud Computing Cookbook

Nginx 1 Web Server Implementation Cookbook
Nginx 1 Web Server Implementation Cookbook

Nginx HTTP Server
Nginx HTTP Server

Nginx HTTP Server Second Edition
Nginx HTTP Server Second Edition


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software