Skip to main content

Ansible

Master Ansible for agentless automation, configuration management, and application deployment. Learn to automate server setup, software installation, and deployments at scale.

What is Ansible?​

Ansible is an open-source automation tool that automates software provisioning, configuration management, and application deployment using simple, human-readable YAML files called playbooks.

Key Features​

  • Agentless - No software to install on managed nodes (uses SSH)
  • Simple - YAML syntax, easy to learn
  • Powerful - Orchestrate complex deployments
  • Idempotent - Safe to run multiple times
  • Extensible - Thousands of modules available
  • Multi-Platform - Linux, Windows, network devices, cloud

Installation​

Linux (Ubuntu/Debian):

sudo apt update
sudo apt install ansible

macOS:

brew install ansible

Using pip:

pip install ansible

Verify installation:

ansible --version

Ansible Architecture​

┌────────────────────────────┐
│ Control Node │
│ (Your machine) │
│ - Ansible installed │
│ - Inventory file │
│ - Playbooks │
└──────────┬─────────────────┘
│ SSH
┌─────┴──────┬──────────┐
│ │ │
┌────▼────┐ ┌───▼────┐ ┌──▼─────┐
│Managed │ │Managed │ │Managed │
│Node 1 │ │Node 2 │ │Node 3 │
│(server) │ │(server)│ │(server)│
└─────────┘ └────────┘ └────────┘

Inventory​

Define your servers in an inventory file.

INI Format (inventory.ini)​

# Single host
web1.example.com

# Group of hosts
[webservers]
web1.example.com
web2.example.com
web3.example.com

[databases]
db1.example.com
db2.example.com

# Host with variables
[webservers]
web1.example.com ansible_user=ubuntu ansible_port=2222

# Group variables
[webservers:vars]
ansible_user=ubuntu
ansible_python_interpreter=/usr/bin/python3

# Nested groups
[production:children]
webservers
databases

YAML Format (inventory.yml)​

all:
children:
webservers:
hosts:
web1.example.com:
web2.example.com:
vars:
ansible_user: ubuntu
http_port: 80

databases:
hosts:
db1.example.com:
ansible_host: 10.0.1.50
db2.example.com:
ansible_host: 10.0.1.51

Dynamic Inventory​

# AWS dynamic inventory
ansible-inventory -i aws_ec2.yml --graph

# Custom script
#!/usr/bin/env python3
import json

inventory = {
"webservers": {
"hosts": ["web1.example.com", "web2.example.com"]
},
"_meta": {
"hostvars": {}
}
}

print(json.dumps(inventory))

Ad-Hoc Commands​

Run single tasks without a playbook:

# Ping all hosts
ansible all -m ping -i inventory.ini

# Check uptime
ansible webservers -m shell -a "uptime"

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

# Copy file
ansible webservers -m copy -a "src=./index.html dest=/var/www/html/"

# Restart service
ansible webservers -m service -a "name=nginx state=restarted" --become

# Gather facts
ansible webservers -m setup

Playbooks​

YAML files defining automation tasks.

Basic Playbook​

# webserver.yml
---
- name: Configure web servers
hosts: webservers
become: yes # Run as sudo

tasks:
- name: Update apt cache
apt:
update_cache: yes

- name: Install nginx
apt:
name: nginx
state: present

- name: Start nginx service
service:
name: nginx
state: started
enabled: yes

- name: Copy index.html
copy:
src: files/index.html
dest: /var/www/html/index.html
owner: www-data
group: www-data
mode: '0644'

Run playbook:

ansible-playbook -i inventory.ini webserver.yml

Playbook with Variables​

---
- name: Deploy application
hosts: webservers
become: yes

vars:
app_name: myapp
app_version: 1.0.0
app_port: 8080

tasks:
- name: Create app directory
file:
path: "/opt/{{ app_name }}"
state: directory
mode: '0755'

- name: Deploy application
copy:
src: "{{ app_name }}-{{ app_version }}.jar"
dest: "/opt/{{ app_name }}/app.jar"

