When traditional sysadmins first encounter Puppet's declarative DSL, it feels like being asked to write poetry in a foreign language. The key is to reframe infrastructure as "executable documentation" rather than "code". Start with simple manifests that replace shell scripts:
# Instead of:
# ssh web01 "apt-get install nginx && systemctl start nginx"
# Write this in Puppet:
package { 'nginx':
ensure => installed,
}
service { 'nginx':
ensure => running,
require => Package['nginx'],
}
The Puppet Forge contains over 5,000 modules, but quality varies. Implement this evaluation checklist:
- Support Score: Look for modules with >80% Puppet Supported or Puppet Approved status
- Activity: Last update within 6 months and regular commit history
- Dependencies: Fewer than 5 required modules (check metadata.json)
For example, when choosing an Apache module between puppetlabs/apache and example42/apache, the puppetlabs version wins with:
"dependencies": [
{"name":"puppetlabs/stdlib","version_requirement":">= 4.13.1 < 7.0.0"}
]
Adopt this directory structure for transparency:
environment/
├── production/
│ ├── manifests/
│ │ └── site.pp # Node definitions
│ ├── modules/
│ │ ├── profile/ # Business logic
│ │ ├── role/ # Server roles
│ │ └── custom_module/ # Local modules
│ └── hiera.yaml # Configuration data
Implement the Roles and Profiles pattern:
# role/webserver.pp
class role::webserver {
include profile::base
include profile::nginx
include profile::firewall
}
Create a module skeleton with pdk new module
and enforce these conventions:
- Parameter validation using Puppet's data types
- Single class responsibility principle
- rspec-puppet tests for all manifests
Example module structure:
custom_module/
├── manifests/
│ ├── init.pp
│ └── config.pp
├── spec/
│ ├── classes/
│ └── fixtures/
├── templates/
│ └── config.erb
└── metadata.json
These extensions are worth learning:
Tool | Purpose | When to Use |
---|---|---|
Hiera | Separate data from code | Environment-specific configurations |
Augeas | Edit config files | Modifying existing files like /etc/ssh/sshd_config |
Trocla | Password management | When storing credentials in Puppet |
Replace "let's just run it" with this systematic approach:
# Dry-run with detailed logging
puppet apply --noop --debug manifest.pp
# Show resource dependency graph
puppet apply --graph manifest.pp
xdg-open /opt/puppetlabs/puppet/cache/state/graphs/relationships.dot
For existing infrastructure, generate Puppet code from current state:
puppet resource package
puppet resource service
Many sysadmin teams face resistance when adopting Puppet, especially when coming from traditional manual administration backgrounds. The key concerns often revolve around:
- Perceived complexity of the DSL (Domain Specific Language)
- Module selection paralysis in the Puppet Forge ecosystem
- Maintainability of custom manifests
- Keeping up with Puppet's evolving best practices
Instead of formal training, consider this incremental approach:
# Sample learning path manifest
node 'learning-path' {
# Stage 1: Basic resource types
include ::learn_puppet::files
include ::learn_puppet::packages
# Stage 2: Simple modules
include ::learn_puppet::webstack
# Stage 3: Advanced patterns
include ::learn_puppet::roles_profiles
include ::learn_puppet::hiera_integration
}
When evaluating Puppet Forge modules:
# Module evaluation checklist in Puppet code
$module_quality_checks = {
'downloads' => >10000,
'puppet_version' => '>= 5.5.0',
'dependencies' => <5,
'ci_status' => 'passing',
'activity' => 'updated_within(6 months)'
}
Implement these directory structures for clarity:
manifests/
├── site.pp # Node definitions
├── roles/ # Business logic
│ ├── webserver.pp
│ └── database.pp
└── profiles/ # Technical implementation
├── apache.pp
└── mysql.pp
modules/
├── organization_custom/ # Company-specific modules
└── third_party/ # Forge modules
- Puppet Strings: Documentation generator
# Example documentation comment # @param ensure Whether to install or remove package # @param version Specific version to install define mymodule::package ( Enum['present','absent'] $ensure = 'present', Optional[String] $version = undef ) { package { $title: ensure => $ensure, version => $version, } }
- Onceover: Lightweight testing framework
- PDK: Puppet Development Kit for module creation
Create a controlled adoption process:
# hiera.yaml version control example
:version: 5
:hierarchy:
- "nodes/%{trusted.certname}"
- "environments/%{environment}"
- "common"
# Only enable extensions after evaluation
$enable_experimental_features = {
'trocla' => false,
'augeas' => true,
'deferred' => true
}
For the "let's just run it" mentality:
# puppet/control-repo/manifests/phases.pp
node 'webserver01' {
# Phase 1: Core OS configuration
include ::phase1::baseline
# Phase 2: Essential services (2 weeks later)
if $::phase >= 2 {
include ::phase2::webstack
}
# Phase 3: Optimization (1 month later)
if $::phase >= 3 {
include ::phase3::tuning
}
}