Liveblogging: Pulling Strings with Puppet: A Gentle Introduction

Sheeri

I’m at the Professional IT Community Conference, aka PICC, for the first day.

I am taking Pulling Strings with Puppet: A Gentle Introduction by Thomas Uphill of the Institute for Advanced Study and Benjamin Rose – Princeton University…here are my notes!

Slides available at http://goo.gl/Swg4G
example files
http://ramblings.narrabills.com/Talks/puppet-picc.tar.bz2

What is puppet?
It’s a language – you describe the state, not the steps to get there (declarative!) – paint a picture of your ideal system, and puppet does the rest.
Written in Ruby

Luke Kanies in 2005 developed it, company is http://puppetlabs.com – puppet Enterprise has commercial support, Windows support, etc.

Why do I want puppet?
It’s human-readable

Puppet has a rich history of versioning, they went from 0.20 -> 0.25 -> 2.6.0 -> 2.7.0 -> 2.7.14

Learning Puppet VM – http://docs.puppetlabs.com/learning – can get VMs for VMware, OVF, VirtualBox, KVM
They’re using the learn vm, puppet master is Jim, piggy and kermit are the client nodes

Resource Abstraction Layer
a bunch of types, exec, mount, service (something started from inet.d), file (just a file), package, user, group, host (/etc/hosts).

Types have providers, the providers take care of figuring out what kind of operating system you have and how to do it.

The core of the language is classes, variables and types. These apply to nodes – e.g. hosts.

Types, classes and variables can be put into modules, or plugins.

Syntax of types

type { "title":
attribute => value,
attribute => value
}

type is “exec”, “mount”, etc from above

basic example:
file {"testfile":
mode => 0644,
owner => root,
path => "/etc/test",
ensure => present
}

You can run a parser to make sure your syntax works:
puppet parser validate syntax.pp

Trifecta of package, file and service types (“core” types).

file
attributes: title
ensure – one of {present, absent, file, directory, link}
path – full path to the file, default is $title
source – url to the file, or local path
content – string contents of the file, or a template
target – any links
recurse – if this is a directory, you may want to copy everything in the dir (false by default)
purge – remove non-puppet file from the directory (true/false, false by default)
owner
group
mode
content -
file { "issue":
path => "/etc/motd",
content => "Hello World!"
}

file { "issue":
path => "/etc/motd"
content => file("/etc/hello")
}

templates
file { "issue":
path => "/etc/motd",
content => template("/etc/hello.erb")
}

cat /etc/hello.erb
Hello !

If hostname is “learn”, it will say “Hello learn!”

puppet apply is the most basic way to apply changes from the manifest
puppet apply mymanifest.pp

Puppet can be a fileserver:
file { "issue":
path => "/etc/motd"
source => "puppet:///files/hello"
}

package
attributes:
title
ensure – one of {present, latest, [version], absent, purged} latest will go and get the package. You might not want to do that if you’re using RH, b/c if you’re running puppet every half hour, it will run yum every half hour. purged takes any mentions of the package off, “absent” is safer
name
source – can provide location of the source

package { "sar":
name => 'sysstat',
ensure => '7.0.2-3.el5'
}

package { "sar":
name => 'sysstat',
ensure => '7.0.2-3.el5'
provider => rpm
source => '/root/sysstat-7.0.2-3.el5.i386.rpm'
}

