Día 04 - Copiar archivos a servidores de aplicación con Ansible
Problema / Desafío
Copiar el archivo /usr/src/data/index.html a la ruta /opt/data en todos los servidores de aplicación usando Ansible. Se debe crear un inventario con los tres servidores y un playbook que realice la copia y valide el resultado.
Conceptos clave
- Módulo
copy: Copia archivos desde el nodo de control hacia los hosts remotos, o copia archivos localmente dentro de los hosts remotos. Cuandosrces una ruta absoluta en el host remoto y se usa conremote_src: yes, copia archivos dentro del mismo servidor remoto. Sinremote_src, Ansible busca el archivo en el nodo de control. - Módulo
slurp: Lee el contenido de un archivo remoto y lo devuelve codificado en base64. Es útil para validar que un archivo fue copiado correctamente sin necesidad de conectarse manualmente al servidor. - Filtro
b64decode: Decodifica contenido en base64 a texto legible. Se usa en combinación conslurppara mostrar el contenido real del archivo. - Inventory (Inventario): Archivo que define los hosts y grupos sobre los que Ansible ejecuta tareas. Puede escribirse en diferentes formatos: INI, YAML y JSON.
- Variables de conexión: Parámetros como
ansible_user,ansible_passwordyansible_hostque definen cómo Ansible se conecta a cada host.
Pasos
- Crear el archivo de inventario con los tres servidores de aplicación
- Crear el playbook con las tareas de copia y validación
- Ejecutar el playbook apuntando al inventario
- Verificar que el archivo fue copiado correctamente en todos los servidores
Comandos / Código
Inventory
[all]
stapp01 ansible_user=tony ansible_password=xxxx ansible_host=stapp01.stratos.xfusioncorp.com
stapp02 ansible_user=steve ansible_password=xxxx ansible_host=stapp02.stratos.xfusioncorp.com
stapp03 ansible_user=banner ansible_password=xxxx ansible_host=stapp03.stratos.xfusioncorp.com
Playbook
---
- name: Copy file to apps servers
hosts: all
become: yes
vars:
file_src: "/usr/src/data/index.html"
file_dst: "/opt/data"
tasks:
- name: copy file
copy:
src: "{{ file_src }}"
dest: "{{ file_dst }}"
- name: validate file
slurp:
src: "/opt/data/index.html"
register: file_content
- name: content file
debug:
msg: "{{ file_content.content | b64decode }}"
Desglose del playbook:
hosts: all: Ejecuta en todos los hosts del inventario (los tres servidores de aplicación).become: yes: Escala privilegios consudopara poder escribir en/opt/data.vars: Define variables reutilizables para las rutas origen y destino.
Tareas:
- copy file: Usa el módulo
copypara copiar el archivo desdefile_srchaciafile_dsten cada servidor remoto. - validate file: Usa
slurppara leer el contenido del archivo copiado y guardarlo en la variablefile_content. - content file: Decodifica el contenido base64 y lo muestra en la salida de Ansible con
debug.
Ejecución
Salida esperada (resumida):
PLAY [Copy file to apps servers] **********************************************
TASK [copy file] **************************************************************
changed: [stapp01]
changed: [stapp02]
changed: [stapp03]
TASK [validate file] **********************************************************
ok: [stapp01]
ok: [stapp02]
ok: [stapp03]
TASK [content file] ***********************************************************
ok: [stapp01] => {
"msg": "<contenido del archivo index.html>"
}
ok: [stapp02] => {
"msg": "<contenido del archivo index.html>"
}
ok: [stapp03] => {
"msg": "<contenido del archivo index.html>"
}
PLAY RECAP ********************************************************************
stapp01 : ok=3 changed=1 unreachable=0 failed=0 skipped=0
stapp02 : ok=3 changed=1 unreachable=0 failed=0 skipped=0
stapp03 : ok=3 changed=1 unreachable=0 failed=0 skipped=0
Formatos de inventario en Ansible
Ansible soporta múltiples formatos para definir inventarios. Cada uno tiene sus ventajas según el caso de uso.
1. Formato INI (el más común)
Es el formato por defecto y el más simple. Usa corchetes para grupos y una línea por host.
[webservers]
web1 ansible_host=192.168.1.10 ansible_user=admin
web2 ansible_host=192.168.1.11 ansible_user=admin
[dbservers]
db1 ansible_host=192.168.1.20 ansible_user=dba
[all:vars]
ansible_ssh_common_args='-o StrictHostKeyChecking=no'
[production:children]
webservers
dbservers
Características:
- Grupos definidos con [nombre_grupo]
- Variables de grupo con [nombre_grupo:vars]
- Grupos de grupos con [nombre_grupo:children]
- Una línea por host con variables inline
2. Formato YAML
Más legible para inventarios complejos. Usa la extensión .yml o .yaml.
all:
children:
webservers:
hosts:
web1:
ansible_host: 192.168.1.10
ansible_user: admin
web2:
ansible_host: 192.168.1.11
ansible_user: admin
dbservers:
hosts:
db1:
ansible_host: 192.168.1.20
ansible_user: dba
vars:
ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
Características: - Estructura jerárquica clara - Ideal cuando hay muchas variables por host - Más fácil de versionar y revisar en diffs de Git
3. Formato JSON
Menos usado manualmente, pero útil cuando el inventario se genera de forma programática.
{
"all": {
"children": {
"webservers": {
"hosts": {
"web1": {
"ansible_host": "192.168.1.10",
"ansible_user": "admin"
},
"web2": {
"ansible_host": "192.168.1.11",
"ansible_user": "admin"
}
}
},
"dbservers": {
"hosts": {
"db1": {
"ansible_host": "192.168.1.20",
"ansible_user": "dba"
}
}
}
},
"vars": {
"ansible_ssh_common_args": "-o StrictHostKeyChecking=no"
}
}
}
Características: - Ideal para inventarios generados por scripts o APIs - Fácil de integrar con herramientas que exportan JSON - Verboso para editar manualmente
4. Inventario dinámico (scripts)
En lugar de un archivo estático, Ansible puede ejecutar un script que genere el inventario en tiempo real. Útil para entornos cloud donde los servidores cambian constantemente.
#!/bin/bash
# dynamic_inventory.sh
# Debe retornar JSON válido con la estructura de inventario
cat <<EOF
{
"webservers": {
"hosts": ["web1.example.com", "web2.example.com"]
},
"_meta": {
"hostvars": {
"web1.example.com": {
"ansible_user": "admin"
},
"web2.example.com": {
"ansible_user": "admin"
}
}
}
}
EOF
# El script debe tener permisos de ejecución
chmod +x dynamic_inventory.sh
# Usar con -i apuntando al script
ansible-playbook -i dynamic_inventory.sh playbook.yml
Características:
- El script debe ser ejecutable y retornar JSON válido
- Debe soportar los flags --list (listar todo) y --host <hostname> (variables de un host)
- Existen plugins oficiales para AWS, GCP, Azure, Docker, etc.
- Ansible detecta automáticamente si el inventario es un archivo estático o un script ejecutable
5. Directorio de inventario
Se puede usar un directorio que contenga múltiples archivos de inventario. Ansible los combina todos automáticamente.
inventory/
├── 01-static-hosts # Archivo INI con hosts fijos
├── 02-dynamic-aws.py # Script dinámico para AWS
└── group_vars/
├── all.yml # Variables para todos los hosts
└── webservers.yml # Variables para el grupo webservers
Comparación de formatos
| Formato | Facilidad de edición | Legibilidad con muchas variables | Generación automática | Caso de uso ideal |
|---|---|---|---|---|
| INI | Alta | Baja (todo en una línea) | Media | Inventarios pequeños y simples |
| YAML | Alta | Alta (estructura jerárquica) | Media | Inventarios medianos con muchas variables |
| JSON | Baja | Media | Alta | Inventarios generados por scripts/APIs |
| Dinámico | N/A | N/A | Nativa | Entornos cloud con infraestructura cambiante |
| Directorio | Alta | Alta | Alta | Entornos mixtos (estático + dinámico) |
El inventario de este ejercicio en formato YAML
Para comparar, así se vería el inventario de este día en formato YAML:
all:
hosts:
stapp01:
ansible_user: tony
ansible_password: xxxx
ansible_host: stapp01.stratos.xfusioncorp.com
stapp02:
ansible_user: steve
ansible_password: xxxx
ansible_host: stapp02.stratos.xfusioncorp.com
stapp03:
ansible_user: banner
ansible_password: xxxx
ansible_host: stapp03.stratos.xfusioncorp.com
Troubleshooting
| Problema | Solución |
|---|---|
Permission denied al copiar a /opt/data |
Verificar que become: yes está definido en el playbook |
Source /usr/src/data/index.html not found |
Confirmar que el archivo existe en el nodo de control. Si está en el host remoto, usar remote_src: yes en el módulo copy |
The destination directory /opt/data does not exist |
Agregar una tarea previa con el módulo file para crear el directorio: file: path=/opt/data state=directory |
Authentication failure en algún servidor |
Verificar usuario y contraseña en el inventario para ese host |
slurp falla con file not found |
La tarea de copia falló silenciosamente. Revisar la salida de la tarea copy para ese host |