I did not intend to create a new blog on Ansible so fast, but here it comes.
My previous blog on that topic was a first step in Ansible world. Now, it is time to use some of the advanced features.

Context

We recently had an issue at a customer site, where some of the servers were having time synchronization issues. I discovered that ntp configuration was not the same across all servers which, from my experience. can lead to time drifts. For most of the web application, time is not so important constraint, unless your application used SAML2 Single Sign On mechanism.

My goal, here, is to check ntp status on all servers in one shot.

First playbook

My first attempt was a very basic playbook:

---
- name: NTP Check
  hosts: all
  gather_facts: no
  tasks:
    - name: Run ntpdc -p
      shell: /usr/sbin/ntpdc -p
      register: output
    - name: Display output
      debug:
        var: output.stdout_lines

I use a variable named output (which is created with register option) and display content for each servers of the inventory (output.stdout_lines).

I faced an issue on two hosts of my list:

ok: [server-123456] => {
"output.stdout_lines": "VARIABLE IS NOT DEFINED!"
}

This was due a difference in ntp client. These two hosts run chrony instead of ntpd, which means I have to run different commands depending on the time synchronization client.

Second attempt

I took the easiest way: If /etc/ntp.conf exists, I run ntpdc command. If not, I used the chronyc command. And, finally, display merged result whatever the ntp client.

Playbook becomes:

---
- name: NTP Check
  hosts: all
  gather_facts: no
  tasks:
    - name: Determine ntp client
      stat:
      path: /etc/ntp.conf
      register: ntpd
    - name: Run ntpdc -p
      shell: /usr/sbin/ntpdc -p
      register: output
      when: ntpd.stat.exists
    - name: Run chronyc
      shell: /usr/bin/chronyc sources
      register: output
      when: not ntpd.stat.exists
    - name: Display output
      debug:
        var: output.stdout_lines

From this playbook, I learn that even if “when” condition is not met, and task will be skipped, output register will be erased for these hosts (and “VARIABLE IS NOT DEFINED!” will be displayed). Which means that output will be populated for my two hosts that use chrony client. Unfortunately, as per Ansible documentation, this is the expected behavior:

“If a task fails or is skipped, Ansible still registers a variable with a failure or skipped status, unless the task is skipped based on tags. See Tags for information on adding and using tags.”

tags will not help me here as I want to have all results in same output and in one command.

Third try

As Ansible can manage errors via exception, I wondered if it would be possible to simply run chrony command if ntpdc command fails.

---
- name: NTP Check
  hosts: all
  gather_facts: no
  tasks:
    - name: Run ntpdc -p (block)
      block:
        - name: Run ntpdc -p
          command: /usr/sbin/ntpdc -p
          register: output
      rescue:
        - name: Run chronyc (block)
          block:
          - name: Run chronyc
            command: /usr/bin/chronyc sources
            register: output
      always:
        - name: Display output
          debug:
            var: output.stdout_lines

If first task block “Run ntpdc” fails, it will jump to rescue block and run chronyc block. Then, whatever ntpdc or chronyc block was run, always will be run with output always defined.
All servers will have similar output as below:

  • For a ntp client:
ok: [server-123456] => {
    "output.stdout_lines": [
        " remote local st poll reach delay offset disp",
        "=======================================================================",
        "*ntp01 10.10.10.10 1 512 377 0.00047 -0.730333 0.11720",
        "=ntp02 10.10.10.11 1 512 377 0.00087 -0.733377 0.11292"
    ]
}
  • For a chrony client:
ok: [server-456789] => {
    "output.stdout_lines": [
    "210 Number of sources = 5",
    "MS Name/IP address Stratum Poll Reach LastRx Last sample ",
    "===============================================================================",
    "^* ntp04 1 6 377 38 -14us[ -46us] +/- 233us",
    "^- ntp05 3 10 377 388 +7566us[+7585us] +/- 116ms",
    "^? ntp06 2 6 377 43 +2947ms[+2947ms] +/- 11.0s",
    "^- ntp07 3 10 377 191 -8696us[-8716us] +/- 120ms",
    "^? ntp08 2 10 377 999 +2953ms[+2953ms] +/- 11.0s"
    ]
}

Note that for these examples, servers were using different ntp client but also different ntp sources which is what I wanted to check.

What next ?

These are still basic playbooks, but I wrote them in few minutes and they fill a need. It is from needs that come ideas.