How to Rollback Specific Playbook Changes in Ansible: A Technical Guide for System Automation


23 views

When managing infrastructure with Ansible, one common pain point is handling rollbacks after executing playbooks with multiple plays. Unlike traditional version control systems, Ansible doesn't inherently provide a rollback mechanism for specific actions within a playbook.

1. Using Check Mode and Tagging

Implement granular control through tags and check mode validation:


- name: Create users (rollbackable)
  user:
    name: "{{ item }}"
    state: present
  with_items:
    - user1
    - user2
    - user3
  tags: user_management

- name: Install packages (rollbackable)
  yum:
    name: "{{ item }}"
    state: present
  with_items:
    - package1
    - package2
  tags: package_management

2. Implementing Custom Rollback Tasks

Design your playbook with explicit rollback capabilities:


- name: Main task - create config
  template:
    src: config.j2
    dest: /etc/app/config.conf
  register: config_change

- name: Rollback task
  template:
    src: config.j2
    dest: /etc/app/config.conf.bak
  when: config_change is failed
  run_once: true

1. Version-Controlled Configuration

Combine Ansible with version control systems for atomic rollbacks:


- name: Commit current config state
  command: git add /etc/app/ && git commit -m "Pre-deployment state"
  changed_when: false

- name: Deploy new configuration
  template:
    src: new_config.j2
    dest: /etc/app/config.conf

- name: Rollback if needed
  command: git checkout -- /etc/app/config.conf
  when: rollback_required

2. Using Ansible Handlers for Recovery

Leverage handlers to automatically trigger rollback procedures:


tasks:
  - name: Deploy new service version
    copy:
      src: service_v2
      dest: /usr/bin/service
    notify: rollback_service_version

handlers:
  - name: rollback_service_version
    copy:
      src: service_v1
      dest: /usr/bin/service
    when: service_status_check.rc != 0
  • Always register task results for conditional rollback triggers
  • Implement comprehensive pre-flight checks before destructive operations
  • Maintain separate rollback playbooks for complex deployments
  • Consider using AWX/Tower for enhanced job tracking and rollback capabilities
  • Document rollback procedures alongside deployment playbooks

- name: Database schema update
  command: /usr/bin/db-update-schema
  register: schema_update
  failed_when: "schema_update.rc not in [0, 2]"

- name: Rollback schema if needed
  command: /usr/bin/db-rollback-schema
  when: schema_update.rc == 1
  notify:
    - restart_application
    - notify_team

- name: Application deployment
  copy:
    src: app.jar
    dest: /opt/app/
  when: schema_update.rc != 1

When managing infrastructure with Ansible, there's no built-in "undo" button. Unlike version control systems that track file changes, Ansible executes tasks sequentially without automatically storing previous states. This becomes particularly challenging when you need to revert specific plays within a complex playbook.

Consider these typical situations where rollback capability would be valuable:

  • User creation that needs to be reversed after testing
  • Package installations causing dependency conflicts
  • Service configurations that break existing functionality
  • File modifications that need to be restored

Here are several methods to implement rollback functionality:

1. Using Check Mode and Diff for Dry Runs

Always test changes first:


ansible-playbook playbook.yml --check --diff

2. Implementing Idempotent Playbooks

Design playbooks to be safely rerun:


- name: Ensure users are present or absent
  user:
    name: "{{ item }}"
    state: "{{ user_state }}"
  loop: "{{ users_list }}"

3. Creating Dedicated Rollback Playbooks

Build companion playbooks for each operation:


# rollback_users.yml
- name: Remove test users
  user:
    name: "{{ item }}"
    state: absent
    remove: yes
  loop:
    - test_user1
    - test_user2
    - test_user3

4. Leveraging Ansible's Built-in Modules

Many modules support undo operations:


- name: Uninstall packages
  package:
    name: "{{ item }}"
    state: absent
  loop:
    - package1
    - package2

Using Tags for Selective Rollback

Organize your playbook with tags:


- name: Install packages
  package:
    name: "{{ item }}"
    state: present
  loop: "{{ packages_to_install }}"
  tags: package_install

- name: Configure service
  template:
    src: service.conf.j2
    dest: /etc/service.conf
  tags: service_config

Then run only the rollback tasks:


ansible-playbook rollback.yml --tags "package_rollback"

Implementing State Snapshots

Capture system state before changes:


- name: Capture current users
  command: "cut -d: -f1 /etc/passwd"
  register: users_before
  changed_when: false

- name: Save package list
  command: "rpm -qa"
  register: packages_before
  changed_when: false
  • Always make playbooks idempotent
  • Use variables to control state (present/absent)
  • Implement comprehensive tagging
  • Document rollback procedures alongside main playbooks
  • Consider using ARA for recording playbook runs
  • Store previous configurations in version control

Here's a complete example for user management with rollback:


# main_playbook.yml
- name: User management
  hosts: all
  vars:
    users_to_manage:
      - name: dev_user
        state: present
      - name: test_user
        state: present
  
  tasks:
    - name: Create or remove users
      user:
        name: "{{ item.name }}"
        state: "{{ item.state }}"
      loop: "{{ users_to_manage }}"
      tags: user_management

# rollback.yml
- name: Rollback user changes
  hosts: all
  tasks:
    - name: Remove all managed users
      user:
        name: "{{ item }}"
        state: absent
        remove: yes
      loop: "{{ users_to_manage | map(attribute='name') | list }}"
      when: item.state == "present"
      tags: user_rollback