- name: Create systemd service
template:
src: templates/app.service.j2
dest: "/etc/systemd/system/{{ app_name }}.service"
notify: Restart application

handlers:
- name: Restart application
systemd:
name: "{{ app_name }}"
state: restarted
daemon_reload: yes

Variables from File​

# vars/main.yml
app_name: myapp
app_version: 1.0.0
database_host: db.example.com
database_port: 5432
# playbook.yml
---
- name: Deploy application
hosts: webservers
become: yes
vars_files:
- vars/main.yml

tasks:
- name: Configure application
template:
src: config.j2
dest: /etc/myapp/config.yml

Handlers​

Run tasks only when notified (typically for service restarts):

---
- name: Configure nginx
hosts: webservers
become: yes

tasks:
- name: Install nginx
apt:
name: nginx
state: present

- name: Copy nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify:
- Reload nginx
- Send notification

handlers:
- name: Reload nginx
service:
name: nginx
state: reloaded

- name: Send notification
debug:
msg: "Nginx configuration changed"

Templates (Jinja2)​

Dynamic configuration files:

templates/nginx.conf.j2:

server {
listen {{ http_port }};
server_name {{ server_name }};

root /var/www/{{ app_name }};
index index.html;

location / {
try_files $uri $uri/ =404;
}

{% if enable_ssl %}
listen 443 ssl;
ssl_certificate /etc/ssl/certs/{{ server_name }}.crt;
ssl_certificate_key /etc/ssl/private/{{ server_name }}.key;
{% endif %}
}

Using template:

- name: Deploy nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/{{ app_name }}
vars:
http_port: 80
server_name: example.com
app_name: myapp
enable_ssl: true

Roles​

Organize playbooks into reusable components.

Role Structure​

roles/
└── webserver/
├── tasks/
│ └── main.yml
├── handlers/
│ └── main.yml
├── templates/
│ └── nginx.conf.j2
├── files/
│ └── index.html
├── vars/
│ └── main.yml
├── defaults/
│ └── main.yml
└── meta/
└── main.yml

roles/webserver/tasks/main.yml:

---
- name: Install nginx
apt:
name: nginx
state: present

- name: Copy nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/default
notify: Reload nginx

- name: Start nginx
service:
name: nginx
state: started
enabled: yes

roles/webserver/handlers/main.yml:

---
- name: Reload nginx
service:
name: nginx
state: reloaded

roles/webserver/defaults/main.yml:

---
http_port: 80
server_name: localhost

Using a role:

---
- name: Configure web servers
hosts: webservers
become: yes

roles:
- webserver

Ansible Vault​

Encrypt sensitive data:

# Create encrypted file
ansible-vault create secrets.yml

# Edit encrypted file
ansible-vault edit secrets.yml

# Encrypt existing file
ansible-vault encrypt vars/passwords.yml

# Decrypt file
ansible-vault decrypt vars/passwords.yml

# View encrypted file
ansible-vault view secrets.yml

secrets.yml (after creating):

---
db_password: SuperSecretPassword123!
api_key: abc123def456

Using encrypted variables:

---
- name: Deploy with secrets
hosts: webservers
become: yes
vars_files:
- secrets.yml

tasks:
- name: Configure database connection
template:
src: db_config.j2
dest: /etc/myapp/db.conf

Run with vault:

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

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

Common Modules​

Package Management​

# APT (Ubuntu/Debian)
- name: Install packages
apt:
name:
- nginx
- postgresql
- python3-pip
state: present
update_cache: yes

# YUM (RHEL/CentOS)
- name: Install packages
yum:
name:
- httpd
- mariadb-server
state: present

File Operations​

# Create directory
- name: Create directory
file:
path: /opt/myapp
state: directory
mode: '0755'
owner: ubuntu
group: ubuntu

# Copy file
- name: Copy file
copy:
src: files/app.conf
dest: /etc/myapp/app.conf
mode: '0644'

# Download file
- name: Download file
get_url:
url: https://example.com/app.jar
dest: /opt/myapp/app.jar

# Create symlink
- name: Create symlink
file:
src: /opt/myapp/app-1.0.0
dest: /opt/myapp/current
state: link

Service Management​

# Start service
- name: Start nginx
service:
name: nginx
state: started
enabled: yes

# Restart service
- name: Restart application
systemd:
name: myapp
state: restarted
daemon_reload: yes

User Management​

# Create user
- name: Create application user
user:
name: appuser
state: present
shell: /bin/bash
groups: docker
append: yes

Docker​

# Pull Docker image
- name: Pull image
docker_image:
name: nginx:latest
source: pull

# Run container
- name: Run container
docker_container:
name: webserver
image: nginx:latest
ports:
- "80:80"
volumes:
- /data:/usr/share/nginx/html
state: started
restart_policy: always

Complete Example: Full Stack Deployment​

Project Structure:

ansible-project/
├── inventory/
│ ├── production
│ └── staging
├── group_vars/
│ ├── all.yml
│ ├── webservers.yml
│ └── databases.yml
├── roles/
│ ├── common/
│ ├── webserver/
│ ├── database/
│ └── application/
├── playbooks/
│ ├── site.yml
│ ├── webservers.yml
│ └── databases.yml
└── ansible.cfg

ansible.cfg:

[defaults]
inventory = inventory/production
host_key_checking = False
retry_files_enabled = False
roles_path = roles

inventory/production:

[webservers]
web1.example.com
web2.example.com

[databases]
db1.example.com

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

group_vars/all.yml:

---
environment: production
app_name: myapp
app_version: 1.0.0

playbooks/site.yml:

---
- name: Configure all servers
hosts: all
become: yes
roles:
- common

- name: Configure web servers
hosts: webservers
become: yes
roles:
- webserver
- application

- name: Configure databases
hosts: databases
become: yes
roles:
- database

Deploy:

ansible-playbook playbooks/site.yml

Best Practices​

1. Use Roles for Organization

roles/
├── common/
├── webserver/
└── database/

2. Idempotent Tasks

# Good - can run multiple times safely
- name: Ensure nginx is installed
apt:
name: nginx
state: present

3. Use Check Mode (Dry Run)

ansible-playbook playbook.yml --check

4. Tagging

- name: Install packages
apt:
name: nginx
tags: [install, nginx]

- name: Configure nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
tags: [configure, nginx]
# Run only install tags
ansible-playbook playbook.yml --tags install

# Skip configure tags
ansible-playbook playbook.yml --skip-tags configure

5. Use Variables Wisely

# defaults/main.yml - default values
http_port: 80

# vars/main.yml - constants
app_name: myapp

# group_vars/ - per-group overrides
# host_vars/ - per-host overrides

6. Error Handling

- name: Start service
service:
name: myapp
state: started
ignore_errors: yes

- name: Check if file exists
stat:
path: /etc/myapp/config.yml
register: config_file

- name: Create config if missing
template:
src: config.yml.j2
dest: /etc/myapp/config.yml
when: not config_file.stat.exists

CI/CD Integration​

GitHub Actions:

# .github/workflows/ansible.yml
name: Ansible Deployment

on:
push:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Install Ansible
run: |
pip install ansible

- name: Run playbook
env:
ANSIBLE_VAULT_PASSWORD: ${{ secrets.VAULT_PASSWORD }}
run: |
echo "$ANSIBLE_VAULT_PASSWORD" > vault_pass.txt
ansible-playbook playbooks/site.yml --vault-password-file vault_pass.txt

Useful Commands​

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

# List tasks
ansible-playbook playbook.yml --list-tasks

# List hosts
ansible-playbook playbook.yml --list-hosts

# Step through playbook
ansible-playbook playbook.yml --step

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

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

# Increase verbosity
ansible-playbook playbook.yml -v
ansible-playbook playbook.yml -vv
ansible-playbook playbook.yml -vvv

Resources​


Master Ansible to automate configuration management and deployments at scale!