When transitioning a server's role (like converting a web server to a different purpose), we face a common infrastructure-as-code dilemma: Ansible excels at declaring desired states but lacks native "undo" functionality. Unlike Puppet's ensure => absent
paradigm, Ansible requires explicit cleanup tasks.
For our NGINX example, we need to modify the role to include removal logic. Here's a task file (tasks/remove.yml
) that should be included in your role:
---
- name: Remove NGINX package
ansible.builtin.package:
name: nginx
state: absent
- name: Purge configuration files
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- /etc/nginx
- /var/www/html
- /var/log/nginx
- name: Remove firewall rules
ansible.posix.firewalld:
service: "{{ item }}"
permanent: yes
state: disabled
immediate: yes
loop:
- http
- https
when: ansible_os_family == 'RedHat'
Create a role variable (defaults/main.yml
) to control removal behavior:
nginx_cleanup: false
Then modify your main tasks file (tasks/main.yml
) with conditional inclusion:
---
- import_tasks: install.yml
when: not nginx_cleanup
- import_tasks: remove.yml
when: nginx_cleanup
For host-specific cleanup, use group variables. First remove the host from [webservers]
, then in group_vars/all.yml
:
nginx_cleanup: "{{ 'webservers' not in group_names }}"
For comprehensive firewall cleanup, consider these additional approaches:
- name: Remove specific port rules
ansible.posix.ufw:
rule: deny
port: "{{ item }}"
proto: tcp
loop:
- 80
- 443
when: ansible_os_family == 'Debian'
Or for more complex scenarios using iptables
directly:
- name: Remove NGINX-related iptables rules
ansible.builtin.command: iptables -D INPUT -p tcp --dport {{ item }} -j ACCEPT
loop:
- 80
- 443
ignore_errors: yes
When transitioning servers between roles in Ansible infrastructure, residual configurations often remain. Unlike Puppet's declarative approach with ensure => absent
, Ansible requires more deliberate cleanup strategies.
For complete removal of packages and configurations, create a removal playbook that reverses installation steps:
- name: Remove nginx and clean configurations
hosts: former_webservers
become: yes
tasks:
- name: Stop nginx service
ansible.builtin.service:
name: nginx
state: stopped
enabled: no
- name: Remove nginx package
ansible.builtin.package:
name: nginx
state: absent
- name: Remove configuration directories
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- /etc/nginx
- /var/www/html
- /var/log/nginx
Firewall rules require explicit removal in Ansible. The ansible.posix.firewall
module provides clean removal:
- name: Clean up firewall rules
ansible.posix.firewall:
rule: "{{ item.rule }}"
permanent: yes
immediate: yes
state: absent
loop:
- { rule: "service name=http" }
- { rule: "service name=https" }
Implement a tagging system for reversible operations during initial role creation:
- name: Install nginx
ansible.builtin.package:
name: nginx
state: present
tags: nginx_install
- name: Configure firewall
ansible.posix.firewall:
rule: "service name=http"
permanent: yes
state: enabled
tags: firewall_config
This allows selective cleanup using --tags
when running the playbook in reverse.
Create a removal playbook that checks current state before removal:
- name: Verify nginx exists before removal
ansible.builtin.command: which nginx
register: nginx_exists
ignore_errors: yes
changed_when: false
- name: Remove nginx if exists
ansible.builtin.package:
name: nginx
state: absent
when: nginx_exists.rc == 0
Maintain separate inventory groups for transition periods:
[webservers:children]
active_webservers
decommissioning_webservers
This allows phased removal while maintaining visibility of servers in transition.