Skip to main content

Puppet Quick Start


Intro Video

Photo of Elle Krout

Elle Krout

Content Team Lead in Content

Elle is a Course Author at Linux Academy and Cloud Assessments with a focus on DevOps and Linux. She's a SaltStack Certified Engineer, and particularly enjoys working with configuration management. Prior to working as a Course Author, she was Linux Academy's technical writer for two years, producing and editing written content; before that, she worked in cloud hosting and infrastructure.Outside of tech, she likes cats, video games, and writing fiction.







Hands-on Labs


Course Details

Go from no Puppet experience to writing your own Puppet modules in this quick start! We take beginners and those who have never learned Puppet before and jump right in by setting up a Puppet Server and agent node and creating an `nginx` module. As we create this `nginx` module, we'll learn how to use resource types, provide operating system-specfic parameters, store data in various Hiera hierarchies, and learn some Puppet best practices. Download The Puppet Project here:


Let's Get Started!

Puppet Introduction and Architecture


Lesson Description:

Welcome to the Puppet Quick Start! What is a quick start you ask? Well, our goal is to take you from knowing nothing tobeing a competent Puppet user in about two hours. And what do we need from you to achieve this?First off, it helps to already be familiar with the idea of configuration management. This is setting up bits of code that will help you provision and manage tens or hundreds or thousands or more-than-thousands of servers from a central, or "master" server. That's what Puppet is, a configuration management platform. And in these next couple of hours, we're going to be installing Puppet, creating an nginx module to run against any Red Hat- or Debian-based servers, use the Puppet Forge, and check out PuppetDB.First, let's break down how Puppet works.Our master server runs the Puppet Server, and stores our manifests (server descriptions), our catalogs (the mapping that determines which servers receive what configurations), and our data store data.The minion runs the `puppet-agent` and collects information about itself, which it then in turn sends back to the Master on port 1840; the minions then request a catalog.Next, the master compiles a catalog for each minion, based on the facts the minion sent it. The master gives that catalog to the minion.The minion ensures that it matches the described state by applying the catalog and going through each manifest in the catalog; anything that does not match is brought up to date in a process called a "catalog converge."Once converged, the minion sends a report about the catalog converge back to the master.In this Quick Start, we'll be creating an nginx module. A module is a full configuration for one specific item, such as nginx. We'll use that to explore not just module creation itself, but some of Puppet's features, like Hiera. We'll see how to use the Puppet Forge to download existing modules, specifically the PuppetDB module. We'll take a brief look at that before ending the course with some suggestions on where to go from here, both if you want to get more in-depth with Puppet and if you want to explore your other configuration management options.Now let's get started!

Installation and Configuration: Puppet Server


Lesson Description:

Puppet Open Source (and Puppet Enterprise) only supports master-agent setups now, and recommends using the Java Virtual Machine-based Puppet Server for the master, not the older, Ruby-based Puppet Master application.We'll be using Ubuntu 18.04 for our Puppet Server. The size of the server needed for the Puppet differs, depending on how many agents that server will support:| | >10 | >1000 | |------:|:---:|:-----:| | **Cores** | 2 | 4 | | **RAM** | 1 GB | 4 GB |Since we'll only be using two agents in this quick start, we have two options if we're using Linux Academy's Cloud Playground: We can use the *Small-sized* server and make some configuration changes during installation, or we can use the *Medium-sized* server, which meets the minimum Puppet requirements for master servers managing between 10 and 1000 agent nodes.> Still using Linux Academy's old-style Cloud Servers? That's okay! Follow the instructions as though you were setting up Puppet Server on a small-sized playground server.We'll be working as `root` in this lesson. Use `sudo -i` to switch from the default `cloud_user` to `root`.## Add the Puppet RepositoryPuppet maintains its own repositories for all supported Puppet Server distributions, including:- Red Hat Enterprise Linux and derived distros - Debian - Fedora - UbuntuTo add the Puppet repository in Ubuntu, use:# wget # dpkg -i puppet6-release-bionic.deb # apt updateIf we want, we can also remove the `.deb` package now that the repository is set up:# rm puppet6-release-bionic.deb## Install the Puppet Server PackageBefore we install the `puppetserver` package from our newly-added repo, let's ensure our `hosts` file is properly set up. By default, our cloud playground has public hostnames we can use. We want to change one of these to work within our internal network.Open `/etc/hosts` and move the hostname mapping to the localhost: localhost puppetYou may also want to add `puppet` as a hostname.Install the Puppet Server:# apt-get install puppetserver## Configure Puppet ServerWhile Puppet can detect the hostname by default, with our playground servers, it needs a little help. Configurations used for initial Puppet Server startup and certificate generation are found at `/etc/puppetlabs/puppet/puppet.conf`. Specifically, we want to add the `certname` value to both the `[main]` and `[master]` sections:[main] certname =[master] certname = vardir = /opt/puppetlabs/server/data/puppetserver logdir = /var/log/puppetlabs/puppetserver rundir = /var/run/puppetlabs/puppetserver pidfile = /var/run/puppetlabs/puppetserver/ codedir = /etc/puppetlabs/code### For Small Servers OnlyIf we're using a smaller server, then prior to starting the Puppet Server daemon we also need to make some changes to the `/etc/default/puppetserver` file, to limit the memory allocation:JAVA_ARGS="-Xms1g -Xmx1g -Djruby.logger.class=com.puppetlabs.jruby_utils.jruby.Slf4jLogger"If the Puppet Server fails to start, or has memory issues later on, drop the memory values farther. To set to 512 MB instead, use:JAVA_ARGS="-Xms512m -Xmx512m -Djruby.logger.class=com.puppetlabs.jruby_utils.jruby.Slf4jLogger"## Set Up the Certificate AuthorityPuppet manages its own intermediate signing CA. Before we start the Puppet Server for the first time, we need to run the CA setup:/opt/puppetlabs/bin/puppetserver ca setup## Start the Puppet ServerWe can now start the Puppet Server (and enable it to start when the system boots) with:# systemctl start puppetserver # systemctl enable puppetserverTo start using the `puppetserver` command instead of the full path, we can refresh our bash prompt, then check our CA list to make sure the Puppet Server has no issues communicating through the certname we set up:# exit # sudo -i # puppetserver ca list

