ansible

Updated: November 24, 2025

Ansible is a configuration tool, that can setup and administer multiple machines on a network.

Note: Ansible only needs to be installed on a controller node.


Table of Contents


SSH KEYS

For ansible to work ssh access is required for the machines you want to config and admin.

How to make a key


INSTALL

Ansible needs to be installed only on the control machine (the machine from which you run Ansible commands). Managed nodes require no Ansible installation, just SSH access and Python (version 2.6+ or 3.5+).

Ansible versions: As of 2024, Ansible Core (formerly Ansible) is at version 2.16.x. For enterprise features, consider AWX (open-source Tower) or Red Hat Ansible Automation Platform.

Ubuntu/Debian
sudo apt update
sudo apt install ansible

Note: Ubuntu packages may lag behind; for latest version, use pip.

macOS
brew install ansible
pip install ansible
# Or for user-specific install
pip install --user ansible

Requires Python 3.8+ for Ansible 2.10+.

Other platforms

For Windows (as control machine), use WSL or pip in a virtual environment. For more options, see official installation guide.

After installation, verify with ansible --version.


Create User

Create an ansible user for carrying out ansible playbooks. This user helps to distinguish if it was ansible that did something in the logs from all other users – even root.

useradd -m ansible                # create ansible user with home directory
usermod -s /bin/bash ansible      # set users shell
su - ansible                      # switch to ansible user

Edit visudo and search for with /wheel First set editor for visudo

EDITOR=vim  visudo
## Samthing without a password
# %wheel    ALL=(ALL)     NOPASSWD: ALL
ansible     ALL=(ALL)     NOPASSWD: ALL

CONFIG

Ansible configuration is managed via ansible.cfg files. The default is /etc/ansible/ansible.cfg, but you can create project-specific configs to avoid affecting global settings.

Configuration precedence (highest to lowest):

  1. ANSIBLE_CONFIG environment variable pointing to a file
  2. ./ansible.cfg (current directory)
  3. ~/.ansible.cfg (home directory)
  4. /etc/ansible/ansible.cfg

Copy the default config and customize it:

cp /etc/ansible/ansible.cfg ./ansible.cfg

Common configuration options:

[defaults]
inventory = ./inventory.ini          # Path to inventory file
remote_user = ansible                # Default SSH user
private_key_file = ~/.ssh/id_rsa     # SSH private key
forks = 5                           # Number of parallel processes
nocows = 1                          # Disable cowsay (set to 0 to enable)
retry_files_enabled = True          # Create .retry files on failure
retry_files_save_path = ~/.ansible-retry  # Where to save retry files
collections_paths = ~/.ansible/collections  # Path for collections
roles_path = ~/.ansible/roles        # Path for roles
host_key_checking = False           # Disable SSH host key checking (for dev)
deprecation_warnings = False        # Hide deprecation warnings

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ControlPath=/tmp/ansible-ssh-%h-%p-%r
pipelining = True                    # Speed up execution by reducing SSH calls

For a full list of options, see ansible.cfg documentation.


VAULT

Ansible Vault encrypts sensitive data like passwords, API keys, and certificates. Encrypted files can be committed to version control safely.

Basic commands:

# Create a new encrypted file
ansible-vault create secrets.yml

# Encrypt an existing file
ansible-vault encrypt secrets.yml

# Edit an encrypted file (opens in editor)
ansible-vault edit secrets.yml

# View encrypted file contents
ansible-vault view secrets.yml

# Decrypt a file (removes encryption)
ansible-vault decrypt secrets.yml

# Change vault password
ansible-vault rekey secrets.yml

# Encrypt a string and output to stdout
ansible-vault encrypt_string --stdin-name 'db_password'

Using in playbooks:

# Prompt for password at runtime
ansible-playbook playbook.yml --ask-vault-pass

# Use a password file
ansible-playbook playbook.yml --vault-password-file ~/.vault_pass

# Use multiple vaults (vault IDs)
ansible-vault create --vault-id dev@prompt dev_secrets.yml
ansible-playbook playbook.yml --vault-id dev@prompt --vault-id prod@prompt