service
service actually makes sure things are running
attributes:
title
ensure – running, stopped, true, false (true=running, false=stopped)
enable – true, false (e.g. true is chkconfig foo on)
hasrestart – true, false (true means does a stop/start atomically)
hasstatus – true, false (true means it uses the “status” command used for the package, otherwise it does a pgrep of the name of the service (won’t work with, say, bind, whose daemon is named)
restart – this is the command to restart the service
status – this is the command to test the status

ordering – you need the package first
Apache example:
service { "httpd":
enable => true,
ensure => running,
hasrestart => true,
hasstatus => true,
}
(comma at end of last stanza is OK)

file { "host.conf":
path /etc/httpd/conf.d/$name,
mode => 0644,
owner => 'apache',
group => 'group',
content => "<VirtualHost *:80>
ServerName kermit.henson.local
DocumentRoot /var/www/html/kermit
</VirtualHost>"
}

Defining a new object uses lower case:
service["sshd"] {ensure => running }

Uppercase is for referencing existing objects:
require => Service["sshd"]
Be careful not to define an object twice (it will throw an error)

Every object has builtins to allow for logic involving other objects:
require – you can say the httpd service requires the package
before – you can say the package comes “before” the service
notify
subscribe – the service is subscribed to the config file so if the config file
tag – attach a human word to your object

Example:
package{'httpd':
ensure => present
}

file { "host.conf":
path /etc/httpd/conf.d/$name,
mode => 0644,
owner => 'apache',
group => 'group',
content => "<VirtualHost *:80>
ServerName kermit.henson.local
DocumentRoot /var/www/html/kermit
</VirtualHost>"

require => Package['httpd']
}

puppet resource is a great way to translate your existing site into a manifest – a manifest is a collection of files that apply to the client.

puppet resource --types
prints out a list of types.

If you want to find out what a puppet manifest would look like on your current system, use puppet resource. Here’s an example of finding a puppet manifest for your swap mount:
puppet resource mount swap
mount { 'swap':
ensure => 'unmounted',
device => '/dev/vg00/swapvol',
dump => '0',
fstype => 'swap',
options => 'defaults',
pass => '0',
target => '/etc/fstab',
}

Facter/Facts
Facter runs Ruby code to generate a value for facts (61 facts available), here are sample facts:
$processor0
$ipaddress
$architecture
$fqdn
$is_virtual
$memorysize
$selinux
$timezone

You can extend this by adding additional facts. w00t!

Variables
can use as parameters
can use in conditionals
facts (e.g. $::processor0)
user defined ($myvar = “My Value”)

$::variable is a top-scope variable. More on scope later.

Conditionals
if/elsif/else
case
selector
booleans:
($x==$y)
($x!=$y)
($x > $y)
($x < $y)
($x < $y) and !($x < $y)

can do arithmetic
$x+$y
$y >> 1 (why would you do this? but you can!)

case $myvar {
"My Value": {$content = "All is good" }
"": { $content = "bad var" }
default: { $content = $myvar }
}

case $::hostname {
/^ldap/: {include ldapserver}
/^www/: {include webserver}
default: {include base}
}

match =~ not match !~
if $::hostname =~ /^ldap/ { include ldapserver }
if $::hostname != /test/ { include production }

capture -
/^ldap(\d+)/ =~ {include "ldap$1"}

Ternary operator (C) (actually it’s more than ternary)
x == y ? "they are equal" : "they are different"

file {'httpd.conf':
  path => $operatingsystem ? {
    'Ubuntu' => '/etc/apache2/$name',
    'RedHat' => '/etc/httpd/conf/$name',
    default => '/usr/local/etc/httpd/conf/$name'
  }
  content => "DocumentRoot /var/www/html"
  }

in – Arrays

if $::hostname in ['www','web'] {
  service {'httpd':
    ensure => true
    }
  }

if $::kernelversion in ['2.6.35-22','2.6.38.6-26.rc1.fcl5'] {
service{'sshd':
ensure=>false
}
}

BNF syntax of puppet grammar

Templates

ERB syntax
<% Ruby code %>
<%= Ruby expression %>
<%# comment %>
-%> – this puts no newline, so for example this will be all one line:
foo bar <% stuff here -%> baz bap
<%% escapes, so replace with <%
%%> escapes, so replace with %>
<%= @fact %>

example:
client.conf.erb
<%= if @ipaddress_eth0 != "NONE" %>
ServerName <%= @printserver %>
<% end %>

iteration in templates

|domain| is variable expansion – replace with whatever the value of domain is

template example:
/etc/puppet/manifests/resolv.conf.erb
# resolv.conf build by Puppet
domain <%= domain %>
search <% searchdomains.each do |domain| -%><%= domain -%><% end -%><%= @domain %>
<% nameservers.each do |server| -%>
nameserver <%= server %>
<% end -%>

$nameservers = [ 'ns1.example.com', 'ns2.example.com', 'ns3.example.com' ]
$searchdomains = [ 'inside.example.com', 'outside.example.com', 'ns3.example.com' ]
file { "resolvconf":
path => "/etc/resolv.conf",
mode => 0644, owner=root, group=root,
content => template('resolvconf.erb')
}

Concatenation
content => template('header.erb','resolvconf.erb')

cron
If you want to manage crontab, use the “cron” type
attributes:
name
command
hour
minute
month
monthday
weekday
user
If you want to manage cron.d, or cron.hourly, use files.
Even though it’s called “cron” it depends on the provider, so you can use it on Mac OS X for example.

cron {'yum_cleaner':
command => "/usr/bin/yum clean all",
user => root,
hour => 2,
minute => 0
}

exec
attributes:
title – must be unique across “exec” (not across the whole manifest)
command
creates -
cwd
environment
group
logoutpu

augeas