Ansible Modules
What are Modules?
Modules are the units of work in Ansible. Each module performs a specific task, such as managing packages, services, files, or users. Ansible comes with thousands of modules organized into collections.
Module Categories
- System: user, group, service, systemd, cron
- Package Management: yum, apt, dnf, pip, npm
- Files: copy, template, file, lineinfile, blockinfile
- Database: mysql_db, postgresql_db, mongodb
- Cloud: ec2, azure_rm, gcp_compute
- Network: ios_command, junos_config, nxos_facts
- Windows: win_service, win_package, win_file
Essential Modules
debug - Print Messages
- name: Print variable
debug:
var: ansible_hostname
- name: Print custom message
debug:
msg: "Server {{ inventory_hostname }} is {{ ansible_distribution }}"
copy - Copy Files
- name: Copy file
copy:
src: /local/path/file.txt
dest: /remote/path/file.txt
owner: root
group: root
mode: '0644'
backup: yes
template - Deploy Jinja2 Templates
- name: Deploy configuration
template:
src: config.j2
dest: /etc/app/config.conf
validate: '/usr/sbin/configtest %s'
file - Manage Files and Directories
- name: Create directory
file:
path: /opt/myapp
state: directory
mode: '0755'
- name: Create symlink
file:
src: /opt/app/current
dest: /usr/local/bin/app
state: link
- name: Remove file
file:
path: /tmp/oldfile
state: absent
lineinfile - Manage Lines in Files
- name: Ensure line exists
lineinfile:
path: /etc/hosts
line: '192.168.1.100 myserver.local'
- name: Replace line matching regex
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PermitRootLogin'
line: 'PermitRootLogin no'
- name: Remove line
lineinfile:
path: /etc/config.conf
regexp: '^OldSetting='
state: absent
user - Manage Users
- name: Create user
user:
name: deploy
uid: 1001
group: wheel
groups: docker
append: yes
shell: /bin/bash
create_home: yes
- name: Remove user
user:
name: olduser
state: absent
remove: yes
service / systemd - Manage Services
- name: Start and enable service
service:
name: nginx
state: started
enabled: yes
- name: Restart service with systemd
systemd:
name: postgresql
state: restarted
daemon_reload: yes
yum / apt - Package Management
# RHEL/CentOS
- name: Install packages with yum
yum:
name:
- httpd
- mod_ssl
state: present
# Debian/Ubuntu
- name: Install packages with apt
apt:
name:
- apache2
- libapache2-mod-ssl
state: present
update_cache: yes
command - Execute Commands
- name: Run command
command: /usr/local/bin/script.sh
args:
chdir: /opt/app
creates: /opt/app/initialized
- name: Command with changed_when
command: echo "hello"
register: result
changed_when: false
shell - Execute Shell Commands
- name: Run shell script with pipes
shell: cat /var/log/app.log | grep ERROR | wc -l
register: error_count
- name: Multi-line shell script
shell: |
if [ -f /tmp/lock ]; then
echo "locked"
exit 1
fi
echo "unlocked"
git - Manage Git Repositories
- name: Clone repository
git:
repo: https://github.com/example/app.git
dest: /opt/app
version: main
force: yes
- name: Update repository
git:
repo: https://github.com/example/app.git
dest: /opt/app
update: yes
get_url - Download Files
- name: Download file
get_url:
url: https://example.com/file.tar.gz
dest: /tmp/file.tar.gz
checksum: sha256:abc123...
mode: '0644'
unarchive - Extract Archives
- name: Extract tar.gz
unarchive:
src: /tmp/app.tar.gz
dest: /opt/app
remote_src: yes
- name: Extract from control node
unarchive:
src: files/app.zip
dest: /opt/app
mysql_db / mysql_user - MySQL Management
- name: Create database
mysql_db:
name: myapp_db
state: present
encoding: utf8mb4
collation: utf8mb4_unicode_ci
- name: Create MySQL user
mysql_user:
name: myapp_user
password: "{{ db_password }}"
priv: 'myapp_db.*:ALL'
host: localhost
state: present
cron - Manage Cron Jobs
- name: Create cron job
cron:
name: "Daily backup"
minute: "0"
hour: "2"
job: "/usr/local/bin/backup.sh"
user: root
- name: Remove cron job
cron:
name: "Old backup"
state: absent
authorized_key - Manage SSH Keys
- name: Add SSH public key
authorized_key:
user: deploy
key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
state: present
wait_for - Wait for Condition
- name: Wait for port to be available
wait_for:
host: "{{ inventory_hostname }}"
port: 8080
delay: 10
timeout: 300
state: started
- name: Wait for file to exist
wait_for:
path: /tmp/app.pid
state: present
uri - HTTP Requests
- name: Check API endpoint
uri:
url: https://api.example.com/health
method: GET
return_content: yes
register: health_check
- name: POST data to API
uri:
url: https://api.example.com/data
method: POST
body_format: json
body:
key: value
headers:
Authorization: "Bearer {{ api_token }}"
stat - Get File Status
- name: Check if file exists
stat:
path: /etc/app/config.conf
register: config_file
- name: Use stat result
debug:
msg: "Config exists"
when: config_file.stat.exists
set_fact - Define Variables
- name: Set custom fact
set_fact:
deployment_time: "{{ ansible_date_time.iso8601 }}"
app_version: "2.0.1"
- name: Use fact
debug:
msg: "Deployed version {{ app_version }} at {{ deployment_time }}"
assert - Validate Conditions
- name: Assert conditions
assert:
that:
- ansible_distribution == "Ubuntu"
- ansible_distribution_major_version >= "20"
- ansible_memtotal_mb >= 2048
fail_msg: "System doesn't meet requirements"
success_msg: "System meets all requirements"
Module Return Values
Modules return data that can be captured with register:
- name: Execute command
command: uptime
register: uptime_result
- name: Display return values
debug:
msg:
- "Return code: {{ uptime_result.rc }}"
- "Stdout: {{ uptime_result.stdout }}"
- "Stderr: {{ uptime_result.stderr }}"
- "Changed: {{ uptime_result.changed }}"
Module Documentation
# View module documentation
ansible-doc copy
# List all modules
ansible-doc -l
# Search for modules
ansible-doc -l | grep mysql
Common Module Parameters
Many modules support these common parameters:
state- Desired state (present, absent, started, stopped, etc.)name- Name of the resourcebecome- Escalate privilegeswhen- Conditional executionregister- Save outputnotify- Trigger handlerstags- Tag for selective execution
Advanced Module Categories
Notification Modules
---
- name: Notification examples
hosts: localhost
tasks:
- name: Send Slack notification
slack:
token: "{{ slack_token }}"
channel: '#deployments'
msg: "Deployment completed on {{ inventory_hostname }}"
color: good
icon_url: https://example.com/icon.png
- name: Send email notification
mail:
host: smtp.example.com
port: 587
username: "{{ smtp_user }}"
password: "{{ smtp_pass }}"
to: ops@example.com
subject: "Ansible Deployment Complete"
body: "Deployment finished at {{ ansible_date_time.iso8601 }}"
- name: Send webhook notification
uri:
url: https://hooks.example.com/webhook
method: POST
body_format: json
body:
event: deployment
status: success
timestamp: "{{ ansible_date_time.epoch }}"
- name: Create ServiceNow ticket
snow_record:
username: "{{ snow_user }}"
password: "{{ snow_pass }}"
instance: dev12345
state: present
table: incident
data:
short_description: "Automated deployment notification"
urgency: 2
impact: 2
Cloud Modules - AWS
---
- name: AWS resource management
hosts: localhost
gather_facts: no
tasks:
- name: Launch EC2 instance
ec2_instance:
name: web-server-01
key_name: my-keypair
instance_type: t3.medium
image_id: ami-0c55b159cbfafe1f0
region: us-east-1
vpc_subnet_id: subnet-12345
security_groups:
- sg-web
tags:
Environment: production
Role: webserver
wait: yes
register: ec2
- name: Create S3 bucket
s3_bucket:
name: my-app-bucket-{{ ansible_date_time.epoch }}
region: us-east-1
versioning: yes
encryption: AES256
tags:
Environment: production
- name: Create RDS database
rds_instance:
db_instance_identifier: myapp-db
engine: postgres
engine_version: "13.7"
instance_class: db.t3.medium
allocated_storage: 100
master_username: dbadmin
master_user_password: "{{ vault_db_password }}"
vpc_security_group_ids:
- sg-database
multi_az: yes
backup_retention_period: 7
region: us-east-1
- name: Create ELB
elb_application_lb:
name: myapp-alb
subnets:
- subnet-public-1
- subnet-public-2
security_groups:
- sg-alb
scheme: internet-facing
listeners:
- Protocol: HTTPS
Port: 443
DefaultActions:
- Type: forward
TargetGroupArn: "{{ target_group_arn }}"
region: us-east-1
- name: Create CloudWatch alarm
cloudwatch_metric_alarm:
name: high-cpu-alarm
metric: CPUUtilization
namespace: AWS/EC2
statistic: Average
comparison: '>='
threshold: 80.0
period: 300
evaluation_periods: 2
dimensions:
InstanceId: "{{ ec2.instance_ids[0] }}"
alarm_actions:
- "{{ sns_topic_arn }}"
region: us-east-1
Cloud Modules - Azure
---
- name: Azure resource management
hosts: localhost
gather_facts: no
tasks:
- name: Create resource group
azure_rm_resourcegroup:
name: production-rg
location: eastus
tags:
Environment: production
- name: Create virtual network
azure_rm_virtualnetwork:
resource_group: production-rg
name: prod-vnet
address_prefixes_cidr:
- 10.0.0.0/16
- name: Create subnet
azure_rm_subnet:
resource_group: production-rg
virtual_network_name: prod-vnet
name: web-subnet
address_prefix_cidr: 10.0.1.0/24
- name: Create public IP
azure_rm_publicipaddress:
resource_group: production-rg
name: web-public-ip
allocation_method: Static
- name: Create VM
azure_rm_virtualmachine:
resource_group: production-rg
name: web-vm-01
vm_size: Standard_DS2_v2
admin_username: azureuser
ssh_password_enabled: false
ssh_public_keys:
- path: /home/azureuser/.ssh/authorized_keys
key_data: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
image:
offer: UbuntuServer
publisher: Canonical
sku: 18.04-LTS
version: latest
tags:
Environment: production
Role: webserver
- name: Create Azure SQL Database
azure_rm_sqldatabase:
resource_group: production-rg
server_name: myapp-sql-server
name: myapp-db
sku:
name: S1
tier: Standard
Container Modules
---
- name: Container management
hosts: docker_hosts
tasks:
- name: Pull Docker image
docker_image:
name: nginx
tag: latest
source: pull
- name: Run Docker container
docker_container:
name: web-app
image: myapp:latest
state: started
restart_policy: unless-stopped
ports:
- "8080:80"
volumes:
- /data/app:/app/data
env:
DATABASE_HOST: db.example.com
DATABASE_PORT: "5432"
networks:
- name: app-network
labels:
environment: production
service: web
- name: Build Docker image
docker_image:
name: myapp
tag: "{{ app_version }}"
build:
path: /opt/app
dockerfile: Dockerfile
pull: yes
source: build
- name: Create Docker network
docker_network:
name: app-network
driver: bridge
ipam_config:
- subnet: 172.20.0.0/16
gateway: 172.20.0.1
- name: Manage Docker Compose
docker_compose:
project_src: /opt/myapp
state: present
recreate: smart
remove_orphans: yes
Monitoring & Logging Modules
---
- name: Monitoring and logging setup
hosts: all
tasks:
- name: Install Datadog agent
datadog_monitor:
api_key: "{{ datadog_api_key }}"
app_key: "{{ datadog_app_key }}"
type: metric alert
query: "avg(last_5m):avg:system.cpu.user{host:{{ inventory_hostname }}} > 90"
name: "High CPU on {{ inventory_hostname }}"
message: "CPU usage is above 90% @pagerduty"
- name: Configure Prometheus node exporter
systemd:
name: node_exporter
enabled: yes
state: started
daemon_reload: yes
- name: Send log to Splunk
splunk_data:
host: splunk.example.com
port: 8088
token: "{{ splunk_token }}"
event:
event: "Deployment completed"
host: "{{ inventory_hostname }}"
source: ansible
sourcetype: automation
- name: Create New Relic deployment marker
newrelic_deployment:
token: "{{ newrelic_api_key }}"
app_name: MyApplication
user: ansible
revision: "{{ git_commit_hash }}"
changelog: "{{ git_commit_message }}"
Security & Secrets Modules
---
- name: Security and secrets management
hosts: all
tasks:
- name: Fetch secret from HashiCorp Vault
set_fact:
db_password: "{{ lookup('hashi_vault', 'secret=secret/data/database:password') }}"
api_key: "{{ lookup('hashi_vault', 'secret=secret/data/api:key') }}"
- name: Fetch from AWS Secrets Manager
set_fact:
app_secret: "{{ lookup('aws_secret', 'production/app/credentials', region='us-east-1') }}"
- name: Fetch from Azure Key Vault
azure_rm_keyvaultsecret_info:
vault_uri: https://myvault.vault.azure.net
name: database-password
register: azure_secret
- name: Create SSL certificate
openssl_certificate:
path: /etc/ssl/certs/{{ ansible_fqdn }}.crt
privatekey_path: /etc/ssl/private/{{ ansible_fqdn }}.key
csr_path: /etc/ssl/csr/{{ ansible_fqdn }}.csr
provider: selfsigned
- name: Manage firewalld
firewalld:
service: https
permanent: yes
state: enabled
immediate: yes
- name: Configure SELinux
selinux:
policy: targeted
state: enforcing
- name: Set SELinux file context
sefcontext:
target: '/opt/app(/.*)?'
setype: httpd_sys_content_t
state: present
- name: Apply SELinux context
command: restorecon -Rv /opt/app
Module Collections
Ansible modules are organized into collections. Collections are distribution packages containing playbooks, roles, modules, and plugins.
Installing Collections
# Install from Ansible Galaxy
ansible-galaxy collection install amazon.aws
# Install specific version
ansible-galaxy collection install community.general:3.8.0
# Install from requirements file
ansible-galaxy collection install -r requirements.yml
# requirements.yml
---
collections:
- name: community.general
version: ">=3.0.0"
- name: ansible.posix
- name: amazon.aws
- name: azure.azcollection
- name: google.cloud
Using Collection Modules
---
- name: Use collection modules
hosts: localhost
tasks:
# Fully Qualified Collection Name (FQCN)
- name: Use AWS module with FQCN
amazon.aws.ec2_instance:
name: my-instance
instance_type: t3.medium
# With collections keyword
- name: Simplified usage
hosts: localhost
collections:
- amazon.aws
- community.general
tasks:
- name: Use module without FQCN
ec2_instance:
name: my-instance
instance_type: t3.medium
Popular Collections
- ansible.builtin: Core Ansible modules (file, copy, template, etc.)
- community.general: General-purpose modules
- ansible.posix: POSIX systems modules
- amazon.aws: AWS cloud modules
- azure.azcollection: Azure cloud modules
- google.cloud: Google Cloud modules
- kubernetes.core: Kubernetes management
- community.docker: Docker container management
- community.postgresql: PostgreSQL database
- community.mysql: MySQL/MariaDB database
Custom Module Development
Simple Custom Module (Python)
#!/usr/bin/python
# library/custom_file_info.py
from ansible.module_utils.basic import AnsibleModule
import os
def main():
module = AnsibleModule(
argument_spec=dict(
path=dict(type='str', required=True),
),
supports_check_mode=True
)
path = module.params['path']
if not os.path.exists(path):
module.fail_json(msg=f"File {path} does not exist")
stat_info = os.stat(path)
result = {
'changed': False,
'path': path,
'size': stat_info.st_size,
'mode': oct(stat_info.st_mode)[-3:],
'is_file': os.path.isfile(path),
'is_directory': os.path.isdir(path),
}
module.exit_json(**result)
if __name__ == '__main__':
main()
Using Custom Module
---
- name: Use custom module
hosts: localhost
tasks:
- name: Get custom file info
custom_file_info:
path: /etc/hosts
register: file_info
- name: Display file info
debug:
msg: "File size: {{ file_info.size }} bytes, mode: {{ file_info.mode }}"
Advanced Custom Module with Change Detection
#!/usr/bin/python
# library/custom_config.py
from ansible.module_utils.basic import AnsibleModule
import json
def main():
module = AnsibleModule(
argument_spec=dict(
path=dict(type='str', required=True),
key=dict(type='str', required=True),
value=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['present', 'absent']),
),
supports_check_mode=True
)
path = module.params['path']
key = module.params['key']
value = module.params['value']
state = module.params['state']
# Read current config
try:
with open(path, 'r') as f:
config = json.load(f)
except FileNotFoundError:
config = {}
except json.JSONDecodeError:
module.fail_json(msg=f"Invalid JSON in {path}")
changed = False
original_value = config.get(key)
if state == 'present':
if config.get(key) != value:
config[key] = value
changed = True
elif state == 'absent':
if key in config:
del config[key]
changed = True
# Check mode - don't make changes
if module.check_mode:
module.exit_json(changed=changed, original_value=original_value)
# Apply changes
if changed:
try:
with open(path, 'w') as f:
json.dump(config, f, indent=2)
except IOError as e:
module.fail_json(msg=f"Failed to write config: {str(e)}")
module.exit_json(
changed=changed,
key=key,
value=value if state == 'present' else None,
original_value=original_value,
config=config
)
if __name__ == '__main__':
main()
Advanced Module Patterns
Module Chaining with Loops
---
- name: Complex module chaining
hosts: webservers
vars:
applications:
- name: app1
port: 8080
replicas: 3
- name: app2
port: 8081
replicas: 5
tasks:
# Create users for each app
- name: Create application users
user:
name: "{{ item.name }}"
system: yes
create_home: yes
home: "/opt/{{ item.name }}"
loop: "{{ applications }}"
# Create directories
- name: Create app directories
file:
path: "{{ item.1 }}"
state: directory
owner: "{{ item.0.name }}"
mode: '0755'
loop: "{{ applications | subelements('paths', skip_missing=True) | default([]) }}"
vars:
paths:
- /opt/{{ item.0.name }}/data
- /opt/{{ item.0.name }}/logs
- /opt/{{ item.0.name }}/config
# Deploy configurations
- name: Deploy app configs
template:
src: "{{ item.name }}/config.j2"
dest: "/opt/{{ item.name }}/config/app.conf"
owner: "{{ item.name }}"
mode: '0600'
validate: 'python3 -c "import json; json.load(open(\"%s\"))"'
loop: "{{ applications }}"
notify: restart applications
# Create systemd services
- name: Create systemd service files
template:
src: app.service.j2
dest: "/etc/systemd/system/{{ item.name }}.service"
loop: "{{ applications }}"
notify:
- reload systemd
- restart applications
# Configure firewall rules
- name: Allow application ports
firewalld:
port: "{{ item.port }}/tcp"
permanent: yes
state: enabled
immediate: yes
loop: "{{ applications }}"
handlers:
- name: reload systemd
systemd:
daemon_reload: yes
- name: restart applications
systemd:
name: "{{ item.name }}"
state: restarted
enabled: yes
loop: "{{ applications }}"
Conditional Module Execution
---
- name: Conditional module execution
hosts: all
tasks:
- name: Gather package facts
package_facts:
manager: auto
- name: Install package only if not present
package:
name: nginx
state: present
when: "'nginx' not in ansible_facts.packages"
- name: Update only if package exists
package:
name: nginx
state: latest
when: "'nginx' in ansible_facts.packages"
- name: OS-specific package installation
package:
name: "{{ item }}"
state: present
loop: "{{ packages[ansible_os_family] }}"
vars:
packages:
Debian:
- apache2
- libapache2-mod-php
RedHat:
- httpd
- php
- name: Version-specific configuration
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PermitRootLogin'
line: 'PermitRootLogin prohibit-password'
when:
- ansible_distribution == 'Ubuntu'
- ansible_distribution_major_version | int >= 20
Error Handling and Retry Logic
---
- name: Advanced error handling
hosts: all
tasks:
- name: Download with retry
get_url:
url: https://example.com/package.tar.gz
dest: /tmp/package.tar.gz
checksum: sha256:abc123...
register: download_result
retries: 5
delay: 10
until: download_result is succeeded
- name: Service with failure handling
block:
- name: Stop service
service:
name: myapp
state: stopped
- name: Deploy new version
copy:
src: /tmp/newapp
dest: /opt/app/
backup: yes
register: deploy
- name: Start service
service:
name: myapp
state: started
- name: Health check
uri:
url: http://localhost:8080/health
status_code: 200
retries: 5
delay: 5
register: health
rescue:
- name: Rollback deployment
copy:
src: "{{ deploy.backup_file }}"
dest: /opt/app/
remote_src: yes
when: deploy.backup_file is defined
- name: Restart with old version
service:
name: myapp
state: restarted
- name: Send failure alert
mail:
subject: "Deployment failed on {{ inventory_hostname }}"
body: "Deployment failed, rolled back to previous version"
always:
- name: Clean temporary files
file:
path: /tmp/newapp
state: absent
- name: Log deployment attempt
lineinfile:
path: /var/log/deployments.log
line: "{{ ansible_date_time.iso8601 }} - Deployment attempt: {{ 'success' if health is succeeded else 'failed' }}"
create: yes
Parallel Execution with Async
---
- name: Parallel module execution
hosts: webservers
tasks:
# Start all updates in parallel
- name: Start package updates (async)
yum:
name: '*'
state: latest
security: yes
async: 1800 # 30 minutes max
poll: 0
register: update_jobs
# Do other work while updates run
- name: Check application status
uri:
url: http://{{ inventory_hostname }}:8080/health
register: health_check
- name: Backup configuration files
copy:
src: /etc/myapp/
dest: /backup/myapp-{{ ansible_date_time.date }}/
remote_src: yes
# Check async task status
- name: Wait for package updates to complete
async_status:
jid: "{{ update_jobs.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 180
delay: 10
- name: Verify update success
assert:
that:
- job_result.finished
- job_result.failed == false
fail_msg: "Package updates failed"
Module Performance Optimization
Batch Operations
---
- name: Efficient module usage
hosts: all
tasks:
# BAD: Multiple task calls
- name: Install package 1
yum:
name: package1
state: present
- name: Install package 2
yum:
name: package2
state: present
# GOOD: Single task with list
- name: Install multiple packages efficiently
yum:
name:
- package1
- package2
- package3
- package4
state: present
# GOOD: Use with_items for complex operations
- name: Create multiple users efficiently
user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
groups: "{{ item.groups }}"
loop:
- { name: user1, uid: 1001, groups: wheel }
- { name: user2, uid: 1002, groups: developers }
- { name: user3, uid: 1003, groups: operators }
Minimizing Fact Gathering
---
- name: Optimize fact gathering
hosts: all
gather_facts: no
tasks:
# Only gather specific facts
- name: Gather minimal facts
setup:
filter:
- ansible_distribution
- ansible_distribution_major_version
- ansible_default_ipv4
- name: Use gathered facts
debug:
msg: "Running on {{ ansible_distribution }} {{ ansible_distribution_major_version }}"
Module Testing
Testing Custom Modules
# test_custom_module.yml
---
- name: Test custom module
hosts: localhost
gather_facts: no
tasks:
- name: Test module with valid input
custom_config:
path: /tmp/test_config.json
key: test_key
value: test_value
state: present
register: result
- name: Verify result
assert:
that:
- result.changed == true
- result.key == 'test_key'
- result.value == 'test_value'
- name: Test idempotency
custom_config:
path: /tmp/test_config.json
key: test_key
value: test_value
state: present
register: result
- name: Verify no change on second run
assert:
that:
- result.changed == false
- name: Test removal
custom_config:
path: /tmp/test_config.json
key: test_key
value: test_value
state: absent
register: result
- name: Verify removal
assert:
that:
- result.changed == true
- name: Cleanup
file:
path: /tmp/test_config.json
state: absent
Module Debugging
Debug Module Execution
# Run with maximum verbosity
ansible-playbook playbook.yml -vvvv
# Debug specific module
ANSIBLE_KEEP_REMOTE_FILES=1 ansible-playbook playbook.yml
# Check what module would do (without executing)
ansible-playbook playbook.yml --check
# See differences
ansible-playbook playbook.yml --check --diff
# Step through tasks
ansible-playbook playbook.yml --step
# List all tasks
ansible-playbook playbook.yml --list-tasks
# Start at specific task
ansible-playbook playbook.yml --start-at-task="Install packages"
Best Practices
- Prefer dedicated modules: Use specific modules over
commandorshell - Use FQCN: Always use Fully Qualified Collection Names for clarity
- Check return values: Use return values for conditional logic
- Implement idempotency: Use
changed_whenandcreatesparameters - Handle errors gracefully: Use
block/rescue/alwaysfor error handling - Validate changes: Use
validateparameter when available - Use check mode: Test with
--checkbefore running - Batch operations: Install multiple packages in one task
- Use async for long tasks: Don't block on long-running operations
- Cache facts: Enable fact caching for better performance
- Document custom modules: Include DOCUMENTATION, EXAMPLES, RETURN
- Test custom modules: Write tests for custom module behavior
- Keep modules simple: One module should do one thing well
- Use module defaults: Set defaults in ansible.cfg for repeated parameters
- Leverage collections: Organize related modules into collections
Next Steps
- Explore Roles for organizing modules
- Learn about Playbooks for orchestrating modules
- Try module examples in the Playground
- Read Advanced Topics for custom module development