Idempotence and Random Variables in Ansible

2020-07-03 07:52发布

问题:

Is there a way to guarantee idempotence for playbooks that use randomly generated variables?

For example, I want to setup my crontabs to trigger emails on multiple servers at different times, so I create random integers using ansible's set_fact module:

  tasks:
  - set_fact:
      first_run_30="{{ 30 | random }}"
    run_once: yes

Then apply those generated variables to my crontab using ansible like so:

   - name: Setup cron30job
    cron: name=cron30job minute={{first_run_30}},{{first_run_30 | int + 30}} job='/bin/bash /cron30job.sh' state=present user=root
    environment:
      MAILTO: 'me@somelist.com'
      MAILFROM: 'me@somehost.com'

This works very well, however, ansible's indempotence principle is, I believe, broken using this strategy because each time a play is made you see a change:

TASK: [Setup cron30job] ***************************************** 
changed: [127.0.0.1]

Further, in the crontab checking under root each time during three separate runs:

[ansible]# cat /var/spool/cron/root 
#Ansible: cron30job
5,35 * * * * /bin/bash /sw/test/cron30job.sh
#Ansible: cron30job
9,39 * * * * /bin/bash /sw/test/cron30job.sh
#Ansible: cron30job
6,36 * * * * /bin/bash /sw/test/cron30job.sh

If there is a workaround, or maybe indempotence just will not be possible in my scenario, I would like to know.

回答1:

Instead of a random value, you could get something related to the node, like an hash of the hostname or the last byte of the ip address.

This is an example:

- name: Get a pseudo-random minute 
  shell: expr $((16#`echo "{{inventory_hostname}}" | md5sum | cut -c 1-4`)) % 30
  register: minute
  changed_when: false


回答2:

As of Ansible version 2.3, it’s possible to initialize the random number generator from a seed. This way, you can create random-but-idempotent numbers:

"{{ 59 |random(seed=inventory_hostname) }} * * * * root /script/from/cron"

Source: random number filter

I've used this pattern to produce random cron start times with:

  1. different minutes on different target servers
  2. different minutes for different hours on the same server (randomness)
  3. the same minute for the same day and server when repeatedly running Ansible (idempotence)

Requires Ansible >=2.3:

 cron:
    name: "{{some_name}}_{{item.day}}"
    state: present
    job: "{{some_job}}"
    weekday: "{{item.day}}"
    hour: "{{item.hour}}"
    minute: "{{59|random(seed=inventory_hostname + item.dow)}}"
  with_items:
 - { day: 0, hour: 3, dow: "sunday" }
 - { day: 1, hour: 7, dow: "monday" }
 - { day: 2, hour: 1, dow: "tuesday" }
 - { day: 3, hour: 5, dow: "wednesday" }
 - { day: 4, hour: 2, dow: "thursday" }
 - { day: 5, hour: 4, dow: "friday" }
 - { day: 6, hour: 7, dow: "saturday" }


回答3:

Just in case you don't have ansible >= 2.3 available, a hashed approach using jinja2:

{{ (inventory_hostname |hash('md5')|int(0, 16)) % 60 }}


标签: cron ansible