Best practices:


INVENTORY FILES

Inventory files define the hosts Ansible manages. They can be static (INI/YAML) or dynamic (scripts/plugins).

Static inventory (INI format):

# inventory.ini
[webservers]
web1.example.com ansible_user=ubuntu
web2.example.com ansible_user=ubuntu

[dbservers]
db1.example.com ansible_user=centos

[all:vars]
ansible_python_interpreter=/usr/bin/python3

YAML format:

# inventory.yml
all:
  children:
    webservers:
      hosts:
        web1.example.com:
          ansible_user: ubuntu
        web2.example.com:
          ansible_user: ubuntu
    dbservers:
      hosts:
        db1.example.com:
          ansible_user: centos
  vars:
    ansible_python_interpreter: /usr/bin/python3

Inventory precedence:

Dynamic inventory: Ansible supports cloud providers, CMDBs, etc.

# AWS EC2
pip install boto3
ansible-inventory -i aws_ec2.yml --list

# Example aws_ec2.yml
plugin: aws_ec2
regions:
  - us-east-1
filters:
  instance-state-name: running

Host variables and group variables:

For more, see inventory documentation.


Collections

Collections are packaged Ansible content including modules, roles, and plugins. They replace the monolithic Ansible distribution.

Installing collections:

# Install specific collection
ansible-galaxy collection install community.general

# Install multiple
ansible-galaxy collection install community.general community.mysql

# Install from requirements file
ansible-galaxy collection install -r requirements.yml

Using collection modules:

- name: Use community module
  community.general.ufw:
    rule: allow
    port: "80"

- name: Use AWS collection
  amazon.aws.ec2_instance:
    name: my-instance
    state: present

Popular collections:

Creating collections:

ansible-galaxy collection init mynamespace.mycollection

For more, see collections documentation.


Variables

Variables store values for reuse in playbooks. They can be defined at multiple levels with different precedence.

Variable precedence (highest to lowest):

  1. Extra vars (-e flag)
  2. Task vars
  3. Block vars
  4. Role vars
  5. Play vars
  6. Host vars
  7. Group vars
  8. Role defaults
  9. Facts

Defining variables:

# In playbook
- hosts: webservers
  vars:
    http_port: 80
    server_name: "{{ ansible_hostname }}"

# In inventory
[webservers:vars]
http_port=80

# Host vars (host_vars/web1.yml)
http_port: 8080

# Group vars (group_vars/webservers.yml)
package_name: nginx

Variable types:

# Strings
app_name: "myapp"

# Numbers
port: 8080

# Booleans
debug: true

# Lists
packages: [nginx, git, vim]

# Dictionaries
config:
  port: 80
  host: localhost

Using variables:

- name: Install package
  ansible.builtin.package:
    name: "{{ package_name }}"
    state: present

- name: Configure service
  ansible.builtin.template:
    src: config.j2
    dest: "/etc/{{ app_name }}/config"

Variable files:

# vars/main.yml
app_vars:
  name: myapp
  version: "1.0"

# Include vars
- name: Load variables
  ansible.builtin.include_vars:
    file: vars/main.yml

Facts

Facts are variables containing information about hosts, automatically gathered by Ansible.

Common facts:

Gathering facts:

- name: Gather all facts
  ansible.builtin.setup:

- name: Gather specific facts
  ansible.builtin.setup:
    filter: ansible_distribution*

- name: Gather custom facts
  ansible.builtin.setup:
    fact_path: /etc/ansible/facts.d

Using facts:

- name: Install OS-specific packages
  ansible.builtin.package:
    name: "{{ 'apache2' if ansible_distribution == 'Ubuntu' else 'httpd' }}"
    state: present

- name: Show system info
  ansible.builtin.debug:
    msg: "Host {{ ansible_hostname }} has {{ ansible_processor_vcpus }} CPUs"

Custom facts: Create /etc/ansible/facts.d/custom.fact:

[packages]
webserver=nginx
database=mysql

