Module #

If the playbook is like a scenario plan and the inventory is the list of target actors, then the module is the real tool that does the physical work on the ground. Modules are the smallest execution unit in the Ansible ecosystem. Every task line you write in a playbook essentially calls one specific module with particular parameters. Deeply understanding how modules work, the response statuses they produce, and choosing the right module is the main foundation for building safe and efficient infrastructure automation.

Modules as the Primary Execution Unit #

Modules are self-contained scripts designed specifically to perform very specific system administration functions — such as installing packages, modifying configuration files, managing users, or interacting with cloud provider APIs.

Although Ansible is written in Python, Ansible modules aren’t limited to the Python language. You can write custom modules in any programming language (like Bash or Go) as long as the module can accept input parameters in JSON format and return execution result status in standard JSON format too.

When a playbook runs, Ansible compiles your task parameters into the relevant module, sends the script to the target server, runs it locally on the target side, then immediately deletes it after execution finishes to keep the target system clean.


The Idempotency Mechanism and Response Statuses #

Every standard Ansible module is strictly designed to follow the idempotency principle. Before modifying the system, the module performs an initial inspection of the actual conditions on the Managed Node at that moment. The module only takes corrective action if the system’s actual state doesn’t match the desired state you declared in the playbook.

Here’s a visualization of an Ansible module’s decision-making flow for achieving idempotency:

flowchart TD
    CN["Control Node (Send Module Parameters via JSON)"] -->|1. Transfer & Run Script| MN["Managed Node (Local Execution)"]
    MN --> Check{"2. Inspect Actual State\n(Is Current State == Desired State?)"}
    Check -- "Yes" --> NoChange["3. Skip Modification\n(ok / changed: false)"]
    Check -- "No" --> ApplyChange["3. Apply System Modification\n(changed / changed: true)"]
    NoChange --> Response["4. Return JSON Output\n(status, stdout, rc)"]
    ApplyChange --> Response
    Response -->|5. Capture JSON Response| CN

After finishing a task, the module returns a JSON data structure to the Control Node. The most crucial parameter in that JSON response is the change status:

  • ok (Green State): The target server’s state already matches your declaration, so the module doesn’t make any modifications. The JSON response contains "changed": false.
  • changed (Yellow State): The target server’s state doesn’t match yet, and the module successfully applied changes to align it. The JSON response contains "changed": true.
  • failed (Red State): An execution failure occurred (for example due to permission errors, network disconnects, or invalid parameters). The JSON response contains "failed": true along with an error message in the "msg" parameter.

Here’s an example JSON response from the file module confirming a directory already exists:

{
  "path": "/var/www/html",
  "changed": false,
  "state": "directory",
  "owner": "www-data",
  "group": "www-data",
  "mode": "0755"
}

Module Classification by Function #

Ansible provides thousands of ready-to-use modules grouped by system work area. Understanding this classification helps you pick the most efficient tool for a given task.

1. Package Management #

Used to manage software lifecycles (installation, updates, removal) declaratively across various operating systems:

# apt module — For the Debian/Ubuntu family specifically
- name: Ensure the latest version of Git is installed
  apt:
    name: git
    state: latest
    update_cache: true

# dnf module — For modern RHEL/CentOS/Rocky Linux
- name: Ensure MariaDB Client is installed
  dnf:
    name: mariadb
    state: present

2. Files & Directories Management #

Used to copy files, render dynamic templates, or set directory permissions:

# copy module — Copies static files from Control Node to Managed Node
- name: Copy the static firewall configuration file
  copy:
    src: files/iptables.rules
    dest: /etc/iptables.rules
    owner: root
    group: root
    mode: '0600'

# template module — Renders dynamic configuration files using the Jinja2 engine
- name: Render the Nginx virtual host configuration file
  template:
    src: templates/vhost.conf.j2
    dest: /etc/nginx/sites-available/myapp.conf

# file module — Manages directories, symlinks, and file access permissions
- name: Create the application logs directory with strict permissions
  file:
    path: /var/log/myapp
    state: directory
    owner: deploy
    group: deploy
    mode: '0750'

3. Service Management #

Used to control the running status of background daemon system services:

# systemd module — Manages standard modern Linux systemd services
- name: Ensure the SSH service is running and enabled at boot
  systemd:
    name: ssh
    state: started
    enabled: true

