Converting puppetd to run using cron

This page contains two possible solutions for using Puppet via cron

Goals

Our goals were to:
– Run puppet as cron
– conserve memory usage
– not use —splay (since puppetd still hangs in memory)
– spread out the puppetd times they run and manually load balance (if needed)

Puppet Recipe

                $timeoffset1 = generate('/usr/bin/env', '/etc/puppet/modules/puppet/bin/randomnum.pl', "$fqdn", "30")
                $timeoffset2 = generate('/usr/bin/env', '/etc/puppet/modules/puppet/bin/math.pl', "$timeoffset1", "+", "30")
                ## for 0.24.6+, uncomment this line and comment previous line.                
                # $timeoffset2 = $timeoffset1 + 30 
                cron { "puppet":
                        ensure  => present,
                        command => "/usr/sbin/puppetd --onetime --no-daemonize --logdest syslog > /dev/null 2>&1",
                        user    => 'root',
                        minute  => [ $timeoffset1, $timeoffset2 ],
                        # Probably want to move these into their own resource.
                        package { "perl": ensure => "installed", },
                        file { "/etc/puppet/modules/puppet/etc/": ensure => "directory", },
                }

See below for the randomnum and addnum scripts.

You can also create a tag to have some nodes run as daemon and others run as cron.

Random Number Script

Generates a random number and stores the entry so it can be recalled. If you want to load balance your puppetd, just edit the files in /etc/puppet/modules/puppet/etc/ folder.

You must create the folder and have the proper perms on /etc/puppet/modules/puppet/etc/ in order for it to work. Adjust according to your specific setup.

#!/usr/bin/perl
#### /etc/puppet/modules/puppet/bin/randomnum.pl
use strict;
umask 066;

die "Usage: <file basename> <run interval>" unless $ARGV[1];

my $puppetclientetcdir="/etc/puppet/modules/puppet/etc/";
my $keyfile=$puppetclientetcdir.$ARGV[0].".key";

if (!-e $keyfile) {
        my $range=$ARGV[1];
        my $random_number = int rand $range;
        open my $OUT,'>', $keyfile or die "Couldn't write to $keyfile: $!";
        print $OUT $random_number;
        close $OUT;
}

open my $IN,"$keyfile" or die "Couldn't open $keyfile: $!";
foreach my $line (<$IN>) {
  if ($line =~ /^\d+(:?\n|)$/) {
    chomp $line;
    print $line;
    close $IN;
    exit;
  }
}
close $IN;

# If we get to here something is wrong, just print the XKCD random number
# and exit with an error.
print "4";
exit 1;
#### /etc/puppet/modules/puppet/bin/randomnum.pl END OF FILE

Additional Number Script

Creates the second time within an hour to run cron (add 30 min)

#!/usr/bin/perl
# $Id$
#### /etc/puppet/modules/puppet/bin/math.pl
#### No operator sanity checking, potential security consideration. :)
use strict;
die "Usage: $0 <operand1> <operator> <operand2>\n Example: $0 1 + 2\n Example: $0 2 \\* 8" unless $ARGV[2];

my $result = eval("return $ARGV[0] $ARGV[1] $ARGV[2];"); warn $@ if $@;

print int $result;
#### /etc/puppet/modules/puppet/bin/math.pl END OF FILE

Setting Cron using a Puppet custom function

An alternative cleaner implementation to the above is to use a “random” hash value for cron based on the IP address of the machine.

Here is a Puppet custom function to do this:

Usage Example

cron { "puppet":
  ensure  => present,
  command => "/usr/sbin/puppetd --onetime --no-daemonize --logdest syslog > /dev/null 2>&1",
  user    => 'root',
  minute  => ip_to_cron(2)
}

Cron random custom function

The content of the following file needs to be added as a function, usually to your

modulename/lib/puppet/parser/functions/ip_to_cron.rb

cat ip_to_cron.rb

# provides a "random" value to cron based on the last byte of the machine IP address.
# used to avoid starting a certain cron job at the same time on all servers.
# if used with no parameters, it will return a single value between 0-59
# first argument is the occurrence within a timeframe, for example if you want it to run 2 times per hour
# the second argument is the timeframe, by default its 60 minutes, but it could also be 24 hours etc
# ohadlevy@gmail.com
#
# example usage
# ip_to_cron()     - returns one value between 0..59
# ip_to_cron(2)    - returns an array of two values between 0..59
# ip_to_cron(2,24) - returns an array of two values between 0..23

module Puppet::Parser::Functions
    newfunction(:ip_to_cron, :type => :rvalue) do |args|
        occours = args[0].to_i || 1
        scope   = args[1].to_i || 60
        ip      = lookupvar('ipaddress').split('.')[3].to_i
        base    = ip % scope
        if occours == 1
            base
        else
            (1..occours).map {|i| ((base - (scope / occours * i)) % scope }.sort
        end
    end
end

A simpler setup using inbuilt shells and not requiring an extra script.

                class cron {


                    $minute = generate('/usr/bin/env', 'sh', '-c',  'printf $((RANDOM%60+0))')


                    cron { "manual-puppet":
                        command => "/usr/sbin/puppetd --onetime --no-daemonize --logdest syslog > /dev/null 2>&1",
                        user => "root",
                        hour => "*",
                        minute => $minute,
                        ensure => present,
                    }


                }

Hopefully others will fine the above useful. It generates the random minute each time its run rather than the above script which keeps the variables the same for that host each time its run.

Another simple setup using inbuilt shells and not requiring an extra script, but using the IP address instead of RANDOM

class cron {
    $minute1 = generate('/usr/bin/env', 'bash', '-c',ยท
        'printf $(($(ip route show dev eth0 | head -1 | awk \'{ print $7 }\'| awk -F . \'{print $1+$2+$3+$4}\') % 30))')
    $minute2 = $minute1 + 30

    cron { "manual-puppet":
        command => "/usr/sbin/puppetd --onetime --no-daemonize --logdest syslog > /dev/null 2>&1",
        user => "root",
        minute => [$minute1, $minute2],
        ensure => present,
    }
}

A much easier way:

    cron { "puppet":
        command => "/usr/sbin/puppetd --onetime --no-daemonize --logdest syslog > /dev/null 2>&1",
        user => "root",
        minute => fqdn_rand( 60 ),
        ensure => present,
    }

BTW, generate runs on the puppet master, not on each node, so you’ll get the same number every time with the above examples.