Installation and Configuration: Puppet Agent


Lesson Description:

## Installation and Configuration: Puppet AgentWith the Puppet Server setup, we now want to provide agents for it to manage. We'll start by creating a CentOS 7 agent on the Cloud Playground. There are no size requirements, so a "micro"-sized server will work perfectly fine.### Prepare the ServerWe need to update our `/etc/hosts` file to both add the hostname change if applicable and inform the server of the location of our Puppet Server: localhost localhost.localdomain localhost4 localhost4.localdomain4 web01 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 # Cloud Server Hostname mapping puppet Add Puppet RepositoriesWe now want to add the Puppet 6.0 repository for RHEL-based distros:sudo rpm -Uvh, run a `yum update` to enable the repository for use:sudo yum update### Install PuppetTo install Puppet on the agent server, all we have to do is run:sudo yum install puppet-agentThen start up the service:sudo systemctl start puppet sudo systemctl enable puppet### Configure PuppetThe Puppet agent will automatically look for the server with the hostname `puppet`; if we wanted to change this, we could have to make changes within the `/etc/puppetlabs/puppet/puppet.conf` file, where all our Puppet agent-related configurations would be stored. For example, if we wanted to use our "mylabserver" hostname, we could update this to read:[main] server = LABSERVERID.mylabserver.comWe could also use the `puppet-config` command-line utility:sudo /opt/puppetlabs/bin/puppet config set server Approve AgentNext, we want to drop to `root`:sudo -iAnd with our agent ready, we can now go ahead and confirm the certificate on the Puppet Sever. Before we log in to the Server, however, let's output the agent's certificate fingerprint:puppet agent --fingerprintBut wait! We have an error. Caused by conflicting Ruby versions, we need to disable our `rvm` settings in `/etc/profile.d` to continue:mv /etc/profile.d/ /etc/profile.d/ **log out and log back in**:exit sudo -iNow, we can switch to our Puppet Server, let's see what we have to accept:ssh cloud_user@puppet sudo -i puppetserver ca listIf the fingerprints match, we can approve the server:puppetserver ca sign --certname

Puppet Modules

Module Creation


Lesson Description:

Now that we have Puppet up and running, we can start to actually use it to manage our servers, or in this case a single server. Since Puppet is a configuration management (or infrastructure as code) solution, we use Puppet by providing it written descriptions of our desired end server state. This is done in the Puppet language, which is comprised of a number of resource declarations that allow us to define the results we want for our managed nodes.If none of those terms are making sense yet, that's okay. We'll break it down as we go. But before we do that, let's first figure out where we'll be storing our _module_, those end-state recipes we're going to write.## EnvironmentsBy default, Puppet has environment use enabled and sets our default environment to _production_. Since our goal here is to jump right into using Puppet, that's going to be the environment we use.Our environments are stored in the Puppet code directory, located at `/etc/puppetlabs/code`. The production environment is one folder below that; let's move into it:# cd /etc/puppetlabs/code/environments/productionWithin our environment, we have three directories and a couple of files:+ `environment.conf`: Contains environmental settings; no need to touch this to adjust the production environment out-of-box + `hiera.yaml`: Our Hiera configuration file that we'll take a look at this next video! + `data`: The directory where we'll store our Hiera data, covered more in the next lesson + `manifests`: Where our main manifest(s) are stored, and where we'll take our end-state configurations and map them to which servers we want to use them against + `modules`: Directory where we'll write and store our end-state configuration file## ModulesLet's move into the `modules` directory:# cd modulesIf we list out the contents of this directory, we can see that it is empty. At this point, we have two options: We can manually create a module directory for our `nginx` module, or we can use the Puppet Development Kit to generate one.> In previous versions of Puppet, the `puppet module generate` command was used instead of the PDK. This is deprecated, and should not be used with Puppet 6.The Puppet Development Kit makes the creation of a module, or at least the skeleton of one, a quick and simple process, so we're going to take this route. First, we need to install it:# apt-get install pdkWhen the installation is complete, we can then generate our new module with the `pdk new module` command:# pdk new module nginxWe'll be prompted with a series of questions:[Q 1/4] If you have a Puppet Forge username, add it here. We can use this to upload your module to the Forge when it's complete. --> clouduser[Q 2/4] Who wrote this module? This is used to credit the module's author. --> Elle K[Q 3/4] What license does this module code fall under? This should be an identifier from Common values are "Apache-2.0", "MIT", or "proprietary". --> Apache-2.0[Q 4/4] What operating systems does this module support? Use the up and down keys to move between the choices, space to select and enter to continue. --> RedHat based Linux, Debian based Linux, Windows (Use arrow or number (1-7) keys, pres--> RedHat based Linux, Debian based LinuxMetadata will be generated based on this information, continue? YesSince we aren't uploading this to the Puppet Forge, you can leave the Puppet Forge username with the default setting; if you want to change it later, it can be altered in the `metadata.json` file that sits in the newly-created `nginx` folder. We also left the license option with the default setting. Finally, be sure `RedHat based Linux` and `Debian based Linux` are selected for the supported operating systems.Now, let's see what was actually generated:# cd nginx # ls data hiera.yaml spec Gemfile Rakefile examples manifests tasks Gemfile.lock appveyor.yml files metadata.json templatesThat's quite a lot of files of folders! But we don't have to worry about every single one of them, because the Puppet Dev Kit did most of the heavy lifting. Instead, let's focus on the directories we'll interact with most:+ `data`: Just like in the above `production` directory, this `data` file stores Hiera information + `examples`: Stores examples on how to use the module's classes and types + `files`: Stores static files for nodes + `manifests`: Stores our manifests, the files that build out our module + `spec`: Spec tests for any plug-ins + `tasks`: Contains any Puppet tasks, which allow us to provide ad-hoc commands and scripts to run on our nodes + `templates`: Stores any templates, which can be used to generate content### Writing a ManifestAs with module creation, we can use the PDK to generate a module class. A class is a single component of our end-state description, while a manifest is the containing file. Our install class, for example, will tell Puppet that we can to install nginx on a node, and would be contained in the `install.pp` manifest. That's it. It won't be starting the `nginx` service or changing any configuration files.Let's go ahead and create that `install.pp` file. Similar to the creation of the module itself, we have two options here: opening our preferred text editor and just writing the class from scratch, or using the `pdk` command. Let's use the Puppet Dev Kit:# pdk new class installNow we can open this file in our editors and see what we just generated:# cd manifests # $EDITOR install.ppHere are the file's contents:# A description of what this class does # # @summary A short summary of the purpose of this class # # @example # include nginx::install class nginx::install { }We have a description, a summary, an example, and a very simple starting function bracket to work with. Let's first update the commented data:# Installs nginx # # @summary Installs nginx # # @example # include nginx::installWe now need to fill out that defined class, but before we do that, some things to consider:+ When tabbing in, use two single spaces, as you would with Ruby + Keep "hashrockets" between values and mappings alignedWithout our `nginx::install` class, we want to provide it with information about the _resource type_ we want to use to perform an action. Resource types allow us to manage a single kind of resource on our node. We'll be using the `package` resource type, which allows us to manage packages on our nodes:class nginx::install { package { 'install_nginx': } }`install_nginx` is simply the name we provided this specific task the `package` resource is supposed to perform. Each resource type has a number of defined attributes (also called parameters) that we can use alongside it. For our install class, we're going to use the `name` and `ensure` attributes:class nginx::install { package { 'install_nginx': name => 'nginx', ensure => 'present', } }The `name` value is the package name for installation. `ensure` makes certain that the package is `present` on the targeted system.## TestingLet's save and exit the file. We want to try this out on our node to see if it works. But first, let's validate our Puppet code:# puppet parser validate install.ppIf nothing is returned, we know the file is properly formatted.

