Docs For AI
Iac

Ansible

Configuration management and application deployment - playbooks, roles, inventory, and automation patterns

Ansible

Ansible is an agentless automation tool for configuration management, application deployment, and task automation. It uses SSH to connect to remote hosts and execute tasks defined in YAML playbooks.

Core Concepts

ConceptDescription
InventoryList of managed hosts organized into groups
PlaybookYAML file defining tasks to execute on hosts
RoleReusable collection of tasks, handlers, templates, and variables
TaskSingle unit of work (install package, copy file, start service)
ModuleBuilt-in function for a specific task (apt, copy, service, docker_container)
HandlerTask triggered by notifications (restart service after config change)
FactsSystem information gathered from managed hosts

Project Structure

ansible/
├── ansible.cfg              # Configuration
├── inventory/
│   ├── production.yml       # Production hosts
│   └── staging.yml          # Staging hosts
├── playbooks/
│   ├── site.yml             # Main playbook
│   ├── deploy.yml           # Application deployment
│   └── setup.yml            # Server setup
├── roles/
│   ├── common/              # Base server config
│   ├── docker/              # Docker installation
│   ├── nginx/               # Nginx reverse proxy
│   ├── app/                 # Application deployment
│   └── monitoring/          # Monitoring agents
├── group_vars/
│   ├── all.yml              # Variables for all hosts
│   ├── webservers.yml       # Variables for web servers
│   └── databases.yml        # Variables for DB servers
├── host_vars/
│   └── prod-api-01.yml      # Host-specific variables
└── templates/
    └── nginx.conf.j2        # Jinja2 templates

Inventory

# inventory/production.yml
all:
  children:
    webservers:
      hosts:
        web-01:
          ansible_host: 10.0.1.10
        web-02:
          ansible_host: 10.0.1.11
      vars:
        app_port: 3000
        nginx_worker_processes: auto

    databases:
      hosts:
        db-01:
          ansible_host: 10.0.2.10
          postgresql_max_connections: 200
        db-02:
          ansible_host: 10.0.2.11
          postgresql_max_connections: 200

    monitoring:
      hosts:
        monitor-01:
          ansible_host: 10.0.3.10

  vars:
    ansible_user: deploy
    ansible_ssh_private_key_file: ~/.ssh/deploy_key
    ansible_python_interpreter: /usr/bin/python3

Playbooks

Server Setup Playbook

# playbooks/setup.yml
---
- name: Configure all servers
  hosts: all
  become: true
  roles:
    - common

- name: Configure web servers
  hosts: webservers
  become: true
  roles:
    - docker
    - nginx
    - app

- name: Configure database servers
  hosts: databases
  become: true
  roles:
    - role: postgresql
      vars:
        postgresql_version: 16

Application Deployment Playbook

# playbooks/deploy.yml
---
- name: Deploy application
  hosts: webservers
  become: true
  serial: 1                    # Rolling deploy, one host at a time
  max_fail_percentage: 0       # Stop if any host fails

  vars:
    app_image: "myregistry/app:{{ app_version }}"

  pre_tasks:
    - name: Remove from load balancer
      uri:
        url: "http://{{ lb_api }}/deregister/{{ inventory_hostname }}"
        method: POST
      delegate_to: localhost

  tasks:
    - name: Pull new image
      docker_image:
        name: "{{ app_image }}"
        source: pull
        force_source: true

    - name: Stop existing container
      docker_container:
        name: app
        state: stopped
      ignore_errors: true

    - name: Start new container
      docker_container:
        name: app
        image: "{{ app_image }}"
        state: started
        restart_policy: unless-stopped
        ports:
          - "{{ app_port }}:3000"
        env:
          NODE_ENV: production
          DATABASE_URL: "{{ database_url }}"

    - name: Wait for health check
      uri:
        url: "http://localhost:{{ app_port }}/health"
        status_code: 200
      register: health
      until: health.status == 200
      retries: 30
      delay: 2

  post_tasks:
    - name: Register with load balancer
      uri:
        url: "http://{{ lb_api }}/register/{{ inventory_hostname }}"
        method: POST
      delegate_to: localhost

