Ansible

Automation go brrrrrr


0. Prerequisites

Linux -> This is a great site to learn basics -> https://linuxjourney.com/ If you are more adventurers -> https://www.reddit.com/r/archlinux/comments/u5by1d/how_does_a_noob_read_the_arch_wiki/

Or you could also take a look at my LFCS, CompTIA Linux+ Courses.

1. Building Tasks

Using host groups

  1. Purpose of Inventory File
  • Helps target Ansible tasks to specific systems
  • Enables control and monitoring through host groups
  1. Sample Inventory File Structure (/etc/ansible/hosts)
  • Can use IP address ranges (e.g., 10.0.1.1 through 10.0.1.3)
  • Contains different sections:
    • All hosts section (different from default ‘all’ group)
    • Uses Ansible SSH host variables
    • Creates aliases for hosts
    • Specific host groups (web servers, database servers, backup servers)
    • Variable sections (both group-specific and all-system variables)
  1. Host Groups Features
  • Default ‘all’ group automatically includes every host
  • Can create custom host groups
  • Allows targeting specific groups of systems
  • Variables can be set at different levels:
    • Group level
    • All systems level
    • Individual host level
  1. Practical Example
  • Webservers.yml playbook demonstration:
    • Targets web servers host group
    • Runs with root privileges (sudo)
    • Uses apt package manager
    • Performs package uninstallation
    • Updates cache for repository definitions
  • Execution:
    • Uses ansible-playbook command
    • Gathers facts from target systems
    • Performs specified tasks across grouped systems
  1. Key Benefits
  • Simplifies system management
  • Enables easy execution of playbooks against specific groups
  • Provides flexible targeting options
  • Makes system automation more efficient

Using tags

  1. Purpose
  • Tags allow targeting specific tasks within playbooks
  • Provides selective execution of tasks
  1. Implementation
  • Tags can be added at two hierarchy levels:
    • Task level
    • Play level (for multiple plays with multiple tasks)
  1. Sample Playbook (tags.yml)
  • Contains three tasks:
    • Install Apache2 (tag: Apache2)
    • Install NTP (tag: NTP)
    • Start NTP (tag: NTP_start)
  1. Using Tags in Commands
  • Basic syntax: ansible-playbook –tags [tagname] [playbook]
  • Options:
    • –tags: Execute specific tagged tasks
    • –list-tags: View all available tags in playbook
    • –skip-tags: Execute all tasks except specified tagged ones
  1. Example Commands
  • Run NTP installation only:
    • ansible-playbook –tags NTP tags.yml
  • Skip NTP start:
    • ansible-playbook –skip-tags ntp_start tags.yml
  1. Additional Features
  • Cache update optimization example:
    • Apache2 installation includes cache update only if existing info is >1 hour old
  1. Key Points
  • Simple and straightforward method
  • Popular way to control playbook execution
  • One of many methods for controlling Ansible playbooks
  • Provides flexibility in task execution

Running tasks against localhost

  1. Main Concept:
  • Efficiently running tasks against Ansible control node using connection local value
  • Two primary methods demonstrated
  1. First Method (controlnode.yml):
  • Uses hosts: localhost (predefined, no inventory definition needed)
  • Debug feature used to get inventory variable information
  • Verbosity levels:
    • Range: 0-4
    • Level 1 used in example
    • Requires -v flag when running playbook
    • Default level is 0 (no specification needed)
  1. Second Method (connection_controlnode.yml):
  • Uses connection: local
  • Bypasses SSH overhead
  • Communicates directly with local node
  • More performance-efficient than first method
  • Same task execution as first method
  1. Running the Playbooks:
  • Command format: ansible-playbook [filename] -v
  • Both methods executed successfully
  • Second method slightly faster (approximately 1 second vs 2 seconds)
  1. Key Points:
  • Multiple ways exist to run tasks locally (about 4 methods)
  • Connection local approach recommended for maximum performance
  • Useful for advanced troubleshooting
  • Both methods provide detailed facts about localhost
  1. Performance Consideration:
  • Second method (connection: local) is more efficient due to:
    • No SSH overhead
    • Direct local communication
    • Recommended for performance-critical scenarios

Using the command line to control execution

  1. Starting at Specific Tasks:
  • Command line option: –start-at-task
  • Allows skipping initial tasks in playbook
  • Syntax: ansible-playbook playbook.yml –start-at-task ‘Task Name’
  • Task name must match exactly (use single quotes for names with spaces)
  • Useful when initial tasks aren’t required
  1. Interactive Execution:
  • Command line option: –step
  • Allows stepping through playbook tasks one by one
  • Prompts for confirmation before each task
  • User can choose which tasks to execute
  • Useful for:
    • Troubleshooting playbooks
    • Understanding playbook flow
    • Selective task execution
  1. Example Demonstration:
  • Used tags.yml playbook with tasks:
    • Installing Apache2
    • Installing NTP
    • Starting NTP service
  • Demonstrated both –start-at-task and –step options
  • Successfully executed selective tasks
  1. Important Concepts:
  • Idempotency:
    • Can run playbooks multiple times safely
    • No negative impact on already completed tasks
    • Different from traditional scripts
    • Example: Won’t reinstall already installed packages
  • Naming conventions important for task names
  • CLI options provide flexibility in execution
  1. Benefits:
  • Better control over playbook execution
  • Enhanced troubleshooting capabilities
  • Safer execution through idempotency
  • Flexibility in task selection and execution

Specifying variables in the inventory file

  1. Introduction
  • Variables add power to Ansible automation
  • Multiple flexible ways to pass variables using inventory file
  • Inventory file is a critical component
  1. Variable Assignment in Inventory File Different methods: a) Host-specific variables
  • Example: target6 with backup_file variable
  • Direct assignment under host

b) All systems variables

  • Using “all:vars” section
  • Applies to every system
  • Uses built-in automatic host group

c) Host group variables

  • Example: web servers group
  • Variables specific to group members
  1. Using Variables in Playbooks
  • Syntax: Double curly braces {{variable_name}}
  • Example playbook components:
    • Host group targeting
    • File module usage
    • Variable consumption from inventory
    • When statement for validation
  1. Command Line Variables
  • Can pass variables via command line
  • Example: file_state variable
  • Syntax: variable_name=value
  1. Practical Example
  • Created file using playbook
  • Used inventory variables for file location
  • Command line variable for file state
  • Demonstrated cleanup using state=absent
  1. Key Points
  • Variables enhance Ansible flexibility
  • Multiple methods for variable assignment
  • Important for advanced task execution
  • Integration between inventory and playbooks
  • Command line options for variable passing

Creating dynamic inventory files

Dynamic Inventory in Ansible

  • Purpose: Addresses limitations of static inventory files, especially for cloud infrastructure
  • Allows real-time inventory updates through cloud provider APIs

AWS Implementation Example:

  1. Prerequisites:

    • Install Python packages:
      • python3-pip
      • Boto3 (AWS Python package)
  2. Configuration Steps: a. Create Directory Structure:

    • Create /opt/ansible/inventory/

    b. Create AWS EC2 YAML File:

    • File: aws_ec2.yaml
    • Contents:
      • Plugin: aws_ec2
      • AWS access key
      • AWS secret key
      • Group settings (default: organized by tags)
      • Composed section for private IP addressing

    c. Modify Ansible Configuration:

    • Location: /etc/ansible/ansible.cfg
    • Add: enable_plugins = aws_ec2 in inventory section
  3. Testing:

    • Command: ansible-inventory –inventory /opt/ansible/inventory/aws_ec2.yaml –list
    • Returns: Complete inventory of EC2 instances via AWS API

Key Points:

  • Available for all major cloud providers
  • Requires appropriate API access/permissions
  • Enables real-time inventory management
  • Essential for dynamic cloud environments
  • Each cloud provider has specific implementation steps

Note: Similar process can be followed for other cloud providers with their respective configurations and requirements.

Using templates

  1. Introduction to Templates
  • Major aspect of configuration management and automation tools
  • Ansible supports Jinja2 templating language
  • Jinja2 is popular among Python developers
  1. Template File Structure
  • Must have .J2 extension
  • Can be named anything
  • Example template contains:
    • Message from node (using inventory_hostname variable)
    • Today’s message (using web server variable)
    • Variables can be pulled from facts or defined in playbook
  1. Playbook Structure (use_templates.yml)
  • Applied to web servers host group
  • Uses sudo privileges
  • Contains variables section:
    • Defines web_server_message
  • Tasks:
    • Ensures Apache2 service is running
    • Uses template to create index.html
    • Specifies source (Jinja2.J2) and destination (Apache server location)
  1. Implementation
  • Template creates HTML page
  • Variables integrated from:
    • Target node
    • Playbook-defined variables
  • Execution using: ansible-playbook use_templates.yml
  • Creates/updates index.html on target systems
  1. Advanced Capabilities
  • Templates can include:
    • Conditional statements
    • Loops
    • Filters
    • Data transformations
    • Arithmetic calculations
  1. Practical Example Results
  • Successfully created web page showing:
    • Node identification (from inventory_hostname)
    • Custom message (from playbook variable)
    • Additional static content

Note: Templates provide powerful functionality for dynamic content generation and configuration management in Ansible.

Conditional Execution

  1. Purpose & Importance
  • Essential for power and flexibility in automation
  • Moves beyond static decision-making
  • Allows dynamic task execution based on conditions
  1. WHEN Statement
  • Can be applied to tasks
  • Executes tasks if condition returns true
  • Works with:
    • Ansible facts
    • Registered variables
    • Playbook variables
    • Inventory variables
    • Custom facts
  1. Practical Example (cond.yml playbook)
  • Targets all hosts
  • Requires root privileges
  • Contains two main tasks: a) Upgrade in Redhat
    • Condition: ansible_os_family is Redhat
    • Uses yum module b) Upgrade in Debian
    • Condition: ansible_os_family is Debian
    • Uses apt module
  1. Technical Notes
  • No need for double curly brackets in conditions
  • Uses direct Python communication
  • Implements Jinja2 functionality
  1. Execution Example
  • Playbook checks OS family on all hosts
  • Skips Redhat tasks (no Redhat systems)
  • Executes Debian updates where applicable
  • Typical runtime: 60-120 seconds for updates
  1. Future Integration
  • Can be combined with loops (upcoming feature)
  • Enhances automation capabilities
  • Provides additional flexibility in task execution

Benefits:

  • Simplifies system management
  • Enables automated decision-making
  • Reduces repetitive tasks
  • Provides flexible automation solutions

Using loops

  1. Introduction
  • Ansible automates repetitive IT tasks
  • Useful for tasks like changing file permissions or creating user accounts
  • Loops work well with conditionals
  • Two types: simple loops and complex loops
  • Complex loops can iterate over nested lists or retry tasks until conditions are met
  1. Basic Loop Example (loop1.yml)
  • Playbook targets web servers
  • Variables section defines ‘packages’ list (Git, Vim, Ruby)
  • Uses apt module for installation
  • Initially used with_items parameter for iteration
  • Deprecation warning suggests more efficient method
  • Updated method: directly specify packages in name parameter without loop
  1. Dictionary Loops (loop2.yml)
  • Uses hierarchical structure in variables section
  • Contains different website categories (AWS, Microsoft, Google, misc)
  • Each entry has author and author ID
  • Uses with_dictionary syntax
  • Example filters based on author ID condition (as001)
  • Debug module displays results
  1. Benefits
  • Reduces code redundancy
  • Efficient task completion
  • Built-in functionality in modules (like apt)
  • Can include conditions within loops
  • Flexible iteration through different data structures
  1. Important Features
  • Can use verbose options for detailed execution information
  • Supports both simple and complex data structures
  • Deprecation warnings help maintain current best practices
  • Effective for handling repetitive tasks
  • Can combine with conditions for specific matching
  1. Best Practices
  • Use built-in module functionality when available
  • Pay attention to deprecation warnings
  • Choose appropriate loop type based on data structure
  • Consider loops for code efficiency
  • Utilize proper construction for optimal task completion

Testing plays with check mode

  1. Traditional Testing Method
  • Usually tested against actual equipment
  • Considered relatively safe due to idempotency
  • Multiple playbook runs don’t cause damage
  1. Check Feature
  • Alternative to testing on actual equipment
  • Command option: –check
  • Purpose: Sanity checking before production deployment
  1. Check Feature Benefits
  • Verifies Ansible’s ability to:
    • Reach targets
    • Gather facts
    • Execute playbook plays
    • Detect syntax errors
    • Identify problems
  1. Practical Example
  • Used use_templates.yml playbook
  • Features jinja2 compliance
  • Creates web page with message
  1. Error Detection Example
  • Demonstrated with intentional errors:
    • Target three unavailability
    • Moved jinja2.J2 template
  • Shows colorful error display
  • After fixing template location (J3 to J2):
    • Rerun showed only remaining target availability issue
  1. Key Takeaway
  • Use check feature for debugging before production deployment
  • Simple but powerful troubleshooting tool
  • Helps prevent issues in production environment

