Skip to content
This repository has been archived by the owner on Jul 13, 2023. It is now read-only.

Latest commit

 

History

History
384 lines (288 loc) · 16.1 KB

roles_and_profiles.md

File metadata and controls

384 lines (288 loc) · 16.1 KB

{% include '/version.md' %}

Roles and profiles

Quest objectives

  • Understand the roles and profiles pattern.
  • Create profile classes that wrap component classes and set parameters specific to your site infrastructure.
  • Create role classes to define the full configuration for a system through a combination of profile classes.
  • Learn how to use regular expressions to create more flexible node definitions.

Getting started

In the last quest, we shifted from managing the Pasture application on a single system to distributing its components across the pasture-app.puppet.vm and pasture-db.puppet.vm hosts. The application server and database server you configured each play a different role in your infrastructure and have a different classification in Puppet.

As your Puppetized infrastructure grows in scale and complexity, you'll need to manage more and more kinds of systems. Defining all the classes and parameters for these systems directly in your site.pp manifest doesn't scale well. The roles and profiles pattern gives you a consistent and modular way to define how the components provided in your Puppet modules come together to define each different kind of system you need to manage.

The concepts introduced in this quest mark an important transition in your progress from learning Puppet basics to understanding how to build these out into the higher level abstractions suitable for managing production infrastructure. This also means a shift in focus from the well-defined aspects of Puppet tools and syntax to more open-ended questions about what you should do with them.

The roles and profiles pattern given in this quest has been successfully adopted by some of Puppet's largest customers and is used internally to manage our own infrastructure. It is an example of how higher-level abstractions in your Puppet code can greatly simplify the task of managing configuration across a great number and variety of systems while maintaining a concise and expressive base of Puppet code. You may find that this kind of abstraction can be better achieved for your particular needs by modifying or abandoning the roles and profiles pattern. Even if this is your ultimate conclusion, however, your divergences should be well reasoned and based on a strong understanding of the high-level abstraction that the roles and profiles pattern provides.

When you're ready to get started, enter the following command:

quest begin roles_and_profiles

What are roles and profiles?

The roles and profiles pattern we cover in this quest isn't a new tool or feature in the Puppet ecosystem. Rather, it's a way of using the tools we've already introduced to create something you can maintain and expand as your Puppetized infrastructure evolves.

The explanation of roles and profiles begins with what we call component modules. Component modules—like your Pasture module and the PostgreSQL module you downloaded from the Forge—are designed to configure a specific piece of technology on a system. The classes these modules provide are written to be flexible. Their parameters provide an API you can use to specify precisely how you need the component technology to be configured.

The roles and profiles pattern gives you a consistent way to organize these component modules according to the specific applications in your infrastructure.

A profile is a class that declares one or more related component modules and sets their parameters as needed. The set of profiles on a system defines and configures the technology stack it needs to fulfill its business role.

A role is a class that combines one or more profiles to define the desired state for a whole system. A role should correspond to the business purpose of a server. If your CTO asks what a system is for, the role should fit that high-level answer: something like "a database server for the Pasture application." A role itself should only compose profiles and set their parameters—it should not have any parameters itself.

Writing profiles

Using roles and profiles is a design pattern, not something written into the Puppet source code. As far as the Puppet parser is concerned, the classes that define your roles and profiles are no different than any other class.

Task 1:

To get started setting up your profiles, create a new profile module directory in your modulepath:

mkdir -p profile/manifests

We'll begin by creating a pair of profiles related to the Pasture application. The profile for the application server will use a conditional statement to manage two different configurations: a 'large' deployment that connects to an external database, and a 'small' deployment that only provides the basic 'cowsay' endpoint. A second profile will manage the PostgreSQL database that backs the 'large' instance of the app server.

To make it clear that all of these profiles relate to the Pasture application, we'll place them in a pasture subdirectory within profile/manifests.

Create that subdirectory.

mkdir profile/manifests/pasture

Task 2:

Next, create a profile for the Pasture application.

vim profile/manifests/pasture/app.pp

Here, you'll define the profile::pasture::app class.

The quest tool created a pasture-app-small.puppet.vm and pasture-app-large.puppet.vm node for this quest, so we can determine the appropriate profile based on the node name. We'll use a conditional statement to set the $default_character and $db parameters based on whether the node's fqdn fact contains the string 'large' or 'small'. We'll also add an else block to fail with an appropriate error message if the fqdn variable doesn't match 'small' or 'large'.

class profile::pasture::app {
  if $facts['fqdn'] =~ 'large' {
    $default_character = 'elephant'
    $db                = 'postgres://pasture:m00m00@pasture-db.puppet.vm/pasture'
  } elsif $facts['fqdn'] =~ 'small' {
    $default_character = 'cow'
    $db                = 'none'
  } else {
    fail("The ${facts['fqdn']} node name must match 'large' or 'small'.")
  }
  class { 'pasture':
    default_message   => 'Hello Puppet!',
    sinatra_server    => 'thin',
    default_character => $default_character,
    db                => $db,
  }
}

Task 3:

Next, create a profile for the Pasture database using the pasture::db component class.

vim profile/manifests/pasture/db.pp

You don't need to customize any parameters here, so you can use the include syntax to declare the pasture::db class.

class profile::pasture::db {
  include pasture::db
}

While these profiles define the configuration of components directly related to the Pasture application, you'll typically need to manage aspects of these systems that aren't directly related to their business role. A profile module may include profile classes to manage things like user accounts, DHCP configuration, firewall rules, and NTP. Because these classes are applied across many or all of the systems in your infrastructure, the convention is to keep them in a base subdirectory. To give an example of a base profile, we'll create a profile::base::motd profile class to wrap the motd component class you created earlier.

Task 4:

Create a base subdirectory in your profile module's manifests directory.

mkdir profile/manifests/base

Next, create a manifest to define your profile::base::motd profile.

vim profile/manifests/base/motd.pp

Like the profile::pasture::db profile class, the profile::base::motd class is a wrapper class with an include statement for the motd class.

class profile::base::motd {
  include motd
}

Writing these wrapper classes may initially seem like unnecessary complexity, especially in the case of simple component classes like motd and pasture::db. The value of consistency, however, outweighs the effort of creating these wrapper classes.

A profile class is the single source of truth for how a component is configured in your site infrastructure. If you ever decide to add parameters to your MOTD module, for example, you can easily set those parameters in a single profile class and see the changes take effect across any nodes whose role includes that profile. If you had set parameters specifically on a role or node level, on the other hand, any changes would have to be duplicated across every system.

Now you have a few profiles available. Each of these profiles defines how a component on a system should be configured. The next step is to combine them into roles.

Writing roles

A role combines profiles to define the full set of components you want Puppet to manage on a system. A role should consist of only include statements to pull in the list of profile classes that make up the role. A role should not directly declare non-profile classes or individual resources.

A role's name should be a simple description of the business purpose of the system it describes. Specific implementation details related to the technology stack are left to the profiles. For example, role::myapp_webserver and role::myapp_database are appropriate names for role classes, while role::postgres_db or role::apache_server are not.

Task 5:

In this case, we need two roles to define the systems involved in the Pasture application: role::pasture_app and role::pasture_db. First, create the directory structure for your role module:

mkdir -p role/manifests

Create a manifest to define your role::pasture_app role.

vim role/manifests/pasture_app.pp
class role::pasture_app {
  include profile::pasture::app
  include profile::base::motd
}

Next, create a role for your database server.

vim role/manifests/pasture_db.pp
class role::pasture_db {
  include profile::pasture::db
  include profile::base::motd
}

Classification

With your roles clearly defined, classification is very simple. Each node in your infrastructure can be classified with a single role class.

We can make use of another feature of Puppet's node definition syntax to make our classification model even more concise. So far, you've used simple strings as titles for the node definition blocks in your site.pp manifest. Instead of a string, you can use a regular expression as a node definition's title. This lets you apply the node definition to any node whose title matches the pattern the regular expression specifies.

We'll use the following regular expression to match the two nodes we want to classify as Pasture application servers: /^pasture-app/. The / characters here are delimiters that indicate that we're using a regular expression rather than an ordinary string. The ^ character marks the beginning of the string. The rest of the characters are the ones we want to match in our node name.

If you would like to learn more about regular expressions, you can use rubular.com as a quick reference and testing tool. You might test the regular expression given above against your node names to validate the match. You can also refer to regular-expressions.info for a more in-depth guide on regular expressions. Generally, the kinds of regular expressions needed for node definitions are quite simple.

Be aware that there are some special characters in regular expressions that you will need to escape if you want to use them as literal characters to match in your node name. For example, the dot (.) is a special character that will match any single character. If you want to match a name that includes a dot or any other special character, you will need to escape it with a backslash (\.). If the regular expressions in your node definitions start getting overly complex, it may be a sign that you need to revisit your node naming scheme or look into an alternate classification method such as the PE console node classifier or an external node classifier.

Task 6:

To get started creating your node definitions, open your site.pp manifest:

vim /etc/puppetlabs/code/environments/production/manifests/site.pp

Create a node definition block for your application and database nodes.

Remember, a node definition block can take a regular expression as its title. You want any node whose host name begins with pasture-app to be classified with the role::pasture_app role, so create a node definition block with the title /^pasture-app/ and include the role::pasture_app role for matching nodes. Similarly, any node whose name matches pasture-db should be classified with the role::pasture_db role, so add a node definition with the title /^pasture-db/ that includes this role.

Now that you've implemented the roles and profiles pattern, remove the previous node definitions for the pasture-app.puppet.vm and pasture-db.puppet.vm nodes.

When you're done, the section of your site.pp after the initial comments should look like the following.

node default {
  # This is where you can declare classes for all nodes.
  # Example:
  #   class { 'my_class': }
}

node /^pasture-app/ {
  include role::pasture_app
}

node /^pasture-db/ {
  include role::pasture_db
}

Task 7:

Use the puppet job tool to trigger a Puppet agent run on the pasture-db.puppet.vm node. (If your access token has expired, run puppet access login learning --lifetime 1d and supply the password puppet to generate a new token.)

puppet job run --nodes pasture-db.puppet.vm

When this run is complete, trigger runs on your two application server nodes.

puppet job run --nodes pasture-app-small.puppet.vm,pasture-app-large.puppet.vm

Once these Puppet runs have completed, you can use the curl command to test the functionality of your application on the two different servers.

curl 'pasture-app-small.puppet.vm/api/v1/cowsay?message="hello"'
curl 'pasture-app-large.puppet.vm/api/v1/cowsay?message="HELLO!"'

Note that the database functionality we introduced in the previous quest will work on the 'large' instance of our application. The 'small' instance will return a 501 error:

curl 'pasture-app-small.puppet.vm/api/v1/cowsay/sayings'

Review

In this quest, we introduced the roles and profiles pattern, a way to organize the functionality provided by your component modules according to the role of each system in your infrastructure.

You created profile classes to wrap your component modules and specify their parameters, then brought these profile classes together into roles.

Finally, you used regular expressions in your site.pp manifest to create flexible node definitions that assign the correct role to each system in your infrastructure.

Additional Resources

  • Read more about Roles and Profiles at our docs page.
  • Have a look at the blog post that helped establish the Roles and Profiles pattern as a recommended Puppet workflow.
  • Roles and Profiles are covered in our Getting Started with Puppet and Puppet Practitioner courses. Explore our in-person and online training options for more information.
  • You can learn more about using regular expressions in node definitions on our docs page .
  • You can find a tool to easily test your regular expressions here.