Comparison of Command Execution Modules #

Sometimes you need to run pure shell commands because no standard Ansible module supports your custom application. For these needs, Ansible provides four command execution modules: command, shell, raw, and script.

You must be very careful when using these modules because they’re not idempotent by default.

ModuleHow It WorksBuilt-in IdempotencyBest Use Case
commandExecutes binary commands directly on the target without going through a shell.✗ NoRunning local binary applications (e.g. openssl). Safer because it’s not affected by local shell variables.
shellExecutes commands through a shell interpreter (like /bin/sh or /bin/bash).✗ NoExecuting commands that need pipe operators (`
rawSends the command string directly over SSH without wrapping it into a Python module.✗ NoBootstrapping the first Python installation on minimal servers or network routers.
scriptCopies your local script to the target, executes it, then deletes it again.✗ NoRunning complex database migration scripts you already have without rewriting them into YAML.

Idempotency Mitigation for Command Execution Modules #

If you’re forced to use the command or shell modules, you can enforce idempotency manually using guard parameters like creates, removes, or control the reporting status with changed_when.

# Example Idempotency Mitigation for Shell/Command Modules
- name: Download the external installer script if the installer file doesn't exist yet
  get_url:
    url: https://example.com/install.sh
    dest: /tmp/install.sh

- name: Run the installer only if the resulting binary file hasn't been created yet
  command: bash /tmp/install.sh
  creates: /usr/local/bin/myapp # This task is skipped if /usr/local/bin/myapp already exists

- name: Check the custom application configuration without reporting a false 'changed' status
  command: myapp --check-syntax
  register: check_result
  changed_when: false # Tells Ansible to always report OK/SUCCESS status (not CHANGED)

Using register and Conditionals (when) #

You can capture the output of a module execution into a memory variable using the register parameter. You can then use that captured variable to determine the execution logic of subsequent tasks using the when conditional.

Here’s an example of a dynamic playbook scenario: We check whether a configuration file exists using the stat module, save the result, then only create the new configuration file if it doesn’t already exist on the system.

---
- name: Dynamic file check and configuration scenario
  hosts: all
  become: true
  vars:
    config_path: /etc/myapp/config.yml

  tasks:
    - name: Check detailed information about the configuration file
      stat:
        path: "{{ config_path }}"
      register: file_status # The stat module result is saved to the file_status variable

    - name: Display the check variable contents for debugging
      debug:
        var: file_status.stat.exists

    - name: Copy the default configuration if the file is confirmed missing
      copy:
        src: files/default-config.yml
        dest: "{{ config_path }}"
        owner: root
        group: root
        mode: '0644'
      when: not file_status.stat.exists # Only runs if the file doesn't exist

Ansible Collections and Community Modules #

In earlier Ansible versions, all modules were bundled into one core package. As the cloud industry grew, the Ansible core package became too large and slow at distributing module updates.

Starting with version 2.10, Ansible introduced the Ansible Collections concept. Ansible Core is now kept very lightweight, containing only base operating system modules. All cloud provider modules (like AWS, GCP, OpenStack) and network device modules are separated into external repositories managed modularly by the community or related vendors.

You can easily search and install these external module collections through the Ansible Galaxy portal:

# Install the latest AWS module collection from Ansible Galaxy
ansible-galaxy collection install amazon.aws

Inside a playbook, modules from external collections are called using their full namespace name (Fully Qualified Collection Name - FQCN):

- name: Create a new EC2 instance using a custom AWS module
  amazon.aws.ec2_instance:
    name: web-app-prod
    instance_type: t3.medium

Summary #

  • Modules are isolated execution units — Ansible calls a specific module with JSON parameters to perform real tasks on managed nodes.
  • Built-in Idempotency — Most modules check the target system state first and only make changes if the actual state doesn’t match the desired state.
  • Three Main Response Statuses — Modules return ok (state already matches), changed (successfully modified), or failed (execution failure).
  • Use Declarative Modules — Avoid the command/shell modules unless necessary; use built-in modules whose idempotency is automatically guaranteed.
  • Register & When — You can capture module execution output into registered memory variables (register) to control playbook flow conditionally (when).

← Previous: Inventory Next: Installation →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact