When it comes to split responsibilities, such as that between the Ops teams and Dev teams, we usually do a couple things:
1. Use hiera to store key/value pairs that can be visible worldwide (like application versions). This allows developers to be able to test their own stuff, and when ready, submit pull requests into the production environment branches
2. Use node scoped variables for Ops-only information
3. Make Ops the keeper of facts and secrets in the node definitions
4. Structure our hierarchy to include applications, datacenters and/or business units (ours is actually more complex, but this is simplified)
5. Parameterize all classes we wish to use hiera for.
5.1 In some cases, explicitly call hiera based on availability of node scoped variables (this is more for pre-hiera backwards compatibility)
6. Use folder based environments (though it seems Puppet suggests moving away from these, they give us more flexibility). We use a script we found on the github called branch2env.py (slightly modified to fit our environment better). I haven't used r10k, but it sounds like it does something similar.
The actual node definition might look something like:
node vars-dc1-finance {
# This is the "folder" for puppet/hiera code to look at
$environment = 'production'
# These are "facts" that aid in hiera lookups
$datacenter = 'dc1'
$biz = 'finance'
# This is a node scoped secret
$order_service::webapp::password = 'foofoo'
}
include base
include order_service::webapp
}
A basic hierarchy might be:
- nodes/%{::fqdn}
- dc/%{datacenter}
- biz/%{biz}
- common
Some example hiera yamls:
dc/dc1.yaml:
biz/finance.yaml:
java::version: '1.7.0_60'
order_service::webapp::version: '1.2.3'
biz/hr.yaml:
java::version: '1.6.0_30'
order_service::webapp::version: '1.0.0'
common.yaml:
#This pulls from a node scoped variable
order_service::webapp::password: %{order_service::webapp::password}
# These are defaults in the event something higher in the hierarchy does define them.
java::version: '1.6.0_18'
order_service::webapp::version: '1.0.0'
Now for some basic class definitions:
class order_service::webapp ($version, $password, $endpoint1) {
include java
package { "order_service-${version}" :
ensure => installed
}
file { "/etc/creds/order_service" :
content => "${password}"
}
# This template uses the $endpoint1 variable via <%= @endpoint1 %>
file { "/etc/overrides/order_service":
content => template("order_service/webapp/overrides.xml.erb")
}
}
class java ($version) {
package { "jdk-${version}" :
ensure => installed
}
}
Our hiera and modules are environment aware. And environment is stored in puppet.conf via an erb.
hiera.yaml:
:datadir: "/etc/puppet/hiera/%{environment}"
puppet.conf:
environment = production (This is added via an erb template that reads the node scoped $environment variable)
modulepath = /etc/puppet/modules/$environment
Our nodes are included in site.pp from a predefined location. This allows us to do a couple things:
1. Keep tighter control of production assets
2. Allow developers to add in node definitions in cases this is necessary (mainly due to old policies).
e.g.
import /etc/puppet/nodes/prod/* (points to a for-Ops-only repo)
import /etc/puppet/nodes/dev/* (points to a developer accessed repo)
We went through a few iterations of getting this right and figuring out how to reduce duplication of data as much as possible. Just try to remember a few things:
1. Try to keep your node definitions as simple as possible. When possible only add facts not derived from facter to node definitions.
2. Keep configuration data out of your classes. Even defaults. Use common.yaml or similar to add in defaults.
3. Unless absolutely necessary DO NOT use resource style declarations (e.g. class { "java": version => '1.7.0_60'}). Hiera handles parameter lookups quite well and you won't get duplicate declaration issues.
HTH,
Jake