Puppet Part3 DEVOPS ONLINE TRAINING
July 11, 2026 — LiveStream
Disclosure: some links above are affiliate links — if you buy through them I may earn a small commission at no extra cost to you. Thanks for supporting the channel!
file { '/etc/motd':
ensure => file,
content => 'Welcome to our Puppet-managed server!',
owner => 'root',
group => 'root',
mode => '0644',
}
This simple manifest ensures that the `/etc/motd` file exists, has specific content, ownership, and permissions. Each resource declaration follows a `resource_type { 'resource_title': attribute => value, ... }` pattern. Simple, right? But imagine having hundreds of such resources for various configurations across multiple servers. Keeping them in one big file or scattered randomly is a recipe for disaster. This is why we need modules.
mymodule/
manifests/
(Contains classes and defined types)
init.pp
(The primary class for the module, `mymodule`)config.pp
(A class for configuration, `mymodule::config`)install.pp
(A class for installation, `mymodule::install`)files/
(Static files to be distributed to agents)
myconfig.conf
templates/
(ERB templates for dynamic file content)
server.conf.erb
lib/
(Custom facts, functions, resource types, providers)facts.d/
(External facts scripts)data/
(Hiera data for module)examples/
(Example usage of the module)spec/
(RSpec tests for the module)metadata.json
(Metadata about the module: version, dependencies, author)
# myapache/manifests/init.pp
class myapache (
$ensure_package = 'present',
$port = 80,
$docroot = '/var/www/html',
) {
package { 'apache2':
ensure => $ensure_package,
}
file { '/etc/apache2/sites-available/000-default.conf':
ensure => file,
content => template('myapache/vhost.conf.erb'), # Using a template
require => Package['apache2'],
notify => Service['apache2'],
}
service { 'apache2':
ensure => running,
enable => true,
subscribe => File['/etc/apache2/sites-available/000-default.conf'],
}
file { $docroot:
ensure => directory,
owner => 'www-data',
group => 'www-data',
mode => '0755',
require => Package['apache2'],
}
}
Notice the parameters `$ensure_package`, `$port`, `$docroot`. These make the class flexible. You can customize its behavior without changing the class definition itself. This level of parameterization is a key learning in any advanced "Puppet Part3 DEVOPS ONLINE TRAINING."
# In your node definition, e.g., site.pp or a dedicated node manifest
node 'webserver01.example.com' {
include myapache # Simple inclusion, uses default parameters
}
node 'webserver02.example.com' {
class { 'myapache': # Declaring with custom parameters
port => 8080,
docroot => '/srv/web',
}
}
This separation of definition and declaration is crucial for managing complex infrastructure. You define the blueprint once in a module, and then you apply it to various nodes with specific configurations.
file { '/etc/mywebapp/config.json':
ensure => file, # Can be 'file', 'directory', 'link', 'absent'
content => '{ "database": "production_db", "port": 5432 }', # Inline content
source => 'puppet:///modules/mywebapp/config.json', # Content from a file in the module
owner => 'webapp',
group => 'webapp',
mode => '0640', # Permissions in octal
# Templates (ERB) for dynamic content:
# content => template('mywebapp/config.json.erb'),
}
* **`ensure`**: Specifies the state (`file`, `directory`, `link`, `absent`).
* **`content`**: Directly specifies the content.
* **`source`**: Fetches content from a file in a module (e.g., `puppet:///modules/mymodule/myfile`).
* **`template`**: Uses an ERB template to generate content dynamically (e.g., `template('mymodule/mytemplate.erb')`). This is where the real power lies for generating environment-specific configurations.
* **`owner`, `group`, `mode`**: Standard file system permissions.
package { 'nginx':
ensure => present, # Can be 'present', 'latest', 'purged', 'absent', or a specific version like '1.18.0'
provider => 'apt', # Optional: Specify package manager (e.g., apt, yum, brew)
before => File['/etc/nginx/nginx.conf'], # Order dependency
}
* **`ensure`**: `present` (installs if missing), `latest` (upgrades to the newest version), `absent` (removes package), `purged` (removes package and configuration files). You can also pin to a specific version.
* **`provider`**: Usually Puppet auto-detects, but sometimes explicit specification helps (e.g., `yum`, `apt`, `gem`, `pip`).
service { 'nginx':
ensure => running, # Can be 'running' or 'stopped'
enable => true, # Starts service on boot (true/false)
subscribe => File['/etc/nginx/nginx.conf'], # React to file changes
require => Package['nginx'], # Ensure package is installed first
}
* **`ensure`**: `running` (ensures service is active), `stopped` (ensures service is inactive).
* **`enable`**: `true` ensures the service starts automatically on boot.
* **`subscribe`**: This is a reactive dependency. If the subscribed resource changes (e.g., a configuration file), the service will be restarted.
* **`require`**: A proactive dependency. Ensures the required resource is managed *before* this one.
user { 'devops_user':
ensure => present,
home => '/home/devops_user',
shell => '/bin/bash',
password => '$6$salt$encrypted_password_hash', # Use output from mkpasswd -m sha-512
groups => ['sudo', 'docker'], # Add to additional groups
managehome => true, # Create home directory if it doesn't exist
}
* **`ensure`**: `present` or `absent`.
* **`home`**, **`shell`**: Standard Unix user attributes.
* **`password`**: Crucial for security. *Never* put plain text passwords. Always use a hashed password.
* **`groups`**: An array of supplementary groups the user should belong to.
* **`managehome`**: If `true`, Puppet will create the home directory if it doesn't exist.
Understanding these resources deeply, including their attributes and interdependencies, is vital for writing effective Puppet code. In a comprehensive "Puppet Part3 DEVOPS ONLINE TRAINING," you'd practice these with real-world scenarios.
$my_app_version = '1.0.0'
$doc_root = '/var/www/my_app'
package { 'my_app':
ensure => $my_app_version,
}
file { $doc_root:
ensure => directory,
owner => 'my_app_user',
group => 'my_app_group',
mode => '0755',
}
Variables can be defined at different scopes (global, node, class), with specific rules determining which value is used. Understanding variable scope is crucial to avoid unexpected behavior.
# Install different web servers based on OS family
if $::osfamily == 'RedHat' {
package { 'httpd': ensure => present }
service { 'httpd': ensure => running, enable => true }
} elsif $::osfamily == 'Debian' {
package { 'apache2': ensure => present }
service { 'apache2': ensure => running, enable => true }
} else {
# Handle other OS families or raise an error
notify { 'Unsupported OS':
message => "Cannot configure web server for OS family ${::osfamily}",
level => error,
}
}
The `::` before `osfamily` indicates that it's a top-scope fact, accessible everywhere. Common facts like `$::ipaddress`, `$::hostname`, `$::memorysize` are invaluable for dynamic provisioning. You can also create custom facts if Facter doesn't provide the information you need.
if $environment == 'production' {
# Apply production-specific configurations
file { '/etc/app/config.prod.yaml':
ensure => file,
source => 'puppet:///modules/myapp/config.prod.yaml',
}
} elsif $environment == 'staging' {
# Apply staging-specific configurations
file { '/etc/app/config.stage.yaml':
ensure => file,
source => 'puppet:///modules/myapp/config.stage.yaml',
}
} else {
# Default or development configurations
file { '/etc/app/config.dev.yaml':
ensure => file,
source => 'puppet:///modules/myapp/config.dev.yaml',
}
}
case $::operatingsystem {
'CentOS', 'RedHat': {
package { 'vim-enhanced': ensure => present }
}
'Ubuntu', 'Debian': {
package { 'vim': ensure => present }
}
default: {
notify { 'unsupported_vim':
message => "Vim package not explicitly defined for ${::operatingsystem}",
level => warning,
}
}
}
Using facts with conditionals makes your Puppet code remarkably flexible and resilient, allowing it to adapt to diverse infrastructure requirements. This is a core competency taught in any practical "Puppet Part3 DEVOPS ONLINE TRAINING."
# For Debian/Ubuntu
wget https://apt.puppet.com/puppet-release-$(lsb_release -cs).deb
sudo dpkg -i puppet-release-$(lsb_release -cs).deb
sudo apt update
sudo apt install puppet-agent
# For RHEL/CentOS
sudo yum install https://yum.puppet.com/puppet-release-el-7.noarch.rpm # For EL7
# For EL8: sudo yum install https://yum.puppet.com/puppet-release-el-8.noarch.rpm
sudo yum install puppet-agent
2. **Configure Puppet Agent to point to Master**:
Edit `/etc/puppetlabs/puppet/puppet.conf` on the agent:
[main]
certname = <AGENT_HOSTNAME> # e.g., web01.example.com
server = <PUPPET_MASTER_HOSTNAME_OR_IP> # e.g., puppet.example.com
environment = production # or whatever environment you're using
[agent]
# Optionally, specify runinterval (default is 30m)
# runinterval = 30m
Make sure your agent can resolve the master's hostname. Add an entry to `/etc/hosts` if DNS isn't set up.
3. **Start and Enable Puppet Agent Service**:
sudo systemctl start puppet
sudo systemctl enable puppet
4. **Initial Agent Run to Request Certificate**:
sudo /opt/puppetlabs/bin/puppet agent -t
This command attempts to contact the master, sends its certificate signing request (CSR), and tries to fetch its catalog. It will likely fail the first time, waiting for the certificate to be signed.
sudo /opt/puppetlabs/bin/puppet cert list
You will see output like:
"web01.example.com" (SHA256) A1B2C3D4...
2. **Sign the Agent's Certificate**:
sudo /opt/puppetlabs/bin/puppet cert sign web01.example.com
Once signed, the agent can successfully retrieve its certificate and proceed with catalog compilation and enforcement on its next run.
3. **Verify Agent Run**:
Run `sudo /opt/puppetlabs/bin/puppet agent -t` again on the agent. This time, it should connect, get a catalog, and apply configurations.
Understanding this entire handshake process, certificate signing, and common troubleshooting steps (like `puppet agent -t --debug` for verbose output) is absolutely vital for managing a Puppet environment effectively. This is where a hands-on "Puppet Part3 DEVOPS ONLINE TRAINING" truly empowers you.
Related: Mastering Data Separation with Hiera in Puppet
Puppet modules are self-contained, reusable units of Puppet code that organize manifests, templates, files, and plugins. They are crucial in DevOps because they allow engineers to structure their configurations logically, promote code reuse across different systems and environments, and simplify the management of complex infrastructures. Instead of monolithic code, modules break down configurations into manageable, testable components.
Puppet facts, gathered by Facter, provide system-specific information (like OS type, IP address, CPU count) about the node Puppet is running on. They enhance configuration management by allowing Puppet manifests to be dynamic and adaptive. Instead of writing separate configurations for every OS or hardware variant, you can use facts with conditional logic to create a single, intelligent manifest that configures systems differently based on their unique characteristics, improving flexibility and reducing code duplication.
Both `require` and `subscribe` establish dependencies between Puppet resources, but their behavior differs. `require` creates a proactive dependency, ensuring that the required resource is *managed before* the current resource. If the required resource fails, the current resource won't be managed. `subscribe`, on the other hand, creates a reactive dependency. If the subscribed resource changes, the current resource will be *refreshed or restarted*. For example, a `service` might `subscribe` to a `file` resource, so if the configuration file changes, the service automatically restarts.
To set up a Puppet agent: first, install the `puppet-agent` package on the client machine. Then, configure `/etc/puppetlabs/puppet/puppet.conf` by setting `certname` to the agent's hostname and `server` to the Puppet master's hostname or IP. Ensure the agent can resolve the master's hostname. Finally, start and enable the Puppet service, and run `sudo /opt/puppetlabs/bin/puppet agent -t` to send a certificate signing request (CSR) to the master. An administrator must then sign this certificate on the master using `puppet cert sign