Docsity
Docsity

Prepare for your exams
Prepare for your exams

Study with the several resources on Docsity


Earn points to download
Earn points to download

Earn points by helping other students or get them with a premium plan


Guidelines and tips
Guidelines and tips

Building Hosts with Puppet: Managing Configuration with Modules and Definitions, Exams of Logic

System AdministrationCloud ComputingDevOpsNetwork AdministrationSecurity

An introduction to using Puppet for managing hosts, with a focus on structuring assignments of classes to nodes using inheritance and a base class, as well as making use of modules, classes, and definitions. It covers the creation of scopes, managing classes in separate files, and using aliases for resources. The document also demonstrates the use of parameters and metaparameters to specify relationships between resources and classes.

What you will learn

  • How does the scope hierarchy work in Puppet?
  • How can you manage and organize multiple classes in a Puppet module?
  • What is the role of parameters and metaparameters in Puppet?
  • What is the purpose of using inheritance and a base node when assigning classes to nodes in Puppet?
  • What is the difference between classes and definitions in Puppet?

Typology: Exams

2021/2022

Uploaded on 09/27/2022

scream
scream 🇬🇧

4.5

(11)

59 documents

1 / 36

Toggle sidebar

Related documents


Partial preview of the text

Download Building Hosts with Puppet: Managing Configuration with Modules and Definitions and more Exams Logic in PDF only on Docsity! C H A P T E R 2    29 Building Hosts with Puppet In Chapter 1 we installed and configured Puppet, created our first module, and applied that module and its configuration via the Puppet agent to a host. In this chapter, we’re going to extend this process to build some more complete modules and hosts with Puppet for a hypothetical company, Example.com Pty Ltd. Each host’s functionality we build will introduce new Puppet concepts and ideas. Example.com Pty Ltd has four hosts we’re going to manage with Puppet: a Web server, a database server, a mail server and our Puppet master server located in a flat network. You can see that network in Figure 2-1. Figure 2-1. The Example.com Pty Ltd Network Like many organizations, though, Example.com is not a very homogenous environment and each host uses a different operating system, as follows: • mail.example.com – (Red Hat Enterprise Linux 5) • db.example.com – (Solaris 10) • web.example.com – (Ubuntu 10.04) • puppet.example.com – (Ubuntu 10.04) CHAPTER 2  BUILDING HOSTS WITH PUPPET 30 To solve this problem, we’ll begin by working through how we use Puppet in a multiple operating system environment. Be sure you’ve installed the base operating system on these hosts as described in Chapter 1, because we’ll perform some basic configuration on the hosts. We’ll start with configuring SSH for each host, then we’ll install and configure some role-specific applications for the hosts as follows: • Postfix (mail.example.com) • MySQL (db.example.com ) • Apache and a website (web.example.com) • Manage the Puppet master with Puppet (puppet.example.com ) As we configure each host, we’ll introduce some of the different features and functions available in Puppet. By the end of the chapter you’ll have a firm grasp of the basics. In subsequent chapters, we’ll build on this knowledge and introduce some of Puppet’s more advanced features. Getting Started Before proceeding, we must have the proper setup, so we need to install the Puppet master and agent and then create node definitions for each of our hosts.  Note As we mentioned in Chapter 1, the Puppet software is called the “agent.” Puppet calls the definition of the host itself a “node.” Installing Puppet First, we need to install the Puppet master and agent. We’re going to install the Puppet master on puppet.example.com and the Puppet agent on all our hosts, including puppet.example.com. We’re installing the agent on the Puppet master because we’re going to use Puppet to manage itself! We then need to connect, create and sign certificates for each host. To do this, you should follow the installation instructions for the relevant operating system from Chapter 1 on each of the four hosts. For example, for installation on the Red Hat Enterprise Linux host, use the instructions in the Installing on Red Hat Enterprise Linux and Fedora section. You can then move on to configuring the nodes (aka hosts).  Tip If you use a provisioning tool like Kickstart or Preseed, you can also include Puppet installation and signing as part of your build process. You can see an example of how to do that at http://projects.puppetlabs.com/projects/1/wiki/Bootstrapping_With_Puppet. CHAPTER 2  BUILDING HOSTS WITH PUPPET 33  Caution When starting out with Puppet it is common to structure the assignment of classes to nodes using inheritance and a base node. This structure allows classes common to every node to be placed in the base node. This organization structure may pose a problem in the future as the number of nodes and the scale of puppet increases and base classes need to be added or removed from only a subset of all nodes. In order to avoid future refactoring, avoid using node inheritance in preference of a flat node classification tree. A good alternative to the base node and class inheritance is to employ conditional statements, which we’ll introduce later in this chapter, to determine which classes a node should and should not receive instead of relying on node inheritance. Variable Scoping The concept of node inheritance is a good place to talk about an important and sometimes tricky concept in Puppet: variable scoping. Let’s imagine we’ve decided to configure some variables in our nodes, for example: node base { $location = "dc1" … $location = "dc2" } In most programming languages, the $location variable would start out with a value of "dc1" and then, when it was next assigned, it would change to a value of "dc2". In Puppet, these same two statements cause an error: err: Cannot reassign variable location at /etc/puppet/manifests/node.pp:4 Why is this? Puppet is declarative and hence dynamically scoped. Allowing variable reassignment would have to rely on order in the file to determine the value of the variable and order does not matter in a declarative language. The principal outcome of this is that you cannot redefine a variable inside the same scope it was defined in, like our node. Let’s take another example, of a class this time instead of a node: class ssh_sudo { $package = "openssh" package { $package: ensure => installed } $package = "sudo" package { $package: ensure => installed } } You can see that we’ve tried to define the $package variable twice. If we were to try to compile and apply this configuration, the Puppet agent would return the following error: err: Cannot reassign variable package at /etc/puppet/modules/ssh/manifests/init.pp:5 CHAPTER 2  BUILDING HOSTS WITH PUPPET 34  Note The error helpfully also tells us the file, and line number in the file, where we’ve tried to redefine the variable. So what’s a scope? Each class, definition, or node introduces a new scope, and there is also a top scope for everything defined outside of those structures. Scopes are created hierarchically and the important thing you need to remember about scope hierarchy is that it is created when Puppet code is evaluated, rather than when it is defined, for example: $package = "openssh" class ssh { package { $package: ensure => installed, } } class ssh_server include ssh $package = "openssh-server" } include ssh_server Here a top level scope, in which $package is defined, is present. Then there’s a scope for the ssh_server class and a scope below that for the ssh class. When Puppet runs the $package variable will have a value of "openssh-server" because this is what the variable was when evaluation occurred. Naturally, in these different scopes, you can reassign the value of a variable: class apache { $apache = 1 } class passenger { $apache = 2 } The same variable can be used and defined in both the apache and passenger classes without generating an error because they represent different scopes. Going back to node inheritance, you can probably see how this dynamic scoping is going to be potentially confusing, for example: class apache { $apacheversion = "2.0.33" package { "apache2": ensure => $apacheversion, } } CHAPTER 2  BUILDING HOSTS WITH PUPPET 35 node 'web.example.com' { include apache } node 'web2.example.com' inherits 'web.example.com' { $apacheversion = "2.0.42" } Here we’ve created a class called apache and a package resource for the apache2 package. We’ve also created a variable called $apacheversion and used that as the value of the ensure attribute of the package resource. This tells Puppet that we want to install version 2.0.33 of Apache. We’ve then included our apache class in a node, web.example.com. But we’ve also decided to create another node, web2.example.com, which inherits the contents of the web.example.com node. In this case, however, we’ve decided to install a different Apache version and therefore we specified a new value for the $apacheversion variable. But instead of using this new value, Puppet will continue to install the 2.0.33 version of Apache because the $apacheversion variable is maintained in its original scope of the web.example.com node and the new variable value is ignored. There is a work-around for this issue that you can see here: class apache { $apacheversion = "2.0.33" package { "apache2": ensure => $apacheversion, } } class base { include apache } node 'web.example.com' { $apacheversion = "2.0.42" include base } Instead of defining a base node we’ve defined a base class that includes the apache class. When we created our node, we specified the $apacheversion we want and then included the base class, ensuring we’re in the right scope. We could put other like items in our base class and specify any required variables.  Note You can learn more about variable scoping, workarounds and related issues at http://projects.puppetlabs.com/projects/puppet/wiki/Frequently_Asked_Questions#Common+Misconce ptions. With Puppet installed and node definitions in place, we can now move on to creating our modules for the various hosts. But first, let’s do a quick refresher on modules in general. CHAPTER 2  BUILDING HOSTS WITH PUPPET 38 This tells us that when we commit that Git will add the contents to the repository and create a revision based on that state. Now let’s commit our revision to the repository. $ git commit –a –m "This is our initial commit" The –m option specifies a commit message that allows us to document the revision we’re about to commit. It’s useful to be verbose here and explain what you have changed and why, so it’s easier to find out what’s in each revision and make it easier to find an appropriate point to return to if required. If you need more space for your commit message you can omit the –m option and Git will open your default editor and allow you to type a more comprehensive message. The changes are now committed to the repository and we can use the git log command to see our recent commit. $ git log We can see some information here about our commit. First, Git uses SHA1 hashes to track revisions; Subversion, for example, uses numeric numbers – 1, 2, 3, etc. Each commit has a unique hash assigned to it. We will also see some details about who created the commit and our commit message telling us what the commit is all about. Every time you add a new module or file you will need to add it to Git using the git add command and then commit it to store it in the repository. I recommend you add and commit changes regularly to ensure you have sufficiently granular revisions to allow you to easily roll back to an earlier state.  Tip If you’re interested in Git, we strongly recommend Scott Chacon’s excellent book Pro Git – also published by Apress. The book is available in both dead tree form and online at http://progit.org/book/. Scott is also one of the lead developers of the Git hosting site, GitHub – http://www.github.com, where you can find a number of Puppet related modules. Our simple sudo module is a good introduction to Puppet, but it only showcased a small number of Puppet’s capabilities. It’s now time to expand our Puppet knowledge and develop some new more advanced modules, starting with one to manage SSH on our hosts. We’ll then create a module to manage Postfix on mail.example.com, one to manage MySQL on our Solaris host, db.example.com, another to manage Apache and web sites, and finally one to manage Puppet with Puppet itself. We’ll also introduce you to some best practices for structuring, writing and managing modules and configuration. Creating a module to Manage SSH We know that we first need to create an appropriate module structure. We’re going to do this under the /etc/puppet/modules directory on our Puppet master. $ cd /etc/puppet/modules $ mkdir –p ssh/{manifests,templates,files} $ touch ssh/manifests/init.pp CHAPTER 2  BUILDING HOSTS WITH PUPPET 39 Next, we create some classes inside the init.pp file and some initial resources, as shown in Listing 2-2. Listing 2-2. The ssh module class ssh::install { package { "openssh": ensure => present, } } class ssh::config { file { "/etc/ssh/sshd_config": ensure = > present, owner => 'root', group => 'root', mode => 0600, source => "puppet:///modules/ssh/sshd_config", require => Class["ssh::install"], notify => Class["ssh::service"], } } class ssh::service { service { "sshd": ensure => running, hasstatus => true, hasrestart => true, enable => true, require => Class["ssh::config"], } } class ssh { include ssh::install, ssh::config, ssh::service } We’ve created three classes: ssh, ssh::install, ssh::config, and ssh::service. As we mentioned earlier, modules can be made up multiple classes. We use the :: namespace syntax as a way to create structure and organization in our modules. The ssh prefix tells Puppet that each class belongs in the ssh module, and the class name is suffixed.  Note We’d also want to create a sshd_config file in the ssh/files/ directory so that our File["/etc/ssh/sshd_config"] resource can serve out that file. The easiest way to do this is to copy an existing functional sshd_config file and use that. Later we’ll show you how to create template files that allow you to configure per-host configuration in your files. Without this file Puppet will report an error for this resource. CHAPTER 2  BUILDING HOSTS WITH PUPPET 40 In Listing 2-2, we created a functional structure by dividing the components of the service we’re managing into functional domains: things to be installed, things to be configured and things to be executed or run. Lastly, we created a class called ssh (which we need to ensure the module is valid) and used the include function to add all the classes to the module. Managing Our Classes Lots of classes with lots of resources in our init.pp file means that the file is going to quickly get cluttered and hard to manage. Thankfully, Puppet has an elegant way to manage these classes rather than clutter the init.pp file. Each class, rather than being specified in the init.pp file, can be specified in an individual file in the manifests directory, for example in a ssh/manifests/install.pp file that would contain the ssh::install class: class ssh::install { package { "openssh": ensure => present, } } When Puppet loads the ssh module, it will search the path for files suffixed with .pp, look inside them for namespaced classes and automatically import them. Let’s quickly put our ssh::config and ssh::service classes into separate files: $ touch ssh/manifests/{config.pp,service.pp} This leaves our init.pp file containing just the ssh class: class ssh include ssh::install, ssh::config, ssh::service } Our ssh module directory structure will now look like: ssh ssh/files/sshd_config ssh/manifests/init.pp ssh/manifests/install.pp ssh/manifests/config.pp ssh/manifests/service.pp ssh/templates Neat and simple.  Tip You can nest classes another layer, like ssh::config::client, and our auto-importing magic will still work by placing this class in the ssh/manifests/config/client.pp file. CHAPTER 2  BUILDING HOSTS WITH PUPPET 43 } /(RedHat|CentOS|Fedora)/: { $ssh_package_name = 'openssh-server' } } } You can see that inside our ssh::params class we’ve created another type of conditional, the case statement. Much like a selector, the case statement iterates over the value of a variable, here $operatingsystem. Unlike a selector, case statements allow us to specify a block of things to do if the value of the variable matches one of the cases. In our case we’re setting the value of a new variable we’ve created, called $ssh_package_name. You could do other things here, such as include a class or a resource, or perform some other function.  Note You can read more about case statements at http://docs.puppetlabs.com/guides/language_ tutorial.html#case_statement. Also available is an if/else syntax that you can read about at http://docs.puppetlabs.com/guides/language_tutorial.html#ifelse_statement. And finally, we need to include our new class in the ssh class:\ class ssh { include ssh::params, ssh::install, ssh::config, ssh::service } These includes tell Puppet that when you include the ssh module, you’re getting all of these classes. FUNCTIONS The include directive we use to include our classes and modules is called a function. Functions are commands that run on the Puppet master to perform actions. Puppet has a number of other functions, including the generate function that calls external commands and returns the result, and the notice function that logs messages on the master and is useful for testing a configuration. For example: notice("This is a notice message including the value of the $ssh_package variable") Functions only run on the Puppet master and cannot be run on the client, and thus can only work with the resources available on the master. You can see a full list of functions at http://docs.puppetlabs.com/references/stable/function.html and we’ll introduce you to a variety of other functions in subsequent chapters. You can also find some documentation on how to write your own functions at http://projects.puppetlabs.com/projects/puppet/wiki/Writing_Your_Own_Functions, and we’ll talk about developing functions in Chapter 10. CHAPTER 2  BUILDING HOSTS WITH PUPPET 44 We’re going to come back to the ssh::params class and add more variables as we discover other elements of our OpenSSH configuration that are unique to particular platforms, but for the moment how does including this new class change our Package["ssh"] resource? package { $ssh::params::ssh_package_name: ensure => installed, } You can see our namespacing is useful for other things, here using variables from other classes. We can refer to a variable in another class by prefixing the variable name with the class it’s contained in, here ssh::params. In this case, rather than our messy conditional, the package name to be installed will use the value of the $ssh::params::ssh_package_name parameter. Our resource is now much neater, simpler and easier to read.  Tip So how do we refer to namespaced resources? Just like other resources, Package[$ssh::params::ssh_package_name]. The ssh::config Class Now let’s move onto our next class, ssh::config, which we can see in Listing 2-4. Listing 2-4. The ssh::config class class ssh::config { file { "/etc/ssh/sshd_config": ensure = > present, owner => 'root', group => 'root', mode => 0440, source => "puppet:///modules/ssh/sshd_config", require => Class["ssh::install"], notify => Class["ssh::service"], } } We know that the location of the sshd_config files will vary across different operating systems. Therefore, we’re going to have to add another conditional for the name and location of that file. Let’s go back to our ssh::params class from Example 2-3 and add a new variable: class ssh::params { case $operatingsystem { Solaris { $ssh_package_name = 'openssh' $ssh_service_config = '/etc/ssh/sshd_config' } … } CHAPTER 2  BUILDING HOSTS WITH PUPPET 45 We add the $ssh_service_config variable to each of the cases in our conditional and then update our file resource in the ssh::config class: file { $ssh::params::ssh_service_config: ensure = > present, … } Again, we have no need for a messy conditional in the resource, we can simply reference the $ssh::params::ssh_service_config variable. We can also see that the file resource contains two metaparameters, require and notify. These metaparameters both specify relationships between resources and classes. You’ll notice here that both metaparameters reference classes rather than individual resources. They tell Puppet that it should create a relationship between this file resource and every resource in the referenced classes.  Tip It is a best practice to establish relationships with an entire class, rather than with a resource contained within another class, because this allows the internal structure of the class to change without refactoring the resource declarations related to the class. For example, the require metaparameter tells Puppet that all the resources in the specified class must be processed prior to the current resource. In our example, the OpenSSH package must be installed before Puppet tries to manage the service’s configuration file. The notify metaparameter creates a notification relationship. If the current resource (the service’s configuration file) is changed, then Puppet should notify all the resources contained in the ssh::service class. In our current case, a “notification” will cause the service resources in the ssh::service class restart, ensuring that if we change a configuration file that the service will be restarted and running with the correct, updated configuration.  Tip In Puppet 2.6.0, a shorthand method called “chaining” was introduced for specifying metaparameter relationships, such as require and notify. You can read about chaining at http://docs.puppetlabs.com/guides/language_tutorial.html#chaining_resources. So why specify the whole ssh::service class rather than just the Service["sshd"] resource? This is another piece of simple best practice that allows us to simplify maintaining our classes and the relationships between them. Imagine that, instead of a single package, we had twenty packages. If we didn’t require the class then we’d need to specify each individual package in our require statement, like this: require => [ Package["package1"], Package["package2"], Package["package3"] ], CHAPTER 2  BUILDING HOSTS WITH PUPPET 48 node 'mail.example.com' { include base } Here we’ve created a class called base, in which we’re going to place the modules that will be base or generic to all our nodes. Thus far, these are our sudo and ssh modules. We then include this class in each node statement.  Note We talked earlier about node inheritance and some of its scoping issues. As we explained there, using a class instead of node inheritance helps avoids these issues. You can read about it at http://projects.puppetlabs.com/projects/puppet/wiki/Frequently_Asked_Questions#Common+Misconce ptions. With a basic SSH module in place, and we can now manage the SSH daemon and its configuration. Creating a Module to Manage Postfix Let’s now create a module to manage Postfix on mail.example.com. We start with a similar structure to our SSH module. In this case, we know which platform we’re going to install our mail server on so we don’t need to include any conditional logic. However, if we had multiple mail servers on different platforms, it would be easy to adjust our module using the example we’ve just shown to cater for disparate operations systems. postfix postfix/files/master.cf postifx/manifests/init.pp postfix/manifests/install.pp postfix/manifests/config.pp postfix/manifests/service.pp postfix/templates/main.cf.erb The postfix::install class We also have some similar resources present in our Postfix module that we saw in our SSH module, for example in the postfix::install class we install two packages, postfix and mailx: class postfix::install { package { [ "postfix", "mailx" ]: ensure => present, } } Note that we’ve used an array to specify both packages in a single resource statement this is a useful shortcut that allows you specify multiple items in a single resource. CHAPTER 2  BUILDING HOSTS WITH PUPPET 49 The postfix::config class Next, we have the postfix::config class, which we will use to configure our Postfix server. class postfix::config { File { owner => "postfix", group => "postfix", mode => 0644, } file { "/etc/postfix/master.cf": ensure = > present, source => "puppet:///modules/postfix/master.cf", require => Class["postfix::install"], notify => Class["postfix::service"], } file { "/etc/postfix/main.cf": ensure = > present, content => template("postfix/main.cf.erb"), require => Class["postfix::install"], notify => Class["postfix::service"], } } You may have noticed some new syntax: We specified the File resource type capitalized and without a title. This syntax is called a resource default, and it allows us to specify defaults for a particular resource type. In this case, all File resources within the postfix::config class will be owned by the user postfix, the group postfix and with a mode of 0644. Resource defaults only apply to the current scope, but you can apply global defaults by specifying them in your site.pp file. A common use for global defaults is to define a global “filebucket” for backing up the files Puppet changes. You can see the filebucket type and an example of how to use it globally at http://docs.puppetlabs.com/references/stable/type.html#filebucket.  Tip A common use for global defaults is to define a global “filebucket” for backing up the files Puppet changes. You can see the filebucket type and an example of how to use it globally at http://docs.puppetlabs.com/references/stable/type.html#filebucket. CHAPTER 2  BUILDING HOSTS WITH PUPPET 50 METAPARAMETER DEFAULTS Like resource defaults, you can also set defaults for metaparameters, such as require, using Puppet variable syntax. For example: class postfix::config { $require = Class["postfix::install"] … } This would set a default for the require metaparameter in the postfix::config class and means we could remove all the require => Class["postfix::install"] statements from our resources in that class. We’ve also introduced a new attribute in our File["/etc/postfix/main.cf"] resource – content. We’ve already seen the source attribute, which allows Puppet to serve out files, and we’ve used it in one of our File resources, File["/etc/postfix/master.cf"]. The content attribute allows us to specify the content of the file resources as a string. But it also allows us to specify a template for our file. The template is specified using a function called template. As previously mentioned, functions are commands that run on the Puppet master and return values or results. In this case, the template function allows us to specify a Ruby ERB template (http://ruby- doc.org/stdlib/libdoc/erb/rdoc/), from which we can create the templated content for our configuration file. We specify the template like this: content => template("postfix/main.cf.erb"), We’ve specified the name of the function, “template,” and inside brackets the name of the module that contains the template and the name of the template file. Puppet knows when we specify the name of the module to look inside the postfix/templates directory for the requisite file – here, main.cf.erb. THE REQUIRE FUNCTION In addition to the include function, Puppet also has a function called require. The require function works just like the include function except that it introduces some order to the inclusion of resources. With the include function, resources are not included in any sequence. The only exception is individual resources, which have relationships (using metaparameters, for example) that mandate some ordering. The require function tells Puppet that all resources being required must be processed first. For example, if we specified: class ssh { require ssh::params include ssh::install, ssh::config, ssh::service } then the contents of ssh::params would be processed before any other includes or resources in the ssh class. This is useful as a simple way to specify some less granular ordering to your manifests than metaparameter relationships, but it’s not recommended as a regular approach. The reason it is not CHAPTER 2  BUILDING HOSTS WITH PUPPET 53 achieve the desired behavior of enabling or disabling the service without completely re-declaring the bind service resource. This organization structure also ensures we avoid duplicate resource declarations, remembering that a resource can only be declared once. You can also add values to attributes in subclasses, like so: class bind { service { "bind": require => Package["bind"] } } class bind::server inherits bind { Service["bind"] { require +> Package["bind-libs"] } } Here we have defined the proxy class containing the bind service, which in turn requires the bind package to be installed. We have then created a subclass called bind::server that inherits the bind service but adds an additional package, bind-libs, to the require metaparameter. To do this, we use the +> operator. After this addition, the bind service would now functionally look like this: service { "bind": require => [ Package["bind"], Package["bind-libs"] ] } We can also unset particular values in subclasses using the undef attribute value. class bind { service { "bind": require => Package["bind"] } } class bind::client inherits bind { Service["bind"] { require => undef } } Here, we again have the bind class with the bind service, which requires the bind package. In the subclass, though, we have removed the require attribute using the undef attribute value. It is important to remember that class inheritance suffers from the same issues as node inheritance: variables are maintained in the scope they are defined in, and are not overridden. You can learn more at http://projects.puppetlabs.com/projects/1/wiki/Frequently_Asked_Questions#Class+Inherita nce+and+Variable+Scope. Managing MySQL with the mysql Module Our next challenge is managing MySQL on our Solaris host, db.example.com. To do this we’re going to create a third module called mysql. We create our module structure as follows: mysql mysql/files/my.cnf mysql/manifests/init.pp mysql/manifests/install.pp mysql/manifests/config.pp CHAPTER 2  BUILDING HOSTS WITH PUPPET 54 mysql/manifests/service.pp mysql/templates/ The mysql::install class Let’s quickly walk through the classes to create, starting with mysql::install. class mysql::install { package { [ "mysql5", "mysql5client", "mysql5rt", "mysql5test", "mysql5devel" ]: ensure => present, require => User["mysql"], } user { "mysql": ensure => present, comment => "MySQL user", gid => "mysql", shell => "/bin/false", require => Group["mysql"], } group { "mysql": ensure => present, } } You can see that we’ve used two new resource types in our mysql::install class, User and Group. We also created a mysql group and then a user and added that user, using the gid attribute, to the group we created. We then added the appropriate require metaparameters to ensure they get created in the right order. The mysql::config class Next, we add our mysql::config class: class mysql::config { file { "/opt/csw/mysql5/my.cnf": ensure = > present, source => "puppet:///modules/mysql/my.cnf", owner => "mysql", group => "mysql", require => Class["mysql::install"], notify => Class["mysql::service"], } file { "/opt/csw/mysql5/var": group => "mysql", owner => "mysql", recurse => true, require => File["/opt/csw/mysql5/my.cnf"], } } CHAPTER 2  BUILDING HOSTS WITH PUPPET 55 You can see we’ve added a File resource to manage our /opt/csw/mysql5 directory. By specifying the directory as the title of the resource and setting the recurse attribute to true, we are asking Puppet to recurse through this directory and all directories underneath it and change the owner and group of all objects found inside them to mysql. The mysql::service class Then we add our mysql::service class: class mysql::service { service { "cswmysql5": ensure => running, hasstatus => true, hasrestart => true, enabled => true, require => Class["mysql::config"], } } Our last class is our mysql class, contained in the init.pp file where we load all the required classes for this module: class mysql { include mysql::install, mysql::config, mysql::service } Lastly, we can apply our mysql module to the db.example.com node. node "db.example.com" { include base include mysql } Now, when the db.example.com node connects, Puppet will apply the configuration in both the base and mysql modules. AUDITING In addition to the normal mode of changing configuration (and the --noop mode of modelling the proposed configuration), Puppet has a new audit mode that was introduced in version 2.6.0. A normal Puppet resource controls the state you’d like a configuration item to be in, like this for example: file { '/etc/hosts': owner => 'root', group => 'root', mode => 0660, } CHAPTER 2  BUILDING HOSTS WITH PUPPET 58 notify => Class["apache::service"], } } We gave a definition a title (apache::vhost) and then specified a list of potential variables. Variables can be specified as a list, and any default values specified, for example $ssl=true. Defaults will be overridden if the parameter is specified when the definition is used. Inside the definition we can specify additional resources or classes, for example here we’ve included the apache class that ensures all required Apache configuration will be performed prior to our definition being evaluated. This is because it doesn’t make sense to create an Apache VirtualHost if we don’t have Apache installed and ready to serve content. In addition to the apache class, we’ve added a basic file resource which manages Apache site files contained in the /etc/apache2/sites-enabled directory. The title of each file is constructed using the priority parameter, and the title of our definition is specified using the $name variable.  Tip The $name variable contains the name, also known as the title, of a declared defined resource. This is the value of the string before the colon when declaring the defined resource. This file resource’s content attribute is specified by a template, the specific template being the value of the $template parameter. Let’s look at a fairly simple ERB template for an Apache VirtualHost in Listing 2-8. Listing 2-8. VirtualHost Template NameVirtualHost *:<%= port %> <VirtualHost *:<%= port %>> ServerName <%= name %> <%if serveraliases.is_a? Array -%> <% serveraliases.each do |name| -%><%= " ServerAlias #{name}\n" %><% end -%> <% elsif serveraliases != '' -%> <%= " ServerAlias #{serveraliases}" -%> <% end -%> DocumentRoot <%= docroot %> <Directory <%= docroot %>> Options Indexes FollowSymLinks MultiViews AllowOverride None Order allow,deny allow from all </Directory> ErrorLog /var/log/apache2/<%= name %>_error.log LogLevel warn CustomLog /var/log/apache2/<%= name %>_access.log combined ServerSignature On </VirtualHost> Each parameter specified in the definition is used, including the $name variable to name the virtual host we’re creating. CHAPTER 2  BUILDING HOSTS WITH PUPPET 59 You can also see some embedded Ruby in our ERB template: <%if serveraliases.is_a? Array -%> <% serveraliases.each do |name| -%><%= " ServerAlias #{name}\n" %><% end -%> <% elsif serveraliases != '' -%> <%= " ServerAlias #{serveraliases}" -%> <% end -%> Here we’ve added some logic to the serveraliases parameter. If that parameter is an array of values, then create each value as a new server alias; if it’s a single value, then create only one alias. Let’s now see how we would use this definition and combine our definition and template: apache::vhost { 'www.example.com': port => 80, docroot => '/var/www/www.example.com', ssl => false, priority => 10, serveraliases => 'home.example.com', } Here we have used our definition much the same way we would specify a resource by declaring the apache::vhost definition and passing it a name, www.example.com (which is also the value of the $name variable). We’ve also specified values for the required parameters. Unless a default is already specified for a parameter, you need to specify a value for every parameter of a definition otherwise Puppet will return an error. We could also override parameters, for example by specifying a different template: template => 'apache/another_vhost_template.erb', So in our current example, the template would result in a VirtualHost definition that looks like Listing 2-9. Listing 2-9. The VirtualHost Configuration File NameVirtualHost *:80 <VirtualHost *:80> ServerName www.example.com ServerAlias home.example.com DocumentRoot /var/www/www.example.com <Directory /var/www/www.example.com> Options Indexes FollowSymLinks MultiViews AllowOverride None Order allow,deny allow from all </Directory> ErrorLog /var/log/apache2/www.example.com_error.log LogLevel warn CustomLog /var/log/apache2/www.example.com_access.log combined ServerSignature On </VirtualHost> CHAPTER 2  BUILDING HOSTS WITH PUPPET 60 The final class in our module is the apache class in the init.pp file, which includes our Apache classes: class apache { include apache::install, apache::service } You can see we’ve included our three classes but not the definition, apache::vhost. This is because of some module magic called “autoloading.” You learned how everything in modules is automatically imported into Puppet, so you don’t need to use the import directive. Puppet scans your module and loads any .pp file in the manifests directory that is named after the class it contains, for example the install.pp file contains the apache::install class and so is autoloaded. The same thing happens with definitions: The vhost.pp file contains the definition apache::vhost, and Puppet autoloads it. However, as we declare definitions, for example calling apache::vhost where we need it, we don’t need to do an include apache::vhost because calling it implies inclusion. Next, we include our classes into our www.example.com node and call the apache::vhost definition to create the www.example.com website. node "www.example.com" { include base include apache apache::vhost { 'www.example.com': port => 80, docroot => '/var/www/www.example.com', ssl => false, priority => 10, serveraliases => 'home.example.com', } } We could now add additional web servers easily and create additional Apache VirtualHosts by calling the apache::vhost definition again, for example: apache::vhost { 'another.example.com': port => 80, docroot => '/var/www/another.example.com', ssl => false, priority => 10, } Managing Puppet with the Puppet Module In our very last module we’re going to show you Puppet being self-referential, so you can manage Puppet with Puppet itself. To do this we create another module, one called puppet, with a structure as follows: puppet puppet/files/ puppet/manifests/init.pp puppet/manifests/install.pp CHAPTER 2  BUILDING HOSTS WITH PUPPET 63 require => File["/etc/puppet/puppet.conf"], } } You can see that our class puppet::master includes the classes puppet and puppet::params. This will mean all the preceding Puppet configuration will be applied, in addition to the new package and service resources we’ve defined in this class. We can now add this new module to our nodes, leaving them looking like this: class base { include sudo, ssh, puppet } node 'puppet.example.com' { include base include puppet::master } node 'web.example.com' { include base include apache apache::vhost { 'www.example.com': port => 80, docroot => '/var/www/www.example.com', ssl => false, priority => 10, serveraliases => 'home.example.com', } } node 'db.example.com' { include base include mysql } node 'mail.example.com' { include base include postfix } We’ve added the puppet module to the base class we created earlier. This will mean it’s added to all the nodes that include base. We’ve also added the puppet::master class, which adds the additional resources needed to configure the Puppet master, to the puppet.example.com node. Summary In this chapter, you’ve been introduced to quite a lot of Puppet’s basic features and language, including: • How to structure modules, including examples of modules to manage SSH, Postfix, MySQL and Apache. CHAPTER 2  BUILDING HOSTS WITH PUPPET 64 • How to use language constructs like selectors, arrays and case statements • A greater understanding of files and templates • Definitions that allow you to manage configuration, such as Apache VirtualHosts • Variable scoping You’ve also seen how a basic Puppet configuration in a simple environment might be constructed, including some simple modules to manage your configuration. Also, Puppet Forge contains a large collection of pre-existing modules that you can either use immediately or modify to suit your environment. In the next chapter, we’ll look at how to scale Puppet beyond the basic Webrick server, using tools like Mongrel and Passenger and allowing you to manage larger numbers of hosts. Resources • Puppet Documentation: http://docs.puppetlabs.com • Puppet Wiki: http://projects.puppetlabs.com/projects/puppet/wiki • Puppet Forge: http://forge.puppetlabs.com
Docsity logo



Copyright © 2024 Ladybird Srl - Via Leonardo da Vinci 16, 10126, Torino, Italy - VAT 10816460017 - All rights reserved