docs: add ansible howto collection
Four reference files covering: - ansible.md — core commands, ansible.cfg, key settings - ansible-inventory.md — static/dynamic inventory, directory layout - ansible-variables.md — 22-level precedence, scoping, merge behavior - ansible-roles.md — structure, defaults vs vars, dependencies
This commit is contained in:
4
TASKS.md
4
TASKS.md
@@ -3,8 +3,8 @@
|
||||
## Current
|
||||
|
||||
- [x] Scaffold project structure
|
||||
- [ ] Create topic template
|
||||
- [ ] Write first topic
|
||||
- [x] Create topic template
|
||||
- [x] Write first topic (ansible — 4 files)
|
||||
|
||||
## Backlog
|
||||
|
||||
|
||||
2
TODO.md
2
TODO.md
@@ -3,7 +3,7 @@
|
||||
## Topics to Write
|
||||
|
||||
- [ ] git — common workflows, rebase, stash, bisect
|
||||
- [ ] ansible — playbook patterns, inventory, vault
|
||||
- [x] ansible — playbook patterns, inventory, vault, variables, roles
|
||||
- [ ] podman — build, run, compose, volumes
|
||||
- [ ] jq — filters, select, map, slurp
|
||||
- [ ] curl — headers, auth, methods, output
|
||||
|
||||
214
topics/ansible-inventory.md
Normal file
214
topics/ansible-inventory.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# Ansible Inventory
|
||||
|
||||
> Define which hosts to manage, how to group them, and where to find them.
|
||||
|
||||
## Inventory Formats
|
||||
|
||||
### INI Format
|
||||
|
||||
```ini
|
||||
# inventory/hosts.ini
|
||||
|
||||
[webservers]
|
||||
web1.example.com
|
||||
web2.example.com ansible_port=2222
|
||||
|
||||
[dbservers]
|
||||
db1.example.com ansible_user=postgres
|
||||
db[1:3].example.com # db1, db2, db3
|
||||
|
||||
[loadbalancers]
|
||||
lb1.example.com
|
||||
|
||||
# Group of groups
|
||||
[production:children]
|
||||
webservers
|
||||
dbservers
|
||||
loadbalancers
|
||||
|
||||
# Group variables
|
||||
[webservers:vars]
|
||||
http_port=8080
|
||||
app_env=production
|
||||
|
||||
[all:vars]
|
||||
ansible_python_interpreter=/usr/bin/python3
|
||||
```
|
||||
|
||||
### YAML Format (preferred)
|
||||
|
||||
```yaml
|
||||
# inventory/hosts.yml
|
||||
all:
|
||||
vars:
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
children:
|
||||
production:
|
||||
children:
|
||||
webservers:
|
||||
hosts:
|
||||
web1.example.com:
|
||||
web2.example.com:
|
||||
ansible_port: 2222
|
||||
vars:
|
||||
http_port: 8080
|
||||
dbservers:
|
||||
hosts:
|
||||
db1.example.com:
|
||||
ansible_user: postgres
|
||||
loadbalancers:
|
||||
hosts:
|
||||
lb1.example.com:
|
||||
```
|
||||
|
||||
## Directory Layout
|
||||
|
||||
```
|
||||
inventory/
|
||||
├── hosts.yml # Host definitions
|
||||
├── group_vars/
|
||||
│ ├── all.yml # Vars for every host
|
||||
│ ├── all/ # Dir form (merged, alphabetical)
|
||||
│ │ ├── common.yml
|
||||
│ │ └── vault.yml # Encrypted secrets
|
||||
│ ├── webservers.yml # Vars for webservers group
|
||||
│ └── production.yml # Vars for production parent group
|
||||
└── host_vars/
|
||||
├── web1.example.com.yml
|
||||
└── db1.example.com/ # Dir form
|
||||
├── main.yml
|
||||
└── vault.yml
|
||||
```
|
||||
|
||||
### Multiple Environments
|
||||
|
||||
```
|
||||
inventories/
|
||||
├── staging/
|
||||
│ ├── hosts.yml
|
||||
│ ├── group_vars/
|
||||
│ └── host_vars/
|
||||
└── production/
|
||||
├── hosts.yml
|
||||
├── group_vars/
|
||||
└── host_vars/
|
||||
```
|
||||
|
||||
```bash
|
||||
ansible-playbook site.yml -i inventories/staging/
|
||||
ansible-playbook site.yml -i inventories/production/
|
||||
```
|
||||
|
||||
## Dynamic Inventories
|
||||
|
||||
### Script-Based (legacy)
|
||||
|
||||
A script that outputs JSON when called with `--list` or `--host <name>`.
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env python3
|
||||
# inventory/dynamic.py — must be executable (chmod +x)
|
||||
import json, sys
|
||||
|
||||
def get_inventory():
|
||||
return {
|
||||
"webservers": {
|
||||
"hosts": ["web1.example.com", "web2.example.com"],
|
||||
"vars": {"http_port": 8080}
|
||||
},
|
||||
"dbservers": {
|
||||
"hosts": ["db1.example.com"]
|
||||
},
|
||||
"_meta": {
|
||||
"hostvars": {
|
||||
"web1.example.com": {"ansible_port": 22},
|
||||
"db1.example.com": {"ansible_user": "postgres"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if "--list" in sys.argv:
|
||||
print(json.dumps(get_inventory(), indent=2))
|
||||
elif "--host" in sys.argv:
|
||||
host = sys.argv[sys.argv.index("--host") + 1]
|
||||
hostvars = get_inventory().get("_meta", {}).get("hostvars", {})
|
||||
print(json.dumps(hostvars.get(host, {})))
|
||||
```
|
||||
|
||||
The `_meta.hostvars` key avoids per-host `--host` calls (efficiency).
|
||||
|
||||
### Plugin-Based (modern, preferred)
|
||||
|
||||
```yaml
|
||||
# inventory/aws_ec2.yml — filename must end with the plugin suffix
|
||||
plugin: amazon.aws.aws_ec2
|
||||
regions:
|
||||
- eu-west-1
|
||||
keyed_groups:
|
||||
- key: tags.Environment
|
||||
prefix: env
|
||||
- key: instance_type
|
||||
prefix: type
|
||||
filters:
|
||||
tag:Managed: ansible
|
||||
compose:
|
||||
ansible_host: private_ip_address
|
||||
```
|
||||
|
||||
Common inventory plugins:
|
||||
|
||||
| Plugin | Source | Suffix |
|
||||
|-------------------------------|------------|-----------------|
|
||||
| `amazon.aws.aws_ec2` | AWS EC2 | `aws_ec2.yml` |
|
||||
| `azure.azcollection.azure_rm`| Azure | `azure_rm.yml` |
|
||||
| `google.cloud.gcp_compute` | GCP | `gcp.yml` |
|
||||
| `community.general.proxmox` | Proxmox | `proxmox.yml` |
|
||||
| `community.docker.docker_containers` | Docker | `docker.yml` |
|
||||
| `ansible.builtin.constructed` | Derived | `constructed.yml`|
|
||||
|
||||
```bash
|
||||
# Verify dynamic inventory output
|
||||
ansible-inventory -i inventory/aws_ec2.yml --graph
|
||||
ansible-inventory -i inventory/aws_ec2.yml --list
|
||||
```
|
||||
|
||||
### Constructed Inventory (compose groups from existing data)
|
||||
|
||||
```yaml
|
||||
# inventory/constructed.yml
|
||||
plugin: ansible.builtin.constructed
|
||||
strict: false
|
||||
groups:
|
||||
is_debian: ansible_os_family == "Debian"
|
||||
is_large: ansible_memtotal_mb > 8192
|
||||
keyed_groups:
|
||||
- key: ansible_distribution | lower
|
||||
prefix: os
|
||||
```
|
||||
|
||||
## Special Variables
|
||||
|
||||
| Variable | Purpose |
|
||||
|-----------------------|------------------------------------|
|
||||
| `ansible_host` | IP/hostname to connect to |
|
||||
| `ansible_port` | SSH port (default: 22) |
|
||||
| `ansible_user` | SSH user |
|
||||
| `ansible_ssh_private_key_file` | SSH key path |
|
||||
| `ansible_become` | Enable privilege escalation |
|
||||
| `ansible_connection` | Connection type (`ssh`, `local`) |
|
||||
| `ansible_python_interpreter` | Python path on target |
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Script inventories must be executable (`chmod +x`) and have a shebang
|
||||
- Plugin inventory files must end with the correct suffix or be listed in `enable_plugins`
|
||||
- `group_vars/` directory must sit next to the inventory file, or at playbook level
|
||||
- YAML inventory: hosts need trailing colon even with no vars (`web1.example.com:`)
|
||||
- Groups named `all` and `ungrouped` are implicit — don't redefine them
|
||||
- Directory form `group_vars/all/` merges files alphabetically — name carefully
|
||||
|
||||
## See Also
|
||||
|
||||
- `ansible` — core commands and ansible.cfg
|
||||
- `ansible-variables` — variable precedence across inventory layers
|
||||
- `ansible-roles` — role structure and organization
|
||||
211
topics/ansible-roles.md
Normal file
211
topics/ansible-roles.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# Ansible Roles
|
||||
|
||||
> Reusable, self-contained units of automation with a standard directory structure.
|
||||
|
||||
## Role Directory Structure
|
||||
|
||||
```
|
||||
roles/
|
||||
└── nginx/
|
||||
├── defaults/
|
||||
│ └── main.yml # Low-precedence defaults (user overrides these)
|
||||
├── vars/
|
||||
│ └── main.yml # High-precedence vars (internal to role)
|
||||
├── tasks/
|
||||
│ └── main.yml # Entry point for tasks
|
||||
├── handlers/
|
||||
│ └── main.yml # Handlers triggered by notify
|
||||
├── templates/
|
||||
│ └── nginx.conf.j2 # Jinja2 templates
|
||||
├── files/
|
||||
│ └── index.html # Static files for copy module
|
||||
├── meta/
|
||||
│ └── main.yml # Dependencies, galaxy metadata
|
||||
└── README.md
|
||||
```
|
||||
|
||||
All directories are optional — include only what the role needs.
|
||||
|
||||
### What Goes Where
|
||||
|
||||
| Directory | Purpose | Precedence |
|
||||
|-------------|--------------------------------|-------------|
|
||||
| `defaults/` | Default variable values | **Lowest** — meant to be overridden |
|
||||
| `vars/` | Internal role variables | **High** — not easily overridden |
|
||||
| `tasks/` | Main task list | Entry point: `tasks/main.yml` |
|
||||
| `handlers/` | Restart/reload triggers | Run once at end of play |
|
||||
| `templates/` | Jinja2 files (`.j2`) | Referenced as `template: nginx.conf.j2` |
|
||||
| `files/` | Static files | Referenced as `copy: src=index.html` |
|
||||
| `meta/` | Role deps, platform info | Processed before role tasks run |
|
||||
|
||||
## Creating a Role
|
||||
|
||||
```bash
|
||||
# Scaffold with ansible-galaxy
|
||||
ansible-galaxy role init roles/nginx
|
||||
|
||||
# Minimal manual structure
|
||||
mkdir -p roles/nginx/{tasks,defaults,templates,handlers,meta}
|
||||
```
|
||||
|
||||
## Using Roles
|
||||
|
||||
### In a Playbook
|
||||
|
||||
```yaml
|
||||
# site.yml
|
||||
- hosts: webservers
|
||||
roles:
|
||||
- nginx # simple
|
||||
- role: nginx # with params
|
||||
vars:
|
||||
nginx_listen_port: 8080
|
||||
- role: nginx # conditional
|
||||
when: install_nginx | bool
|
||||
- role: nginx # tagged
|
||||
tags: [web, nginx]
|
||||
```
|
||||
|
||||
### With `include_role` / `import_role`
|
||||
|
||||
```yaml
|
||||
tasks:
|
||||
# Static import — processed at parse time
|
||||
- import_role:
|
||||
name: nginx
|
||||
|
||||
# Dynamic include — processed at runtime (supports loops/conditionals)
|
||||
- include_role:
|
||||
name: "{{ item }}"
|
||||
loop:
|
||||
- nginx
|
||||
- certbot
|
||||
```
|
||||
|
||||
| | `import_role` | `include_role` |
|
||||
|---|---|---|
|
||||
| When parsed | Playbook load | Runtime |
|
||||
| Tags/when | Applied to all tasks inside | Applied to include itself |
|
||||
| Loops | Not supported | Supported |
|
||||
| Handlers | Visible globally | Scoped to include |
|
||||
|
||||
## Role Dependencies
|
||||
|
||||
```yaml
|
||||
# roles/nginx/meta/main.yml
|
||||
dependencies:
|
||||
- role: common
|
||||
- role: firewall
|
||||
vars:
|
||||
firewall_allowed_ports:
|
||||
- 80
|
||||
- 443
|
||||
```
|
||||
|
||||
Dependencies run **before** the role's own tasks. Duplicate dependencies are skipped unless `allow_duplicates: true` is set.
|
||||
|
||||
## Example Role: nginx
|
||||
|
||||
```yaml
|
||||
# roles/nginx/defaults/main.yml
|
||||
nginx_listen_port: 80
|
||||
nginx_worker_processes: auto
|
||||
nginx_server_name: "_"
|
||||
nginx_root: /var/www/html
|
||||
nginx_access_log: /var/log/nginx/access.log
|
||||
```
|
||||
|
||||
```yaml
|
||||
# roles/nginx/tasks/main.yml
|
||||
- name: Install nginx
|
||||
apt:
|
||||
name: nginx
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Deploy configuration
|
||||
template:
|
||||
src: nginx.conf.j2
|
||||
dest: /etc/nginx/nginx.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Reload nginx
|
||||
|
||||
- name: Enable and start nginx
|
||||
service:
|
||||
name: nginx
|
||||
state: started
|
||||
enabled: true
|
||||
```
|
||||
|
||||
```yaml
|
||||
# roles/nginx/handlers/main.yml
|
||||
- name: Reload nginx
|
||||
service:
|
||||
name: nginx
|
||||
state: reloaded
|
||||
```
|
||||
|
||||
## Splitting Tasks
|
||||
|
||||
```yaml
|
||||
# roles/nginx/tasks/main.yml
|
||||
- import_tasks: install.yml
|
||||
- import_tasks: configure.yml
|
||||
- import_tasks: service.yml
|
||||
|
||||
# Conditional platform tasks
|
||||
- import_tasks: debian.yml
|
||||
when: ansible_os_family == "Debian"
|
||||
- import_tasks: redhat.yml
|
||||
when: ansible_os_family == "RedHat"
|
||||
```
|
||||
|
||||
## Project Layout with Roles
|
||||
|
||||
```
|
||||
ansible-project/
|
||||
├── ansible.cfg
|
||||
├── site.yml # Master playbook
|
||||
├── webservers.yml # Play for web tier
|
||||
├── dbservers.yml # Play for db tier
|
||||
├── inventory/
|
||||
│ ├── hosts.yml
|
||||
│ ├── group_vars/
|
||||
│ └── host_vars/
|
||||
├── roles/
|
||||
│ ├── common/ # Shared baseline
|
||||
│ ├── nginx/
|
||||
│ ├── postgresql/
|
||||
│ └── requirements.yml # Galaxy dependencies
|
||||
└── collections/
|
||||
└── requirements.yml # Collection dependencies
|
||||
```
|
||||
|
||||
```yaml
|
||||
# roles/requirements.yml
|
||||
- src: geerlingguy.docker
|
||||
version: "6.1.0"
|
||||
- src: geerlingguy.certbot
|
||||
```
|
||||
|
||||
```bash
|
||||
ansible-galaxy install -r roles/requirements.yml -p roles/
|
||||
```
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `defaults/` is for users to override; `vars/` is for role internals — mixing them up breaks precedence
|
||||
- Handlers run at end of play, not after each task — use `meta: flush_handlers` if needed mid-play
|
||||
- Role names must be valid Python identifiers (no hyphens) when used as variable namespaces
|
||||
- `import_role` makes role tasks visible to `--list-tasks`; `include_role` does not
|
||||
- Dependencies declared in `meta/` run every time the role is referenced unless deduplicated
|
||||
- Template paths are relative to `templates/` — use `nginx.conf.j2`, not `templates/nginx.conf.j2`
|
||||
- File paths in `copy` are relative to `files/` — use `index.html`, not `files/index.html`
|
||||
|
||||
## See Also
|
||||
|
||||
- `ansible` — core commands and ansible.cfg
|
||||
- `ansible-variables` — how defaults/ vs vars/ fits into precedence
|
||||
- `ansible-inventory` — inventory structure for role-consuming projects
|
||||
201
topics/ansible-variables.md
Normal file
201
topics/ansible-variables.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# Ansible Variables
|
||||
|
||||
> Variable precedence, inheritance, and scoping — the part everyone gets wrong.
|
||||
|
||||
## Precedence (lowest to highest)
|
||||
|
||||
Ansible merges variables from many sources. **Higher number wins.**
|
||||
|
||||
```
|
||||
1. command line values (for constants, not variables)
|
||||
2. role defaults (roles/x/defaults/main.yml)
|
||||
3. inventory file or script group vars
|
||||
4. inventory group_vars/all
|
||||
5. playbook group_vars/all
|
||||
6. inventory group_vars/*
|
||||
7. playbook group_vars/*
|
||||
8. inventory file or script host vars
|
||||
9. inventory host_vars/*
|
||||
10. playbook host_vars/*
|
||||
11. host facts / cached set_facts
|
||||
12. play vars (vars: in play)
|
||||
13. play vars_prompt
|
||||
14. play vars_files
|
||||
15. role vars (roles/x/vars/main.yml)
|
||||
16. block vars (vars: on block)
|
||||
17. task vars (vars: on task)
|
||||
18. include_vars
|
||||
19. set_facts / registered vars
|
||||
20. role params (role: {role: x, param: val})
|
||||
21. include params
|
||||
22. extra vars (-e / --extra-vars) *** always wins ***
|
||||
```
|
||||
|
||||
### Practical Summary
|
||||
|
||||
| Source | Precedence | Use for |
|
||||
|-------------------------|:----------:|--------------------------------|
|
||||
| Role defaults | Lowest | Sane defaults, meant to be overridden |
|
||||
| `group_vars/all` | Low | Organization-wide defaults |
|
||||
| `group_vars/<group>` | Medium | Environment/group-specific |
|
||||
| `host_vars/<host>` | Medium+ | Host-specific overrides |
|
||||
| Play/block/task `vars:` | High | Playbook-scoped values |
|
||||
| Role vars | High | Role internals, not for override |
|
||||
| `set_fact` | High | Runtime-computed values |
|
||||
| Extra vars (`-e`) | Highest | CLI overrides, CI/CD pipelines |
|
||||
|
||||
## Group Variable Inheritance
|
||||
|
||||
When a host belongs to multiple groups, merge order is:
|
||||
|
||||
1. **`all` group** (lowest)
|
||||
2. **Parent groups** (alphabetical within same level)
|
||||
3. **Child groups** (override parents)
|
||||
4. **Host vars** (override all groups)
|
||||
|
||||
### Controlling Merge Order
|
||||
|
||||
```yaml
|
||||
# inventory/hosts.yml
|
||||
all:
|
||||
children:
|
||||
env_staging:
|
||||
vars:
|
||||
app_env: staging
|
||||
log_level: debug
|
||||
env_production:
|
||||
vars:
|
||||
app_env: production
|
||||
log_level: warn
|
||||
webservers:
|
||||
hosts:
|
||||
web1.example.com: # member of webservers AND env_production
|
||||
vars:
|
||||
http_port: 8080
|
||||
```
|
||||
|
||||
Use `ansible_group_priority` to force ordering (default: 1):
|
||||
|
||||
```yaml
|
||||
# group_vars/env_production.yml
|
||||
ansible_group_priority: 10 # higher = wins over other groups
|
||||
app_env: production
|
||||
```
|
||||
|
||||
## Variable Scoping
|
||||
|
||||
| Scope | Defined in | Visible to |
|
||||
|----------|-----------------------------------|---------------------------|
|
||||
| Global | `extra_vars`, config, env | Everything |
|
||||
| Play | `vars:`, `vars_files:`, `include_vars` | Current play |
|
||||
| Host | Inventory, facts, `set_fact` | That host across plays |
|
||||
| Role | `defaults/`, `vars/` | Role + dependents |
|
||||
| Task | `register`, task-level `vars:` | Subsequent tasks (same host) |
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Layered Defaults
|
||||
|
||||
```yaml
|
||||
# group_vars/all.yml — base defaults
|
||||
ntp_server: ntp.pool.org
|
||||
log_level: info
|
||||
app_port: 8080
|
||||
|
||||
# group_vars/production.yml — override for prod
|
||||
log_level: warn
|
||||
|
||||
# host_vars/web1.example.com.yml — host-specific
|
||||
app_port: 9090
|
||||
```
|
||||
|
||||
Result for `web1` in `production`: `ntp_server=ntp.pool.org`, `log_level=warn`, `app_port=9090`.
|
||||
|
||||
### Role Defaults vs Role Vars
|
||||
|
||||
```yaml
|
||||
# roles/nginx/defaults/main.yml — LOW precedence, user overrides these
|
||||
nginx_worker_processes: auto
|
||||
nginx_listen_port: 80
|
||||
|
||||
# roles/nginx/vars/main.yml — HIGH precedence, internal to role
|
||||
nginx_conf_path: /etc/nginx/nginx.conf # don't let users change this
|
||||
nginx_user: www-data
|
||||
```
|
||||
|
||||
**Rule of thumb:** put everything in `defaults/` unless the role breaks if the value changes.
|
||||
|
||||
### Merging Dictionaries and Lists
|
||||
|
||||
By default, Ansible **replaces** (does not merge) dicts and lists. To merge:
|
||||
|
||||
```ini
|
||||
# ansible.cfg
|
||||
[defaults]
|
||||
hash_behaviour = merge # CAUTION: global, can cause surprises
|
||||
```
|
||||
|
||||
Prefer explicit merging with `combine` filter:
|
||||
|
||||
```yaml
|
||||
- name: Merge defaults with overrides
|
||||
set_fact:
|
||||
final_config: "{{ default_config | combine(override_config, recursive=True) }}"
|
||||
```
|
||||
|
||||
### Registered Variables
|
||||
|
||||
```yaml
|
||||
- name: Check service status
|
||||
command: systemctl is-active nginx
|
||||
register: nginx_status
|
||||
ignore_errors: true
|
||||
|
||||
- name: Restart if stopped
|
||||
service:
|
||||
name: nginx
|
||||
state: started
|
||||
when: nginx_status.rc != 0
|
||||
```
|
||||
|
||||
### Vault-Encrypted Variables
|
||||
|
||||
```yaml
|
||||
# group_vars/all/vault.yml (encrypted)
|
||||
vault_db_password: "s3cret"
|
||||
|
||||
# group_vars/all/main.yml (references vault)
|
||||
db_password: "{{ vault_db_password }}"
|
||||
```
|
||||
|
||||
Prefix vault variables with `vault_` so it's clear where values originate.
|
||||
|
||||
## Debugging Variables
|
||||
|
||||
```bash
|
||||
# Show all vars for a host
|
||||
ansible -m debug -a "var=hostvars[inventory_hostname]" web1.example.com
|
||||
|
||||
# Show specific variable
|
||||
ansible -m debug -a "var=http_port" web1.example.com
|
||||
|
||||
# In a playbook
|
||||
- debug: var=ansible_facts
|
||||
- debug: msg="{{ http_port }} on {{ inventory_hostname }}"
|
||||
```
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `extra_vars` always win — they override even role `vars/`. Don't use `-e` for defaults
|
||||
- `set_fact` persists for the host across plays in the same run (not across runs unless cached)
|
||||
- `hash_behaviour = merge` is global and rarely what you want — prefer `combine` filter
|
||||
- Variable names are flat — `foo.bar` is dict access, not a separate variable
|
||||
- Jinja2 variables in `when:` don't need `{{ }}` — just `when: my_var == "x"`
|
||||
- Undefined variables fail by default. Use `{{ my_var | default('fallback') }}`
|
||||
- `group_vars/` at playbook level AND inventory level both load — watch for conflicts
|
||||
|
||||
## See Also
|
||||
|
||||
- `ansible` — core commands and ansible.cfg
|
||||
- `ansible-inventory` — inventory structure, dynamic inventories
|
||||
- `ansible-roles` — role structure and defaults vs vars
|
||||
127
topics/ansible.md
Normal file
127
topics/ansible.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Ansible
|
||||
|
||||
> Agentless automation — push-based configuration management over SSH.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
| Concept | Description |
|
||||
|-------------|--------------------------------------------------|
|
||||
| Inventory | Hosts and groups to manage |
|
||||
| Playbook | YAML file defining tasks to run on hosts |
|
||||
| Role | Reusable unit of tasks, vars, templates, handlers|
|
||||
| Task | Single action (install pkg, copy file, etc.) |
|
||||
| Module | Built-in unit of work (`apt`, `copy`, `template`)|
|
||||
| Handler | Task triggered by `notify`, runs once at end |
|
||||
| Facts | Auto-gathered host info (`ansible_os_family`) |
|
||||
| Vault | Encrypted secrets storage |
|
||||
|
||||
## Common Commands
|
||||
|
||||
```bash
|
||||
# Run ad-hoc command on all hosts
|
||||
ansible all -m ping
|
||||
ansible all -m shell -a "uptime"
|
||||
|
||||
# Run playbook
|
||||
ansible-playbook site.yml
|
||||
ansible-playbook site.yml -l webservers # limit to group
|
||||
ansible-playbook site.yml --tags deploy # run tagged tasks only
|
||||
ansible-playbook site.yml --skip-tags debug # skip tagged tasks
|
||||
ansible-playbook site.yml -e "version=2.1" # extra vars (highest precedence)
|
||||
|
||||
# Dry run / check mode
|
||||
ansible-playbook site.yml --check --diff
|
||||
|
||||
# List hosts that would be affected
|
||||
ansible-playbook site.yml --list-hosts
|
||||
ansible-playbook site.yml --list-tasks
|
||||
ansible-playbook site.yml --list-tags
|
||||
|
||||
# Vault
|
||||
ansible-vault create secrets.yml
|
||||
ansible-vault edit secrets.yml
|
||||
ansible-vault encrypt existing.yml
|
||||
ansible-vault decrypt existing.yml
|
||||
ansible-playbook site.yml --ask-vault-pass
|
||||
ansible-playbook site.yml --vault-password-file ~/.vault_pass
|
||||
|
||||
# Galaxy (roles/collections)
|
||||
ansible-galaxy role install geerlingguy.docker
|
||||
ansible-galaxy role init my_role
|
||||
ansible-galaxy collection install community.general
|
||||
|
||||
# Debug / info
|
||||
ansible --version
|
||||
ansible-config dump --only-changed
|
||||
ansible-inventory --graph
|
||||
ansible-inventory --host <hostname>
|
||||
```
|
||||
|
||||
## ansible.cfg
|
||||
|
||||
Config is loaded from the **first file found** in this order:
|
||||
|
||||
1. `$ANSIBLE_CONFIG` (env variable)
|
||||
2. `./ansible.cfg` (current directory)
|
||||
3. `~/.ansible.cfg` (home directory)
|
||||
4. `/etc/ansible/ansible.cfg` (system-wide)
|
||||
|
||||
### Practical ansible.cfg
|
||||
|
||||
```ini
|
||||
[defaults]
|
||||
inventory = ./inventory/
|
||||
roles_path = ./roles/
|
||||
collections_path = ./collections/
|
||||
remote_user = deploy
|
||||
private_key_file = ~/.ssh/id_ed25519
|
||||
host_key_checking = False
|
||||
retry_files_enabled = False
|
||||
stdout_callback = yaml
|
||||
callback_enabled = timer, profile_tasks
|
||||
forks = 20
|
||||
timeout = 30
|
||||
gathering = smart
|
||||
fact_caching = jsonfile
|
||||
fact_caching_connection = /tmp/ansible_facts
|
||||
fact_caching_timeout = 3600
|
||||
vault_password_file = ~/.vault_pass
|
||||
interpreter_python = auto_silent
|
||||
log_path = ./ansible.log
|
||||
|
||||
[privilege_escalation]
|
||||
become = True
|
||||
become_method = sudo
|
||||
become_user = root
|
||||
become_ask_pass = False
|
||||
|
||||
[ssh_connection]
|
||||
pipelining = True
|
||||
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no
|
||||
control_path_dir = /tmp/.ansible-cp
|
||||
```
|
||||
|
||||
### Key Settings Explained
|
||||
|
||||
| Setting | Effect |
|
||||
|------------------------|-------------------------------------------------|
|
||||
| `forks` | Parallel host connections (default: 5) |
|
||||
| `pipelining` | Reduces SSH operations, major speed gain |
|
||||
| `gathering = smart` | Cache facts, skip re-gathering |
|
||||
| `stdout_callback=yaml` | Human-readable output instead of JSON |
|
||||
| `fact_caching` | Persist facts between runs |
|
||||
| `retry_files_enabled` | Disable `.retry` file clutter |
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Config in current dir (`./ansible.cfg`) is ignored if the directory is world-writable
|
||||
- `become: true` in `ansible.cfg` applies globally — prefer setting it per play
|
||||
- `host_key_checking = False` is fine for labs, not for production
|
||||
- `forks` above ~50 can exhaust file descriptors on the control node
|
||||
- `pipelining` requires `requiretty` disabled in sudoers on targets
|
||||
|
||||
## See Also
|
||||
|
||||
- `ansible-inventory` — inventory structure and dynamic inventories
|
||||
- `ansible-variables` — variable precedence and inheritance
|
||||
- `ansible-roles` — role structure and best practices
|
||||
Reference in New Issue
Block a user