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