Ansible: Variables, Bucles y Condicionales - Técnicas Avanzadas de Automatización
Introducción
Si bien los playbooks básicos de Ansible pueden automatizar tareas simples, dominar verdaderamente la infraestructura como código requiere comprender las variables, bucles y condicionales. Estos tres conceptos fundamentales transforman los scripts de automatización rígidos en configuraciones flexibles, reutilizables e inteligentes que se adaptan a diferentes entornos, manejan estructuras de datos complejas y toman decisiones basadas en el estado del sistema.
Las variables te permiten parametrizar tus playbooks, haciéndolos funcionar en diferentes entornos sin duplicación de código. Los bucles te permiten realizar tareas repetitivas de manera eficiente sin escribir código redundante. Los condicionales proporcionan la lógica para ejecutar tareas selectivamente basándose en facts, variables o resultados de comandos. Juntas, estas características te permiten escribir playbooks DRY (Don't Repeat Yourself) que son mantenibles, escalables y listos para producción.
Esta guía completa explora la gestión avanzada de variables, técnicas sofisticadas de bucles y lógica condicional inteligente en Ansible. Aprenderás cómo manejar estructuras de datos complejas, implementar configuraciones dinámicas y construir playbooks que tomen decisiones inteligentes basadas en el estado de tu infraestructura.
Comprendiendo las Variables de Ansible
Conceptos Básicos y Convenciones de Nomenclatura de Variables
Las variables de Ansible siguen reglas específicas y mejores prácticas:
---
# Valid variable names
web_server_port: 80
database_host: db.example.com
app_version: "1.2.3"
enable_ssl: true
max_connections: 100
# Invalid variable names (avoid these)
# web-server-port: 80 # Hyphens not allowed
# 2fa_enabled: yes # Cannot start with number
# user.name: "admin" # Dots not allowed in names
# Best practices for naming
project_name: myapp # Use snake_case
project_environment: production # Be descriptive
project_database_host: db.prod.local # Use prefixes for grouping
Tipos de Variables y Estructuras de Datos
Ansible soporta varios tipos de datos:
---
# String variables
server_name: "web-server-01"
description: 'Production web server'
# Numeric variables
port: 8080
max_memory: 2048
percentage: 85.5
# Boolean variables
enable_monitoring: true
debug_mode: false
ssl_enabled: yes # yes/no also work as boolean
# Lists (arrays)
packages:
- nginx
- postgresql
- redis
# Alternative list syntax
web_servers: ["web1.example.com", "web2.example.com", "web3.example.com"]
# Dictionaries (hashes/maps)
database:
host: db.example.com
port: 5432
name: production_db
user: dbadmin
max_connections: 100
# Complex nested structures
application:
name: myapp
version: "2.1.0"
environments:
production:
hosts:
- prod-web-01
- prod-web-02
database:
host: prod-db.example.com
replica: prod-db-replica.example.com
cache:
enabled: true
ttl: 3600
staging:
hosts:
- stage-web-01
database:
host: stage-db.example.com
cache:
enabled: false
Precedencia y Ámbito de Variables
Ansible tiene 22 niveles de precedencia de variables (de menor a mayor):
# Variable precedence (simplified hierarchy):
# 1. Command line values (ansible-playbook -e "var=value")
# 2. Role defaults (roles/myrole/defaults/main.yml)
# 3. Inventory file or script group vars
# 4. Inventory group_vars/all
# 5. Playbook group_vars/all
# 6. Inventory group_vars/*
# 7. Playbook group_vars/*
# 8. Inventory file or script host vars
# 9. Inventory host_vars/*
# 10. Playbook host_vars/*
# 11. Host facts / cached set_facts
# 12. Play vars
# 13. Play vars_prompt
# 14. Play vars_files
# 15. Role vars (roles/myrole/vars/main.yml)
# 16. Block vars (within blocks)
# 17. Task vars (within tasks)
# 18. Include_vars
# 19. Set_facts / registered vars
# 20. Role (and include_role) params
# 21. Include params
# 22. Extra vars (-e "var=value")
Ejemplo demostrando precedencia:
---
# group_vars/all.yml
app_port: 8080
# group_vars/webservers.yml
app_port: 8081
# host_vars/web1.example.com.yml
app_port: 8082
# playbook.yml
- name: Variable precedence demo
hosts: web1.example.com
vars:
app_port: 8083 # This takes precedence over group_vars and host_vars
tasks:
- name: Show effective port
debug:
msg: "Application will run on port {{ app_port }}"
# Output: "Application will run on port 8083"
# Command line (highest precedence):
# ansible-playbook playbook.yml -e "app_port=9000"
# Output: "Application will run on port 9000"
Definición y Uso de Variables
Definiendo Variables en Playbooks
---
- name: Variable definition examples
hosts: webservers
# Method 1: vars section
vars:
http_port: 80
https_port: 443
server_name: www.example.com
# Method 2: vars_files
vars_files:
- vars/common.yml
- vars/{{ environment }}.yml
# Method 3: vars_prompt (interactive)
vars_prompt:
- name: deploy_version
prompt: "Which version to deploy?"
private: no
default: "latest"
- name: db_password
prompt: "Database password"
private: yes
encrypt: sha512_crypt
confirm: yes
tasks:
- name: Use variables in tasks
debug:
msg: "Server {{ server_name }} listens on {{ http_port }} and {{ https_port }}"
Accediendo Variables con Jinja2
---
- name: Variable access patterns
hosts: all
vars:
simple_var: "hello"
user_info:
name: john
email: [email protected]
roles:
- admin
- developer
servers:
- name: web1
ip: 192.168.1.10
- name: web2
ip: 192.168.1.11
tasks:
# Simple variable access
- name: Simple variable
debug:
msg: "{{ simple_var }}"
# Dictionary access - dot notation
- name: Dictionary with dot notation
debug:
msg: "User: {{ user_info.name }} - Email: {{ user_info.email }}"
# Dictionary access - bracket notation (safer)
- name: Dictionary with brackets
debug:
msg: "User: {{ user_info['name'] }} - Email: {{ user_info['email'] }}"
# List access by index
- name: List element access
debug:
msg: "First role: {{ user_info.roles[0] }}"
# Nested structure access
- name: Nested access
debug:
msg: "First server: {{ servers[0].name }} at {{ servers[0].ip }}"
# Variable in strings (concatenation)
- name: String concatenation
debug:
msg: "The server name is {{ server_name }} and the port is {{ http_port }}"
# Using filters
- name: Variable with filters
debug:
msg: "{{ simple_var | upper }}" # Converts to uppercase
Registrando Variables desde Salidas de Tareas
---
- name: Register variables from task results
hosts: localhost
tasks:
- name: Check disk space
command: df -h /
register: disk_space
changed_when: false
- name: Display entire registered variable
debug:
var: disk_space
verbosity: 2
- name: Display specific parts of registered variable
debug:
msg: |
Command: {{ disk_space.cmd }}
Return Code: {{ disk_space.rc }}
Output: {{ disk_space.stdout }}
Error Output: {{ disk_space.stderr }}
- name: Parse command output
shell: df -h / | tail -1 | awk '{print $5}' | sed 's/%//'
register: disk_usage_percent
changed_when: false
- name: Use registered variable in condition
debug:
msg: "WARNING: Disk usage is {{ disk_usage_percent.stdout }}%"
when: disk_usage_percent.stdout | int > 80
- name: Register complex data
shell: |
cat << EOF
{
"status": "running",
"uptime": "5 days",
"connections": 42
}
EOF
register: service_status
changed_when: false
- name: Parse JSON output
set_fact:
parsed_status: "{{ service_status.stdout | from_json }}"
- name: Use parsed JSON data
debug:
msg: "Service status: {{ parsed_status.status }}, Connections: {{ parsed_status.connections }}"
Usando set_fact para Variables Dinámicas
---
- name: Dynamic variable creation with set_fact
hosts: all
tasks:
- name: Set simple fact
set_fact:
deployment_time: "{{ ansible_date_time.iso8601 }}"
- name: Set calculated fact
set_fact:
total_memory_gb: "{{ (ansible_memtotal_mb / 1024) | round(2) }}"
- name: Set complex fact
set_fact:
server_info:
hostname: "{{ ansible_hostname }}"
os: "{{ ansible_distribution }} {{ ansible_distribution_version }}"
ip: "{{ ansible_default_ipv4.address }}"
memory: "{{ total_memory_gb }} GB"
- name: Use set facts
debug:
msg: "Deployed at {{ deployment_time }} to {{ server_info.hostname }}"
- name: Build fact from multiple sources
set_fact:
deployment_config: |
Hostname: {{ ansible_hostname }}
Environment: {{ environment | default('development') }}
Deployed by: {{ ansible_user_id }}
Timestamp: {{ deployment_time }}
- name: Conditional fact setting
set_fact:
server_role: "{{ 'production' if ansible_hostname.startswith('prod') else 'staging' }}"
- name: Merge dictionaries
set_fact:
combined_config: "{{ default_config | combine(custom_config) }}"
vars:
default_config:
port: 8080
debug: false
max_connections: 100
custom_config:
port: 9000
ssl_enabled: true
Técnicas Avanzadas de Bucles
Bucles Básicos con loop
---
- name: Basic loop examples
hosts: all
become: yes
tasks:
# Simple list loop
- name: Install packages
apt:
name: "{{ item }}"
state: present
loop:
- nginx
- postgresql
- redis-server
- git
# Loop with variables
- name: Create users
user:
name: "{{ item }}"
state: present
shell: /bin/bash
create_home: yes
loop:
- alice
- bob
- charlie
# Loop with list variable
- name: Create directories
file:
path: "{{ item }}"
state: directory
mode: '0755'
loop: "{{ directories }}"
vars:
directories:
- /opt/app
- /opt/app/logs
- /opt/app/data
- /opt/app/config
# Loop with range
- name: Create numbered directories
file:
path: "/var/backup/day{{ item }}"
state: directory
loop: "{{ range(1, 8) | list }}" # Creates day1 through day7
Bucles sobre Diccionarios
---
- name: Dictionary loop examples
hosts: all
become: yes
vars:
users:
alice:
uid: 1001
groups: [sudo, developers]
comment: "Alice Smith"
bob:
uid: 1002
groups: [developers]
comment: "Bob Johnson"
charlie:
uid: 1003
groups: [sudo, operators]
comment: "Charlie Brown"
virtual_hosts:
example.com:
root: /var/www/example.com
port: 80
api.example.com:
root: /var/www/api
port: 8080
tasks:
# Loop over dictionary with dict2items
- name: Create users from dictionary
user:
name: "{{ item.key }}"
uid: "{{ item.value.uid }}"
groups: "{{ item.value.groups | join(',') }}"
comment: "{{ item.value.comment }}"
state: present
loop: "{{ users | dict2items }}"
# Access key and value separately
- name: Configure virtual hosts
template:
src: vhost.conf.j2
dest: "/etc/nginx/sites-available/{{ item.key }}.conf"
loop: "{{ virtual_hosts | dict2items }}"
notify: reload nginx
# Alternative: use with_dict (older syntax)
- name: Create vhost directories
file:
path: "{{ item.value.root }}"
state: directory
mode: '0755'
with_dict: "{{ virtual_hosts }}"
Bucles Anidados Complejos
---
- name: Nested loop examples
hosts: all
become: yes
vars:
environments:
- name: production
servers:
- web1
- web2
- db1
- name: staging
servers:
- stage-web
- stage-db
firewall_rules:
- chain: INPUT
ports: [80, 443]
protocol: tcp
- chain: INPUT
ports: [22]
protocol: tcp
tasks:
# Nested loop with subelements
- name: Create server-specific files
file:
path: "/etc/config/{{ item.0.name }}/{{ item.1 }}.conf"
state: touch
loop: "{{ environments | subelements('servers') }}"
# Loop with nested structure
- name: Configure firewall rules
ufw:
rule: allow
port: "{{ item.1 }}"
proto: "{{ item.0.protocol }}"
loop: "{{ firewall_rules | subelements('ports') }}"
# Cartesian product (all combinations)
- name: Create all environment-server combinations
debug:
msg: "Creating config for {{ item.0 }} on {{ item.1 }}"
loop: "{{ ['production', 'staging'] | product(['web', 'db', 'cache']) | list }}"
# Flattened nested loop
- name: Install packages for all environments
apt:
name: "{{ item }}"
state: present
loop: "{{ environments | map(attribute='servers') | flatten }}"
Controles Avanzados de Bucles
---
- name: Advanced loop control examples
hosts: all
tasks:
# Loop with index
- name: Show item with index
debug:
msg: "Item {{ ansible_loop.index }}: {{ item }}"
loop:
- first
- second
- third
# Loop with extended attributes
- name: Extended loop info
debug:
msg: |
Current: {{ item }}
Index: {{ ansible_loop.index }}
Index0: {{ ansible_loop.index0 }}
First: {{ ansible_loop.first }}
Last: {{ ansible_loop.last }}
Length: {{ ansible_loop.length }}
Remaining: {{ ansible_loop.revindex }}
loop:
- alpha
- beta
- gamma
# Loop with label (cleaner output)
- name: Loop with custom label
user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
groups: "{{ item.groups }}"
loop:
- { name: alice, uid: 1001, groups: [sudo, dev] }
- { name: bob, uid: 1002, groups: [dev] }
- { name: charlie, uid: 1003, groups: [ops] }
loop_control:
label: "{{ item.name }}" # Only shows name in output
# Pause between loop iterations
- name: Sequential deployment with pause
command: /opt/deploy.sh {{ item }}
loop:
- service1
- service2
- service3
loop_control:
pause: 5 # Wait 5 seconds between iterations
# Loop with custom variable name
- name: Nested loop with custom var names
debug:
msg: "Creating {{ env_name }}-{{ server_name }}"
loop: "{{ environments }}"
loop_control:
loop_var: env_name
vars:
inner_loop:
- web
- db
Bucles con until (Lógica de Reintentos)
---
- name: Loop with retry logic
hosts: all
tasks:
# Retry until condition is met
- name: Wait for service to be ready
uri:
url: http://localhost:8080/health
status_code: 200
register: result
until: result.status == 200
retries: 10
delay: 5
# Retry with complex condition
- name: Wait for database migration
command: /opt/app/check-migration.sh
register: migration_status
until: >
migration_status.rc == 0 and
'COMPLETED' in migration_status.stdout
retries: 30
delay: 10
changed_when: false
# Retry with exponential backoff simulation
- name: API call with retries
uri:
url: https://api.example.com/deploy
method: POST
body_format: json
body:
version: "{{ app_version }}"
status_code: [200, 201]
register: api_result
until: api_result.status in [200, 201]
retries: 5
delay: "{{ 2 ** (ansible_loop.index0 | default(0)) }}"
# Conditional retry
- name: Check deployment status
shell: kubectl get deployment myapp -o jsonpath='{.status.availableReplicas}'
register: replicas
until: replicas.stdout | int >= 3
retries: 20
delay: 15
changed_when: false
Ejecución Condicional con when
Condicionales Básicos
---
- name: Basic conditional examples
hosts: all
become: yes
vars:
environment: production
enable_monitoring: true
server_role: webserver
tasks:
# Simple equality check
- name: Install production packages
apt:
name:
- monitoring-agent
- log-collector
state: present
when: environment == "production"
# Boolean check
- name: Configure monitoring
template:
src: monitoring.conf.j2
dest: /etc/monitoring/config.conf
when: enable_monitoring
# Multiple conditions (AND)
- name: Configure production web server
template:
src: nginx-prod.conf.j2
dest: /etc/nginx/nginx.conf
when:
- environment == "production"
- server_role == "webserver"
notify: reload nginx
# Multiple conditions (OR)
- name: Install common packages
apt:
name: htop
state: present
when: ansible_distribution == "Ubuntu" or ansible_distribution == "Debian"
# Inequality check
- name: Warn about old OS version
debug:
msg: "WARNING: OS version is outdated"
when: ansible_distribution_version is version('18.04', '<')
# Check if variable is defined
- name: Use optional configuration
template:
src: custom.conf.j2
dest: /etc/app/custom.conf
when: custom_config is defined
# Check if variable is undefined
- name: Set default value
set_fact:
app_port: 8080
when: app_port is not defined
# Check for empty value
- name: Validate required variable
fail:
msg: "database_host is required"
when: database_host is not defined or database_host | length == 0
Condicionales Basados en Facts
---
- name: Fact-based conditionals
hosts: all
become: yes
tasks:
# OS family checks
- name: Install packages on Debian systems
apt:
name: nginx
state: present
when: ansible_os_family == "Debian"
- name: Install packages on RedHat systems
yum:
name: nginx
state: present
when: ansible_os_family == "RedHat"
# Distribution-specific tasks
- name: Ubuntu-specific configuration
template:
src: ubuntu-config.j2
dest: /etc/app/config
when: ansible_distribution == "Ubuntu"
# Version-based conditionals
- name: Use systemd on modern systems
systemd:
name: myapp
state: started
enabled: yes
when: ansible_service_mgr == "systemd"
- name: Legacy init.d service
service:
name: myapp
state: started
enabled: yes
when: ansible_service_mgr != "systemd"
# Architecture-based tasks
- name: Install 64-bit specific package
apt:
name: some-package-amd64
state: present
when: ansible_architecture == "x86_64"
# Memory-based decisions
- name: Configure for high-memory server
lineinfile:
path: /etc/app/config
line: "memory_limit=8G"
when: ansible_memtotal_mb > 16000
# Disk space checks
- name: Check available disk space
command: df -h /var | tail -1 | awk '{print $5}' | sed 's/%//'
register: disk_usage
changed_when: false
- name: Warn about low disk space
debug:
msg: "WARNING: Disk usage is {{ disk_usage.stdout }}%"
when: disk_usage.stdout | int > 80
# Hostname pattern matching
- name: Configure production servers
template:
src: prod-config.j2
dest: /etc/app/config
when: inventory_hostname is match('prod-.*')
# Multiple fact-based conditions
- name: Optimize for production Ubuntu servers
sysctl:
name: "{{ item.name }}"
value: "{{ item.value }}"
state: present
loop:
- { name: 'net.core.somaxconn', value: '4096' }
- { name: 'net.ipv4.tcp_max_syn_backlog', value: '8192' }
when:
- ansible_distribution == "Ubuntu"
- ansible_distribution_version is version('20.04', '>=')
- ansible_memtotal_mb > 8000
Condicionales con Variables Registradas
---
- name: Registered variable conditionals
hosts: all
become: yes
tasks:
# Check command return code
- name: Check if service exists
command: systemctl status myapp
register: service_check
failed_when: false
changed_when: false
- name: Install service if not present
include_tasks: install-service.yml
when: service_check.rc != 0
# Check command output
- name: Get current version
command: /opt/app/bin/version
register: current_version
changed_when: false
- name: Upgrade if version is old
include_tasks: upgrade.yml
when: current_version.stdout is version('2.0.0', '<')
# Check file existence
- name: Check if config exists
stat:
path: /etc/app/config.yml
register: config_file
- name: Create default config
template:
src: default-config.yml.j2
dest: /etc/app/config.yml
when: not config_file.stat.exists
- name: Update config if it exists
lineinfile:
path: /etc/app/config.yml
line: "feature_flag: enabled"
when: config_file.stat.exists
# Check service state
- name: Check if nginx is running
systemd:
name: nginx
register: nginx_status
check_mode: yes
- name: Reload nginx if running
systemd:
name: nginx
state: reloaded
when: nginx_status.status.ActiveState == "active"
# Complex condition with registered variables
- name: Check multiple services
systemd:
name: "{{ item }}"
register: services_status
loop:
- nginx
- postgresql
- redis
check_mode: yes
- name: Report if any service is down
debug:
msg: "Service {{ item.item }} is not running"
loop: "{{ services_status.results }}"
when: item.status.ActiveState != "active"
Patrones Condicionales Avanzados
---
- name: Advanced conditional patterns
hosts: all
become: yes
vars:
deployment_strategy: rolling
allowed_strategies: [rolling, blue-green, canary]
tasks:
# Check if value in list
- name: Validate deployment strategy
fail:
msg: "Invalid deployment strategy: {{ deployment_strategy }}"
when: deployment_strategy not in allowed_strategies
# String pattern matching
- name: Configure production servers
template:
src: prod.conf.j2
dest: /etc/app/config
when: inventory_hostname is match('prod-web-\d+')
# String contains
- name: Special handling for database servers
include_tasks: db-tasks.yml
when: "'db' in inventory_hostname"
# Type checking
- name: Validate port number
fail:
msg: "Port must be numeric"
when: app_port is not number
# List/dict checks
- name: Process if list has items
include_tasks: process-items.yml
when: item_list is defined and item_list | length > 0
# Conditional with filters
- name: Install on even-numbered servers
debug:
msg: "Installing on {{ inventory_hostname }}"
when: inventory_hostname | regex_search('\d+$') | int is even
# Combined complex conditions
- name: Production deployment gate
assert:
that:
- deployment_approved | default(false)
- backup_completed | default(false)
- rollback_plan_exists | default(false)
fail_msg: "Pre-deployment checks failed"
success_msg: "All deployment gates passed"
when: environment == "production"
# Conditional based on multiple facts
- name: Optimize kernel parameters
sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
loop: "{{ kernel_params | dict2items }}"
when:
- ansible_virtualization_type != "docker"
- ansible_memtotal_mb > 4000
- ansible_processor_vcpus > 2
# Ternary operations
- name: Set environment-specific value
set_fact:
max_connections: "{{ 200 if environment == 'production' else 50 }}"
- name: Configure with ternary
template:
src: app.conf.j2
dest: /etc/app/config
vars:
log_level: "{{ 'info' if environment == 'production' else 'debug' }}"
cache_enabled: "{{ true if ansible_memtotal_mb > 8000 else false }}"
Combinando Variables, Bucles y Condicionales
Ejemplo del Mundo Real: Despliegue Multi-Entorno
---
- name: Deploy application across environments
hosts: all
become: yes
vars:
environments:
production:
domain: app.example.com
replicas: 3
resources:
memory: "2Gi"
cpu: "1000m"
features:
caching: true
monitoring: true
debug: false
staging:
domain: staging.app.example.com
replicas: 2
resources:
memory: "1Gi"
cpu: "500m"
features:
caching: true
monitoring: true
debug: true
development:
domain: dev.app.example.com
replicas: 1
resources:
memory: "512Mi"
cpu: "250m"
features:
caching: false
monitoring: false
debug: true
app_version: "{{ deploy_version | default('latest') }}"
tasks:
# Set environment based on inventory group
- name: Determine environment
set_fact:
current_env: "{{ 'production' if 'prod' in group_names else 'staging' if 'stage' in group_names else 'development' }}"
- name: Load environment configuration
set_fact:
env_config: "{{ environments[current_env] }}"
- name: Display deployment info
debug:
msg: |
Deploying to: {{ current_env }}
Domain: {{ env_config.domain }}
Replicas: {{ env_config.replicas }}
Version: {{ app_version }}
# Install environment-specific packages
- name: Install monitoring tools
apt:
name:
- prometheus-node-exporter
- telegraf
state: present
when: env_config.features.monitoring
loop: "{{ env_config.features | dict2items }}"
# Configure based on server role and environment
- name: Configure application
template:
src: app-config.j2
dest: /etc/app/config.yml
vars:
config:
domain: "{{ env_config.domain }}"
debug: "{{ env_config.features.debug }}"
cache:
enabled: "{{ env_config.features.caching }}"
ttl: "{{ 3600 if current_env == 'production' else 60 }}"
database:
pool_size: "{{ 50 if current_env == 'production' else 10 }}"
logging:
level: "{{ 'info' if current_env == 'production' else 'debug' }}"
# Deploy with rolling strategy based on environment
- name: Deploy application
include_tasks: deploy-app.yml
when: inventory_hostname in groups['appservers']
vars:
deployment:
version: "{{ app_version }}"
replicas: "{{ env_config.replicas }}"
strategy: "{{ 'rolling' if current_env == 'production' else 'recreate' }}"
Ejemplo Complejo: Validación de Infraestructura
---
- name: Infrastructure validation and reporting
hosts: all
gather_facts: yes
vars:
requirements:
min_memory_mb: 4096
min_disk_gb: 50
required_ports: [22, 80, 443]
os_family: Debian
min_os_version: "20.04"
compliance_checks:
- name: SSH hardening
command: grep "^PermitRootLogin no" /etc/ssh/sshd_config
- name: Firewall enabled
command: ufw status | grep -q "Status: active"
- name: Automatic updates
command: test -f /etc/apt/apt.conf.d/20auto-upgrades
tasks:
# Gather additional facts
- name: Check disk space
shell: df -BG / | tail -1 | awk '{print $4}' | sed 's/G//'
register: available_disk
changed_when: false
- name: Check open ports
wait_for:
port: "{{ item }}"
timeout: 1
state: started
register: port_checks
failed_when: false
loop: "{{ requirements.required_ports }}"
# Validate requirements with detailed reporting
- name: Validate memory
set_fact:
memory_check:
passed: "{{ ansible_memtotal_mb >= requirements.min_memory_mb }}"
current: "{{ ansible_memtotal_mb }}"
required: "{{ requirements.min_memory_mb }}"
message: "{{ 'OK' if ansible_memtotal_mb >= requirements.min_memory_mb else 'INSUFFICIENT' }}"
- name: Validate disk space
set_fact:
disk_check:
passed: "{{ available_disk.stdout | int >= requirements.min_disk_gb }}"
current: "{{ available_disk.stdout }}"
required: "{{ requirements.min_disk_gb }}"
message: "{{ 'OK' if available_disk.stdout | int >= requirements.min_disk_gb else 'INSUFFICIENT' }}"
- name: Validate OS
set_fact:
os_check:
passed: "{{ ansible_os_family == requirements.os_family and ansible_distribution_version is version(requirements.min_os_version, '>=') }}"
current: "{{ ansible_distribution }} {{ ansible_distribution_version }}"
required: "{{ requirements.os_family }} {{ requirements.min_os_version }}+"
message: "{{ 'OK' if (ansible_os_family == requirements.os_family and ansible_distribution_version is version(requirements.min_os_version, '>=')) else 'INCOMPATIBLE' }}"
# Run compliance checks
- name: Execute compliance checks
shell: "{{ item.command }}"
register: compliance_results
failed_when: false
changed_when: false
loop: "{{ compliance_checks }}"
# Build comprehensive report
- name: Build validation report
set_fact:
validation_report:
hostname: "{{ inventory_hostname }}"
timestamp: "{{ ansible_date_time.iso8601 }}"
requirements:
memory: "{{ memory_check }}"
disk: "{{ disk_check }}"
os: "{{ os_check }}"
compliance:
- name: "{{ item.item.name }}"
status: "{{ 'PASS' if item.rc == 0 else 'FAIL' }}"
- loop: "{{ compliance_results.results }}"
overall_status: "{{ 'COMPLIANT' if (memory_check.passed and disk_check.passed and os_check.passed) else 'NON-COMPLIANT' }}"
- name: Display validation report
debug:
var: validation_report
# Take action based on validation
- name: Fail if non-compliant
fail:
msg: |
Server {{ inventory_hostname }} failed validation:
Memory: {{ memory_check.message }}
Disk: {{ disk_check.message }}
OS: {{ os_check.message }}
when: not (memory_check.passed and disk_check.passed and os_check.passed)
- name: Save report to file
copy:
content: "{{ validation_report | to_nice_json }}"
dest: "/var/log/validation-{{ ansible_date_time.epoch }}.json"
when: validation_report.overall_status == "NON-COMPLIANT"
Mejores Prácticas
1. Organización de Variables
# Good: Organized variable structure
---
# group_vars/all.yml
company_name: "Example Corp"
base_domain: "example.com"
common_packages:
- curl
- wget
- vim
- git
# group_vars/webservers.yml
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
# host_vars/web1.example.com.yml
server_specific_config:
role: primary
backup_enabled: true
2. Convenciones de Nomenclatura de Variables
# Good: Clear, prefixed variable names
mysql_root_password: "secret"
mysql_max_connections: 100
mysql_port: 3306
nginx_worker_processes: 4
nginx_client_max_body_size: "64M"
app_version: "1.2.3"
app_environment: "production"
# Avoid: Generic names that might conflict
password: "secret" # Too generic
port: 3306 # Ambiguous
version: "1.2.3" # Which version?
3. Optimización de Bucles
---
# Good: Single task with loop
- name: Install multiple packages
apt:
name: "{{ item }}"
state: present
loop: "{{ required_packages }}"
# Avoid: Multiple identical tasks
- name: Install nginx
apt:
name: nginx
state: present
- name: Install postgresql
apt:
name: postgresql
state: present
# ... and so on
4. Mejores Prácticas de Condicionales
---
# Good: Clear, readable conditionals
- name: Install monitoring agent
apt:
name: monitoring-agent
state: present
when:
- environment == "production"
- enable_monitoring | default(true)
- ansible_os_family == "Debian"
# Good: Use meaningful variable names for complex conditions
- name: Determine if deployment should proceed
set_fact:
should_deploy: "{{ (deployment_approved and backup_completed and health_check_passed) }}"
- name: Deploy application
include_tasks: deploy.yml
when: should_deploy
5. Manejo de Errores
---
- name: Safe variable access
debug:
msg: "Database host: {{ database.host | default('localhost') }}"
- name: Validate required variables
assert:
that:
- database_password is defined
- database_password | length > 0
fail_msg: "database_password is required"
- name: Safe dictionary access
set_fact:
db_port: "{{ database.port | default(5432) }}"
when: database is defined
Solución de Problemas
Depurando Variables
---
- name: Debug variables
hosts: localhost
tasks:
# Show all variables for host
- name: Display all variables
debug:
var: hostvars[inventory_hostname]
# Show specific variable
- name: Show specific var
debug:
var: ansible_os_family
# Show variable with message
- name: Formatted debug
debug:
msg: "The OS family is {{ ansible_os_family }}"
# Conditional debug with verbosity
- name: Verbose debug
debug:
var: some_complex_variable
verbosity: 2 # Only shows with -vv or higher
Probando Condicionales
# Dry run to test logic
ansible-playbook playbook.yml --check
# Show variable values
ansible-playbook playbook.yml -e "ansible_verbosity=2"
# Test specific tags
ansible-playbook playbook.yml --tags "config" --check --diff
Conclusión
Dominar las variables, bucles y condicionales en Ansible es esencial para crear automatización flexible, mantenible y poderosa. Estas características transforman la simple ejecución de tareas en infraestructura como código inteligente que se adapta a diferentes entornos, maneja escenarios complejos y toma decisiones basadas en datos.
Puntos clave:
- Usa niveles de precedencia de variables apropiados para flexibilidad
- Organiza las variables lógicamente en group_vars y host_vars
- Aprovecha los bucles para evitar código repetitivo
- Aplica condicionales para crear playbooks adaptativos
- Combina estas características para automatización sofisticada
- Sigue convenciones de nomenclatura y mejores prácticas
- Prueba y valida tu lógica exhaustivamente
Al aplicar estos conceptos, puedes construir automatización robusta que escala en toda tu infraestructura mientras permanece legible y mantenible.


