Ansible Automation

Ansible cheatsheet — run playbooks, ad-hoc commands, manage inventory, use vault. ansible-playbook site.yml -i inventory --tags deploy. Every command covered.

9 min read

What it is

Ansible is an open-source automation tool that simplifies IT infrastructure provisioning, configuration management, and application deployment through agentless, human-readable playbooks.

Installation

Linux (Debian/Ubuntu)

sudo apt update
sudo apt install ansible

Linux (RHEL/CentOS/Fedora)

sudo yum install ansible
# or
sudo dnf install ansible

macOS

brew install ansible

Windows

Ansible is designed to manage remote systems, typically Linux. While you can install Ansible on Windows, it’s primarily for managing other machines. For managing Windows hosts, you’ll need to ensure WinRM is configured on the target machines.

Install using pip (requires Python):

python -m pip install ansible

Ensure Python is in your PATH.

Core Concepts

  • Inventory: A list of hosts that Ansible manages. Can be static (a file) or dynamic (generated by cloud providers or scripts).
  • Playbook: A YAML file that describes a series of tasks to be executed on managed hosts.
  • Task: An action to be performed on a managed host, defined by a module.
  • Module: A unit of code that Ansible executes on the target host (e.g., apt, copy, service).
  • Role: A way to organize playbooks, variables, files, templates, and handlers into reusable units.
  • Handler: A task that runs only when notified by another task (e.g., restarting a service only if a configuration file changes).
  • Variable: A named value that can be used in playbooks to customize behavior.
  • Fact: Information gathered by Ansible about the managed hosts (e.g., operating system, IP address).
  • Connection: How Ansible connects to managed hosts (e.g., SSH for Linux, WinRM for Windows).
  • Vault: Ansible’s tool for encrypting sensitive data like passwords and private keys.

Commands / Usage

Running Ad-Hoc Commands

Execute a single task across one or more hosts without writing a playbook.

  • Ping a host:

    ansible webservers -m ping
    

    Check if Ansible can connect to and execute modules on hosts in the webservers group.

  • Get system facts:

    ansible all -m setup
    

    Gather detailed information about all hosts.

  • Install a package:

    ansible webservers -m apt -a "name=nginx state=present" --become
    

    Ensure the nginx package is installed on webservers. --become runs the task with elevated privileges (like sudo).

  • Copy a file:

    ansible dbservers -m copy -a "src=/path/to/local/file.conf dest=/etc/app.conf" --become
    

    Copy a local file to the destination on dbservers.

  • Start a service:

    ansible appservers -m service -a "name=apache2 state=started" --become
    

    Ensure the apache2 service is running on appservers.

  • Run a command:

    ansible monitoring -m command -a "df -h"
    

    Execute the df -h command on hosts in the monitoring group. Use shell module for commands requiring shell features like pipes or redirection.

    ansible webservers -m shell -a "grep 'error' /var/log/nginx/error.log | tail -n 5"
    

Running Playbooks

Execute a defined set of tasks from a YAML playbook file.

  • Run a playbook:

    ansible-playbook deploy_app.yml
    

    Execute the tasks defined in deploy_app.yml.

  • Specify an inventory file:

    ansible-playbook -i hosts.ini deploy_app.yml
    

    Use hosts.ini as the inventory source.

  • Limit execution to specific hosts or groups:

    ansible-playbook -i hosts.ini deploy_app.yml --limit webservers
    

    Run the playbook only on hosts in the webservers group.

  • Ask for variables:

    ansible-playbook -i hosts.ini deploy_app.yml --ask-vars
    

    Prompt the user to enter values for variables defined in the playbook.

  • Check mode (dry run):

    ansible-playbook -i hosts.ini deploy_app.yml --check
    

    Simulate playbook execution without making any changes. Shows what would happen.

  • Become (escalate privileges):

    ansible-playbook -i hosts.ini deploy_app.yml --become
    

    Execute tasks with escalated privileges (e.g., sudo).

  • Specify become method:

    ansible-playbook -i hosts.ini deploy_app.yml --become --become-method sudo
    

    Use sudo as the privilege escalation method. Other common methods include su and pbrun.

  • Specify become user:

    ansible-playbook -i hosts.ini deploy_app.yml --become --become-user root
    

    Execute tasks as the root user.

  • Skip tags:

    ansible-playbook -i hosts.ini deploy_app.yml --skip-tags "configure_nginx"
    

    Run the playbook but skip any tasks tagged with configure_nginx.

  • Start at task:

    ansible-playbook -i hosts.ini deploy_app.yml --start-at-task "Install packages"
    

    Begin playbook execution at the task named "Install packages".

  • List hosts:

    ansible-playbook --list-hosts deploy_app.yml
    

    Show all hosts that the playbook will run on.

  • List tasks:

    ansible-playbook --list-tasks deploy_app.yml
    

    Show all tasks that the playbook will execute.

