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
| Concept | Description |
|---|---|
| Inventory | List of managed hosts organized into groups |
| Playbook | YAML file defining tasks to execute on hosts |
| Role | Reusable collection of tasks, handlers, templates, and variables |
| Task | Single unit of work (install package, copy file, start service) |
| Module | Built-in function for a specific task (apt, copy, service, docker_container) |
| Handler | Task triggered by notifications (restart service after config change) |
| Facts | System 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 templatesInventory
# 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/python3Playbooks
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: 16Application 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: localhostRoles
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: denyHandlers
# roles/common/handlers/main.yml
---
- name: restart chrony
service:
name: chrony
state: restarted
- name: restart sshd
service:
name: sshd
state: restartedTemplates (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-passTerraform + Ansible Integration
| Phase | Tool | Action |
|---|---|---|
| Provisioning | Terraform | Create VMs, networks, load balancers, DNS |
| Configuration | Ansible | Install software, configure services, deploy apps |
| Updates | Ansible | Rolling deploys, config changes |
| Scaling | Terraform | Add/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.ymlBest Practices
Ansible Guidelines
- Idempotency: Ensure all tasks can be run multiple times without side effects
- Ansible Vault: Encrypt all secrets; never commit plaintext credentials
- Roles: Extract reusable logic into roles; use Ansible Galaxy for community roles
- Handlers: Use handlers for service restarts to avoid unnecessary restarts
- Tags: Tag tasks for selective execution (
--tags deploy,--tags config) - Check mode: Always test with
--check --diffbefore applying to production - Serial deployment: Use
serial: 1for zero-downtime rolling deployments - Facts caching: Enable fact caching for faster repeated runs