2. Roles

An overview of roles in Ansible

  1. Roles in Ansible
  • Method for organizing and sharing complex Ansible orchestrations
  • Automatically loads related components based on file structure
  • Components include:
    • handlers
    • tasks
    • files
    • templates
    • variables
    • defaults
  1. Ansible Galaxy
  • Website for hosting and sharing roles
  • Includes client tool (part of default Ansible installation)
  1. Role Directory Structure
  • Default location: /etc/ansible/roles/
  • Can store roles in any location
    • Specify location in playbook
    • Edit Ansible config file for additional locations
  1. Creating Roles using Ansible Galaxy Client
  • Command: ansible-galaxy init [role_name]
  • Must be in target directory when creating role
  • Requires appropriate permissions
  • Creates complete directory structure with:
    • Directory hierarchy
    • Basic YAML files
    • Template files with usage comments
  1. Benefits of Roles
  • Promotes code reusability
  • Improves infrastructure management
  • Enhances Ansible efficiency
  1. Practical Example
  • Created test role “testrole1”
  • Generated standard directory structure
  • Includes main.yml files in each subdirectory
  • Files contain basic templates with usage instructions

Note: Roles should be used where appropriate to maximize Ansible’s capabilities and maintain organized, reusable code.

Using variables in roles

  1. Variable Definition and Reusability
  • Experts create reusable files for common variable definitions
  • Concept aligns with Ansible roles functionality
  1. Role Structure and Defaults
  • Located in Ansible roles directory
  • Contains ‘defaults’ directory with main.yml
  • Default variables can be set in defaults/main.yml
  • Example: variable ‘my_var’ set with default value
  1. Variable Override Options
  • Defaults can be overridden through multiple methods
  • ‘vars’ directory and its main.yml is one way to override
  • CLI provides another override option
  1. Implementation Example
  • roles.yml playbook demonstrates role incorporation
  • Targets web servers with root access
  • Uses test_role_one
  • Tasks directory contains main.yml with debug module
  • Debug module prints ‘my_var’ value
  1. Variable Precedence Demonstration
  • Default value shows when no override exists
  • Variables in vars/main.yml override defaults
  • Example override value: “this is a value from VARs in the role”
  1. CLI Override Capability
  • Highest precedence for variable values
  • Syntax: ansible-playbook roles.yml -e “my_var=‘this is an override’”
  • Requires proper quoting for strings with spaces
  • Single quotes inside double quotes for variable assignment
  1. Key Concepts
  • Easy integration of variables with roles
  • Clear variable precedence hierarchy
  • Multiple override options available
  • Important for flexible and maintainable automation

Using role-based templates

  1. Role Structure and Templates
  • Roles simplify template usage in Ansible playbooks
  • Default location: /etc/ansible/roles
  • Templates should be placed in the templates directory within the role
  • Role structure automatically looks for templates in the template sub-directory
  1. Task Configuration
  • main.yaml in tasks directory contains:
    • index.html creation task
    • Apache2 service start task
    • Jinja template implementation
  1. Template Implementation
  • Jinja template moved to templates directory within role
  • No need to keep playbook and template in same directory
  • Role structure handles template location automatically
  1. Variable Handling
  • Default values can prevent undefined variable errors
  • Default variables set in defaults/main.yaml
  • Example:
    • Variable: webserver_message
    • Default value: “this is a default message”
  1. Playbook Execution
  • Basic execution uses default message
  • Can override default by specifying variable:
    • ansible-playbook roles.yaml -e “webserver_message=‘this is my real message’”
  • Must use single quotes for strings with spaces
  1. Best Practices
  • Use defaults directory to:
    • Provide fallback values
    • Prevent undefined variable errors
    • Set default configurations
  • Templates can be easily integrated with roles
  • Variables can be overridden during playbook execution

This structure provides organized and flexible template management within Ansible roles.

Roles and Ansible Galaxy

  1. Purpose & Introduction
  • Premier web location for Ansible resources
  • Helps with project acceleration and implementation
  • Provides downloadable roles and components
  • Free resource for Ansible users
  1. Website Features
  • Organized by popular categories on homepage
  • Search functionality available
  • Filters for specific content types (roles, plugins, modules)
  • Contains multiple resource types:
    • Roles
    • Plugins
    • Modules
  1. Resource Installation
  • Provides installation syntax for each component
  • Installation process:
    • Uses Ansible Galaxy Command
    • Default installation path: ~/.ansible/roles
    • Creates standard role directory structure:
      • README
      • defaults
      • handlers
      • tasks
  1. Search Example: AWS
  • Can search for AWS-related components
  • Shows various resources:
    • Roles
    • Plugins (example showed 48 modules)
    • Can filter results by resource type
  1. Best Practices
  • Important to read README files before implementation
  • Should review role documentation thoroughly
  • Not recommended to implement without understanding documentation
  1. Benefits
  • Inspires new uses for Ansible
  • Assists with daily tasks
  • Expands beyond basic/familiar uses
  • Cost-effective (free) resource
  • Helps avoid limited tool usage
  1. Installation Example
  • Demonstrated with AWS inspector role
  • Shows practical implementation
  • Creates proper directory structure
  • Maintains standard Ansible organization

Role management

  1. Ansible’s Simplicity
  • Ansible is known for being simple and straightforward
  • Role management follows this simple approach
  1. Configuration Settings
  • Location: /etc/ansible/ansible.cfg
  • Important setting: roles_path in default section
  • Uncommenting roles_path sets default location for roles
  • Default path: /etc/ansible/roles
  1. Role Installation Options a) Using Default Directory:
  • Uncomment roles_path in ansible.cfg
  • Roles automatically install to /etc/ansible/roles
  • Requires sudo permissions due to directory restrictions

b) Custom Installation:

  • Leave roles_path commented
  • Use -p flag with install command
  • Manually specify installation path
  1. Installing Roles from Ansible Galaxy
  • Command: ansible-galaxy install [role-name]
  • Example demonstrated: ansible-network.aws role
  • Sudo required for installation in default directory
  1. Role Removal
  • Use standard Linux remove command
  • Simply delete role files from directory

Best Practices:

  • Plan role installation locations carefully
  • Maintain centralized control over role locations
  • Ensure proper permissions for role management

Note: The approach emphasizes simplicity while maintaining control over role organization and management.

3. Ansible Secrets Management

  1. Introduction to Secrets
  • Secrets refer to passwords, tokens, and sensitive information in Ansible code
  • Clear text secrets in code pose security risks
  • Risk increases when code is shared in public repositories (GitHub, Ansible Galaxy)
  1. Ansible Vault - File Level Encryption
  • Uses AES encryption with shared secrets
  • Basic Commands:
    • ansible-vault create: Creates new encrypted file
    • ansible-vault encrypt: Encrypts existing file
    • ansible-vault edit: Edits encrypted file
  • Running encrypted playbooks:
    • Use –ask-vault-pass flag
    • System prompts for vault password
  1. Variable Level Encryption
  • More granular approach than full file encryption
  • Using encrypt_string:
    • Command: ansible-vault encrypt_string @prompt
    • Encrypts specific strings rather than entire files
    • Generates encrypted syntax for playbook use
  1. Implementation Example
  • Encrypted strings can be placed in variables section of playbook
  • Reference encrypted variables in tasks
  • Running playbooks with encrypted variables:
    • Requires vault password for decryption
    • Use –ask-vault-pass flag
    • Playbook remains unencrypted while variables are secured
  1. Benefits
  • Flexible security options (file or variable level)
  • Easy implementation
  • Maintains code readability while protecting sensitive data
  • Allows selective access to secrets
  • Compatible with version control and sharing

4. Network Management with Ansible

  1. Core Benefits and Strengths
  • Automates repetitive network tasks
  • Separates data model from execution layer
  • Supports multi-vendor networks and devices
  • Uses SSH and HTTPS for secure management
  • Access to vast number of shared modules and roles
  • Ensures strong security during management and automation
  1. IP Address Management a) Playbook Example (ip_address.yml):
  • Runs against localhost with local connection
  • Variables handling:
    • Default gateway in dotted decimal (e.g., 10.10.0.1)
    • Network mask in dotted decimal notation
    • Converts between formats

