Note: At the time of writing the latest version of open-source Puppet is 5.5, therefore in this article I am going to talk about this version only.
Here you will find an introduction to basic puppet setup, which would explain all the key points of how the work of the puppet master and slaves is organized. This article is best suitable for reading and doing at the same time. For that you will need two machines to represent the server and the agent based on CentOS 7. If you don't know how to do that, please refer to this article: Virtualization basics and an introduction to KVM and then come back here.
Puppet features master-slave architecture or, as some might call it, server-agent architecture. Indeed, some do call it so – even Puppet's own terminology refers to them as Puppet Server and Puppet agent.
Both the server and the agent are obtained from the same Puppet Platform repository [1]. To perform the installation execute the following command:
# rpm -Uvh https://yum.puppet.com/puppet5/puppet5-release-el-7.noarch.rpm
Now that we have the repository installed, let's begin installing Puppet Server [2]:
# yum install puppetserver
Since we are working on a CentOS machine, the package includes a systemd unit file for Puppet Server's service, which should be enabled and started:
# systemctl enable puppetserver
# systemctl start puppetserver
Apart from that, a firewalld service is also included in the distribution so the port on which Puppet Server listens for incoming connections from agents (8140) can be open by adding a puppetmaster service:
# firewall-cmd --add-service --permanent puppetmaster
# firewall-cmd --reload
After the successful installation of the server, the next step would be to install and setup our agent. Again, first Puppet Platform repository needs to be installed and then:
# yum install puppet-agent
After the required package has been installed, we need to specify where our puppet server is. The best way to do that would be to make your puppet server resolvable as puppet. This can be achieved by having both our machines in one domain and creating a DNS A-record with puppet subdomain for the server. For example, in my setup, the server is puppet.natucci.de (to be clear, it is actually dns.natucci.de, but also has another A-record puppet.natucci.de pointing to the same IP-address) and the agent is ipa.natucci.de. This allows me not to do anything in order to enable the agent to discover the server.
If you are following the instructions from the article I menioned in the beginning, you are using libvirt and so you should be able to use dnsmasq as your DNS – please refer to article under note number [5]. Otherwise, as a last resort, you can opt to specifying the IP-address to hostnames mapping in /etc/hosts and that is also sufficient.
After the server was made discoverable for our agent, we need to start the connection process of the two. For that, simply run puppet agent manually:
# puppet agent -t
Once this is done, the host should be listed among others that await their processing. This can be checked on the server by typing:
# puppet cert list --all
The host is represented by its X509 certificate, validity of which can be verified on our server. Type puppet cert print <agent's hostname> and compare to the actual certificate of the agent – openssl x509 -text -in /etc/puppetlabs/puppet/ssl/certs/<agent's hostname>.pem [4]. If the certificates match, you are safe to sign the certificate request:
# puppet cert sign <agent's hostname>
After signing, you will probably want to do another test run of the agent (puppet agent -t) to make sure the connection process has finished successfully, and enable the service that will do puppet runs automatically in the future:
# systemctl enable puppet
To see the service logs, use journalctl:
# journalctl -u puppet
At this point we have a working puppet setup – let's write our first module. But before we begin, I want to show a few useful commands that can come in handy when preparing and debugging a manifest.
puppet apply. With it you can debug puppet's language statements almost interactively without packaging a module:# puppet apply --execute 'file { "/tmp/february": ensure => present }'
Notice: Compiled catalog for dns.natucci.de in environment production in 0.01 seconds
Notice: /Stage[main]/Main/File[/tmp/february]/ensure: created
Notice: Applied catalog in 0.09 seconds
Default action for puppet apply is to apply the content of a file, by the way. Let's put the same code in /tmp/manifest.pp:
# puppet apply /tmp/manifest.pp
Notice: Compiled catalog for dns.natucci.de in environment production in 0.01 seconds
Notice: Applied catalog in 0.08 seconds
Nothing happened because the file is still where it was after the last to last run – success.
# puppet resource file /root/.vimrc
file { '/root/.vimrc':
ensure => 'file',
content => '{md5}2d92627a3a68889e8bcea951a6a1a258',
ctime => '2018-09-21 16:14:37 +0200',
group => 0,
mode => '0644',
mtime => '2018-09-18 15:01:30 +0200',
owner => 0,
selrange => 's0',
selrole => 'object_r',
seltype => 'admin_home_t',
seluser => 'system_u',
type => 'file',
}
# puppet resource service puppetserver
service { 'puppetserver':
ensure => 'running',
enable => 'true',
}
That can be useful when you want to see what attribute a given resource has, and what the current values of the attributes are. The command is extra useful because it prints out the state of a resource in puppet language, so that if you want to write a resource of your own, you can base it off an existing one by using puppet resource and then modifying the output accordingly.
puppet config print – outputs the effective configuration, which lets you understand why sometimes something works not the way you had expected it to (maybe using wrong environment?)Now, there is an exceptional step-by-step guide available on access.redhat.com on how to write a puppet module. Please refer to the chapter two in the article available in the footnote [6]. And because it is so great, here I will only emphasize the most important steps that the process of building a module consists of:
First of all, the module needs to be generated by using puppet module generate <module-name>.
As you write your manifests, there will be a need to ensure that the code you have written is syntactically and semantically correct. There are two main ways to check syntax: puppet-lint <file-or-path> and puppet parser validate <file>. The first command is distributed in a form of a ruby gem and in CentOS can be obtained from the default repository as rubygem-puppet-lint package [7]. The latter command is built-in in puppet. The semantics can be validated by writing tests with rspec. [8]
Finished puppet module needs to be built with puppet module build <path>. This command creates a pkg sub-directory containing an archive that is ready to be installed to the puppet server with puppet module install <path-to-module>.
These were some of the things that I discovered and found useful myself. But there is a whole website devoted to the knowledge every puppet master should be at least aware of: Puppet Cookbook [12] – an exceptionally useful website.
There are different ways of assigning classes to nodes. One of them is through site.pp. [9]
It can be done in global one: /etc/puppetlabs/puppet/manifests/site.pp or specific to the environment: /etc/puppetlabs/code/environments/production/manifests/site.pp
[root@dns ~]# head /etc/puppetlabs/code/environments/production/manifests/site.pp
node 'dns.natucci.de' {
class {'cloud::dns':} ->
class {'cloud::default':} ->
class {'hdp::default':} ->
class {'hdp::server':}
}