301 lines
10 KiB
Markdown
301 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!
|