Access as ansible_local.custom.packages.webserver.

Disabling fact gathering:

- hosts: all
  gather_facts: false

Conditionals

Conditionals control task execution based on variables, facts, or previous task results.

Basic when clause:

- name: Install nginx on Ubuntu
  ansible.builtin.package:
    name: nginx
    state: present
  when: ansible_distribution == "Ubuntu"

- name: Install httpd on CentOS
  ansible.builtin.package:
    name: httpd
    state: present
  when: ansible_distribution == "CentOS"

Multiple conditions:

- name: Install package
  ansible.builtin.package:
    name: "{{ item }}"
    state: present
  loop: "{{ packages }}"
  when:
    - ansible_distribution in ["Ubuntu", "Debian"]
    - ansible_kernel is version('4.0', '>=')

Registering results:

- name: Check if service exists
  ansible.builtin.command: systemctl status nginx
  register: nginx_status
  ignore_errors: true

- name: Start nginx if not running
  ansible.builtin.service:
    name: nginx
    state: started
  when: nginx_status.rc != 0

Complex conditions:

- name: Run only on production
  ansible.builtin.debug:
    msg: "Production task"
  when: inventory_hostname in groups['prod'] and env == 'production'

- name: Skip on certain hosts
  ansible.builtin.command: echo "skip me"
  when: "'skip' not in ansible_hostname"

Conditional imports:

- name: Include Ubuntu tasks
  ansible.builtin.include_tasks: ubuntu.yml
  when: ansible_distribution == "Ubuntu"

Loops

Loops iterate over lists, dictionaries, or generated sequences.

Basic loop:

- name: Install packages
  ansible.builtin.package:
    name: "{{ item }}"
    state: present
  loop:
    - nginx
    - git
    - vim

Loop with dictionary:

- name: Create users
  ansible.builtin.user:
    name: "{{ item.name }}"
    uid: "{{ item.uid }}"
    state: present
  loop:
    - { name: "alice", uid: 1001 }
    - { name: "bob", uid: 1002 }

Loop variables:

- name: Show loop info
  ansible.builtin.debug:
    msg: "Item {{ item }} at index {{ loop_index }}"
  loop:
    - one
    - two
    - three
  loop_control:
    extended: true

Loop over inventory:

- name: Ping all hosts
  ansible.builtin.command: ping -c 1 {{ item }}
  loop: "{{ groups['all'] }}"
  delegate_to: localhost

Until loops:

- name: Wait for service
  ansible.builtin.uri:
    url: "http://localhost:80"
  register: result
  until: result.status == 200
  retries: 10
  delay: 5

Loop control:

- name: Install packages with pause
  ansible.builtin.package:
    name: "{{ item }}"
    state: present
  loop:
    - nginx
    - mysql
  loop_control:
    pause: 10 # Pause 10 seconds between iterations

Templates

Templates use Jinja2 to generate configuration files with variables.

Basic template:

- name: Deploy nginx config
  ansible.builtin.template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    mode: "0644"

Template file (templates/nginx.conf.j2):

server {
    listen {{ http_port | default(80) }};
    server_name {{ server_name | default(ansible_hostname) }};

    location / {
        proxy_pass http://{{ backend_host }}:{{ backend_port }};
        proxy_set_header Host $host;
    }

    {% if ssl_enabled | default(false) %}
    listen 443 ssl;
    ssl_certificate /etc/ssl/certs/{{ server_name }}.crt;
    ssl_certificate_key /etc/ssl/private/{{ server_name }}.key;
    {% endif %}
}

Template filters:

# String filters
{{ username | upper }}
{{ path | dirname }}
{{ list | join(', ') }}

# Default values
{{ variable | default('default_value') }}

# Conditionals in templates
{% if condition %}
content
{% endif %}

{% for item in items %}
{{ item }}
{% endfor %}

Validating templates:

- name: Deploy config with validation
  ansible.builtin.template:
    src: config.j2
    dest: /etc/myapp/config
    validate: myapp --check-config %s

Inline templates:

- name: Create config file
  ansible.builtin.copy:
    content: |
      # {{ ansible_managed }}
      port = {{ app_port }}
      host = {{ app_host }}      
    dest: /etc/myapp/config

ADHOC Commands

Adhoc commands run single tasks without playbooks. Useful for quick checks, one-off tasks, and testing.

Basic syntax:

ansible [pattern] -m [module] -a '[module_args]' [options]

Common examples:

# Test connectivity
ansible all -m ansible.builtin.ping

# Get hostnames
ansible all -m ansible.builtin.command -a 'hostname'

# Check disk usage
ansible all -m ansible.builtin.command -a 'df -h'

# Check uptime
ansible webservers -m ansible.builtin.command -a 'uptime'

# Dry run (check mode)
ansible all -m ansible.builtin.command -a 'reboot' --check

Privilege escalation:

# Become root with password prompt
ansible all -m ansible.builtin.user -a 'name=testuser state=present' -b -K

# Become specific user
ansible all -m ansible.builtin.user -a 'name=testuser state=present' -b --become-user=root -K

Advanced examples:

# Install package
ansible webservers -m ansible.builtin.package -a 'name=nginx state=present' -b

# Check service status
ansible all -m ansible.builtin.service -a 'name=sshd state=started'

# Copy file
ansible all -m ansible.builtin.copy -a 'src=/local/file dest=/remote/file' -b

# Run shell command with pipe
ansible all -m ansible.builtin.shell -a 'ps aux | grep nginx'

Options:


COMMANDS

Common Ansible command-line options and patterns.

Gathering facts:

# Show all facts for a host
ansible hostname -m ansible.builtin.setup

# Show specific fact
ansible hostname -m ansible.builtin.setup -a 'filter=ansible_distribution*'

# Gather facts without running tasks
ansible hostname -m ansible.builtin.setup --check

Common flags:


Includes

Includes allow reusing tasks across playbooks. Use include_tasks for task files, include_role for roles.

Including task files:

# In playbook.yml
tasks:
  - name: Include common tasks
    ansible.builtin.include_tasks: common.yml

  - name: Include tasks with variables
    ansible.builtin.include_tasks:
      file: setup.yml
      vars:
        package_name: nginx

Including roles:

- name: Include a role
  ansible.builtin.include_role:
    name: common
    vars:
      some_var: value

Conditional includes:

- name: Include tasks only on Ubuntu
  ansible.builtin.include_tasks: ubuntu_tasks.yml
  when: ansible_distribution == 'Ubuntu'

Best practices:


Galaxy

Ansible Galaxy hosts community-contributed roles and collections.

Role management:

# Install a role
ansible-galaxy role install geerlingguy.nginx

# Install role to specific path
ansible-galaxy role install geerlingguy.nginx -p ./roles

# List installed roles
ansible-galaxy role list

# Remove a role
ansible-galaxy role remove geerlingguy.nginx

# Create a new role
ansible-galaxy role init myrole

Collection management:

# Install a collection
ansible-galaxy collection install community.general

# Install to specific path
ansible-galaxy collection install community.general -p ./collections

# List installed collections
ansible-galaxy collection list

# Remove a collection
ansible-galaxy collection remove community.general

Publishing:

# Build and publish a collection
ansible-galaxy collection build
ansible-galaxy collection publish ./my-collection-1.0.0.tar.gz --api-key YOUR_API_KEY

Requirements files:

# requirements.yml
roles:
  - name: geerlingguy.nginx
    version: 3.0.0

collections:
  - name: community.general
    version: 5.0.0
ansible-galaxy role install -r requirements.yml
ansible-galaxy collection install -r requirements.yml

Roles

Roles organize playbooks into reusable components. A role is a directory structure with tasks, handlers, variables, etc.

Creating a role:

# Initialize role structure
ansible-galaxy role init myrole

# Or manually create directories
mkdir -p roles/myrole/{tasks,handlers,vars,defaults,files,templates,meta}

Role directory structure:

roles/myrole/
├── tasks/
│   └── main.yml          # Main tasks
├── handlers/
│   └── main.yml          # Event handlers
├── vars/
│   └── main.yml          # Role variables
├── defaults/
│   └── main.yml          # Default variables
├── files/                # Static files
├── templates/            # Jinja2 templates
├── meta/
│   └── main.yml          # Role metadata
└── README.md

Example tasks/main.yml:

- name: Install packages
  ansible.builtin.package:
    name: "{{ item }}"
    state: present
  loop:
    - vim
    - git
    - curl
    - htop

- name: Ensure service is running
  ansible.builtin.service:
    name: nginx
    state: started
    enabled: true

Role variables:

# defaults/main.yml
nginx_port: 80

# vars/main.yml (higher precedence)
nginx_user: www-data

Using roles in playbooks:

- hosts: webservers
  roles:
    - role: myrole
      vars:
        nginx_port: 8080

PLAYBOOK

Playbooks are YAML files defining automation tasks. They specify hosts, variables, tasks, and roles.

Basic structure:

---
- name: My playbook
  hosts: webservers
  become: true
  vars:
    http_port: 80
    max_clients: 200

  tasks:
    - name: Install nginx
      ansible.builtin.package:
        name: nginx
        state: present

    - name: Configure nginx
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: restart nginx

  handlers:
    - name: restart nginx
      ansible.builtin.service:
        name: nginx
        state: restarted

  roles:
    - common
    - nginx

Running playbooks:

# Basic run
ansible-playbook playbook.yml

# With vault password
ansible-playbook playbook.yml --ask-vault-pass

# Limit to specific hosts
ansible-playbook playbook.yml --limit webservers

# Dry run
ansible-playbook playbook.yml --check --diff

# Start at specific task
ansible-playbook playbook.yml --start-at-task "Install nginx"

# Verbose output
ansible-playbook playbook.yml -v

Multiple plays:

---
- name: Setup webservers
  hosts: webservers
  roles:
    - web

- name: Setup databases
  hosts: dbservers
  roles:
    - db

Delegate

Delegation runs tasks on different hosts than the playbook target. Useful for load balancers, monitoring, or local actions.

Basic delegation:

- name: Update load balancer
  hosts: webservers
  tasks:
    - name: Remove from load balancer
      ansible.builtin.uri:
        url: "http://lb.example.com/remove/{{ inventory_hostname }}"
      delegate_to: loadbalancer.example.com

Async tasks with delegation:

- name: Restart server
  hosts: app_servers
  tasks:
    - name: Restart application
      ansible.builtin.command: systemctl restart myapp
      async: 45
      poll: 0

    - name: Wait for server to respond
      ansible.builtin.wait_for:
        host: "{{ inventory_hostname }}"
        port: 80
        delay: 10
        timeout: 60
      delegate_to: localhost

Delegating facts:

- name: Gather facts from monitoring server
  hosts: webservers
  tasks:
    - name: Get monitoring facts
      ansible.builtin.setup:
      delegate_to: monitoring.example.com
      delegate_facts: true

Local actions:

- name: Run local command
  hosts: all
  tasks:
    - name: Update local inventory
      ansible.builtin.command: ./update_inventory.sh
      delegate_to: localhost
      run_once: true

COPYING FILES

Copy static files or deploy templates with variable substitution.

Copying files:

- name: Copy configuration file
  ansible.builtin.copy:
    src: files/nginx.conf
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: "0644"
    backup: true # Create backup if file exists

Copying content directly:

- name: Create motd
  ansible.builtin.copy:
    content: |
      Welcome to {{ ansible_hostname }}
      Managed by Ansible      
    dest: /etc/motd
    mode: "0644"

Using templates:

- name: Deploy nginx config
  ansible.builtin.template:
    src: templates/nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    mode: "0644"
    validate: nginx -t -c %s # Validate config before deploying

File operations:

- name: Ensure directory exists
  ansible.builtin.file:
    path: /opt/myapp
    state: directory
    mode: "0755"

- name: Remove old config
  ansible.builtin.file:
    path: /etc/old_config
    state: absent

