Puppet on Linode

One of the best things to come out of the Continuous Delivery movement is the concept of infrastructure as code. This is the idea that you should treat your servers in the same manner that you treat the programs you write to put on those servers. In other words, you should be able to programmatically specify what type of operating system you want on your server, which packages should be installed, and how they should be configured. Further, all of that should be code, and it should be stored in your version control system and treated with the same rigor as you treat the other code you write.

The movement towards cloud computing has helped this -- we typically no longer have physical servers that have a CD drive and a manually-attended installation. Also, the growth of a number of tools that support this concept (such as Puppet, Chef, Ansible, and SaltStack) have helped the adoption of this concept spread.

To help my learning in this arena, I set up a couple of Linodes earlier this month, installed Puppet on them, and started playing.

Unfortunately, while I was able to find some information about how to accomplish this, some of the Linode docs were outdated. So I'm creating this blog post to help me remember what I did to get up and running.

Create Linodes

I needed two Linode servers; one to be the Puppet master node, and one to be the slave. More on that in a bit. For this, I logged into my Linode account in my browser and added 2 Linode 1024 nodes. In the "Settings" tab for them, I named one of the VMs "puppetmaster", and the other "puppetslave." This name will only help you identify the nodes in your Linode manager in your browser...It has no effect on the machine itself. My go-to operating system is Ubuntu, so that's what I installed on them.

Set Hostnames

Once the Linodes booted, I ensured that the machines each knew who they were supposed to be. For example, on the master node:

[root@puppet ~]$ cat /etc/hosts
127.0.0.1   puppet.mavelo.us puppet localhost
127.0.1.1   ubuntu

# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

The only change I made here was to the first line: added puppet.mavelo.us and puppet before localhost. And, of course, I made a similar change on the slave node.

Set up DNS "A" Records

For my DNS, I use DNSimple. I logged into my account and added "A" records through the web interface to point puppet.mavelo.us and puppetslave.mavelo.us to the newly-created Linodes.

Install Puppet

Now it's time to actually install Puppet on each of the servers. On the master, login to the server as root and run:

apt-get install puppetmaster

On the slave, login as root and run:

apt-get install puppet

Create and Sign a Certificate

Puppet uses self-signed SSL certificates to communicate between master and slave. This process starts by having the slave agent create a certificate request, and then signing that certificate on the master node. Because the name of the puppet master is actually puppet (as above), the slave will automatically know where the master node is (the slave communicates by default with puppet.your.domain.)

So, on the slave, create the certificate request:

puppet agent -t

Then, on the master node, you need to sign the certificate request:

puppet cert list
puppet cert sign <name>  (for example: puppet cert sign puppetslave.mavelo.us)

Create a Manifest

Now it's time to actually start using Puppet. To start, you'll need to create a simple manifest file, called site.pp. This file lives, by default, at /etc/puppet/manifests/site.pp:

node default {
        package { 'tcsh':
                ensure => 'installed'
        }

        group { 'mike':
                ensure => present,
        }

        user { 'mike':
                ensure     => present,
                gid        => 'mike',
                groups     => [ 'sudo', 'users' ],
                shell      => '/usr/bin/tcsh',
                home       => '/home/mike',
                managehome => true,
        }
}

This will install my favorite shell, tcsh, create a group for me, and also create a user. My user will have a default shell of tcsh, add me to the sudo and mike groups (mike group is the default), and create my home directory at /home/mike.

Run Puppet on the Slave

By default, Puppet on the slave is probably already running. However, it only checks in with the master for changes once every 30 minutes. I want to check it on-demand. So I killed all running puppet processes via a judicious kill -9 command. After Puppet was stopped, I ran Puppet manually:

[root@puppetslave ~]$ puppet agent --test
Info: Retrieving plugin
Info: Caching catalog for puppetslave.mavelo.us
Info: Applying configuration version '1425162886'
Notice: /Stage[main]/Main/Node[default]/Group[mike]/ensure: created
Notice: /Stage[main]/Main/Node[default]/User[mike]/ensure: created
Notice: /Stage[main]/Main/Node[default]/Package[tcsh]/ensure: ensure changed 'purged' to 'present'
Notice: Finished catalog run in 3.22 seconds

And just like that, I had a server that was configured to have the packages and users on it that I wanted, configured in the way that I wanted, through something that looks an awfully lot like code.

Conclusion

This whole infrastructure-as-code thing is a very powerful concept. This blog post isn't the end-all-be-all of Puppet or anything like that, and it isn't meant to be. There are many other resources out there for Puppet and any of the other topics covered here, I'm sure. But it scratches my itch.

I have a couple of different follow-ups to this post that I'd like to do, so keep your eyes peeled. I hope to get to them over the next couple of weeks!