Files
MajorWiki/01-linux/shell-scripting/ansible-getting-started.md
MajorLinux 6592eb4fea wiki: audit fixes — broken links, wikilinks, frontmatter, stale content (66 files)
- 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>
2026-04-02 11:16:29 -04:00

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)