Backup and restore:

- name: Backup current config
  ansible.builtin.copy:
    src: /etc/nginx/nginx.conf
    dest: /etc/nginx/nginx.conf.backup
    remote_src: true
  when: ansible_hostname == 'web1'

MAKING WEBSERVER WITH REVERSE PROXY

Example role for setting up Apache with reverse proxy.

tasks/main.yml:

- name: Install Apache
  ansible.builtin.package:
    name: apache2
    state: present

- name: Enable required modules
  ansible.builtin.apache2_module:
    name: "{{ item }}"
    state: present
  loop:
    - proxy
    - proxy_http
  notify: restart apache

- name: Deploy proxy configuration
  ansible.builtin.template:
    src: proxy.conf.j2
    dest: /etc/apache2/conf-available/proxy.conf
    mode: "0644"
  notify: enable proxy config

- name: Enable site
  ansible.builtin.command: a2ensite proxy
  notify: restart apache

templates/proxy.conf.j2:

<VirtualHost *:80>
    ServerName {{ ansible_hostname }}

    ProxyPass / http://{{ hostvars[groups['appservers'][0]]['ansible_default_ipv4']['address'] }}:8000/
    ProxyPassReverse / http://{{ hostvars[groups['appservers'][0]]['ansible_default_ipv4']['address'] }}:8000/

    ErrorLog ${APACHE_LOG_DIR}/proxy_error.log
    CustomLog ${APACHE_LOG_DIR}/proxy_access.log combined
</VirtualHost>

handlers/main.yml:

- name: enable proxy config
  ansible.builtin.command: a2enconf proxy

- name: restart apache
  ansible.builtin.service:
    name: apache2
    state: restarted

HANDLERS

Handlers are tasks that run only when notified by other tasks. They prevent unnecessary service restarts.

Example handlers/main.yml:

- name: restart nginx
  ansible.builtin.service:
    name: nginx
    state: restarted

- name: reload nginx
  ansible.builtin.service:
    name: nginx
    state: reloaded

- name: restart php-fpm
  ansible.builtin.service:
    name: php8.1-fpm
    state: restarted

Using handlers in tasks:

tasks:
  - name: Update nginx config
    ansible.builtin.template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    notify: reload nginx

  - name: Update PHP config
    ansible.builtin.template:
      src: php.ini.j2
      dest: /etc/php/8.1/fpm/php.ini
    notify:
      - restart php-fpm
      - reload nginx

Handler execution:

handlers:
  - name: restart services
    ansible.builtin.service:
      name: "{{ item }}"
      state: restarted
    loop:
      - nginx
      - php-fpm
    listen: restart web services

tasks:
  - name: Update configs
    # ... task ...
    notify: restart web services

Force handler execution:

ansible-playbook playbook.yml --force-handlers

SECRETS (PASSWORDS)

Handle sensitive data using Ansible Vault, environment variables, or external secret managers.

Using Ansible Vault:

# In vars file (encrypted with ansible-vault)
db_password: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  ...

# Or use lookup
db_password: "{{ lookup('ansible.builtin.password', 'credentials/dbpassword.txt') }}"

Environment variables:

- name: Run application
  ansible.builtin.command: myapp
  environment:
    DB_PASSWORD: "{{ db_password }}"
    API_KEY: "{{ api_key }}"

Supervisor config example:

[program:testapp]
command=/usr/bin/gunicorn --bind 0.0.0.0:8000 testapp:app
directory=/opt/testapp
user=nobody
autostart=true
autorestart=true
redirect_stderr=true
environment=DB_PASSWORD="{{ db_password }}"

Handlers for supervisor:

- name: reread supervisor config
  ansible.builtin.command: supervisorctl reread

- name: restart testapp
  ansible.builtin.supervisorctl:
    name: testapp
    state: restarted

Playbook with appserver role:

- hosts: appservers
  become: true
  roles:
    - appserver
  vars:
    db_password: "{{ vault_db_password }}"

SETUP DATABASE

Example role for MySQL database setup. Requires community.mysql collection.

Install collection:

ansible-galaxy collection install community.mysql

tasks/main.yml:

- name: Install MySQL packages
  ansible.builtin.package:
    name: "{{ item }}"
    state: present
  loop:
    - mysql-server
    - python3-mysqldb

- name: Start and enable MySQL
  ansible.builtin.service:
    name: mysql
    state: started
    enabled: true

- name: Set root password
  community.mysql.mysql_user:
    name: root
    password: "{{ mysql_root_password }}"
    login_unix_socket: /var/run/mysqld/mysqld.sock

- name: Create database
  community.mysql.mysql_db:
    name: "{{ db_name }}"
    state: present
    login_unix_socket: /var/run/mysqldb/mysqld.sock

- name: Create database user
  community.mysql.mysql_user:
    name: "{{ db_user }}"
    password: "{{ db_password }}"
    priv: "{{ db_name }}.*:ALL"
    host: "%"
    state: present

- name: Configure MySQL for remote access
  ansible.builtin.template:
    src: mysqld.cnf.j2
    dest: /etc/mysql/mysql.conf.d/mysqld.cnf
    mode: "0644"
  notify: restart mysql

- name: Setup backup cron job
  ansible.builtin.cron:
    name: "MySQL Backup"
    hour: "10"
    minute: "22"
    user: root
    job: "/usr/bin/mysqldump --all-databases > /root/dbbackup.dump"

templates/mysqld.cnf.j2:

[mysqld]
bind-address = 0.0.0.0

handlers/main.yml:

- name: restart mysql
  ansible.builtin.service:
    name: mysql
    state: restarted

Playbook usage:

- hosts: dbservers
  become: true
  roles:
    - dbserver
  vars:
    mysql_root_password: "{{ vault_mysql_root_password }}"
    db_name: myapp
    db_user: myapp_user
    db_password: "{{ vault_db_password }}"

Best Practices

Guidelines for writing maintainable, secure Ansible code.

Project Structure:

myproject/
├── ansible.cfg
├── inventory/
│   ├── hosts.ini
│   └── group_vars/
├── playbooks/
├── roles/
│   └── myrole/
│       ├── tasks/
│       ├── handlers/
│       ├── vars/
│       ├── defaults/
│       ├── files/
│       ├── templates/
│       └── meta/
├── collections/
├── requirements.yml
└── README.md

Playbook Best Practices:

Security:

Performance:

Idempotency:

Version Control:

Documentation:


Troubleshooting

Common issues and debugging techniques.

Connection Issues:

# Test SSH connection
ssh -i ~/.ssh/key user@host

# Test Ansible connection
ansible host -m ansible.builtin.ping -u user -i inventory

# Debug connection
ansible host -m ansible.builtin.ping -vvv

Task Failures:

# Run with verbose output
ansible-playbook playbook.yml -v

# Check syntax
ansible-playbook playbook.yml --syntax-check

# Dry run
ansible-playbook playbook.yml --check --diff

# Run specific task
ansible-playbook playbook.yml --start-at-task "Install nginx"

Variable Issues:

# Debug variables
- name: Show variables
  ansible.builtin.debug:
    var: variable_name

- name: Show all variables
  ansible.builtin.debug:
    var: hostvars[inventory_hostname]

Common Errors:

Performance Issues:

Logs:


Ansible Lint

Ansible Lint checks playbooks for best practices and potential issues.

Installation:

pip install ansible-lint

Basic usage:

# Lint a playbook
ansible-lint playbook.yml

# Lint entire directory
ansible-lint .

# Lint with specific rules
ansible-lint --tags yaml playbook.yml

Common rules:

Configuration (.ansible-lint):

exclude_paths:
  - .git/
  - test/

rules:
  yaml: enable
  syntax-check: enable
  deprecated: enable
  command-instead-of-module: enable

CI/CD Integration:

# .github/workflows/lint.yml
name: Lint
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Install ansible-lint
        run: pip install ansible-lint
      - name: Run ansible-lint
        run: ansible-lint .

Fixing common issues: