Intermediate

Efficient and Scalable Ansible Automation Strategies

Welcome to Pluralsight. In this course, Efficient and Scalable Ansible Automation Strategies, you will dive into the world of automation with Ansible AWX through an intensive course focus...

Table of Contents

  1. Course Introduction
  2. Launch your first automation with AWX
  1. Module 2 — Construction of complex workflows with AWX (9m 26s)
  1. Module 3 — AWX integration with external platforms (11m 6s)
  1. Module 4 — Scaling and Optimizing AWX for Performance (8m 44s)

1. Introduction to the course

Welcome to Pluralsight. In this course, Efficient and Scalable Ansible Automation Strategies, you will dive into the world of automation with Ansible AWX through an intensive course focused on hands-on demonstrations. The teaching approach remains simple, concise and concrete so that you can gain confidence step by step.

The course begins by laying out the foundations: creating projects, templates and basic automation tasks. Gradually, we are moving to more advanced workflows, real-world integrations and powerful optimization techniques. This course is very demo-oriented — you can follow along and build your own AWX infrastructure from scratch.

Prerequisites

To get the most out of this course, it is recommended to have knowledge of:

  • Infrastructure provisioning
  • The YAML language
  • Git
  • Linux basics
  • Basics of Kubernetes

2. Launch your first automation with AWX

2.1 Installing AWX

Context and environment

AWX is the upstream open source version of Ansible Automation Platform (AAP). The latest stable version at the time of this course is 24.6.1. AWX requires containers and Kubernetes to work. The environment used in this course is:

  • Server: Ubuntu 24 LTS with Docker and Ansible preinstalled
  • Client: Windows 11 from which we connect to the Ubuntu server via the Remote SSH extension of VS Code
  • Kubernetes Distribution: minikube

All commands, scripts, and playbooks used in this course are available in the Exercise Files section.

Checking Docker permissions

Before installing anything, you must ensure that your user account has the necessary permissions to run Docker containers without root access:

groups $USER

If you see your user in the docker group, you are ready. Otherwise, run the following commands and restart your system:

sudo groupadd docker
sudo usermod -aG docker $USER

Script 1: Installing minikube and kubectl

The first script installs minikube and kubectl, configures permissions, displays minikube status and lists all running pods.

#!/bin/bash

echo "Installing kubectl..."
sudo snap install kubectl --classic

echo -e "\n\n\nDownloading Minikube binary..."
wget https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 -O minikube

echo -e "\n\n\nSetting executable permissions for Minikube..."
chmod 755 minikube

echo -e "\n\n\nMoving Minikube to /usr/local/bin..."
sudo mv minikube /usr/local/bin/

echo -e "\n\n\nChecking Minikube version..."
minikube version

echo -e "\e[96m\n\n\nDo you want to start Minikube now? (y/n): \e[0m"
read -r proceed

if [ "$proceed" != "y" ] && [ "$proceed" != "Y" ]; then
    echo "Minikube start skipped. Exiting script."
    exit 0
fi

echo -e "\n\n\nStarting Minikube with Docker driver..."
minikube start --vm-driver=docker

sleep 2
echo -e "\n\n\nChecking Minikube status..."
minikube status

sleep 2
echo -e "\n\n\nListing all Kubernetes pods across namespaces..."
kubectl get pods -A

Script 2: Installing the AWX Operator

The second script installs the AWX Operator. The AWX Operator is a tool that automates the deployment and management of AWX in the Kubernetes environment. It supports the complex tasks of deploying, scaling, and managing AWX instances in Kubernetes.

#!/bin/bash

echo "Step 1: Cloning AWX Operator GitHub repo..."
git clone https://github.com/ansible/awx-operator.git

cd awx-operator/

echo -e "\n\n\nStep 2: Checking out version 2.19.0..."
git checkout 2.19.0

echo -e "\n\n\nStep 3: Setting namespace and deploying AWX Operator..."
export NAMESPACE=ansible-awx
make deploy

sleep 2

echo -e "\n\n\nStep 4: Getting AWX pods in namespace 'ansible-awx'..."
kubectl get pods -n ansible-awx

echo -e "\n\n\nStep 5: Cloning awx-demo.yml for customization..."
cp awx-demo.yml awx-ubuntu.yml
echo -e "\n\n\nStep 5: Created awx-ubuntu.yml for awx installation..."

echo -e "\n\n\nEditing 'awx-ubuntu.yml' for awx installation:"

cat <<EOF > awx-ubuntu.yml
---
apiVersion: awx.ansible.com/v1beta1
kind: AWX
metadata:
  name: awx-ubuntu
spec:
  service_type: NodePort
EOF

echo -e "\e[96m\n\n\nVerify the awx-ubuntu.yml file before beggining awx installation.\e[0m"

AWX Deployment

