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

162
skills/ansible/SKILL.md Normal file
View File

@@ -0,0 +1,162 @@
---
name: ansible
description: |
Ansible automation reference for playbooks, roles, inventory, variables, and modules.
Includes Proxmox VE and Docker integration via community.general and community.docker collections.
Use when writing playbooks, troubleshooting Ansible runs, or designing automation workflows.
Triggers: ansible, playbook, inventory, role, task, handler, vars, jinja2, galaxy, proxmox_kvm, proxmox_lxc, docker_container, docker_compose.
---
# Ansible Skill
Ansible automation reference for configuration management and application deployment.
## Quick Reference
```bash
# Test connectivity
ansible all -m ping
ansible <group> -m ping
# Run playbook
ansible-playbook playbook.yml
ansible-playbook playbook.yml -l <host> # Limit to host
ansible-playbook playbook.yml --check # Dry-run
ansible-playbook playbook.yml -vvv # Verbose
# Tags
ansible-playbook playbook.yml --tags "deploy"
ansible-playbook playbook.yml --skip-tags "backup"
ansible-playbook playbook.yml --list-tags
# Variables
ansible-playbook playbook.yml -e "var=value"
ansible-playbook playbook.yml -e "@vars.yml"
# Ad-hoc commands
ansible <group> -m shell -a "command"
ansible <group> -m copy -a "src=file dest=/path"
ansible <group> -m apt -a "name=package state=present"
# Galaxy
ansible-galaxy collection install -r requirements.yml
ansible-galaxy role install <role>
```
## Reference Files
Load on-demand based on task:
| Topic | File | When to Load |
|-------|------|--------------|
| Playbook Structure | [playbooks.md](references/playbooks.md) | Writing playbooks |
| Inventory | [inventory.md](references/inventory.md) | Host/group configuration |
| Variables | [variables.md](references/variables.md) | Variable precedence, facts |
| Modules | [modules.md](references/modules.md) | Common module reference |
| Troubleshooting | [troubleshooting.md](references/troubleshooting.md) | Common errors, debugging |
### Proxmox Integration
| Topic | File | When to Load |
|-------|------|--------------|
| Proxmox Modules | [proxmox/modules.md](references/proxmox/modules.md) | VM/LXC management via API |
| Proxmox Auth | [proxmox/authentication.md](references/proxmox/authentication.md) | API tokens, credentials |
| Proxmox Gotchas | [proxmox/gotchas.md](references/proxmox/gotchas.md) | Common issues, workarounds |
| Dynamic Inventory | [proxmox/dynamic-inventory.md](references/proxmox/dynamic-inventory.md) | Auto-discover VMs/containers |
### Docker Integration
| Topic | File | When to Load |
|-------|------|--------------|
| Docker Deployment | [docker/deployment.md](references/docker/deployment.md) | Containers, images, networks, volumes |
| Compose Patterns | [docker/compose-patterns.md](references/docker/compose-patterns.md) | Roles, templates, multi-service stacks |
| Docker Troubleshooting | [docker/troubleshooting.md](references/docker/troubleshooting.md) | Common errors, debugging |
## Playbook Quick Reference
```yaml
---
- name: Deploy application
hosts: webservers
become: true
vars:
app_port: 8080
pre_tasks:
- name: Validate requirements
ansible.builtin.assert:
that:
- app_secret is defined
tasks:
- name: Install packages
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop:
- nginx
- python3
- name: Deploy config
ansible.builtin.template:
src: app.conf.j2
dest: /etc/app/app.conf
notify: Restart app
handlers:
- name: Restart app
ansible.builtin.service:
name: app
state: restarted
post_tasks:
- name: Verify deployment
ansible.builtin.uri:
url: "http://localhost:{{ app_port }}/health"
```
## Variable Precedence (High to Low)
1. Extra vars (`-e "var=value"`)
2. Task vars
3. Block vars
4. Role/include vars
5. Play vars
6. Host facts
7. host_vars/
8. group_vars/
9. Role defaults
## Directory Structure
```text
ansible/
├── ansible.cfg # Configuration
├── inventory/
│ └── hosts.yml # Inventory
├── group_vars/
│ ├── all.yml # All hosts
│ └── webservers.yml # Group-specific
├── host_vars/
│ └── server1.yml # Host-specific
├── roles/
│ └── app/
│ ├── tasks/
│ ├── handlers/
│ ├── templates/
│ ├── files/
│ └── defaults/
├── playbooks/
│ └── deploy.yml
├── templates/
│ └── config.j2
└── requirements.yml # Galaxy dependencies
```
## Idempotency Checklist
- [ ] Tasks produce same result on repeated runs
- [ ] No `changed_when: true` unless necessary
- [ ] Use `state: present/absent` not `shell` commands
- [ ] Check mode (`--check`) shows accurate changes
- [ ] Second run shows all "ok" (no changes)

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'] }}"
```