Skip to content
Pipelines and Pizza 🍕
Go back

Ansible for Beginners: Improving Patching with Ansible

7 min read

Building on our Git fundamentals, this week we expand on Ansible — a simple yet powerful automation engine for configuration management, orchestration, and especially monthly server patching.

If you’ve ever clicked “Update All” at 2 a.m. on a jump box while whispering “please don’t brick prod,” this post is for you. We’ll turn patch Tuesday (or “whenever we have time” Wednesday) into a tidy, repeatable playbook you can run with confidence—and a cup of coffee that stays hot.

My goal isn’t to impress you with YAML acrobatics. It’s to give you a workflow you can actually use this month, with just enough safety rails to sleep fine afterward.

Table of Contents

Open Table of Contents

Why Ansible?

Ansible is agentless, meaning no special software needs to run on managed nodes — just SSH and Python. It’s popular in DevOps for:

  • Configuration Management: Install packages, manage files, start/stop services.
  • Orchestration: Coordinate changes across multiple servers.
  • Provisioning: Deploy infrastructure resources.
  • Application Deployment: Automate complex multi-tier deployments.
  • Patching: Safely apply OS and package updates on a predictable cadence.

Why beginners love it (and veterans keep it):

  • Human-readable YAML. No mystery meat.
  • Huge module ecosystem.
  • Easy dry-runs (--check) so you can rehearse before the real thing.

Real talk: I used to keep a spreadsheet of servers to patch. It worked—right up until it didn’t. Ansible replaced that fragile routine with something boring, reliable, and fast. Boring is good when uptime matters.


Ansible Architecture

Ansible has a control node and managed nodes.

  • Control Node: Where you run ansible commands.
  • Managed Nodes: Servers you manage via SSH.

Key Components:

  • Inventory: List of managed nodes.
  • Modules: Units of work (e.g., apt, dnf, service, reboot).
  • Playbooks: YAML files describing tasks.
  • Roles: Structured, reusable playbooks.

Mental model:

Control Node
   |
   +--(SSH)--> Managed Node(s)
                 |
                 +-- Python executes modules

You talk to hosts over SSH; Ansible drops tiny, purpose-built tasks on the other side. No long-running daemons, no agents to babysit.


Installation and Setup

Two-minute install:

pip install ansible
# optional best practices:
pip install ansible-lint

On Windows: use WSL2 (Ubuntu recommended) and run the same commands.

Verify:

ansible --version

If that worked, you’re already 80% of the way to useful automation.


Inventory Files

Ansible uses an inventory to know which hosts to manage.

Example (inventory.ini):

[web]
web1.example.com
web2.example.com

[db]
db1.example.com

[all:vars]
ansible_user=ubuntu

Pro tip: Start small. Put two or three non-prod hosts in a stage group and practice there first.

Note: Ensure SSH access and keys are configured in advance; all examples assume working SSH.

Smoke test:

ansible -i inventory.ini all -m ping

If you see “pong,” you’re in business.


Quick Patching with Ad-Hoc Commands

When you just need to get it done.

Debian/Ubuntu:

# Update package index and perform a safe full upgrade
ansible -i inventory.ini all -m apt -a "update_cache=yes upgrade=dist" --become

# Dry run (no changes), show what would happen
ansible -i inventory.ini all -m apt -a "update_cache=yes upgrade=dist" --become --check

RHEL/CentOS/Alma/Rocky:

# Bring all packages to latest
ansible -i inventory.ini all -m dnf -a "name='*' state=latest" --become

# Dry run (approximation; check mode support varies by module)
ansible -i inventory.ini all -m dnf -a "name='*' state=latest" --become --check

Need a reboot?

ansible -i inventory.ini web -m reboot --become

Why this matters: Ad-hoc commands are your “grab a wrench” toolkit—perfect for small fleets and emergency fixes.


Playbooks and YAML Basics

Playbooks are declarative: you describe the end state; Ansible figures out the steps.

Example: a minimal patching playbook:

---
- name: Minimal patch for Debian/Ubuntu
  hosts: all
  become: yes
  tasks:
    - name: Update and upgrade
      apt:
        update_cache: yes
        upgrade: dist
      when: ansible_os_family == "Debian"

    - name: Update to latest on RHEL-like
      dnf:
        name: "*"
        state: latest
      when: ansible_pkg_mgr == "dnf"

Run:

ansible-playbook -i inventory.ini site.yml

Think of playbooks as saved, repeatable commands—with documentation baked in.


Safe, Idempotent Patching (with Reboots)

Idempotence means running the same playbook twice won’t cause surprises. For patching, we add seatbelts and airbags:

  • Rolling updates so we don’t take everything down at once.
  • Conditional reboots—only when necessary.
  • Pre-flight checks to catch silly issues (like low disk space).
  • Dry-runs so you can rehearse.