Managing Inventory

Ansible needs to know which hosts to manage.

  • Static inventory file (INI format):

    [webservers]
    web1.example.com
    web2.example.com
    
    [dbservers]
    db1.example.com ansible_user=postgres
    
    [monitoring:vars]
    ansible_user=monitor
    

    Group hosts, define host-specific variables, and group variables.

  • Static inventory file (YAML format):

    all:
      children:
        webservers:
          hosts:
            web1.example.com:
            web2.example.com:
        dbservers:
          hosts:
            db1.example.com:
              ansible_user: postgres
        monitoring:
          hosts:
            monitor.example.com:
          vars:
            ansible_user: monitor
    

    A more structured way to define inventory.

  • Dynamic inventory: Ansible can use scripts or plugins to generate inventory from cloud providers (AWS, Azure, GCP), virtualization platforms (VMware), or CMDBs. Example using AWS EC2 dynamic inventory script:

    ansible-playbook -i ec2.py deploy_app.yml
    

    Requires ec2.py (or similar) script and necessary AWS credentials.

Managing Variables

Variables make playbooks flexible and reusable.

  • Defining variables in playbooks (using vars keyword):
    ---
    - name: Deploy web application
      hosts: webservers
      vars:
        app_version: "1.2.3"
        deploy_dir: "/var/www/myapp"
      tasks:
        - name: Deploy code
          copy:
    

{% raw %} src: "files/myapp-{{ app_version }}.tar.gz" {% endraw %} {% raw %} dest: "{{ deploy_dir }}/" {% endraw %} ```

  • Defining variables in inventory: (See Static Inventory examples above)

  • Defining variables in group_vars or host_vars: Create directories group_vars/ and host_vars/ in the same directory as your playbook or inventory. group_vars/webservers.yml:

    app_port: 8080
    

    host_vars/web1.example.com.yml:

    app_specific_setting: "enabled"
    
  • Defining variables on the command line:

    ansible-playbook -i hosts.ini deploy_app.yml -e "app_version=1.2.4 deploy_dir=/opt/myapp"
    

    Use the -e flag to pass extra variables.

  • Using facts as variables:

    - name: Configure firewall for app port
      firewalld:
    

{% raw %} port: "{{ app_port }}/tcp" {% endraw %} permanent: true state: enabled when: "'webservers' in group_names" vars: app_port: 8080 # Example: variable defined elsewhere ``` Access gathered facts like ansible_os_family, ansible_default_ipv4.address.

Using Roles

Organize playbook content into reusable units.

  • Directory structure:

    roles/
      webserver/
        tasks/
          main.yml
        handlers/
          main.yml
        templates/
          nginx.conf.j2
        files/
          app.conf
        vars/
          main.yml
        defaults/
          main.yml
        meta/
          main.yml
    
  • Include role in playbook:

    ---
    - name: Deploy web application using roles
      hosts: webservers
      roles:
        - webserver
        - common
    
  • Passing variables to roles:

    ---
    - name: Deploy web application with custom role vars
      hosts: webservers
      roles:
        - role: webserver
          app_port: 8081
          nginx_worker_processes: 4
    

Ansible Vault

Encrypt sensitive data.

  • Create an encrypted file:

    ansible-vault create secrets.yml
    

    You’ll be prompted to set a vault password.

  • Edit an encrypted file:

    ansible-vault edit secrets.yml
    
  • Encrypt an existing file:

    ansible-vault encrypt existing_vars.yml
    
  • Decrypt a file:

    ansible-vault decrypt secrets.yml
    
  • View an encrypted file:

    ansible-vault view secrets.yml
    
  • Run a playbook using vault:

    ansible-playbook deploy_app.yml --ask-vault-pass
    

    Prompt for the vault password.

    ansible-playbook deploy_app.yml --vault-password-file ~/.vault_pass.txt
    

    Use a file containing the vault password.

Configuration

Ansible’s behavior can be customized via ansible.cfg.

  • Default config file locations:

    1. ANSIBLE_CONFIG environment variable
    2. ansible.cfg in the current directory
    3. ~/.ansible.cfg
    4. /etc/ansible/ansible.cfg
  • Common ansible.cfg settings:

    [defaults]
    inventory = /etc/ansible/hosts
    remote_user = ansible_user
    private_key_file = ~/.ssh/id_rsa
    host_key_checking = False
    retry_files_enabled = False
    deprecation_warnings = False
    
    [privilege_escalation]
    become = True
    become_method = sudo
    become_user = root
    

