Skip to main content
  1. Notes/
  2. Puppet/

Puppet

2677 words
Table of Contents

Warning

This is based on puppet 3.6 and has limited relevance on newer versions.

Puppet is a tool much like CFEngine and others allowing you the manage a set of systems by defining their desired state. Puppet allows the user to use a Domain Specific Language to define the state in which resources on a machine should be. With this the user defines the state of the machine. This makes the tool largely idempotent meaning the tool must be runnable any number of times with the machine being left in the same state the 100th time as it was in the 1st state.

This state includes but is not limited to:

  • users
  • groups
  • software
  • services
  • targets

To manage nodes Puppet uses a client/server model. The server is called master and the client is a node. The node runs an agent which periodically (30m) starts a Puppet run:

  • The agent collects facts and sends these to the master
  • The master compiles a catalog and sends the catalog to the node
  • The node applies the catalog and reports back to the master

Puppet Manifest
#

Puppet Domain-specific Language (DSL)
#

Example:

resource_type { 'title':
  attribute => value,
  ...
}

Simple examples
#

Hello World
#

A simple puppet manifest.

notify { 'Hello World': }

Validate code with puppet parser validate:

# puppet parser validate helloworld.pp

# echo $?
0

Validate code with puppet apply:

# puppet apply --noop helloworld.pp
Notice: Compiled catalog for loki.doubtfull.snd in environment production in 0.01 seconds
Notice: /Stage[main]/Main/Notify[Hello World]/message: current_value absent, should be Hello World (noop)
Notice: Class[Main]: Would have triggered 'refresh' from 1 events
Notice: Stage[main]: Would have triggered 'refresh' from 1 events
Notice: Finished catalog run in 0.13 seconds

Execute the code with puppet apply:

# puppet apply helloworld.pp
Notice: Compiled catalog for loki.doubtfull.snd in environment production in 0.01 seconds
Notice: Hello World
Notice: /Stage[main]/Main/Notify[Hello World]/message: defined 'message' as 'Hello World'
Notice: Finished catalog run in 0.05 seconds

Hello World 2
#

“Hello World” using the file resource type:

file { '/tmp/helloworld.txt':
  ensure  => 'file',
  content => 'Hello World',
  mode    => '664',
}

Validate code with puppet parser validate:

# puppet parser validate helloworld2.pp

# echo $?
0

Validate code with puppet apply:

# puppet apply --noop helloworld2.pp
Notice: Compiled catalog for loki.doubtfull.snd in environment production in 0.08 seconds
Notice: /Stage[main]/Main/File[/tmp/helloworld.txt]/ensure: current_value absent, should be file (noop)
Notice: Class[Main]: Would have triggered 'refresh' from 1 events
Notice: Stage[main]: Would have triggered 'refresh' from 1 events
Notice: Finished catalog run in 0.03 seconds

Execute the code with puppet apply:

# puppet apply helloworld2.pp
Notice: Compiled catalog for loki.doubtfull.snd in environment production in 0.09 seconds
Notice: /Stage[main]/Main/File[/tmp/helloworld.txt]/ensure: defined content as '{md5}b10a8db164e0754105b7a99be72e3fe5'
Notice: Finished catalog run in 0.09 seconds

Check the results:

# cat /tmp/helloworld.txt
Hello World#

warning

notice the lack of newline.

Fix:

--- helloworld2.pp.orig 2017-07-19 14:17:03.696247324 +0200
+++ helloworld2.pp      2017-07-19 14:17:14.588234830 +0200
@@ -1,5 +1,5 @@
 file { '/tmp/helloworld.txt':
   ensure  => 'file',
-  content => 'Hello World',
+  content => "Hello World\n",
   mode    => '664',
 }

Re-execute the code with puppet apply:

# puppet apply helloworld2.pp
Notice: Compiled catalog for loki.doubtfull.snd in environment production in 0.09 seconds
Notice: /Stage[main]/Main/File[/tmp/helloworld.txt]/content: content changed '{md5}b10a8db164e0754105b7a99be72e3fe5' to '{md5}e59ff97941044f85df5297e1c302d260'
Notice: Finished catalog run in 0.19 seconds

