Create a Puppet test network with VirtualBox

Puppet is “a collection of tools built around a language that allows systems administrators to specify configurations, or manifests, to describe the state of a computer system.” Also see the linode library docs.

This document explains the process of creating a test environment on a laptop, with two virtual machines configured on a simulated internal network. One will be the puppet master, the other will be the puppet client. Then we will explore some configurations for common usage.

When I started with Puppet, it seemed difficult to find documentation with everything I was looking for in one place. Some of my initial questions were:

  • “Do I need modules, classes, templates, or all of the above? What are the differences?”
  • “How can I create a simple way to reproduce one folder on each node?”
  • “How do I configure the ntp and sudoers file and distribute them to the nodes?”
  • “How can I create users and change their passwords?”


Download and install VirtualBox.

Download the install DVD for CentOS, or for your preferred Linux distribution.

Create two virtual machines using the install DVD as the virtual DVD-ROM drive. Set the hostnames: puppetm and puppetc.

Open port 8140 for both TCP and UDP. In CentOS, this can be done using “system-config-securitylevel”, Other Ports.

Select “Host only networking adapter” (in this example, called vboxnet0) and leave the selection of the main network Adapter (such as Intel Pro/1000 MT)

Check that the built-in DHCP server in VirtualBox is active, and take note of the IP addresses granted.

Add those IP addresses to the /etc/hosts on both the puppet master and client:  localhost.localdomain localhost puppetm.localdomain puppetm puppetc.localdomain puppetc

Install Puppet via yum:

rpm -Uvh
yum update
yum install puppet puppet-server

Start the puppetmaster service on the first system with /etc/init.d/puppetmaster start. Check /var/log/messages to confirm that the key has been created and signed.

On the puppet client system, request the cert from the master:

puppetd --server puppetm.localdomain --waitforcert 60 --test

On the puppet master, check if the cert request is listed:

puppetca --list

and if so, sign the cert:

puppetca --sign puppetc.localdomain

And now that installation is a piece of cake, let’s move on to the more interesting bits on configuration.

and make these changes:

path /etc/puppet/files
allow *.localdomain

Configure site.pp:


# Define the default node behavior. Let's go ahead and add these that will need to be configured.
node default {
  include sudo
  include ntp
# Define which folder includes classes
import "classes/*"

Configure ntp and ensure it is running:

Copy your modified ntp.conf to /etc/puppet/files/

class ntp {
  file { "ntp.conf":
  name => "/etc/ntp.conf",
  mode => 644,
  owner => "root",
  group => "root",
  source => "puppet://puppetm/files/ntp.conf"
 package { "ntp": ensure => installed }
 service { "ntpd":
   subscribe => File["ntp.conf"],
   require => File["ntp.conf"],
   restart => true,
   ensure => running,

Configure /etc/sudoers:

Copy your modified sudoers file to /etc/puppet/files/


class sudo {
  file { "/etc/sudoers":
   owner => "root",
   group => "root",
   mode => 440,
   source => "puppet://puppetm/files/sudoers"

Configure the file server to pull down a directory

Create the folder /etc/puppet/files/opt, add some dummy scripts here:

cd /etc/puppet/files/opt

Configure optscripts.pp:

class optscripts {
# Create the destination dir before copying. Puppet reported failed
# dependencies in this example when paths were longer than two folders deep,
# so the following seemed to work. The problem of copying the files did not
# occur when testing with a short path like /opt/scripts.

file { "/opt":
   ensure => directory
# Create the subfolders
exec { mkscriptsfldr: command => "/bin/mkdir -p /opt/scripts/build/install" }

# Configure the file distributor to copy the scripts
   file { "/opt/scripts/build/install":
   source => "puppet://puppetm/files/opt",
   recurse => "true",
   owner => "root",
   subscribe => File["/opt/scripts/build/install"],
   refreshonly => true

Test puppetd with puppetd -v -o, then check logs: tail /var/log/messages

If all works, you should now see these changes on the client:

  • the newly copied /opt/scripts folder
  • ntp.conf updated and the ntpd process running
  • /etc/sudoers updated

To see how file changes will look, try adding some lines to one of the dummy install scripts on the master, such as Then rerun

puppetd -v -o

on the client and look in the logs. You should see that the file contents have changed.

Add some standard user accounts

Puppet documentation suggests that this is best used for static system accounts, such as mysql or nagios, so that you don’t have to deal with changing passwords. But this post shows how to manage user accounts and passwords too.

In /etc/puppet/manifests, create folders ‘users’ and ‘groups’. Under groups, create virt_groups.pp:

class virt_groups {
   @group { "administration":
    gid => "1000",
    ensure => present

Under users, create this file:

class virt_users {
  @user { "jbond":
    ensure => "present",
    uid => "1001",
    gid => "1000",
    comment => "Shaken, not stirred",
    home => "/home/jbond",
    # this next line creates the home dir
    managehome => true,            
 #  this next line changes their password in /etc/shadow! Grab the first part of the password only with:
 #  `cat /etc/shadow | grep $username| cut -f 2 -d : `  and add it here in single quotes:
    password => '$1$syYkvOHY$ZxAabcdEFG1'

  @user { "drno":
    ensure => "present",
    uid => "1002",
    gid => "1000",         
  groups => ['spectre', 'administration'],
    comment => "Doctor No,drno@spectre",
    home => "/home/drno", 
    managehome => true,           
    password => '$1$syYkvOHY$ZxAabcdEFG2'

(Notice the syntax to add a user to more than one group, above.)

Create classes/administrators.pp, which will add the user to the administrators group:

class administrators inherits virt_users {
   realize (

Now return to site.pp and add the lines:

node default {
  include sudo
  include ntp
  include administrators
  include optscripts
import "groups/*"
import "users/*"
import "classes/*"

Check /etc/passwd and you should see the user has been created and added to the administrators group.

Comparing Modules to Classes

See related article here.

Use Puppetrun to manually push out a change

For the impatient, you can have puppet force a synchronization immediately to all nodes.

Open port 8139 tcp/udp on the client.

Ensure that puppetd is running on the client(s) with option “listen = true” in /etc/puppet/puppet.conf:

# puppet.conf: in the [puppetd] section:

and restart /etc/init.d/puppet on the client.

Configure access from the master to client. Edit file /etc/puppet/namespaceauth.conf on the client only:

allow *.localdomain
allow *.localdomain
allow *.localdomain
allow *.localdomain
allow *.localdomain
allow *.localdomain
allow *.localdomain
allow *.localdomain

Note that this file is very picky about formatting, and would not allow puppetrun access until correctly configured. Also double check that your /etc/hosts has the correct name and IP listed for both master and client(s).

Then run the puppetrun push on the puppet master:

puppetrun --host puppetc.localdomain

client finished with exit code 0

#On the client's logs, you should see:
puppetd: triggered run: content changed: executed successfully