Written by Jarek Grazabel, Lead DevOps Engineer at DevOpsGroup
Whether it’s pushing out a redesign or an important update, software is defined by rapid change and code is the lynchpin of this process. But for developers, updating it manually can be both timely and challenging.
That’s where code management systems can be enormously helpful. With them, it’s possible to make changes to computer code quickly and easily. And, at the same time, you’re able to understand these in context.
However, while it’s an established fact that code is best managed through versioning platforms such as GIT, things are less clear when it comes to using multiple roles or modules within configuration or deployment platforms.
Ideally, a consistent and effective approach involves allowing several people to work on individual modules and to test them in whole environments. Code management tools are incredibly useful for this process, and to demonstrate, I’ll take a look at Puppet’s r10k.
Puppet and r10k
Puppet r10k, which is also known as Robot10k, is one of the most popular code management tools out there. Before being adopted by Puppet Enterprise as a standalone piece of software, it was written in the Ruby coding language by software engineer Adrien Thebo, who struggled to name his creation.
As the author himself explains: “It’s called R10K because I’m terrible at names. When I started working on R10K I couldn’t come up with a decent name. While trying to come up with something clever, I recalled that Randall Munroe wrote a bot for controlling IRC chatter, and gave it the most generic name he could think of – Robot 9000. Since I just needed a name, any name, I decided to go with an equally generic name by incrementing the robot index.”
Quickly as I discovered r10k, I was impressed, and it solved a lot of issues in big infrastructure where you have large number of modules and people working on them. That also brought me closer to the detailed model of one repository per module, which is recommended as the best practice.
It’s easier to manage and code, as you can only version a single module, have a good representation of this versioning in git, and have a reference version of that module in the Puppet environment to make sure you can work on your chosen module version. In the testing stage, you can them tag the entire “environment suite” to contain all the modules. This was done thanks to the Puppetfile.
Installation and configuration
I found it pretty hard in the beginning. When I started with r10k (2016ish), I didn’t really know where to start and how to configure it; the documentation wasn’t my best friend. In Puppet 3, things were slightly more complicated as it didn’t have a concept of environment. However, I managed to work around this by configuring puppet.conf to be aware of the environments.
Puppet 4 changed that completely, and the concept of environments is there from start. I followed the Quick Start Guide and configured everything the way I wanted, but it didn’t work. There was a “hidden” –puppetfile flag required for each deployment to be added. Otherwise, r10k didn’t do anything. In the recent versions, it seems like this behaviour has changed, and even if you don’t provide this option, everything seems to be deployed correctly. Try to remember adding it, anyway.
In my Gitlab, I created a group for my puppet code – and all puppet repositories belong to it. My r10k.yaml configuration looks as follows:
--- cachedir: /var/cache/r10k sources: puppet: remote: firstname.lastname@example.org:puppet4/puppet4.git basedir: /etc/puppetlabs/code/environments invalid_branches: correct hiera: remote: email@example.com:puppet4/hieradata.git basedir: /etc/puppetlabs/code/hieradata invalid_branches: correct git: username: "git" provider: "rugged" private_key: "/etc/puppetlabs/puppetserver/ssh/r10k.rsa"
So, in terms of my config files, things are self-explanatory. I have two sources: one for Puppet code and one for hieradata. With Git, I used a rugged provider, which worked better in terms of SSH. I think this decision led me to use rugged as I was using gitlab SSH on a non-standard port number, and then I just left it like that. After a couple of deployments using r10k and my directory, the structure now looks like:
[root@puppetmaster ~]# ll /etc/puppetlabs/code/environments/ total 8 drwxr-xr-x. 6 root root 4096 Jun 6 2017 dev drwxr-xr-x. 6 root root 4096 Jun 25 10:35 production [root@puppetmaster ~]# ll /etc/puppetlabs/code/hieradata/ total 8 drwxr-xr-x. 3 root root 4096 Jun 25 13:32 dev drwxr-xr-x. 3 root root 4096 Jun 25 13:33 production
To achieve this, I actually removed the master branch from my git repositories, created a production branch, and made it default and protected.
In this example, my production branch for the first source (puppet) contains a directory containing site.pp, where all my node definitions are located. Puppetfile is in the main directory, too.
Puppetfile in production looks like:
forge 'http://forge.puppetlabs.com' # Modules discovered by generate-puppetfile mod 'camptocamp/systemd', '0.4.0' mod 'puppet/selinux', '1.1.0' mod 'puppet/staging', '2.2.0' mod 'puppet/zabbix', '4.1.1' mod 'puppetlabs/apache', '1.11.0' mod 'puppetlabs/apt', '2.4.0' mod 'puppetlabs/concat', '2.2.1' mod 'puppetlabs/firewall', '1.9.0' mod 'puppetlabs/mysql', '3.11.0' mod 'puppetlabs/pe_gem', '0.2.0' mod 'puppetlabs/postgresql', '4.9.0' mod 'puppetlabs/ruby', '0.6.0' mod 'puppetlabs/stdlib', '4.17.0' mod 'garethr-docker', '5.3.0' mod 'stahnma-epel', '1.2.2' mod 'rtyler-jenkins', '1.7.0' mod 'puppetlabs-java', '2.2.0' mod 'darin-zypprepo', '1.0.2' mod 'puppet-archive', '2.2.0' mod 'profiles', :git => 'firstname.lastname@example.org:puppet4/profiles.git', :branch => 'production'
All my modules come from Puppet Forge, but my profile modules were written by myself. It sets profiles for my nodes. I pointed it to my local git server and pointed that module to be taken from the production branch.
To demonstrate r10k in action, I will use “DOG” branch. I cloned my Puppet source branch into DOG and modified Puppetfile in my DOG branch to contain only my profile’s module.
forge 'http://forge.puppetlabs.com' mod 'profiles', :git => 'email@example.com:puppet4/profiles.git', :branch => 'DOG'
OK… let’s run r10k…
[root@puppetmaster ~]# r10k deploy environment DOG --verbose --puppetfile INFO -> Deploying environment /etc/puppetlabs/code/environments/DOG INFO -> Environment DOG is now at c3520a918a7e29245450322b31ee00610834e301 INFO -> Deploying Puppetfile content /etc/puppetlabs/code/environments/DOG/modules/profiles
Now, after this run, you should see your code deployed:
[root@puppetmaster ~]# tree /etc/puppetlabs/code/environments/DOG/ /etc/puppetlabs/code/environments/DOG/ ├── environment.conf ├── manifests │ └── site.pp ├── modules │ └── profiles │ ├── examples │ │ └── init.pp │ ├── Gemfile │ ├── manifests │ │ ├── base.pp │ │ ├── docker_grafana.pp │ │ ├── init.pp │ │ ├── jenkins.pp │ │ ├── mandatory_packages.pp │ │ └── zabbix.pp │ ├── metadata.json │ ├── Rakefile │ ├── README.md │ └── spec │ ├── classes │ │ └── init_spec.rb │ └── spec_helper.rb ├── Puppetfile └── README.md
Of course, my profiles won’t work as they depend on another modules, but it was just to demonstrate.
What’s so cool about this?
It’s easy to get excited about the tool when imagining you have lots of modules, or if there’s a new version of docker module available on Puppet Forge and you don’t just want to download it and hope it will work. But instead, you want to test it independently.
What you have to do is modify your Puppet file in the feature branch to reflect that module change. You can use a reference, git tag, commit point or branch name for your modules. For example:
mod 'profiles', :git => 'firstname.lastname@example.org:puppet4/profiles.git', :branch => 'DOG'
mod 'profiles', :git => 'email@example.com:puppet4/profiles.git', :tag => '1.2.beta'
mod 'profiles', :git => 'firstname.lastname@example.org:puppet4/profiles.git', :commit => 'c3520a918a7e29245450322b31ee00610834e301'
You can modify it and be as granular as you want in configuring your Puppetfile. The beauty is that if you have your branching strategies and code deployment strategies in place, and if you use tags (for example) for your own modules, there’s no risk. Even if someone makes changes to your production branch or tags it to the higher version, your modules won’t change for that environment.
Why? Because the tag won’t be referenced in Puppetfile. Unless you’re 100 percent confident about promoting the module to production, you’ll need to update Puppetfile in your production branch – referencing a new tag or commit point. You can wrap this all with your CI/CD pipeline and have automated r10k deployments as soon as a new Puppetfile is being pushed in the branch.
The next will come… Ansible Galaxy.