Solving real world problems with Jerakia

Background

I’ve always been a great admirer of Hiera, and I still remember the pain and turmoil of living in a pre-Hiera world trying to come up with insane code patterns within Puppet to try and organize my data in a sensible way. Hiera was, and still is, the answer to lots of problems.

For me however, when I moved beyond a small-scale, single-customer orientated Puppet implementation into larger, complex and diverse environments I started to find that I was spending a lot of time trying to figure out how to model things in Hiera to meet my use requirements. It’s a great tool but it has some limitations in the degree of flexibiity it offers around how to store and look up your data.

Some examples of problems I was trying to solve were; How can I…use a different backend for one particular module?give a team a separate hierarchy just for their app?give access to a subset of data to a particular user or team?enjoy the benefits of eyaml encryption without having to use YAML?implemenet a dynamic hierarchy rather than hard coding it in config?group together applciation specific data into separate YAML files?

There are many more examples, and after some time I began exploring some solutions. Initially I started playing around with the idea of a “smart backend” to hiera that could give me more flexibility in my implementation and that eventually grew into what is now Jerakia. In fact, you can still use Jerakia as a regular Hiera backend, or you can wire it directly into Puppet as a data binding terminus.

Introducing Jerakia

Jerakia is a lookup tool that has the concept of a policy, which contains a number of lookups to perform. Policies are written in Ruby DSL allowing the maximum flexibility to get around those pesky edge cases. In this post we will look at how to deploy a standard lookup policy and then enhance it to solve one of the use cases above.

Define a policy

After installing Jerakia the first setup is to create our default policy in /etc/jerakia/policy.d/default.rb

123policy :default do end

Jerakia policies are containers for lookups. A policy can have any number of lookups and they are run in the order they are defined

Writing our first lookup

A lookup must contain, at the very least, a name and a datasource to use for the lookup. The current datasource that ships with Jerakia is the file datasource. This takes several options, including format and searchpath to define how lookups should be processed. Within the lookup we have access to scope[] which contains all the information we need to determine what data should be returned. In Puppetspeak, the scope contains all facts and top-level variables passed from the agent

