Add puppet tuturials

Change-Id: Id882c83ead969eafdbdb8a2b3609dce862f8bab5
This commit is contained in:
Dmitry Ilyin 2013-11-01 18:18:45 +04:00
parent 2da21774e6
commit 5975f7bc81
5 changed files with 738 additions and 24 deletions

View File

@ -1,8 +1,5 @@
.. _develop:
Development Documentation
=========================
.. toctree::
:maxdepth: 2
@ -12,3 +9,6 @@ Development Documentation
develop/nailgun
develop/alternatives
develop/api_doc
develop/module_structure
develop/fuel_settings
develop/puppet_tips

View File

@ -0,0 +1,106 @@
Using Fuel settings
~~~~~~~~~~~~~~~~~~~
Fuel uses a special way to pass setting from Nailgun to Puppet manifests.
Before the start of deployment process Astute uploads all settings, each
server should have to the file */etc/astute.yaml* placed on every node.
When Puppet is run facter reads this file entirely into a single fact
*$astute_settings_yaml*. Then these settings are parsed by parseyaml function
at the very beggining of site.pp file and set as rich data structure called
*$fuel_settings*. All of the setting used during node deployment are stored
there and can be used anywhere in Puppet code.
For example, single top level variables are available as
*$::fuel_settings['debug']*. More complex structures are also available as
values of *$::fuel_settings* hash keys and can be accessed like usual hashes
and arrays.
There are also a lot of aliases and generated values that help you get needed
values easier. You can always create variables from any of settings hash keys
and work with this variable within your local scope or from other classes
using fully qualified paths.::
$debug = $::fuel_settings['debug']
Some variables and structures are generated from settings hash by filtering
and transformation functions. For example there is $node structure.::
$node = filter_nodes($nodes_hash, 'name', $::hostname)
It contains only settings of current node filtered from all nodes hash.
If you are going to use your module inside Fuel Library and need some
settings you can just get them from this *$::fuel_settings* structure.
Most variables related to network and OpenStack
services configuration are already available there and you can use them as
they are. But if your modules requires some additional or custom settings
you'll have to either use **Custom Attributes** by editing json files before
deployment, or, if you are integrating your project with Fuel Library, you
should contact Fuel UI developers and ask them to add your configuration
options to Fuel setting panel.
Once you have finished definition of all classes you need inside your module
you can add this module's declaration either into the Fuel manifests such as
*cluster_simple.pp* and *cluster_ha.pp* located inside
*osnailyfacter/manifests* folder or to the other classes that are already
being used if your additions are related to them.
Example module
~~~~~~~~~~~~~~
Let's demonstrate how to add new module to the Fuel Library by adding a simple
class that will change terminal color of Red Hat based systems.
Our module will be named *profile* and have only one class.::
profile
profile/manifests
profile/manifests/init.pp
profile/files
profile/files/colorcmd.sh
init.pp could have a class definition like this.::
class profile {
if $::osfamily == 'RedHat' {
file { 'colorcmd.sh' :
ensure => present,
owner => 'root',
group => 'root',
mode => '0644',
path => "/etc/profile.d/colorcmd.sh",
source => 'puppet:///modules/profile/colorcmd.sh',
}
}
}
This class just downloads *colorcmd.sh* file and places it to the defined
location if this class is run on Red Hat or CentOS system. The profile module
can be added to Fuel modules by uploading its folder to */etc/puppet/modules*
on the Fuel Master node.
Now we need to declare this module somewhere inside Fuel manifests. Since this
module should be run on every server, we can use our main *site.pp* manifest
found inside the *osnailyfacter/examples* folder. On the deployed master node
this file will be copied to */etc/puppet/manifests* and used to deploy Fuel
on all other nodes.
The only thing we need to do here is to add *include profile* to the end of
*/etc/puppet/manifests/site.pp* file on already deployed master node and to
*osnailyfacter/examples/site.pp* file inside Fuel repository.
Declaring a class outside of node block will force this class to be included
everywhere. If you want to include you module only on some nodes, you can add
its declaration inside *cluster_simple* and *cluster_ha* classed to the blocks
associated with required node's role.
You can add some additional logic to allow used to enable or disable this
module from Fuel UI or at least by passing Custom Attributes to Fuel
configuration.::
if $::fuel_settings['enable_profile'] {
include 'profile'
}
This block uses the *enable_profile* variable to enable or disable inclusion of
profile module. The variable should be passed from Nailgun and saved to
*/etc/astute.yaml* files of managed nodes.
You can do it by either downloading settings files and manually editing
them before deployment or by asking Fuel UI developers to include additional
options to the settings panel.