After executing the second script, the process proceeds as follows:

  1. The script clones the AWX Operator repository.
  2. The operator is installed and a custom resource file (awx-ubuntu.yml) is created.
  3. The kubectl command is used to install AWX from this custom resource file:
kubectl apply -f awx-ubuntu.yml
  1. We can verify that the pods start in the ansible-awx namespace:
kubectl get pods -n ansible-awx

The AWX Operator controller manager pod is created first, followed by the Postgres pod and other required pods. This process takes approximately 10 minutes. It is important to ensure that no task has failed at the end.

  1. To obtain the internal URL of the AWX service:
minikube service awx-ubuntu-service -n ansible-awx --url
  1. To access AWX from external systems, you must perform port forwarding:
kubectl port-forward service/awx-ubuntu-service -n ansible-awx --address 0.0.0.0 10445:80

In this example, port 10445 is used. You can choose any available port.

AWX autostart script

To avoid having to manually restart the AWX pods after each system reboot, an autostart-awx.sh script is provided in the exercise files. This script can be scheduled via a cron job to run at startup:

#!/bin/bash

# Start Kubernetes
minikube start --vm-driver=docker

## Wait for Kubernetes to be ready
sleep 60

# Optional: export KUBECONFIG if needed
# export KUBECONFIG=/home/your-user/.kube/config

# Start port forwarding in background and redirect output
kubectl port-forward service/awx-ubuntu-service -n ansible-awx --address 0.0.0.0 10445:80 > /tmp/awx-port-forward.log 2>&1 &

Connecting to AWX

With the pods up and running, open a browser and navigate to http://<IP_Ubuntu>:10445. The AWX home page appears. To recover the default administrator password:

kubectl get secret awx-ubuntu-admin-password -n ansible-awx -o jsonpath="{.data.password}" | base64 --decode

Use this password with the admin account to log in. The first thing to do after logging in is to change the password: go to Users, click Edit and set a new password.

Installation Summary

The steps taken to install AWX:

  1. Installing minikube and kubectl
  2. Installing the AWX Operator
  3. Installing AWX via a custom resource file (awx-ubuntu.yml)

2.2 Creating projects and templates

What is a project in AWX?

A project in AWX is simply a way to organize your playbooks. It links AWX to the Git repository (or other control source) where your playbooks are hosted.

Creating a project

  1. In the left menu, under Resources, click on Projects.
  2. Click on the blue Add button.
  3. Complete the fields:
  • Name: server_info_project
  • Organization: Default
  • Source Control Type: Git
  • Source Control URL: URL of the Git repository containing the playbooks
  • Commit/Branch: last commit (this field can be updated throughout the course because the repository is updated)
  • Source Code Credentials are not necessary if the repository is public.
  1. Click on Save.

As soon as the project is created, AWX performs a test synchronization with the Git repository. The result of this synchronization is visible under Jobs and should display a success status.

What is a job template?

A job template tells AWX what to run, where to run it and how to run it. It is the central object that orchestrates the execution of playbooks.

Creating a job template

  1. In the left menu, under Resources, click on Templates.
  2. Click Add and select Job Template.
  3. Complete the fields:
  • Name: gather_server_details
  • Job Type: Run
  • Inventory: select an inventory (to be created later, for now use the default inventory)
  • Project: server_info_project
  • Playbook: select a playbook available in the Git repository
  • Credentials: optional at this stage
  • Variables: allows you to define additional variables passed to the playbook at runtime
  1. Advanced options allow fine control of execution (performance, target hosts, privilege escalation, external integrations, etc.). For now, click Save.

Then click on Launch to run the template. The job appears under Jobs and should appear as successful, confirming connectivity with Git and the ability to retrieve and run playbooks from the repository.


2.3 Adding credentials and inventories

Credentials

Credentials allow AWX to authenticate to target hosts or services. AWX already provides a default credential upon installation.

Creating a credential
  1. Under Resources, click on Credentials.
  2. Click on Add.
  3. Enter the Name: windows_server_credential.
  4. Choose the Credential Type according to your needs:
  • Amazon Web Services → stores Access Key and Secret Key
  • GitHub Personal Access Token → stores a token
  • Machine → stores username, password or private SSH key (this type is used here)
  1. Enter the username and password of the Windows administrator account to manage (used to connect via WinRM).
  2. There is also an option to store an SSH private key.
  3. At the bottom of the page, advanced options for privilege escalation:
  • Linux: use sudo
  • Windows: use runas
  • These fields can be configured to request running information.
  1. Click on Save.

Inventories

An inventory is the list of servers on which you want to run tasks.

Creating an inventory
  1. Under Resources, click on Inventories then Add Inventory.
  2. Enter the Name: windows_inventory.
  3. The Instance Groups field is important: it allows you to control on which execution node the jobs run. This allows, for example, to assign certain inventories (production, staging) to different groups of instances for security or performance reasons.
  4. Click on Save.
  5. Go to the Hosts tab and click on Add.
  6. In the Name field, enter the hostname or IP address of the server to manage.
  7. Click on Save.
