Authors: Jeffrey McCune James Turnbull
Now we need to add the Puppet schema to our LDAP directory's configuration.
Caution
You may need to tweak or translate the default LDAP schema for some directory servers, but it is suitable for OpenLDAP.
The Puppet schema document is available in the Puppet source package in theext/ldap/puppet.schema
file, or you can take it from the project's Git repository athttps://github.com/puppetlabs/puppet/blob/master/ext/ldap/puppet.schema
.
We need to add it to our schema directory andslapd.conf
configuration file. For example, on an Ubuntu or Debian host, the schema directory is/etc/ldap/schema
, and theslapd.conf
configuration is located in the/etc/ldap
directory. On Red Hat, the configuration file is located in /etc/openldap and the schemas are located in /etc/openldap/schema. Copy thepuppet.schema
file into the appropriate directory, for example on Ubuntu:
$ cp puppet/ext/ldap/puppet.schema /etc/ldap/schema
Now you can add aninclude
statement to yourslapd.conf
configuration file; there should be a number of existing statements you can model:
include /etc/ldap/schema/puppet.schema
Or you can add a schema to a running OpenLDAP server, like so:
$ ldapadd -x -H ldap://ldap.example.com/ -D "cn=config" -W -f puppet.ldif
To update OpenLDAP with the new schema, you may also now need to restart your server.
# /etc/init.d/slapd restart
Now that you've added the schema and configured the LDAP server, you need to tell Puppet to use an LDAP server as the source of its node configuration.
LDAP configuration is very simple. Let's look at the required configuration options from the [master] section of the puppet.conf configuration file in
Listing 5-7
.
Listing 5-7.
LDAP configuration in Puppet
[master]
node_terminus = ldap
ldapserver = ldap.example.com
ldapbase = ou=Hosts,dc=example,dc=com
First, we set thenode_terminus
option toldap
to tell Puppet to look to an LDAP server as our node source. Next, we specify the hostname of our LDAP server, in this caseldap.example.com
, in theldapserver
option. Lastly, in theldapbase
option, we specify the base search path. Puppet recommends that hosts be stored in an OU calledHosts
under our main directory structure, but you can configure this to suit your environment.
If required, you can specify a user and password using theldapuser
andldappassword
options and override the default LDAP port of 389 with theldapport
option. There is some limited support for TLS or SSL, but only if your LDAP server does not require client-side certificates.
Tip
You can see a full list of the potential LDAP options athttp://docs.puppetlabs.com/references/stable/configuration.html
.
After configuring Puppet to use LDAP nodes, you should restart your Puppet master daemon to ensure that the new configuration is updated.
Now you need to add your node configuration to the LDAP server. Let's take a quick look at the Puppet LDAP schema in
Listing 5-9
.
Listing 5-8.
The LDAP schema
attributetype ( 1.3.6.1.4.1.34380.1.1.3.10 NAME 'puppetClass'
DESC 'Puppet Node Class'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
attributetype ( 1.3.6.1.4.1.34380.1.1.3.9 NAME 'parentNode'
DESC 'Puppet Parent Node'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.34380.1.1.3.11 NAME 'environment'
DESC 'Puppet Node Environment'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
attributetype ( 1.3.6.1.4.1.34380.1.1.3.12 NAME 'puppetVar'
DESC 'A variable setting for puppet'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
objectclass ( 1.3.6.1.4.1.34380.1.1.1.2 NAME 'puppetClient' SUP top AUXILIARY
DESC 'Puppet Client objectclass'
MAY ( puppetclass $ parentnode $ environment $ puppetvar ))
The Puppet schema is made up of an object class,puppetClient
, and four attributes:puppetclass
,parentnode
,environment
andpuppetvar
. The object classpuppetClient
is assigned to each host that is a Puppet node. Thepuppetclass
attribute contains all of the classes defined for that node. At this stage, you cannot add definitions, just classes. Theparentnode
attribute allows you to specify node inheritance,environment
specifies the environment of the node, andpuppetvar
specifies any variables assigned to the node.
In addition, any attributes defined in your LDAP node entries are available as variables to Puppet. This works much like Facter facts (see
Chapter 1
); for example, if the host entry has theipHost
class, theipHostNumber
attribute of the class is available as the variable$ipHostNumber
. You can also specify attributes with multiple values; these are created as arrays.
You can also define default nodes in the same manner as doing so in your manifest node definitions: creating a host in your directory calleddefault
. The classes assigned to this host will be applied to any node that does not match a node in the directory. If no default node exists and no matching node definition is found, Puppet will return an error.
You can now add your hosts, or the relevant object class and attributes to existing definitions for your hosts, in the LDAP directory. You can import your host definitions using LDIF files or manipulate your directory using your choice of tools such as phpldapadmin (http://phpldapadmin.sourceforge.net/wiki/index.php/Main_Page
).
Listing 5-9
is an LDIF file containing examples of node definitions.
Listing 5-9.
LDIF nodes
# LDIF Export for: ou=Hosts,dc=example,dc=com
dn: ou=Hosts,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: Hosts
dn: cn=default,ou=Hosts,dc=example,dc=com
cn: default
description: Default
objectClass: device
objectClass: top
objectClass: puppetClient
puppetclass: base
dn: cn=basenode,ou=Hosts,dc=example,dc=com
cn: basenode
description: Basenode
objectClass: device
objectClass: top
objectClass: puppetClient
puppetclass: base
dn: cn=web,ou=Hosts,dc=example,dc=com
cn: web
description: Webserver
objectClass: device
objectClass: top
objectClass: puppetClient
parentnode: basenode
puppetclass: apache
dn: cn=web1.example.com, ou=Hosts,dc=example,dc=com
cn: web1
description: webserving host
objectclass: device
objectclass: top
objectclass: puppetClient
objectclass: ipHost
parentnode: web
ipHostNumber: 192.168.1.100
This listing includes adefault
node, a node calledbasenode
, and a template node calledweb
. Each node has particular classes assigned to it, and theweb
node has thebasenode
defined as its parent node and thus inherits its classes also. Lastly, we define a client node, calledweb1
, which inherits theweb
node as a parent.
In this chapter we've explored how you can use both external node classification and the LDAP node terminus. Both of these allow you to scale to larger numbers of nodes without needing to maintain large numbers of nodes in your manifest files. In
Chapter 7
, we'll also look at how you can use Puppet Dashboard or the Foreman dashboard as an external node classifier.
The following links will take you to Puppet documentation related to external nodes:
http://docs.puppetlabs.com/guides/external_nodes.html
http://projects.puppetlabs.com/projects/puppet/wiki/Ldap_Nodes
http://docs.puppetlabs.com/references/stable/configuration.html
So far in the book, you've seen how Puppet models configuration on a single host. In many cases, however, you have configuration on multiple hosts that have a relationship; for example, your monitoring system needs to know about configuration on hosts being monitored. In this chapter we look at three features that exist in Puppet to help model resources on multiple hosts: virtual resources, exported resources, and stored configuration.
The first feature, virtual resources, is a method of managing resources where multiple configurations require a resource. For example, a user may be required on some hosts but not others. Virtual resources allow you to define a resource but be selective about where you instantiate that resource.
The second feature, exported resources, allows us to take resources defined on one host and use them on other hosts; for example, it allows us to tell a Puppet-managed load balancer about each of the workers available to it. Puppet collects and stores each of these resources when configuration runs occur, and then it provides these resources and their information to other hosts if they ask.
Lastly, stored configuration provides a mechanism to store these resources. Stored configurations allow Puppet to write resources into a SQL database. This database will then be queried by Puppet and required resources will be collected and included in the configuration catalog.
In this chapter you will learn how to use virtual and exported resources, including how to use the exported resource feature to collect specific resources from stored configuration. We cover a number of use cases, including the automatic management of SSH host keys, automated load balancer re-configuration, and automated monitoring with Nagios.
We demonstrate how to configure Puppet with a SQL server for stored configurations and how to prune old configuration data from the SQL database in order to prevent other systems from collecting stale resources. We also show you how to use message queuing to allow you to better scale your stored configuration environment and how to accommodate a multiple-Puppet-master environment, like we demonstrated in
Chapter 5
.
Virtual resources are closely related to the topic of exported resources. Because of the similarity, it's important to cover virtual resources first to provide a foundation for learning about exported resources.
Virtual resources are designed to address the situation where multiple classes require a single resource to be managed. This single resource doesn't clearly “belong” to any one class, and it is cumbersome to break each of these resources out into a unique class. Virtual resources also help solve the problem of duplicate resource declaration errors in Puppet.
To illustrate the problem, consider the Example.com operator. He would like the ability to declare user resources to manage the accounts for his colleagues, but each person should have their account
managed on only some systems. For example, all developer accounts need to be managed on all development and testing systems, while being absent from the production systems. Conversely, the system administrator accounts need to be present on every system. Finally, there are service accounts, e.g., theapache
andmysql
users and groups required by multiple Puppet classes, such as theapache
,mysql
, andwebapp
classes. Thewebapp
class requires themysql
andapache
service accounts, but should not declare the resource itself since themysql
class will likely have a conflicting resource declaration.
Virtual resources provide the ability for the Example.com operator to define a large set of user resources in once place and selectively add a smaller subset of those users to the configuration catalog. The operator doesn't need to worry about duplicate resource declarations, because the resources are only declared once and then instantiated, or “realized,” one or more times.
Declaring a virtual resource is easy, just add the@
character to the beginning of the resource declaration to make the resource virtual. You can then use one of two methods to realize your virtual resources:
Let's see how the Example.com operator might declare and realize the user and service accounts in
Listing 6-1
.
Listing 6-1.
Virtual user resources
class accounts::virtual {
@user { "mysql”:
ensure => present,
uid => 27,
gid => 27,
home => "/var/lib/mysql”,
shell => "/bin/bash”,
}
@user { "apache”:
ensure => present,
uid => 48,
gid => "apache”,
home => "/var/www”,
shell => "/sbin/nologin”,
}
}
Resources declared virtually will not be managed until they're realized. Simply declaring theaccounts::virtual
class makes these virtual resources available, but is not enough to manage themysql
andapache
user accounts.
Listing 6-2
shoes how the operator makes sure themysql
user account is present on the system.
______________________
1
So named because the syntax looks like a spaceship.
Listing 6-2.
Realizing a virtual resource using the spaceship operator
class webapp {
include accounts::virtual
package { "webapp": ensure => present }
User <| title == "mysql" |>
}
In the last line of thiswebapp
class, the operator uses the spaceship operator to find the user resource with the title ofmysql
. This syntax specifies a very specific resource to realize, however an error will not be thrown if there is no virtual user resource with the titlemysql
. The spaceship operator is analogous to a search function, where returning no results is perfectly valid. In situations where a specific resource is required, the realize function may be used to generate an error if the virtual resource is not found.
Therealize
function provides another method to make a virtual resource real. A specific resource identified by the type and title must be passed as an argument to the realize function. This requirement of a specific resource makes the realize function much less flexible than the collection syntax and spaceship operator. The realize function is more appropriate to use when an error should be thrown if the virtual resource has not been declared in the catalog. For example, the operator may want catalog compilation to fail if there is nomysql
user resource, as you can see in
Listing 6-3
.
Listing 6-3.
The realize() function
class webapp {
realize(User["mysql")
package { "webapp":
ensure => present,
}
}
The configuration catalog resulting from thewebapp
class defined in
Listing 6-3
is the same as the configuration catalog generated from thewebapp
class shown in
listing 6-2
. We've seen the operator realize a specific resource, themysql
user, but how does he handle the situation where he'd like to make a number of virtual resources real? Puppet provides a convenient way to solve this problem without forcing the operator to specify each and every resource by name.
When using the spaceship operator, any parameter may be used to collect resources. This feature allows a large number of relationships to be managed in a concise and clear style. For example, if there are multiple user accounts with a primary group of “apache,” the operator may realize all of them using a single statement:
User <| gid == "apache" |>
So far you've seen how to realize collections of virtual resources using the spaceship operator and specific resources using therealize
function. A key aspect of the Puppet model is specifying relationships between resources, and we haven't yet discussed how to establish a relationship to a
realized virtual resource. Prior to Puppet 2.6.0, this was very difficult to configure, but new syntax added in version 2.6.0 makes this problem very easy to solve.
In Puppet 2.6.0, resource collections may also have a block associated with them to add additional parameters. When realizing virtual resources, the relationship metaparameters may be specified to ensure the resource is managed in the correct order. Look at
Listing 6-4
to see how the Example.com operator ensures themysql
user account is always managed before thewebapp
package.
Listing 6-4.
Specifying parameters in a collection
class webapp {
User <| title == mysql |> { before => Package["webapp"] }
package { "webapp":
ensure => present,
}
}
As you can see, appending a block containing parameters after the collection will add the parameter to all of the realized resources. This also works for collections that contain many resources, such as:
User <| gid == "apache" |> { before => Package["apache"] }
In addition to a block associated with a collection, Puppet version 2.6.0 and newer also supports a new relationship-chaining syntax. This syntax allows relationships to be declared without using the metaparameters before, require, subscribe and notify as we'll see in the next section.
A major new feature in Puppet 2.6.0, the relationship-chaining syntax allows you to replace the before, require, subscribe and notify parameters with arrow operators. These new operators allow relationships to be declared outside of the blocks where resources themselves are declared.
For example, two resources may be declared without any relation to each other, and their relationship established at a later point in time.
define apache::account($ensure=present) {
user { "apache":
ensure => $ensure,
gid => 48
}
group { "apache":
ensure => $ensure,
gid => 48,
}
if ($ensure == present) {
Group["apache"] -> User["apache"]
} else {
User["apache”] -> Group["apache”]
}
}