View File

@ -0,0 +1,288 @@
Contributing to Fuel Library
============================
This chapter will explain how to add new module or project into Fuel Library,
how to integrate with other components
and how to avoid different problems and potential mistakes. Fuel Library is a
very big project and even experienced Puppet user will have problems
understanding its structure and internal workings.
The Puppet modules structure
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
First let's start with Puppet modules structure. If you want to contribute you
code into the Fuel Library it should be organized into a Puppet module. Modules
are self-contained sets of Puppet code that usually are made to perform specific
function. For example you could have a module for every service you are going
to configure or for every part of your project. Usually it's a good idea to
make a module independent but sometimes it could require or be required by
other modules so module can be thinked about as a library.
The most important part of every Puppet module is its **manifests** folder.
This folder contains Puppet classes and definitions which also contain
resources managed by this module. Modules and classes also form namespaces.
Each class or definition should be placed each into single file inside
manifests folder and this file should be named same as class or definition.
Module should have top level class that serves as a module's entry point and
is named same as the module. This class should be placed into *init.pp* file.
This example module shows the standard structure every Puppet module should
follow.::
example
example/manifests/init.pp
example/manifests/params.pp
example/manifests/client.pp
example/manifests/server
example/manifests/server/vhost.pp
example/manifests/server/service.pp
example/templates
example/templates/server.conf.erb
example/files
example/files/client.data
The first file in manifests folder is named init.pp and should contain entry
point class of this module. This class should be named same as our module.::
class example {
}
The second file is *params.pp*. These files are not mandatory but are often
used to store different configuration values and parameters used by other
classes of the module. For example, it could contain service name and package
name of our hypothetical example module. There could be conditional statements
if you need to change default values in different environments. *Params* class
should be named as child to module's namespace as all other classes of the
module.::
class example::params {
$service = 'example'
$server_package = 'example-server'
$client_package = 'example-client'
$server_port = '80'
}
All other inside the manifests folder contain classes as well and can
perform any action you might want to identify as a separate piece of code.
This generally falls into sub-classes that don't require its users to
configure the parameters explicitly, or possibly these are simply optional
classes that are not required in all cases. In the following example,
we create a client class to define a client package that will be installed,
placed into a file called *client.pp*.::
class example::client {
include example::params
package { $example::params::client_package :
ensure => installed,
}
}
As you can see we have used package name from params class. Consolidating
all values that might require editing into a single class, as opposed to
hardcoding them, allows you to reduce the effort required to maintain and
develop the module further in the future. If you are going to use any values
from params class you should not forget to include it first to force its
code to execute and create all required variables.
You can add more levels into the namespace structure if you want. Let's create
server folder inside our manifests folder and add *service.pp* file there. It
would be responsible for installation and running server part of our imaginary
software. Placing the class inside subfolder adds one level into name of
contained class.::
class example::server::service (
$port = $example::params::server_port,
) inherits example::params {
$package = $example::params::server_package
$service = $example::params::service
package { $package :
ensure => installed,
}
service { $service :
ensure => running,
enabled => true,
hasstatus => true,
hasrestart => true,
}
file { 'example_config' :
ensure => present,
path => '/etc/example.conf',
owner => 'root',
group => 'root',
mode => '0644',
content => template('example/server.conf.erb'),
}
file { 'example_config_dir' :
ensure => directory,
path => '/etc/example.d',
owner => 'example',
group => 'example',
mode => '0755',
}
Package[$package] -> File['example_config', 'example_config_dir'] ~>
Service['example_config']
}
This example is a bit more complex. Let's see what it does.
Class *example::server::service* is **parameterized** and can accept one
parameter - port to which server process should bind to. It also uses a popular
"smart defaults" hack. This class inherits the params class and uses its values
default only if no port parameter is provided. In this case, you can't use
*include params* to load the default values because it's called by the
*inherits example::params* clause of the class definition.
Then inside our class we take several variable from params class and declare
them as variable of the local scope. This is conveniency hack to make their
names shorter.
Next we declare our resources. These resources are package, service, config
file and config dir. Package resource will install package which name is taken
from variable if it's not already installed. File resources create config file
and config dir and service resource would start the daemon process and enable
its autostart.
And the last but not least part of this class is *dependency* declaration. We
have used "chain" syntax to specify the order of evaluation of these
resources. Of course it's important first to install package, then
configuration files and only then start the service. Trying to start service
before installing package will definitely fail. So we need to tell Puppet that
there are dependencies between our resources.
The arrow operator that has a tilde instead of a minus sign (~>) means not
only dependency relationship but also *notifies* the object to the right
of the arrow to refresh itself. In our case any changes in configuration
file would make the service to restart and load new configuration file.
Service resource react to notification event by restating managed service.
Other resources may perform different actions instead if they support it.
Ok, but where do we get our configuration file content from? It's generated by
template function. Templates are text files with Ruby's erb language tags that
are used to generate needed text file using pre-defined text and some
variables from manifest.
These template files are located inside the **templates** folder of the
module and usually have *erb* extension. Calling template function with
template name and module name prefix will try to load this template and
compile it using variables from the local scope of the class function was
called from. For example we want to set bind port of our service in its
configuration file so we write template like this and save it inside
templates folder as server.conf.erb file.::
bind_port = <%= @port %>
Template function will replace 'port' tag with value of port variable from our
class during Puppet's catalog compilation.
Ok, now we have our service running and client package installed. But what if
our service needs several virtual hosts? Classes cannot be declared several
times with different parameters so it's where **definitions** come to the
rescue. Definitions are very similar to classes, but unlike classes, they
have titles like resources do and can be used many times with different
title to produce many instances of managed resources. Defined types can
also accept parameters like parametrized classes do.
Definitions are placed in single files inside manifests directories same as
classes and are similarly named using namespace hierarchy.
Let's create our vhost definition.::
define example::server::vhost (
$path = '/var/data',
) {
include example::params
$config = “/etc/example.d/${title}.conf”
$service = $example::params::service
file { $config :
ensure => present,
owner => 'example',
group => 'example',
mode => '0644',
content => template('example/vhost.conf.erb'),
}
File[$config] ~> Service[$service]
}
This defined type only creates a file resource with its name populated
by the title used when it gets defined and sets notification relationship
with service to make it restart when vhost file is changed.
This defined type can be used by other classes like a simple resource type to
create as many vhost files as we need.::
example::server::vhost { 'mydata' :
path => '/path/to/my/data',
}
Defined types can form relationships in a same way as resources do but you
need to capitalize all elements of path to make reference.::
File['/path/to/my/data'] -> Example::Server::Vhost['mydata']
Now we can work with text files using templates but what if we need to manage
binary data files? Binary files or text files that will always be same can be
placed into **files** directory of our module and then be taken by file
resource.
Let's imagine that our client package need some binary data file we need to
redistribute with it. Let's add file resource to our *example::client* class.::
file { 'example_data' :
path => '/var/lib/example.data',
owner => 'example',
group => 'example',
mode => '0644',
source => 'puppet:///modules/example/client.data',
}
We have specified source as a special puppet URL scheme with module's and
file's name. This file will be placed to specified location during puppet run.
But on each run Puppet will check this files checksum overwriting it if it
changes so don't use this method with mutable data. Puppet's fileserving works
both in client-server and masterless modes but unlike template requires
managed server to have access to master during catalog run because file's
content is not included into catalog.
Ok, we have all classes and resources we need to manage our hypothetical
example service. Let's try to put everything together. Our example class
defined inside *init.pp* is still empty so we can use it to declare all other
classes.::
class example {
include example::params
include example::client
class { 'example::server::service' :
port => '100',
}
example::server::vhost { 'site1' :
path => '/data/site1',
}
example::server::vhost { 'site2' :
path => '/data/site2',
}
example::server::vhost { 'test' :
path => '/data/test',
}
}
Now we have entire module packed inside *example* class and we can just
include this class to any node where we want to see our service running.
Declaration of parametrized class also did override default port number from
params file and we have three separate virtual hosts for out service. Client
package is also included into this class.

