Puppet:Mastering Infrastructure Automation
上QQ阅读APP看书,第一时间看更新

An overview of Puppet's modules

A module can be seen as a higher-order organizational unit. It bundles up classes and defined types that contribute to a common management goal (specific system aspects or a piece of software, for example). These manifests are not all that is organized through modules; most modules also bundle files and file templates. There can also be several kinds of Puppet plugins in a module. This section will explain these different parts of a module and show you where they are located. You will also learn about the means of module documentation and how to obtain existing modules for your own use.

Parts of a module

For most modules, manifests form the most important part - the core functionality. The manifests consist of classes and defined types, which all share a namespace, rooted at the module name. For example, an ntp module will contain only classes and defines whose names start with the ntp:: prefix.

Many modules contain files that can be synced to the agent's filesystem. This is often used for configuration files or snippets. You have seen examples of this, but let's repeat them. A frequent occurrence in many manifests is file resources such as the following:

file { '/etc/ntp.conf': 
  source => 'puppet:///modules/ntp/ntp.conf', 
} 

The above resource references a file that ships with a hypothetical ntp module. It has been prepared to provide generally suitable configuration data. However, there is often a need to tweak some parameters inside such a file, so that the node manifests can declare customized config settings for the respective agent. The tool of choice for this is templates, which will be discussed in the next chapter.

Another possible component of a module that you have already read about is Custom Facts—code that gets synchronized to the agent and runs before a catalog is requested, so that the output becomes available as facts about the agent system.

These facts are not the only Puppet plugins that can be shipped with modules. There are also parser functions (also called custom functions), for one. These are actual functions that you can use in your manifests. In many situations, they are the most convenient way, if not the only way, to build some specific implementations.

The final plugin type that has also been hinted at in an earlier chapter is the custom native types and providers, which are conveniently placed in modules as well.

Module structure

All the mentioned components need to be located in specific filesystem locations for the master to pick them up. Each module forms a directory tree. Its root is named after the module itself. For example, the ntp module is stored in a directory called ntp/.

All manifests are stored in a subdirectory called manifests/. Each class and defined type has its own respective file. The ntp::package class will be found in manifests/package.pp, and the defined type called ntp::monitoring::nagios will be found in manifests/monitoring/nagios.pp. The first particle of the container name (ntp) is always the module name, and the rest describes the location under manifests/. You can refer to the module tree in the following paragraphs for more examples.

The manifests/init.pp file is special. It can be thought of as a default manifest location, because it is looked up for any definition from the module in question. Both the examples that were just mentioned can be put into init.pp and will still work. Doing this makes it harder to locate the definitions, though.

In practice, init.pp should only hold one class, which is named after the module (such as the ntp class), if your module implements such a class. This is a common practice, as it allows the manifests to use a simple statement to tap the core functionality of the module:

include ntp

You can refer to the Modules' best practices section for some more notes on this subject.

The files and templates that a module serves to the agents are not this strictly sorted into specific locations. It is only important that they be placed in the files/ and templates/ subdirectories, respectively. The contents of these subtrees can be structured to the module author's liking, and the manifest must reference them correctly. Static files should always be addressed through URLs, such as these:

puppet:///modules/ntp/ntp.conf
puppet:///modules/my_app/opt/scripts/find_my_app.sh

These files are found in the corresponding subdirectories of files/:

.../modules/ntp/files/ntp.conf
.../modules/my_app/files/opt/scripts/find_my_app.sh

The modules prefix in the URI is mandatory and is always followed by the module name. The rest of the path translates directly to the contents of the files/ directory. There are similar rules for templates. You can refer to Chapter 6, Leveraging the Full Toolset of the Language, for the details.

Finally, all plugins are located in the lib/ subtree. Custom facts are Ruby files in lib/facter/. Parser functions are stored in lib/puppet/parser/functions/, and for custom resource types and providers, there is lib/puppet/type/ and lib/puppet/provider/, respectively. This is not a coincidence; these Ruby libraries are looked up by the master and the agent in the according namespaces. There are examples for all these components later in this chapter.

In short, following are the contents of a possible module in a tree view:

/opt/puppetlabs/code/environments/production/modules/my_app
  templates      # templates are covered in the next chapter
  files
    subdir1      # puppet:///modules/my_app/subdir1/<filename>
    subdir2      # puppet:///modules/my_app/subdir2/<filename>
      subsubdir  # puppet:///modules/my_app/subdir2/subsubdir/...
  manifests
    init.pp      # class my_app             is defined here
    params.pp    # class my_app::params     is defined here
    config
      detail.pp  # my_app::config::detail   is defined here
      basics.pp  # my_app::config::basics   is defined here
  lib
    facter         # contains .rb files with custom facts
    puppet
      functions    # contains .rb files with Puppet 4 functions
      parser
        functions  # contains .rb files with parser functions
      type         # contains .rb files with custom types
      provider     # contains .rb files with custom providers

Documentation in modules

A module can and should include documentation. The Puppet master does not process any module documentation by itself. As such, it is largely up to the authors to decide how to structure the documentation of the modules that are created for their specific site only. That being said, there are some common practices and it's a good idea to adhere to them. Besides, if a module should end up being published on the Forge, appropriate documentation should be considered mandatory.

Note

The process of publishing modules is beyond the scope of this book. You can find a guide at https://docs.puppetlabs.com/puppet/latest/reference/modules_publishing.html.

For many modules, the main focus of the documentation is centered on the README file, which is located right in the module's root directory. It is customarily formatted in Markdown as README.md or README.markdown. The README file should contain explanations, and often, there is a reference documentation as well.

Puppet DSL interfaces can also be documented right in the manifest, in the rdoc and YARD format. This applies to classes and defined types:

# Class: my_app::firewall
#
# This class adds firewall rules to allow access to my_app.
#
# Parameters: none
class my_app::firewall {
  # class code here
}

You can generate HTML documentation (including navigation) for all your modules using the puppet doc subcommand. This practice is somewhat obscure, so it won't be discussed here in great detail. However, if this option is attractive to you, we encourage you to peruse the documentation.

The following command is a good starting point:

puppet help doc

Another useful resource is the puppetlabs-strings module (https://forge.puppetlabs.com/puppetlabs/strings), which will eventually supersede puppet doc.

Plugins are documented right in their Ruby code. There are examples for this in the following sections.