The init.pp and site.pp Files


Lesson Description:

We now want to create our `init.pp`. This is what gets called when we reference just the `nginx` module itself, and all modules must have an `init.pp` file.# pdk new class nginx # vim manifests/init.ppWe're going to use this file to reference all of our classes, so that when we call the `nginx` module, all tasks are complete. Since we only have our `nginx::install` class right now, we'll start with that.The `contain` function ensures that our classes are performed in the correct order, making sure the contained class is not run prior to the manifest (in this instance, the `init.pp`), but is finished before the manifest is complete.class nginx { contain nginx::install }Save and exit this `init.pp` fie.We can now map our `nginx` module to our agent node, allowing us to apply it. This is done in the `production` environment's `site.pp` file, which is the default manifest name. All we have to do is define our node, then include our module:node { class { 'nginx': } }Save and exit the file.We can now make sure our module works by logging in to our CentOS 7 agent node, becoming `root`, and running:# puppet agent -tThen too confirm that nginx was installed, run:# which nginx

Additional Classes


Lesson Description:

We want to pad out our `nginx` module by providing two additional classes: the `service` class and the `config` class.### config.ppPull down the `deb-nginx.conf` and `rh-nginx.conf` files. We want to store these in the `files` directory of our nginx module.curl content-puppetqs-nginx/master/files/deb-nginx.conf -o deb-nginx.conf curl content-puppetqs-nginx/master/files/rh-nginx.conf -o rh-nginx.confDrop back down to the `nginx` module directory, then create the `config` class:pdk new class configOpen the newly-created file and update the document string:# $EDITOR manifests/config.pp# Manages the nginx.conf file # # @summary Manages the nginx.conf file # # @example # include nginx::configWe then want to use the `file` resource type to manage where our files go. We're working on testing this against just our Red Hat-based CentOS 7 server right now, so we'll focus on the `rh-nginx.conf` file.When referencing files in our class, we want to use the Puppet URI. Instead of inputting the full path, we can use the `puppet:///` shorthand to reference our `/etc/puppetlabs/code/environments/production` directory; it also knows to pull from the `files` directly for static files, and the directory itself should not be referenced in the path:class nginx::config { file { 'nginx_config': path => '/etc/nginx/nginx.conf', source => 'puppet:///modules/nginx/rh-nginx.conf', ensure => 'present', } }Save and exit the file.### service.ppFinally, we want to provide a class that allows us to both make sure our `nginx` daemon is started and enabled, as well as able to restart when any changes are made to our configuration file from our `config` class:pdk new class serviceUpdate the Puppet docstring:# Manage the state of the nginx daemon # # @summary Manage the state of the nginx daemon # # @example # include nginx::serviceAdd the `service` class:class nginx::service { service { 'nginx_service': name => 'nginx', ensure => 'running', enable => true, hasrestart => true, } }We also want to use this service to trigger a restart when our `config` file changes, hence the use of the `hasrestart` attribute.Save and exit the `service.pp` file.Let's also update our `config` class to trigger this restart with the `notify` metaparameter:# $EDITOR manifests/config.ppclass nginx::config { file { 'nginx_config': path => '/etc/nginx/nginx.conf', source => 'puppet:///modules/nginx/rh-nginx.conf', ensure => 'present', notify => Service['nginx_service'], } }Notice how, when referencing our `service` class, we use a `Service` with a capital `S`. This differentiates it from the `service` resource type.Save and exit.### init.pp Now we want to update our `init.pp` to `contain` the two new classes we created. We also want to make sure they are run in the correct order. We do this by using the `Class` reference (notice the capital `C`; this works the same as `Service` and is referencing the name of a class, not the `class` function itself) and "chaining" the existing classes together to define the order in which they are run. These are called "chaining arrows," with the `->` being an ordering arrow where the resource to the "right" of the arrow is always run after the left, and `~>` being a notifying arrow, which only runs the resource to the right of the arrow should the left-resource provoke a change.class nginx { contain nginx::install contain nginx::config contain nginx::serviceClass['nginx::install'] -> Class['nginx::config'] ~> Class['nginx::service'] }Save and exit.Run the parser against the new and updated files:puppet parser validate init.pp service.pp config.ppOn the CentOS 7 agent node, perform a Puppet run:puppet agent -t