View File

@ -0,0 +1,316 @@
Resource duplication and file conflicts
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you have been developing your module that somehow uses services which are
already in use by other components of OpenStack, most likely you will try to
declare some of the same resources that have already been declared.
Puppet architecture doesn't allow declaration of resources that have same
type and title even if they do have same attributes.
For example, your module could be using Apache and has Service['apache']
declared. When you are running your module outside Fuel nothing else tries to
control this service to and everything work fine. But when you will try to add
this module to Fuel you will get resource duplication error because Apache is
already managed by Horizon module.
There is pretty much nothing you can do about this problem because uniqueness
of Puppet resources is one on its core principles. But you can try to solve
the problem by one of following ways.
The best thing you can do is to try to use an already declared resource by
settings dependencies to the other class that does use it. This will not work
in many cases and you may have to modify both modules or move conflicting
resource elsewhere to avoid conflicts.
Puppet does provide a good solution to this problem - **virtual resources**.
The idea behind it is that you move resource declaration to separate class and
make them virtual. Virtual resources will not be evaluated until you realize
them and you can do it in all modules that do require this resources.
The trouble starts when these resources have different attributes and complex
dependencies. Most current Puppet modules doesn't use virtual resources and
will require major refactoring to add them.
Puppet style guidelines advise to move all classes related with the same
service inside a single module instead of using many modules to work with
same service to minimize conflicts, but in many cases this approach
doesn't work.
There are also some hacks such are defining resource inside *if !
defined(Service['apache']) { ... }* block or using **ensure_resource**
function from Puppet's stdlib.
Similiar problems often arise then working with configuration files.
Even using templates doesn't allow several modules to directly edit save
file. There are a number of solutions to this starting from using
configurations directories and snippets if service supports them to
representing lines or configuration options as resources and managing
them instead of entire files.
Many services does support configuration directories where you can place
configuration files snippets. Daemon will read them all, concatinate and
use like it was a single file. Such services are the most convinient to
manage with Puppet. You can just separate you configuration mand manage
its pieces as templates. If your service doesn't know how to work with
snippets you still can use them. You only need to create parts of your
configuration file in some directory and then just combine them all
using simple exec with *cat* command. There is also a speciall *concat*
resource type to make this approach easier.
Some configuration files could have stamdard structure and can be managed
by custom resource types. For example, there is the *ini_file* resource
type to manage values in compatible configuration as single resources.
There is also *augeas* resource type that can manage many popular
configuration file formats.
Each approach has its own limitations and editing single file from
many modules is still non-trivial task in most cases.
Both resource duplication and file editing problems doesn't have a good
solution for every possible case and significantly limit possibility
of code reuse.
The last approach to solving this problem you can try is to modify files
by scripts and sed patches ran by exec resources. This can have unexpected
results because you can't be sure of what other operations are performed
on this configuration file, what text patterns exist there, and if your
script breaks another exec.
Puppet module containment
~~~~~~~~~~~~~~~~~~~~~~~~~
Fuel Library consists of many modules with a complex structure and
several dependencies defined between the provided modules.
There is a known Puppet problem related to dependencies between
resources contained inside classes declared from other classes.
If you declare resources inside a class or definition they will be
contained inside it and entire container will not be finished until all
of its contents have been evaluated.
For example, we have two classes with one notify resource each.::
class a {
notify { 'a' :}
}
class b {
notify { 'b' :}
}
Class['a'] -> Class['b']
include a
include b
Dependencies between classes will force contained resources to be executed in
declared order.
But if we add another layer of containers dependencies between them will not
affect resources declared in first two classes.::
class a {
notify { 'a' :}
}
class b {
notify { 'b' :}
}
class l1 {
include a
}
class l2 {
include b
}
Class['l1'] -> Class['l2']
include 'l1'
include 'l2'
This problem can lead to unexpected and in most cases unwanted behaviour
when some resources 'fall out' from their classes and can break the logic
of the deployment process.
The most common solution to this issue is **Anchor Pattern**. Anchors are
special 'do-nothing' resources found in Puppetlab's stdlib module.
Anchors can be declared inside top level class and be containd
inside as any normal resource. If two anchors was declared they can be
named as *start* and *end* anchor. All classes, that should be contained
inside the top-level class can have dependencies with both anchors.
If a class should go after the start anchor and before the end anchor
it will be locked between them and will be correctly containd inside
the parent class.::
class a {
notify { 'a' :}
}
class b {
notify { 'b' :}
}
class l1 {
anchor { 'l1-start' :}
include a
anchor { 'l1-end' :}
Anchor['l1-start'] -> Class['a'] -> Anchor['l1-end']
}
class l2 {
anchor { 'l2-start' :}
include b
anchor { 'l2-end' :}
Anchor['l2-start'] -> Class['b'] -> Anchor['l2-end']
}
Class['l1'] -> Class['l2']
include 'l1'
include 'l2'
This hack does help to prevent resources from randomly floating out of their
places, but look very ugly and is hard to understand. We have to use this
technique in many of Fuel modules which are rather complex and require such
containment.
If your module is going to work with dependency scheme like this, you could
find anchors useful too.
There is also another solution found in the most recent versions of Puppet.
*Contain* function can force declared class to be locked within its
container.::
class l1 {
contain 'a'
}
class l2 {
contain 'b'
}
Puppet scope and variables
~~~~~~~~~~~~~~~~~~~~~~~~~~
The way Puppet looks for values of variables from inside classes can be
confusing too. There are several levels of scope in Puppet.
**Top scope** contains all facts and built-in variables and goes from the
start of *site.pp* file before any class or node declaration. There is also a
**node scope**. It can be different for every node block. Each class and
definition start their own **local scopes** and their variables and resource
defaults are available their. **They can also have parent scopes**.
Reference to a variable can consist of two parts
**$(class_name)::(variable_name)** for example *$apache::docroot*. Class name
can also be empty and such record will explicitly reference top level scope
for example *$::ipaddress*.
If you are going to use value of a fact or top-scope variable it's usually a
good idea to add two colons to the start of its name to ensure that you
will get the value you are looking for.
If you want to reference variable found in another class and use fully
qualified name like this *$apache::docroot*. But you should remember that
referenced class should be already declared. Just having it inside your
modules folder is not enough for it. Using *include apache* before referencing
*$apache::docroot* will help. This technique is commonly used to make
**params** classes inside every module and are included to every other class
that use their values.
And finally if you reference a local variable you can write just *$myvar*.
Puppet will first look inside local scope of current class of defined type,
then inside parent scope, then node scope and finally top scope. If variable
is found on any of this scopes you get the first match value.
Definition of what the parent scope is varies between Puppet 2.* and Puppet
3.*. Puppet 2.* thinks about parent scope as a class from where current class
was declared and all of its parents too. If current class was inherited
from another class base class also is parent scope allowing to do popular
*Smart Defaults* trick.::
class a {
$var = a
}
class b(
$a = $a::var,
) inherits a {
}
Puppet 3.* thinks about parent scope only as a class from which current class
was inherited if any and doesn't take declaration into account.
For example::
$msg = 'top'
class a {
$msg = "a"
}
class a_child inherits a {
notify { $msg :}
}
Will say 'a' in puppet 2.* and 3.* both. But.::
$msg = 'top'
class n1 {
$msg = 'n1'
include 'n2'
}
class n2 {
notify { $msg :}
}
include 'n1'
Will say 'n1' in puppet 2.6, will say 'n1' and issue *deprication warning* in
2.7, and will say 'top' in puppet 3.*
Finding such variable references replacing them with fully qualified names is
very important part Fuel of migration to Puppet 3.*
Where to find more information
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The best place to start learning Puppet is Puppetlabs' official learning
course (http://docs.puppetlabs.com/learning/). There is also a special virtual
machine image you can use to safely play with Puppet manifests.
Then you can continue to read Puppet reference and other pages of Puppetlabs
documentation.
You can also find a number of printed book about Puppet and how to use it to
manage your IT infrastructure.
Pro Puppet
http://www.apress.com/9781430230571
Pro Puppet. 2nd Edition
http://www.apress.com/9781430260400
Puppet 2.7 Cookbook
http://www.packtpub.com/puppet-2-7-for-reliable-secure-systems-cloud-computing-
cookbook/book
Puppet 3 Cookbook
http://www.packtpub.com/puppet-3-cookbook/book
Puppet 3: Beginners Guide
http://www.packtpub.com/puppet-3-beginners-guide/book
Instant Puppet 3 Starter
http://www.packtpub.com/puppet-3-starter/book
Pulling Strings with Puppet Configuration Management Made Easy
http://www.apress.com/9781590599785
Puppet Types and Providers Extending Puppet with Ruby
http://shop.oreilly.com/product/0636920026860.do
Managing Infrastructure with Puppet. Configuration Management at Scale
http://shop.oreilly.com/product/0636920020875.do

View File

@ -81,14 +81,14 @@ Details on Cluster Provisioning & Deployment (via Facter extension)
Astute --> Naily: provisioned
Naily --> Nailgun: provisioned
Nailgun --> WebUser: status on UI
Astute -> MC: Create /etc/naily.facts
Astute -> MC: Create /etc/astute.yaml
Astute -> MC: run puppet
MC -> Puppet: runonce
Puppet -> Puppet_master: get modules,class
Puppet_master --> Puppet: modules, class
Puppet -> Facter: get facts
Facter --> Puppet: set of facts
Facter --> Puppet: set facts and parse astute.yaml
Puppet -> Puppet: applies $role
Puppet --> MC: done
@ -97,23 +97,27 @@ Details on Cluster Provisioning & Deployment (via Facter extension)
Naily --> Nailgun: deploy is done
Nailgun --> WebUser: deploy is done
Once deploy and provisioning messages are accepted by Naily, provisioining method is called in Astute.
Provisioning part creates system in Cobbler and calls reboot over Cobbler. Then
Astute uses `MCollective direct addressing mode <http://www.devco.net/archives/2012/06/19/mcollective-direct-addressing-mode.php>`_
to check if all required nodes are available,
include puppet agent on them. If some nodes are not ready yet, Astute waits for a few seconds and does request again.
When nodes are booted in target OS,
Astute uses naily_fact MCollective plugin to post data to a special file /etc/naily.fact on target system.
Data include role and all other variables needed for deployment. Then, Astute calls puppetd MCollective plugin
to start deployment. Puppet is started on nodes, and requests Puppet master for modules and manifests.
site.pp on Master node defines one common class for every node.
Accordingly, puppet agent starts its run. Modules contain facter extension, which runs before deployment. Extension
reads facts from /etc/naily.fact placed by mcollective, and extends Facter data with these facts, which can be
easily used in Puppet modules. Case structure in running class chooses appropriate class to import, based on $role
variable, received from /etc/naily.fact. It loads and starts to execute. All variables from file are available
like ordinary facts from Facter.
It is possible to use the system without Nailgun and Naily: user creates a YAML file with all required
data, and calls Astute binary script. Script loads data from YAML and instantiates Astute instance
the same way as it's instanciated from Naily.
Once deploy and provisioning messages are accepted by Naily, provisioining
method is called in Astute. Provisioning part creates system in Cobbler and
calls reboot over Cobbler. Then Astute uses `MCollective direct addressing
mode
<http://www.devco.net/archives/2012/06/19/mcollective-direct-addressing-mode.ph
p>`_
to check if all required nodes are available, include puppet agent on them. If
some nodes are not yet ready, Astute waits for a few seconds and tries to
request again. When nodes are booted in target OS, Astute uses upload_file
MCollective plugin to push data to a special file */etc/astute.yaml* on the
target system.
Data include role and all other variables needed for deployment. Then, Astute
calls puppetd MCollective plugin to start deployment. Puppet is started on
nodes, and requests Puppet master for modules and manifests. *site.pp* on
Master node defines one common class for every node.
Accordingly, puppet agent starts its run. Modules contain facter extension,
which runs before deployment. Extension reads data from */etc/astute.yaml*
placed by mcollective, and extends Facter data with it as a single fact, which
is then parsed by *parseyaml* function to create *$::fuel_settings* data
structure. This structure contains all variables as a single hash and
supports embedding of other rich structures such as nodes hash or arrays.
Case structure in running class chooses appropriate class to import,
based on *role* and *deployment_mode* variables found in */etc/astute.yaml*.