Roles

Common Role

# roles/common/tasks/main.yml
---
- name: Update apt cache
  apt:
    update_cache: true
    cache_valid_time: 3600

- name: Install base packages
  apt:
    name:
      - curl
      - wget
      - vim
      - htop
      - net-tools
      - unzip
      - jq
    state: present

- name: Configure timezone
  timezone:
    name: "{{ timezone | default('UTC') }}"

- name: Configure NTP
  apt:
    name: chrony
    state: present
  notify: restart chrony

- name: Harden SSH
  lineinfile:
    path: /etc/ssh/sshd_config
    regexp: "{{ item.regexp }}"
    line: "{{ item.line }}"
  loop:
    - { regexp: '^#?PermitRootLogin', line: 'PermitRootLogin no' }
    - { regexp: '^#?PasswordAuthentication', line: 'PasswordAuthentication no' }
    - { regexp: '^#?MaxAuthTries', line: 'MaxAuthTries 3' }
  notify: restart sshd

- name: Configure firewall
  ufw:
    rule: allow
    port: "{{ item }}"
    proto: tcp
  loop:
    - "22"
    - "80"
    - "443"

- name: Enable firewall
  ufw:
    state: enabled
    policy: deny

Handlers

# roles/common/handlers/main.yml
---
- name: restart chrony
  service:
    name: chrony
    state: restarted

- name: restart sshd
  service:
    name: sshd
    state: restarted

Templates (Jinja2)

# roles/nginx/templates/nginx.conf.j2
upstream app {
{% for host in groups['webservers'] %}
    server {{ hostvars[host]['ansible_host'] }}:{{ app_port }};
{% endfor %}
}

server {
    listen 80;
    server_name {{ domain_name }};

    location / {
        proxy_pass http://app;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /health {
        access_log off;
        return 200 "ok";
    }
}

Common Commands

# Run playbook
ansible-playbook -i inventory/production.yml playbooks/deploy.yml

# Dry run (check mode)
ansible-playbook -i inventory/production.yml playbooks/deploy.yml --check --diff

# Limit to specific hosts
ansible-playbook -i inventory/production.yml playbooks/deploy.yml --limit web-01

# Pass extra variables
ansible-playbook -i inventory/production.yml playbooks/deploy.yml -e "app_version=v2.1.0"

# Run ad-hoc commands
ansible webservers -i inventory/production.yml -m shell -a "docker ps"
ansible all -i inventory/production.yml -m ping

# List hosts in inventory
ansible-inventory -i inventory/production.yml --list

# Encrypt secrets
ansible-vault encrypt group_vars/all/secrets.yml
ansible-vault edit group_vars/all/secrets.yml
ansible-playbook site.yml --ask-vault-pass

Terraform + Ansible Integration

PhaseToolAction
ProvisioningTerraformCreate VMs, networks, load balancers, DNS
ConfigurationAnsibleInstall software, configure services, deploy apps
UpdatesAnsibleRolling deploys, config changes
ScalingTerraformAdd/remove instances, update infrastructure
# Terraform outputs server IPs for Ansible inventory
output "web_server_ips" {
  value = aws_instance.web[*].private_ip
}
# Generate Ansible inventory from Terraform output
terraform output -json web_server_ips | \
  jq -r '.[] | "  " + . + ":"' > inventory/dynamic.yml

Best Practices

Ansible Guidelines

  1. Idempotency: Ensure all tasks can be run multiple times without side effects
  2. Ansible Vault: Encrypt all secrets; never commit plaintext credentials
  3. Roles: Extract reusable logic into roles; use Ansible Galaxy for community roles
  4. Handlers: Use handlers for service restarts to avoid unnecessary restarts
  5. Tags: Tag tasks for selective execution (--tags deploy, --tags config)
  6. Check mode: Always test with --check --diff before applying to production
  7. Serial deployment: Use serial: 1 for zero-downtime rolling deployments
  8. Facts caching: Enable fact caching for faster repeated runs

On this page