Serialize Ansible Tasks

Most of the time in Ansible, the default strategy and forks will work well. However, there are some race conditions or flow control issues. The strategy and serial play keywords can provide controls to avoid such issues. The following example will demonstrate specifically how serializing a task will help avoid a race condition.

Get Next Race Condition

Oftentimes getting the next resource, such as an IP address, VLAN, or subnet is a two-step process. The first step is to get the list of next available resources and the second step will actually assign that resource. The below example is somewhat contrived, as the netbox_ip_address can already complete this in a single step, but will work for the purposes of this demonstration.

Given the following playbook, you can see the two-step process between the first 2 tasks.

---

- name: "GET VERSION INFORMATION"
  hosts: "all"
  gather_facts: "no"
  connection: "local"
  vars:
    prefix_id: "15"

  tasks:
  - name: "GET NEXT AVAILABLE IP ADDRESSES"
    uri:
      url: "http://127.0.0.1:8000/api/ipam/prefixes/{{ prefix_id }}/available-ips/"
      method: "GET"
      headers:
        authorization: "Token {{ token }}"
      status_code: 200
    register: "available_ip"

  - name: "ASSIGN A SPECIFIC IP FROM NETBOX"
    netbox.netbox.netbox_ip_address:
      netbox_url: 'http://netbox:8000'
      netbox_token: "{{ token }}"
      data:
        address: "{{ available_ip['json'][0]['address'] }}"
        interface:
          name: "GigabitEthernet2"
          device: "{{ inventory_hostname }}"
      state: "present"
      validate_certs: false
    register: "msg"

  - name:
    debug:
      var: msg['msg']

When the playbook runs, Ansible flow control performs the same query at nearly the same moment, and then assigns the IP address based on that returned value. In this case, it is now going to set the same value multiple times.

root@4625fa37f36f:/ntc# ansible-playbook -i inventory pb_get_next_ip.yml

PLAY [GET VERSION INFORMATION] *****************************************************************************************************************************************************************************

TASK [GET NEXT AVAILABLE IP ADDRESSES] **********************************************************************************************************************************************************************
ok: [csr2]
ok: [csr1]

TASK [ASSIGN A SPECIFIC IP FROM NETBOX] ********************************************************************************************************************************************************************
changed: [csr2]
changed: [csr1]

TASK [debug] ***********************************************************************************************************************************************************************************************
ok: [csr1] => {
    "msg['msg']": "ip_address 10.40.0.1/24 created"
}
ok: [csr2] => {
---
    "msg['msg']": "ip_address 10.40.0.1/24 created"
}

PLAY RECAP *************************************************************************************************************************************************************************************************
csr1                       : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
csr2                       : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

root@4625fa37f36f:/ntc#

As noted in the debug statement, the same IP address was set. Checking the NetBox GUI, we can confirm this to be the case

netbox-output

Without using the serial keyword in the play definition, the ordering is roughly the following:

  • Task1 for CSR1 and CSR2
  • Task2 for CSR1 and CSR2
  • Task3 for CSR1 and CSR2

By using the serial keyword within the play definition, the order can be changed in which the tasks are executed.

---

- name: "GET VERSION INFORMATION"
  hosts: "all"
  gather_facts: "no"
  connection: "local"
  serial: 1
  vars:
    prefix_id: "15"

  tasks:

With serial, the ordering is now:

  • Task1 for CSR1
  • Task2 for CSR1
  • Task3 for CSR1
  • Task1 for CSR2
  • Task2 for CSR2
  • Task3 for CSR2

Re-running the playbook, we can now get the desired effect.

root@4625fa37f36f:/ntc# ansible-playbook -i inventory pb_get_next_ip.yml

PLAY [GET VERSION INFORMATION] *****************************************************************************************************************************************************************************

TASK [GET NEXT AVAILABLE IP ADDRESSES] *********************************************************************************************************************************************************************
ok: [csr1]

TASK [ASSIGN A SPECIFIC IP FROM NETBOX] ********************************************************************************************************************************************************************
changed: [csr1]

TASK [debug] ***********************************************************************************************************************************************************************************************
ok: [csr1] => {
    "msg['msg']": "ip_address 10.40.0.1/24 created"
}

PLAY [GET VERSION INFORMATION] *****************************************************************************************************************************************************************************

TASK [GET NEXT AVAILABLE IP ADDRESSES] *********************************************************************************************************************************************************************
ok: [csr2]

TASK [ASSIGN A SPECIFIC IP FROM NETBOX] ********************************************************************************************************************************************************************
changed: [csr2]

TASK [debug] ***********************************************************************************************************************************************************************************************
ok: [csr2] => {
    "msg['msg']": "ip_address 10.40.0.2/24 created"
}

PLAY RECAP *************************************************************************************************************************************************************************************************
csr1                       : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
csr2                       : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

root@4625fa37f36f:/ntc#

As indicated by the message, CSR2 did in fact correctly get the second IP address.

The flow control for out of the box Ansible doesn’t work for every use case. However, the constructs can be modified based on the actual use case and desired outcome.

-Ken



ntc img
ntc img

Contact Us to Learn More

Share details about yourself & someone from our team will reach out to you ASAP!

Author