Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:47:38 +08:00
commit 18faa0569e
47 changed files with 7969 additions and 0 deletions

View File

@@ -0,0 +1,294 @@
# Ansible Docker Compose Patterns
Common patterns for managing Docker Compose stacks with Ansible.
## Project Structure
```
roles/
└── docker_app/
├── tasks/
│ └── main.yml
├── templates/
│ ├── docker-compose.yml.j2
│ └── .env.j2
├── defaults/
│ └── main.yml
└── handlers/
└── main.yml
```
## Role Template
### defaults/main.yml
```yaml
app_name: myapp
app_version: latest
app_port: 8080
app_data_dir: "/opt/{{ app_name }}"
# Compose settings
compose_pull: always
compose_recreate: auto # auto, always, never
# Resource limits
app_memory_limit: 512M
app_cpu_limit: 1.0
```
### templates/docker-compose.yml.j2
```yaml
name: {{ app_name }}
services:
app:
image: {{ app_image }}:{{ app_version }}
container_name: {{ app_name }}
restart: unless-stopped
ports:
- "{{ app_port }}:{{ app_internal_port | default(app_port) }}"
volumes:
- {{ app_data_dir }}/data:/app/data
{% if app_config_file is defined %}
- {{ app_data_dir }}/config:/app/config:ro
{% endif %}
environment:
TZ: {{ timezone | default('UTC') }}
{% for key, value in app_env.items() %}
{{ key }}: "{{ value }}"
{% endfor %}
{% if app_memory_limit is defined or app_cpu_limit is defined %}
deploy:
resources:
limits:
{% if app_memory_limit is defined %}
memory: {{ app_memory_limit }}
{% endif %}
{% if app_cpu_limit is defined %}
cpus: '{{ app_cpu_limit }}'
{% endif %}
{% endif %}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:{{ app_internal_port | default(app_port) }}/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
- {{ app_network | default('default') }}
{% if app_network is defined %}
networks:
{{ app_network }}:
external: true
{% endif %}
```
### tasks/main.yml
```yaml
---
- name: Create application directory
ansible.builtin.file:
path: "{{ app_data_dir }}"
state: directory
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0755'
- name: Create data directories
ansible.builtin.file:
path: "{{ app_data_dir }}/{{ item }}"
state: directory
owner: "{{ ansible_user }}"
mode: '0755'
loop:
- data
- config
- name: Deploy compose file
ansible.builtin.template:
src: docker-compose.yml.j2
dest: "{{ app_data_dir }}/docker-compose.yml"
owner: "{{ ansible_user }}"
mode: '0644'
notify: Redeploy stack
- name: Deploy environment file
ansible.builtin.template:
src: .env.j2
dest: "{{ app_data_dir }}/.env"
owner: "{{ ansible_user }}"
mode: '0600'
notify: Redeploy stack
when: app_secrets is defined
- name: Ensure stack is running
community.docker.docker_compose_v2:
project_src: "{{ app_data_dir }}"
state: present
pull: "{{ compose_pull }}"
recreate: "{{ compose_recreate }}"
register: compose_result
- name: Show deployment result
ansible.builtin.debug:
msg: "Deployed {{ compose_result.containers | length }} containers"
when: compose_result is changed
```
### handlers/main.yml
```yaml
---
- name: Redeploy stack
community.docker.docker_compose_v2:
project_src: "{{ app_data_dir }}"
state: present
pull: always
recreate: always
```
## Multi-Service Stack
### templates/docker-compose.yml.j2 (full stack)
```yaml
name: {{ stack_name }}
services:
app:
image: {{ app_image }}:{{ app_version }}
restart: unless-stopped
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
environment:
DATABASE_URL: "postgres://{{ db_user }}:{{ db_password }}@db:5432/{{ db_name }}"
REDIS_URL: "redis://redis:6379"
networks:
- internal
- web
db:
image: postgres:15
restart: unless-stopped
volumes:
- db_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: {{ db_user }}
POSTGRES_PASSWORD: {{ db_password }}
POSTGRES_DB: {{ db_name }}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U {{ db_user }}"]
interval: 5s
timeout: 5s
retries: 5
networks:
- internal
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
- redis_data:/data
networks:
- internal
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- "{{ http_port | default(80) }}:80"
- "{{ https_port | default(443) }}:443"
volumes:
- {{ app_data_dir }}/nginx/conf.d:/etc/nginx/conf.d:ro
- {{ app_data_dir }}/nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- app
networks:
- web
networks:
internal:
driver: bridge
web:
driver: bridge
volumes:
db_data:
redis_data:
```
## Zero-Downtime Update
```yaml
- name: Zero-downtime update
hosts: docker_hosts
serial: 1 # One host at a time
tasks:
- name: Pull new image
community.docker.docker_image:
name: "{{ app_image }}"
tag: "{{ app_version }}"
source: pull
- name: Drain connections (if load balanced)
# ... remove from load balancer ...
- name: Update stack
community.docker.docker_compose_v2:
project_src: "{{ app_data_dir }}"
state: present
recreate: always
- name: Wait for health
ansible.builtin.uri:
url: "http://localhost:{{ app_port }}/health"
status_code: 200
register: health
until: health.status == 200
retries: 30
delay: 2
- name: Restore to load balancer
# ... add back to load balancer ...
```
## Secrets Management
### With ansible-vault
```yaml
# group_vars/secrets.yml (encrypted)
app_secrets:
DB_PASSWORD: supersecret
API_KEY: abc123
JWT_SECRET: longsecret
```
```yaml
# templates/.env.j2
{% for key, value in app_secrets.items() %}
{{ key }}={{ value }}
{% endfor %}
```
### With external secrets
```yaml
- name: Fetch secret from 1Password
ansible.builtin.set_fact:
db_password: "{{ lookup('community.general.onepassword', 'database', field='password') }}"
- name: Deploy with secret
community.docker.docker_compose_v2:
project_src: "{{ app_data_dir }}"
env_files:
- "{{ app_data_dir }}/.env"
state: present
```

View File

@@ -0,0 +1,307 @@
# Docker Deployment with Ansible
Managing Docker containers and compose stacks via Ansible.
## Collection Setup
```bash
ansible-galaxy collection install community.docker
```
## Compose Deployment (Recommended)
### Deploy from local compose file
```yaml
- name: Deploy application stack
hosts: docker_hosts
become: true
tasks:
- name: Create project directory
ansible.builtin.file:
path: /opt/myapp
state: directory
owner: "{{ ansible_user }}"
mode: '0755'
- name: Copy compose file
ansible.builtin.template:
src: docker-compose.yml.j2
dest: /opt/myapp/docker-compose.yml
owner: "{{ ansible_user }}"
mode: '0644'
- name: Copy environment file
ansible.builtin.template:
src: .env.j2
dest: /opt/myapp/.env
owner: "{{ ansible_user }}"
mode: '0600'
- name: Deploy with compose
community.docker.docker_compose_v2:
project_src: /opt/myapp
state: present
pull: always
register: deploy_result
- name: Show deployed services
ansible.builtin.debug:
var: deploy_result.containers
```
### Compose operations
```yaml
# Pull latest images and recreate
- name: Update stack
community.docker.docker_compose_v2:
project_src: /opt/myapp
state: present
pull: always
recreate: always
# Stop stack (keep volumes)
- name: Stop stack
community.docker.docker_compose_v2:
project_src: /opt/myapp
state: stopped
# Remove stack
- name: Remove stack
community.docker.docker_compose_v2:
project_src: /opt/myapp
state: absent
remove_volumes: false # Keep data volumes
```
## Container Deployment (Individual)
### Run container
```yaml
- name: Run nginx container
community.docker.docker_container:
name: nginx
image: nginx:1.25
state: started
restart_policy: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- /opt/nginx/html:/usr/share/nginx/html:ro
- /opt/nginx/conf.d:/etc/nginx/conf.d:ro
env:
TZ: "America/Los_Angeles"
labels:
app: web
env: production
- name: Run database
community.docker.docker_container:
name: postgres
image: postgres:15
state: started
restart_policy: unless-stopped
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
env:
POSTGRES_USER: "{{ db_user }}"
POSTGRES_PASSWORD: "{{ db_password }}"
POSTGRES_DB: "{{ db_name }}"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U {{ db_user }}"]
interval: 10s
timeout: 5s
retries: 5
```
### Container lifecycle
```yaml
# Stop container
- name: Stop container
community.docker.docker_container:
name: myapp
state: stopped
# Restart container
- name: Restart container
community.docker.docker_container:
name: myapp
state: started
restart: true
# Remove container
- name: Remove container
community.docker.docker_container:
name: myapp
state: absent
# Force recreate
- name: Recreate container
community.docker.docker_container:
name: myapp
image: myapp:latest
state: started
recreate: true
```
## Image Management
```yaml
# Pull image
- name: Pull latest image
community.docker.docker_image:
name: myapp
tag: latest
source: pull
force_source: true # Always check for updates
# Build from Dockerfile
- name: Build image
community.docker.docker_image:
name: myapp
tag: "{{ version }}"
source: build
build:
path: /opt/myapp
dockerfile: Dockerfile
pull: true # Pull base image updates
# Remove image
- name: Remove old images
community.docker.docker_image:
name: myapp
tag: old
state: absent
```
## Network Management
```yaml
# Create network
- name: Create app network
community.docker.docker_network:
name: app_network
driver: bridge
ipam_config:
- subnet: 172.20.0.0/16
gateway: 172.20.0.1
# Create macvlan network
- name: Create macvlan network
community.docker.docker_network:
name: lan
driver: macvlan
driver_options:
parent: eth0
ipam_config:
- subnet: 192.168.1.0/24
gateway: 192.168.1.1
# Attach container to network
- name: Run container on network
community.docker.docker_container:
name: myapp
image: myapp:latest
networks:
- name: app_network
ipv4_address: 172.20.0.10
```
## Volume Management
```yaml
# Create named volume
- name: Create data volume
community.docker.docker_volume:
name: app_data
driver: local
# Create volume with options
- name: Create NFS volume
community.docker.docker_volume:
name: shared_data
driver: local
driver_options:
type: nfs
device: ":/exports/data"
o: "addr=192.168.1.10,rw"
# Backup volume
- name: Backup volume
community.docker.docker_container:
name: backup
image: alpine
command: tar czf /backup/data.tar.gz /data
volumes:
- app_data:/data:ro
- /opt/backups:/backup
auto_remove: true
```
## Common Patterns
### Wait for service health
```yaml
- name: Deploy database
community.docker.docker_container:
name: postgres
image: postgres:15
# ... config ...
- name: Wait for database
community.docker.docker_container_info:
name: postgres
register: db_info
until: db_info.container.State.Health.Status == "healthy"
retries: 30
delay: 2
```
### Rolling update
```yaml
- name: Pull new image
community.docker.docker_image:
name: myapp
tag: "{{ new_version }}"
source: pull
- name: Update container
community.docker.docker_container:
name: myapp
image: "myapp:{{ new_version }}"
state: started
recreate: true
restart_policy: unless-stopped
```
### Cleanup
```yaml
- name: Remove stopped containers
community.docker.docker_prune:
containers: true
containers_filters:
status: exited
- name: Remove unused images
community.docker.docker_prune:
images: true
images_filters:
dangling: true
- name: Full cleanup (careful!)
community.docker.docker_prune:
containers: true
images: true
networks: true
volumes: false # Don't remove data!
builder_cache: true
```

View File

@@ -0,0 +1,292 @@
# Ansible Docker Troubleshooting
Common issues and debugging patterns.
## Module Issues
### "Could not find docker-compose"
```yaml
# docker_compose_v2 requires Docker Compose V2 (plugin)
# NOT standalone docker-compose binary
# Check on target host:
# docker compose version # V2 (plugin)
# docker-compose version # V1 (standalone) - won't work
```
Fix: Install Docker Compose V2:
```yaml
- name: Install Docker Compose plugin
ansible.builtin.apt:
name: docker-compose-plugin
state: present
```
### "Permission denied"
```yaml
# User not in docker group
- name: Add user to docker group
ansible.builtin.user:
name: "{{ ansible_user }}"
groups: docker
append: true
become: true
# Then reconnect or use become
- name: Run with become
community.docker.docker_container:
name: myapp
# ...
become: true
```
### "Cannot connect to Docker daemon"
```yaml
# Docker not running
- name: Ensure Docker is running
ansible.builtin.service:
name: docker
state: started
enabled: true
become: true
# Socket permission issue
# Add become: true to docker tasks
```
## Container Issues
### Get container logs
```yaml
- name: Get logs
community.docker.docker_container_exec:
container: myapp
command: cat /var/log/app.log
register: logs
ignore_errors: true
- name: Alternative - docker logs
ansible.builtin.command: docker logs --tail 100 myapp
register: docker_logs
changed_when: false
- name: Show logs
ansible.builtin.debug:
var: docker_logs.stdout_lines
```
### Container keeps restarting
```yaml
- name: Get container info
community.docker.docker_container_info:
name: myapp
register: container_info
- name: Show restart count
ansible.builtin.debug:
msg: "Restart count: {{ container_info.container.RestartCount }}"
- name: Show last exit code
ansible.builtin.debug:
msg: "Exit code: {{ container_info.container.State.ExitCode }}"
- name: Get logs from dead container
ansible.builtin.command: docker logs myapp
register: crash_logs
changed_when: false
- name: Show crash logs
ansible.builtin.debug:
var: crash_logs.stderr_lines
```
### Health check failing
```yaml
- name: Check health status
community.docker.docker_container_info:
name: myapp
register: info
- name: Show health
ansible.builtin.debug:
msg: |
Status: {{ info.container.State.Health.Status }}
Failing: {{ info.container.State.Health.FailingStreak }}
Log: {{ info.container.State.Health.Log | last }}
# Manual health check
- name: Test health endpoint
ansible.builtin.command: >
docker exec myapp curl -f http://localhost:8080/health
register: health
ignore_errors: true
changed_when: false
```
## Network Issues
### Container can't reach external network
```yaml
- name: Test DNS from container
ansible.builtin.command: docker exec myapp nslookup google.com
register: dns_test
changed_when: false
ignore_errors: true
- name: Test connectivity
ansible.builtin.command: docker exec myapp ping -c 1 8.8.8.8
register: ping_test
changed_when: false
ignore_errors: true
# Check iptables
- name: Check IP forwarding
ansible.builtin.command: sysctl net.ipv4.ip_forward
register: ip_forward
changed_when: false
- name: Enable IP forwarding
ansible.posix.sysctl:
name: net.ipv4.ip_forward
value: '1'
state: present
become: true
when: "'0' in ip_forward.stdout"
```
### Containers can't communicate
```yaml
- name: List networks
community.docker.docker_network_info:
name: "{{ network_name }}"
register: network_info
- name: Show connected containers
ansible.builtin.debug:
var: network_info.network.Containers
# Verify both containers on same network
- name: Test inter-container connectivity
ansible.builtin.command: >
docker exec app ping -c 1 db
register: ping_result
changed_when: false
```
## Compose Issues
### Services not starting in order
```yaml
# depends_on only waits for container start, not readiness
# Use healthcheck + condition
# In compose template:
services:
app:
depends_on:
db:
condition: service_healthy # Wait for health check
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 5s
timeout: 5s
retries: 5
```
### Orphaned containers
```yaml
# Containers from old compose runs
- name: Remove orphans
community.docker.docker_compose_v2:
project_src: /opt/myapp
state: present
remove_orphans: true
```
### Volume data not persisting
```yaml
# Check volume exists
- name: List volumes
ansible.builtin.command: docker volume ls
register: volumes
changed_when: false
# Check volume contents
- name: Inspect volume
ansible.builtin.command: docker volume inspect myapp_data
register: volume_info
changed_when: false
- name: Show volume mountpoint
ansible.builtin.debug:
msg: "{{ (volume_info.stdout | from_json)[0].Mountpoint }}"
```
## Debug Playbook
```yaml
---
- name: Docker debug
hosts: docker_hosts
tasks:
- name: Docker version
ansible.builtin.command: docker version
register: docker_version
changed_when: false
- name: Compose version
ansible.builtin.command: docker compose version
register: compose_version
changed_when: false
- name: List containers
ansible.builtin.command: docker ps -a
register: containers
changed_when: false
- name: List images
ansible.builtin.command: docker images
register: images
changed_when: false
- name: Disk usage
ansible.builtin.command: docker system df
register: disk
changed_when: false
- name: Show all
ansible.builtin.debug:
msg: |
Docker: {{ docker_version.stdout_lines[0] }}
Compose: {{ compose_version.stdout }}
Containers:
{{ containers.stdout }}
Images:
{{ images.stdout }}
Disk:
{{ disk.stdout }}
```
## Common Error Reference
| Error | Cause | Fix |
|-------|-------|-----|
| `docker.errors.DockerException` | Docker not running | Start docker service |
| `docker.errors.APIError: 404` | Container/image not found | Check name/tag |
| `docker.errors.APIError: 409` | Container name conflict | Remove or rename |
| `PermissionError` | Not in docker group | Add user or use become |
| `requests.exceptions.ConnectionError` | Docker socket inaccessible | Check socket permissions |
| `FileNotFoundError: docker-compose` | V1 compose not installed | Use docker_compose_v2 |

View File

@@ -0,0 +1,181 @@
# Ansible Inventory Reference
## YAML Inventory Format
```yaml
all:
children:
webservers:
hosts:
web1:
ansible_host: 192.168.1.10
web2:
ansible_host: 192.168.1.11
vars:
http_port: 80
databases:
hosts:
db1:
ansible_host: 192.168.1.20
db_port: 5432
db2:
ansible_host: 192.168.1.21
production:
children:
webservers:
databases:
vars:
ansible_user: ubuntu
ansible_ssh_private_key_file: ~/.ssh/id_rsa
```
## INI Inventory Format
```ini
[webservers]
web1 ansible_host=192.168.1.10
web2 ansible_host=192.168.1.11
[webservers:vars]
http_port=80
[databases]
db1 ansible_host=192.168.1.20 db_port=5432
db2 ansible_host=192.168.1.21
[production:children]
webservers
databases
[all:vars]
ansible_user=ubuntu
```
## Host Variables
Common host variables:
| Variable | Purpose |
|----------|---------|
| `ansible_host` | IP or hostname to connect |
| `ansible_port` | SSH port (default: 22) |
| `ansible_user` | SSH username |
| `ansible_ssh_private_key_file` | SSH key path |
| `ansible_become` | Enable sudo |
| `ansible_become_user` | Sudo target user |
| `ansible_python_interpreter` | Python path |
## Group Variables
```yaml
# group_vars/webservers.yml
http_port: 80
document_root: /var/www/html
# group_vars/all.yml
ntp_server: time.example.com
dns_servers:
- 8.8.8.8
- 8.8.4.4
```
## Host Variables Files
```yaml
# host_vars/web1.yml
site_name: production-web1
ssl_cert_path: /etc/ssl/certs/web1.crt
```
## Dynamic Groups
```yaml
# In playbook
- hosts: "{{ target_group | default('all') }}"
```
Run with:
```bash
ansible-playbook playbook.yml -e "target_group=webservers"
```
## Patterns
```bash
# All hosts
ansible all -m ping
# Single host
ansible web1 -m ping
# Group
ansible webservers -m ping
# Multiple groups
ansible 'webservers:databases' -m ping
# Intersection (AND)
ansible 'webservers:&production' -m ping
# Exclusion
ansible 'webservers:!web1' -m ping
# Regex
ansible '~web[0-9]+' -m ping
```
## Limit
```bash
# Limit to specific hosts
ansible-playbook playbook.yml -l web1
ansible-playbook playbook.yml --limit web1,web2
ansible-playbook playbook.yml --limit 'webservers:!web3'
```
## Inventory Check
```bash
# List hosts
ansible-inventory --list
ansible-inventory --graph
# Host info
ansible-inventory --host web1
# Validate
ansible all --list-hosts
```
## Multiple Inventories
```bash
# Multiple files
ansible-playbook -i inventory/production -i inventory/staging playbook.yml
# Directory of inventories
ansible-playbook -i inventory/ playbook.yml
```
## Special Groups
| Group | Contains |
|-------|----------|
| `all` | All hosts |
| `ungrouped` | Hosts not in any group |
## Local Connection
```yaml
localhost:
ansible_host: 127.0.0.1
ansible_connection: local
```
Or in inventory:
```ini
localhost ansible_connection=local
```

View File

@@ -0,0 +1,341 @@
# Ansible Modules Reference
## File Operations
### copy
```yaml
- name: Copy file
ansible.builtin.copy:
src: files/config.conf
dest: /etc/app/config.conf
owner: root
group: root
mode: '0644'
backup: true
```
### template
```yaml
- name: Template config
ansible.builtin.template:
src: templates/config.j2
dest: /etc/app/config.conf
owner: root
group: root
mode: '0644'
notify: Restart app
```
### file
```yaml
# Create directory
- name: Create directory
ansible.builtin.file:
path: /opt/app
state: directory
owner: app
group: app
mode: '0755'
# Create symlink
- name: Create symlink
ansible.builtin.file:
src: /opt/app/current
dest: /opt/app/release
state: link
# Delete file
- name: Remove file
ansible.builtin.file:
path: /tmp/old-file
state: absent
```
### lineinfile
```yaml
- name: Ensure line in file
ansible.builtin.lineinfile:
path: /etc/hosts
line: "192.168.1.10 myhost"
state: present
- name: Replace line
ansible.builtin.lineinfile:
path: /etc/config
regexp: '^PORT='
line: 'PORT=8080'
```
## Package Management
### apt (Debian/Ubuntu)
```yaml
- name: Install package
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Install multiple
ansible.builtin.apt:
name:
- nginx
- python3
state: present
- name: Remove package
ansible.builtin.apt:
name: nginx
state: absent
```
### package (Generic)
```yaml
- name: Install package
ansible.builtin.package:
name: httpd
state: present
```
## Service Management
### service
```yaml
- name: Start and enable
ansible.builtin.service:
name: nginx
state: started
enabled: true
- name: Restart
ansible.builtin.service:
name: nginx
state: restarted
- name: Reload
ansible.builtin.service:
name: nginx
state: reloaded
```
### systemd
```yaml
- name: Daemon reload
ansible.builtin.systemd:
daemon_reload: true
- name: Enable and start
ansible.builtin.systemd:
name: myapp
state: started
enabled: true
```
## Command Execution
### command
```yaml
- name: Run command
ansible.builtin.command: /bin/mycommand arg1 arg2
register: result
changed_when: "'changed' in result.stdout"
```
### shell
```yaml
- name: Run shell command
ansible.builtin.shell: |
cd /opt/app
./setup.sh && ./configure.sh
args:
executable: /bin/bash
```
### script
```yaml
- name: Run local script on remote
ansible.builtin.script: scripts/setup.sh
args:
creates: /opt/app/.installed
```
## User Management
### user
```yaml
- name: Create user
ansible.builtin.user:
name: appuser
groups: docker,sudo
shell: /bin/bash
create_home: true
state: present
- name: Remove user
ansible.builtin.user:
name: olduser
state: absent
remove: true
```
### group
```yaml
- name: Create group
ansible.builtin.group:
name: appgroup
state: present
```
## Docker (community.docker)
### docker_container
```yaml
- name: Run container
community.docker.docker_container:
name: myapp
image: myapp:latest
state: started
restart_policy: unless-stopped
ports:
- "8080:80"
volumes:
- /data:/app/data
env:
DB_HOST: database
```
### docker_compose_v2
```yaml
- name: Deploy with compose
community.docker.docker_compose_v2:
project_src: /opt/app
project_name: myapp
state: present
pull: always
env_files:
- /opt/app/.env
```
### docker_image
```yaml
- name: Pull image
community.docker.docker_image:
name: nginx
tag: "1.25"
source: pull
```
## Networking
### uri
```yaml
- name: API call
ansible.builtin.uri:
url: "http://localhost:8080/api/health"
method: GET
return_content: true
register: response
- name: POST request
ansible.builtin.uri:
url: "http://api.example.com/data"
method: POST
body_format: json
body:
key: value
```
### wait_for
```yaml
- name: Wait for port
ansible.builtin.wait_for:
host: localhost
port: 8080
timeout: 300
- name: Wait for file
ansible.builtin.wait_for:
path: /var/log/app.log
search_regex: "Server started"
```
## Debug/Assert
### debug
```yaml
- name: Print variable
ansible.builtin.debug:
msg: "Value: {{ my_var }}"
- name: Print var directly
ansible.builtin.debug:
var: my_var
```
### assert
```yaml
- name: Validate conditions
ansible.builtin.assert:
that:
- my_var is defined
- my_var | length > 0
fail_msg: "my_var must be defined and non-empty"
success_msg: "Validation passed"
```
### fail
```yaml
- name: Fail with message
ansible.builtin.fail:
msg: "Required condition not met"
when: condition
```
## Misc
### pause
```yaml
- name: Wait 10 seconds
ansible.builtin.pause:
seconds: 10
- name: Wait for user
ansible.builtin.pause:
prompt: "Press enter to continue"
```
### stat
```yaml
- name: Check file exists
ansible.builtin.stat:
path: /etc/config
register: config_file
- name: Use result
ansible.builtin.debug:
msg: "File exists"
when: config_file.stat.exists
```

View File

@@ -0,0 +1,243 @@
# Ansible Playbook Reference
## Basic Structure
```yaml
---
- name: Playbook description
hosts: target_group
become: true # Run as root
gather_facts: true # Collect system info
vars:
my_var: value
vars_files:
- vars/secrets.yml
pre_tasks:
- name: Pre-task
ansible.builtin.debug:
msg: "Running before main tasks"
roles:
- role_name
tasks:
- name: Main task
ansible.builtin.debug:
msg: "Main task"
handlers:
- name: Handler name
ansible.builtin.service:
name: service
state: restarted
post_tasks:
- name: Post-task
ansible.builtin.debug:
msg: "Running after main tasks"
```
## Task Options
```yaml
tasks:
- name: Task with common options
ansible.builtin.command: /bin/command
become: true # Privilege escalation
become_user: www-data # Run as specific user
when: condition # Conditional execution
register: result # Store output
ignore_errors: true # Continue on failure
changed_when: false # Override change detection
failed_when: result.rc != 0 # Custom failure condition
tags:
- deploy
- config
notify: Handler name # Trigger handler
```
## Loops
```yaml
# Simple loop
- name: Install packages
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop:
- nginx
- python3
# Loop with dict
- name: Create users
ansible.builtin.user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
loop:
- { name: 'user1', groups: 'admin' }
- { name: 'user2', groups: 'users' }
# Loop over dict
- name: Process items
ansible.builtin.debug:
msg: "{{ item.key }}: {{ item.value }}"
loop: "{{ my_dict | dict2items }}"
# Loop with index
- name: With index
ansible.builtin.debug:
msg: "{{ index }}: {{ item }}"
loop: "{{ my_list }}"
loop_control:
index_var: index
```
## Conditionals
```yaml
# Simple when
- name: Only on Ubuntu
ansible.builtin.apt:
name: package
when: ansible_distribution == "Ubuntu"
# Multiple conditions
- name: Complex condition
ansible.builtin.command: /bin/something
when:
- ansible_os_family == "Debian"
- ansible_distribution_version is version('20.04', '>=')
# Or conditions
- name: Or condition
ansible.builtin.command: /bin/something
when: condition1 or condition2
# Check variable
- name: If defined
ansible.builtin.debug:
msg: "{{ my_var }}"
when: my_var is defined
```
## Blocks
```yaml
- name: Block example
block:
- name: Task 1
ansible.builtin.command: /bin/task1
- name: Task 2
ansible.builtin.command: /bin/task2
rescue:
- name: Handle failure
ansible.builtin.debug:
msg: "Block failed"
always:
- name: Always run
ansible.builtin.debug:
msg: "Cleanup"
```
## Handlers
```yaml
tasks:
- name: Update config
ansible.builtin.template:
src: config.j2
dest: /etc/app/config
notify:
- Restart service
- Reload config
handlers:
- name: Restart service
ansible.builtin.service:
name: app
state: restarted
- name: Reload config
ansible.builtin.service:
name: app
state: reloaded
```
Handlers run once at end of play, even if notified multiple times.
## Including Tasks
```yaml
# Include tasks file
- name: Include tasks
ansible.builtin.include_tasks: tasks/setup.yml
# Import tasks (static)
- name: Import tasks
ansible.builtin.import_tasks: tasks/setup.yml
# Include with variables
- name: Include with vars
ansible.builtin.include_tasks: tasks/deploy.yml
vars:
environment: production
```
## Tags
```yaml
tasks:
- name: Tagged task
ansible.builtin.command: /bin/command
tags:
- deploy
- always # Always runs regardless of tag selection
- name: Never runs by default
ansible.builtin.command: /bin/command
tags: never # Only runs when explicitly tagged
```
Run with tags:
```bash
ansible-playbook playbook.yml --tags "deploy"
ansible-playbook playbook.yml --skip-tags "slow"
```
## Check Mode
```yaml
# Force check mode behavior
- name: Always runs in check
ansible.builtin.command: /bin/command
check_mode: false # Runs even in check mode
- name: Never runs in check
ansible.builtin.command: /bin/command
check_mode: true # Only runs in check mode
```
## Delegation
```yaml
# Run on different host
- name: Update load balancer
ansible.builtin.command: /bin/update-lb
delegate_to: loadbalancer
# Run locally
- name: Local action
ansible.builtin.command: /bin/local-command
delegate_to: localhost
# Run once for all hosts
- name: Single execution
ansible.builtin.command: /bin/command
run_once: true
```

View File

@@ -0,0 +1,155 @@
# Ansible Proxmox Authentication
## API Token Setup
Create a dedicated Ansible user and API token on Proxmox:
```bash
# On Proxmox node
pveum user add ansible@pve
pveum aclmod / -user ansible@pve -role PVEAdmin
pveum user token add ansible@pve mytoken --privsep 0
```
**Note:** `--privsep 0` gives the token the same permissions as the user.
## Playbook Variables
### Direct in playbook (NOT recommended)
```yaml
vars:
proxmox_api_host: proxmox.example.com
proxmox_api_user: ansible@pve
proxmox_api_token_id: mytoken
proxmox_api_token_secret: "{{ vault_proxmox_token }}"
```
### Group vars with vault
```yaml
# group_vars/all.yml
proxmox_api_host: proxmox.example.com
proxmox_api_user: ansible@pve
proxmox_api_token_id: mytoken
# group_vars/secrets.yml (ansible-vault encrypted)
proxmox_api_token_secret: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
```
### Environment variables
```bash
export PROXMOX_HOST=proxmox.example.com
export PROXMOX_USER=ansible@pve
export PROXMOX_TOKEN_ID=mytoken
export PROXMOX_TOKEN_SECRET=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
```
```yaml
# In playbook
vars:
proxmox_api_host: "{{ lookup('env', 'PROXMOX_HOST') }}"
proxmox_api_user: "{{ lookup('env', 'PROXMOX_USER') }}"
proxmox_api_token_id: "{{ lookup('env', 'PROXMOX_TOKEN_ID') }}"
proxmox_api_token_secret: "{{ lookup('env', 'PROXMOX_TOKEN_SECRET') }}"
```
## Reusable Auth Block
Define once, reuse across tasks:
```yaml
vars:
proxmox_auth: &proxmox_auth
api_host: "{{ proxmox_api_host }}"
api_user: "{{ proxmox_api_user }}"
api_token_id: "{{ proxmox_api_token_id }}"
api_token_secret: "{{ proxmox_api_token_secret }}"
validate_certs: false # For self-signed certs
tasks:
- name: Create VM
community.general.proxmox_kvm:
<<: *proxmox_auth
node: joseph
vmid: 300
name: myvm
state: present
- name: Start VM
community.general.proxmox_kvm:
<<: *proxmox_auth
vmid: 300
state: started
```
## TLS Certificate Handling
### Self-signed certificates
```yaml
community.general.proxmox_kvm:
# ... auth params ...
validate_certs: false
```
### Custom CA
```bash
export SSL_CERT_FILE=/path/to/ca-bundle.crt
```
Or in ansible.cfg:
```ini
[defaults]
# For urllib3/requests
ca_cert = /path/to/ca-bundle.crt
```
## Minimum Required Permissions
For full VM/container management:
| Permission | Path | Purpose |
|------------|------|---------|
| VM.Allocate | / | Create VMs |
| VM.Clone | / | Clone templates |
| VM.Config.* | / | Modify VM config |
| VM.PowerMgmt | / | Start/stop VMs |
| VM.Snapshot | / | Create snapshots |
| Datastore.AllocateSpace | / | Allocate disk space |
| Datastore.Audit | / | List storage |
Or use the built-in `PVEAdmin` role for full access.
## Troubleshooting Auth Issues
```yaml
# Debug task to test connection
- name: Test Proxmox API connection
community.general.proxmox_kvm:
api_host: "{{ proxmox_api_host }}"
api_user: "{{ proxmox_api_user }}"
api_token_id: "{{ proxmox_api_token_id }}"
api_token_secret: "{{ proxmox_api_token_secret }}"
validate_certs: false
vmid: 100
state: current
register: result
ignore_errors: true
- name: Show result
ansible.builtin.debug:
var: result
```
Common errors:
| Error | Cause | Fix |
|-------|-------|-----|
| 401 Unauthorized | Bad token | Verify token ID format: `user@realm!tokenname` |
| 403 Forbidden | Insufficient permissions | Check user ACLs with `pveum user permissions ansible@pve` |
| SSL certificate problem | Self-signed cert | Set `validate_certs: false` |
| Connection refused | Wrong host/port | Verify API URL (port 8006) |

View File

@@ -0,0 +1,195 @@
# Ansible Proxmox Dynamic Inventory
Query Proxmox API for automatic inventory generation.
## Plugin Setup
### Requirements
```bash
pip install proxmoxer requests
ansible-galaxy collection install community.general
```
### Inventory File
Create `inventory/proxmox.yml`:
```yaml
plugin: community.general.proxmox
url: https://proxmox.example.com:8006
user: ansible@pve
token_id: mytoken
token_secret: "{{ lookup('env', 'PROXMOX_TOKEN_SECRET') }}"
validate_certs: false
# Include VMs and containers
want_facts: true
want_proxmox_nodes_ansible_host: false
# Filter by status
filters:
- status == "running"
# Group by various attributes
groups:
# By Proxmox node
node_joseph: proxmox_node == "joseph"
node_maxwell: proxmox_node == "maxwell"
node_everette: proxmox_node == "everette"
# By type
vms: proxmox_type == "qemu"
containers: proxmox_type == "lxc"
# By template naming convention
docker_hosts: "'docker' in proxmox_name"
pihole: "'pihole' in proxmox_name"
# Host variables from Proxmox
compose:
ansible_host: proxmox_agent_interfaces[0].ip-addresses[0].ip-address | default(proxmox_name)
ansible_user: "'ubuntu'"
proxmox_vmid: proxmox_vmid
proxmox_node: proxmox_node
```
### Enable in ansible.cfg
```ini
[inventory]
enable_plugins = community.general.proxmox, yaml, ini
```
## Testing Inventory
```bash
# List all hosts
ansible-inventory -i inventory/proxmox.yml --list
# Graph view
ansible-inventory -i inventory/proxmox.yml --graph
# Specific host details
ansible-inventory -i inventory/proxmox.yml --host myvm
```
## Common Patterns
### Filter by Tags
Proxmox 7+ supports VM tags:
```yaml
groups:
webservers: "'web' in proxmox_tags"
databases: "'db' in proxmox_tags"
production: "'prod' in proxmox_tags"
```
### Filter by VMID Range
```yaml
filters:
- vmid >= 200
- vmid < 300
groups:
dev_vms: proxmox_vmid >= 200 and proxmox_vmid < 300
prod_vms: proxmox_vmid >= 300 and proxmox_vmid < 400
```
### IP Address from QEMU Agent
Requires QEMU guest agent running in VM:
```yaml
compose:
# Primary IP from agent
ansible_host: >-
proxmox_agent_interfaces
| selectattr('name', 'equalto', 'eth0')
| map(attribute='ip-addresses')
| flatten
| selectattr('ip-address-type', 'equalto', 'ipv4')
| map(attribute='ip-address')
| first
| default(proxmox_name)
```
### Static + Dynamic Inventory
Combine with static inventory:
```bash
# inventory/
# static.yml # Static hosts
# proxmox.yml # Dynamic from Proxmox
ansible-playbook -i inventory/ playbook.yml
```
## Available Variables
Variables populated from Proxmox API:
| Variable | Description |
|----------|-------------|
| proxmox_vmid | VM/container ID |
| proxmox_name | VM/container name |
| proxmox_type | "qemu" or "lxc" |
| proxmox_status | running, stopped, etc. |
| proxmox_node | Proxmox node name |
| proxmox_pool | Resource pool (if any) |
| proxmox_tags | Tags (Proxmox 7+) |
| proxmox_template | Is template (bool) |
| proxmox_agent | QEMU agent enabled (bool) |
| proxmox_agent_interfaces | Network info from agent |
| proxmox_cpus | CPU count |
| proxmox_maxmem | Max memory bytes |
| proxmox_maxdisk | Max disk bytes |
## Caching
Enable caching for faster inventory:
```yaml
plugin: community.general.proxmox
# ... auth ...
cache: true
cache_plugin: jsonfile
cache_connection: /tmp/ansible_proxmox_cache
cache_timeout: 300 # 5 minutes
```
Clear cache:
```bash
rm -rf /tmp/ansible_proxmox_cache
```
## Troubleshooting
### No hosts returned
1. Check API connectivity:
```bash
curl -k "https://proxmox:8006/api2/json/cluster/resources" \
-H "Authorization: PVEAPIToken=ansible@pve!mytoken=secret"
```
2. Check filters aren't too restrictive - try removing them
3. Verify token permissions include `VM.Audit`
### QEMU agent data missing
- Agent must be installed and running in guest
- `want_facts: true` must be set
- May take a few seconds after VM boot
### Slow inventory queries
- Enable caching (see above)
- Use filters to reduce results
- Avoid `want_facts: true` if not needed

View File

@@ -0,0 +1,202 @@
# Ansible Proxmox Gotchas
Common issues when using Ansible with Proxmox VE.
## 1. Token ID Format
**Wrong:**
```yaml
api_token_id: mytoken
```
**Correct:**
```yaml
api_token_id: mytoken # Just the token name, NOT user@realm!tokenname
```
The module combines `api_user` and `api_token_id` internally.
## 2. VMID Required for Most Operations
Unlike Terraform, you must always specify `vmid`:
```yaml
# Won't auto-generate VMID
- name: Create VM
community.general.proxmox_kvm:
# ... auth ...
vmid: 300 # REQUIRED - no auto-assignment
name: myvm
```
To find next available VMID:
```yaml
- name: Get cluster resources
ansible.builtin.uri:
url: "https://{{ proxmox_api_host }}:8006/api2/json/cluster/resources"
headers:
Authorization: "PVEAPIToken={{ proxmox_api_user }}!{{ proxmox_api_token_id }}={{ proxmox_api_token_secret }}"
validate_certs: false
register: resources
- name: Calculate next VMID
ansible.builtin.set_fact:
next_vmid: "{{ (resources.json.data | selectattr('vmid', 'defined') | map(attribute='vmid') | max) + 1 }}"
```
## 3. Node Parameter Required
Must specify which node to operate on:
```yaml
- name: Create VM
community.general.proxmox_kvm:
# ... auth ...
node: joseph # REQUIRED - which Proxmox node
vmid: 300
```
## 4. Clone vs Create
Cloning requires different parameters than creating:
```yaml
# CLONE from template
- name: Clone VM
community.general.proxmox_kvm:
# ... auth ...
node: joseph
vmid: 300
name: myvm
clone: tmpl-ubuntu-2404-standard # Template name or VMID
full: true
# CREATE new (less common)
- name: Create VM
community.general.proxmox_kvm:
# ... auth ...
node: joseph
vmid: 300
name: myvm
ostype: l26
scsihw: virtio-scsi-pci
bootdisk: scsi0
scsi:
scsi0: 'local-lvm:32,format=raw'
```
## 5. Async Operations
Large operations (clone, snapshot) can timeout. Use async:
```yaml
- name: Clone large VM
community.general.proxmox_kvm:
# ... auth ...
clone: large-template
vmid: 300
timeout: 600 # Module timeout
async: 900 # Ansible async timeout
poll: 10 # Check every 10 seconds
```
## 6. State Idempotency
`state: present` doesn't update existing VMs:
```yaml
# This WON'T change cores on existing VM
- name: Create/update VM
community.general.proxmox_kvm:
# ... auth ...
vmid: 300
cores: 4 # Ignored if VM exists
state: present
```
To modify existing VMs, use `proxmox_kvm` with `update: true` (Ansible 2.14+) or use the API directly.
## 7. Network Interface Format (LXC)
LXC containers use a specific JSON-like string format:
```yaml
# WRONG
netif:
net0:
bridge: vmbr0
ip: dhcp
# CORRECT
netif: '{"net0":"name=eth0,bridge=vmbr0,ip=dhcp"}'
# Multiple interfaces
netif: '{"net0":"name=eth0,bridge=vmbr0,ip=dhcp","net1":"name=eth1,bridge=vmbr12,ip=dhcp"}'
```
## 8. Disk Resize Only Grows
`proxmox_disk` resize only increases size:
```yaml
# This adds 20G to current size
- name: Grow disk
community.general.proxmox_disk:
# ... auth ...
vmid: 300
disk: scsi0
size: +20G # Relative increase
state: resized
# NOT possible to shrink
```
## 9. Template vs VM States
Templates don't support all states:
```yaml
# Can't start a template
- name: Start template
community.general.proxmox_kvm:
vmid: 100
state: started # FAILS - templates can't run
```
Convert template to VM first if needed.
## 10. Collection Version Matters
Module parameters change between versions. Check installed version:
```bash
ansible-galaxy collection list | grep community.general
```
Update if needed:
```bash
ansible-galaxy collection install community.general --upgrade
```
## 11. Cloud-Init Not Supported
Unlike Terraform's Proxmox provider, the Ansible modules have limited cloud-init support. For cloud-init VMs:
1. Clone template with cloud-init already configured
2. Use API calls to set cloud-init parameters
3. Or configure post-boot with Ansible
```yaml
# Workaround: Use URI module for cloud-init config
- name: Set cloud-init IP
ansible.builtin.uri:
url: "https://{{ proxmox_api_host }}:8006/api2/json/nodes/{{ node }}/qemu/{{ vmid }}/config"
method: PUT
headers:
Authorization: "PVEAPIToken={{ proxmox_api_user }}!{{ proxmox_api_token_id }}={{ proxmox_api_token_secret }}"
body_format: form-urlencoded
body:
ipconfig0: "ip=192.168.1.100/24,gw=192.168.1.1"
ciuser: ubuntu
validate_certs: false
```

View File

@@ -0,0 +1,232 @@
# Ansible Proxmox Modules
Proxmox VE management via `community.general` collection.
## Collection Setup
```bash
ansible-galaxy collection install community.general
```
## Core Modules
### proxmox (LXC Containers)
```yaml
- name: Create LXC container
community.general.proxmox:
api_host: proxmox.example.com
api_user: ansible@pve
api_token_id: mytoken
api_token_secret: "{{ proxmox_token_secret }}"
node: joseph
vmid: 200
hostname: mycontainer
ostemplate: local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst
storage: local-lvm
cores: 2
memory: 2048
disk: 10
netif: '{"net0":"name=eth0,bridge=vmbr0,ip=dhcp"}'
state: present
- name: Start container
community.general.proxmox:
api_host: proxmox.example.com
api_user: ansible@pve
api_token_id: mytoken
api_token_secret: "{{ proxmox_token_secret }}"
node: joseph
vmid: 200
state: started
- name: Stop container
community.general.proxmox:
# ... auth params ...
vmid: 200
state: stopped
force: true # Force stop if graceful fails
- name: Remove container
community.general.proxmox:
# ... auth params ...
vmid: 200
state: absent
```
### proxmox_kvm (VMs)
```yaml
- name: Create VM from template
community.general.proxmox_kvm:
api_host: proxmox.example.com
api_user: ansible@pve
api_token_id: mytoken
api_token_secret: "{{ proxmox_token_secret }}"
node: joseph
vmid: 300
name: myvm
clone: tmpl-ubuntu-2404-standard
full: true # Full clone (not linked)
storage: local-lvm
format: raw
timeout: 500
- name: Start VM
community.general.proxmox_kvm:
# ... auth params ...
node: joseph
vmid: 300
state: started
- name: Stop VM (ACPI shutdown)
community.general.proxmox_kvm:
# ... auth params ...
vmid: 300
state: stopped
force: false # Graceful ACPI
- name: Force stop VM
community.general.proxmox_kvm:
# ... auth params ...
vmid: 300
state: stopped
force: true
- name: Current state (running/stopped/present/absent)
community.general.proxmox_kvm:
# ... auth params ...
vmid: 300
state: current
register: vm_state
```
### proxmox_template
```yaml
- name: Convert VM to template
community.general.proxmox_template:
api_host: proxmox.example.com
api_user: ansible@pve
api_token_id: mytoken
api_token_secret: "{{ proxmox_token_secret }}"
node: joseph
vmid: 100
state: present # Convert to template
- name: Delete template
community.general.proxmox_template:
# ... auth params ...
vmid: 100
state: absent
```
### proxmox_snap
```yaml
- name: Create snapshot
community.general.proxmox_snap:
api_host: proxmox.example.com
api_user: ansible@pve
api_token_id: mytoken
api_token_secret: "{{ proxmox_token_secret }}"
vmid: 300
snapname: before-upgrade
description: "Snapshot before major upgrade"
vmstate: false # Don't include RAM
state: present
- name: Rollback to snapshot
community.general.proxmox_snap:
# ... auth params ...
vmid: 300
snapname: before-upgrade
state: rollback
- name: Remove snapshot
community.general.proxmox_snap:
# ... auth params ...
vmid: 300
snapname: before-upgrade
state: absent
```
### proxmox_nic
```yaml
- name: Add NIC to VM
community.general.proxmox_nic:
api_host: proxmox.example.com
api_user: ansible@pve
api_token_id: mytoken
api_token_secret: "{{ proxmox_token_secret }}"
vmid: 300
interface: net1
bridge: vmbr12
model: virtio
tag: 12 # VLAN tag
state: present
- name: Remove NIC
community.general.proxmox_nic:
# ... auth params ...
vmid: 300
interface: net1
state: absent
```
### proxmox_disk
```yaml
- name: Add disk to VM
community.general.proxmox_disk:
api_host: proxmox.example.com
api_user: ansible@pve
api_token_id: mytoken
api_token_secret: "{{ proxmox_token_secret }}"
vmid: 300
disk: scsi1
storage: local-lvm
size: 50G
format: raw
state: present
- name: Resize disk
community.general.proxmox_disk:
# ... auth params ...
vmid: 300
disk: scsi0
size: +20G # Increase by 20G
state: resized
- name: Detach disk
community.general.proxmox_disk:
# ... auth params ...
vmid: 300
disk: scsi1
state: absent
```
## State Reference
| Module | States |
|--------|--------|
| proxmox (LXC) | present, started, stopped, restarted, absent |
| proxmox_kvm | present, started, stopped, restarted, absent, current |
| proxmox_template | present, absent |
| proxmox_snap | present, absent, rollback |
| proxmox_nic | present, absent |
| proxmox_disk | present, absent, resized |
## Common Parameters
All modules share these authentication parameters:
| Parameter | Description |
|-----------|-------------|
| api_host | Proxmox hostname/IP |
| api_user | User (format: user@realm) |
| api_token_id | API token name |
| api_token_secret | API token value |
| validate_certs | Verify TLS (default: true) |
| timeout | API timeout seconds |

View File

@@ -0,0 +1,295 @@
# Ansible Troubleshooting Reference
## Common Errors
| Error | Cause | Solution |
|-------|-------|----------|
| SSH connection failed | Wrong host/key/user | Check ansible_host, ansible_user, key |
| Permission denied | Need sudo/wrong user | Add `become: true`, check sudo config |
| Module not found | Collection not installed | `ansible-galaxy collection install` |
| Variable undefined | Missing var/typo | Check var name, define in vars |
| Syntax error | YAML/Jinja2 issue | Run `ansible-playbook --syntax-check` |
| Host unreachable | Network/SSH issue | `ansible host -m ping`, check firewall |
## Debug Commands
```bash
# Test connectivity
ansible all -m ping
ansible host -m ping -vvv
# Syntax check
ansible-playbook playbook.yml --syntax-check
# Dry run (check mode)
ansible-playbook playbook.yml --check
# Diff mode (show changes)
ansible-playbook playbook.yml --diff
# Verbose output
ansible-playbook playbook.yml -v # Minimal
ansible-playbook playbook.yml -vv # More
ansible-playbook playbook.yml -vvv # Connection debug
ansible-playbook playbook.yml -vvvv # Full debug
# List tasks without running
ansible-playbook playbook.yml --list-tasks
# List hosts
ansible-playbook playbook.yml --list-hosts
# Start at specific task
ansible-playbook playbook.yml --start-at-task="Task name"
# Step through tasks
ansible-playbook playbook.yml --step
```
## Connection Issues
### Test SSH
```bash
# Direct SSH test
ssh -i ~/.ssh/key user@host
# Ansible ping
ansible host -m ping -vvv
# Check SSH config
ansible host -m debug -a "var=ansible_ssh_private_key_file"
```
### Common SSH Fixes
```yaml
# In inventory or ansible.cfg
ansible_ssh_private_key_file: ~/.ssh/mykey
ansible_user: ubuntu
ansible_host: 192.168.1.10
host_key_checking: False # Only for testing
```
### SSH Connection Options
```yaml
# In inventory
host1:
ansible_host: 192.168.1.10
ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
ansible_ssh_extra_args: '-o ConnectTimeout=10'
```
## Permission Issues
### Sudo Not Working
```yaml
# Enable become
- hosts: all
become: true
become_method: sudo
become_user: root
```
```bash
# On target host, check sudoers
sudo visudo
# User should have:
# ubuntu ALL=(ALL) NOPASSWD: ALL
```
### Ask for Sudo Password
```bash
ansible-playbook playbook.yml --ask-become-pass
```
## Variable Issues
### Debug Variables
```yaml
- name: Print all vars
ansible.builtin.debug:
var: vars
- name: Print specific var
ansible.builtin.debug:
var: my_var
- name: Print hostvars
ansible.builtin.debug:
var: hostvars[inventory_hostname]
- name: Print facts
ansible.builtin.debug:
var: ansible_facts
```
### Check Variable Precedence
```bash
# See where variable comes from
ansible-inventory --host hostname --yaml
```
### Undefined Variable
```yaml
# Provide default
value: "{{ my_var | default('fallback') }}"
# Check if defined
- name: Task
when: my_var is defined
# Fail early if required
- name: Validate
ansible.builtin.assert:
that: my_var is defined
fail_msg: "my_var must be set"
```
## Module Issues
### Module Not Found
```bash
# Install collection
ansible-galaxy collection install community.docker
# Check installed
ansible-galaxy collection list
# Update collections
ansible-galaxy collection install -r requirements.yml --force
```
### Module Arguments
```bash
# Get module documentation
ansible-doc ansible.builtin.copy
ansible-doc community.docker.docker_compose_v2
```
## Idempotency Issues
### Task Always Shows "changed"
```yaml
# Bad - always changed
- name: Run script
ansible.builtin.command: /bin/script.sh
# Good - check first
- name: Run script
ansible.builtin.command: /bin/script.sh
args:
creates: /opt/app/.installed
# Good - explicit changed_when
- name: Run script
ansible.builtin.command: /bin/script.sh
register: result
changed_when: "'Created' in result.stdout"
```
### Test Idempotency
```bash
# Run twice, second should show all "ok"
ansible-playbook playbook.yml
ansible-playbook playbook.yml # Should show "changed=0"
```
## Handler Issues
### Handler Not Running
- Handlers only run if task reports "changed"
- Handlers run at end of play, not immediately
- Force handler run: `ansible-playbook --force-handlers`
```yaml
# Force handler to run immediately
- name: Config change
ansible.builtin.template:
src: config.j2
dest: /etc/app/config
notify: Restart app
- name: Flush handlers
ansible.builtin.meta: flush_handlers
- name: Continue with restarted service
ansible.builtin.uri:
url: http://localhost:8080/health
```
## Performance Issues
### Slow Playbook
```yaml
# Disable fact gathering if not needed
- hosts: all
gather_facts: false
# Or gather specific facts
- hosts: all
gather_facts: true
gather_subset:
- network
```
```bash
# Increase parallelism
ansible-playbook playbook.yml -f 20 # 20 forks
# Use pipelining (add to ansible.cfg)
# [ssh_connection]
# pipelining = True
```
### Callback Timer
```ini
# ansible.cfg
[defaults]
callbacks_enabled = timer, profile_tasks
```
## Recovery
### Failed Playbook
```bash
# Retry failed hosts
ansible-playbook playbook.yml --limit @playbook.retry
# Start at failed task
ansible-playbook playbook.yml --start-at-task="Failed Task Name"
```
### Cleanup After Failure
```yaml
- name: Risky operation
block:
- name: Do something
ansible.builtin.command: /bin/risky
rescue:
- name: Cleanup on failure
ansible.builtin.file:
path: /tmp/incomplete
state: absent
always:
- name: Always cleanup
ansible.builtin.file:
path: /tmp/lock
state: absent
```

View File

@@ -0,0 +1,246 @@
# Ansible Variables Reference
## Variable Precedence (High to Low)
1. **Extra vars** (`-e "var=value"`)
2. **Task vars** (in task)
3. **Block vars** (in block)
4. **Role/include vars**
5. **set_facts / registered vars**
6. **Play vars_files**
7. **Play vars_prompt**
8. **Play vars**
9. **Host facts**
10. **Playbook host_vars/**
11. **Inventory host_vars/**
12. **Playbook group_vars/**
13. **Inventory group_vars/**
14. **Playbook group_vars/all**
15. **Inventory group_vars/all**
16. **Role defaults**
## Defining Variables
### In Playbook
```yaml
- hosts: all
vars:
app_name: myapp
app_port: 8080
vars_files:
- vars/common.yml
- "vars/{{ environment }}.yml"
```
### In Tasks
```yaml
- name: Set variable
ansible.builtin.set_fact:
my_var: "value"
- name: Register output
ansible.builtin.command: whoami
register: user_result
- name: Use registered
ansible.builtin.debug:
msg: "User: {{ user_result.stdout }}"
```
### In Roles
```yaml
# roles/app/defaults/main.yml (low priority)
app_port: 8080
# roles/app/vars/main.yml (high priority)
internal_setting: value
```
## Variable Types
```yaml
# String
name: "value"
# Number
port: 8080
# Boolean
enabled: true
# List
packages:
- nginx
- python3
# Dictionary
user:
name: admin
groups:
- wheel
- docker
```
## Accessing Variables
```yaml
# Simple
msg: "{{ my_var }}"
# Dictionary
msg: "{{ user.name }}"
msg: "{{ user['name'] }}"
# List
msg: "{{ packages[0] }}"
msg: "{{ packages | first }}"
# Default value
msg: "{{ my_var | default('fallback') }}"
# Required (fail if undefined)
msg: "{{ my_var }}" # Fails if undefined
```
## Jinja2 Filters
```yaml
# Default
value: "{{ var | default('default') }}"
# Mandatory
value: "{{ var | mandatory }}"
# Type conversion
port: "{{ port_string | int }}"
flag: "{{ flag_string | bool }}"
# String operations
upper: "{{ name | upper }}"
lower: "{{ name | lower }}"
title: "{{ name | title }}"
# Lists
first: "{{ list | first }}"
last: "{{ list | last }}"
length: "{{ list | length }}"
joined: "{{ list | join(',') }}"
# JSON
json_str: "{{ dict | to_json }}"
yaml_str: "{{ dict | to_yaml }}"
# Path operations
basename: "{{ path | basename }}"
dirname: "{{ path | dirname }}"
```
## Facts
```yaml
# Accessing facts
os: "{{ ansible_distribution }}"
version: "{{ ansible_distribution_version }}"
ip: "{{ ansible_default_ipv4.address }}"
hostname: "{{ ansible_hostname }}"
memory_mb: "{{ ansible_memtotal_mb }}"
cpus: "{{ ansible_processor_vcpus }}"
```
### Gathering Facts
```yaml
- hosts: all
gather_facts: true # Default
# Or manually
- name: Gather facts
ansible.builtin.setup:
filter: ansible_*
# Specific facts
- name: Get network facts
ansible.builtin.setup:
gather_subset:
- network
```
## Environment Variables
```yaml
# Lookup
value: "{{ lookup('env', 'MY_VAR') }}"
# Set for task
- name: Run with env
ansible.builtin.command: /bin/command
environment:
MY_VAR: "{{ my_value }}"
```
## Secrets/Vault
```bash
# Create encrypted file
ansible-vault create secrets.yml
# Edit encrypted file
ansible-vault edit secrets.yml
# Encrypt existing file
ansible-vault encrypt vars.yml
# Run with vault password
ansible-playbook playbook.yml --ask-vault-pass
ansible-playbook playbook.yml --vault-password-file ~/.vault_pass
```
## Prompt for Variables
```yaml
- hosts: all
vars_prompt:
- name: password
prompt: "Enter password"
private: true
- name: environment
prompt: "Which environment?"
default: "staging"
```
## Conditionals with Variables
```yaml
- name: Check defined
when: my_var is defined
- name: Check undefined
when: my_var is not defined
- name: Check truthy
when: my_var | bool
- name: Check falsy
when: not my_var | bool
- name: Check in list
when: item in my_list
- name: Version comparison
when: version is version('2.0', '>=')
```
## Hostvars
Access variables from other hosts:
```yaml
- name: Get from other host
ansible.builtin.debug:
msg: "{{ hostvars['web1']['ansible_host'] }}"
```