Job template update

Once the credentials and inventory have been created, update the template:

  1. Go to the template created previously.
  2. Under Inventory, select windows_inventory.
  3. Under Credentials, select the created credential.
  4. Save changes.

2.4 Creation and execution of the first playbook

Playbook hello_world.yml

The simplest possible playbook to verify that AWX is working correctly:

- name: Hello World Sample
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Hello Message
      debug:
        msg: "Hello World!"

Playbook server_info.yml — First real playbook

This playbook retrieves and displays the hostname and IP address of the targeted Windows server.

- name: Display server hostname and IP address
  hosts: all
  gather_facts: no
  tasks:
    - name: Get hostname
      win_shell: |
        hostname
      register: hostname_output

    - name: Show hostname
      debug:
        var: hostname_output.stdout_lines

    - name: Get IP Address
      win_shell: |
        (Get-NetIPAddress -AddressFamily IPv4).IPAddress
      register: ip_address_output

    - name: Show IP Address
      debug:
        var: ip_address_output.stdout_lines

Key points from this playbook:

  • hosts: all — The playbook runs on all hosts in the selected inventory.
  • gather_facts: no — Additional server details will not be captured automatically by AWX.
  • Module win_shell runs PowerShell scripts on Windows hosts.
  • The keyword register saves the result of the task in a variable.
  • The debug module displays the results stored in variables.

WinRM configuration on Windows host

Before running the playbook, you must configure AWX to connect to the DC01 server via WinRM. In the inventory, go to host DC01 and edit the Variables field:

ansible_connection: winrm
ansible_winrm_server_cert_validation: ignore

Important Note: In a production environment, it is recommended to use certificate-based authentication, especially when a certificate authority infrastructure is in place. Refer to official Ansible documentation for detailed steps.

Your Windows server must also be configured to accept WinRM connections.

Using gather_facts

Playbook server_info_gather_facts.yml

This playbook demonstrates how to use Ansible’s gather_facts feature to automatically collect detailed server information:

- name: Display server hostname and IP address
  hosts: all
  gather_facts: yes
  tasks:
    - name: Show gathered facts
      debug:
        var: ansible_facts

With gather_facts: yes, AWX automatically collects a large amount of information: IP addresses, operating system information, CPU architecture, and much more — without writing additional commands.

Playbook using_gather_facts_data.yml — Conditionals

Data from gather_facts can be used in conditionals to perform more complex operations:

- name: Display server hostname and IP address
  hosts: all
  gather_facts: yes
  tasks:
    - name: Display message if running on VMware
      debug:
        msg: "This is a virtual server running on VMware"
      when: ansible_facts['virtualization_type'] == "VMware"

This playbook uses the ansible_facts['virtualization_type'] fact to display a message only if the server is running on VMware. In the demonstration example, the message is displayed because server DC01 is actually running on a VMware platform.


2.5 Configuring Role-Based Access Control (RBAC)

RBAC Concept

In real-world environments, it is not desirable for everyone to have complete control over everything. This is where Role-Based Access Control (RBAC) comes in. RBAC provides fine control over who can do what in AWX.

Configuring RBAC in AWX

Steps to assign permissions to a user:

  1. Go to the resource to which you wish to give access (project, inventory, job template, etc.).
  2. In the demonstrated case, select the gather_server_details template.
  3. Go to the Access tab.
  4. Click on Add.
  5. You can assign roles:
  • Direct to a user (approach used in demo)
  • To a team (to organize users into groups)
  1. Select the user (TestUser).
  2. Assign the Execute role — this allows the user to only execute the template, without being able to modify or delete it.

Checking RBAC

When connecting with TestUser:

  • The user can launch the template successfully.
  • When opening the template properties, the Edit and Delete options are not available.

This perfectly demonstrates how RBAC works in AWX: each user only sees and can do what they have permissions for.


3. Building Complex Workflows with AWX

This module addresses more advanced workflows: management of blocks, loops, and intelligent execution strategies. You’ll see how AWX handles errors, dynamic data, and scaling on different hosts.

3.1 Error handling with Block and Rescue

Key concepts

Error handling in Ansible uses three keywords to define structured exception handling:

KeywordBehavior
blockGroups several tasks logically; allows sharing rescue and always behaviors
rescueRuns when a task inside the block fails — useful for fallback or recovery actions
alwaysExecutes regardless of success or failure of block or rescue — similar to finally in traditional programming

Playbook with intentional error — 2-block_rescue.yml

This playbook demonstrates error handling. The PowerShell command to get the IP address is intentionally incorrect (IPv instead of IPv4) to generate an error:

---
- name: Manage Windows Server Info
  hosts: dc01
  gather_facts: no
  tasks:
    - name: Main task block with error handling
      block:
        - name: Get Hostname
          win_shell: hostname
          register: hostname_output
          
        - name: Display hostname
          debug:
            var: hostname_output.stdout_lines

        - name: Get IP Address
          win_shell: (Get-NetIPAddress -AddressFamily IPv).IPAddress
          register: ip_address_output

        - name: Display IP Address
          debug:
            var: ip_address_output.stdout_lines
      rescue:
        - name: Handle errors if hostname or IP retrieval fails
          debug:
            msg: "An error occurred while fetching server information."
      always:
        - name: Always run this task
          debug:
            msg: "Block execution completed, whether success or failure."

Playbook fixed — 2-block_rescue_OK.yml

The version corrected with the correct PowerShell command (IPv4):

---
- name: Manage Windows Server Info
  hosts: dc01
  gather_facts: no
  tasks:
    - name: Main task block with error handling
      block:
        - name: Get Hostname
          win_shell: hostname
          register: hostname_output
          
        - name: Display hostname
          debug:
            var: hostname_output.stdout_lines

        - name: Get IP Address
          win_shell: (Get-NetIPAddress -AddressFamily IPv4).IPAddress
          register: ip_address_output

        - name: Display IP Address
          debug:
            var: ip_address_output.stdout_lines
      rescue:
        - name: Handle errors if hostname or IP retrieval fails
          debug:
            msg: "An error occurred while fetching server information."
      always:
        - name: Always run this task
          debug:
            msg: "Block execution completed, whether success or failure."

Behavior during execution

When running the playbook with the intentional error:

  1. The first task (retrieving the hostname) succeeds.
  2. The second task (IP retrieval) fails because of the incorrect command.
  3. The rescue section runs and displays the error message.
  4. The always section runs in all cases and displays the completion message.

Note: If no errors occur, the rescue section does not execute. You can perform any valid action in the rescue section (sending notification, rollback, logging, etc.).


3.2 Ansible loops and complex variables

Loop concept

Ansible supports loops to run a task multiple times with different items, using the loop keyword (as well as other keywords like with_items). Loops simplify repetitive operations like creating users or installing packages.

Playbook 2-loops.yml

This playbook uses a list variable to monitor the status of several Windows services:

---
- name: Manage Windows Server Info
  hosts: dc01
  gather_facts: no
  vars:
    services_to_check:
      - w32time
      - bits
      - spooler

  tasks:
    - name: Main task block with error handling
      block:
        - name: Check status of important services
          win_service:
            name: "{{ item }}"
          loop: "{{ services_to_check }}"

      rescue:
        - name: Handle errors if Retrieving service status fails
          debug:
            msg: "An error occurred while fetching service information."

Key points:

  • The services_to_check variable contains a list of Windows service names to monitor.
  • The keyword loop loops through each service in the list.
  • The special variable {{ item }} represents the current item in the loop.
  • The win_service module retrieves the status of each service.
  • This structure avoids writing repetitive tasks for each service.

At runtime, Ansible retrieves the status of services one by one. By clicking on the name of a service in the AWX interface, you can see its complete status.


3.3 Task execution strategies

Ansible execution policies define how tasks are distributed and executed across multiple hosts in your inventory. Each policy has distinct behavior and a specific use case.

Linear Strategy

Behavior: Runs one task at a time on all hosts, in the same order. Each task completes on all hosts before moving on to the next one.

Example: With 3 tasks and 3 hosts (A, B, C):

  1. Task 1 running on A, B and C → Task 1 completed on all
  2. Task 2 is running on A, B and C → Task 2 completed on all
  3. Task 3 runs on A, B and C

Use case:

  • When you want a predictable execution flow.
  • When tasks depend on each other between hosts (for example, configuration steps must complete everywhere before continuing).

Free Strategy

Behavior: Tasks run in parallel and independently of other hosts. Each host executes its tasks as quickly as possible without waiting for others. Tasks run in order per host, but not in sync with other hosts.

Example: It is possible for host A to reach Task 3 while host B is still on Task 1.

Use case:

  • When speed takes priority over synchronization.
  • Ideal for large environments and parallel workloads.

Host Pinned Strategy

Behavior: Similar to the linear strategy, but each host is assigned a dedicated worker thread throughout the play. This strategy ensures that output and execution remain isolated and consistent per host.

Use case:

  • Mainly used for troubleshooting, testing or debugging.
  • Less common in everyday automations.

Policy Summary

StrategyRecommended use
linearConsistent scheduling, dependencies between hosts
freePerformance, wide environments
host_pinnedHost control, debugging

3.4 Creating a dynamic inventory with Azure Resource Manager

Dynamic Inventory Concept

In Ansible, creating a dynamic inventory means configuring AWX to pull host data from an external source. This external source can be:

  • A custom script
  • A cloud provider like Amazon EC2, Azure, VMware vCenter, etc.

Prerequisites: Create an Azure Core Service

The first step is to create an Azure backend service to allow AWX to authenticate with Azure. Run the following commands from your Azure terminal:

az ad sp create-for-rbac --name ansible-awx-sp --role Contributor \
  --scopes /subscriptions/{Subscription_ID} --sdk-auth

The output contains the subscriptionId, clientId, clientSecret and tenantId.

Creating the Azure credential in AWX

From the output of the previous command, create a credential of type Microsoft Azure Resource Manager with:

  • Subscription ID
  • Client ID
  • Secret Client
  • Tenant ID

Creating dynamic inventory

  1. Under Inventories, create a new inventory named Azure Dynamic Inventory.
  2. Go to the Sources tab and add a source.
  3. Give the source a name and select the type Microsoft Azure Resource Manager.
  4. In the Variables section, you can apply filters by resource groups or tags. For this demo, we only retrieve the VMs from the ansible-managed resource group:
resource_groups:
  - ansible-managed
  1. Click on Sync.

Result

The synchronization appears in Jobs and should show a success status. Navigating through the created inventory, the Hosts tab displays the list of VMs imported from the ansible-managed resource group in Azure. Clicking on a VM provides detailed information about it.


4. AWX Integration with External Platforms

This module explores how to integrate Ansible AWX with external systems like Docker, Kubernetes, networking equipment and cloud providers. AWX can extend automation beyond servers and interact with real platforms and APIs.

4.1 Creating a Kubernetes namespace and pod from AWX

Objective

This playbook uses Ansible to create a Kubernetes namespace and deploy an Nginx pod to it, all orchestrated from AWX.

Playbook 3-integrate_docker_k8s.yml

---
- name: Deploy nginx pod
  hosts: 192.168.5.8
  gather_facts: no

  vars:
    namespace: demo-space2

  tasks:

    - name: Create namespace
      kubernetes.core.k8s:
        api_version: v1
        kind: Namespace
        name: "{{ namespace }}"
        state: present

    - name: Deploy nginx pod
      kubernetes.core.k8s:
        api_version: v1
        kind: Pod
        namespace: "{{ namespace }}"
        name: nginx-pod2
        state: present
        definition:
          metadata:
            labels:
              app: nginx
          spec:
            containers:
              - name: nginx
                image: nginx
                ports:
                  - containerPort: 80

    - name: Get list of pods
      kubernetes.core.k8s_info:
        kind: Pod
        namespace: "{{ namespace }}"
      register: pod_info

    - name: Display pod list
      debug:
        var: pod_info.resources

Explanation of tasks:

  1. Create namespace: Creates a new namespace demo-space2. Namespaces allow resources to be logically grouped in a Kubernetes cluster, making management and isolation easier.

  2. Deploy nginx pod: Deploys a single pod running an Nginx container in the created namespace. The kubernetes.core.k8s module allows you to define the pod configuration via a simple YAML structure. The container image is nginx and port 80 is exposed.

  3. Get list of pods: Uses the kubernetes.core.k8s_info module to retrieve the list of all running pods in the demo-space2 namespace. The output is saved in a variable.

  4. Display pod list: Displays pod information via the debug module.

Verification

After execution, you can check the namespace and pod created from the terminal with the kubectl commands:

kubectl get namespaces
kubectl get pods -n demo-space2

4.2 Creating a Docker container from AWX

Objective

This playbook uses Ansible to deploy an Nginx web server inside a Docker container and serve a custom HTML page.

Playbook 3-docker_nginx_webapp.yml

---
- name: Deploy nginx container with custom webpage (module-based)
  hosts: 192.168.5.8
  gather_facts: no

  vars:
    html_path: /home/uzair/nginx-content

  tasks:

    - name: Ensure nginx-content directory exists
      file:
        path: "{{ html_path }}"
        state: directory

    - name: Create sample HTML page
      copy:
        dest: "{{ html_path }}/index.html"
        content: |
          <html>
          <head><title>Welcome</title></head>
          <body><h1>Hello from Ansible & NGINX (Docker module)!</h1></body>
          </html>

    - name: Pull nginx image
      community.docker.docker_image:
        name: nginx
        source: pull

    - name: Start nginx container with volume
      community.docker.docker_container:
        name: webapp-nginx
        image: nginx
        state: started
        ports:
          - "8080:80"
        volumes:
          - "{{ html_path }}:/usr/share/nginx/html:ro"

    - name: Set directory permission to be world-readable
      file:
        path: "{{ html_path }}"
        mode: '0755'
        recurse: yes

    - name: Set index.html file permission to be world-readable
      file:
        path: "{{ html_path }}/index.html"
        mode: '0644'

Explanation of tasks:

  1. Ensure nginx-content directory exists: Creates a local directory to store website content.

  2. Create sample HTML page: Places a simple HTML file in this directory. This will be the home page served by the container.

  3. Pull nginx image: Uses the community.docker.docker_image module to download the latest Nginx image from Docker Hub.

  4. Start nginx container with volume: Launches a new container via the community.docker.docker_container module:

  • Maps host port 8080 to container port 80
  • Mount the local HTML directory as a read-only volume (ro) at the default Nginx web root path: /usr/share/nginx/html
  1. Permissions: The last two tasks adjust the file and directory permissions to be readable by everyone — necessary for Nginx to be able to access and serve the files without permissions issues.

Verification

After execution, we can check that the container works via the curl command:

curl http://192.168.5.8:8080

You can also access the server IP address on port 8080 from a browser to view the web page.


4.3 Configuring a network interface on a Juniper vSRX firewall

Objective

This playbook uses Ansible to assign an IP address to the ge-0/0/0 network interface of a Juniper vSRX firewall.

Preparation

Before running the playbook, you can check the current state of the interfaces on the vSRX from your terminal:

show version
show interfaces terse | match ge

These commands allow you to display the device version and the list of physical interfaces starting with ge, to visually confirm the state before changes.

Playbook 3-vSRX_Configure_interface.yml

- name: Configure ge-0/0/0 interface on Juniper vSRX
  hosts: 192.168.5.20
  gather_facts: no

  tasks:
    - name: Get interfaces
      junipernetworks.junos.junos_command:
        commands:
        - show interfaces terse
      register: all_ifaces

    - name: Filter ge-* interfaces
      debug:
        msg: "{{ item }}"
      loop: "{{ all_ifaces.stdout_lines[0] | select('search', 'ge-') | list }}"

    - name: Configure ge-0/0/0
      junipernetworks.junos.junos_config:
        lines:
          - set interfaces ge-0/0/0 unit 0 family inet address 192.168.10.10/24

    - name: Show config for ge-0/0/0
      junipernetworks.junos.junos_command:
        commands:
          - show configuration interfaces ge-0/0/0
      register: config_output

    - name: Display config
      debug:
        var: config_output.stdout_lines

    - name: Verify interfaces
      junipernetworks.junos.junos_command:
        commands:
        - show interfaces terse
      register: all_ifaces

    - name: Filter ge-* interfaces
      debug:
        msg: "{{ item }}"
      loop: "{{ all_ifaces.stdout_lines[0] | select('search', 'ge-') | list }}"

Explanation of tasks:

  1. Get interfaces: Execute the show interfaces terse command to obtain a snapshot of the current state of the interfaces. The result is stored in a variable.

  2. Filter ge- interfaces*: Uses a loop with a select('search', 'ge-') filter to display only interfaces starting with ge-.

  3. Configure ge-0/0/0: The main task — configures interface ge-0/0/0 with IP address 192.168.10.10/24 via module junipernetworks.junos.junos_config. This task applies the configuration directly to the device.

  4. Show config for ge-0/0/0: Shows the interface configuration to immediately confirm that the changes have been applied correctly.

  5. Verify interfaces (repeat): Repeats the verification of interfaces after configuration to ensure that the interface is not only configured, but also appears correctly in the list of active interfaces.

Result

In the command output, we can see that the interface ge-0/0/0 appears with the configured IP address. By checking directly from the vSRX terminal, we confirm that the IP address has been assigned to the interface.


4.4 Calling a public REST API from AWX

Objective

This playbook demonstrates how to use Ansible not only for infrastructure tasks, but also to integrate external APIs and work with their JSON responses.

Playbook 3-call_public_api.yml

---
- name: Get list of cat breeds
  hosts: localhost
  gather_facts: no

  tasks:

    - name: Call the Cat Facts API for breeds
      uri:
        url: https://catfact.ninja/breeds?limit=10
        method: GET
        return_content: yes
        headers:
          Accept: application/json
      register: cat_breeds

    - name: Display first cat breed name
      debug:
        msg: "{{ cat_breeds.json.data }}"

Key points:

  • hosts: localhost — The API call is made from the control node itself.
  • Module uri sends an HTTP GET request to the Cat Facts API.
  • An Accept: application/json header is included to request a JSON response.
  • The response is stored in the cat_breeds variable.
  • The debug module displays the data field of the JSON response, which contains the list of the first 10 cat breeds.

Note: In real-world scenarios, such API responses are typically used in tracking tasks rather than just being displayed in the console. For example, one could use the data to provision resources or make automation decisions.


4.5 Creating an S3 bucket from AWX

Objective

This playbook uses Ansible to create an S3 bucket and upload a file to it using AWS modules.

Playbook 3-s3_create_and_upload.yml

- name: Create S3 bucket and upload a file
  hosts: localhost
  connection: local
  gather_facts: false

  vars:
    bucket_name: uzair-test-bucket-003
    file_path: /tmp/sample.txt
    s3_object_name: sample.txt

  tasks:
    - name: Create an S3 bucket
      amazon.aws.s3_bucket:
        name: "{{ bucket_name }}"
        state: present
        region: us-east-1
    
    - name: Create a sample file to upload
      shell: echo "Hello from Ansible!" > "{{ file_path }}"
      args:
        creates: "{{ file_path }}"

    - name: Upload the file from /tmp/sample.txt to the S3 bucket
      amazon.aws.s3_object:
        bucket: "{{ bucket_name }}"
        object: "{{ s3_object_name }}"
        src: "{{ file_path }}"
        mode: put
        region: us-east-1

Key points:

  • connection: local — AWS tasks run locally on the control node (not on a remote host).
  • amazon.aws.s3_bucket: Creates the S3 bucket. The bucket name is defined in a variable and must be globally unique on AWS.
  • shell with creates: Generates a simple text file to upload. The creates argument ensures that the file is only created if it does not already exist.
  • amazon.aws.s3_object: Upload the file to the S3 bucket. The file is placed with the same name as the original file.

Result

After execution, we can verify in the AWS console that the new bucket has been created and that the file has been correctly uploaded to it.

Note on managing Azure with AWX

If you want to manage Azure with Ansible, additional steps are required. By default, AWX does not include the Python libraries or Azure CLI needed to manage Azure resources. To fix this, you need to create a custom runtime environment containing all the required dependencies.

Step Summary:

  1. Create a requirements.txt file listing all the Python packages needed to interact with Azure (eg: MSAL, azure-mgmt-resource, etc.).
  2. Create a requirements.yml file specifying the Ansible collections needed to manage Azure.
  3. Write a Dockerfile that builds a custom image including these dependencies.
  4. Build the image and push it to a container registry (Docker Hub or private registry).
  5. In AWX, create a new Execution Environment and configure it in the template by referencing the custom image from your registry.

Once configured, any job template using this runtime will download the image and run with all necessary Azure support.

Important note: This same approach can be applied to any other third-party platform not natively supported in AWX. Simply create a custom runtime environment with the required Ansible libraries and collections.


5. Scaling and Optimizing AWX for Performance

This module explores ways to optimize AWX for better performance and scale it to handle larger environments. Topics covered include key Ansible configuration file settings, asynchronous actions, fact caching, and clustering.

5.1 Optimizing Ansible configuration for performance

The ansible.cfg file in AWX

AWX automatically reads the file ansible.cfg from the root of the Git project. This allows you to customize Ansible’s behavior directly in AWX.

Reference ansible.cfg file

[defaults]
forks = 20
pipelining = True
timeout = 30

# Enable fact caching for faster subsequent runs
fact_caching = jsonfile
fact_caching_connection = /runner/facts
gathering = smart
fact_caching_timeout = 3600

Explanation of each parameter:

ParameterValueDescription
forks20Allows running up to 20 parallel tasks on hosts, accelerating execution in multi-host environments
pipeliningTrueReduces SSH overhead by minimizing the number of connections per task — speeds up execution by reducing SSH connection setup time
timeout3030-second connection timeout for non-responsive hosts — prevents delays by quickly switching to slow or unreachable hosts
fact_cachingjsonfileEnables the facts cache with a JSON file backend
fact_caching_connection/runner/factsCache location on disk
gatheringsmartThe gather_facts only runs when needed, instead of every run — speeds up execution by avoiding unnecessary fact gathering
fact_caching_timeout3600Keeps data cached for 1 hour (3600 seconds)

Fact caching demo playbook — 4-optimize_performance.yml

---
- name: Gather facts and validate caching
  hosts: all
  gather_facts: yes

  tasks:
    - name: Show remote hostname
      debug:
        msg: "Remote hostname is {{ ansible_hostname }}"

    - name: Verify fact cache exists in /runner/facts
      command: ls -l /runner/facts
      delegate_to: localhost
      run_once: true

    - name: Show cached file content
      command: cat /runner/facts/{{ inventory_hostname }}
      delegate_to: localhost
      run_once: true
      ignore_errors: yes

Explanation of tasks:

  1. Show remote hostname: Collects and displays the hostname of the managed host (in the demo, it is an Ubuntu server named Ubuntu 24).

  2. Verify fact cache exists: Lists the contents of the /runner/facts directory to confirm that the cache file has been created. The delegate_to:localhost parameter performs this task on the AWX control node. run_once: true ensures that the task runs only once, no matter how many hosts are in the inventory.

  3. Show cached file content: Shows the contents of the cache JSON file to confirm that the facts data is correctly saved.

Performance gain demonstration

  • First run: The facts are not yet hidden → the job takes longer.
  • Second run: AWX reuses the hidden facts instead of recollecting them → the job completes much faster.

This behavior clearly illustrates how caching can significantly improve the speed of playbook execution, especially in environments with many hosts.


5.2 Implementing asynchronous actions and polling

Problem to resolve

By default, Ansible waits for a task to complete before moving on to the next one. For long operations (installations, system updates, etc.), this can unnecessarily block the execution of the entire playbook.

Solution: async and poll parameters

  • async: Tells Ansible that the task is allowed to run for up to X seconds in the background.
  • poll: When set to 0, Ansible does not wait for the task to complete and immediately moves on to the next one.

Playbook 4-Async_poll.yml

---
- name: Run long task asynchronously
  hosts: all
  gather_facts: no

  tasks:
    - name: Simulate a long-running task
      ansible.builtin.command: /bin/sleep 60
      async: 120
      poll: 0

    - name: Display message while background task runs
      ansible.builtin.debug:
        msg: "The sleep command has been started in the background."

Explanation:

  • The first task simulates a long operation by executing the sleep 60 command.
  • async: 120 — The task can run for up to 120 seconds.
  • poll:0 — Ansible does not wait for this task to complete and immediately moves on to the next task.
  • The second task immediately displays a confirmation message while the background task continues to run in parallel.

Demonstration result

When running in the AWX portal, the debug message is displayed instantly, while the background task continues to run. This is how async and poll improve the performance and responsiveness of playbooks in AWX.

Real use cases:

  • Long Software Installations
  • System updates
  • Backup operations
  • Any operation where you don’t want to block the entire playbook for a long running task

5.3 Scaling automation with AWX clusters

The problem of scaling up

In large-scale environments, running many concurrent jobs can saturate a single AWX instance. AWX supports horizontal scaling via clusters, allowing jobs to be run on multiple nodes to distribute load and improve performance.

AWX Distributed Architecture

AWX supports a distributed architecture composed of several types of nodes:

1. Control Plane

This is the main AWX instance — the one used in this course. It orchestrates and distributes jobs to execution nodes.

2. Execution Nodes

These are additional worker nodes running AWX runtime environment containers that handle the actual execution of the playbooks.

Features:

  • Each execution node is assigned to an Instance Group.
  • Instance Groups can be mapped to specific inventories, job templates or organizations.
  • Jobs are dispatched from the control node and run on the execution nodes.

Advantages:

  • Load balancing
  • High availability
  • Job segregation (job segregation according to nodes)
3. Hop Nodes

These are optional nodes that transfer jobs to other execution nodes. Useful for complex networks where the execution nodes are not directly accessible from the control node.

Concrete use cases

  • Assign an execution node for all Windows automation playbooks.
  • Another node for cloud provisioning tasks.
  • This gives granular control over job distribution in large environments.

Configuring clusters in AWX

To add instances in the AWX portal:

  1. Go to Instances and click on Add.
  2. Enter the name, for example Win-Inst-1.
  3. In the Instance Type drop-down, select:
  • Execution Node for an execution node
  • Hop Node for a bounce node
  1. Repeat to add Win-Inst-2.

Creating an Instance Group

  1. Under Instance Groups, create a new group named Win-IG.
  2. Go to the Instances tab and click on Associate to link the two Windows instances created previously to this group.

Association of a template to an Instance Group

  1. Choose a template and click Edit.
  2. In the Instance Groups option, select the Win-IG group.
  3. Click on Save.

Result: Each job associated with this template will run on any execution node that is part of this instance group. In this way, we can segregate and distribute jobs according to the operating system, the environment (production, staging) or the business units.


6. Summary and conclusion

This course covered the entire automation lifecycle with Ansible AWX, from installation to advanced optimization and scaling techniques:

Module Summary

ModuleMain themeDuration
Module 1AWX installation, projects, templates, credentials, inventories, RBAC20m 56s
Module 2Block/Rescue, loops, execution strategies, dynamic inventories9m 26s
Module 3Kubernetes, Docker, Juniper, REST API, AWS S3 integration11m 6s
Module 4Ansible.cfg optimization, async/poll, clustering8m 44s

Key concepts mastered

  • AWX: Open source web interface for Ansible, based on Kubernetes via minikube
  • Projects: Organization of playbooks via Git
  • Job Templates: Execution orchestration (what, where, how)
  • Credentials: Secure management of authentications (Machine, AWS, Azure, GitHub)
  • Inventories: Static (manual hosts) and dynamic (Azure Resource Manager, AWS EC2, etc.)
  • RBAC: Granular access control by user or team
  • Block/Rescue/Always: Structured error management
  • Loops: Processing lists with loop and {{ item }}
  • Execution strategies: linear, free, host_pinned
  • Integration modules: kubernetes.core.k8s, community.docker, junipernetworks.junos, uri, amazon.aws
  • Fact caching: Acceleration of runs thanks to the JSON cache
  • Async/Poll: Running long-running tasks in the background
  • Clustering: Horizontal scaling with execution nodes, hop nodes and instance groups


Search Terms

efficient · scalable · ansible · automation · strategies · infrastructure · ci/cd · devops · awx · playbook · objective · azure · configuring · execution · inventory · rbac · template · concept · dynamic · installing · job · performance · scaling · script

Interested in this course?

Contact us to book it or get a custom training plan for your team.