We're going to start things off by setting up dynamic environments with r10k. The main purpose of r10k is to manage your Puppet environments by mapping the branches of a version control repository (usually Git) to Puppet environments. In doing so we can take advantage of best practices in code development and get all the advantages of version control and apply them to Puppet environments.
Without further ado, let's get started!
We're going to set up a temporary working directory for this workshop. We'll
have a few directories for this: src
, for the source code that we'll be editing,
git, to emulate a workflow with a remote Git server, and puppet, where we'll be
actually deploying our Puppet code.
mkdir ~/r10k-workshop
cd ~/r10k-workshop
mkdir src git puppet
The vast majority of workflows with r10k rely on having some sort of central Git server. Services like GitHub, Bitbucket, and so forth are common but we want to avoid relying on the network for this workshop. To get around this we create a bare git repository that will behave like we're pushing to a remote Git server.
cd git
git init --bare environments.git
Now that we're created a central repository for our Puppet environments, we can now clone it and start working with it.
cd ../src
git clone ~/r10k-workshop/git/environments.git
cd environments
In order to make things a bit more simple later in the workshop we're going to
change the default modulepath for our environments. Instead of putting our
modules in the standard modules
directory we'll put them in site
. In
addition we want a 1:1 relationship between Git branches and Puppet environments
so we're going to rename our branch to "production". When that's done we can
push the initial code to our "central" git repository.
cat > environment.conf <<EOD
modulepath = site:modules
manifest = site.pp
EOD
git add environment.conf
git commit -m "Add environment.conf"
git branch -m production
git push -u origin production
Now that we have an environment configured how we want it, we need to start
populating it with content. We'll use puppet module generate
to scaffold out a
new module and add our own init.pp
with some simple content.
mkdir site
cd site
yes '' | puppet module generate ashpool/helloworld
mv ashpool-helloworld helloworld
cat > helloworld/manifests/init.pp <<EOD
class helloworld {
notify { "Hello world!": }
}
EOD
git add helloworld
git commit -m "Add helloworld module"
git push
cd ~/r10k-workshop/src/environments
cat > site.pp <<EOD
node default {
include helloworld
}
EOD
git add site.pp
git commit -m 'Add helloworld module to default node'
git push
So r10k is what this demo is all about, but we need to do a little bit of setup
beforehand. We need to tell r10k where to retrieve our environments and where to
put it. All this information is kept in r10k.yaml
, so we'll fill out that
configuration file and use it from here on out.
cd ~/r10k-workshop/puppet
mkdir environments
cat > r10k.yaml <<EOD
sources:
control:
basedir: "${PWD}/environments"
remote: "${PWD}/../git/environments.git"
EOD
In real world environments there are Puppet modules to manage your r10k configuration, such as zack/r10k (https://github.com/acidprime/r10k) so you don't need to manage the configuration of r10k by hand. In this workshop we're trying to show how everything fits together and avoid accessing the network, but if you're using r10k in a production environment you should consider using a Puppet module to manage this configuration.
cat > ~/.puppet/puppet.conf <<EOD
[main]
environmentpath = "${HOME}/r10k-workshop/puppet/environments"
EOD
So r10k is configured and we have our code in Git, all we need to do is actually run r10k.
r10k deploy -c r10k.yaml environment
By default r10k only prints messages that are of level WARN or greater, so if
you don't see anything printed then that probably means that everything is okay.
If you want to see more information about what's happening you can pass the
--verbose
flag to get more information
So r10k has run - let's see what was done!
ls environments
ls environments/production
cat environments/production/site/helloworld/manifests/init.pp
As you can see, we now have a directory called production
with the contents of
our production
branch in our environments directory. production
is the
default environment so when Puppet tries to fetch a catalog it will use this
environment by default.
Let's try using this new environment with puppet apply
.
puppet apply --environment production ~/r10k-workshop/puppet/environments/production/site.pp
Now that we've successfully created the production environment, let's try making some changes to that environment.
cd ~/r10k-workshop/src/environments/site
cat > helloworld/manifests/init.pp <<EOD
class helloworld {
notify { "Hello world!": message => "I am in the production environment"}
}
EOD
git add helloworld/manifests/init.pp
git commit -m "Update helloworld module to print environment"
git push
We've made a change to the production branch and pushed it; when we run r10k it will bring those changes down to our production environment.
cd ~/r10k-workshop/puppet
r10k deploy -c r10k.yaml environment
After r10k has run we can look at the file that we changed to verify the new contents.
cat environments/production/site/helloworld/manifests/init.pp
Since 'production' is the default environment, we can call puppet apply
without specifically passing the environment.
puppet apply ~/r10k-workshop/puppet/environments/production/site.pp
Making changes is all well and good, but one of the especially valuable things
about this whole process is the ability to rapidly create new environments and
deploy changes in isolation. As was shown earlier, creating a Git branch called
production
in Git created a corresponding Puppet environment. Just as easily
as we made the production environment, we can make a new Git branch that will be
isolated from the production branch.
cd ~/r10k-workshop/src/environments
git checkout -b ashpool_test
cd site
cat > helloworld/manifests/init.pp <<EOD
class helloworld {
notify { "Hello world!": message => "I am in the \${environment} environment"}
}
EOD
git add helloworld/manifests/init.pp
git commit -m "Update helloworld module for ashpool test environment"
git push -u origin ashpool_test
We've created a new branch and pushed it to the remote, let's run r10k to actually get that code in our environments directory.
In this example we're calling r10k deploy environment
with an argument -- the
name of the environment we want to deploy. If you invoke this command with no
arguments then all environments will be deployed; if you pass one or more
environment names then only those will be deployed. This allows us to deploy
exactly what we want, so we don't have to deploy every environment when we only
care about a single environment.
cd ~/r10k-workshop/puppet
r10k deploy -c r10k.yaml environment ashpool_test
So we've run r10k, let's inspect the aftermath.
ls environments
ls environments/ashpool_test
cat environments/ashpool_test/site/helloworld/manifests/init.pp
Huzzah! We now have two environments, each with their own version of the
helloworld
module.
We should be able to run puppet apply
against the new environment and receive
the updated code.
puppet apply --environment ashpool_test ~/r10k-workshop/puppet/environments/production/site.pp
On the 'ashpool_test' environment we made a snazzy change that will
automatically notify us of the environment being use, and we want to bring that
into production. We can use git merge
to bring the changes made in
ashpool_test into the production branch.
cd ~/r10k-workshop/src/environments
git checkout production
git merge ashpool_test
git push
You're probably sensing a trend that every time we make a change to Git, we need to run r10k. There are many ways to automate the execution of r10k automatically when changes are pushed to a Git repository, including a variety of post-receive hooks. For this workshop we're going to keep things simple and just run r10k by hand but know there are options available for automating this. In addition there will be a presentation on more advanced r10k workflows later today, be sure to check it out!
Let's run through the deploy/verify process to confirm that the merge actually added the changes to the production environment.
Deploy:
cd ~/r10k-workshop/puppet
r10k deploy -c r10k.yaml environment production
And inspect:
cat environments/production/site/helloworld/manifests/init.pp
puppet apply ~/r10k-workshop/puppet/environments/production/site.pp
Success!
All of the changes made to the ashpool_test environment are now in production so we no longer need that branch. We can delete the branch from our working Git repository and the remote repository.
cd ~/r10k-workshop/src/environments
git push origin :ashpool_test
git branch -d ashpool_test
Once it's been deleted we can run r10k and it will automatically delete any environments that are no longer in Git.
cd ~/r10k-workshop/puppet
r10k deploy -c r10k.yaml environment
And for the sake of thoroughness, we can verify that the ashpool_test environment is actually gone.
ls environments
One environment, just as expected.
As you manage more systems with Puppet, you'll probably want to start reusing existing modules instead of writing your own. Unfortunately trying to incorporate external modules into a Git repository have many pitfalls. Git subtrees and Git submodules have a large number of pitfalls and can break in very strange ways. In addition modules from the Puppet Forge cannot be cleanly incorporated into a Git repository without doing a bulk check in of the module. To address this problem, r10k supports the concept of a Puppetfile - a version control file that specifies a set of external modules that should be installed for a given environment.
In this workshop we will not be installing modules from the Puppet Forge, but it's as easy to use modules from the Forge as it is to install modules with Git.
When r10k installs modules specified in a Puppetfile, it installs them to the 'modules' directory relative to the Puppetfile. This means that we need to have multiple directories for our modules - the site directory where we added the helloworld module, and the new modules directory. When we created environment.conf earlier we added the 'modules' directory as well as site, so we can use it here.
To begin with, we need an external module that we can incorporate into our Puppet environments.
cd ~/r10k-workshop/src
yes '' | puppet module generate tessier/icebreaker
mv tessier-icebreaker icebreaker
cat > icebreaker/manifests/init.pp <<EOD
class icebreaker {
notify { "Hello icebreaker!": }
}
EOD
Now that we have our module created, we can version control it with Git.
cd icebreaker
git init
git add .
git commit -m "Initial commit of icebreaker module"
We also need to make a remote repository that we can push the module to.
cd ~/r10k-workshop/git
git init --bare tessier-icebreaker.git
cd ~/r10k-workshop/src/icebreaker
git remote add origin ~/r10k-workshop/git/tessier-icebreaker.git
git push -u origin master
Note that in this example we created the module first and then added it to Git,
while for the environment we created an empty repository and then cloned it.
Since we're using puppet module generate
to create the module instead of
creating it from scratch like we did earlier it's easier to do things in this
order.
Now that we have a standalone module in Git we can add it to our Puppet environments via a Puppetfile entry.
cd ~/r10k-workshop/src/environments
cat > Puppetfile <<EOD
mod "icebreaker", :git => "${PWD}/../../git/tessier-icebreaker.git"
EOD
git add Puppetfile
git commit -m "Add icebreaker to Puppetfile"
git push
Since we want to include this new module on our nodes, we'll need to update site.pp to include the module.
cd ~/r10k-workshop/src/environments
cat > site.pp <<EOD
node default {
include helloworld
include icebreaker
}
EOD
git add site.pp
git commit -m 'Add icebreaker module to default node'
git push
We have an external Puppet module ready to do and it's specified in our
Puppetfile, so we need to run r10k to deploy it. Since we're deploying an
environment that has a Puppetfile, we need to add the --puppetfile
option to
update the modules specified in the Puppetfile.
cd ~/r10k-workshop/puppet
r10k deploy -c r10k.yaml environment production --puppetfile
Now let's take a look at what was installed.
ls environments/production
ls environments/production/modules
cat environments/production/modules/icebreaker/manifests/init.pp
Inside of our environment we now have a modules
directory, which contains our
icebreaker module.
We should also be able to run puppet apply
to run this new module.
puppet apply ~/r10k-workshop/puppet/environments/production/site.pp
One of the things that Puppet modules tend to do is change and improve, so we need to be able to make changes to our icebreaker module and deploy those to our environments.
cd ~/r10k-workshop/src/icebreaker
cat > manifests/init.pp <<EOD
class icebreaker {
notify { "Hello Dorsett!": }
}
EOD
git add manifests/init.pp
git commit -m "Update icebreaker notify for Case"
git push
In our previous example, we were using r10k deploy environment --puppetfile
to update all modules in a given Puppetfile. However if you have a lot of
modules this can be rather slow. If you want to update a single module you can
use r10k deploy module
to update a specific set of modules.
cd ~/r10k-workshop/puppet
r10k deploy -c r10k.yaml module icebreaker
cat environments/production/modules/icebreaker/manifests/init.pp
When r10k installs Puppet modules from Git, it assumes that it should use the latest commit on the 'master' branch. This means that you can make changes to an external Puppet module and push changes to your environment without having to constantly update Git commits, like how Git submodules work. However this should be done for modules that are under development, and modules that are in a final state should be pinned to a commit or tag.
Let's run through how pinning a Git module works.
Before we can pin our icebreaker module, we need a Git ref that we can pin the module to. To make things easier for this demo, we'll create a Git tag.
cd ~/r10k-workshop/src/icebreaker
git tag 0.1.0 HEAD^ -a -m "Initial module release"
git push --tags
In this case we tagged the commit 'HEAD^', which is the second latest commit. We're doing this so that the master branch and the 0.1.0 tag point to different commits.
cd ~/r10k-workshop/src/environments
cat > Puppetfile <<EOD
mod "icebreaker", :git => "${PWD}/../../git/tessier-icebreaker.git", :ref => '0.1.0'
EOD
git add Puppetfile
git commit -m "Update Puppetfile for icebreaker 0.1.0"
git push
Since we made a change to the Puppetfile, we have to update both the environment and the Puppet module.
cd ~/r10k-workshop/puppet
r10k deploy -c r10k.yaml environment production --puppetfile
cat environments/production/modules/icebreaker/manifests/init.pp
And now the icebreaker module has gone from master to the 0.1.0 tag.
We can run puppet apply
to make sure that the expected module version is used.
puppet apply ~/r10k-workshop/puppet/environments/production/site.pp