Hiera is Puppet's
builtin key/value data lookup system, where we can store the data we use to configure our system. It has some peculiar characteristics:
-
It's hierarchical: We can configure different hierarchies of data sources and these are traversed in order to find the value of the desired key, from the level at the top, to the one at the bottom. This is very useful to allow granular configurations of different settings for different groups of servers
-
It has a modular backend system: data can be stored on different places, according to the used plugins, from simple
Yaml
orJson
files, toMongoDb
,Mysql
,PostgreSQL
,Redis
and others. In ourcontrol-repo
we use the Hiera-eyaml backend which uses plain Yaml files for data storage and allows encryption of the values of selected keys (typically the ones which contains passwords or secrets)
Hiera
is important because it allows to assign values to the parameters of Puppet
classes.
A parameter called server
of a class called ntp
, for example, can be evaluated via a lookup of the Hiera
key ntp::server
:
class ntp (
String $server = 'ntp.pool.org'
) { ... }
Given the above class we can override the default value for the server
parameter of the class ntp
with a similar entry in one of the .yaml
files used in Hiera's
hierarchies:
---
ntp::server: 'time.nist.gov'
This is useful to cleanly separate our Puppet code
, where we declare, inside classes, the resources we want to apply to our nodes, from the data
which defines how these resources should be.
In Puppet 4.9
, Hiera version 5
has been introduced and this is the version we use in our control-repo
.
Hiera's
configuration file (hiera.yaml
) has changed format in version 5, here's the default, which uses the core Yaml
backend and has only a layer called common:
---
version: 5
hierarchy:
- name: Common # A level of the hierarchy. They can be more using different data sources
path: common.yaml # The path of the file, under the datadir, where data is stored
defaults:
data_hash: yaml_data # Use the YAML backend
datadir: data # Yaml files are stored in the data dir of our Puppet environment/control-repo
Here's the actual structure of the hiera.yaml
file used in this control-repo
, it uses the hiera-eyaml
backend and has various levels in its hierarchy:
---
version: 5
defaults:
datadir: data
data_hash: yaml_data
hierarchy:
- name: "Eyaml hierarchy"
lookup_key: eyaml_lookup_key # eyaml backend
paths:
- "nodes/%{trusted.certname}.yaml"
- "role/%{::role}-%{::env}.yaml"
- "role/%{::role}.yaml"
- "zone/%{::zone}.yaml"
- "common.yaml"
options:
pkcs7_private_key: /etc/puppetlabs/puppet/keys/private_key.pkcs7.pem
pkcs7_public_key: /etc/puppetlabs/puppet/keys/public_key.pkcs7.pem
The key infos we can get from it are:
-
The
eyaml-backend
is used (lookup_key: eyaml_lookup_key
) -
It's
public
andprivate keys
are stored in the directory/etc/puppetlabs/puppet/keys/
. They have to be copied there wherever we runPuppet
to compile a catalog which uses encrypted data: typically on thePuppet Server
, on theVagrant VMs
where we usePuppet
for local testing during development, and eventually also on theVagrant VMs
used inCI
. These keys are not present in thiscontrol-repo
(otherwise it would defy the whole concept of using encryption to protect sensitive data). -
The
Yaml
files containing ourHiera
data are placed in the directory data (datadir: data
) in ourcontrol-repo
/Puppet environment
directory. So, for example, for productionPuppet
environment, all theYAML
files are under/etc/puppetlabs/code/environments/production/data
-
The hierarchy is based on several
paths
under thedatadir
. Variables are used there (%{varname}
). Hierarchy goes from the most specific path:nodes/%{::trusted.certname}.yaml
which refers to a specific node to the most generic (common.yaml
) which is used as default value for keys which are not set at higher levels.
For full reference on the format of Hiera 5
configuration file, check the Official Documentation
Hiera 4
, used from Puppet
versions 4.3 to 4.8, introduced the possibility of defining, inside a module, the default values of each class parameter using Hiera
.
The actual user data, outside modules, was configured by a global /etc/puppetlabs/puppet/hiera.yaml
file, which defines Hiera
configurations for every Puppet environment
.
With Hiera 5
is possible to have environment specific configurations, so we can have a hiera.yaml
inside a environment directory which may be different for each environment:
/etc/puppetlabs/code/environments/$environment_name/hiera.yaml
So, for the production environment
:
/etc/puppetlabs/code/environments/production/hiera.yaml
This is useful to test hierarchies or backend changes before committing them to the production environment
.
We can have also per module configurations, so in a NTP module
, for example, we can have a:
$module_path/users/hiera.yaml
with the, now familiar, version 5 syntax:
---
version: 5
defaults:
datadir: data
data_hash: yaml_data
hierarchy:
- name: "In module hierarchy"
paths:
- "%{facts.virtual}.yaml"
- "%{facts.os.name}-%{facts.os.release.major}.yaml"
- "%{facts.os.name}.yaml"
- "%{facts.os.family}-%{facts.os.release.major}.yaml"
- "%{facts.os.family}.yaml"
- "common.yaml"
this refers .yaml
files under the data
directory of the module.
The interesting thing in this is that we have a uniform and common way to lookup for data, across the three layers: global
, environment
and module
: each hierarchy of each layer is used to compose a "super hierarchy" which is traversed seamlessly.
In the module data is also possible to define the kind of lookup to perform for each class parameter.
Previously the lookup was always a "normal" one: the value returned is the one of the key found the first time while traversing the hierarchy.
Now (actually since Hiera 4
) it's possible to specify for some parameters alternative lookup methods (for example merging all the values found across the hierarchy for the requested key). This is done in the same data files where we specify our key values, so, for example, in our $module_path/users/data/common.yaml
we can have:
lookup_options:
# This lookup option applies to parameter 'local' of class 'users'
users::local:
# Merge the values found across hierarchies, instead of getting the first one
merge:
# Do a deep merge, useful when dealing with Hashes (to override single subkeys)
strategy: deep
merge_hash_arrays: true
# This lookup option applies to parameter 'admins' of class 'users'
users::admins:
merge:
# In this case we expect an array and will merge all the values found in a single one
strategy: unique
# It's even possible to define a prefix (here --) to force the removal of entries
knockout_prefix: "--"
Note that we can use regular expressions when defining specific lookup options for some keys:
lookup_options:
"^profile::(.*)::(.*)_hash$":
merge:
strategy: deep
knockout_prefix: "--"
"^profile::(.*)::(.*)_list$":
merge:
strategy: unique
knockout_prefix: "--"
It's possible to use the puppet lookup
command to query Hiera
for a given key.
If we run this on our Puppet Server
we can easily find out the value of a given key for the specified node:
puppet lookup profiles --node git.lab # Looks for the profiles key on the node git.lab
If we add the --debug
option we will see a lot of useful information about where and how data is looked for.
We can also use the lookup()
function inside our Puppet code
, it replaces (and deprecates), the old hiera()
, hiera_array()
, hiera_hash()
and hiera_include()
.
The general syntax is:
lookup( <NAME>, [<VALUE TYPE>], [<MERGE BEHAVIOR>], [<DEFAULT VALUE>] )
or
lookup( [<NAME>], <OPTIONS HASH> )
Some examples follow.
Normal lookup. Same of hiera('ntp::user')
:
lookup('ntp::user')
Normal lookup with default. Same of hiera('ntp::user','root')
:
lookup('ntp::user','root')
Array lookup, same of hiera_array('ntp_servers')
:
lookup('ntp_servers', Array, 'unique')
Deep merge lookup, same of hiera_hash('users')
with deep_merge
set to true:
lookup('users', Hash, 'deep')
Include classes found on Hiera
, same of hiera_include('classes')
lookup('classes', Array[String], 'unique').include
All the above examples can be written in an expanded way. In the following example an array is merged across the hierarchies with the option to use the "--"
prefix to exclude specific entries:
lookup({
'name' => 'ntp_servers',
'merge' => {
'strategy' => 'unique',
'knockout_prefix' => '--',
},
})
Check the official reference for all the options available for the lookup function.