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!