Ansible-Pull

Anisible works great for applying a configuration to a server. It has a declarative state, and changes only what needs to modified to align the system with the desired configuration.

Wrangling the Servers

For my small lab, I don’t want to have to remember to run my ansible playbooks. Because I won’t remember, and the servers will fall behind on patches, and configuration will drift. So I need an orchestrator to automate running the baseline playbooks. I tried Ansible Tower / AWX, but it’s too much for my needs. I’d rather save the CPU and RAM for my workloads, not my infrastructure.

Ansible-pull is the perfect amount of automation for a small environment. It runs locally on each server via cron, ensuring the baseline is consistent and up-to-date.


On a new server, I have a single line that kicks off the playbook, either manually or via cloud-init.

1
wget -O - http://gitlab.home.nicksabine.com/nsabine/ansible-baseline/-/raw/main/install.sh | su -

That install.sh installs ansible and runs the baseline playbook:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/usr/bin/env bash

if [ "$EUID" -ne 0 ]
  then echo "Please run as root"
  exit 1
fi

apt-get -y install ansible git

/usr/bin/ansible-pull -U http://gitlab.home.nicksabine.com/nsabine/ansible-baseline -i hosts site.yml

My git repo has the standard ansible playbook layout:

GitLab repo for ansible-baseline

GitLab repo for ansible-baseline

site.yml

1
2
3
4
5
6
7
8
---

# Apply common configuration to all hosts
- hosts: all
  gather_facts: true

  roles:
  - common

hosts:

1
2
[local]
127.0.0.1

roles/common/tasks/main.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
---
- name: create cron job under /etc/cron.d
  ansible.builtin.cron:
    name: ansible-pull
    minute: "0"
    user: root
    job: "/usr/bin/ansible-pull -U http://gitlab.home.nicksabine.com/nsabine/ansible-baseline -i hosts site.yml > /dev/null 2>&1"
    cron_file: ansible-pull


- name: disable cdrom apt repository
  lineinfile:
    path: /etc/apt/sources.list
    state: absent
    regexp: '^deb cdrom'

- name: install packages
  ansible.builtin.apt:
    state: latest
    update_cache: yes
    pkg:
    - sudo
    - git
    - unattended-upgrades
    - systemd-journal-remote
    - qemu-guest-agent
    - mosh

- name: Install posix roles from Ansible Galaxy
  command: ansible-galaxy collection install ansible.posix
  args:
    creates: /usr/lib/python3/dist-packages/ansible_collections/ansible/posix

- name: create user
  ansible.builtin.user:
    name: nsabine
    shell: /bin/bash
    groups: sudo
    append: yes
    uid: 1000

- name: nsabine SSH authorized_keys
  ansible.posix.authorized_key:
    user: nsabine
    state: present
    key: '{{ item }}'
  with_file:
    - files/nsabine.pub

- name: sudoers no passwd
  lineinfile:
    path: /etc/sudoers
    state: present
    regexp: '^%sudo'
    line: '%sudo ALL=(ALL) NOPASSWD: ALL'
    validate: '/usr/sbin/visudo -cf %s'

- name: start unattended-upgrades
  ansible.builtin.systemd:
    name: unattended-upgrades
    state: started

There’s a few more things in there, but you get the point. The initial install script runs the playbook the first time, which creates a cron job for itself to run every hour.

With this setup, I have a simple baseline to keep the servers in sync, and an extensible framework to standardize any configuration.

Conditionals

You can write conditions to selectively apply configuration to some of your servers. For example, I can differentiate on ansible facts to select servers by hostname, operating system, etc.

Here I use hostname to decide if I want to start the log sender vs log receiver:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
- name: start systemd-journal-upload
  ansible.builtin.systemd:
    name: systemd-journal-upload
    state: started
    enabled: true
  when: "ansible_hostname != 'logs'"

- name: start systemd-journal-remote
  ansible.builtin.systemd:
    name: systemd-journal-remote
    enabled: true
  when: "ansible_hostname == 'logs'"

comments powered by Disqus