Search code examples
configuration-filesdryconfiguration-managementpuppet-enterpriseredis-cluster

How do I avoid repeating myself in class declarations for puppet nodes?


Basically, I find myself making the same long class declaration every time:

node 'gluster3redis097.myservice.com' {
    class { 'redis' :
        class {'invoke' : }
        class {'users' : }
        class {'redis' :

        package_ensure        => '3.0.5',

        #extra_config_file    => '/etc/redis.d/redis-gluster3-master.conf',
        daemonize             => 'yes',
        pid_file              => '/var/run/redis.pid',
        log_level             => 'notice',
        log_file              => '/var/log/redis/redis.log',
        #save_db_to_disk       => false,
        workdir               => './',
        bind                  => $::ipaddress,
        slaveof                    => "${$gluster3redis_master_ips[37]}:6379",
        slave_serve_stale_data => true,
        # 2015.12.01  nathan  Do not allow inadvertent writes to the slave
        slave_read_only        => true,
        repl-diskless-sync-delay => '5',
        repl-ping-slave-period => '10',
... and so on ... 
... and so forth ...

Let us suppose, for this cluster, every FIFTH node has a separate master.

So, guess the only part that changes?

slaveof                    => "${$gluster3redis_master_ips[37]}:6379",

There has to be a better way.
-- Pocahontas (1995)1

According to https://docs.puppetlabs.com/puppet/latest/reference/lang_node_definitions.html#multiple-names, it's a bad idea to use the Puppet inherits keyword.

Plus, to my dismay, in https://docs.puppetlabs.com/puppet/latest/reference/lang_node_definitions.html#aside-best-practices , they describe the following best practices:

Aside: Best Practices

Although node statements can contain almost any Puppet code, we recommend that you only use them to set variables and declare classes. Avoid using resource declarations, collectors, conditional statements, chaining relationships, and functions in them; all of these belong in classes or defined types. This will make it easier to switch between node definitions and an ENC.2

Could I define a custom type?

I read through How to pass node specific information to class in puppet? , but I'm not sure he's asking the same thing that I am, and although I'm perfectly willing to learn how to define types, I'm not familiar with them enough to decide whether to go that route.

How the blazes do I avoid repeating myself with every Puppet Node definition like this for this Redis cluster?

I would welcome even a very generic answer that I could apply to my particular case, which I have also generalized.

UPDATE: Applying a common configuration using Hiera by a common.yaml file for this set of environments seems to have worked. I will elaborate further in an answer, if Dan Bowling does not volunteer one.


Solution

  • Here's a more flushed out answer for the comment I initially placed:

    Any time you find yourself re-declaring settings as class parameters, Hiera should be considered. From the docs:

    Hiera is a key/value lookup tool for configuration data, built to make Puppet better and let you set node-specific data without repeating yourself.

    Your first step is to identify a hierarchy, since Hiera will use that hierarchy to look up an appropriate value for the requested key. In your example, a simple hierarchy is all that is needed. Here's an example hiera.yaml configuration file:

    :backends:
      - yaml
    
    :hierarchy:
      - "node/%{::hostname}"
      - "common"
    
    :yaml:
      :datadir: '/your/hiera/data/directory'
    
    :merge_behavior: deeper    
    

    About the above config:

    1. Hiera will look in /your/hiera/data/directory/node/nodehostname.yaml for a value first. This is where you can define per-host configurations.
    2. For all other values, Hiera will revert to the default common.yaml in /your/hiera/data/directory/node/common.yaml

    So, your common.yaml might look like this:

    redis::package_ensure: '3.0.5'
    redis::pid_file: '/var/run/redis.pid'
    

    And you will have node{1,2} with this /your/hiera/data/directory/node/node{1,2}.yaml:

    redis::slaveof: 'your redis master value'
    

    And node{3,4} would have this /your/hiera/data/directory/node/node{3,4}.yaml:

    redis::slaveof: 'your other redis master value'
    

    The :merge_behavior: deeper in hiera.yaml is useful if you want to merge settings at different hierarchy levels or combine complex hashes into single values. For more info, see Hiera Lookup Types.