Puppet6 and Ubuntu18.04 - Control Repo with r10k and Hiera

This is a compilations of notes that leads to the setup of a control repo managed with r10k and the use of Hiera.

This article follows:


These notes were heavily inspired by various articles and documentation pages, notably:

Why Ubuntu 18.04 and not some generic article? Because I tested on an Ubuntu 18.04 and don’t know what would change for other distros (paths, packages, …)

Creating a control repo managed with Git: r10k


The goal these notes targets is simple: we want to use a version control system to manage our Puppet manifest.

“r10k” is a powerful tool to manage your Puppet manifests. It bases its comportment on the branches of a git repository; each branch corresponding to an environment. This makes testing new things really easy.



Install r10k on Puppet server:

# Well, installing gems with sudo is discouraged, but here, we need a system wide install
sudo gem install r10k

The control-repo


Clone the control repo template anywhere on your host:

git clone https://github.com/puppetlabs/control-repo
cd control-repo

Configure r10k configuration in /etc/puppetlabs/r10k/r10k.yamlto specify the different paths used:

# The location to use for storing cached Git repos
:cachedir: '/var/puppet/r10k/cache'

# A list of git repositories to create
  # This will clone the git repository and instantiate an environment per
  # branch in /usr/local/etc/puppet/environments
    remote: '/path/to/the/control-repo/.git'
    basedir: '/etc/puppetlabs/code/environments'

Copy your current manifest:

cp /etc/puppetlabs/code/environments/production/manifests/site.pp manifests/site.pp

Add the modules you previously used and the modules they need in the Puppetfile, present in the repository:

mod 'zleslie-ssh', '1.2.0'

# These modules should be included too, as zleslie-ssh needs them.
mod 'puppetlabs-stdlib', '6.0.0'
mod 'puppetlabs-concat', '6.0.0'

My first thought was “Erk! we have to manually specify the modules dependencies”, and then I was like “Ah, ok, maybe that way I won’t use obscure things to manage my infrastructure”.

Note for a fresh repo:

Maybe you don’t want to keep the template’s history, so you can remove the .git directory and re-create a new repo.

rm -rf .git
git init

# There's no "master" environment in our control repo
git checkout -b production

In any case, commit your work.


To deploy the changes in the control repo, use r10k from the control repo:

sudo r10k deploy production --verbose --puppetfile

The changes will be applied in the next 30 mins.

Automate “r10k deploy”

In order to automate the deployment of the changes done in the branches, we will configure a post-commit hook:

#!/usr/bin/env bash
branch=$(git rev-parse --abbrev-ref HEAD)

echo "Deploying environments ${branch} with r10k…"
sudo r10k deploy environment ${branch} --verbose --puppetfile

echo "Caching types for environments ${branch}…"
sudo /opt/puppetlabs/bin/puppet generate types --environment ${branch}

Note: If you use an online VCS for this repo, you may want to create a post-merge hook to, so it deploys on pull.

Each time a commit is done in a branch, the corresponding Puppet environment will be updated.

Each time a branch is pulled with changes, the corresponding Puppet environment will be updated.

You can make a change in a manifest and commit, to see if everything is still fine.

The role/profile organisation


When we’re speaking of file organisation, everyone has it’s idea on what is good, and what is not. I propose to follow the Puppet’s recommendations: the Role/Profile model:

If we look at the repo template we cloned earlier, we can see this structure:

|-- Puppetfile        <-- Where you put modules
|-- README.md
|-- data              <-- Hiera stuff, for later
|   `-- [...]
|-- environment.conf
|-- hiera.yaml
|-- manifests
|   `-- site.pp       <-- All your manifest, for now
|-- scripts
|   `-- [...]
`-- site-modules      <-- Where you should split your site.pp
    |-- profile
    |   `-- manifests
    |       |-- base.pp
    |       `-- example.pp
    `-- role
        `-- manifests
            |-- database_server.pp
            |-- example.pp
            `-- webserver.pp

You should edit a few files from the role and profile manifests to understand what’s going on.

Split site.pp

A profile manifest looks like:

We’re going to split site.pp to have something more roleprofilesque:

Now, let’s create a base profile. Puppet classes can inherit from other classes. It’s discouraged to do so, except for the roles, so we’re going to create a base_node role, and a puppet_server role, extending the base role.

Let’s update site.pp to use the different roles:

# Puppet server
node puppet {
  include role::puppet_server

# Some testing node
node puppet-node {
  include role::puppet_node

Cleaner, huh ?

Scratching the Hiera’s surface


Hiera is a powerful tool that allows to define some values, per-node.

Because maybe we wanted to use different SSH keys or different usernames for the node and server, we should use Hiera to do so.

Hiera data is stored in data, in the control-repo

|-- common.yaml            <-- Values for each nodes
`-- nodes
    `-- example-node.yaml  <-- Values for a specific node

As this is only an intro, we’re not doing anything fancy here, but we’ll specify an user and a public key for all the nodes. This will remove the hardcoded username and ssh key from our manifests.

First, we’ll have to change the user profile to accept variables:

class profile::user (
  String[1] $username,
  String[1] $ssh_key,
) {
  user { $username:
    ensure         => present,
    groups         => [
    shell          => '/bin/bash',
    home           => "/home/${username}",
    managehome     => true,
    password       => '*',
    purge_ssh_keys => true,

  $ssh_elements = $ssh_key.split(' ')

  ssh_authorized_key { $ssh_elements[2]:
    ensure => present,
    user   => $username,
    type   => $ssh_elements[0],
    key    => $ssh_elements[1],

And then, we tell Hiera which values to assign to the variables:

# data/common.yaml
profile::user::username: johndoe
profile::user::ssh_key: ssh-rsa SOME_LONG_KEY john_doe@somewhere

Commit and apply.


We now have a versioned control repo with a role/profile organisation. The deployment of the control repo is almost automated via git hooks and r10k.

What’s next?