Check the results again:

# cat /tmp/helloworld.txt
Hello World
#

No sense of order
#

Without coercion the puppet manifests are executed in seemingly random order.

Code:

notify { 'Hello World 1': }
notify { 'Hello World 2': }
notify { 'Hello World 3': }
notify { 'Hello World 4': }
notify { 'Hello World A': }

Execute the code with puppet apply:

# puppet apply nosenseoforder.pp
Notice: Compiled catalog for loki.doubtfull.snd in environment production in 0.01 seconds
Notice: Hello World 1
Notice: /Stage[main]/Main/Notify[Hello World 1]/message: defined 'message' as 'Hello World 1'
Notice: Hello World 3
Notice: /Stage[main]/Main/Notify[Hello World 3]/message: defined 'message' as 'Hello World 3'
Notice: Hello World A
Notice: /Stage[main]/Main/Notify[Hello World A]/message: defined 'message' as 'Hello World A'
Notice: Hello World 4
Notice: /Stage[main]/Main/Notify[Hello World 4]/message: defined 'message' as 'Hello World 4'
Notice: Hello World 2
Notice: /Stage[main]/Main/Notify[Hello World 2]/message: defined 'message' as 'Hello World 2'
Notice: Finished catalog run in 0.09 seconds

This has some impact if you for example have a package type and a service type in the same manifest!

Bring order to chaos
#

Puppet assumes that most resources are not related to each other and will manage the resources in whatever order it deems most efficient.

Puppet uses four metaparameters to establish relationships. Each of them can be set as an attribute in any resource.


attribute Causes a resource to be applied


before before the target resource.

require after the target resource.

notify before the target resource. The target resource will refresh if the notifying resource changes.

subscribe after the target resource. The subscribing resource will refresh if the target resource changes.
#

Form:

Resource_type[ resource_title ]

note

capitalize the resource

The two examples below create the same ordering relationship:

package { 'openssh-server':
  ensure => present,
  before => File['/etc/ssh/sshd_config'],
}

file { '/etc/ssh/sshd_config':
  ensure  => file,
  mode    => 600,
  source  => 'puppet:///modules/sshd/sshd_config',
  require => Package['openssh-server'],
}

The two examples below create the same notification relationship:

file { '/etc/ssh/sshd_config':
  ensure => file,
  mode   => 600,
  source => 'puppet:///modules/sshd/sshd_config',
  notify => Service['sshd'],
}

service { 'sshd':
  ensure    => running,
  enable    => true,
  subscribe => File['/etc/ssh/sshd_config'],
}

Chaining Arrows
#

You can create relationships between two resources or groups of resources using the -> and ~> operators.


-> ordering arrow Causes the resource on the left to be applied before the resource on the right. Written with a hyphen and a greater-than sign.

~> notification arrow Causes the resource on the left to be applied first, and sends a refresh event to the resource on the right if the left resource changes. Written with a tilde and a greater-than sign.


Example 1:

# ntp.conf is applied first, and will notify the ntpd service if it changes:
File['/etc/ntp.conf'] ~> Service['ntpd']

Example 2:

# Anchor this as per #8040 - this ensures that classes won't float off and
# mess everything up.  You can read about this at:
# http://docs.puppetlabs.com/puppet/2.7/reference/lang_containment.html#known-issues
anchor { 'ntp::begin': } ->
class { '::ntp::install': } ->
class { '::ntp::config': } ~>
class { '::ntp::service': } ->
anchor { 'ntp::end': }

note

anchor { ’ntp::begin’: } and anchor { ’ntp::end’: } are just
markers.

Facts
#

In the early stages of a Puppet run a node will collect system infomation with Facter and present them as pre-set variables also known as facts.

In the manifests they are simply used as variables. Listing of `facter-example.pp`:

if $::osfamily == 'redhat' {
  $fpath = '/tmp/example'
}

Facts also appear in a $facts hash. Example:

if $facts['os']['family'] == 'redhat' {
  $fpath = '/tmp/example'
}

These facts can be queried using the facter command and extended by adding targets to one of these directories:

  • /etc/facter/facts.d
  • /etc/puppetlabs/facter/facts.d

Conventions for new facts
#

There are four types of extension accepted by Facter:

A plain text variant (extension .txt):

foo=bar

note

NO quotes!!!!

A yaml variant (extension .yaml):

---
foo: 'bar'

A json variant (extension .json):

{
  "foo": "bar"
}

And an executeable variant, output same as txt variant.

Variables
#

Variables are defines much like any other, however Puppet only allows a given variable to be assigned once within a given scope.

Example:

$boolean_var = true
$number_var = 5
$string_var = "Custom message.\n"
$array_var = [ 'a', 'b', 'c' ]
$hash_var = {
 'first'  => 'primary',
 'second' => 'secondary',
 'third'  => 'tertiary',
}

$:: prefix for facter variables

Example `scope-example.pp`:

$myvar = "Top scope value"
node 'www1.example.com' {
  $myvar = "Node scope value"
  notice( "from www1: $myvar" )
  include myclass
}
node 'db1.example.com' {
  notice( "from db1: $myvar" )
  include myclass
}
class myclass {
  $myvar = "Local scope value"
  notice( "from myclass: $myvar" )
}

Results on www1.example.com:

Notice: Scope(Node[www1.example.com]): from www1: Node scope value
Notice: Scope(Class[Myclass]): from myclass: Local scope value
Notice: Compiled catalog for www1.example.com in environment production in 0.01 seconds
Notice: Finished catalog run in 0.04 seconds

Results in db1.example.com:

Notice: Scope(Node[db1.example.com]): from db1: Top scope value
Notice: Scope(Class[Myclass]): from myclass: Local scope value
Notice: Compiled catalog for db1.example.com in environment production in 0.01 seconds
Notice: Finished catalog run in 0.20 seconds

Conditionals
#

if / unless
#

if $::is_virtual {
  file { '/tmp/virtual.txt':
    ensure  => 'file',
    content => "This is a virtual system. Disk is ${disk}.\n",
  }
}
if $::osfamily == 'RedHat' or $::osfamily == 'CentOS' {
  notify { "This is a Red Hat family system." : }
}
elsif $::osfamily == 'Solaris' {
  notify { "This is a Solaris system." : }
}
else {
  notify { "Not sure what OS family this system is." : }
}

case
#

case $::osfamily {
  'RedHat', 'CentOS': {
    notify { "This is a Red Hat family system." : }
  }
  'Solaris': {
    notify { "This is a Solaris system." : }
  }
  default: {
    notify { "Not sure what OS family this system is." : }
  }
}

selector
#

$message = $operatingsystem ? {
  /Linux/      => 'Powered by Linux.',
  'Solaris'    => 'Powered by Solaris.',
  default      => 'Welcome.',
}

Implementing Regular Expressions
#

Puppet regexps are like ruby regexp:

if $hostname =~ /^www(\d+)\./ {
  notice("Welcome to web server number $1")
}

if $hostname !~ /^www/ {
  notice("Dunno where you are")
}

Metacharacter Description


. character [ ] character within the brackets [^ ] character not contained within the brackets \\w alphanumeric character \\W nonalphanumeric character \\d digit character \\D nondigit character \\s space character \\S nonspace character

Modifier Description


* zero or more times ? zero or one time

  •       one or more times
    

{x} exactly x times {x,} x or more times {,y} y or less times {x,y} at least x but not more than y times

Metacharacter Description


^ Matches the beginning of the line $ Matches the end of the line

Implementing Classes
#

minimal:

class class_name {
  resource definitions...
}

parameters:

class class_name ($param = 'value') {
  resource definitions...
}

note

Puppet modules should define Puppet classes, one per manifest, and
the main class defined in manifests/init.pp should have the same name as the module.

Puppet Modules
#

Puppet Modules are comparable to ansible’s roles. You can roll you own or download the

Creating Modules
#

In short:

  • Create a skeleton structure, puppet module generate
  • Edit the content
  • Test the module by applying the test/init.pp manifest in the structure.
  • Build the module puppet module build
  • Install the module puppet module install

generate
#

To create a new module the puppet module generate - command is executed. Take care to adhere to this since when including the module only the module’s name is mentioned. So a module that is generated like this:

puppet module generate foo-bar

Will be included like this:

include bar

editing the content
#

When the module is generated take care to look at the metadata.json the reference to puppetlabs-stdlib is often not needed and would create useless dependencies.

note

Update the version in metadata.json when making changes!!

testing
#

The skeleton contains a test/init.pp manifest. This is a manifest that references the created module making it possible to test it.

building
#

To build the module puppet module build this generates a tar file with the name in pkg/–.tar.gz

installing
#

Install the module by executing:

puppet module install <name>|<forgename>

puppet module install

http://docs.puppetlabs.com/puppet/3.6/reference/modules_fundamentals.html

namespaces
#

http://docs.puppetlabs.com/puppet/3.6/reference/lang_namespaces.html

Files
#

Files
#

source => 'puppet:///modules/MODULENAME/FILENAME',

note

/etc/puppet/modules/MODULENAME/files/FILENAME

file { "/etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-${os_maj_release}":
  ensure => present,
  owner  => 'root',
  group  => 'root',
  mode   => '0644',
  source => "puppet:///modules/epel/RPM-GPG-KEY-EPEL-${os_maj_release}",
}

note

/etc/puppet/modules/epel/files/RPM-GPG-KEY-EPEL-7

Templates
#

content => template('MODULENAME/FILENAME.erb'),
file { 'mysql-config-file':
  path                    => $mysql::server::config_file,
  content                 => template('mysql/my.cnf.erb'),
  mode                    => '0644',
  selinux_ignore_defaults => true,
}

note

/etc/puppet/modules/mysql/templates/my.cnf.erb

<%# COMMENT %>
<%= EXPRESSION %>
<%= @ipaddress %>   <%= @fqdn %>

Test code:

validate_erb() {
  erb -P -x -T - $1 | ruby -c
}

Implementing Puppet
#

Implementing a Puppet Master
#

Commands:

yum install -y puppet-server
systemctl start puppetmaster.service
systemctl enable puppetmaster.service
firewall-cmd --permanent --add-port=8140/tcp
firewall-cmd --reload

File that contains nodes: /etc/puppet/manifests/site.pp.

Implementing a Puppet Client
#

Client commands:

yum install -y puppet

echo "server = <puppetmaster fqdn>" >> /etc/puppet/puppet.conf

systemctl start puppet.service
systemctl enable puppet.service

Commands on the master:

puppet cert list
puppet cert sign node.example.com.
[agent]
runinterval = 15m
puppet agent --configprint runinterval

Commands
#

puppet agent
#

Print the run interval of an agent:

# puppet agent --configprint runinterval
1800

puppet resource
#

The puppet resource command uses the Puppet RAL to directly interact with the system. You can use it list or generate example code for a type.

List resource types :

# puppet resource --type
augeas
computer
cron
exec
file
filebucket
group
host
interface
k5login
macauthorization
mailalias
maillist
mcx
mount
...
zfs
zone
zpool
# puppet resource package foo
package { 'foo':
  ensure => 'absent',
}

# puppet resource package bash
package { 'bash':
  ensure => '4.2.46-21.el7_3',
}

# puppet resource file /etc/passwd
file { '/etc/passwd':
  ensure  => 'file',
  content => '{md5}25d97bb55f526f3db405e66124f6f926',
  ctime   => '2017-07-18 08:56:00 +0200',
  group   => '0',
  mode    => '644',
  mtime   => '2017-07-18 08:56:00 +0200',
  owner   => '0',
  type    => 'file',
}

# puppet resource service sshd
service { 'sshd':
  ensure => 'running',
  enable => 'true',
}

puppet doc
#

Generate a reference for all Puppet types.:

# puppet doc | less

note

the output is long so use less

puppet describe
#

Prints help about Puppet resource types, providers, and metaparameters.:

# puppet describe yumrepo

