Ansible Automation Ops Guide: From Configuration Management to Application Deployment
Batching configuration changes across ten servers by SSHing into each one and typing commands takes half an hour at best — and you will likely mistype on one or two of them. When the fleet grows to fifty or a hundred servers, that manual process simply does not scale. Ansible was designed for exactly this: an automation tool built around the idea of describing the end state and letting the tool make it real. Its standout trait is being agentless — no client software on the target machines, only SSH access — which makes it one of the lowest-friction configuration management tools you can adopt.
1. Installation and Basic Concepts
You only install Ansible on the control machine:
# Install
pip install ansible
# Verify
ansible --version
Lay down a few concepts first:
| Concept | Description |
|---|---|
| Control Node | The machine running Ansible |
| Managed Node | A target machine, reachable over SSH |
| Inventory | The host list, describing which machines belong to which groups |
| Playbook | A YAML task script describing "what to do" |
| Module | Performs a specific operation (apt, copy, service, etc.) |
"Declarative plus idempotent" is the heart of Ansible: every module checks the current state, skips what is already satisfied, and produces the same result no matter how many times you run it. Running the same Playbook ten times yields the same end state as running it once. This is also why Ansible is great at "drift correction": if a server was modified by hand, re-running the Playbook pulls it back to the desired state without reinstalling the OS.
2. Inventory Configuration
Hosts live in an inventory file with group and group-variable support:
# hosts.ini
[web]
web1.example.com
web2.example.com
[db]
db1.example.com
[all:vars]
ansible_user=deploy
ansible_python_interpreter=/usr/bin/python3
After writing it, ansible web -m ping -i hosts.ini quickly verifies connectivity. During day-to-day work, ansible all --list-hosts shows every machine in the current inventory — a cheap way to avoid the "configured the wrong group" mistake. Give groups meaningful names (web, db, cache) and add comments describing what each machine does; six months later you will thank yourself when maintaining it.
3. Playbook Example
A complete Nginx Playbook that chains install, template, and start together:
---
- name: Configure Web Servers
hosts: web
become: yes
vars:
nginx_port: 80
tasks:
- name: Install Nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Copy Nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/default
notify: restart nginx
- name: Start Nginx
service:
name: nginx
state: started
enabled: yes
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
Notice the pairing of notify: restart nginx with the handlers at the bottom: the restart fires only when the "Copy Nginx config" task actually changed the file; if nothing changed, it is skipped. That is Ansible's key mechanism for avoiding "restart the service on every run." The template file nginx.conf.j2 can reference variables like {{ nginx_port }}. Another switch you will use constantly is become: yes, which runs tasks with sudo privileges — installing software and changing system config almost always need it. Name the Playbook and every task (name is required); while running, the terminal shows each task's status: ok means unchanged, changed means modified, failed means error — you can locate a problem step at a glance.
4. Role Organization
As Playbooks multiply, extract the repeated logic into Roles. A Role is a reusable unit with a fixed directory layout:
roles/
nginx/
tasks/main.yml
templates/nginx.conf.j2
vars/main.yml
handlers/main.yml
defaults/main.yml
tasks holds tasks, templates holds templates, and vars/defaults hold variables (defaults have lower precedence and can be overridden). Once written, a Playbook can reference it with roles: - nginx, or you can pull community-maintained Roles from Ansible Galaxy and integrate a mature setup in a few dozen lines. When writing your own Role, put values that change in vars and machine-related values that vary per environment in defaults, so callers can still override them.
5. Common Use Cases
| Scenario | Solution |
|---|---|
| Server initialization | Install common software, configure SSH, set timezone |
| App deployment | Git pull, build, restart services |
| Configuration management | Manage Nginx, MySQL, Redis configs |
| Security hardening | Firewall rules, Fail2ban, SSH hardening |
6. Ad-hoc Commands: Small Operations Without a Playbook
Not everything deserves a Playbook. For a quick disk check or a batch service restart, an ad-hoc command does it in one line:
# Check disk usage on the web group
ansible web -i hosts.ini -m shell -a "df -h"
# Restart MySQL on the db group
ansible db -i hosts.ini -m service -a "name=mysql state=restarted"
The syntax is ansible <host-group> -m <module> -a "<arguments>". When a command grows beyond a line or two, or will be run repeatedly by people, promote it to a Playbook — the rule of thumb is "will this happen again?"
A Practical Scenario
A team needs to harden 30 servers uniformly: disable root password login, configure fail2ban, and unify the NTP timezone. Doing it by hand takes an afternoon and still misses a machine or two. Write a security Role, run ansible-playbook -i hosts.ini security.yml, and all 30 are done in under ten minutes. When a new teammate joins or a machine is added, the same Playbook is reused as-is. Run with --check first to review the plan and -v to see exactly what ran on each machine — all the logs are in the terminal, far more reliable than SSHing around by hand.
Common Questions
- Ansible cannot reach a target? SSH in manually first to confirm connectivity, then check
ansible_userand key config in the inventory; validate withansible -m ping. - The service restarted twice across two runs? Check whether you used handlers with notify and whether the module is idempotent; no config change means no restart should fire.
- What about sensitive data? Encrypt variable files containing passwords with
ansible-vault encrypt, decrypt with a passphrase at run time, and never commit plaintext secrets to the repository. - How do I preview before running? Use
--checkfor a dry run and--diffto see how files would change, then execute for real once you are sure. - How do I isolate multiple environments? Use separate inventory files (e.g., production.ini, staging.ini) with
group_varsto differentiate environments — never hardcode environment-specific values in code.
Reference: Ansible documentation https://docs.ansible.com/ ; Ansible Galaxy https://galaxy.ansible.com/