- Fixed 4 broken markdown links (bad relative paths in See Also sections) - Corrected n8n port binding to 127.0.0.1:5678 (matches actual deployment) - Updated SnapRAID article with actual majorhome paths (/majorRAID, disk1-3) - Converted 67 Obsidian wikilinks to relative markdown links or plain text - Added YAML frontmatter to 35 articles missing it entirely - Completed frontmatter on 8 articles with missing fields Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
209 lines
5.1 KiB
Markdown
209 lines
5.1 KiB
Markdown
---
|
|
title: "Ansible Getting Started: Inventory, Playbooks, and Ad-Hoc Commands"
|
|
domain: linux
|
|
category: shell-scripting
|
|
tags: [ansible, automation, infrastructure, linux, idempotent]
|
|
status: published
|
|
created: 2026-03-08
|
|
updated: 2026-03-08
|
|
---
|
|
|
|
# Ansible Getting Started: Inventory, Playbooks, and Ad-Hoc Commands
|
|
|
|
Ansible is how I manage infrastructure at scale — or even just across a handful of machines. You write what you want the end state to look like, Ansible figures out how to get there. No agents needed on the remote machines, just SSH.
|
|
|
|
## The Short Answer
|
|
|
|
```bash
|
|
# Install Ansible
|
|
pip install ansible
|
|
|
|
# Run a one-off command on all hosts
|
|
ansible all -i inventory.ini -m ping
|
|
|
|
# Run a playbook
|
|
ansible-playbook -i inventory.ini site.yml
|
|
```
|
|
|
|
## Core Concepts
|
|
|
|
**Inventory** — the list of machines Ansible manages. Can be a static file or dynamically generated.
|
|
|
|
**Playbook** — a YAML file describing tasks to run on hosts. The main thing you write.
|
|
|
|
**Module** — the building blocks of tasks. `apt`, `dnf`, `service`, `copy`, `template`, `user`, etc. Ansible has modules for almost everything.
|
|
|
|
**Idempotency** — run the same playbook ten times, the result is the same as running it once. Ansible modules are designed this way. This matters because it means you can re-run playbooks safely without side effects.
|
|
|
|
## Inventory File
|
|
|
|
```ini
|
|
# inventory.ini
|
|
|
|
[webservers]
|
|
web1.example.com
|
|
web2.example.com ansible_user=admin
|
|
|
|
[databases]
|
|
db1.example.com ansible_user=ubuntu ansible_port=2222
|
|
|
|
[all:vars]
|
|
ansible_user=myuser
|
|
ansible_ssh_private_key_file=~/.ssh/id_ed25519
|
|
```
|
|
|
|
Test connectivity:
|
|
|
|
```bash
|
|
ansible all -i inventory.ini -m ping
|
|
```
|
|
|
|
A successful response looks like:
|
|
|
|
```
|
|
web1.example.com | SUCCESS => {
|
|
"ping": "pong"
|
|
}
|
|
```
|
|
|
|
## Ad-Hoc Commands
|
|
|
|
For quick one-offs without writing a playbook:
|
|
|
|
```bash
|
|
# Run a shell command
|
|
ansible all -i inventory.ini -m shell -a "uptime"
|
|
|
|
# Install a package
|
|
ansible webservers -i inventory.ini -m apt -a "name=nginx state=present" --become
|
|
|
|
# Restart a service
|
|
ansible webservers -i inventory.ini -m service -a "name=nginx state=restarted" --become
|
|
|
|
# Copy a file
|
|
ansible all -i inventory.ini -m copy -a "src=./myfile dest=/tmp/myfile"
|
|
```
|
|
|
|
`--become` escalates to sudo.
|
|
|
|
## Writing a Playbook
|
|
|
|
```yaml
|
|
---
|
|
# site.yml
|
|
- name: Configure web servers
|
|
hosts: webservers
|
|
become: true
|
|
|
|
vars:
|
|
app_port: 8080
|
|
|
|
tasks:
|
|
- name: Update apt cache
|
|
ansible.builtin.apt:
|
|
update_cache: true
|
|
cache_valid_time: 3600
|
|
|
|
- name: Install nginx
|
|
ansible.builtin.apt:
|
|
name: nginx
|
|
state: present
|
|
|
|
- name: Start and enable nginx
|
|
ansible.builtin.service:
|
|
name: nginx
|
|
state: started
|
|
enabled: true
|
|
|
|
- name: Deploy config file
|
|
ansible.builtin.template:
|
|
src: templates/nginx.conf.j2
|
|
dest: /etc/nginx/nginx.conf
|
|
owner: root
|
|
group: root
|
|
mode: '0644'
|
|
notify: Reload nginx
|
|
|
|
handlers:
|
|
- name: Reload nginx
|
|
ansible.builtin.service:
|
|
name: nginx
|
|
state: reloaded
|
|
```
|
|
|
|
Run it:
|
|
|
|
```bash
|
|
ansible-playbook -i inventory.ini site.yml
|
|
|
|
# Dry run — shows what would change without doing it
|
|
ansible-playbook -i inventory.ini site.yml --check
|
|
|
|
# Verbose output
|
|
ansible-playbook -i inventory.ini site.yml -v
|
|
```
|
|
|
|
## Handlers
|
|
|
|
Handlers run at the end of a play, only if notified. The canonical use is "reload service after config change":
|
|
|
|
```yaml
|
|
tasks:
|
|
- name: Deploy config
|
|
ansible.builtin.template:
|
|
src: templates/app.conf.j2
|
|
dest: /etc/app/app.conf
|
|
notify: Restart app
|
|
|
|
handlers:
|
|
- name: Restart app
|
|
ansible.builtin.service:
|
|
name: myapp
|
|
state: restarted
|
|
```
|
|
|
|
If the config file didn't change (idempotent — it was already in the right state), the notify never fires and the service isn't restarted.
|
|
|
|
## Roles
|
|
|
|
Once playbooks get complex, organize them into roles:
|
|
|
|
```
|
|
roles/
|
|
webserver/
|
|
tasks/
|
|
main.yml
|
|
handlers/
|
|
main.yml
|
|
templates/
|
|
nginx.conf.j2
|
|
defaults/
|
|
main.yml
|
|
```
|
|
|
|
Use a role in a playbook:
|
|
|
|
```yaml
|
|
- name: Set up web servers
|
|
hosts: webservers
|
|
become: true
|
|
roles:
|
|
- webserver
|
|
```
|
|
|
|
Roles keep things organized and reusable across projects.
|
|
|
|
## Gotchas & Notes
|
|
|
|
- **YAML indentation matters.** Two spaces is standard. Tab characters will break your playbooks.
|
|
- **`--check` is your friend.** Always dry-run against production before applying changes.
|
|
- **SSH key access is required.** Ansible connects over SSH — password auth works but key auth is what you want for automation.
|
|
- **`gather_facts: false`** speeds up playbooks when you don't need host facts (OS, IP, etc.). Add it at the play level for simple playbooks.
|
|
- **Ansible is not idempotent by magic.** Shell and command modules run every time regardless of state. Use the appropriate module (`apt`, `service`, `file`, etc.) instead of `shell` whenever possible.
|
|
- **The `ansible-lint` tool** catches common mistakes before they run. Worth adding to your workflow.
|
|
|
|
## See Also
|
|
|
|
- [managing-linux-services-systemd-ansible](../process-management/managing-linux-services-systemd-ansible.md)
|
|
- [linux-server-hardening-checklist](../../02-selfhosting/security/linux-server-hardening-checklist.md)
|