123456789101112131415policy :default do   lookup :main, do    datasource :file, {      :format     => :yaml,      :docroot    => “/var/lib/jerakia”,      :searchpath => [        “hostname/#{scope[:fqdn]}”,        “environment/#{scope[:environment]}”,        “common”      ],    }  end end

We now have a fairly standard lookup policy which should be fairly familar to Hiera users. A Jerakia lookup request contains two parts, a lookup key and a namespace. This allows us to group together lookup keys such as portdocroot and logroot into a namespace such as apache. When integrating from Hiera/Puppet, the module is used for the namespace, and the variable name for the key. In Puppet we declare;

12345class apache (  $port,) {  …}

This will reach Jerakia as a lookup request with the key port in the namespace apache, and with our lookup policy above a typical request would look for the key “port” in the following files, in order

123/var/lib/jerakia/hostname/foo.craigdunn.org/apache.yaml/var/lib/jerakia/environment/dev/apache.yaml/var/lib/jerakia/common/apache.yaml

This is slightly different behaviour than you would find in Puppet using Hiera, if you are using Jerakia against an existing Hiera filesystem layout which has namespace::key in path, rather than key in path/namespace.yaml then check out the hiera plugin which provides a lookup method called plugin.hiera.rewrite_lookup to mimic hiera behaviour. More on lookup plugins in the next post!

Adding some complexity

So far what we have done is not rocket science, and certainly nothing that can’t be easily achieved with Hiera. So let’s mix it up a bit by defining a use case that will change our requirements. This use case is based on a real world scenario.

We have a team based in Ireland. Their servers are identified with the top level variable location. They need to be able to manage PHP and Apache using Puppet, but they need a data lookup hierarchy based on their project, which is something only they use. Furthermore, we wish to give them access to manage data specifically for the modules they are responsible for, without being able to read, override or edit data for other modules (eg: network, firewall, kernel).

So, the requirements are to provide a different lookup hierarchy for servers that are in the location “ie”, but only when configuring the apache or php modules, and to source the data from a different location separate from our main data repo. With Jerakia this is easily solvable, lets first look at creating the lookup for the Ireland team…

1234567891011121314151617181920212223242526policy :default do   lookup :ireland do    datasource :file, {      :format     => :yaml,      :docroot    => “/var/external/data/ie”,      :searchpath => [        “project/#{scope[:project]}”,        “common”,      ]    }  end   lookup :main, do    datasource :file, {      :format     => :yaml,      :docroot    => “/var/lib/jerakia”,      :searchpath => [        “hostname/#{scope[:fqdn]}”,        “environment/#{scope[:environment]}”,        “common”      ],    }  end end

So now we have defined a separate lookup for our Ireland based friends. The problem here is that every request will first load the lookup ireland and then proceed down to the main lookup. This is no different than just adding new hierarchy entries in hiera, they are global. This means potentially bad data creeping in, if for example they accidentally override the firewall rules or network configuration.

To get around this we can use the confine method in the lookup block to restrict this lookup to requests that have “location: ie” in the scope, and are requesting keys in the apache or php namespaces, meaning the requesting modules. If the confine criteria is not met then the lookup will be invalidated and skipped, and the next one used. Finally, we do not want to risk dirty configuration from default values that we have in our hierarchy for apache and php, so we need to tell Jerakia that if this lookup is considered valid (eg: it has met all the criteria of confine) then only use this lookup and don’t proceed down the chain of available lookups. To do this, we use the stop method.

The confine takes two arguments, a value and a match. The match is a string that can contain regular expressions. The confine method supports either a single match, or an array of matches to compare. So in order to confine this lookup to the location ie we can confine it as follows

1confine scope[:location], “ie”

By confining in this way we tell Jerakia to invalidate and skip this lookup unless location is “ie”. Similarly we can add another confine statement to ensure that only lookups for the apache and php namespaces are handled by this lookup. Our final policy would look like this:

123456789101112131415161718192021222324252627282930313233343536policy :default do   lookup :ireland do    datasource :file, {      :format     => :yaml,      :docroot    => “/var/external/data/ie”,      :searchpath => [        “project/#{scope[:project]}”,        “common”,      ]    }     confine scope[:location], “ie”     confine request.namespace[0], [      “apache”,      “php”,    ]     stop   end   lookup :main, do    datasource :file, {      :format     => :yaml,      :docroot    => “/var/lib/jerakia”,      :searchpath => [        “hostname/#{scope[:fqdn]}”,        “environment/#{scope[:environment]}”,        “common”      ],    }  end end

Conclusion

This example demonstrates that using Jerakia lookup policies you can tailor your data lookups quite extensivly giving a high amount of flexibility. This is especially useful in larger organisations with many customers using one central Puppet infrastructure.

This is just one example of using Jerakia to solve a use case, I hope to blog a small mini-series on other use cases and solutions, and welcome any suggestions that come from the real-world!

Next up…

Jerakia is still fairly experimental at the time of writing (0.1.6) and there is still a lot of room for improvement both in exposed functionality and in the underlying code. I’d like to see it mature and there are still plenty of features to add, and code to be tidied up. There is some excellent work being done in Puppet 4.0 with regards to internal handling of data lookups that I think would complement our aims very well (currently all work has been done against 3.x) and the next phase of major development will be exploring these options.

Also, I talk about Puppet a lot because I am a Puppet user and the problems that I were trying to solve were Puppet/Hiera related, that doesn’t mean that Jerakia is exclusively a Puppet tool. The plan is to integrate it with other tools in the devops space, which given the policy driven model should be fairly straightforward.

My next post will focus on extending Jerakia and will cover writing and using lookup plugins to enhance the power of lookups and output filters to provide features like eyaml style decryption of data regardless of the data source. I will also cover Jerakia’s pluggable datastore model that encourages community development.Follow and share if you liked this

Subscribe to Craig Dunn

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe