有效地分发 cron 任务

当你有许多服务器需要执行相同的 cron 作业时,不在同一时间运行它们通常是个好主意。 如果所有作业都要访问一个公共服务器,就会给该服务器带来大量负载, 即使这些服务器不会同时访问公共服务器,所有服务器也会在同一时间处于繁忙状态, 这可能会削减它们提供其他服务的能力。

Puppet 的 inline_template 函数允许我们使用 Ruby 的逻辑根据主机名为 cron 作业设置不同的运行时间。

操作步骤

  1. 在一个节点中添加如下代码:

    define cron_random( $command, $hour )
    {
      cron { $name:
        command => $command,
        minute  => inline_template("<%= (hostname+name).hash.abs %60 %>"),
        hour    => $hour,
        ensure  => "present",
      }
    }
    
    cron_random { "hello-world":
        command => "/bin/echo 'Hello world'",
        hour => 2,
    }
    
    cron_random { "hello-world-2":
        command => "/bin/echo 'Hello world'",
        hour => 1,
    }
    
  2. 运行 Puppet:

    # puppet agent --test
    info: Retrieving plugin
    info: Caching catalog for cookbook.bitfieldconsulting.com
    info: Applying configuration version '1305713506'
    notice: /Stage[main]//Node[cookbook]/Cron_random[hello-world]/
    Cron[hello-world]/ensure: created
    notice: /Stage[main]//Node[cookbook]/Cron_random[hello-world-2]/
    Cron[hello-world-2]/ensure: created
    notice: Finished catalog run in 1.07 seconds
    
  3. 检查 crontab 查看是否成功地配置了 cron 作业:

    # crontab -l
    # HEADER: This file was autogenerated at Fri Jul 29 10:58:45 +0000
    2011 by puppet.
    # HEADER: While it can still be managed manually, it is definitely
    not recommended.
    # HEADER: Note particularly that the comments starting with
    'Puppet Name' should
    # HEADER: not be deleted, as doing so could cause duplicate cron
    jobs.
    # Puppet Name: hello-world
    25 2 * * * /bin/echo 'Hello world'
    # Puppet Name: hello-world-2
    49 1 * * * /bin/echo 'Hello world'
    

工作原理

我们想要为每个 cron 作业选择一个 随机的 执行分钟数; 而不是真正的随机 (或者说,不是每次运行 Puppet 都会改变 cron 作业的运行时间), 但这也或多或少地保证了每个主机上的每个 cron 作业运行时间的不同。

我们可以使用 Ruby 的 hash 方法实现它,它会对任何对象(本例为一个字符串)计算出一个哈希值。 尽管看上去这个哈希值是随机的,但它每次运行时都相同,所以当再次运行 Puppet 时其值不会改变。

哈希值生成的是一个大整数,而我们想要的是一个 0 到 59 之间的整数,所以我们使用了 Ruby 的 % (模)运算符将其结果限制在这个范围内。因为只有 60 种可能的值,尽管 hash 函数被设计为尽可能产生随机的输出,还是会有些许的碰撞而且这些碰撞对于 minute 应该是均匀分布的。

因为我们希望每个哈希值在不同的主机上是不同的,所以使用主机名做 hash 处理。 然而,我们还希望同一台主机上的不同作业的哈希值也不同,所以联合使用了主机名和作业名 (例如 hello-world)做 hash 处理。

更多用法

在本例中,我们仅对 cron 作业的 minute 进行了随机化,并将 hour 作为 define 定义的一部分。若你同时希望指定要在周几运行,可以在 cron_random 中添加一个附加参数来指定, 可以像下面这样为其指定默认值:

define cron_random( $command, $hour, $weekday = "*" ) {

若你想要对 cron 作业的 hour 进行随机化(例如:要做的作业可以在一天之内的任何时间执行, 并且必须将它们均匀分布在所有的 24 个小时上),可以对 cron_random 做如下修改:

hour => inline_template("<%= (hostname+name).hash.abs % 24 %>"),

参见本书