Serial is your friend: it’s like taking servers off the shelf one at a time.

---
- name: Monthly Patching (Rolling + Reboots)
  hosts: all
  become: yes
  gather_facts: yes
  serial: 25%        # roll through 25% of hosts at a time
  any_errors_fatal: true

  pre_tasks:
    - name: Ensure hosts are reachable
      wait_for_connection:
        timeout: 60

    - name: Check free disk space (>= 1GB recommended)
      shell: df -Pk / | awk 'NR==2{print $4}'
      register: root_free_kb
      changed_when: false

    - name: Fail if insufficient disk space for updates
      fail:
        msg: "Not enough disk space for patching."
      when: root_free_kb.stdout | int < 1048576

  tasks:
    - name: Debian/Ubuntu - update and dist-upgrade
      apt:
        update_cache: yes
        upgrade: dist
      register: debian_updates
      when: ansible_os_family == "Debian"
      notify: maybe_reboot

    - name: RHEL-like - update all packages to latest
      dnf:
        name: "*"
        state: latest
      register: rhel_updates
      when: ansible_pkg_mgr == "dnf"
      notify: maybe_reboot

    - name: Debian/Ubuntu - check reboot-required file
      stat:
        path: /var/run/reboot-required
      register: reboot_flag
      when: ansible_os_family == "Debian"

    - name: RHEL-like - check if reboot is required (needs-restarting)
      shell: needs-restarting -r || true
      register: needs_reboot
      changed_when: false
      failed_when: false
      when: ansible_os_family == "RedHat"

    - name: Set reboot_needed fact
      set_fact:
        reboot_needed: >-
          {{
            (ansible_os_family == "Debian" and reboot_flag.stat.exists | default(false))
            or
            (ansible_os_family == "RedHat" and (needs_reboot.rc | default(0)) == 1)
          }}

  handlers:
    - name: maybe_reboot
      when: reboot_needed | default(false)
      listen: maybe_reboot
      block:
        - name: Reboot and wait
          reboot:
            reboot_timeout: 900

        - name: Verify system is back and stable
          wait_for_connection:
            timeout: 120

  post_tasks:
    - name: Collect kernel and uptime for report
      shell: "uname -r && uptime -p"
      register: system_report
      changed_when: false

    - name: Show summary
      debug:
        msg:
          - "Host: {{ inventory_hostname }}"
          - "Rebooted: {{ reboot_needed | default(false) }}"
          - "Kernel/Uptime: {{ system_report.stdout_lines }}"

Safety net: run with --check --diff first to preview changes.


Hands-On Lab: Monthly Patch Run

Goal: Safely patch all servers with rolling updates and conditional reboots.

  • Step 0: Make coffee. You’ll have time to drink it.
  • Step 1: Create inventory.ini with host groups.
  • Step 2: Save the Monthly Patching playbook as patch.yml.
  • Step 3: Dry run:
ansible-playbook -i inventory.ini patch.yml --check --diff
  • Step 4: Execute:
ansible-playbook -i inventory.ini patch.yml
  • Step 5: If you’re nervous, limit to one group or host:
ansible-playbook -i inventory.ini patch.yml --limit web

If something fails, Ansible stops the batch (thanks, serial). Fix the issue, re-run, and carry on.


Scheduling and Reporting

Make it boring—in the best way.

Options:

  • Cron: Run ansible-playbook monthly from a trusted control node; log output to a file.
  • AWX/Ansible Automation Platform: Click “Template,” pick your project and inventory, set a Schedule, and enjoy a history of runs with notifications.
  • Reporting: Email or Slack a summary by parsing debug results or using callback plugins.

Example cron entry:

0 2 1 * * /usr/local/bin/ansible-playbook -i /etc/ansible/inventory.ini /opt/patch.yml >> /var/log/ansible/patch-$(date +\%Y-\%m).log 2>&1

Runbooks age well when they leave breadcrumbs. Keep logs, keep notes, keep your future self happy.


Q&A

Q: Do I need root access? A: Only for privileged changes — use --become. Keep sudo tight and audited.

Q: Can Ansible manage Windows patching? A: Yes. Use win_updates over WinRM and the same rolling strategy. Same playbook pattern, different modules.

Q: What about rollback? A: For OS updates, favor snapshots/AMIs or VM backups before patching. Package managers offer limited rollback; snapshots are your safety rope.

Q: How do I avoid downtime? A: Use serial, health checks, and maintenance windows. Start with a small --limit, then expand. Practice in non-prod first.


Closing thought: Good ops feels calm. With a small investment in Ansible, patch night turns from adrenaline to routine. That’s the win.