puppet parser validate
#

Validate the syntax of one or more Puppet manifests.:

# puppet parser validate lala.pp
Error: Could not parse for environment production: Syntax error at '->'; expected '}' at /home/username/lala.pp:4

puppet apply
#

Apply a standalone Puppet manifest to the local system.:

# puppet apply --noop lala.pp

note

this is a dry-run used for syntax testing.

# puppet apply lala.pp

puppet module generate
#

Generate boilerplate for a new module:

# puppet module generate author-modulename
We need to create a metadata.json file for this module.  Please answer the
following questions; if the question is not applicable to this module, feel free
to leave it blank.

Puppet uses Semantic Versioning (semver.org) to version modules.
What version is this module?  [0.1.0]
-->

Who wrote this module?  [author]
-->

What license does this module code fall under?  [Apache 2.0]
-->

How would you describe this module in a single sentence?
-->

Where is this module's source code repository?
-->

Where can others go to learn more about this module?
-->

Where can others go to file issues about this module?
-->

----------------------------------------
{
  "name": "author-modulename",
  "version": "0.1.0",
  "author": "author",
  "summary": null,
  "license": "Apache 2.0",
  "source": "",
  "project_page": null,
  "issues_url": null,
  "dependencies": [
    {
      "name": "puppetlabs-stdlib",
      "version_range": ">= 1.0.0"
    }
  ]
}
----------------------------------------

About to generate this metadata; continue? [n/Y]
-->

Notice: Generating module at /home/username/examples/author-modulename...
Notice: Populating ERB templates...
Finished; module generated in author-modulename.
author-modulename/Rakefile
author-modulename/manifests
author-modulename/manifests/init.pp
author-modulename/spec
author-modulename/spec/classes
author-modulename/spec/classes/init_spec.rb
author-modulename/spec/spec_helper.rb
author-modulename/tests
author-modulename/tests/init.pp
author-modulename/README.md
author-modulename/metadata.json

Or shorter:

# puppet module generate --skip-interview foo-bar

Notice: Generating module at /home/username/examples/foo-bar...
Notice: Populating ERB templates...
Finished; module generated in foo-bar.
foo-bar/Rakefile
foo-bar/manifests
foo-bar/manifests/init.pp
foo-bar/spec
foo-bar/spec/classes
foo-bar/spec/classes/init_spec.rb
foo-bar/spec/spec_helper.rb
foo-bar/tests
foo-bar/tests/init.pp
foo-bar/README.md
foo-bar/metadata.json

puppet module build
#

# puppet module build author-modulename
Notice: Building /home/username/examples/author-modulename for release
Module built: /home/username/examples/author-modulename/pkg/author-modulename-0.1.0.tar.gz

puppet module install
#

# sudo puppet module install /home/username/examples/author-modulename/pkg/author-modulename-0.1.0.tar.gz
Notice: Preparing to install into /etc/puppet/modules ...
Notice: Downloading from https://forgeapi.puppetlabs.com ...
Notice: Installing -- do not interrupt ...
/etc/puppet/modules
└─┬ author-modulename (v0.1.0)
  └── puppetlabs-stdlib (v4.5.1) [/usr/share/puppet/modules]

puppet module install from the Puppet Forge
#

# sudo puppet module install puppetlabs-mysql --version 3.11.0
Notice: Preparing to install into /etc/puppet/modules ...
Notice: Downloading from https://forgeapi.puppetlabs.com ...
Notice: Installing -- do not interrupt ...
/etc/puppet/modules
└─┬ puppetlabs-mysql (v3.11.0)
  ├── puppet-staging (v2.2.0)
  └── puppetlabs-stdlib (v4.17.1)

facter
#

Collect and display facts about the system.:

# facter |egrep "^(architecture|kernel|hardwaremodel)\ "
architecture => x86_64
hardwaremodel => x86_64
kernel => Linux

Or more specific:

# facter  architecture kernel hardwaremodel
architecture => x86_64
hardwaremodel => x86_64
kernel => Linux

warning

A regular user does not get the same output as root!

Glossary
#

TODO
#