300 lines
10 KiB
Markdown
300 lines
10 KiB
Markdown
---
|
||
layout: post
|
||
title: 'Intro to Puppet: The Bare Minimum'
|
||
tag:
|
||
- puppet
|
||
- technical
|
||
---
|
||
|
||
Last month, some of my coworkers were looking for a brief introduction to
|
||
[Puppet](http://www.puppetlabs.com/). Puppet is a type of configuration manager
|
||
for your servers. It allows you to create definitions of your server that can
|
||
then be automatically maintained. Puppet is mostly self documenting so it makes
|
||
it easy to know what your servers are doing while giving you a great way to
|
||
automate setting up large numbers of servers.
|
||
|
||
This is that brief talk. All code is available on
|
||
[Github in my puppet-walkthru repository](https://github.com/atomaka/puppet-walkthru).
|
||
You will need [Git](http://www.git-scm.com/),
|
||
[Virtual Box](https://www.virtualbox.org/) and
|
||
[Vagrant](http://www.vagrantup.com/) installed. To begin, clone the repository
|
||
and launch the Vagrantfile:
|
||
|
||
```
|
||
git clone https://github.com/atomaka/puppet-walkthru.git
|
||
cd puppet-walkthru
|
||
vagrant up
|
||
```
|
||
|
||
This will setup a virtual machine on your computer with Puppet installed. All
|
||
code can be found on the virtual machine in the /vagrant directory.
|
||
|
||
```
|
||
vagrant ssh
|
||
sudo su cd /vagrant
|
||
```
|
||
|
||
You are now ready to work through the first example.
|
||
|
||
## 1. Managing Users
|
||
|
||
Puppet maintains state on your computer using what are referred to as
|
||
[resources](http://docs.puppetlabs.com/references/latest/type.html). The
|
||
built-in resources provided by Puppet provide a good start. In
|
||
[example one](https://github.com/atomaka/puppet-walkthru/blob/master/manifests/1-user-type.pp),
|
||
you can see how to use a Puppet resource to add and remove a user.
|
||
|
||
```
|
||
user { 'tm':
|
||
ensure => present,
|
||
}
|
||
user { 'fowlks':
|
||
ensure => absent,
|
||
}
|
||
```
|
||
|
||
You can run this code on your virtual machine with
|
||
`puppet apply manifests/1-user-type.pp`. Afterward, you should notice that the
|
||
user "tm" exists on your system.
|
||
|
||
The [user resource](http://docs.puppetlabs.com/references/latest/type.html#user)
|
||
type manages local users on your system. This works on a wide variety of
|
||
systems, although some do not support some of the more specific features. In
|
||
this example, we make sure the user "tm" is present on the system and make sure
|
||
the user "fowlks" is not present.
|
||
|
||
ensure is a keyword for all Puppet resources. present and absent are the most
|
||
common values although some resource types have others. ensure will make sure
|
||
that definition exists on your server and absent will obviously do the opposite.
|
||
|
||
## 2. Managing Files
|
||
|
||
Managing files is one of the most common tasks for server administration and
|
||
Puppet offers many ways to handle this. We’ll explore these in the
|
||
[next example](https://github.com/atomaka/puppet-walkthru/blob/master/manifests/2-file-type.pp).
|
||
|
||
```
|
||
file { '/tmp/test1.txt':
|
||
ensure => present,
|
||
content => 'Hello',
|
||
}
|
||
file { '/tmp/test2.txt':
|
||
ensure => present,
|
||
source => '/vagrant/files/test2.txt',
|
||
}
|
||
$something = "Hello"
|
||
file { '/tmp/test3.txt':
|
||
ensure => present,
|
||
content => template('/vagrant/templates/test3.txt.erb'),
|
||
}
|
||
```
|
||
|
||
Run this on your virtual machine using `puppet apply manifests/2-file-type.pp`
|
||
and you should be alerted that three files were created. You can verify this by
|
||
viewing the contents of the tmp directory with `ls /tmp`.
|
||
|
||
The first
|
||
[file resource](http://docs.puppetlabs.com/references/latest/type.html#file)
|
||
simply creates a file at the specified location that says "Hello."
|
||
Unfortunately, this isn’t very useful since we do not want to have to type our
|
||
entire cnfiguration file in our Puppet definition. The second resource is
|
||
slightly more useful. This allows us to copy a file from our Puppet repository
|
||
to a specified location.
|
||
|
||
Finally, we can also create templates. The last example uses a file from our
|
||
repository and copies it to the specified location. However, we can also include
|
||
variables that can be used in our file. In this case, we set a variable to
|
||
something and it is then displayed in the file: `You said: Hello`. The contents
|
||
of `$something` are used in the file.
|
||
|
||
## 3. Installing Packages
|
||
|
||
The last common task we’ll look at is installing packages. Puppet provides a way
|
||
to define which
|
||
[packages](http://docs.puppetlabs.com/references/latest/type.html#package) can
|
||
be installed. By default, this uses your distributions built-in package manager
|
||
although there are ways to specify various providers. Our example
|
||
[shows the most basic usage](https://github.com/atomaka/puppet-walkthru/blob/master/manifests/3-package-type.pp).
|
||
|
||
```
|
||
package { 'vim':
|
||
ensure => present,
|
||
}
|
||
package { 'alpine-pico':
|
||
ensure => absent,
|
||
}
|
||
```
|
||
|
||
Try to open vim and you will notice it cannot run. Once you run this code with
|
||
`puppet apply manifests/3-package-type.pp`, the vim package will then be
|
||
present.
|
||
|
||
## 4. Ordering (or lack thereof)
|
||
|
||
The trickest thing for beginners to Puppet is dealing with its
|
||
[non-deterministic behavior](http://puppetlabs.com/blog/inside-puppet-about-determinism).
|
||
This is easier to
|
||
[show than explain](https://github.com/atomaka/puppet-walkthru/blob/master/manifests/4-order-example.pp).
|
||
|
||
```
|
||
notify { 'First': }
|
||
notify { 'Second': }
|
||
notify { 'Third': }
|
||
notify { 'Fourth': }
|
||
notify { 'Fifth': }
|
||
notify { 'Sixth': }
|
||
notify { 'Seventh': }
|
||
```
|
||
|
||
When run, you would expect this to spit out First, Second, …, Seventh in order.
|
||
Invoke this code with `puppet apply manifests/4-order-example.pp` and be
|
||
surprised at the results. The order of the code is much different than what is
|
||
in the file. Furthermore, if you were to add `notify { 'Eighth': }` the ordering
|
||
might change completely.
|
||
|
||
## 5. But I Need Order
|
||
|
||
But there are dependencies when setting up systems. Puppet allows for this, you
|
||
just are required to
|
||
[explicitly define them](https://github.com/atomaka/puppet-walkthru/blob/master/manifests/5-ordered-example.pp).
|
||
The biggest advantage here is that if one line of dependencies fails, your
|
||
entire configuration does not. It takes some getting used to and can be
|
||
frustrating, but it is worth it.
|
||
|
||
```
|
||
notify { 'First': }
|
||
notify { 'Second':
|
||
require => Notify['First'],
|
||
}
|
||
notify { 'Third':
|
||
require => Notify['Second'],
|
||
}
|
||
notify { 'Fourth':
|
||
require => Notify['Third'],
|
||
}
|
||
notify { 'Fifth':
|
||
require => Notify['Fourth'],
|
||
}
|
||
notify { 'Sixth':
|
||
require => Notify['Fifth'],
|
||
}
|
||
notify { 'Seventh':
|
||
require => Notify['Sixth'],
|
||
}
|
||
```
|
||
|
||
By using the `require` parameter, we have have forced ordering. If you run this
|
||
code with `puppet apply manifests/5-ordered-example.pp`, you will see the order
|
||
you expected in example number four.
|
||
|
||
## 6. Know Your Environment
|
||
|
||
Puppet also provides a way for you to know about the system that the Puppet code
|
||
is running on with a system called Facter.
|
||
|
||
```
|
||
notify { "${::osfamily}": }
|
||
notify { "${::ipaddress}": }
|
||
notify { "${::uptime}": }
|
||
```
|
||
|
||
When run with `puppet apply manifests/6-facts-example.pp`,
|
||
[this code](https://github.com/atomaka/puppet-walkthru/blob/master/manifests/6-facts-example.pp)
|
||
will display the information about the virtual machine you are running on. We
|
||
will look at why this is useful later.
|
||
|
||
## 7. Doing Something Useful
|
||
|
||
Now that we have seen some quick forced examples of how to use Puppet, we now
|
||
have enough knowledge to do something that is actually useful. Using Puppet, we
|
||
can
|
||
[configure an entire service](https://github.com/atomaka/puppet-walkthru/blob/master/manifests/7-full-example.pp).
|
||
If you are not familiar, [NTP](http://www.ntp.org/) is a networking protocol for
|
||
time management. It is useful for mainitaining the same system time across all
|
||
of your servers. And we can use Puppet to install it!
|
||
|
||
```
|
||
package { 'ntp':
|
||
ensure => present,
|
||
}
|
||
file { '/etc/ntp.conf':
|
||
ensure => present,
|
||
require => Package['ntp'],
|
||
source => '/vagrant/files/ntp.conf.debian',
|
||
}
|
||
service { 'ntp':
|
||
ensure => running,
|
||
enable => true,
|
||
subscribe => File['/etc/ntp.conf'],
|
||
}
|
||
```
|
||
|
||
When running this code with `puppet apply manifest/7-full-example.pp`, you
|
||
should notice three things happen. First, the ntp package will be installed.
|
||
Since we are on Ubuntu, this is done using apt-get. Secondly, a configuration
|
||
file was copied from our Puppet repository to the location specified. Finally,
|
||
the ntp service was started.
|
||
|
||
Install, configure, start is one of the most common patterns in Linux/UNIX
|
||
systems administration and we can easily automate it with Puppet.
|
||
|
||
Something to note is our use of subscribe when using the
|
||
[service resource](http://docs.puppetlabs.com/references/latest/type.html#service)
|
||
type. This makes sure that the ntp service is restarted only if the
|
||
configuration file has changed.
|
||
|
||
## 7. Managing Multiple Operating Systems
|
||
|
||
Before this section, be sure to reset what we did in the previous example by
|
||
running bash support/cleanup7.sh. We just need to uninstall ntp and our config
|
||
file so we can do it all again.
|
||
|
||
Unfortunately, our environments are never uniform and we are stuck dealing with
|
||
different versions of operating systems. Luckily, we have tools that we can use
|
||
to deal with it. We touched on this in section six, but now we will actually use
|
||
them
|
||
[to install ntp again](https://github.com/atomaka/puppet-walkthru/blob/master/manifests/8-independent-example.pp).
|
||
This time, our code will work on both Debian and RedHat family Linux
|
||
distributions.
|
||
|
||
```
|
||
case $::osfamily {
|
||
'RedHat': {
|
||
$service = 'ntpd'
|
||
$conf = 'ntp.conf.redhat'
|
||
}
|
||
'Debian': {
|
||
$service = 'ntp'
|
||
$conf = 'ntp.conf.debian'
|
||
}
|
||
}
|
||
notify { 'OS Information':
|
||
message => "${::osfamily}: Setting service to ${service} and conf to ${conf}",
|
||
before => Package['ntp'],
|
||
}
|
||
package { 'ntp':
|
||
ensure => present,
|
||
}
|
||
file { '/etc/ntp.conf':
|
||
ensure => present,
|
||
require => Package['ntp'],
|
||
source => "/vagrant/files/${conf}",
|
||
}
|
||
service { $service:
|
||
ensure => running,
|
||
enable => true,
|
||
subscribe => File['/etc/ntp.conf'],
|
||
}
|
||
```
|
||
|
||
When on our Ubuntu virtual machine, running this code with
|
||
`puppet apply manifest/8-independent-example.pp` will setup NTP just as we did
|
||
in example seven. However, this code can also run on RedHat / CentOS machines
|
||
without any adjustments.
|
||
|
||
This is handled using the facts we discussed in section six. We check to see
|
||
what distribution we are using with `$::osfamily` and make choices based on
|
||
that. Since the service and the config file are different on RedHat, we assign
|
||
variables to these and adjust them as needed.
|
||
|
||
You now have enough knowledge to get started with Puppet!
|