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.





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: https://interactive.linuxacademy.com/diagrams/ThePuppetProject.html


Puppet Quick Start

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 Repository Puppet maintains its own repositories for all supported Puppet Server distributions, including: Red Hat Enterprise Linux and derived distrosDebianFedoraUbuntu To add the Puppet repository in Ubuntu, use: # wget https://apt.puppetlabs.com/puppet6-release-bionic.deb # dpkg -i puppet6-release-bionic.deb # apt update If 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 Package Before 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: <LABSERVERID>.mylabserver.com localhost puppet You may also want to add puppet as a hostname. Install the Puppet Server: # apt-get install puppetserver Configure Puppet Server While 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 = <LABSERVERID>.mylabserver.com [master] certname = <LABSERVERID>.mylabserver.com vardir = /opt/puppetlabs/server/data/puppetserver logdir = /var/log/puppetlabs/puppetserver rundir = /var/run/puppetlabs/puppetserver pidfile = /var/run/puppetlabs/puppetserver/puppetserver.pid codedir = /etc/puppetlabs/code For Small Servers Only If 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 Authority Puppet 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 Server We can now start the Puppet Server (and enable it to start when the system boots) with: # systemctl start puppetserver # systemctl enable puppetserver To 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:

With 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 Server (Optional) We do not change the hostname in the course, but such tasks should be completed up-front if desired. If you want to use a different hostname for the server than the one provided, set this now. For example, if I wanted to change this server to be web01: # sudo hostnamectl set-hostname web01 We also 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 ellejaclyn2c.mylabserver.com puppet LABSERVERID.mylabserver.com Add Puppet Repositories We now want to add the Puppet 6.0 repository for RHEL-based distros: # sudo rpm -Uvh https://yum.puppet.com/puppet6/puppet6-release-el-7.noarch.rpm Next, run a yum update to enable the repository for use: # sudo yum update Install Puppet To install Puppet on the agent server, all we have to do is run: # sudo yum install puppet-agent Then start up the service: # sudo systemctl start puppet # sudo systemctl enable puppet Configure Puppet The Puppet agent will automatically look for the server with the hostname puppet, so we actually have no configuration changes to make; if we wanted to change this, we would 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.com We could also use the puppet-config command-line utility: # sudo /opt/puppetlabs/bin/puppet config set server LABSERVERID.mylabserver.com Approve Agent With our agent ready, we can now go ahead and confirm the certificate on the Puppet Server. Before we log in to the Server, however, let's output the agent's certificate fingerprint: # sudo puppet agent --fingerprint Now, on the Puppet Server, let's see what we have to accept: # puppetserver ca list If the fingerprints match, we can approve the server: # puppetserver ca sign --certname AGENTSERVERID.mylabserver.com

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. Environments By 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/production Within 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-boxhiera.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 lessonmanifests: 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 againstmodules: Directory where we'll write and store our end-state configuration file Modules Let's move into the modules directory: # cd modules If 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 pdk When the installation is complete, we can then generate our new module with the pdk new module command: # pdk new module nginx We'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 https://spdx.org/licenses/. 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 Linux Metadata will be generated based on this information, continue? Yes Since 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 CHANGELOG.md README.md data hiera.yaml spec Gemfile Rakefile examples manifests tasks Gemfile.lock appveyor.yml files metadata.json templates That'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 informationexamples: Stores examples on how to use the module's classes and typesfiles: Stores static files for nodesmanifests: Stores our manifests, the files that build out our modulespec: Spec tests for any plug-instasks: Contains any Puppet tasks, which allow us to provide ad-hoc commands and scripts to run on our nodestemplates: Stores any templates, which can be used to generate content Writing a Manifest As 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 install Now we can open this file in our editors and see what we just generated: # cd manifests # $EDITOR install.pp Here 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::install We 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 RubyKeep "hashrockets" between values and mappings aligned Without 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. Testing Let'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.pp If 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 AGENTSERVER.mylabserver.com { 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.pp Pull down the deb-nginx.conf and rh-nginx.conf files. We want to store these in the files directory of our nginx module. curl https://raw.githubusercontent.com/linuxacademy/ content-puppetqs-nginx/master/files/deb-nginx.conf -o deb-nginx.conf curl https://raw.githubusercontent.com/linuxacademy/ content-puppetqs-nginx/master/files/rh-nginx.conf -o rh-nginx.conf Drop back down to the nginx module directory, then create the config class: pdk new class config Open 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::config We 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.pp Finally, 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 service Update the Puppet docstring: # Manage the state of the nginx daemon # # @summary Manage the state of the nginx daemon # # @example # include nginx::service Add 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.pp class 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::service Class['::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.pp On 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 os.familyparams.pp But 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_pathconfig_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 Changes On 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 PUPPETMASTER.mylabserver.com { 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.yaml Hiera 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/niera.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: 5 defaults: # 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/data From 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.yaml Right 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_ensureservice_enableservice_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: true Then 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 -t We'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 -t Everything 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 <%= $nginx::vhosts_port %>; listen [::]:<%= $nginx::vhosts_port %>; root <%= $nginx::vhosts_root %>; server_name <%= $nginx::vhosts_name %> www.<%= $nginx::vhosts_name %>; } }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 <%= $variable_name %> 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/AGENTLABSERVER.mylabserver.com.yamlFrom 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: 'the-puppet-project.com' 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 forge.puppet.com. 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-puppetdb Now, 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 translate Not 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.pp Generally, 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 PUPPETSERVER.mylabserver.com { # 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 PuppetDB Now, 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.com We 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 OrchestrationSaltStack Certified Engineer Chef: Basic Chef FluencyChef Local Cookbook DevelopmentExtending Chef Ansible: Ansible Quick StartUsing Ansible for Configuration Management and OrchestrationRHCS 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.