b) IP Address Manipulation Features:

  • Creates network address with prefix
  • Displays default gateway
  • Uses IP address filter for format conversion
  • Calculates next usable host address
  • Converts between:
    • IP addresses to integers
    • Back to IP format with prefix notation

c) Key Filters:

  • IP address filter: formats network/prefix display
  • IPV4 filter: converts back to IP format
  • Integer conversion capabilities
  1. Network Device Support a) Supported Vendors:
  • Arista
  • Cisco devices with various OS:
    • ASA operating systems
    • IOS
    • IOS XE
    • Nexus OS
  • F5 BIG-IP
  • Junos OS
  • Many other vendors (majority of network vendors supported)
  1. Connection Methods and Challenges a) Traditional Requirements:
  • SSH and Python typically needed on managed nodes
  • Many network devices lack native Python support

b) Alternative Connection Options:

  • Network CLI
  • NETCONF
  • HTTP API
  • Local (legacy/proprietary approach, declining in use)
  • Trend toward Linux/Unix-based systems with Python support
  1. Extended Functionality and Integration a) Enhancement Options:
  • Built-in modules
  • Custom modules
  • Plugins for enhanced internal functionality
  • Roles from Ansible Galaxy

b) Implementation Considerations:

  • Growing vendor support for Python/Linux
  • Increasing integration capabilities
  • Focus on automation and orchestration across IT infrastructure
  • Flexibility in connection methods
  • Strong community support and resources
  1. Best Practices
  • Regular practice with IP address manipulation
  • Utilizing built-in filters and mechanisms
  • Understanding various connection methods
  • Leveraging available modules and roles
  • Keeping up with vendor-specific implementations

5. IDEMPOTENCE in Ansible

  1. Definition and Core Concept:
  • Idempotence means executing commands repeatedly without changing the final result/target state
  • Originally used as a programming term before becoming crucial in IT administration
  • Considered a major selling point and essential feature of Ansible
  • Ensures predictable and consistent system states
  1. Historical Challenges (Pre-Ansible):
  • Traditional automation scripts faced significant issues:
    • Scripts would execute partially (e.g., 12 lines successful, crash on 13th line)
    • Upon rerun, would fail immediately due to previously executed steps
    • No built-in state awareness
    • Couldn’t maintain consistency across multiple executions
    • No way to track what had already been done
  1. Ansible’s Implementation of Idempotence: a) State Management:
    • Uses built-in state examination
    • Tracks current state of resources
    • Compares desired state vs current state
    • Only makes changes when necessary

b) Package Management Example:

  • For uninstallation:
    • Uses state: absent
    • Checks if package exists
    • Removes if present
    • Takes no action if already absent
  • For installation:
    • Verifies if package is installed
    • Installs only if absent
    • Skips if already present
    • Maintains desired state without redundant operations
  1. Handling Non-Idempotent Commands: a) Solution Approaches:
    • Building state information into commands
    • Using registration mechanisms
    • Implementing state verification

b) Practical Example (register_state.yml):

  • Target Configuration:

    • Works with webservers group
    • Uses target variable: /temp/idempotent.txt
  • Task Sequence:

    1. File Creation (Idempotent):

      • Uses copy module
      • Sources from local files directory
      • Creates idempotent.txt on target
      • Skips if file already exists
    2. File Modification (Non-Idempotent):

      • Uses sed command
      • Replaces word “file” with “change”
      • Creates temporary file during process
      • Moves to target destination
    3. State Verification:

      • Uses grep command
      • Searches for word “change”
      • Returns status code:
        • 0 = success (change found)
        • Non-zero = failure (change not found)
      • Provides verification mechanism
  1. Benefits and Importance:
  • Infrastructure Consistency:

    • Maintains desired state across systems
    • Prevents configuration drift
    • Ensures predictable results
  • Operational Advantages:

    • Safe to run playbooks multiple times
    • No risk of damaging existing configurations
    • Reduces human error
    • Simplifies automation processes
  • Management Benefits:

    • Reliable system state management
    • Easier troubleshooting
    • Consistent deployment processes
    • Improved automation reliability
  1. Best Practices:
  • Always verify module idempotence capabilities
  • Implement state checking for non-idempotent commands
  • Use built-in Ansible modules when possible
  • Test playbooks multiple times to ensure idempotent behavior
  • Document any non-idempotent operations