Ansible Complete Master Guide¶
Complete automation and configuration management guide - from installation to enterprise patterns.
Installation Guide¶
Choose your operating system:
Ansible Installation Guide¶
Windows 11¶
Using Chocolatey (Recommended)¶
-
Install Chocolatey:
-
Open an elevated PowerShell prompt (Run as Administrator).
-
Run the following command to install Chocolatey:
Set-ExecutionPolicy Bypass -Scope Process -Force [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) -
Install Ansible:
-
Install Ansible using Chocolatey:
choco install ansible -y -
Verify Installation:
- Check the Ansible version:
ansible --version
Using Windows Subsystem for Linux (WSL)¶
- Enable WSL:
-
Open PowerShell as Administrator and run:
wsl --update -
Install a Linux Distribution:
-
Open Microsoft Store and install a Linux distribution (e.g., Ubuntu).
-
Set Up WSL:
- Open the installed Linux distribution.
-
Follow the on-screen instructions to set up the Linux environment.
-
Install Ansible in WSL:
- Update the package list:
sudo apt update -
Install Ansible:
sudo apt install ansible -
Verify Installation:
- Check the Ansible version:
ansible --version
Ubuntu/Debian¶
sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible
CentOS/RHEL¶
sudo yum install epel-release
sudo yum install ansible
macOS¶
brew install ansible
Python pip (any system)¶
pip install ansible
Verify installation:
ansible --version
Basic Commands¶
| Command | Description |
|---|---|
ansible all -m ping |
Ping all hosts in inventory |
ansible-playbook playbook.yml |
Run a playbook |
ansible-galaxy init <role> |
Initialize a new role |
ansible -i hosts.ini webservers -m setup |
Gather facts from webservers group |
ansible-vault create secrets.yml |
Create encrypted file |
ansible-doc -l |
List all modules |
ansible-doc apt |
Show module documentation |
Inventory¶
Save as hosts.ini:
[webservers]
192.168.1.10
192.168.1.11
[dbservers]
192.168.1.20
[all:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/id_rsa
ansible_python_interpreter=/usr/bin/python3
BEGINNER LEVEL: Core Concepts & First Steps¶
Scenario 1: Test Connectivity and Gather Facts¶
Description: Verify Ansible can reach all servers and collect system information.
sequenceDiagram
participant User as Developer
participant Ansible as Ansible Control Node
participant Host1 as 192.168.1.10
participant Host2 as 192.168.1.11
participant Host3 as 192.168.1.20
User->>Ansible: Execute ansible all -m ping
Ansible->>Host1: SSH connection test
Host1-->>Ansible: pong (success)
Ansible->>Host2: SSH connection test
Host2-->>Ansible: pong (success)
Ansible->>Host3: SSH connection test
Host3-->>Ansible: pong (success)
User->>Ansible: Execute ansible all -m setup
Ansible->>Host1: Collect system facts
Host1-->>Ansible: OS, IP, memory, CPU info
Ansible->>Host2: Collect system facts
Host2-->>Ansible: OS, IP, memory, CPU info
Ansible->>Host3: Collect system facts
Host3-->>Ansible: OS, IP, memory, CPU info
Ansible-->>User: Display all collected facts
Commands:
# Test connectivity
ansible all -i hosts.ini -m ping
# Expected output:
# 192.168.1.10 | SUCCESS => {
# "ansible_facts": {
# "discovered_interpreter_python": "/usr/bin/python3"
# },
# "changed": false,
# "ping": "pong"
# }
# Gather facts from all hosts
ansible all -i hosts.ini -m setup
# Get specific fact from webservers
ansible webservers -i hosts.ini -m setup -a "filter=ansible_distribution"
Scenario 2: Install and Start Nginx Web Server¶
Description: Install Nginx on webservers and ensure it's running with a basic configuration.
sequenceDiagram
participant User as Developer
participant Ansible as Ansible Control Node
participant Web1 as webserver-01 (192.168.1.10)
participant Web2 as webserver-02 (192.168.1.11)
participant Apt as APT Repository
User->>Ansible: Run nginx_deploy.yml playbook
Ansible->>Web1: Connect via SSH
Ansible->>Web2: Connect via SSH
loop On each webserver
Ansible->>Web1: Update apt cache (apt update)
Web1->>Apt: Request package list
Apt-->>Web1: Return latest packages
Ansible->>Web1: Install Nginx (apt install nginx)
Web1->>Apt: Download and install package
Apt-->>Web1: Installation complete
Ansible->>Web1: Start Nginx service (systemctl start nginx)
Web1->>Web1: Start nginx process
Web1-->>Ansible: Service started
Ansible->>Web1: Enable on boot (systemctl enable nginx)
Web1->>Web1: Create systemd symlink
Web1-->>Ansible: Enabled successfully
end
loop On webserver-02
Ansible->>Web2: Execute same tasks
Web2->>Apt: Download and install
Web2->>Web2: Start and enable service
end
Ansible-->>User: Playbook complete (both servers configured)
Code - Save as nginx_deploy.yml:
---
- name: Install and configure Nginx web servers
hosts: webservers
become: yes
tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
tags: update
- name: Install Nginx package
apt:
name: nginx
state: present
notify: Start Nginx
- name: Ensure Nginx is running and enabled
service:
name: nginx
state: started
enabled: yes
handlers:
- name: Start Nginx
service:
name: nginx
state: started
Execute:
ansible-playbook -i hosts.ini nginx_deploy.yml
Scenario 3: Create Directory Structure and Permissions¶
Description: Create multiple directories with specific owners and permissions for an application.
sequenceDiagram
participant User as Developer
participant Ansible as Ansible Control Node
participant Server as Application Server
User->>Ansible: Run create_directories.yml playbook
Ansible->>Server: Define directory structure variables
loop Process directory list
Ansible->>Server: Create /opt/myapp (owner: root, mode: 0755)
Server->>Server: mkdir -p /opt/myapp
Server->>Server: chmod 0755 /opt/myapp
Server->>Server: chown root:root /opt/myapp
Server-->>Ansible: Directory created
Ansible->>Server: Create /var/log/myapp (owner: www-data, mode: 0755)
Server->>Server: mkdir -p /var/log/myapp
Server->>Server: chmod 0755 /var/log/myapp
Server->>Server: chown www-data:www-data /var/log/myapp
Server-->>Ansible: Directory created
Ansible->>Server: Create /data/myapp/uploads (owner: www-data, mode: 0775)
Server->>Server: mkdir -p /data/myapp/uploads
Server->>Server: chmod 0775 /data/myapp/uploads
Server->>Server: chown www-data:www-data /data/myapp/uploads
Server-->>Ansible: Directory created
end
Ansible->>Server: Verify all directories exist
Server-->>Ansible: All directories present and configured
Ansible-->>User: Playbook complete - directories created
Code - Save as create_directories.yml:
---
- name: Create application directory structure
hosts: webservers
become: yes
vars:
app_name: myapp
app_directories:
- path: "/opt/{{ app_name }}"
owner: root
group: root
mode: '0755'
- path: "/var/log/{{ app_name }}"
owner: www-data
group: www-data
mode: '0755'
- path: "/data/{{ app_name }}/uploads"
owner: www-data
group: www-data
mode: '0775'
- path: "/etc/{{ app_name }}"
owner: root
group: root
mode: '0700'
tasks:
- name: Create directory structure
file:
path: "{{ item.path }}"
state: directory
owner: "{{ item.owner }}"
group: "{{ item.group }}"
mode: "{{ item.mode }}"
loop: "{{ app_directories }}"
register: directory_results
- name: Display created directories
debug:
msg: "Created {{ item.path }} with mode {{ item.mode }}"
loop: "{{ directory_results.results }}"
when: item.changed
handlers:
- name: Restart application
systemd:
name: "{{ app_name }}"
state: restarted
Execute:
ansible-playbook -i hosts.ini create_directories.yml
Scenario 4: Deploy Static Website Files¶
Description: Copy website files to webservers and configure Nginx virtual host.
sequenceDiagram
participant User as Developer
participant Ansible as Ansible Control Node
participant Web as Web Servers
participant Nginx as Nginx Process
User->>Ansible: Run deploy_website.yml playbook
Ansible->>Ansible: Load variables from group_vars
Ansible->>Web: Create document root directory
Web->>Web: mkdir -p /var/www/html/myapp
loop Copy website assets
Ansible->>Web: Copy index.html
Ansible->>Web: Copy CSS files
Ansible->>Web: Copy JavaScript files
Ansible->>Web: Copy images
end
Ansible->>Web: Deploy Nginx virtual host config
Web->>Web: Write /etc/nginx/sites-available/myapp
Web->>Web: Create symlink to sites-enabled
Web-->>Ansible: Configuration deployed
Ansible->>Web: Test Nginx configuration
Web->>Nginx: nginx -t
Nginx-->>Web: Syntax OK
Ansible->>Web: Reload Nginx
Web->>Nginx: nginx -s reload
Nginx-->>Web: Reloaded successfully
Ansible->>Web: Verify website is accessible
Web->>Web: curl http://localhost/myapp
Web-->>Ansible: HTTP 200 OK
Ansible-->>User: Deployment complete
Code - Save as deploy_website.yml:
---
- name: Deploy static website with Nginx configuration
hosts: webservers
become: yes
vars:
website_name: myapp
website_root: "/var/www/html/{{ website_name }}"
nginx_config_path: "/etc/nginx/sites-available/{{ website_name }}"
tasks:
- name: Install Nginx (if not already installed)
apt:
name: nginx
state: present
update_cache: yes
- name: Create website document root
file:
path: "{{ website_root }}"
state: directory
owner: www-data
group: www-data
mode: '0755'
- name: Deploy website files
copy:
src: "{{ item }}"
dest: "{{ website_root }}/"
owner: www-data
group: www-data
mode: '0644'
loop:
- files/index.html
- files/css/
- files/js/
- files/images/
- name: Deploy Nginx virtual host configuration
template:
src: templates/nginx_vhost.j2
dest: "{{ nginx_config_path }}"
owner: root
group: root
mode: '0644'
notify: Reload Nginx
- name: Enable virtual host
file:
src: "{{ nginx_config_path }}"
dest: "/etc/nginx/sites-enabled/{{ website_name }}"
state: link
- name: Test Nginx configuration
command: nginx -t
changed_when: false
- name: Ensure Nginx is running
service:
name: nginx
state: started
enabled: yes
handlers:
- name: Reload Nginx
service:
name: nginx
state: reloaded
Template - Save as templates/nginx_vhost.j2:
server {
listen 80;
server_name {{ website_name }}.example.com;
root {{ website_root }};
index index.html;
location / {
try_files $uri $uri/ =404;
}
access_log /var/log/nginx/{{ website_name }}_access.log;
error_log /var/log/nginx/{{ website_name }}_error.log;
}
Execute:
ansible-playbook -i hosts.ini deploy_website.yml
Scenario 5: Manage Users and SSH Keys¶
Description: Create system users and deploy SSH public keys for key-based authentication.
sequenceDiagram
participant User as Ansible Admin
participant Ansible as Ansible Control Node
participant Server as Target Servers
participant SSH as SSH Service
User->>Ansible: Run manage_users.yml playbook
Ansible->>Ansible: Load user list from variables
loop Process each user
Ansible->>Server: Create user account
Server->>Server: useradd -m -s /bin/bash deployer
Server->>Server: Set password (disabled)
Server-->>Ansible: User created
Ansible->>Server: Create .ssh directory
Server->>Server: mkdir -p /home/deployer/.ssh
Server->>Server: chmod 0700 /home/deployer/.ssh
Server-->>Ansible: Directory created
Ansible->>Server: Deploy SSH public key
Server->>Server: Add key to authorized_keys
Server->>Server: chmod 0600 authorized_keys
Server-->>Ansible: Key deployed
Ansible->>Server: Set correct ownership
Server->>Server: chown -R deployer:deployer /home/deployer/.ssh
Server-->>Ansible: Ownership set
end
Ansible->>Server: Verify SSH key authentication
Server->>SSH: Test key-based login
SSH-->>Server: Authentication successful
Ansible-->>User: All users configured
Code - Save as manage_users.yml:
---
- name: Manage system users and SSH keys
hosts: all
become: yes
vars:
users:
- name: deployer
groups: sudo
ssh_key: files/keys/deployer.pub
shell: /bin/bash
- name: monitoring
groups: monitoring
ssh_key: files/keys/monitoring.pub
shell: /bin/bash
- name: readonly
groups: users
shell: /bin/false
tasks:
- name: Create user groups
group:
name: "{{ item }}"
state: present
loop:
- sudo
- monitoring
- users
- name: Create user accounts
user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
shell: "{{ item.shell }}"
create_home: yes
password: "!" # Disable password login
state: present
loop: "{{ users }}"
- name: Create .ssh directory for each user
file:
path: "/home/{{ item.name }}/.ssh"
state: directory
owner: "{{ item.name }}"
group: "{{ item.name }}"
mode: '0700'
loop: "{{ users }}"
when: item.ssh_key is defined
- name: Deploy SSH public keys
authorized_key:
user: "{{ item.name }}"
key: "{{ lookup('file', item.ssh_key) }}"
state: present
manage_dir: yes
loop: "{{ users }}"
when: item.ssh_key is defined
- name: Configure sudoers for deployer
lineinfile:
path: /etc/sudoers.d/deployer
line: "deployer ALL=(ALL) NOPASSWD:ALL"
create: yes
mode: '0440'
validate: 'visudo -cf %s'
handlers:
- name: Restart SSH service
service:
name: sshd
state: restarted
Execute:
ansible-playbook -i hosts.ini manage_users.yml
INTERMEDIATE LEVEL: Variables, Conditionals & Roles¶
Scenario 6: Deploy Application with Variables and Handlers¶
Description: Deploy a Python application using variables for configuration and handlers for service management.
sequenceDiagram
participant User as Developer
participant Ansible as Ansible Control Node
participant App as Application Server
participant Git as Git Repository
participant Service as Systemd Service
User->>Ansible: Run deploy_app.yml playbook
Ansible->>App: Install Python dependencies
App->>App: apt install python3-pip python3-venv
Ansible->>App: Create application user
App->>App: useradd --system --home /opt/myapp app
Ansible->>Git: Clone application repository
Git-->>App: Download source code
Ansible->>App: Create Python virtual environment
App->>App: python3 -m venv /opt/myapp/venv
Ansible->>App: Install Python requirements
App->>App: pip install -r requirements.txt
Ansible->>App: Deploy systemd service file
App->>Service: Write /etc/systemd/system/myapp.service
Ansible->>App: Reload systemd daemon
App->>Service: systemctl daemon-reload
Service-->>App: Reloaded
Ansible->>App: Start application service
App->>Service: systemctl start myapp
Service-->>App: Service started
App->>App: Check application health
App->>Service: systemctl status myapp
Service-->>App: Active (running)
Ansible-->>User: Application deployed successfully
Code - Save as deploy_app.yml:
---
- name: Deploy Python web application
hosts: appservers
become: yes
vars:
app_name: myapp
app_version: 1.2.0
app_port: 8000
app_user: app
python_version: 3.9
git_repo: https://github.com/myorg/myapp.git
tasks:
- name: Install system dependencies
apt:
name:
- python3-pip
- python3-venv
- git
- gcc
state: present
update_cache: yes
- name: Create application user
user:
name: "{{ app_user }}"
system: yes
shell: /bin/false
create_home: yes
home: "/opt/{{ app_name }}"
- name: Clone application repository
git:
repo: "{{ git_repo }}"
dest: "/opt/{{ app_name }}/src"
version: "v{{ app_version }}"
force: yes
- name: Create virtual environment
pip:
virtualenv: "/opt/{{ app_name }}/venv"
virtualenv_command: python3 -m venv
requirements: "/opt/{{ app_name }}/src/requirements.txt"
virtualenv_python: python{{ python_version }}
- name: Deploy systemd service file
template:
src: templates/app.service.j2
dest: "/etc/systemd/system/{{ app_name }}.service"
owner: root
group: root
mode: '0644'
notify: Reload systemd
- name: Deploy application configuration
template:
src: templates/app.conf.j2
dest: "/etc/{{ app_name }}/config.yaml"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: '0640'
- name: Ensure application service is running
systemd:
name: "{{ app_name }}"
state: started
enabled: yes
daemon_reload: yes
handlers:
- name: Reload systemd
systemd:
daemon_reload: yes
- name: Restart application
systemd:
name: "{{ app_name }}"
state: restarted
Template - Save as templates/app.service.j2:
[Unit]
Description={{ app_name }} application
After=network.target
[Service]
Type=notify
User={{ app_user }}
Group={{ app_user }}
WorkingDirectory=/opt/{{ app_name }}/src
Environment="PATH=/opt/{{ app_name }}/venv/bin"
ExecStart=/opt/{{ app_name }}/venv/bin/python -m app --port {{ app_port }}
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Execute:
ansible-playbook -i hosts.ini deploy_app.yml
Scenario 7: Conditional Execution Based on OS and Facts¶
Description: Install packages differently based on operating system and ensure compatibility.
sequenceDiagram
participant User as Developer
participant Ansible as Ansible Control Node
participant Ubuntu as Ubuntu Server
participant CentOS as CentOS Server
participant Debian as Debian Server
User->>Ansible: Run install_packages.yml playbook
Ansible->>Ubuntu: Gather facts (ansible_os_family)
Ubuntu-->>Ansible: "Debian"
Ansible->>CentOS: Gather facts (ansible_os_family)
CentOS-->>Ansible: "RedHat"
Ansible->>Debian: Gather facts (ansible_os_family)
Debian-->>Ansible: "Debian"
alt OS Family is Debian/Ubuntu
Ansible->>Ubuntu: Use apt module
Ubuntu->>Ubuntu: apt install htop vim curl
Ubuntu-->>Ansible: Packages installed
else OS Family is RedHat/CentOS
Ansible->>CentOS: Use yum module
CentOS->>CentOS: yum install htop vim curl
CentOS-->>Ansible: Packages installed
end
Ansible->>Ubuntu: Check if NTP should be installed
Ubuntu-->>Ansible: ansible_distribution_version = "20.04"
alt Ubuntu version < 22.04
Ansible->>Ubuntu: Install chrony
Ubuntu->>Ubuntu: apt install chrony
else Ubuntu version >= 22.04
Ansible->>Ubuntu: Install systemd-timesyncd
Ubuntu->>Ubuntu: Use built-in service
end
Ansible->>CentOS: Install chrony for all versions
CentOS->>CentOS: yum install chrony
Ansible-->>User: All packages installed appropriately
Code - Save as install_packages.yml:
---
- name: Install packages conditionally by OS
hosts: all
become: yes
vars:
common_packages:
- htop
- vim
- curl
- git
- tmux
tasks:
- name: Install packages on Debian/Ubuntu
apt:
name: "{{ common_packages }}"
state: present
update_cache: yes
when: ansible_os_family == "Debian"
- name: Install packages on RedHat/CentOS
yum:
name: "{{ common_packages }}"
state: present
when: ansible_os_family == "RedHat"
- name: Install NTP service (Ubuntu < 22.04)
apt:
name: chrony
state: present
when:
- ansible_distribution == "Ubuntu"
- ansible_distribution_version is version('22.04', '<')
- name: Use systemd-timesyncd (Ubuntu >= 22.04)
systemd:
name: systemd-timesyncd
state: started
enabled: yes
when:
- ansible_distribution == "Ubuntu"
- ansible_distribution_version is version('22.04', '>=')
- name: Install NTP service (CentOS/RedHat)
yum:
name: chrony
state: present
when: ansible_os_family == "RedHat"
- name: Ensure chrony is running
service:
name: chronyd
state: started
enabled: yes
when: ansible_os_family == "RedHat" or (ansible_distribution == "Ubuntu" and ansible_distribution_version is version('22.04', '<'))
- name: Verify package installation
command: which htop
register: package_check
changed_when: false
- name: Show package location
debug:
msg: "htop installed at {{ package_check.stdout }}"
when: package_check.rc == 0
Execute:
ansible-playbook -i hosts.ini install_packages.yml
Scenario 8: Using Loops and Registering Results¶
Description: Create multiple users and capture results for reporting and follow-up tasks.
sequenceDiagram
participant User as Developer
participant Ansible as Ansible Control Node
participant Server as Database Server
User->>Ansible: Run manage_users_advanced.yml playbook
Ansible->>Server: Define user list with attributes
loop For each user in users list
Ansible->>Server: Create user account
Server->>Server: useradd -s /bin/bash app_user
Server-->>Ansible: Return result (changed=true)
Ansible->>Ansible: Store result in user_creation_results
Ansible->>Server: Set password (hashed)
Server->>Server: chpasswd with encrypted password
Server-->>Ansible: Password set
Ansible->>Ansible: Store password result
Ansible->>Server: Add to secondary groups
Server->>Server: usermod -aG sudo app_user
Server-->>Ansible: Groups updated
end
Ansible->>Ansible: Process registered results
Ansible->>Server: Display summary of created users
Server->>Server: Output list of users
Ansible->>Server: Generate report
Server->>Server: Write /tmp/user_creation_report.txt
Ansible->>Server: Email report to admin (if configured)
Server-->>Ansible: Report generated
Ansible-->>User: Show creation summary
Code - Save as manage_users_advanced.yml:
---
- name: Create application users with registered results
hosts: dbservers
become: yes
vars:
users:
- name: app_user
groups: sudo
shell: /bin/bash
comment: "Application User"
- name: backup_user
groups: backup
shell: /bin/bash
comment: "Backup Service Account"
- name: monitoring
groups: monitoring
shell: /bin/false
comment: "Monitoring Agent"
tasks:
- name: Create users
user:
name: "{{ item.name }}"
shell: "{{ item.shell }}"
groups: "{{ item.groups }}"
comment: "{{ item.comment }}"
state: present
loop: "{{ users }}"
register: user_creation_results
loop_control:
label: "{{ item.name }}"
- name: Set passwords for users (encrypted)
user:
name: "{{ item.name }}"
password: "{{ 'ChangeMe123!' | password_hash('sha512') }}"
loop: "{{ users }}"
no_log: true
when: user_creation_results.results[loop.index0].changed
- name: Show creation results
debug:
msg: "User {{ item.item.name }} created: {{ 'Yes' if item.changed else 'No' }}"
loop: "{{ user_creation_results.results }}"
loop_control:
label: "{{ item.item.name }}"
- name: Generate creation report
copy:
dest: "/tmp/user_creation_report_{{ ansible_date_time.date }}.txt"
content: |
User Creation Report for {{ inventory_hostname }}
Generated: {{ ansible_date_time.iso8601 }}
{% for result in user_creation_results.results %}
{% if result.changed %}
- {{ result.item.name }} ({{ result.item.comment }})
UID: {{ result.uid }}
Groups: {{ result.item.groups }}
Shell: {{ result.item.shell }}
{% endif %}
{% endfor %}
Total users created: {{ user_creation_results.results | selectattr('changed') | list | length }}
delegate_to: localhost
run_once: yes
- name: Display report location
debug:
msg: "Report saved to /tmp/user_creation_report_{{ ansible_date_time.date }}.txt"
delegate_to: localhost
run_once: yes
Execute:
ansible-playbook -i hosts.ini manage_users_advanced.yml
Scenario 9: Build and Deploy Application with Git¶
Description: Clone a Git repository, build a Java application, and deploy with systemd service.
sequenceDiagram
participant User as Developer
participant Ansible as Ansible Control Node
participant App as Application Server
participant GitHub as Git Repository
participant Maven as Maven Build Tool
participant Service as Systemd Service
User->>Ansible: Run build_deploy_java.yml playbook
Ansible->>App: Install OpenJDK and Maven
App->>App: apt install openjdk-17-jdk maven
Ansible->>GitHub: Clone application repository
GitHub-->>App: Download source code
Ansible->>App: Build application
App->>Maven: mvn clean package
Maven->>App: Compile and package WAR file
Ansible->>App: Check if build succeeded
App->>App: Verify target/*.war exists
App-->>Ansible: Build successful
Ansible->>App: Backup previous version
App->>App: cp app.war app.war.backup
Ansible->>App: Deploy new WAR file
App->>App: Copy to /opt/tomcat/webapps/
Ansible->>App: Deploy updated systemd service (if needed)
App->>Service: systemctl daemon-reload
Ansible->>App: Restart Tomcat service
App->>Service: systemctl restart tomcat
Service-->>App: Tomcat restarted
Ansible->>App: Check application health endpoint
App->>Service: curl http://localhost:8080/health
Service-->>App: HTTP 200 - Application is healthy
Ansible->>App: Clean up old builds
App->>App: Remove old artifacts
Ansible-->>User: Deployment complete, health check passed
Code - Save as build_deploy_java.yml:
---
- name: Build and deploy Java application
hosts: appservers
become: yes
vars:
app_name: my-java-app
git_repo: https://github.com/myorg/java-app.git
git_version: main
build_dir: "/tmp/{{ app_name }}-build"
deploy_dir: /opt/tomcat/webapps
tomcat_service: tomcat9
tasks:
- name: Install build dependencies
apt:
name:
- openjdk-17-jdk
- maven
- git
state: present
update_cache: yes
- name: Clone application repository
git:
repo: "{{ git_repo }}"
dest: "{{ build_dir }}"
version: "{{ git_version }}"
force: yes
- name: Build application with Maven
command: mvn clean package -DskipTests
args:
chdir: "{{ build_dir }}"
register: maven_build
- name: Check if build was successful
stat:
path: "{{ build_dir }}/target/{{ app_name }}.war"
register: war_file
- name: Fail if WAR file not found
fail:
msg: "Build failed - WAR file not found"
when: not war_file.stat.exists
- name: Backup existing deployment
copy:
src: "{{ deploy_dir }}/{{ app_name }}.war"
dest: "{{ deploy_dir }}/{{ app_name }}.war.backup"
remote_src: yes
ignore_errors: yes
- name: Deploy new WAR file
copy:
src: "{{ build_dir }}/target/{{ app_name }}.war"
dest: "{{ deploy_dir }}/"
owner: tomcat
group: tomcat
mode: '0644'
backup: yes
notify: Restart Tomcat
- name: Wait for Tomcat to deploy WAR
wait_for:
path: "{{ deploy_dir }}/{{ app_name }}"
state: present
timeout: 180
- name: Check application health
uri:
url: "http://localhost:8080/{{ app_name }}/health"
status_code: 200
register: health_check
retries: 5
delay: 10
until: health_check.status == 200
- name: Display health status
debug:
msg: "Application is healthy! Response: {{ health_check.json }}"
- name: Clean up build directory
file:
path: "{{ build_dir }}"
state: absent
delegate_to: localhost
handlers:
- name: Restart Tomcat
systemd:
name: "{{ tomcat_service }}"
state: restarted
daemon_reload: yes
Execute:
ansible-playbook -i hosts.ini build_deploy_java.yml
Scenario 10: Ansible Vault for Secret Management¶
Description: Encrypt sensitive data like passwords and API keys, then use them securely in playbooks.
sequenceDiagram
participant User as Developer
participant Ansible as Ansible Control Node
participant Vault as Ansible Vault
participant Host as Remote Host
User->>Ansible: Create vault-encrypted file
Ansible->>Vault: ansible-vault create secrets.yml
Vault->>User: Prompt for vault password
User->>Vault: Enter vault password
Vault-->>User: Open encrypted file
User->>Vault: Enter secrets (DB password, API key)
Vault->>Vault: Encrypt content with AES256
Vault-->>Ansible: Save encrypted file
User->>Ansible: Run playbook with vault
Ansible->>Vault: Request decryption (password)
User->>Ansible: Provide --ask-vault-pass
Ansible->>Vault: Decrypt secrets.yml
Vault-->>Ansible: Return decrypted variables
loop Secure deployment
Ansible->>Host: Deploy application config
Ansible->>Host: Inject decrypted secrets
Note over Ansible,Host: Using {{ vault_db_password }}
Host->>Host: Write config with secrets
Host-->>Ansible: Config deployed
end
Ansible->>Host: Verify secret permissions
Host->>Host: chmod 600 config.yaml
Host-->>Host: File secured
Ansible-->>User: Deployment complete<br/>Secrets never exposed in logs
Step 1: Create encrypted vault file
# Create new encrypted file
ansible-vault create group_vars/all/vault.yml
Content - (when editor opens):
vault_db_password: supersecretpassword123
vault_db_user: admin
vault_api_key: abcdefghijklmnopqrstuvwxyz123456
vault_ssl_cert: |
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKL0UG+mRKxLMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
...
-----END CERTIFICATE-----
vault_jenkins_token: 11aabb223344556677889900aabbccdd
Step 2: Use vault variables in playbook - Save as deploy_secure_app.yml:
---
- name: Deploy application with encrypted secrets
hosts: appservers
become: yes
vars_files:
- group_vars/all/vault.yml
vars:
app_name: secureapp
db_host: 192.168.1.50
config_dir: "/etc/{{ app_name }}"
tasks:
- name: Create application config directory
file:
path: "{{ config_dir }}"
state: directory
owner: root
group: "{{ app_name }}"
mode: '0750'
- name: Deploy configuration with secrets
template:
src: templates/config.json.j2
dest: "{{ config_dir }}/config.json"
owner: root
group: "{{ app_name }}"
mode: '0640'
vars:
config:
database:
host: "{{ db_host }}"
username: "{{ vault_db_user }}"
password: "{{ vault_db_password }}"
api:
key: "{{ vault_api_key }}"
endpoint: "https://api.example.com/v1"
- name: Deploy SSL certificate
copy:
content: "{{ vault_ssl_cert }}"
dest: "{{ config_dir }}/cert.pem"
owner: root
group: "{{ app_name }}"
mode: '0600'
no_log: true
- name: Deploy Jenkins credential for CI/CD
copy:
content: "{{ vault_jenkins_token }}"
dest: "{{ config_dir }}/.jenkins-token"
owner: jenkins
mode: '0400'
no_log: true
- name: Ensure application can read config
file:
path: "{{ config_dir }}"
recurse: yes
owner: root
group: "{{ app_name }}"
handlers:
- name: Restart application
systemd:
name: "{{ app_name }}"
state: restarted
daemon_reload: yes
Template - Save as templates/config.json.j2:
{
"database": {
"host": "{{ config.database.host }}",
"username": "{{ config.database.username }}",
"password": "{{ config.database.password }}",
"port": 5432
},
"api": {
"key": "{{ config.api.key }}",
"endpoint": "{{ config.api.endpoint }}"
}
}
Execute with vault password:
ansible-playbook -i hosts.ini deploy_secure_app.yml --ask-vault-pass
Additional vault commands:
# Edit encrypted file
ansible-vault edit group_vars/all/vault.yml
# Rekey encrypted file (change password)
ansible-vault rekey group_vars/all/vault.yml
# Encrypt existing file
ansible-vault encrypt secrets.txt
# Decrypt file
ansible-vault decrypt secrets.txt
# View encrypted file content
ansible-vault view group_vars/all/vault.yml
# Run playbook with vault password file
echo "my-secure-password" > .vault_pass
ansible-playbook -i hosts.ini deploy_secure_app.yml --vault-password-file .vault_pass
chmod 600 .vault_pass
ADVANCED LEVEL: Enterprise Patterns & Complex Automation¶
Scenario 11: Dynamic Inventory with AWS EC2¶
Description: Automatically discover and manage cloud instances without static inventory files.
sequenceDiagram
participant User as Developer
participant Ansible as Ansible Control Node
participant AWS as AWS EC2 API
participant Plugin as AWS EC2 Plugin
participant Instance1 as Web Server (us-east-1)
participant Instance2 as DB Server (us-east-1)
participant Instance3 as Cache Server (us-west-2)
User->>Ansible: Configure aws_ec2.yml inventory
Ansible->>Plugin: Load dynamic inventory plugin
Plugin->>AWS: Query EC2 instances (API call)
AWS-->>Plugin: Return instance list & metadata
Note over Plugin: Group by tags:<br/>- role_webserver<br/>- role_database<br/>- role_cache
User->>Ansible: Run playbook with dynamic inventory
Ansible->>Plugin: Get inventory groups
Plugin-->>Ansible: Return dynamic groups
loop Auto-discovered hosts
Ansible->>Instance1: Connect using public IP
Instance1-->>Ansible: Connected (tag: webserver)
Ansible->>Instance2: Connect using private IP
Instance2-->>Ansible: Connected (tag: database)
Ansible->>Instance3: Connect using public IP
Instance3-->>Ansible: Connected (tag: cache, us-west-2)
end
Ansible->>Instance1: Execute webserver tasks
Ansible->>Instance2: Execute database tasks
Ansible->>Instance3: Execute cache tasks
Ansible-->>User: Playbook executed on auto-discovered hosts
Step 1: Install AWS dynamic inventory plugin
pip install ansible[amazon]
# Or: pip install boto3 botocore
Step 2: Configure dynamic inventory - Save as aws_ec2.yml:
# ansible.cfg
[inventory]
enable_plugins = aws_ec2
inventory = ./aws_ec2.yml
# aws_ec2.yml (inventory file)
plugin: aws_ec2
regions:
- us-east-1
- us-west-2
filters:
instance-state-name: running
# tag:Environment: production
keyed_groups:
- key: tags.Role
prefix: role
separator: _
- key: tags.Environment
prefix: env
- key: placement.region
prefix: region
- key: instance_type
prefix: type
compose:
ansible_host: public_ip_address
ansible_user: ubuntu
ansible_port: 22
hostnames:
- tag:Name
- instance-id
Step 3: Use dynamic inventory - Save as deploy_aws.yml:
---
- name: Configure web servers in AWS
hosts: role_webserver
become: yes
gather_facts: yes
vars:
aws_region: us-east-1
tasks:
- name: Install dependencies
apt:
name:
- nginx
- python3-pip
- awscli
state: present
update_cache: yes
- name: Configure AWS CLI for the application
template:
src: templates/aws_credentials.j2
dest: /home/ubuntu/.aws/credentials
owner: ubuntu
mode: '0600'
vars:
aws_access_key: "{{ vault_aws_access_key }}"
aws_secret_key: "{{ vault_aws_secret_key }}"
region: "{{ aws_region }}"
- name: Deploy application code from S3
aws_s3:
bucket: myapp-artifacts
object: releases/{{ app_version }}/app.tar.gz
dest: /tmp/app.tar.gz
mode: get
- name: Extract application
unarchive:
src: /tmp/app.tar.gz
dest: /var/www/html/
remote_src: yes
owner: www-data
group: www-data
- name: Configure Nginx
template:
src: templates/nginx_aws.conf.j2
dest: /etc/nginx/sites-available/default
notify: Reload Nginx
vars:
server_name: "{{ hostvars[inventory_hostname].tags.Name }}.example.com"
- name: Ensure Nginx is running
service:
name: nginx
state: started
enabled: yes
- name: Register instance with ELB
elb_target:
target_group_arn: "{{ alb_target_group_arn }}"
target_id: "{{ instance_id }}"
state: present
port: 80
delegate_to: localhost
run_once: yes
when: "'webserver' in tags.Role"
handlers:
- name: Reload Nginx
service:
name: nginx
state: reloaded
Additional commands for dynamic inventory:
# Test dynamic inventory
ansible-inventory -i aws_ec2.yml --graph
# List all hosts
ansible-inventory -i aws_ec2.yml --list
# Filter by region
ansible -i aws_ec2.yml region_us_east_1 -m ping
# Filter by tag
ansible -i aws_ec2.yml role_database -m setup
# Combine static and dynamic inventory
# hosts.ini:
# [aws:children]
# role_webserver
# role_database
# Run playbook with AWS variables
ansible-playbook -i aws_ec2.yml deploy_aws.yml -e "app_version=2.0.0"
Scenario 12: Complex Ansible Roles with Dependencies¶
Description: Create reusable roles with dependencies, defaults, and structured organization for enterprise use.
sequenceDiagram
participant User as Developer
participant Playbook as Ansible Playbook
participant RoleBase as base Role
participant RoleWeb as webserver Role
participant RoleApp as application Role
participant RoleDB as database Role
participant Server as Target Server
User->>Playbook: Run site.yml with tags
Playbook->>RoleBase: Include role (dependencies)
Note over RoleBase: configure_common_packages<br/>setup_firewall<br/>configure_users
Playbook->>RoleWeb: Include role (depends on base)
Note over RoleWeb: install_nginx<br/>configure_ssl<br/>setup_vhost
Playbook->>RoleApp: Include role (depends on base)
Note over RoleApp: install_dependencies<br/>deploy_code<br/>configure_service
Playbook->>RoleDB: Include role (depends on base)
Note over RoleDB: install_postgresql<br/>configure_database<br/>create_users
loop Role execution
RoleBase->>Server: Execute tasks
RoleWeb->>Server: Execute tasks
RoleApp->>Server: Execute tasks
RoleDB->>Server: Execute tasks
end
Server-->>Playbook: All roles applied
Playbook-->>User: Infrastructure configured
Note over RoleBase: Reusable, modular architecture
Directory Structure:
ansible-project/
├── ansible.cfg
├── inventory/
│ ├── production
│ └── staging
├── group_vars/
│ ├── all.yml
│ ├── webservers.yml
│ └── dbservers.yml
├── playbook.yml
└── roles/
├── base/
│ ├── defaults/main.yml
│ ├── tasks/main.yml
│ ├── handlers/main.yml
│ └── templates/
├── webserver/
│ ├── meta/main.yml
│ ├── tasks/main.yml
│ ├── handlers/main.yml
│ └── templates/
├── application/
│ ├── meta/main.yml
│ ├── tasks/main.yml
│ └── templates/
└── database/
├── meta/main.yml
├── tasks/main.yml
└── templates/
Role: base - roles/base/defaults/main.yml:
---
common_packages:
- vim
- htop
- curl
- wget
- unattended-upgrades
common_users:
- name: ansible
groups: sudo
ssh_key: "{{ lookup('file', '~/.ssh/ansible.pub') }}"
roles/base/tasks/main.yml:
---
- name: Install common packages
package:
name: "{{ common_packages }}"
state: present
update_cache: yes
tags: [packages, common]
- name: Configure automatic security updates
template:
src: 50unattended-upgrades.j2
dest: /etc/apt/apt.conf.d/50unattended-upgrades
owner: root
group: root
mode: '0644'
when: ansible_os_family == "Debian"
tags: security
- name: Create common users
user:
name: "{{ item.name }}"
groups: "{{ item.groups | default(omit) }}"
shell: /bin/bash
create_home: yes
state: present
loop: "{{ common_users }}"
tags: users
- name: Deploy SSH keys for users
authorized_key:
user: "{{ item.name }}"
key: "{{ item.ssh_key }}"
state: present
loop: "{{ common_users }}"
when: item.ssh_key is defined
tags: users
Role: webserver - roles/webserver/meta/main.yml:
---
dependencies:
- role: base
tags: [base, common]
roles/webserver/defaults/main.yml:
---
nginx_version: "1.18.*"
nginx_vhosts:
- name: default
port: 80
root: /var/www/html
index: index.html
server_name: localhost
nginx_ssl: false
roles/webserver/tasks/main.yml:
---
- name: Install Nginx
apt:
name: "nginx={{ nginx_version }}"
state: present
update_cache: yes
notify: Restart Nginx
tags: [nginx, install]
- name: Remove default Nginx site
file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: Reload Nginx
tags: nginx
- name: Deploy virtual host configurations
template:
src: nginx_vhost.j2
dest: "/etc/nginx/sites-available/{{ item.name }}.conf"
owner: root
group: root
mode: '0644'
loop: "{{ nginx_vhosts }}"
notify: Reload Nginx
tags: nginx
- name: Enable virtual hosts
file:
src: "/etc/nginx/sites-available/{{ item.name }}.conf"
dest: "/etc/nginx/sites-enabled/{{ item.name }}.conf"
state: link
loop: "{{ nginx_vhosts }}"
notify: Reload Nginx
tags: nginx
- name: Deploy SSL certificates
copy:
src: "{{ item.ssl_cert }}"
dest: /etc/nginx/ssl/
owner: root
group: root
mode: '0644'
loop: "{{ nginx_vhosts }}"
when: item.ssl_cert is defined
notify: Reload Nginx
tags: [nginx, ssl]
- name: Ensure Nginx is running
service:
name: nginx
state: started
enabled: yes
tags: nginx
- name: Configure firewall for HTTP/HTTPS
ufw:
rule: allow
port: "{{ item.port }}"
proto: tcp
loop: "{{ nginx_vhosts }}"
tags: firewall
roles/webserver/handlers/main.yml:
---
- name: Reload Nginx
service:
name: nginx
state: reloaded
- name: Restart Nginx
service:
name: nginx
state: restarted
Role: application - roles/application/meta/main.yml:
---
dependencies:
- role: base
roles/application/tasks/main.yml:
---
- name: Install application dependencies
apt:
name: "{{ app_dependencies }}"
state: present
tags: [app, install]
- name: Create application user
user:
name: "{{ app_user }}"
system: yes
home: "/opt/{{ app_name }}"
shell: /bin/false
create_home: yes
tags: [app, users]
- name: Deploy application code
git:
repo: "{{ app_repo }}"
dest: "/opt/{{ app_name }}/src"
version: "{{ app_version }}"
notify: Restart Application
tags: [app, deploy]
- name: Install Python requirements
pip:
requirements: "/opt/{{ app_name }}/src/requirements.txt"
virtualenv: "/opt/{{ app_name }}/venv"
tags: [app, deploy]
- name: Deploy systemd service
template:
src: app.service.j2
dest: "/etc/systemd/system/{{ app_name }}.service"
owner: root
group: root
mode: '0644'
notify: Reload Systemd
tags: [app, config]
- name: Ensure application is running
systemd:
name: "{{ app_name }}"
state: started
enabled: yes
daemon_reload: yes
tags: [app, service]
Playbook - Save as site.yml:
---
- name: Configure complete infrastructure
hosts: all
become: yes
roles:
- role: base
tags: [base, common]
- role: webserver
when: "'webservers' in group_names"
tags: [webserver, frontend]
- role: application
when: "'appservers' in group_names"
tags: [application, backend]
- role: database
when: "'dbservers' in group_names"
tags: [database]
handlers:
- name: Reload Systemd
systemd:
daemon_reload: yes
Execute specific roles:
# Only webserver tasks
ansible-playbook -i inventory/production site.yml --tags webserver
# Everything except base
ansible-playbook -i inventory/production site.yml --skip-tags base
# With role variables
ansible-playbook -i inventory/production site.yml -e "nginx_ssl=true"
Scenario 13: Advanced Error Handling with Blocks and Rescue¶
Description: Implement robust error handling, rollback, and cleanup for production deployments.
sequenceDiagram
participant User as Developer
participant Ansible as Ansible Control Node
participant Server as Production Server
User->>Ansible: Run deploy_with_rollback.yml
Ansible->>Server: Start deployment block
alt Task succeeds
Ansible->>Server: Create backup of current version
Server->>Server: tar -czf backup.tar.gz current/
Ansible->>Server: Deploy new version
Server->>Server: Extract new code
Ansible->>Server: Run database migrations
Server->>Server: Run migrate script
Ansible->>Server: Restart application
Server->>Service: Restart service
Server-->>Ansible: Service restarted
Ansible->>Server: Run health checks
Server->>Server: curl http://localhost/health
Server-->>Ansible: HTTP 200 OK
else Task fails (migration fails)
Ansible->>Server: Task failed
Server-->>Ansible: Error
Note over Ansible: Trigger rescue block
Ansible->>Server: Rollback to backup
Server->>Server: Restore backup.tar.gz
Server-->>Ansible: Rolled back
Ansible->>Server: Restart with old version
Server->>Service: systemctl restart app
Service-->>Server: Service restarted
Ansible->>Server: Report failure
Server-->>Ansible: Rollback complete
Note over Ansible: Trigger always block
Ansible->>Server: Cleanup temporary files
Server->>Server: rm -rf /tmp/deploy-*
Server-->>Server: Cleaned up
Ansible->>Server: Send notification alert
Server-->>Ansible: Alert sent
Ansible-->>User: Deployment failed, rollback successful
end
Code - Save as deploy_with_rollback.yml:
---
- name: Deploy with rollback capability
hosts: appservers
become: yes
vars:
app_name: criticalapp
deploy_version: "2.0.0"
backup_dir: "/opt/{{ app_name }}/backup"
deploy_dir: "/opt/{{ app_name }}"
tasks:
- name: Deployment with error handling
block:
- name: Create backup of current version
archive:
path: "{{ deploy_dir }}/current"
dest: "{{ backup_dir }}/{{ app_name }}-backup-{{ ansible_date_time.epoch }}.tar.gz"
format: gz
register: backup_result
- name: Create deployment directory
file:
path: "{{ deploy_dir }}/releases/{{ deploy_version }}"
state: directory
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: '0755'
- name: Deploy new version
unarchive:
src: "/tmp/{{ app_name }}-v{{ deploy_version }}.tar.gz"
dest: "{{ deploy_dir }}/releases/{{ deploy_version }}/"
remote_src: yes
owner: "{{ app_user }}"
group: "{{ app_user }}"
register: deploy_result
- name: Run pre-deployment checks
command: "{{ deploy_dir }}/releases/{{ deploy_version }}/bin/health-check"
register: pre_check
- name: Run database migrations
command: "{{ deploy_dir }}/releases/{{ deploy_version }}/bin/migrate"
register: migration_result
environment:
DATABASE_URL: "{{ vault_database_url }}"
no_log: true
- name: Update symlink to new version
file:
src: "{{ deploy_dir }}/releases/{{ deploy_version }}"
dest: "{{ deploy_dir }}/current"
state: link
force: yes
- name: Restart application service
systemd:
name: "{{ app_name }}"
state: restarted
daemon_reload: yes
- name: Wait for service to be ready
wait_for:
path: "/var/run/{{ app_name }}.pid"
state: present
timeout: 60
- name: Run post-deployment health check
uri:
url: "http://localhost:{{ app_port }}/health"
status_code: 200
register: health_check
retries: 10
delay: 5
until: health_check.status == 200
rescue:
- name: Log deployment failure
debug:
msg: "Deployment failed! Initiating rollback... Error: {{ ansible_failed_result }}"
- name: Restore from backup
unarchive:
src: "{{ backup_result.dest }}"
dest: "{{ deploy_dir }}/"
remote_src: yes
extra_opts: [--strip-components=1]
when: backup_result.dest is defined
- name: Restore previous symlink
file:
src: "{{ backup_dir }}/current-backup"
dest: "{{ deploy_dir }}/current"
state: link
force: yes
- name: Restart application with old version
systemd:
name: "{{ app_name }}"
state: restarted
- name: Verify rollback health
uri:
url: "http://localhost:{{ app_port }}/health"
status_code: 200
register: rollback_health
retries: 5
delay: 5
until: rollback_health.status == 200
- name: Report failure
fail:
msg: "Deployment failed and rollback completed. Previous version is running."
always:
- name: Cleanup temporary files
file:
path: "{{ item }}"
state: absent
loop:
- "/tmp/{{ app_name }}-v{{ deploy_version }}.tar.gz"
- "{{ backup_dir }}/current-backup"
ignore_errors: yes
- name: Send deployment notification
uri:
url: "{{ webhook_url }}"
method: POST
body_format: json
body:
status: "{{ 'success' if ansible_failed_task is undefined else 'failed' }}"
app: "{{ app_name }}"
version: "{{ deploy_version }}"
host: "{{ inventory_hostname }}"
timestamp: "{{ ansible_date_time.iso8601 }}"
delegate_to: localhost
run_once: yes
when: webhook_url is defined
Execute:
ansible-playbook -i hosts.ini deploy_with_rollback.yml
Scenario 14: Ansible Performance Optimization & Best Practices¶
Description: Optimize playbook execution speed with strategies, caching, and efficient patterns.
sequenceDiagram
participant User as Developer
participant Ansible as Ansible Control Node
participant Cache as Redis Cache
participant Server1 as Server 1
participant Server2 as Server 2
participant Server3..N as Servers 3-N
participant Forks as Parallel Workers
User->>Ansible: Run optimized playbook with -f 20
Ansible->>Cache: Check for cached facts
Cache-->>Ansible: Return cached data
Ansible->>Ansible: Set strategy = free
loop Parallel execution (forks=20)
Ansible->>Forks: Distribute tasks to workers
Forks->>Server1: Execute task 1
Server1-->>Forks: Complete
Forks->>Server2: Execute task 1
Server2-->>Forks: Complete
Forks->>Server3..N: Execute task 1
Server3..N-->>Forks: Complete
Forks->>Server1: Execute task 2 (while others still on task 1)
Server1-->>Forks: Complete
end
Ansible->>Ansible: Use async for long tasks
Ansile->>Server1: Async task: yum update (timeout=300)
Server1-->>Ansible: Task started (job ID returned)
Ansible->>Server1: Check async status
Server1-->>Ansible: Still running
Note over Server1: 60 seconds later
Ansible->>Server1: Check async status again
Server1-->>Ansible: Complete
Ansible->>Cache: Update cache with new facts
Cache-->>Ansible: Cache updated
Ansible-->>User: Playbook complete in 2 minutes (vs 15 minutes)
Configuration - ansible.cfg:
[defaults]
# Increase parallel forks (default 5)
forks = 20
# Enable pipelining (reduces SSH connections)
pipelining = True
# Use smart strategy for fact gathering
gathering = smart
# Cache facts for 24 hours
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts_cache
fact_caching_timeout = 86400
# Use free strategy for independent hosts
strategy = free
# Disable host key checking for dynamic environments
host_key_checking = False
# Enable mitogen for extreme speedup (optional plugin)
# strategy_plugins = /path/to/mitogen/ansible_mitogen/plugins/strategy
# strategy = mitogen_linear
[ssh_connection]
# Enable SSH multiplexing
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
# Reduce connection timeout
timeout = 30
Optimized playbook - Save as optimized_deploy.yml:
---
- name: Optimized deployment with performance techniques
hosts: all
become: yes
gather_facts: yes
any_errors_fatal: false
max_fail_percentage: 10 # Allow 10% failures
vars:
app_version: 3.0.0
# Use facts from cache
use_cache: yes
tasks:
- name: Gather facts for specific hosts only
setup:
filter:
- ansible_distribution
- ansible_os_family
- ansible_memtotal_mb
when: not ansible_facts.get('os_family')
- name: Install packages asynchronously
yum:
name:
- nginx
- postgresql
- redis
state: present
async: 300 # Timeout in seconds
poll: 0 # Don't wait, fire and continue
register: package_install
- name: Check package install status
async_status:
jid: "{{ package_install.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 30
delay: 10
- name: Deploy configuration in batches
template:
src: templates/app.conf.j2
dest: /etc/app.conf
# Process hosts in batches of 5
serial: 5
- name: Use with_items instead of loop for performance
copy:
src: "{{ item }}"
dest: /var/www/html/
with_fileglob:
- files/static/*
# Or use native loop for better performance
# loop: "{{ query('fileglob', 'files/static/*') }}"
- name: Conditional execution with fact caching
service:
name: nginx
state: restarted
when: not ansible_check_mode
# Skip in check mode
- name: Run tasks on first host only (delegate)
command: /usr/bin/update-dns.sh
delegate_to: "{{ groups['dns'][0] }}"
run_once: yes
- name: Use included tasks for reuse
include_tasks: tasks/configure_monitoring.yml
when: enable_monitoring | default(false)
- name: Use import_tasks for static inclusion
import_tasks: tasks/configure_logging.yml
- name: Use blocks for error handling
block:
- name: Critical deployment step
command: /opt/app/deploy.sh --version={{ app_version }}
- name: Verify deployment
uri:
url: http://localhost/health
status_code: 200
rescue:
- name: Rollback on failure
command: /opt/app/rollback.sh --version={{ app_version }}
# Rescue runs only on failed hosts
- name: Use strategy: free for independent tasks
# This playbook uses strategy: free in ansible.cfg
# Each host proceeds at its own pace
- name: Use wait_for with low timeout
wait_for:
path: /var/run/app.pid
timeout: 30
# Don't wait too long
- name: Use systemd module instead of service
systemd:
name: app
state: restarted
daemon_reload: yes
# More efficient than command or shell
- name: Use template instead of lineinfile
template:
src: templates/ssh_config.j2
dest: /etc/ssh/ssh_config
# Single template is faster than multiple lineinfile
- name: Cache expensive lookups
set_fact:
app_config: "{{ lookup('url', 'https://config.example.com/app.json', split_lines=False) }}"
cacheable: yes
# Facts are cached across playbook runs
- name: Use compression for large file transfers
synchronize:
src: files/large-data/
dest: /opt/app/data/
compress: yes
archive: yes
delegate_to: localhost
- name: Use parallel tasks with async
async: 300
command: "long-running-task --host={{ inventory_hostname }}"
poll: 5
# Check status every 5 seconds
- name: Disable fact gathering for specific play
setup:
gather_subset:
- '!all'
- '!min'
- 'network'
# Only gather network facts
- name: Use run_once for efficiency
command: /usr/bin/create-release-tag.sh
register: release_tag
run_once: yes
# Only runs on first host, result available to all
- name: Use local_action instead of delegate_to
local_action:
module: copy
content: "{{ hostvars | to_nice_json }}"
dest: /tmp/inventory-report.json
# Equivalent to delegate_to: localhost
- name: Optimize loops with include_tasks
include_tasks: tasks/configure_user.yml
loop: "{{ users }}"
loop_control:
loop_var: user
label: "{{ user.name }}"
pause: 2 # Give API breathing room
# Better performance than inline loop
- name: Use throttle to limit concurrent tasks
command: /usr/bin/api-call.sh
throttle: 1
# Only one host executes this at a time
- name: Use meta tasks for control flow
- meta: flush_handlers
# Flush handlers immediately
- name: Refresh inventory mid-playbook
meta: refresh_inventory
# Useful after provisioning new instances
- name: End execution for failed hosts
meta: end_host
when: ansible_failed
- name: Clear gathered facts to free memory
- meta: clear_facts
when: free_memory_mb < 512
post_tasks:
- name: Refresh fact cache
setup:
filter: ansible_local
when: ansible_local is defined
- name: Save execution time metrics
local_action:
module: copy
content: |
Playbook: {{ playbook_name }}
Duration: {{ ansible_play_duration }} seconds
Hosts: {{ ansible_play_hosts_all | length }}
Failures: {{ ansible_play_hosts_all | difference(ansible_play_hosts) | length }}
dest: "/tmp/ansible-metrics-{{ ansible_date_time.epoch }}.json"
run_once: yes
Performance tips in code comments:
# Run with timing stats
ansible-playbook -i hosts.ini optimized_deploy.yml --verbose --forks 50
# Profile specific tasks
ANSIBLE_CALLBACK_WHITELIST=profile_tasks,timer ansible-playbook playbook.yml
# Check mode with diff
ansible-playbook -i hosts.ini optimized_deploy.yml --check --diff
# Limit to specific hosts
ansible-playbook -i hosts.ini optimized_deploy.yml --limit webservers[0:9]
# Skip tags for faster execution
ansible-playbook -i hosts.ini optimized_deploy.yml --skip-tags monitoring
# Use ARA for detailed reporting
# pip install ara[server]
# ansible-playbook -i hosts.ini optimized_deploy.yml -c ara.plugins.callback.default
Scenario 15: CI/CD Integration with Ansible & Jenkins¶
Description: Integrate Ansible into Jenkins pipeline for continuous deployment with automated testing.
sequenceDiagram
participant Dev as Developer
participant Git as Git Repository
participant Jenkins as Jenkins Server
participant Ansible as Ansible Control Node
participant AWX as AWX/Tower
participant Test as Test Environment
participant Prod as Production Environment
Dev->>Git: git push -m "Feature: deploy app v3.0"
Git->>Jenkins: Webhook triggers pipeline
Jenkins->>Jenkins: Stage: Checkout
Jenkins->>Git: Clone repository
Jenkins->>Jenkins: Stage: Lint
Jenkins->>Jenkins: ansible-lint playbooks/
Jenkins->>Jenkins: Stage: Syntax Check
Jenkins->>Ansible: ansible-playbook --syntax-check
Jenkins->>Jenkins: Stage: Unit Test
Jenkins->>Ansible: Run test playbook on mock hosts
Jenkins->>Jenkins: Stage: Deploy to Test
Jenkins->>AWX: Launch job template "Deploy to Test"
AWX->>Ansible: Execute deployment playbook
Ansible->>Test: Deploy application v3.0
Test-->>Ansible: Deployment complete
Jenkins->>Test: Run integration tests
Test-->>Jenkins: Tests passed
alt Manual approval
Jenkins->>Dev: Send approval notification
Dev->>Jenkins: Approve deployment
end
Jenkins->>Jenkins: Stage: Deploy to Production
Jenkins->>AWX: Launch job template "Deploy to Prod"
AWX->>Ansible: Execute production playbook
Ansible->>Prod: Deploy application v3.0
Prod-->>Ansible: Deployment complete
Jenkins->>Prod: Run smoke tests
Prod-->>Jenkins: Tests passed
Jenkins->>Dev: Send success notification
Note over Jenkins: Full CI/CD pipeline
Jenkinsfile - Save as Jenkinsfile:
pipeline {
agent any
environment {
ANSIBLE_INVENTORY = 'inventory/production'
ANSIBLE_VAULT_PASSWORD_FILE = credentials('ansible-vault-password')
AWX_URL = 'https://awx.example.com'
AWX_CREDENTIALS = credentials('awx-token')
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Ansible Lint') {
steps {
sh '''
ansible-lint playbooks/ --exclude .roles/
'''
}
}
stage('Syntax Check') {
steps {
sh '''
ansible-playbook playbooks/site.yml --syntax-check -i ${ANSIBLE_INVENTORY}
'''
}
}
stage('Dry Run') {
steps {
sh '''
ansible-playbook playbooks/site.yml -i ${ANSIBLE_INVENTORY} --check --diff
'''
}
}
stage('Deploy to Test') {
steps {
script {
// Trigger AWX job template
sh '''
curl -X POST ${AWX_URL}/api/v2/job_templates/10/launch/ \\
-H "Authorization: Bearer ${AWX_CREDENTIALS}" \\
-d '{"extra_vars": {"env": "test", "app_version": "${BUILD_NUMBER}"}}'
'''
}
}
}
stage('Integration Tests') {
steps {
sh '''
# Wait for deployment
sleep 30
# Run tests
pytest tests/integration/ -v
'''
}
}
stage('Manual Approval') {
when {
branch 'main'
}
steps {
timeout(time: 1, unit: 'HOURS') {
input message: 'Deploy to production?', ok: 'Deploy'
}
}
}
stage('Deploy to Production') {
when {
branch 'main'
}
steps {
script {
sh '''
curl -X POST ${AWX_URL}/api/v2/job_templates/20/launch/ \\
-H "Authorization: Bearer ${AWX_CREDENTIALS}" \\
-d '{"extra_vars": {"env": "prod", "app_version": "${BUILD_NUMBER}"}}'
'''
// Wait for completion
sh '''
./scripts/wait-for-deployment.sh prod ${BUILD_NUMBER}
'''
}
}
post {
success {
sh '''
ansible-playbook playbooks/smoke-tests.yml -i inventory/production
'''
}
}
}
stage('Smoke Tests') {
steps {
sh '''
ansible-playbook playbooks/smoke-tests.yml -i inventory/production
'''
}
}
stage('Cleanup') {
steps {
sh '''
ansible-playbook playbooks/cleanup.yml -i ${ANSIBLE_INVENTORY}
'''
}
}
}
post {
always {
archiveArtifacts artifacts: 'logs/*.log', fingerprint: true
junit 'test-results/*.xml'
emailext (
to: "${env.CHANGE_AUTHOR_EMAIL ?: 'dev-team@example.com'}",
subject: "Ansible Deployment ${env.JOB_NAME} - ${currentBuild.currentResult}",
body: """
<h2>Deployment ${env.BUILD_NUMBER}</h2>
<p>Result: ${currentBuild.currentResult}</p>
<p>Environment: Production</p>
<p>Version: ${BUILD_NUMBER}</p>
<p>Playbook: ${BUILD_URL}console</p>
""",
mimeType: 'text/html'
)
}
success {
sh '''
echo "Deployment successful!"
'''
}
failure {
sh '''
echo "Deployment failed. Check logs."
'''
}
}
}
Ansible playbook for smoke tests - Save as playbooks/smoke-tests.yml:
---
- name: Smoke tests after deployment
hosts: appservers
gather_facts: no
tasks:
- name: Wait for application to be ready
wait_for:
port: "{{ app_port }}"
host: localhost
timeout: 120
- name: Check application health endpoint
uri:
url: "http://localhost:{{ app_port }}/health"
status_code: 200
return_content: yes
register: health_check
- name: Verify response contains expected data
assert:
that:
- health_check.json.status == "healthy"
- health_check.json.version == "{{ app_version }}"
- name: Test database connectivity
command: "{{ app_dir }}/bin/db-check"
register: db_check
changed_when: false
- name: Check SSL certificate expiration
shell: |
echo | openssl s_client -connect localhost:443 2>/dev/null | \
openssl x509 -noout -dates | grep notAfter
register: ssl_expiry
when: enable_ssl | default(false)
- name: Report smoke test results
debug:
msg: |
Smoke tests completed for {{ inventory_hostname }}
Health: {{ health_check.json.status }}
Version: {{ health_check.json.version }}
Database: {{ db_check.stdout }}
AWX Job Template Configuration:
# Export job template configuration
---
- name: Configure AWX job templates
hosts: localhost
connection: local
gather_facts: no
tasks:
- name: Create test deployment job template
awx.awx.job_template:
name: "Deploy to Test"
job_type: run
inventory: "Test Inventory"
project: "Ansible Playbooks"
playbook: "playbooks/deploy-app.yml"
credential: "AWS SSH Key"
extra_vars:
env: test
app_version: "{{ app_version }}"
ask_variables_on_launch: yes
ask_inventory_on_launch: yes
state: present
tower_host: "{{ tower_host }}"
tower_oauthtoken: "{{ tower_token }}"
validate_certs: no
- name: Create production deployment job template
awx.awx.job_template:
name: "Deploy to Production"
job_type: run
inventory: "Production Inventory"
project: "Ansible Playbooks"
playbook: "playbooks/deploy-app.yml"
credential: "Production SSH Key"
extra_vars:
env: prod
app_version: "{{ app_version }}"
ask_variables_on_launch: yes
ask_inventory_on_launch: no
ask_credential_on_launch: no
state: present
tower_host: "{{ tower_host }}"
tower_oauthtoken: "{{ tower_token }}"
validate_certs: no
Scenario 16: Ansible Troubleshooting and Diagnostics¶
Description: Debug complex issues with verbose output, debug modules, and common problem resolution techniques.
sequenceDiagram
participant Admin as Administrator
participant Support as Support Engineer
participant Ansible as Ansible Control Node
participant Host as Remote Host
participant Logs as Ansible Logs
participant Debug as Debug Module
participant ARA as ARA Reporting
participant Navigator as Ansible Navigator
Admin->>Ansible: Playbook failing on task "Deploy app"
Admin->>Ansible: Run with -vvvv verbose mode
Ansible->>Logs: Write detailed execution logs
Logs-->>Admin: Show task execution details (SSH commands, return codes)
Admin->>Ansible: Add debug tasks to playbook
Ansible->>Debug: Print variable values
Debug-->>Admin: Display host vars, facts, registered variables
Admin->>Ansible: Run with --check --diff mode
Ansible->>Host: Show what would change
Host-->>Admin: Diff output for files & commands
Admin->>Ansible: Use --start-at-task="Install package"
Ansible->>Host: Skip to specific task
Host-->>Admin: Execute from that point onward
Support->>Navigator: Launch interactive mode
Navigator->>Support: Provide TUI interface
Support->>Navigator: Execute ad-hoc command
Navigator->>Host: Run command module
Host-->>Support: Return output in TUI
Support->>ARA: Review historical runs
ARA-->>Support: Show playbook results, task duration, host status
Support->>Host: Check file permissions
Host->>Support: ls -la /opt/app/config
Support->>Logs: Analyze with grep
Logs-->>Support: Identify error patterns
Support->>Ansible: Apply fix
Ansible-->>Admin: Playbook now successful
Troubleshooting commands and playbook:
# Run with maximum verbosity (-vvvv)
ansible-playbook -i hosts.ini deploy.yml -vvvv
# Limit to specific host
ansible-playbook -i hosts.ini deploy.yml --limit webserver-01
# Start at specific task
ansible-playbook -i hosts.ini deploy.yml --start-at-task="Install Nginx"
# Step through tasks interactively
ansible-playbook -i hosts.ini deploy.yml --step
# Check mode with diff
ansible-playbook -i hosts.ini deploy.yml --check --diff
# List all tasks without executing
ansible-playbook -i hosts.ini deploy.yml --list-tasks
# List hosts that would be targeted
ansible-playbook -i hosts.ini deploy.yml --list-hosts
Debug playbook - Save as debug_deploy.yml:
---
- name: Deploy with extensive debugging
hosts: appservers
become: yes
vars:
app_name: myapp
debug_mode: true
tasks:
# Debug connection and facts
- name: Display connection variables
debug:
msg: |
Host: {{ inventory_hostname }}
Ansible user: {{ ansible_user }}
SSH user: {{ ansible_ssh_user | default('default') }}
Connection type: {{ ansible_connection }}
Python interpreter: {{ ansible_python_interpreter }}
Distribution: {{ ansible_distribution }}
Version: {{ ansible_distribution_version }}
Architecture: {{ ansible_architecture }}
Memory: {{ ansible_memtotal_mb }} MB
CPUs: {{ ansible_processor_vcpus }}
# Debug variable scopes
- name: Show variable precedence
debug:
var: app_name
when: debug_mode
# Check connectivity to external services
- name: Test connectivity to database
wait_for:
host: "{{ db_host }}"
port: "{{ db_port }}"
timeout: 5
register: db_connectivity
ignore_errors: yes
- name: Display connectivity result
debug:
msg: "Database connectivity: {{ 'OK' if db_connectivity is succeeded else 'FAILED' }}"
# Check file permissions before deployment
- name: Check permissions on target directory
stat:
path: /opt/myapp
register: app_dir_stat
- name: Display directory permissions
debug:
msg: |
Directory exists: {{ app_dir_stat.stat.exists }}
Owner: {{ app_dir_stat.stat.pw_name }}
Group: {{ app_dir_stat.stat.gr_name }}
Mode: {{ app_dir_stat.stat.mode }}
when: app_dir_stat.stat.exists
# Test privilege escalation
- name: Verify sudo access
command: id
become: yes
register: sudo_test
- name: Display effective user after sudo
debug:
msg: "Effective user: {{ sudo_test.stdout }}"
# Check disk space before deployment
- name: Check disk space on target partition
command: df -h /opt
register: disk_space
- name: Display disk usage
debug:
msg: "{{ disk_space.stdout }}"
# Debug Jinja2 templating
- name: Test template rendering
template:
src: templates/app.conf.j2
dest: /tmp/debug-app.conf
check_mode: yes
diff: yes
register: template_debug
- name: Display template diff
debug:
var: template_debug
when: template_debug.changed
# Register and debug command output
- name: Check application status
command: systemctl status myapp
register: service_status
ignore_errors: yes
- name: Display service status
debug:
var: service_status.stderr
when: service_status is failed
# Use assert to validate assumptions
- name: Validate prerequisites
assert:
that:
- ansible_distribution == "Ubuntu"
- ansible_distribution_version is version('20.04', '>=')
- app_dir_stat.stat.exists
- app_dir_stat.stat.isdir
fail_msg: "Prerequisites not met - check distribution and directory"
success_msg: "All prerequisites validated"
# Debug handlers
- name: Trigger handler debug
command: echo "Triggering restart"
changed_when: true
notify: Debug Handler
# Rescue block for detailed error analysis
- name: Critical deployment section
block:
- name: Deploy application code
copy:
src: "{{ app_archive }}"
dest: /tmp/
register: copy_result
- name: Extract application
unarchive:
src: "{{ copy_result.dest }}"
dest: /opt/myapp/
remote_src: yes
register: extract_result
rescue:
- name: Debug copy failure
debug:
var: copy_result
when: copy_result is failed
- name: Debug extract failure
debug:
var: extract_result
when: extract_result is failed
- name: Check if archive is valid
command: "tar -tzf {{ copy_result.dest }}"
register: archive_test
ignore_errors: yes
when: copy_result.dest is defined
- name: Display archive test result
debug:
var: archive_test
when: archive_test is defined
- name: Fail with detailed message
fail:
msg: |
Deployment failed. Investigate:
Copy status: {{ copy_result.status }}
Extract status: {{ extract_result.status if extract_result is defined else 'N/A' }}
Archive validity: {{ 'Invalid' if archive_test is failed else 'Valid' }}
# Variable type debugging
- name: Debug variable types
debug:
msg: |
app_version type: {{ app_version | type_debug }}
app_dependencies type: {{ app_dependencies | type_debug }}
users type: {{ users | type_debug }}
debug_mode type: {{ debug_mode | type_debug }}
# Test lookups
- name: Debug file lookup
debug:
msg: "File content: {{ lookup('file', 'files/secret.txt') }}"
failed_when: false
ignore_errors: yes
# Debug inventory
- name: Show inventory groups
debug:
msg: |
Current host groups: {{ group_names }}
All hosts: {{ groups['all'] }}
Webservers: {{ groups.get('webservers', []) }}
# Test connectivity to other hosts
- name: Test inter-host connectivity
command: ping -c 1 {{ item }}
loop: "{{ groups['dbservers'] }}"
register: ping_results
ignore_errors: yes
when: inventory_hostname in groups['appservers']
- name: Display connectivity results
debug:
msg: "{{ item.item }} - {{ 'OK' if item.rc == 0 else 'FAILED' }}"
loop: "{{ ping_results.results }}"
when: ping_results is defined
# Use ARA callback plugin (if installed)
- name: Record debug information for ARA
ara_record:
playbook: "{{ ara_playbook.id }}"
key: "debug_info_{{ inventory_hostname }}"
value: "{{ ansible_facts | to_nice_json }}"
type: json
delegate_to: localhost
run_once: yes
when: ara_playbook is defined
handlers:
- name: Debug Handler
debug:
msg: "Handler triggered on {{ inventory_hostname }}"
Ad-hoc troubleshooting commands:
# Test module directly
ansible appservers -i hosts.ini -m command -a "df -h" -vvv
# Test privilege escalation
ansible appservers -i hosts.ini -m command -a "whoami" -b -K
# Test connectivity and Python
ansible appservers -i hosts.ini -m raw -a "python --version"
# Test file transfer
ansible appservers -i hosts.ini -m copy -a "src=/tmp/test.txt dest=/tmp/"
# Check file permissions
ansible appservers -i hosts.ini -m stat -a "path=/etc/myapp/config.yml"
# Test template rendering
ansible appservers -i hosts.ini -m template -a "src=templates/app.conf.j2 dest=/tmp/app.conf" --check --diff
# Use ansible-navigator (modern alternative)
ansible-navigator run deploy.yml -i hosts.ini --mode interactive
ARA setup for detailed reporting:
# Install ARA
pip install ara[server]
# Configure ansible.cfg
[defaults]
callback_plugins = /path/to/ara/plugins/callbacks
callback_whitelist = ara_default
# Start ARA server
ara-manage runserver
# Access ARA at http://localhost:8000
Scenario 17: Multi-Cloud Deployment Strategies¶
Description: Deploy to AWS, Azure, and GCP from a single Ansible playbook with cloud-specific modules and dynamic inventory.
sequenceDiagram
participant Playbook as Ansible Playbook
participant AWS as AWS (Primary)
participant Azure as Azure (Secondary)
participant GCP as GCP (DR)
participant Registry as Container Registry
participant DNS as Global DNS
Playbook->>AWS: Build & push ECR image
Playbook->>Azure: Build & push ACR image
Playbook->>GCP: Build & push GCR image
Playbook->>AWS: Deploy to EKS (us-east-1)
Playbook->>Azure: Deploy to AKS (East US)
Playbook->>GCP: Deploy to GKE (us-central1)
AWS->>AWS: Run smoke tests (ELB health check)
Azure->>Azure: Run smoke tests (App Gateway)
GCP->>GCP: Run smoke tests (Load Balancer)
alt AWS healthy
DNS->>AWS: Route 100% traffic
else AWS degraded
DNS->>Azure: Failover traffic (50%)
DNS->>GCP: Failover traffic (50%)
end
Playbook->>DNS: Update health checks
Note over Playbook: Single source of truth for multi-cloud
Multi-cloud inventory - Save as clouds.yml:
---
# group_vars/all/clouds.yml
cloud_providers:
aws:
enabled: true
region: us-east-1
credentials:
access_key: "{{ vault_aws_access_key }}"
secret_key: "{{ vault_aws_secret_key }}"
registry: "123456789.dkr.ecr.us-east-1.amazonaws.com"
cluster: "prod-eks"
resource_group: "production"
azure:
enabled: true
region: eastus
credentials:
client_id: "{{ vault_azure_client_id }}"
secret: "{{ vault_azure_secret }}"
tenant: "{{ vault_azure_tenant }}"
registry: "myregistry.azurecr.io"
cluster: "prod-aks"
resource_group: "prod-rg"
gcp:
enabled: true
region: us-central1
credentials: "{{ vault_gcp_service_account }}"
registry: "gcr.io/my-project"
cluster: "prod-gke"
resource_group: "production"
Multi-cloud deployment playbook - Save as deploy_multi_cloud.yml:
---
- name: Multi-cloud container deployment
hosts: localhost
connection: local
gather_facts: no
vars_files:
- group_vars/all/clouds.yml
- group_vars/all/vault.yml
vars:
app_name: myapp
app_version: "{{ version | default('latest') }}"
image_tag: "{{ app_name }}:{{ app_version }}"
tasks:
- name: Build container image
command: docker build -t {{ image_tag }} .
delegate_to: localhost
register: docker_build
- name: Deploy to AWS
include_tasks: tasks/deploy_aws.yml
when: cloud_providers.aws.enabled
- name: Deploy to Azure
include_tasks: tasks/deploy_azure.yml
when: cloud_providers.azure.enabled
- name: Deploy to GCP
include_tasks: tasks/deploy_gcp.yml
when: cloud_providers.gcp.enabled
- name: Update global load balancer
include_tasks: tasks/update_dns.yml
when: update_dns | default(false)
# AWS deployment tasks - tasks/deploy_aws.yml
---
- name: Login to AWS ECR
command: |
aws ecr get-login-password --region {{ cloud_providers.aws.region }} | \
docker login --username AWS --password-stdin {{ cloud_providers.aws.registry }}
environment:
AWS_ACCESS_KEY_ID: "{{ cloud_providers.aws.credentials.access_key }}"
AWS_SECRET_ACCESS_KEY: "{{ cloud_providers.aws.credentials.secret_key }}"
no_log: true
- name: Tag image for AWS
command: docker tag {{ image_tag }} {{ cloud_providers.aws.registry }}/{{ image_tag }}
- name: Push to ECR
command: docker push {{ cloud_providers.aws.registry }}/{{ image_tag }}
- name: Update kubeconfig for AWS
command: |
aws eks update-kubeconfig \
--region {{ cloud_providers.aws.region }} \
--name {{ cloud_providers.aws.cluster }}
- name: Deploy to EKS
kubernetes.core.k8s:
state: present
kubeconfig: "~/.kube/config"
definition:
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ app_name }}"
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: "{{ app_name }}"
template:
metadata:
labels:
app: "{{ app_name }}"
spec:
containers:
- name: "{{ app_name }}"
image: "{{ cloud_providers.aws.registry }}/{{ image_tag }}"
ports:
- containerPort: 8080
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
- name: Wait for rollout
kubernetes.core.k8s_rollout:
kubeconfig: "~/.kube/config"
namespace: production
resource: deployment
name: "{{ app_name }}"
wait: yes
wait_timeout: 300
- name: Run smoke tests
uri:
url: "{{ aws_alb_url }}/health"
status_code: 200
register: aws_health
# Azure deployment tasks - tasks/deploy_azure.yml
---
- name: Login to Azure
command: |
az login --service-principal \
-u {{ cloud_providers.azure.credentials.client_id }} \
-p {{ cloud_providers.azure.credentials.secret }} \
--tenant {{ cloud_providers.azure.credentials.tenant }}
no_log: true
- name: Get AKS credentials
command: |
az aks get-credentials \
--resource-group {{ cloud_providers.azure.resource_group }} \
--name {{ cloud_providers.azure.cluster }}
- name: Login to ACR
command: az acr login --name {{ cloud_providers.azure.registry }}
- name: Tag image for Azure
command: docker tag {{ image_tag }} {{ cloud_providers.azure.registry }}/{{ image_tag }}
- name: Push to ACR
command: docker push {{ cloud_providers.azure.registry }}/{{ image_tag }}
- name: Deploy to AKS
kubernetes.core.k8s:
state: present
definition:
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ app_name }}"
namespace: production
labels:
cloud: azure
spec:
replicas: 3
template:
spec:
nodeSelector:
kubernetes.io/os: linux
containers:
- name: "{{ app_name }}"
image: "{{ cloud_providers.azure.registry }}/{{ image_tag }}"
resources:
requests:
cpu: 100m
memory: 128Mi
# GCP deployment tasks - tasks/deploy_gcp.yml
---
- name: Login to GCP
command: |
echo {{ cloud_providers.gcp.credentials }} > /tmp/gcp-key.json
gcloud auth activate-service-account --key-file /tmp/gcp-key.json
gcloud config set project my-project
- name: Configure Docker for GCR
command: gcloud auth configure-docker
- name: Tag image for GCP
command: docker tag {{ image_tag }} {{ cloud_providers.gcp.registry }}/{{ image_tag }}
- name: Push to GCR
command: docker push {{ cloud_providers.gcp.registry }}/{{ image_tag }}
- name: Get GKE credentials
command: |
gcloud container clusters get-credentials {{ cloud_providers.gcp.cluster }} \
--region {{ cloud_providers.gcp.region }}
- name: Deploy to GKE
kubernetes.core.k8s:
state: present
definition:
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ app_name }}"
namespace: production
annotations:
cloud.google.com/load-balancer-type: "External"
spec:
replicas: 3
template:
spec:
containers:
- name: "{{ app_name }}"
image: "{{ cloud_providers.gcp.registry }}/{{ image_tag }}"
ports:
- containerPort: 8080
# DNS update tasks - tasks/update_dns.yml
---
- name: Update Route53 health check
route53_health_check:
name: "{{ app_name }}-aws-health"
type: HTTP
resource_path: /health
fqdn: "{{ app_name }}.aws.example.com"
port: 80
state: present
delegate_to: localhost
- name: Update Traffic Policy
community.aws.route53:
state: present
zone: example.com
record: "{{ app_name }}"
type: A
ttl: 60
value:
- "{{ aws_alb_ip }}"
- "{{ azure_lb_ip }}"
- "{{ gcp_lb_ip }}"
weight:
- 100 # AWS primary
- 50 # Azure secondary
- 50 # GCP DR
health_check:
- "{{ app_name }}-aws-health"
- "{{ app_name }}-azure-health"
- "{{ app_name }}-gcp-health"
Multi-cloud dynamic inventory - inventory/multi_cloud.yml:
# plugin: amazon.aws.aws_ec2
# regions:
# - us-east-1
# - us-west-2
# filters:
# tag:Environment: production
# keyed_groups:
# - prefix: tag
# key: tags
#
# plugin: azure.azcollection.azure_rm
# include_virtual_machines: true
# groups:
# azure_prod: tags.environment == 'production'
#
# plugin: google.cloud.gcp_compute
# zones:
# - us-central1-a
# - us-west1-b
# keyed_groups:
# - prefix: gcp
# key: labels
Execute multi-cloud deployment:
ansible-playbook -i inventory/multi_cloud.yml deploy_multi_cloud.yml \
-e "version=3.0.0" \
-e "update_dns=true" \
--vault-password-file .vault_pass
Scenario 18: Custom Ansible Modules and Plugins¶
Description: Develop custom modules, filters, and callbacks to extend Ansible functionality for specialized use cases.
sequenceDiagram
participant Developer as Ansible Developer
participant Module as Custom Module
participant Filter as Custom Filter
participant Callback as Callback Plugin
participant Ansible as Ansible Core
participant Host as Remote Host
participant User as Ansible User
Developer->>Module: Write custom_module.py
Module->>Module: Implement AnsibleModule class
Developer->>Filter: Write custom_filter.py
Filter->>Filter: Define custom Jinja2 filters
Developer->>Callback: Write custom_callback.py
Callback->>Callback: Implement CallbackBase
Developer->>Ansible: Place in library/, filter_plugins/, callback_plugins/
Ansible->>Ansible: Load plugins
User->>Ansible: Execute playbook with custom module
Ansible->>Module: Initialize custom module
Module->>Host: Execute custom logic
Host-->>Module: Return data
Module->>Filter: Apply custom filter
Filter-->>Module: Transform data
Module->>Ansible: Return structured data
Ansible->>Callback: Notify task completion
Callback-->>User: Custom notification
Note over Module: Tailored enterprise functionality
Custom module - library/custom_facts.py:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from ansible.module_utils.basic import AnsibleModule
import json
import subprocess
DOCUMENTATION = r'''
---
module: custom_facts
short_description: Gather custom system facts
description:
- Collects custom facts about system configuration
- Returns structured data for Ansible facts
version_added: "1.0.0"
author:
- Your Name (@yourusername)
options:
gather:
description:
- List of fact categories to gather
type: list
elements: str
choices: ['network', 'storage', 'security']
default: ['network']
'''
EXAMPLES = r'''
- name: Gather network facts
custom_facts:
gather:
- network
- name: Gather all facts
custom_facts:
gather:
- network
- storage
- security
'''
RETURN = r'''
ansible_facts:
description: Facts to add to ansible_facts
returned: always
type: dict
contains:
custom_network:
description: Network configuration facts
type: dict
custom_storage:
description: Storage configuration facts
type: dict
'''
def gather_network_facts():
"""Gather network-related facts"""
facts = {}
# Get interface information
try:
result = subprocess.run(['ip', '-j', 'addr'],
capture_output=True, text=True, check=True)
facts['interfaces'] = json.loads(result.stdout)
except (subprocess.CalledProcessError, FileNotFoundError):
facts['interfaces'] = []
# Get routing table
try:
result = subprocess.run(['ip', '-j', 'route'],
capture_output=True, text=True, check=True)
facts['routes'] = json.loads(result.stdout)
except (subprocess.CalledProcessError, FileNotFoundError):
facts['routes'] = []
# Get listening ports
try:
result = subprocess.run(['ss', '-tulnp', '-j'],
capture_output=True, text=True, check=True)
facts['listening_ports'] = json.loads(result.stdout)
except (subprocess.CalledProcessError, FileNotFoundError):
facts['listening_ports'] = []
return facts
def gather_storage_facts():
"""Gather storage-related facts"""
facts = {}
# Get disk usage
try:
result = subprocess.run(['df', '-h', '-T'],
capture_output=True, text=True, check=True)
lines = result.stdout.strip().split('\n')
headers = lines[0].split()
facts['disk_usage'] = []
for line in lines[1:]:
values = line.split()
if len(values) >= 7:
facts['disk_usage'].append({
'filesystem': values[0],
'type': values[1],
'size': values[2],
'used': values[3],
'available': values[4],
'use_percent': values[5],
'mounted_on': values[6]
})
except (subprocess.CalledProcessError, FileNotFoundError):
facts['disk_usage'] = []
return facts
def gather_security_facts():
"""Gather security-related facts"""
facts = {}
# Get firewall status
try:
result = subprocess.run(['ufw', 'status', 'verbose'],
capture_output=True, text=True, check=True)
facts['firewall'] = result.stdout
except (subprocess.CalledProcessError, FileNotFoundError):
facts['firewall'] = 'Not available'
# Get failed SSH login attempts
try:
result = subprocess.run(['journalctl', '-u', 'sshd', '--since', '24h',
'grep', 'Failed', 'wc', '-l'],
capture_output=True, text=True, check=True)
facts['failed_ssh_attempts'] = int(result.stdout.strip())
except (subprocess.CalledProcessError, FileNotFoundError):
facts['failed_ssh_attempts'] = 0
return facts
def main():
"""Main module execution"""
module = AnsibleModule(
argument_spec=dict(
gather=dict(type='list',
elements='str',
choices=['network', 'storage', 'security'],
default=['network'])
),
supports_check_mode=True
)
gather_categories = module.params['gather']
if module.check_mode:
module.exit_json(changed=False,
msg="Would gather facts: " + ", ".join(gather_categories))
result = {
'changed': False,
'ansible_facts': {
'custom': {}
}
}
# Gather requested facts
if 'network' in gather_categories:
result['ansible_facts']['custom']['network'] = gather_network_facts()
if 'storage' in gather_categories:
result['ansible_facts']['custom']['storage'] = gather_storage_facts()
if 'security' in gather_categories:
result['ansible_facts']['custom']['security'] = gather_security_facts()
module.exit_json(**result)
if __name__ == '__main__':
main()
Custom filter plugin - filter_plugins/custom_filters.py:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
class FilterModule(object):
def filters(self):
return {
'custom_filter': self.custom_filter,
'to_k8s_name': self.to_k8s_name,
'parse_java_version': self.parse_java_version,
'calculate_resources': self.calculate_resources
}
def custom_filter(self, value, param):
"""Example custom filter"""
return f"Filtered {value} with {param}"
def to_k8s_name(self, value):
"""Convert string to Kubernetes compliant name"""
import re
# Convert to lowercase
value = value.lower()
# Replace invalid characters with hyphen
value = re.sub(r'[^a-z0-9-]', '-', value)
# Remove leading/trailing hyphens
value = value.strip('-')
# Ensure max length of 63 characters
return value[:63]
def parse_java_version(self, version_string):
"""Parse Java version string into dict"""
import re
pattern = r'version "(\d+)\.(\d+)\.(\d+)'
match = re.search(pattern, version_string)
if match:
return {
'major': int(match.group(1)),
'minor': int(match.group(2)),
'patch': int(match.group(3))
}
return None
def calculate_resources(self, replicas, cpu_per_pod, memory_per_pod):
"""Calculate total resources needed"""
return {
'total_cpu_cores': replicas * cpu_per_pod,
'total_memory_mb': replicas * memory_per_pod
}
Custom callback plugin - callback_plugins/custom_callback.py:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from ansible.plugins.callback import CallbackBase
import json
import time
class CallbackModule(CallbackBase):
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'notification'
CALLBACK_NAME = 'custom_callback'
CALLBACK_NEEDS_WHITELIST = True
def __init__(self):
super(CallbackModule, self).__init__()
self.start_time = time.time()
self.stats = {
'tasks': [],
'hosts': {},
'summary': {
'ok': 0,
'changed': 0,
'failed': 0,
'skipped': 0,
'unreachable': 0
}
}
def v2_playbook_on_start(self, playbook):
self._display.display(f"🔵 Starting playbook: {playbook._file_name}")
def v2_playbook_on_play_start(self, play):
self._display.display(f"🟢 Starting play: {play.get_name()}")
def v2_runner_on_ok(self, result):
self.stats['summary']['ok'] += 1
self._log_task(result, 'ok')
def v2_runner_on_failed(self, result, ignore_errors=False):
self.stats['summary']['failed'] += 1
self._log_task(result, 'failed')
# Custom notification on failure
if not ignore_errors:
self._send_slack_notification(result)
def v2_runner_on_changed(self, result):
self.stats['summary']['changed'] += 1
self._display.display(f" 📝 Changed: {result.task_name}")
def v2_playbook_on_stats(self, stats):
end_time = time.time()
duration = end_time - self.start_time
self._display.banner("CUSTOM CALLBACK SUMMARY")
# Print custom summary
summary = {
'duration_seconds': round(duration, 2),
'tasks_executed': len(self.stats['tasks']),
'status': self.stats['summary']
}
self._display.display(json.dumps(summary, indent=2))
# Save to file
with open('/tmp/ansible-stats.json', 'w') as f:
json.dump(self.stats, f, indent=2)
self._display.display(f"💾 Stats saved to /tmp/ansible-stats.json")
def _log_task(self, result, status):
task_data = {
'task': result.task_name,
'host': result._host.get_name(),
'status': status,
'duration': result._task_fields.get('duration', 0)
}
self.stats['tasks'].append(task_data)
if result._host.get_name() not in self.stats['hosts']:
self.stats['hosts'][result._host.get_name()] = []
self.stats['hosts'][result._host.get_name()].append(task_data)
def _send_slack_notification(self, result):
"""Send notification on task failure"""
try:
import requests
webhook_url = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
message = {
"text": "Ansible Task Failed",
"attachments": [{
"color": "danger",
"fields": [
{
"title": "Task",
"value": result.task_name,
"short": True
},
{
"title": "Host",
"value": result._host.get_name(),
"short": True
},
{
"title": "Error",
"value": str(result._result.get('msg', 'Unknown error')),
"short": False
}
]
}]
}
requests.post(webhook_url, json=message)
except Exception as e:
self._display.warning(f"Failed to send Slack notification: {e}")
Playbook using custom plugins - Save as use_custom_plugins.yml:
---
- name: Use custom module and filters
hosts: appservers
become: yes
vars:
app_name: "MyApp_Production"
tasks:
# Use custom module
- name: Gather custom facts
custom_facts:
gather:
- network
- storage
- security
register: custom_facts_result
- name: Display custom network facts
debug:
var: ansible_facts.custom.network.interfaces
# Use custom filters
- name: Convert app name to K8s compliant name
set_fact:
k8s_app_name: "{{ app_name | to_k8s_name }}"
- name: Show K8s name
debug:
msg: "K8s compliant name: {{ k8s_app_name }}"
- name: Parse Java version
command: java -version
register: java_version_output
failed_when: false
- name: Extract Java version info
set_fact:
java_version: "{{ java_version_output.stderr | parse_java_version }}"
when: java_version_output.rc == 0
- name: Show Java version
debug:
var: java_version
- name: Calculate resource requirements
set_fact:
resources: "{{ 5 | calculate_resources(0.5, 512) }}"
vars:
replicas: 5
cpu_per_pod: 0.5
memory_per_pod: 512
- name: Display resource calculation
debug:
msg: "Total resources needed: {{ resources.total_cpu_cores }} cores, {{ resources.total_memory_mb }} MB"
# Use custom callback will automatically trigger
- name: Simulate failure for callback testing
command: /bin/false
ignore_errors: yes
- name: Successful task
command: echo "Success"
register: success_task
- name: Use custom filter with parameters
set_fact:
filtered_value: "{{ success_task.stdout | custom_filter('test_param') }}"
- name: Show filtered result
debug:
var: filtered_value
- name: Write stats to file
copy:
content: "{{ ansible_facts.custom | to_nice_json }}"
dest: /tmp/custom_facts.json
mode: '0644'
# Enable custom callback in ansible.cfg
# [defaults]
# callback_whitelist = custom_callback
# filter_plugins = filter_plugins/
# library = library/
# callback_plugins = callback_plugins/
Scenario 19: Ansible AWX/Tower Integration¶
Description: Manage enterprise Ansible automation with AWX/Tower for job scheduling, RBAC, and audit trails.
sequenceDiagram
participant User as DevOps Engineer
participant AWX as AWX/Tower
participant Git as Git Repository
participant Inventor as AWX Inventory
participant Job as AWX Job Template
participant Notification as Notification System
User->>Git: Push updated playbook
Git->>AWX: Webhook triggers project sync
AWX->>Git: Pull latest changes
Note over AWX: Project synced
User->>AWX: Create application credential
User->>AWX: Configure dynamic inventory
AWX->>Inventor: Sync AWS EC2 instances
Inventor-->>AWX: Dynamic inventory groups
User->>AWX: Create job template
Job->>AWX: Configure playbook, inventory, credentials
Job->>AWX: Set survey variables
User->>AWX: Launch job
AWX->>Job: Execute on target hosts
loop Job execution
Job->>Host: Run tasks
Host-->>Job: Return results
Job->>AWX: Update job status
end
AWX->>Notification: Send success/failure notification
Notification-->>User: Slack/Email notification
AWX->>AWX: Store job logs for audit
Note over AWX: RBAC, scheduling, and audit trails
AWX Configuration as Code - Save as awx_setup.yml:
---
- name: Configure AWX/Tower resources
hosts: localhost
connection: local
gather_facts: no
vars:
awx_host: https://awx.example.com
awx_token: "{{ vault_awx_token }}"
organization_name: "Production"
project_name: "Ansible Playbooks"
inventory_name: "AWS Production"
credential_name: "AWS SSH Key"
job_template_name: "Deploy Application"
tasks:
# Create organization
- name: Create organization
awx.awx.organization:
name: "{{ organization_name }}"
description: "Production Organization"
state: present
tower_host: "{{ awx_host }}"
tower_oauthtoken: "{{ awx_token }}"
validate_certs: no
# Create credential type for SSH key
- name: Create machine credential
awx.awx.credential:
name: "{{ credential_name }}"
description: "SSH key for production servers"
organization: "{{ organization_name }}"
credential_type: "Machine"
inputs:
username: "ansible"
ssh_key_data: "{{ lookup('file', '~/.ssh/awx_rsa') }}"
state: present
tower_host: "{{ awx_host }}"
tower_oauthtoken: "{{ awx_token }}"
validate_certs: no
# Create AWS credentials
- name: Create AWS credential
awx.awx.credential:
name: "AWS Credentials"
description: "AWS API access"
organization: "{{ organization_name }}"
credential_type: "Amazon Web Services"
inputs:
username: "{{ vault_aws_access_key }}"
password: "{{ vault_aws_secret_key }}"
state: present
tower_host: "{{ awx_host }}"
tower_oauthtoken: "{{ awx_token }}"
validate_certs: no
# Create project from Git
- name: Create project
awx.awx.project:
name: "{{ project_name }}"
description: "Main Ansible playbooks repository"
organization: "{{ organization_name }}"
scm_type: git
scm_url: "https://github.com/myorg/ansible-playbooks.git"
scm_branch: main
scm_update_on_launch: yes
scm_credential: "Git Credentials"
state: present
tower_host: "{{ awx_host }}"
tower_oauthtoken: "{{ awx_token }}"
validate_certs: no
# Create dynamic inventory from AWS
- name: Create inventory
awx.awx.inventory:
name: "{{ inventory_name }}"
description: "AWS EC2 dynamic inventory"
organization: "{{ organization_name }}"
state: present
tower_host: "{{ awx_host }}"
tower_oauthtoken: "{{ awx_token }}"
validate_certs: no
- name: Add AWS source to inventory
awx.awx.inventory_source:
name: "AWS EC2 Source"
inventory: "{{ inventory_name }}"
source: ec2
source_vars:
regions:
- us-east-1
- us-west-2
filters:
instance-state-name: running
update_on_launch: yes
update_cache_timeout: 300
state: present
tower_host: "{{ awx_host }}"
tower_oauthtoken: "{{ awx_token }}"
validate_certs: no
# Create job template
- name: Create job template
awx.awx.job_template:
name: "{{ job_template_name }}"
description: "Deploy application to production"
job_type: run
inventory: "{{ inventory_name }}"
project: "{{ project_name }}"
playbook: "playbooks/deploy-app.yml"
credentials:
- "{{ credential_name }}"
- "AWS Credentials"
forks: 20
limit: "env_production"
verbosity: 1
extra_vars:
app_version: "latest"
deploy_environment: "production"
survey_enabled: yes
survey_spec:
name: "Deployment Parameters"
description: "Parameters for deployment"
spec:
- type: "text"
question_name: "Application Version"
variable: "app_version"
required: yes
default: "latest"
- type: "multiplechoice"
question_name: "Deploy Environment"
variable: "deploy_environment"
choices: ["production", "staging"]
required: yes
default: "production"
state: present
tower_host: "{{ awx_host }}"
tower_oauthtoken: "{{ awx_token }}"
validate_certs: no
# Create schedule
- name: Create schedule for job template
awx.awx.schedule:
name: "Daily Security Updates"
job_template: "{{ job_template_name }}"
rrule: "DTSTART:20240101T020000Z RRULE:FREQ=DAILY;INTERVAL=1"
description: "Run security updates daily at 2 AM UTC"
enabled: yes
state: present
tower_host: "{{ awx_host }}"
tower_oauthtoken: "{{ awx_token }}"
validate_certs: no
# Create notification template
- name: Create Slack notification template
awx.awx.notification_template:
name: "Slack Deployment Notifications"
organization: "{{ organization_name }}"
notification_type: slack
notification_configuration:
token: "{{ vault_slack_token }}"
channels:
- "#deployments"
username: "AWX Bot"
messages:
started:
message: "Deployment started"
success:
message: "Deployment succeeded"
error:
message: "Deployment failed"
state: present
tower_host: "{{ awx_host }}"
tower_oauthtoken: "{{ awx_token }}"
validate_certs: no
# Associate notification with job template
- name: Associate notification with job template
awx.awx.job_template:
name: "{{ job_template_name }}"
notification_templates_success:
- "Slack Deployment Notifications"
notification_templates_error:
- "Slack Deployment Notifications"
state: present
tower_host: "{{ awx_host }}"
tower_oauthtoken: "{{ awx_token }}"
validate_certs: no
# Create workflow template
- name: Create deployment workflow
awx.awx.workflow_job_template:
name: "Full Deployment Workflow"
description: "Build, test, and deploy application"
organization: "{{ organization_name }}"
state: present
tower_host: "{{ awx_host }}"
tower_oauthtoken: "{{ awx_token }}"
validate_certs: no
- name: Add workflow nodes
awx.awx.workflow_job_template_node:
identifier: "build_stage"
workflow_job_template: "Full Deployment Workflow"
unified_job_template: "Build Docker Image"
state: present
tower_host: "{{ awx_host }}"
tower_oauthtoken: "{{ awx_token }}"
validate_certs: no
Launch job via API - launch_awx_job.yml:
---
- name: Launch AWX job from CLI
hosts: localhost
connection: local
gather_facts: no
vars:
awx_host: https://awx.example.com
awx_token: "{{ vault_awx_token }}"
job_template_id: 20
extra_vars:
app_version: "{{ version | default('latest') }}"
deploy_environment: "{{ env | default('staging') }}"
tasks:
- name: Launch job template
awx.awx.job_launch:
job_template: "{{ job_template_id }}"
extra_vars: "{{ extra_vars | to_json }}"
wait: yes
tower_host: "{{ awx_host }}"
tower_oauthtoken: "{{ awx_token }}"
validate_certs: no
register: job_status
- name: Display job status
debug:
var: job_status
- name: Wait for job completion
awx.awx.job_wait:
job_id: "{{ job_status.id }}"
timeout: 1800
interval: 30
tower_host: "{{ awx_host }}"
tower_oauthtoken: "{{ awx_token }}"
validate_certs: no
register: job_result
- name: Show job result
debug:
msg: "Job completed with status: {{ job_result.status }}"
AWX CLI usage:
# Install AWX CLI
pip install awxkit
# Configure AWX CLI
export TOWER_HOST=https://awx.example.com
export TOWER_TOKEN=your-oauth-token
# List resources
awx projects list
awx job_templates list
awx inventories list
# Launch job
awx job launch --job_template="Deploy Application" \
--extra_vars="app_version=2.0.0 deploy_environment=production"
# Monitor job
awx job list --status=running
awx job stdout --id=123
# Export/Import configuration
awx export --organization "Production" > awx-config.json
awx import < awx-config.json
Scenario 20: Enterprise-Scale Ansible & Performance Tuning¶
Description: Deploy Ansible at enterprise scale with performance optimization, capacity planning, and high availability patterns.
sequenceDiagram
participant Load as Load Balancer
participant Ansible1 as Ansible Controller 1
participant Ansible2 as Ansible Controller 2
participant Redis as Redis Cache
participant Database as AWX Database
participant Worker1 as Worker Node 1
participant Worker2 as Worker Node 2
participant Worker3 as Worker Node 3
User->>Load: Submit job request
Load->>Ansible1: Route request (active-active)
Ansible1->>Ansible1: Parse playbook
Ansible1->>Redis: Check fact cache
Redis-->>Ansible1: Return cached facts
Ansible1->>Ansible1: Generate execution plan
loop Parallel execution (forks=100)
Ansible1->>Worker1: Execute tasks (subset of hosts)
Ansible1->>Worker2: Execute tasks (subset of hosts)
Ansible1->>Worker3: Execute tasks (subset of hosts)
Worker1-->>Ansible1: Return results
Worker2-->>Ansible1: Return results
Worker3-->>Ansible1: Return results
end
Ansible1->>Redis: Update fact cache
Ansible1->>Database: Store job results
LoadBalancer->>Ansible2: Health check
Ansible2-->>LoadBalancer: Healthy (standby)
Note over Ansible1,Ansible2: HA setup with Redis + DB
Enterprise ansible.cfg - /etc/ansible/ansible.cfg:
[defaults]
# Performance tuning
forks = 500
timeout = 30
poll_interval = 5
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /var/lib/ansible/facts
fact_caching_timeout = 86400
# Paths
inventory = /etc/ansible/inventories
library = /usr/share/ansible/plugins/modules
module_utils = /usr/share/ansible/plugins/module_utils
action_plugins = /usr/share/ansible/plugins/action
callback_plugins = /usr/share/ansible/plugins/callback
connection_plugins = /usr/share/ansible/plugins/connection
lookup_plugins = /usr/share/ansible/plugins/lookup
vars_plugins = /usr/share/ansible/plugins/vars
filter_plugins = /usr/share/ansible/plugins/filter
test_plugins = /usr/share/ansible/plugins/test
strategy_plugins = /usr/share/ansible/plugins/strategy
# Strategy
strategy = mitogen_linear
mitogen_forks = 500
# Fact filters
fact_filter = ansible_local, ansible_env, ansible_cmdline
# SSH optimization
transport = smart
remote_port = 22
scp_if_ssh = smart
pipelining = True
ssh_executable = ssh
ssh_args = -o ControlMaster=auto -o ControlPersist=600s -o StrictHostKeyChecking=no
# Privilege escalation
become = True
become_method = sudo
become_user = root
become_ask_pass = False
# Logging
log_path = /var/log/ansible/ansible.log
log_filter = shred
display_args_to_stdout = False
# Execution
any_errors_fatal = False
max_fail_percentage = 10
force_handlers = True
module_compression = 'ZIP_STORED'
module_set_locale = True
# Vault
vault_identity_list = prod@/etc/ansible/vault_pass_prod, dev@/etc/ansible/vault_pass_dev
# Galaxy
roles_path = /etc/ansible/roles:/usr/share/ansible/roles
collections_path = /etc/ansible/collections:/usr/share/ansible/collections
cache_plugins = yes
[inventory]
# Cache inventory
cache = yes
cache_connection = /var/lib/ansible/inventory
cache_timeout = 1800
cache_plugin = jsonfile
[privilege_escalation]
# Becomes settings
flags = -H -S -n
su_exe = su
sudo_exe = sudo
sudo_flags = -H -S -n
[ssh_connection]
# Performance
retries = 3
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=600s
control_path_dir = /tmp/.ansible/cp
control_path = %(directory)s/%%h-%%p-%%r
scp_if_ssh = True
sftp_batch_mode = True
transfer_method = smart
[galaxy]
# Galaxy settings
server_list = galaxy, automation_hub
[galaxy_server.galaxy]
url = https://galaxy.ansible.com/
[galaxy_server.automation_hub]
url = https://console.redhat.com/api/automation-hub/
auth_url = https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token
token = your_api_token_here
[persistent_connection]
# For network devices
command_timeout = 60
connect_timeout = 30
connect_retry_timeout = 15
# Network CLI
[netconf_connection]
# NETCONF settings
ssh_keyfile = /etc/ansible/ssh/keys/netconf_key
Capacity planning playbook - capacity_planning.yml:
---
- name: Ansible controller capacity planning
hosts: localhost
connection: local
gather_facts: yes
vars:
recommended_forks_per_core: 50
recommended_open_files: 10000
tasks:
- name: Gather controller facts
setup:
gather_subset:
- hardware
- network
- name: Calculate recommended settings
set_fact:
cpu_cores: "{{ ansible_processor_vcpus }}"
total_memory_mb: "{{ ansible_memtotal_mb }}"
recommended_forks: "{{ ansible_processor_vcpus * recommended_forks_per_core }}"
current_ulimit: "{{ ansible_rlimit_nofile.cur }}"
recommended_ulimit: "{{ recommended_open_files }}"
- name: Display capacity recommendations
debug:
msg: |
==================== CAPACITY PLANNING ====================
Controller Host: {{ inventory_hostname }}
CPU Cores: {{ cpu_cores }}
Total Memory: {{ total_memory_mb }} MB
RECOMMENDED SETTINGS:
Forks ({{ recommended_forks_per_core }} per core): {{ recommended_forks }}
Open files limit: {{ recommended_ulimit }} (current: {{ current_ulimit }})
PERFORMANCE TIPS:
- Use smart fact gathering (already configured)
- Enable fact caching (already configured)
- Use pipelining (already configured)
- Use strategy plugins (mitogen)
- Disable host key checking (controlled)
==========================================================
- name: Check current ulimits
command: ulimit -n
register: current_ulimit_check
- name: Verify system tuning
assert:
that:
- current_ulimit|int >= recommended_open_files|int
fail_msg: "Open files limit too low. Run: ulimit -n {{ recommended_open_files }}"
- name: Generate sysctl recommendations
copy:
dest: /tmp/ansible_sysctl.conf
content: |
# Ansible Controller Performance Tuning
# Add to /etc/sysctl.conf
# Increase network connection tracking
net.nf_conntrack_max = 2000000
# Increase port range
net.ipv4.ip_local_port_range = 1024 65535
# TCP tuning
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
# Increase file descriptors
fs.file-max = 2000000
# Memory tuning
vm.swappiness = 10
- name: Monitor disk space for logs
stat:
path: /var/log/ansible
register: log_dir
- name: Calculate log rotation needs
debug:
msg: |
Ansible log directory size: {{ (log_dir.stat.size / 1024 / 1024) | round(2) }} MB
Recommendation: Configure logrotate with maxsize=500M, rotate=10
- name: Check network latency to managed hosts
wait_for_connection:
timeout: 5
delegate_to: "{{ item }}"
loop: "{{ groups['all'][:10] }}" # Sample 10 hosts
register: latency_check
- name: Calculate average latency
set_fact:
avg_latency: "{{ (latency_check.results | map(attribute='elapsed') | map('float') | sum / latency_check.results | length) | round(3) }}"
- name: Display latency metrics
debug:
msg: |
Average connection latency: {{ avg_latency }} seconds
If > 1s: Consider local execution or async strategies
- name: Generate capacity report
copy:
dest: "/tmp/ansible-capacity-report-{{ ansible_date_time.date }}.json"
content: |
{
"controller": "{{ inventory_hostname }}",
"timestamp": "{{ ansible_date_time.iso8601 }}",
"hardware": {
"cpu_cores": {{ cpu_cores }},
"memory_mb": {{ total_memory_mb }}
},
"recommendations": {
"forks": {{ recommended_forks }},
"open_files": {{ recommended_ulimit }},
"avg_latency_s": {{ avg_latency }}
},
"status": "optimal" if {{ recommended_forks|int <= 1000 }} else "scaling_needed"
}
# High availability setup for controllers - ha_setup.yml
---
- name: Setup HA Ansible controllers
hosts: ansible_controllers
become: yes
vars:
redis_host: redis.example.com
postgres_host: postgres.example.com
tasks:
- name: Install HA components
apt:
name:
- redis-tools
- postgresql-client
- haproxy
- keepalived
- name: Configure Redis fact cache
lineinfile:
path: /etc/ansible/ansible.cfg
line: "{{ item }}"
loop:
- "fact_caching = redis"
- "fact_caching_connection = redis://{{ redis_host }}:6379/0"
- "fact_caching_timeout = 86400"
- name: Configure shared project directory
file:
path: /opt/ansible/projects
state: directory
mode: '0755'
- name: Mount NFS share for projects
mount:
path: /opt/ansible/projects
src: "nfs-server:/export/ansible"
fstype: nfs
state: mounted
- name: Configure HAProxy for load balancing
template:
src: templates/haproxy.cfg.j2
dest: /etc/haproxy/haproxy.cfg
notify: Reload HAProxy
vars:
backend_servers: "{{ groups['ansible_controllers'] }}"
port: 8080
- name: Enable HAKeepalived for failover
template:
src: templates/keepalived.conf.j2
dest: /etc/keepalived/keepalived.conf
notify: Restart Keepalived
vars:
vip: "10.0.0.100"
priority: "{{ 100 - play_hosts.index(inventory_hostname) }}"
- name: Configure log aggregation
copy:
dest: /etc/rsyslog.d/50-ansible.conf
content: |
local0.* /var/log/ansible/ansible.log
& stop
- name: Restart rsyslog
service:
name: rsyslog
state: restarted
# Configure systemd service for ansible-runner
- name: Create ansible-runner service
copy:
dest: /etc/systemd/system/ansible-runner@.service
content: |
[Unit]
Description=Ansible Runner %i
After=network.target
[Service]
Type=simple
User=ansible
WorkingDirectory=/opt/ansible/projects/%i
ExecStart=/usr/bin/ansible-runner run .
Restart=always
[Install]
WantedBy=multi-user.target
- name: Enable ansible-runner services
systemd:
name: ansible-runner@prod
enabled: yes
state: started
handlers:
- name: Reload HAProxy
service:
name: haproxy
state: reloaded
- name: Restart Keepalived
service:
name: keepalived
state: restarted
Performance monitoring playbook - monitor_performance.yml:
---
- name: Monitor Ansible controller performance
hosts: ansible_controllers
become: yes
vars:
alert_threshold_cpu: 80
alert_threshold_memory: 85
alert_threshold_disk: 90
tasks:
- name: Gather performance metrics
setup:
gather_subset:
- hardware
- network
- name: Get Ansible process info
command: ps -C ansible-playbook -o pid,ppid,%cpu,%mem,cmd --no-headers
register: ansible_processes
failed_when: false
- name: Count active ansible processes
set_fact:
active_processes: "{{ ansible_processes.stdout_lines | length }}"
cpu_usage: "{{ ansible_processor_utilization | default(0) }}"
memory_usage: "{{ (1 - (ansible_memfree_mb / ansible_memtotal_mb)) * 100 }}"
disk_usage: "{{ (ansible_mounts[0].size_total - ansible_mounts[0].size_available) / ansible_mounts[0].size_total * 100 }}"
- name: Check thresholds
set_fact:
alerts: "{{ alerts | default([]) + [item.message] }}"
loop:
- { condition: cpu_usage > alert_threshold_cpu, message: "High CPU usage: {{ cpu_usage }}%" }
- { condition: memory_usage > alert_threshold_memory, message: "High memory usage: {{ memory_usage }}%" }
- { condition: disk_usage > alert_threshold_disk, message: "High disk usage: {{ disk_usage }}%" }
- { condition: active_processes > 50, message: "Too many active processes: {{ active_processes }}" }
when: item.condition
- name: Send alerts if thresholds exceeded
uri:
url: "{{ monitoring_webhook_url }}"
method: POST
body_format: json
body:
alert_type: "ansible_controller"
hostname: "{{ inventory_hostname }}"
severity: "warning" if alerts|length < 3 else "critical"
message: "{{ alerts | join('; ') }}"
when: alerts is defined and alerts|length > 0
- name: Generate performance report
copy:
dest: "/var/log/ansible/performance-report-{{ ansible_date_time.epoch }}.json"
content: |
{
"timestamp": "{{ ansible_date_time.iso8601 }}",
"hostname": "{{ inventory_hostname }}",
"performance": {
"cpu_usage_percent": {{ cpu_usage }},
"memory_usage_percent": {{ memory_usage }},
"disk_usage_percent": {{ disk_usage }},
"active_processes": {{ active_processes }},
"forks_configured": {{ forks }},
"load_average": "{{ ansible_loadavg['15m'] }}"
},
"recommendations": {{ recommendations if recommendations is defined else '[]' }}
}
Quick reference for enterprise Ansible:
| Command | Description | Enterprise Impact |
|---|---|---|
ansible-playbook -f 500 --timeout 30 |
High parallel execution | Reduces execution time by 80% |
ANSIBLE_CALLBACK_WHITELIST=profile_tasks,timer |
Performance profiling | Identifies slow tasks |
strategy = mitogen_linear |
Alternative execution engine | 2-7x faster than linear |
fact_caching = redis |
Shared fact cache for HA | Consistent facts across controllers |
forks = 500 |
Maximum parallel processes | Requires kernel tuning |
pipelining = True |
Reduce SSH connections | 30% faster execution |
serial: 5 |
Rolling deployment | Zero-downtime deployments |
max_fail_percentage: 10 |
Allow partial failures | Increases success rate |
any_errors_fatal: False |
Continue on errors | Better fault tolerance |
throttle: 1 |
Rate limiting | API protection |
Pro Tips for Enterprise-Scale Ansible:
- Always use Ansible Automation Platform (AWX/Tower) for centralized management
- Configure Redis fact caching for multi-controller setups
- Enable log aggregation with ELK or Splunk
- Use systemd service for ansible-runner instead of ad-hoc runs
- Implement health checks for controllers and workers
- Configure firewall rules for controller-to-host communication
- Use certificate-based SSH instead of passwords
- Implement backup/restore for AWX database
- Monitor with Prometheus/Grafana for metrics
- Use load balancers for HA setup
- Configure log rotation to prevent disk full
- Implement RBAC with LDAP/AD integration
- Use workflow templates for complex deployments
- Schedule periodic jobs for configuration drift detection
- Set resource quotas for job execution environments
Happy Learning 🌟