using puppet to pull strings - darksim · using puppet to pull strings: ... what is puppet and why...
TRANSCRIPT
Using Puppet to Pull Strings:a Gentle Introduction to Puppet
Thomas Uphill
http://ramblings.narrabilis.com
http://goo.gl/FZvBD
example fileshttp://ramblings.narrabilis.com/Talks/puppet-picc.tar.bz2
Latest Versionon Google Docs
Overview
What is Puppet and why do I want it?
Puppet 101Introductory Material
Puppet 102Advanced Material
Questions sporadically throughout.
What is puppet?
Think of it as a language.
Describe state, not steps.
Paint a picture of your ideal and most clean system.
Puppet does the rest!
puppetlabs
● Luke Kanies in 2005● http://puppetlabs.com/● puppet Enterprise
○ commercial support○ windows support○ puppet forge
Why do I want it?
you can do cool things with itHuman-parsable.ensure that all your hosts are running ssh
service {"ssh": ensure => running;}
Puppet 101Resource Abstraction LayerSyntaxtrifectacore types(file, package, service)puppet apply / puppet resourcefacter/variablesconditionalsbooleans/arithmeticin
templatestypes II(augeas, exec, cron)classesfunctionsfilebuckets
versions
0.20 0.25 2.6.0 2.7.0
2.7.21
0.20 0.25 2.6.0 2.7.0
2.7.21
3.03.0
3.1.13.1.1
learning puppet vm
http://docs.puppetlabs.com/learning/
VMwareOVFVirtualBoxKVM
Get the Learning Puppet VM
learning puppet vm
KVM
[root@hv] vmware-mount -f learn_puppet.vmdk /mnt[root@hv] qemu-img /mnt/flat -Oqcow2 learn_puppet.qcow2
Resource Abstraction Layer
types providers
ubuntu
SLES
macosx
Windows
user
file
package
service
exec
mount
group
host
RHELCENTOS
Springdale
manifest
pluginmodule
Language (preview)
classattributetypenode
factvariable
class class
Syntax
type { "title":
attribute => value,
attribute => value
}
Syntax
type { "title":
attribute => value,
attribute => value
}
syntax.pp
file {"testfile": mode => 0644, owner => root path => "/etc/test", ensure => present}
[root@learn ~] # puppet parser validate syntax.pp err: Could not parse for environment production: Syntax error at 'path'; expected '}' at /etc/puppetlabs/puppet/manifests/syntax.pp:4err: Try 'puppet help parser validate' for usage
trifecta
file
package
service
core types cheat sheet
docs.puppetlabs.com/puppet_core_types_cheatsheet.pdf
fileAttributes
ensure
pathtitle: source
content
target
recurse
purge
owner
group
mode
file {"issue":path => "/etc/motd",content => "Hello World!"}
/etc/motd
Hello World!
distributing file contents
distributing file contents 2
Attributes
target
recursefile {"issue":path => "/etc/motd",content => file("/etc/hello")}
/etc/motd
Hello File!
/etc/hello
Hello File!
distributing file contents 3
file {"issue":path => "/etc/motd",content => template("/etc/hello.erb")}
/etc/motd
Hello learn!
/etc/hello.erb
Hello <%= hostname %>!
puppet apply (serverless operation)
apply changes using a locally defined manifest
[root@learn ~] # puppet apply mymanifest.pp
distributing file contents 4
Attributes
file {"issue":path => "/etc/motd",source =>"puppet:///files/hello"}
/etc/motd
Hello puppet!
/etc/puppet/manifests/hello
Hello puppet!
packageAttributes
ensure
nametitle: source
henson.local
demo VMs
learn jim
kermitpiggy
serviceAttributes
ensure
nametitle: enable
hasrestarthasstatusrestartstatus
ordering/dependencies
file
package
service
host.conf httpd
httpd
DAG
host.conf httpd
httpd
directed acyclic graph
DirectedAcyclicGraph
Referencing objects - case
Defining a new object uses lower case:
Referencing an existing object uses upper case:
Be careful not to define objects twice!
service["sshd"] { ensure => running }
require => Service["sshd"]
/etc/puppet/lopsa/ordering.pp
package {'httpd': ensure => present}service {"httpd": enable => true, ensure => running, hasrestart => true, hasstatus => true, require => [Package['httpd'],File['host.conf']]}
file {"host.conf": path => "/etc/httpd/conf.d/$name", mode => 0644, owner => 'apache', group => 'apache', content => "<VirtualHost *:80> ServerName kermit.henson.local DocumentRoot /var/www/html/kermit </Virtualhost>", require => Package['httpd']}
ordering/dependencies
puppet resource
[root@learn: ~] $ puppet resource --typesaugeascomputercronexecfilefilebucketgroup...
[root@learn: ~] $ puppet resource mount swapmount { 'swap': ensure => 'unmounted', device => '/dev/vg00/swapvol', dump => '0', fstype => 'swap', options => 'defaults', pass => '0', target => '/etc/fstab',}
memory
Facter/Facts
processors
ipaddress
architecture
dns
VM?
selinux
timezonesystem
$processor0$ipaddress$architecture$fqdn$is_virtual$memorysize$selinux$timezone
variables
use as parametersuse in conditionalsfactsuser defined
$processor0
$myvar = "My Variable"
$::processor0
conditionals
● if/else/unless● case● selector
● booleans
booleans
$x = 1$y = 2($x == $y)($x > $y)($x < $y)($x != $y)($x < $y) and !($x < $y)
false
false
true
true
false
arithmetic
$x=1$y=2$x+$x == $y$x-$x$y/2$y >> 1$y << 1
true
1
1
4
0
if/else/unlessif $myvar == "My Variable" {file {'/my/variable':
content => "All is good", ensure => present
}} elsif $myvar {
file {'/my/variable':content => $myvar,ensure => present
}} else {
file {'/my/variable':content => "bad var",ensure => present
}}
$variablefile = "/my/variable"if $myvar == "My Variable" {file {$variablefile:
content => "All is good", ensure => present
}} elsif $myvar {
file {$variablefile:content => $myvar,ensure => present
}} else {
file {$variablefile:content => "bad var",ensure => present
}}
if/else/unlessunless
not yet implementedif !(condition)
if !($myvar) {file {"/my/file":
content => "no myvar set",ensure => present
}}
casecase $::hostname {
/^ldap/: { include ldapserver }/^www/: { include webserver }/^dns/: { include bind }/^mx[12]/: { include mxprimary }/^mx[3-9]/: { include mxsecondary }default: { include base }
}
Regular Expressions
match =~if $::hostname =~ /^ldap/ {
include ldapserver}
not match !~if $::hostname !~ /test/ {
include production}
casecase $::hostname {
/^ldap/: { include ldapserver }/^www/: { include webserver }/^dns/: { include bind }/^mx[12]/: { include mxprimary }/^mx[3-9]/: { include mxsecondary }default: { include base }
}
Regular Expressions● capture
/^ldap(\d+)/ { include "ldap$1" }
selectorternary operator (C)x == y ? "they are equal" : "they are different"
attribute => $fact ? {'value' => 'result','other_value' => 'other_result',default => 'default_result'
}
"in"if $::hostname in ['www','web'] {
service {'httpd':ensure => true
}}
if $::kernelversion in ['2.6.35-22','2.6.38.6-26.rc1.fc15'] {
service {'sshd':ensure => false
}}
backus naur (bnf)<exp> ::= <exp> <arithop> <exp> | <exp> <boolop> <exp> | <exp> <compop> <exp> | <exp> <matchop> <regex> | ! <exp> | - <exp> | "(" <exp> ")" | <rightvalue>
<arithop> ::= "+" | "-" | "/" | "*" | "<<" | ">>"<boolop> ::= "and" | "or"<compop> ::= "==" | "!=" | ">" | ">=" | "<=" | "<"<matchop> ::= "=~" | "!~"
<rightvalue> ::= <variable> | <function-call> | <literals><literals> ::= <float> | <integer> | <hex-integer> | \
<octal-integer> | <quoted-string><regex> ::= '/regex/'
http://docs.puppetlabs.com/guides/language_guide.html
templates
ERB syntax<% Ruby code %>
<%= Ruby expression %><%# comment %>
-%> no newline<%% replace with <%%%> replace with %>
templates (conditionals)
puppet ERB<%= @fact %> replace with fact/global variable
conditionalclient.conf.erb<% if @ipaddress_eth0 != "NONE" %>ServerName <%= @printserver %><% end %>
templates (iteration)
iteration$nameservers = [ 'ns1.example.com',
'ns2.example.com','ns3.example.com']
$searchdomains = [ 'inside.example.com','outside.example.com','under.example.com']
file {"resolvconf":path => "/etc/resolv.conf",mode => 0644, owner => root, group => root,content => template('resolvconf.erb')
}
iteration$nameservers = [ 'ns1.example.com',
'ns2.example.com','ns3.example.com']
$searchdomains = [ 'inside.example.com','outside.example.com','under.example.com']
file {"resolvconf":path => "/etc/resolv.conf",mode => 0644, owner => root, group => root,content => template('resolvconf.erb')
}
templates (concatenation)
iteration$nameservers = [ 'ns1.example.com',
'ns2.example.com','ns3.example.com']
$searchdomains = [ 'inside.example.com','outside.example.com','under.example.com']
file {"resolvconf":path => "/etc/resolv.conf",mode => 0644, owner => root, group => root,content => template('header.erb','resolvconf.erb')
}
types II
cronexecaugeas
cronAttributes
command
hourname: minute
monthmonthday
user
weekday
execAttributes
command
createstitle: cwd
environmentgrouplogoutput
onlyif
path
refreshrefreshonlyreturnsunless
tool to transform files into objectsconfig changes made to the objectsaugtool and augparse command line toolsWhy?
● manipulate parts of a file● don't clobber another modules work● start from a known good config
augeasAttributes
changes
contexttitle: force
nameonlyif
augeas
sshd_config
augeas{ "sshd password authentication": context => "/files/etc/ssh/sshd_config", changes => [ "set PasswordAuthentication no" ], notify => Service["sshd"]}
class
classes
attributetype
attributetype variable
variable
classinclude otherclass
class syntax
class name {type {title: attributes => values },type {title: attributes => values }
}
class hierarchy
parent nephew
top/global
child
functions
includefilejoinsplitdefinedrequire
templaterealizenotice
http://docs.puppetlabs.com/references/stable/function.html
inline_template
● apply template to variables ● pass to a parameter# iterate over the interfaces# print out their addresses into ifcfg-files$ifs = split($interfaces,',')
define inline_template_test { file {"/tmp/ifcfg-$name": content => inline_template("IPADDR=<%= @ipaddress_${name} %>\n") }}
inline_template_test { $ifs: }/tmp/ifcfg-eth0IPADDR=192.168.122.4
filebuckets
Defaults to local-only modeServer mode:filebucket { puppet: server => "puppet.example.edu" }
Command-line utility:# puppet filebucket --local backup /etc/puppet/puppet.conf /etc/puppet/puppet.conf: be50b3e9acc2c2de8df194b1466fd2c1
# puppet filebucket --local restore /etc/puppet/puppet.conf \be50b3e9acc2c2de8df194b1466fd2c1
vim
.vimrc
" puppet style guide modefiletype plugin indent on
Puppet 102advanced languageserver configuration
plusignmentscopeparameterized classesrun stagesresource chainingpuppetmaster (server operation)webrickfileserverclient configurationdebuggingpuppetcasite.pptype defaultsreferencing/upper case
puppet 102 modulespluginscustom factscustom typespluginsyncpassengerpushingreportingencenvironmentsstored configurationsvirtualizing resourcesexported resourcessystem integration/firewallnagios
plusignment
class openssh {
file { "/etc/ssh/sshd_config":
content => "..."; }
service {
"sshd":
ensure => running,
subscribe => File["/etc/ssh/sshd_config"]; }
class keydistribute {
file {
"/etc/ssh/host_rsa_key":
content => "...";
"/etc/ssh/host_dsa_key":
content => "..."; }
Service["sshd"] {
subscribe +> [ File["/etc/ssh/host_rsa_key"],
File["/etc/ssh/host_dsa_key"] ] }
}
}
scope
class base
hostname
class dns
$::hostname
class dns
class base
hostname
scopeinclude parent::child$var = "top level"class parent { $var = "from parent" $hostname = "hostname from parent"}class parent::child inherits parent { $var = "from parent::child" notice ( "parent::var => ",$parent::var ) notice ( "var => ",$var ) notice ( "hostname => ",$hostname ) notice ( "::hostname => ",$::hostname )}
http://docs.puppetlabs.com/guides/scope_and_puppet.html
parameterized classes# unparameterizedinclude resolverclass resolver { $dnsservers = ['8.8.8.8','8.8.4.4'] file {"resolver-conf": path => "/tmp/resolv.conf", content => template("/root/resolver-conf.erb")
}}
parameterized classes# parameterizedclass {"resolver": dnsservers => ['192.168.1.1','192.168.4.1']}class resolver ($dnsservers =['8.8.8.8','8.8.4.4']) { file {"resolver-conf": path => "/tmp/resolv.conf", content => template("/root/resolver-conf.erb")
}}
parameterized classes# parameterized2
class resolver ($mydnsservers = '') {
$maindnsservers = '8.8.8.8,8.8.4.4'
if $mydnsservers {
$dnsservers = split(sprintf("%s,%s",$mydnsservers,$maindnsservers),",")
} else {
$dnsservers = split($maindnsservers,",")
}
file {'resolver-conf':
path => '/tmp/resolv.conf',
content => template('/etc/picc/resolver-conf.erb')
}
}
class {'resolver':
mydnsservers => "192.168.1.1,192.168.4.1"
}
custom definesMove beyond the singletons of typical puppet manifests.
Defines are used to model repeatable chunks of puppet code.
Common examples include versioning repositories and apache virtual hosts.
Not much to explain, let's just take a look at an example...
class git { package{ "git": } file {'/var/lib/git': ensure => 'directory', mode => 0755, owner => 0, group => 0 }}
define gitrepo($owner, $group, $ispublic = false) { include git $mode = $ispublic ? { true => 2774, false => 2770 }
file { "/var/lib/git/${title}": ensure => directory, owner => "${owner}", group => "${group}", mode => $mode, require => [Package['git'],File['/var/lib/git']] }
exec { "${title}_initialize": command => '/usr/bin/git --bare init .', cwd => "/var/lib/git/${title}", unless => "/bin/test -f /var/lib/git/${title}/HEAD", require => [Packages['git'],File["/var/lib/git/${title}"]] }}
"chain" operatorsClass["setup_repos"] -> Class["install_packages"] -> Class["ldap::client"]
Package["openssh-server"] -> File["/etc/ssh/sshd_config"] -> Service["sshd"] <- File["/etc/ssh/ssh_host_rsa_key.pub"]
Run stages & resource chaining
setup_repos install_packages ldap::client
before => Class['install_packages', 'ldap::client']
before => Class['ldap::client']require => Class['setup_repos']
require => Class['setup_repos', 'install_packages']
Run stages - the parameterized waystage {
"pre":before => Stage["main"];
"post":require => Stage["main"];
}
node "server.example.com" {class {
"setup_repos":stage => "pre";
"install_packages":stage => "main";
"ldap::client":stage => "post";
}}
puppetmaster
Allows all puppet work to be done in a single location and then distributed via a pull-based puppet client.
puppetmaster
Webserver
DHCPserver
DNSserver
Databaseserver
TCPport 8140
SSLx509
puppetmaster
● Pairs well with a versioning system such as git or svn to create living manifests.
● Options controlled through [master] section in puppet.conf
● "puppet-server" package. Creates user/group puppet.
● Simplest case is webrick, works out of the box.
webrick
Works without any additional configuration
service puppetmaster start
iptables -A INPUT -m state --state NEW -m tcp -p tcp --dport 8140 -j ACCEPT
[root@jim ~] # lsof -i |grep 8140puppetmas 3211 puppet 6u IPv4 17188 0t0 TCP *:8140 (LISTEN)
puppetmaster as a fileserver
If you use "source => puppet:///" in any of your file definitions, this is how those files get distributed.
/etc/puppet/fileserver.conf
[files]path /var/lib/puppet/filesallow *.example.comallow *.friendly.example.orgdeny *.malice.example.netdeny impostor.friendly.example.org
puppet.conf
auth.conf
fileserver.conf
manifests/site.pp
server configuration
server configuration/etc/puppet/puppet.conf
[main]logdir = /var/log/puppetrundir = /var/run/puppetssldir = $vardir/ssl
[agent]classfile = $vardir/classes.txtlocalconfig = $vardir/localconfig
server configuration
/etc/puppet/fileserver.conf
[files]path /var/lib/puppet/filesallow *.example.com
[modules]allow *.example.com
[facts]path /var/lib/puppet/factsallow *.example.com
[plugins]path /var/lib/puppet/pluginsallow *.example.com
client configuration
/etc/sysconfig/puppet
PUPPET_SERVER=puppet.example.com#PUPPET_PORT#PUPPET_LOG#PUPPET_EXTRA_OPTS=--waitforcert=500
/etc/puppet/puppet.conf
[agent]server = puppet.example.com
it's go time.
on the master:service puppetmaster start
on the client:service puppet start
...hope for the best.
an aside...debugging clients
[root@client ~] # service puppet stop
Run and print the log output to the shell:[root@client ~] # puppet agent -o --no-daemonize -vor[root@client ~] # puppet agent -t
View even more information on the shell:[root@client ~] # puppet agent -o --no-daemonize --debug
View a crazy, incomprehensible amount of info on the shell:[root@client ~] # puppet agent -o --no-daemonize --trace
info: Creating a new SSL key for kermit.henson.localwarning: peer certificate won't be verified in this SSL sessioninfo: Caching certificate for cawarning: peer certificate won't be verified in this SSL sessionwarning: peer certificate won't be verified in this SSL sessioninfo: Creating a new SSL certificate request for kermit.henson.localinfo: Certificate Request fingerprint (md5): F4:E2:10:61:1C:F7:F8:E4:03:98:14:B0:F7:47:C8:9Ewarning: peer certificate won't be verified in this SSL sessionwarning: peer certificate won't be verified in this SSL sessionwarning: peer certificate won't be verified in this SSL sessionExiting; no certificate found and waitforcert is disabled
puppetca
puppetca
Distributes and manages x509 certificates.
Command line utility: puppet cert (puppetca for oldtimers)
Supports auto-signing
/etc/puppet/autosign.conf
*.example.com*.subdomain.example.net
it's go time
server[root@jim puppet]# puppet cert list
kermit.henson.local (F4:E2:10:61:1C:F7:F8:E4:03:98:14:B0:F7:47:C8:9E)
[root@jim puppet]# puppet cert sign kermit.henson.local
notice: Signed certificate request for kermit.henson.local
notice: Removing file Puppet::SSL::CertificateRequest kermit.henson.local at '/var/lib/puppet/ssl/ca/requests/kermit.henson.local.pem'
it's go time[root@kermit ~]# puppet agent -v --no-daemonize -o
warning: peer certificate won't be verified in this SSL session
info: Caching certificate for kermit.henson.local
info: Caching certificate_revocation_list for ca
err: Could not retrieve catalog from remote server: Error 400 on SERVER: Could not find default node or by name with 'kermit.henson.local, kermit.henson, kermit' on node kermit.henson.local
notice: Using cached catalog
err: Could not retrieve catalog; skipping run
[root@enchantress ~]#
site.pp
master manifestneeds to define each node or a default node
simplest* site.ppnode default {}
site.pp
import 'nodes.pp'import 'functions.pp'import 'variables.pp'import 'defaults.pp'
type defaults (defaults.pp)capitalize the type: ...but do not give a title
These "attribute => value" pairs will now be the defaults for all objects of type "file". You are free to overwrite them later.
File {user => root,group => root,mode => 0644
}
tip: tag template
tagprinter.erb
tags----<% tags.each do |tag| -%><%= tag %><% end -%>
classes-------<% classes.each do |cls| -%><%= cls %><% end -%>
variables-------<% scope.to_hash.keys.each do |var| -%><%= var -%> => <%= scope.lookupvar(var) %><% end -%>
modules
{modulepath}└── {modulename} └── files └── manifests └── templates
● a way to organize your manifests● promotes code reuse● provides namespaces● can push code to clients
modules
{modulepath}└── {modulename} └── files └── manifests └── init.pp └── templates
modules
init.pp
class modulename {file {"example.conf":
path => "/tmp/example.conf",source => "puppet:///modules/modulename/example.
conf"}
}
{modulepath}/{modulename}/files/example.conf
{modulepath}/{modulename}/manifests/init.pp
modules
subclass.pp
class modulename::subclass {file {"subclass.conf":
path => "/tmp/subclass.conf",source => "puppet:///modules/modulename/subclass.conf"
}}
{modulepath}/{modulename}/files/subclass.conf
{modulepath}/{modulename}/manifests/subclass.pp
modules
subclass.pp
class modulename::subclass {file {"subclass.conf":
path => "/tmp/subclass.conf",content => template("modulename/subclass.erb")
}}
{modulepath}/{modulename}/templates/subclass.erb
module cheat sheethttp://docs.puppetlabs.com/module_cheat_sheet.pdf
style guide
http://docs.puppetlabs.com/guides/style_guide.html
● two-space soft tabs (no literal tab characters)● no trailing white space● 80 character line width● fat comma arrows (=>) within blocks of attributes● # for comments● 'my variable' unless "my ${variable}"● ensure first● defaults in site.pp● separate files for all classes in a module
forge
http://forge.puppetlabs.com/
> 2.7.14puppet module< 2.7.14puppet-module
plugins{modulepath}└── {modulename} └── files └── lib └── facter └── puppet └── parser └── functions └── provider └── exec └── package └── anytype └── type └── util
custom facts
/etc/puppet/modules/videocard/lib/facter/videocard.rb
# Josko Plazonic - lifted from Josko March 14, 2011 by Thomas Uphillrequire 'facter'
Facter.add("videocard") doconfine :kernel => :linuxENV["PATH"]="/bin:/sbin:/usr/bin:/usr/sbin"setcode do
controllers = []lspciexists = system "/bin/bash -c 'which lspci
>&/dev//null'"if $?.exitstatus == 0
output = %x{lspci}output.each {|s|
controllers.push($1) if s =~ /VGA compatible controller: (.*)/
}endcontrollers
endend
custom types
/etc/puppet/modules/newtype/lib/puppet/type/newtype.rb# this is beyond the context of this talk...# best to look on http://forge.puppetlabs.com/
Puppet::Type.newtype(:newtype)donewproperty(:myprop, :array_matching => :all)doend
end
pluginsync
plugins synced from master to the clients
/etc/puppet/module
something
libfacter puppet
/var/lib/puppet
libfacter puppet
pluginsync
videocard example, videocard.rbpuppet.conf -> [agent] pluginsync = true
info: Retrieving pluginnotice: /File[/var/lib/puppet/lib/videocard.rb]/ensure: defined info: Loading downloaded plugin var/lib/puppet/lib/videocard.rb
Modules + Facts + TemplatesControl access to system users via cgroups
Filestructure:
/etc/puppet/modules/cgroups└── templates └── cgconfig.conf└── files └── cgrules.conf└── manifests └── init.pp└── lib └── facter └── cgroups.rb
Modules + Facts + Templates
manifests/init.pp class cgroups {
package {"libcgroup":
ensure => present;}
service {"cgconfig":
ensure => running,enable => true,require => Package["libcgroup"];
"cgred":ensure => running,enable => true,require => [ Package["libcgroup"], Service["cgconfig"]
];}
file {"/etc/cgconfig.conf":
content => template("cgroups/cgconfig.conf"),notify => [ Service["cgconfig"], Service["cgred"] ];
"/etc/cgrules.conf":source => "puppet:///modules/cgroups/cgrules.conf",notify => [ Service["cgconfig"], Service["cgred"] ];
}}
Modules + Facts + Templates
files/cgrules.conf@staff cpu staff/
@student cpu,memory users/@faculty cpu,memory users/@grad cpu,memory users/@guest cpu,memory users/@alumni cpu,memory users/
* cpu system/
Modules + Facts + Templates
templates/cgconfig.confgroup staff {
cpu {cpu.shares = 20;
}}
group users {cpu {
cpu.shares = 60;}memory {
memory.limit_in_bytes = <%= mem80pct %>;memory.memsw.limit_in_bytes = <%= memswap80pct %>;
}}
group system {cpu {
cpu.shares = 20;}
Modules + Facts + Templates
lib/facter/cgroups.rbFacter.add(:mem80pct) do setcode do Facter::Util::Resolution.exec('\
free -bto | \grep Mem | \awk \'{OFMT = "%.0f"; print $2*.8}\'\
') endend
Facter.add(:memswap80pct) do setcode do Facter::Util::Resolution.exec('\
free -bto | \grep Total | \awk \'{OFMT = "%.0f"; print $2*.8}\'\
') endend
Modules + Facts + Templates
lib/facter/cgroups.rbFacter.add(:mem80pct) do setcode do Facter::Util::Resolution.exec('\
free -bto | \grep Mem | \awk \'{OFMT = "%.0f"; print $2*.8}\'\
') endend
Facter.add(:memswap80pct) do setcode do Facter::Util::Resolution.exec('\
free -bto | \grep Total | \awk \'{OFMT = "%.0f"; print $2*.8}\'\
') endend
Production - Use passengerApache a better webserver than webrick, mongrel support is deprecated.
Much more stable.
config.ru - must be owned by puppet
Passenger - Apache configurationLoadModule passenger_module modules/mod_passenger.so<IfModule mod_passenger.c> PassengerRoot /usr/lib/ruby/gems/1.8/gems/passenger-3.0.2 PassengerRuby /usr/bin/ruby</IfModule>RackAutoDetect OffRailsAutoDetect OffListen 8140<VirtualHost *:8140> RequestHeader set X-SSL-Subject %{SSL_CLIENT_S_DN}e RequestHeader set X-Client-DN %{SSL_CLIENT_S_DN}e RequestHeader set X-Client-Verify %{SSL_CLIENT_VERIFY}e DocumentRoot /etc/puppet/rack/public/ RackBaseURI /</VirtualHost>
Performance PassengerPuppet docs recommend the use of the following apache config directives:
# Refresh old puppetmaster processes after 5 minutes...PassengerPoolIdleTime 300# Set to num_clients * 1.15, 15% more than required...PassengerMaxPoolSize 50# Puppetmaster takes a while, just use one global queue...PassengerUseGlobalQueue on# We don't need most of the passenger stuff, disable it...PassengerHighPerformance on
Keep an eye on memory usage!
Puppet pushing
Puppet is strictly pull-based by design.
Cannot truly push changes.
"puppetrunner" allows the master to remotely trigger the agent to run.
/etc/puppet/puppet.conf[agent] listen = true
Reporting
Agent creates a state.yaml file detailing the results of each run. Have the agent send the report to the master:
[agent] report = true
Report plugins parse state.yaml from the agents and sends the data off to some other utility.
[master]reports=<report_format>
Reporting Engines
Common report formats include:● http● log● rrdgraph● tagmail● puppetdashboard (PE Console)● foreman
Providers are located in:${RUBY_LIBDIR}/puppet/reports/*.rb
Puppet dashboard
Foreman
foreman
Can extend an existing puppet database, removing need for puppet/foreman syncing scripts.
Extensible, can control DHCP, DNS, etc
http://theforeman.org/
ENC
external node classifier● script● ldap
● classes● top scope variables● environment
ENC
external node classifier
● execute a script to determine classes to apply to a node.
● script receives fqdn as argument 1● must return yaml:
○ classes○ top scope variables○ environment
ENC
/usr/bin/puppet_node_classifier#!/usr/bin/ruby
require 'yaml'
# create an empty hash@enc = Hash.new@enc["classes"] = Hash.new@enc["classes"]["base"] = Hash.new@enc["parameters"] = Hash.new@enc["environment"] = 'production'
if ARGV[0] =~ /^www/ @enc["classes"]["webserver"] = Hash.newend
if ARGV[0] =~ /^kermi/ @enc["classes"]["kermit"] = Hash.newendputs @enc.to_yamlexit(0)
ENC#!/bin/bashset -eset -u
ENC_BASE_URL="https://localhost:443/nodes"
curl -k -H "Accept: text/yaml" "${ENC_BASE_URL}/${1}"
ENC LDAP[master]
node_terminus = ldapldapserver = ldap.henson.localldapbase = ou=hosts,dc=henson,dc=local
schema:attributeTypes:
puppetclassparentnodeenvironmentpuppetvarpuppetClient
# kermit.henson.local servers, hosts,kermit.henson.localdn: cn=kermit.henson.local,ou=servers,ou=hosts,dc=henson,dc=localcn: kermit.henson.localipHostNumber: 192.168.122.4puppetclass: serverpuppetclass: logpolicypuppetclass: netwatch::serverobjectClass: topobjectClass: iphostobjectClass: puppetclient
environments
[myenviro]modulepath = $confdir/environments/myenviro/modulesmanifest = $confdir/environments/myenviro/site.ppmanifestdir = $confdir/environments/myenviro/manifeststemplatedir = $confdir/environments/myenviro/templates
create separate workspaces for clients, change the manifests/modules based on the environment setting
environments[master]
modulepath = $confdir/environments/$environment/modules:$confdir/modules
manifest = $confdir/environments/$environment/manifests/site.pp
[production]
modulespath = $confdir/production/modules
manifest = $confdir/production/manifests/site.pp
[dev]
modulespath = $confdir/test/modules
manifest = $confdir/test/manifests/site.pp
http://docs.puppetlabs.com/guides/environment.html
environments + git● make new branches
git branch bobsbranch
● ssh-keys from git to puppetmaster
● update environments directory on commit to gitpuppet.git/hooks/post-receive
● tell agent to use the new environmentpuppet agent -t --environment bobsbranch
or/etc/puppet/puppet.conf[agent]environment=bobsbranch
http://docs.puppetlabs.com/guides/environment.html
Stored Configurations
Enables use of more advanced puppet features.
Used as a cache, speeds up catalog creation.
puppet.conf[master]
storeconfigs=truedbadapter=mysqldbname=puppetmasterdbuser=puppetmasterdbpassword=superdupersecretdbserver=localhost
fact_names
inventory_facts
param_values
param_namesinventory_nodes
resources
hostsfact_values
source_files
puppet_tagsresource_tags
Stored Configurations DB Schema
Stored Configurations DB Schema
Sample entry from "resources" table:id: 11518title: node001.example.comrestype: Sshkeyhost_id: 28source_file_id: 3exported: 1line: 307updated_at: 2012-04-23 15:25:15created_at: 2012-04-23 15:25:15
Virtualizing Resources
Create a resource, but don't actually send it to the host, keep it local to this manifest.
@ operator
@file { ... }@service { ... }@my_custom_type { ... }
The inverse - resource realization
Ok I changed my mind, send this resource to the host.
<| |> operator, or more recently, the realize() function.
File <| |> realize(File["title"])Service <| |> realize(Service["title"])My_custom_type <| |> realize(My_custom_type["title"])
Realize use case
base.pp:class base {
file {"/tmp/a":
mode => 644;}@user {
"lopsa":ensure => present;
}}
class manifest1 inherits base {
realize(User["lopsa"])...
}
class manifest2 inherits base {
realize(User["lopsa"])...
}
class manifest3 inherits base {
...}
exported resources cheat sheethttp://ramblings.narrabilis.com/Talks/exported_resources_cheatsheet.pdf
http://goo.gl/8KAsM
Exporting resources
Just sets the "exported" flag in mysql. These resources are now visible to all hosts.
@@ operator
@@file { ... }@@service { ... }@@my_custom_type { ... }
Collecting exported resources
This setup is really helpful for inter-machine communication.
<<| |>> operator
File <<| |>>Service <<| |>>My_custom_type <<| |>>
See where this is heading?
In base.pp:
@@sshkey { "${::fqdn}": ensure => present, type => "ssh-rsa", key => "${::sshrsakey}", require => Service["sshd"];}
Sshkey <<| |>>
Done! Now, all of your machines know every other machine's SSH key.
The SSH warning, "are you sure you want to continue? (yes/no):" is a thing of the past!
Of course, this only works for machines within the puppet infrastructure.
Exported resources "gotcha"Be careful when picking the title of any resource that's to be exported.
@@file["/etc/motd"] { mode => 644 }
You'd think this would be fine, but you'll get a "cannot redefine" error if more than one host runs it. Instead, do something like:
@@file {"/etc/motd_${::fqdn}":
path => "/etc/motd",mode => 644;
}
Grouping exported resources
Remember the builtin "tag" parameter?
@@sshkey{ "${::fqdn}": key => ${::sshrsakey}, tag => "group1";}
Sshkey <<| tag == "group1" |>>
Multiple group membership
Tags can be set as an array, but collected based on one value.@@sshkey{ "${::fqdn}": key => "${::sshrsakey}", tag => [ "internal", "external" ];}if $::hostname =~ /internal.example.com/ { Sshkey <<| tag == "internal" |>>}else {Sshkey <<| tag == "external" |>>}
Modify-on-collect
Interact with resources that only exist in the local manifest.
service { "sshd": ensure => running;}
Sshkey <<| |>> { notify => Service["sshd"]}
Systems Integration
Using exported resources, one can build a network adaptive to IP address & hostname changes.
Most common cases are system installation and some form of monitoring.
Puppet has builtin types for integrating with nagios. We'll add in bits to handle monitoring system firewall rules.
simple server/client autoconfig
serverA192.168.1.1
80 192.168.1.2
client
simple server/client autoconfig
File (tell client where to find the server)client stores config in /etc/service.confclass server {
@@file {"server_$::fqdn":
path => '/etc/service.conf',
content => inline_template('server=<%= ipaddress -%>'),
tag => 'server-config'
}
}
class client {
File <<| tag == 'server-config' |>>
}
simple server/client autoconfig
Firewall (tell server to allow client)class client {
@@firewall {"80 allow client $::fqdn port 80":
proto => 'tcp',
source => "$::ipaddress",
dport => "80",
action => 'accept',
tag => 'allow-client'
}
}
class server {
Firewall <<| tag == 'allow-client' |>>
}
https://github.com/puppetlabs/puppetlabs-firewall
Integration with anaconda/kickstart%post
chvt 6
/bin/false
count=0
exit=1
while [ $exit -ne 0 ]; do
puppet agent -t \
--server puppetmaster.example.com --waitforcert 60 >/dev/tty6 2>&1
exit=$?
count=$((count + 1))
if [ $count >= 5 ]; then
break
fi
done
if [ $exit == 0 ]; then
chkconfig puppet on
else
read "Puppet didn't run successfully, investigate"
fi
%end
%packages --nobase
@core
puppet
%end
hiera
● solves the nested if problem● set variables based on facts● hierarchical
Final Q & A
Thanks for attending!
Any questions?
Still awake...?
NaginatorStarted as a module/plugin. Became so useful, stable, and well-tested, it was cooked straight into base installs.
We'll use this following example as a "final review" of sorts. The example will get more complex the further into the manifest we go.
In the following examples, assume we've already created (or downloaded from the puppet forge!) the modules "apache" and "iptables".
nagios_host
Attributes
address
aliastitle: check_command
IP Address for Connections
check_period
contactsensurehost_nametarget
Display Addresscheck_ping,check_ssh,etce.g. "24x7","workhours"Who shall we notify?presentabsentFQDN for connections.Filename
nagios_service
Attributes
check_command
check_intervaltitle: check_period
notification_interval
ensure
host_name
max_check_attempts
target
check_ping,check_ssh,etcChecking frequency"24x7","workhours",etcNotification frequencypresentabsentCorresponds to host definitionFailures before notification occurs.File destination
Bare minimum nagios manifest [1/9]class nagios {
package {"nagios":
ensure => present;"nagios-plugins-all":
ensure => present;}service {
"nagios":ensure => running,enable => true,require => Package["nagios"];
}include apacheinclude apache::sslonly
...
Bare minimum nagios manifest [2/9]file {
# Directory for all of the nagios config files..."/etc/nagios/conf":
ensure => directory,owner => "nagios",group => "nagios",recurse => true,ignore => [ ".svn", ".git", "CVS" ],source => "puppet:///filesroot/nagios/etc/conf",mode => 750,notify => Service["nagios"];
# Apache configuration file for nagios..."/etc/httpd/conf.d/nagios.conf":
require => Package["nagios"],notify => Service["httpd"],content => template("/path/to/file");
...
Bare minimum nagios manifest [3/9]...
# Nagios CGI interface configuration file..."/etc/nagios/cgi.conf":
require => Package["nagios"],notify => Service["nagios"],content => template("/path/to/file");
# main nagios configuration file..."/etc/nagios/nagios.cfg":
require => Package["nagios"],notify => Service["nagios"],content => template("/path/to/file");
}
...
Bare minimum nagios manifest [4/9]# We're going to manage the NRPE config file, but export# it to the clients instead of installing it locally.# This way, we can fill in a template with nagios' IP
address.@@file {
"/etc/nagios/nrpe.cfg":content => template("/path/to/file"),tag => "nagios";
}# While we're at it, lets export a firewall rule.# You might consider sprucing this up in production...@@iptables {
"9995 allow monitor":proto => "any",source => "${::ipaddress}",jump => "ACCEPT",
tag => "monitor";}
...
Bare minimum nagios manifest [5/9]...
# To list all of the nagios definitions here would use# up several more slides for minimal scholastic gain, so# this can be a homework assignment instead!
# Here, we need to create all of the nagios types# we've referenced throughout this example.# This includes:# The "24x7" nagios_timeperiod# The "mycontact" nagios_contact# nagios_command defines (check_ping, check_ssh,
host_notify)
# It's not hard, just refer to the puppet type reference.# Create the objects, fill in the attributes, and you'll# have your own fully-puppetized nagios installation up# and running in no time!
...
Bare minimum nagios manifest [6/9]# We can now collect host & service objects from client
nodes:Nagios_host <<| tag == "nagios" |>>Nagios_service <<| tag == "nagios" |>># Client manifests will have the text "include nagios::
client"class client {
package {"nagios-plugins-all":
ensure => present;"nrpe":
ensure => present;}service {
"nrpe":ensure => running,enable => true,require => Package["nrpe"];
}...
Bare minimum nagios manifest [7/9]# Export our host definition:@@nagios_host {
"${hostname}":ensure => present,address => "${ipaddress}",tag => "nagios",alias => "${fqdn}",target => "/etc/nagios/conf/dynamic_hosts.cfg",check_command => "check_ping!100!500",check_interval => "3",contacts => "mycontact",check_period => "24x7",max_check_attempts => 3,require => File["/etc/nagios/conf"],notify => Service["nagios"];
}
...
Bare minimum nagios manifest [8/9]# Begin to export our service definition(s)# Set defaults first in case we decide to add more later.Nagios_service {
target => "/etc/nagios/conf/dynamic_services.cfg",use => "generic-service",is_volatile => "0",host_name => "${hostname}",contacts => "mycontact",notification_period => "24x7",require => [ Nagios_host["${hostname}"],
File["/etc/nagios/conf"] ], notify => Service["nagios"]
} ...
Bare minimum nagios manifest [9/9]# Now do the actual service export. Just append another# N definitions here and they'll get auto-added to
nagios.@@nagios_service {
"${hostname}_check_ssh":ensure => present,service_description => "SSH",check_command => "check_ssh",check_period => "24x7",check_interval => "3",retry_interval => "3",max_check_attempts => "3",tag => "nagios",notification_interval => "720";
}# Final order of business, collect the nrpe config file:File <<| tag == "nagios" |>> { notify => Service["nrpe"]
}}
}
further reading● pro puppet
James Turnbull , Jeffrey McCunehttp://www.apress.com/9781430230571
● learning puppet - puppetlabshttp://docs.puppetlabs.com/learning/
● types documentation - puppetlabshttp://docs.puppetlabs.com/references/stable/type.html
● puppetlabs pdfshttp://info.puppetlabs.com/download-pdfs.html
● function referencehttp://docs.puppetlabs.com/references/stable/function.html