Common Patterns

  • Deploying an application and restarting a service only on change:

    ---
    - name: Deploy application
      hosts: appservers
      become: yes
      tasks:
        - name: Copy application files
          copy:
            src: ./app/
            dest: /opt/myapp/
          notify: restart app service
    
        - name: Ensure app service is started
          service:
            name: myapp
            state: started
    
      handlers:
        - name: restart app service
          service:
            name: myapp
            state: restarted
    
  • Using with_items to loop over a list:

    ---
    - name: Install multiple packages
      hosts: webservers
      become: yes
      tasks:
        - name: Install packages
          apt:
    

{% raw %} name: "{{ item }}" {% endraw %} state: present loop: - nginx - python3 - ufw ```

  • Using with_fileglob to loop over files:
    ---
    - name: Deploy configuration files
      hosts: servers
      become: yes
      tasks:
        - name: Copy all .conf files from local dir
          copy:
    

{% raw %} src: configs/{{ item }} {% endraw %} dest: /etc/myapp/conf.d/ {% raw %} loop: "{{ query('fileglob', 'configs/*.conf') }}" {% endraw %} notify: restart myapp ```

  • Gathering facts and using them in tasks:

    ---
    - name: Configure firewall based on OS
      hosts: all
      become: yes
      tasks:
        - name: Install firewalld on RedHat family
          yum:
            name: firewalld
            state: present
          when: ansible_os_family == "RedHat"
    
        - name: Install ufw on Debian family
          apt:
            name: ufw
            state: present
          when: ansible_os_family == "Debian"
    
  • Using block and rescue/always for error handling:

    ---
    - name: Deploy with error handling
      hosts: webservers
      become: yes
      tasks:
        - block:
            - name: Attempt to deploy
              command: /opt/deploy_script.sh
            - name: Ensure service is running
              service:
                name: webapp
                state: started
          rescue:
            - name: Send notification on failure
              mail:
                to: admin@example.com
    

{% raw %} subject: "Deployment failed on {{ inventory_hostname }}" {% endraw %} body: "Deployment failed. Please investigate." always: - name: Clean up temporary files file: path: /tmp/deploy_artifact state: absent ```

  • Templating configuration files with Jinja2: templates/nginx.conf.j2:
    server {
    

{% raw %} listen {{ nginx_port }}; {% endraw %} {% raw %} server_name {{ server_name }}; {% endraw %} {% raw %} root {{ web_root }}; {% endraw %}

location / { try_files $uri $uri/ =404; } } Playbook task: yaml — - name: Configure Nginx hosts: webservers vars: nginx_port: 80 server_name: example.com web_root: /var/www/html become: yes tasks: - name: Create Nginx config file template: src: templates/nginx.conf.j2 dest: /etc/nginx/sites-available/default notify: reload nginx ```

Gotchas

  • Idempotency: While Ansible modules are designed to be idempotent (running them multiple times has the same effect as running them once), custom scripts or modules might not be. Always strive for idempotent tasks.
  • command vs. shell module: The command module does not process shell features like pipes (|), redirection (>), or variable expansion ($VAR). Use the shell module for these.
  • Privilege Escalation (--become): Tasks often require root privileges. Remember to use --become (or become: yes in playbooks) and potentially --ask-become-pass (or become_ask_pass: yes) if the privilege escalation password is not configured or differs from the login password.
  • SSH Key Permissions: Ensure your SSH private key has strict permissions (e.g., chmod 600 ~/.ssh/id_rsa). Incorrect permissions can prevent Ansible from connecting.
  • Inventory Hostname Resolution: Ansible relies on the hostnames in your inventory to resolve and connect to hosts. Ensure these hostnames are resolvable via DNS or /etc/hosts on the control machine, or use IP addresses.
  • register and changed_when: When a task uses register to save its output, subsequent tasks might be skipped if the registered task is determined to have "not changed" the system state. Use changed_when: true to force a "changed" status if needed, or ignore_errors: yes to allow playbook execution to continue even if a task fails.
  • Module Defaults: Be aware of the default behavior of modules. For example, the copy module by default preserves file permissions and ownership.
  • gather_facts: By default, Ansible gathers facts about managed hosts at the beginning of a playbook run. This can be time-consuming. If your playbook doesn’t need facts, disable it (gather_facts: no) for faster execution.
  • Order of Operations: Playbooks execute tasks sequentially within a play. Handlers are executed only once at the end of the play, after all tasks have run, and only if notified.
  • Windows Remote Management (WinRM): For managing Windows hosts, ensure WinRM is correctly configured on the target machines and that Ansible is set up to use the winrm connection plugin. This often involves specific Python libraries (pywinrm) and certificate configurations.