Facter and the params.pp File


Lesson Description:

There is a bit of discord in the Puppet community as to whether we should use Hiera, the Puppet key-value store for all variables, or whether Hiera should be used alongside a `params.pp` file to store OS-related configurations. Regardless of where anyone stands on this, however, you're going to run into a `params.pp` file at some point in your Puppet use. So for this module, we'll be containing our OS-specific values in a `params.pp`, then use Hiera for our virtual hosts configuration.We can create these OS-specific parameters because Puppet uses a cross-platform system profiling library called Facter. It records "facts" about a system, such as operating system family and kernel version, and reports it back to the Puppet Master so that the master has a high-level overview of all its nodes.For example, if we want to view a full list of facts about our system, we can just run:# facterOr we could be more specific:# facter params.ppBut how can we use these in our module? Well, right now our `nginx` module only works on Red Hat-based servers, like our CentOS 7 server. We want to play nice with Debian-based servers too, since we can run these against our Puppet Master. To do this, we're going to add a `params.pp` file. From the `nginx` module directory, run:# pdk new class paramsOpen the file. We're going to break down our variables by our existing manifests:`install.pp`:+ `package_name``config.pp`:+ `config_path` + `config_source``service`:+ `service_name`Note how the first word is always the same as the file name. That makes it easier for future users of our module to see where these are used!Two of these, the `package_name` and `service_name` variables, are the same for both distros and will be provided as a default. The `config` parameters will be separated by operating system. Values such as whether or not we want the `nginx` package present or enabled will be added in Hiera, since those are generally determined by the node's purpose, not its operating system.To add the default configurations, we just need to add these values as variables to our `params` class skeleton:class nginx::params { $package_name = 'nginx' $service_name = 'nginx' }Then, for our distro-specific values, we can add a `case` statement, to ensure they are only used for the correct distro:case $::osfamily { 'RedHat': { $config_path = '/etc/nginx/nginx.conf' $config_source = 'puppet:///modules/nginx/rh-nginx.conf' } 'Debian': { $config_path = '/etc/nginx/nginx.conf' $config_source = 'puppet:///modules/nginx/deb-nginx.conf' } }Now we need to reference these variables in the manifest files themselves:`init.pp`class nginx ( $package_name = $nginx::params::package_name, $config_path = $nginx::params::config_path, $config_source = $nginx::params::config_source, $service_name = $nginx::params::service_name, ) inherits nginx::params { contain nginx::install contain nginx::config contain nginx::service `config.pp`class nginx::config ( $config_path = $nginx::params::config_path, $config_source = $nginx::params::config_source, ) inherits nginx::params { file { 'nginx_config': path => $config_path, source => $config_source, ensure => 'present', notify => Service['nginx_service'], }`install.pp`class nginx::install( $package_name = $nginx::params::package_name, ) inherits nginx::params { package { 'install_nginx': name => $package_name, ensure => 'present', }`service.pp`class nginx::service ( $service_name = $nginx::params::service_name, ) inherits nginx::params { service { 'nginx_service': name => $service_name, ensure => 'running', enable => true, hasrestart => true, }### Test the ChangesOn the CentOS 7 Puppet agent, run:# puppet agent -tNo changes should be made; however, we should receive no errors either.We now want to test on a Debian-based machine. Lucky for us, the Puppet Master also manages itself, and it uses Ubuntu. Drop back down to the `production` folder, then open the `manifests/site.pp` file. Add an entry for your Puppet Master, including the `nginx` module:node { class { 'nginx': } }Save and exit.On the master, force a Puppet run:# puppet agent -tTo confirm that everything works, check `nginx`. Ensure the "managed by Puppet" header is in the `/etc/nginx/nginx.conf` file, and make sure the `nginx` daemon is running:# which nginx # head /etc/nginx/nginx.conf # systemctl status nginx


Hiera: Module Data


Lesson Description:

At this point, we've separated out our OS-family-specific variables, but still have bits of our manifests that we need to parameterize out. In an ideal world, every value would be able to be overridden. But since every value isn't affected by the operating system of our servers, we have another data store to work with: Hiera. Hiera lets us store key-value pairs in a hierarchy of directories.We'll start by parameterizing our existing manifest further, storing those values in our _module's_ `data/common.yaml` file. Later in the video, we'll create a `vhosts` class where we'll store our Hiera data in the _production environment's_ `data` directory. There, we can supply Hiera data on a per-node basis, since we might not want the same website virtual hosts for every node, or if we're using `nginx` to support a web frontend to some other application.### common.yamlHiera is configured in a multitude of places: There's an architectural-wide configuration file at `/etc/puppetlabs/puppet/hiera.yaml`, one within the environment itself (for `production`: `/etc/puppetlabs/code/environments/production/hiera.yaml`), then one for each module using Hiera (for `nginx`: `/etc/puppetlabs/code/environments/production/modules/nginx/hiera.yaml`). As with much of this course, we're going to focus on the environment and module files because the overall default configuration for Hiera doesn't need to be changed for our purposes.Let's start small, with our module data. Open up or `cat` out the `hiera.yaml` file from the `nginx` module directory:--- version: 5defaults: # Used for any hierarchy level that omits these keys. datadir: data # This path is relative to hiera.yaml's directory. data_hash: yaml_data # Use the built-in YAML backend.hierarchy: - name: 'common' path: 'common.yaml'Under `defaults`, we have the `datadir` setting: This just tells Hiera that for this module, the Hiera data is under `data`. Hiera reads this from the directory the `hiera.yaml` file is located in, so in this case the full path to our `datadir` is:/etc/puppetlabs/code/environments/production/modules/nginx/dataFrom here, we can see that Hiera will specifically look for our `common` hierarchy, located at `common.yaml` in the `datadir`. Let's close this file and open `common.yaml`, which the Puppet Dev Kit generated for us.# $EDITOR data/common.yamlRight now, it only contains the three dashes that signify the start of a YAML file. What we want to do is add variables and values for the rest of our existing attributes, which are as follows:`install.pp`:+ `package_ensure``config.pp`+ `config_ensure``service.pp`+ `service_ensure` + `service_enable` + `service_hasrestart`We just have to use simple key-value pairs to add these to Hiera:--- nginx::package_ensure: 'present' nginx::config_ensure: 'present' nginx::service_ensure: 'running' nginx::service_enable: true nginx::service_hasrestart: trueThen we need to update our `init.pp` so that it calls these values. Now, Hiera has one benefit to `params.pp` in that we only have to specify our variables at the top of the class here, not in each file. We do still have to update our hardcoded attributes though!When we define our variables in the `init.pp` file, we also want to supply the data type it should be expecting:class nginx ( $package_name = $nginx::params::package_name, $config_path = $nginx::params::config_path, $config_source = $nginx::params::config_source, $service_name = $nginx::params::service_name, String $package_ensure, String $config_ensure, String $service_ensure, Boolean $service_enable, Boolean $service_hasrestart, ) inherits nginx::params {We now want to update our other manifests to call our variables:`config.pp`:class nginx::config ( $config_path = $nginx::params::config_path, $config_source = $nginx::params::config_source, ) inherits nginx::params { file { 'nginx_config': path => $config_path, source => $config_source, ensure => $nginx::config_ensure, notify => Service['nginx_service'], } }`install.pp`class nginx::install( $package_name = $nginx::params::package_name, ) inherits nginx::params { package { 'install_nginx': name => $package_name, ensure => $nginx::package_ensure, } }`service.pp`:class nginx::service ( $service_name = $nginx::params::service_name, ) inherits nginx::params { service { 'nginx_service': name => $service_name, ensure => $nginx::service_ensure, enable => $nginx::service_enable, hasrestart => $nginx::service_hasrestart, } }When we're finished, let's reopen our `data/common.yaml` file and make one change to the `package_ensure` variable, to test our Hiera. Switch it from `present` to `purged`:nginx::package_ensure: 'present'Save and exit, then perform a Puppet run on the agent node:# puppet agent -tWe'll definitely get some errors, but when we run `which nginx`, we can see that the `nginx` package as been removed from our server.On the Master, switch `package_ensure` back to `present`, then rerun the `puppet agent -t` command on the agent:# puppet agent -tEverything installs, configures, and starts correctly! We can see that Hiera is up and working with our module!

Hiera: Node Data


Lesson Description:

Now we're going to see how we can move down the hierarchy and provide node-specific values for a module. For this, we're going create a `vhosts.pp` file. Use `pdk` to generate a new class from the `nginx` directory:# pdk new class vhostsOpen the `manifests/vhosts.pp` file that was just generated. We're not using any new resource types here, so feel free to copy and paste this in:# Generate a virtual hosts file for nginx. Requires node-specific Hiera data # # @summary Generate a virtual hosts file for nginx # # @example # include nginx::vhosts class nginx::vhosts ( $vhosts_dir = $nginx::params::vhosts_dir, ) inherits nginx::params { file { "${nginx::vhosts_name}.conf": content => epp('nginx/vhosts.conf.epp'), ensure => $nginx::vhosts_ensure, path => "${vhosts_dir}/${nginx::vhosts_name}.conf", }file { "$nginx::vhosts_root": ensure => 'directory', }Notice how we have one parameter to add to `params.pp`: the `vhosts_dir` value. We also have a couple of Hiera values in `nginx::vhosts_name` and `vhosts_ensure`. Pay attention, too, to how variables are provided when they're in larger double-quoted strings: instead of using `$nginx::vhosts_server` we use `${nginx::vhosts_server}`.What we want to focus on right now, however, is the `content => epp('nginx/vhosts.conf.epp')` line. What this does is populate a new file based on the given Puppet template, `epp('nginx/vhosts.conf.epp')`, which we need to add to the `templates` directory of our `nginx` module. Let's go ahead and do that:# $EDITOR templates/vhosts.conf.eppFrom here, we can copy in the virtual hosts template:# This file is managed by Puppet; do not make changes by hand server { listen ; listen [::]:;root ; server_name www.; } }This is an `.epp` file, which means it uses Puppet's templating language. If you're familiar with Ruby, you also have the option to create an `.erb` file, but that is outside the scope of this quick start.Notice how we don't have to do anything special to reference what will be our future Hiera values at the start of the file. We just need to call them with the `` format.Save and exit when done.We now want to go ahead and update our `manifests/init.pp`, `manifests/params.pp`, and `data/common.yaml` files:`init.pp`:... $vhosts_dir = $nginx::params::vhosts_dir, String $package_ensure, String $config_ensure, String $service_ensure, Boolean $service_enable, Boolean $service_hasrestart, String $vhosts_port, String $vhosts_root, String $vhosts_name, ) inherits nginx::params { ... `params.pp`:'RedHat': { $config_path = '/etc/nginx/nginx.conf' $config_source = 'puppet:///modules/nginx/rh-nginx.conf' $vhosts_dir = '/etc/nginx/conf.d/' } 'Debian': { $config_path = '/etc/nginx/nginx.conf' $config_source = 'puppet:///modules/nginx/deb-nginx.conf' $vhosts_dir = '/etc/nginx/sites-available/' }`common.yaml`:--- nginx::package_ensure: 'present' nginx::config_ensure: 'present' nginx::service_ensure: 'running' nginx::service_enable: true nginx::service_hasrestart: true nginx::vhosts_ensure: 'present'You may have noticed that we're still missing some Hiera data. But that's because we're going to be dropping back down into our `production` environment directory, and adding our Hiera data to a node-specific configuration. Move back down to `production`:# cd ../..Let's first check out our `heira.yaml` file in this directory:# cat hiera.yamlLike our `nginx/hiera.yaml` file, we have default `data` directory in the same directory as this file. If we were to use `ls` here we would see it. However, what we want to focus on is these lines:- name: "Per-node data (yaml version)" path: "nodes/%{::trusted.certname}.yaml"These define where we'll be placing our per-node data, especially in the `data/nodes/` directory in our `production` environment. The name of the file should be the trusted certname that Puppet uses to talk to the server, ending in `.yaml`. The `hiera.yaml` file defines this using the `trusted.certname` fact, referenced in the `path`.Let's go ahead and add these files:# mkdir data/nodes # $EDITOR data/nodes/ here, we can add the Hiera values that aren't relevant to our `common.yaml` file, because they would be specific to the node or role:---nginx::vhosts_port: '80' nginx::vhosts_root: '/var/www' nginx::vhosts_name: '' nginx::vhosts_ensure: 'present'When we apply this module, the `nginx::vhosts_ensure` variable will take this value, not the one in the `common.yaml` file, although at this point they are the same.Save and exit. We can now force a Puppet run on the agent node:# puppet agent -tLooking for some added challenge after this lesson? Think about how you would write an `if` state in the `vhosts` manifest so that the `nginx::vhosts` class would only be run if the `nginx::vhosts_name` value is provided.

More Puppet

The Puppet Forge


Lesson Description:

We don't need to create new modules for every thing we need to do with Puppet, however. The Puppet Forge is a repository of existing Puppet modules written by PuppetLabs and other contributors. We could even add a module, although not all modules are Puppet-approved, and there are various ways to weed out modules that might not be the most useful in most use cases.The Puppet Forge is located at []( Head over and search for `puppetdb`. PuppetDB is our next lesson, but to prepare for that we're going to install it through the use of the PuppetLabs' PuppetDB module on the Forge.Once on the search page, take a look at the sidebar **Guide to module badges**. This is how we can tell the quality of a module. There are five badges. Three are quality badges:+ **Supported** means it works on Puppet Enterprise and is maintained by Puppet. + **Partner** means it works on Puppet Enterprise and is maintained by a Puppet partner. + **Approved** means that Puppet has reviewed the module and found it meets their standards, but it is not maintained by Puppet or a supported partner.And two are support badges:+ **Tasks** means the module works with Tasks, which is part of Puppet Bolt; it's an automation tool. + **PDK** means the module works with Puppet Development Kit validation and testing.Let's ago head and click on the **PuppetDB** module. Here we have download instructions and information on using the module. Any specific directions for using the module would be here, such as Hiera data we need to add, or changes to any manifests to achieve specific behavior. Luckily for us, we don't have to do anything special to add the PuppetDB module.On the PuppetDB page, we're presented with two installation options. The `Puppetfile` method is related to Puppet Enterprise. Since we're using Puppet Open Source to get started, we'll be downloading the module with the `puppet module` command.Let's switch back to the command line and drop to `root` with `sudo -i`, if you are not there already.The `puppet module` command allows us to search for, install, uninstall, upgrade, and list modules. In previous versions of Puppet, it was also the command used to generate the skeleton of a new module, but that behavior has been deprecated in favor of the Puppet Dev Kit.Let's go ahead and install PuppetDB.# puppet module install puppetlabs-puppetdbNow, move into the `modules` directory for the `production` environment and list the contents of the directory:# ls apt inifile puppetdb concat nginx stdlib firewall postgresql translateNot just the PuppetDB module installed! That's because when we installed a module from the Puppet Forge, all its dependent modules are installed along with it.Since we don't need to make changes to the module itself for it work, all we have to do to install PuppetDB on its desired server is to add it to our `site.pp` file. Return to the `production` directory and open the site mapping:# $EDITOR manifests/site.ppGenerally, PuppetDB would be hosted on its own server, with the option to put the Postgresql database on a third, separate server. However, for test and smaller environments, like ours, we can install it alongside our Puppet Server:node { # Configure puppetdb and its underlying database class { 'puppetdb': } # Configure the Puppet master to use puppetdb class { 'puppetdb::master::config': } }Note that this was taken straight from the PuppetDB instructions on the Puppet Forge. There's no extra work beyond updating the node name!We can now force a Puppet run with:# puppet agent -t> Note that you may need to run this command twice; occasionally, the PostgreSQL database takes too long to respond and the PuppetDB install will not complete. Running the command a second time should solve any of these issues.Check that PuppetDB is up and running:# systemctl status puppetdb#### Using PuppetDBNow, an in-depth look of PuppetDB is out of line with the goals of a quick start, but that doesn't mean we can't give you a place to start. Currently, we can see that PuppetDB is gathering information about our agent node by running:# puppet node status AGENTNODE.mylabserver.comWe can take this a step further by providing additional information to PuppetDB by adding exported resources to our modules. Exported resources let us take PuppetDB information and send it to tools such as Nagios.Additionally, should we make the jump to Puppet Enterprise, PuppetDB has a robust client tool available for use that makes it easier to export and anonymize our PuppetDB data.

Next Steps


Lesson Description:

We've kick started your Puppet skills, but what's next? Well, you have plenty of options. If you want to stick with Puppet, Linux Academy has a Puppet Certification course, or you could further expand your Puppet knowledge by writing more detailed modules. You can even expand out of the `nginx` module we worked on! Here are some suggestions:+ Update the virtual hosts class so multiple vhosts files are generated. + Change the static configuration files into one template that can work across all distros (Hint: you'll need to parameterize the `nginx` user). + Look into using tasks to run scripts on nodes. + Take it a step further! If you're already using a testing solution like Test Kitchen, see if you can get your Puppet modules to work alongside it.Or maybe you're thinking Puppet isn't the configuration management solution you're looking for. In that case, check out some of our other DevOps solutions here:Salt:+ Using Salt for Configuration Management and Orchestration + SaltStack Certified EngineerChef:+ Basic Chef Fluency + Chef Local Cookbook Development + Extending ChefAnsible:+ Ansible Quick Start + Using Ansible for Configuration Management and Orchestration + RHCS in Ansible Automation

Hands-on Labs are real live environments that put you in a real scenario to practice what you have learned without any other extra charge or account to manage.


Take this course and learn a new skill today.

Transform your learning with our all access plan.

Start 7-Day Free Trial