Configuring Time Services With Ansible

Table of Contents

I put together an Ansible role for configuring network time services, using chrony.

This is my most elegant work thus far, though before this is worth packaging, I’d like to configure the scripts to test for Debian vs. Red Hat, as well as add variables for a few things.

You can take a look at the relevant git commit here.

Structure

Here are the files in this project. A task to call the roles, and a role for master/slave.

tasks
├── configure_time.yml
roles/
├── chrony-master
│   ├── handlers
│   │   └── main.yml
│   ├── tasks
│   │   └── main.yml
│   ├── templates
│   │   ├── chrony.keys.j2
│   │   └── chrony-master.conf.j2
│   └── vars
│       └── main.yml
├── chrony-slave
    ├── handlers
    │   └── main.yml
    ├── tasks
    │   └── main.yml
    ├── templates
    │   ├── chrony.conf.j2
    │   └── chrony.keys.j2
    └── vars
       └── main.yml

configure_time.yml

This script simply calls the roles. My timeservers are my Proxmox nodes, since VM time isn’t stable.

---
- hosts: proxmox
  gather_facts: false
  user: root
# Debian masters
  roles:
    - chrony-master


- hosts: all:!proxmox
  gather_facts: false
  user: root
# CentOS slaves
  roles:
    - chrony-slave

tasks/main.yml

These are basically identical for masters and slaves, so here’s the master file:

---
  - name: Include vars
    include_vars: ../vars/main.yml

  - name: Include vars
    include_vars: ~/0/vault/secrets.yml

  - name: Set timezone to America/Denver
    timezone:
      name: America/Denver
    notify: restart crond

  - name: ensure ntpdate isn't installed
    package:
      name: 'ntpdate'
      state: absent

  - name: install chrony
    package:
      name: chrony
      state: present

  # I mask instead of uninstall, because some services  
  # depend on NTP
  - name: Mask NTP service
    systemd:
      name: ntpd
      enabled: no
      masked: yes
      state: stopped

  - name: Ensure chronyd is active
    service:
      name: chronyd
      state: started

 # Debian config path.  Change for CentOS
  - name: install chrony.conf
    template:
      src: ../templates/chrony-master.conf.j2
      dest: /etc/chrony/chrony.conf
      owner: root
      group: root
      mode: 0644
    notify: restart chronyd

  - name: install chrony.keys
    template:
      src: ../templates/chrony.keys.j2
      dest: /etc/chrony/chrony.keys
      owner: root
      group: root
      mode: 0640
    notify: restart chronyd

templates

I’ve basically got two templates. The keys file literally just pulls a variable from the vault.

The config file is slightly different between the master and slaves, and again, I’ll show the master here. The difference variable is so each server doesn’t have a peer entry for itself.

I use IPs for the server peers, and host names for the clients.

# Use public servers from the pool.ntp.org project.
# Please consider joining the pool (http://www.pool.ntp.org/join.html).
server 0.debian.pool.ntp.org iburst xleave
server 1.debian.pool.ntp.org iburst xleave
server 2.debian.pool.ntp.org iburst xleave
{% for host in groups['proxmox'] | difference([inventory_hostname]) %}
peer {{ hostvars[host].ansible_host }} iburst xleave
{% endfor %}

# Record the rate at which the system clock gains/losses time.
driftfile /var/lib/chrony/drift

# Allow the system clock to be stepped in the first three updates
# if its offset is larger than 1 second.
makestep 1.0 3

# This is disabled on the slaves, since VMs don't have an RTC
rtcsync

# This likely does nothing since my network interface is a bridge
# device.  However, if things change, this is good practice.
# Disabled on slaves
hwtimestamp *

# Increase the minimum number of selectable sources required to adjust
# the system clock.
#minsources 2

# Allow NTP client access from local network.
# This is of course not enabled on the slaves
allow {{ network }}
allow fe80::/64

# Serve time even if not synchronized to a time source.
# In case my network gets isolated, I still want
# Kerberos to work.
local stratum 10

# Specify file containing keys for NTP authentication.
keyfile /etc/chrony/chrony.keys

# Specify directory for log files.
logdir /var/log/chrony

# Select which information is logged.
#log measurements statistics tracking

Testing

Testing on my laptop:

chronyc> sources
210 Number of sources = 3
MS Name/IP address         Stratum Poll Reach LastRx Last sample               
===============================================================================
^* gold.lan.nathancurry.com     11   6   377    72  +1924us[+1974us] +/- 9101us
^- silver.lan.nathancurry.c>    12   6   377    75   +262us[ +311us] +/- 6356us
^+ bronze.lan.nathancurry.c>    10   6   377    74   +880us[ +930us] +/-   16ms

And on a time server:

chronyc> sources
210 Number of sources = 5
MS Name/IP address         Stratum Poll Reach LastRx Last sample               
===============================================================================
=- silver.lan.nathancurry.c>     3   7   377  1062   +865us[ +595us] +/-   60ms
=* bronze.lan.nathancurry.c>    10   7   377   158    -15us[+2100ns] +/-   15ms
^- h113.95.219.67.cable.sta>     2   7   377   183    +10ms[  +10ms] +/-   85ms
^- 12.167.151.2                  3   7   377    52    +10ms[  +10ms] +/-   59ms
^- startkeylogger.hungrycat>     3   6   377    52  +9268us[+9289us] +/-   33ms

Pretty sweet.

You can find the relevant git commit here.