Introducción
Un paquete nuevo de Courtyard Sessions tiene una página inicial convincente, pero le falta la descarga para visitantes. Este entorno independiente empieza con el sitio original funcionando en ambos servidores. Mejora el despliegue Ansible preparado para restaurar la versión utilizable anterior cuando falle un candidato y detenerse antes de actualizar otro nodo.
Completa primero Actualiza un host a la vez o ten práctica equivalente con su inspección, delegación, lotes seriales, operaciones copy/file y comprobaciones HTTP completas. Este laboratorio enseña a registrar el enlace real y a recuperar con block/rescue. La duración es una estimación del autor, no un tiempo medido con principiantes.
Restaura una versión fallida y detén el despliegue
En este paso, registrarás la versión anterior real, recuperarás el nodo actual tras una comprobación fallida y detendrás explícitamente el siguiente lote.
Abre la pestaña Website. El título original es Good things, around the corner. Usa The program y Download the program (CSV) para comprobar las dos sesiones originales. La puerta de enlace y los dos servidores SSH son independientes del laboratorio anterior; no necesitan archivos ni procesos previos.
Cambia a la pestaña Controller, que comienza en /home/labex/project. Los archivos preparados inspect.yml y rollout.yml son las herramientas del laboratorio anterior. Lee el paquete seleccionado y el estado actual:
cat group_vars/all.yml
ls packages/courtyard-incomplete
ansible-playbook inspect.yml
release_id selecciona courtyard-incomplete. Existen su página inicial e identidad, pero no schedule.csv. Los dos servidores reales siguen indicando courtyard-preview. Conserva los bytes de todos los paquetes: añadir un CSV al candidato ocultaría el defecto en lugar de practicar la recuperación. Todavía no ejecutes el despliegue sin protección.
Un block agrupa tareas que pueden fallar; sus tareas rescue se ejecutan después de que falle normalmente una tarea del bloque. Registra current antes del bloque mediante stat, que lee metadatos del sistema de archivos. follow: false lee el enlace en sí y no el directorio de destino. El valor guardado previous.stat.lnk_source es el destino anterior real: la recuperación restaura lo que estaba ejecutándose en lugar de suponer un directorio fijo.
La recuperación debe restaurar el enlace, solicitar la página y el CSV completos restaurados y compararlos con el paquete anterior. El filtro basename extrae el nombre del directorio de versión del destino guardado para que las comparaciones existentes con lookup('file', ...) lean el paquete local correspondiente. Solo se reincorpora un nodo restaurado y saludable. Si falta algún recurso restaurado o su contenido es incorrecto, esa tarea de rescue falla y el nodo permanece fuera; el otro servidor sigue atendiendo visitantes.
Un rescue correcto elimina el efecto del fallo original en las decisiones de tratamiento de errores de Ansible. Por ello, any_errors_fatal por sí solo no detiene los lotes posteriores cuando la recuperación tiene éxito. Un fail explícito al final de rescue informa del rechazo y detiene el siguiente lote. La salida distinta de cero es intencionada, aunque el sitio anterior vuelva a funcionar. El YAML inválido y los hosts SSH inalcanzables no activan este mecanismo de rescue; no es una garantía general de reversión.
Sustituye rollout.yml por esta versión completa. cat > reemplaza solo este playbook del alumno; el delimitador entre comillas conserva las expresiones de Ansible. Mantén los espacios mostrados:
cat > rollout.yml <<'YAML'
- name: Release one website backend at a time
hosts: website
order: sorted
gather_facts: false
serial: 1
any_errors_fatal: true
tasks:
- name: Remember the actual previous release link
ansible.builtin.stat:
path: "{{ release_root }}/current"
follow: false
register: previous
- name: Update this backend or recover it
block:
- name: Drain this backend from the pool
ansible.builtin.uri:
url: "{{ pool_api }}/pool"
method: POST
body_format: json
body:
node: "{{ inventory_hostname }}"
enabled: false
delegate_to: controller
- name: Create this release directory
ansible.builtin.file:
path: "{{ release_root }}/releases/{{ release_id }}"
state: directory
mode: '0755'
- name: Copy the selected package over SSH
ansible.builtin.copy:
src: "{{ package_root }}/{{ release_id }}/"
dest: "{{ release_root }}/releases/{{ release_id }}/"
mode: '0644'
- name: Activate the selected release
ansible.builtin.file:
src: "{{ release_root }}/releases/{{ release_id }}"
dest: "{{ release_root }}/current"
state: link
- name: Read the real candidate homepage
ansible.builtin.uri:
url: "http://{{ hostvars[inventory_hostname].ansible_host }}:{{ backend_port }}/"
return_content: true
delegate_to: controller
register: homepage
- name: Require the complete candidate homepage
ansible.builtin.assert:
that: homepage.content == lookup('file', package_root ~ '/' ~ release_id ~ '/index.html', rstrip=False)
- name: Read the real candidate download
ansible.builtin.uri:
url: "http://{{ hostvars[inventory_hostname].ansible_host }}:{{ backend_port }}/schedule.csv"
return_content: true
delegate_to: controller
register: download
- name: Require the complete approved program
ansible.builtin.assert:
that: download.content == lookup('file', package_root ~ '/' ~ approved_release ~ '/schedule.csv', rstrip=False)
- name: Rejoin only after both business checks
ansible.builtin.uri:
url: "{{ pool_api }}/pool"
method: POST
body_format: json
body:
node: "{{ inventory_hostname }}"
enabled: true
delegate_to: controller
rescue:
- name: Restore the previous release link
ansible.builtin.file:
src: "{{ previous.stat.lnk_source }}"
dest: "{{ release_root }}/current"
state: link
- name: Read the restored homepage
ansible.builtin.uri:
url: "http://{{ hostvars[inventory_hostname].ansible_host }}:{{ backend_port }}/"
return_content: true
delegate_to: controller
register: restored_home
- name: Require the complete previous homepage
ansible.builtin.assert:
that: restored_home.content == lookup('file', package_root ~ '/' ~ (previous.stat.lnk_source | basename) ~ '/index.html', rstrip=False)
- name: Read the restored download
ansible.builtin.uri:
url: "http://{{ hostvars[inventory_hostname].ansible_host }}:{{ backend_port }}/schedule.csv"
return_content: true
delegate_to: controller
register: restored_download
- name: Require the complete previous program
ansible.builtin.assert:
that: restored_download.content == lookup('file', package_root ~ '/' ~ (previous.stat.lnk_source | basename) ~ '/schedule.csv', rstrip=False)
- name: Rejoin only after both restoration checks
ansible.builtin.uri:
url: "{{ pool_api }}/pool"
method: POST
body_format: json
body:
node: "{{ inventory_hostname }}"
enabled: true
delegate_to: controller
- name: Stop subsequent batches after recovery
ansible.builtin.fail:
msg: Candidate failed; the previous release is healthy again. Stop this rollout.
YAML
Valida la sintaxis antes de cambiar cualquiera de los servidores:
ansible-playbook rollout.yml --syntax-check
Intenta después publicar la versión incompleta. Se espera que este comando falle tras recuperar el sitio:
ansible-playbook rollout.yml
Lee inmediatamente el estado final. $? contiene el estado de salida del comando anterior y echo lo imprime. Otro comando sustituiría ese valor guardado por el shell:
echo $?
Espera un estado distinto de cero, normalmente 2. La comprobación de la página nueva tiene éxito; la descarga del candidato devuelve HTTP 404; la página y el CSV restaurados pasan; después Stop subsequent batches after recovery falla intencionadamente. HTTP 404 identifica la descarga ausente. La conexión SSH sigue disponible para recuperar. web2 no debe entrar en las tareas de publicación.
Vuelve a ejecutar la herramienta de observación:
ansible-playbook inspect.yml
Ambos servidores deben mostrar courtyard-preview y permanecer activos. Cambia a Website, recarga, entra en The program, descarga otra vez el CSV y comprueba que siguen disponibles las dos sesiones originales. Plan a visit debe seguir mostrando la información de accesibilidad.
El sitio restaurado sigue ofreciendo las dos sesiones originales y la descarga completa.

Ante un error habitual de YAML o nombre de archivo, usa ls, corrige el playbook completo y repite la comprobación sintáctica. Si una edición previa cambió el enlace antes de que funcionase la recuperación, guarda primero el playbook correcto anterior. Puedes usar el mismo despliegue saludable ya aprendido para restaurar el paquete original conocido, seleccionándolo tanto como versión como programa esperado. Las comillas mantienen estas dos asignaciones en un solo argumento --extra-vars:
ansible-playbook rollout.yml --extra-vars "release_id=courtyard-preview approved_release=courtyard-preview"
Ese comando de restauración debe terminar correctamente. Confirma ambos nodos con inspect.yml y repite después el candidato incompleto mediante el comando original ansible-playbook rollout.yml para demostrar el fallo y la recuperación exigidos. No restablezcas la VM ni reescribas el candidato. Mantén el playbook reutilizable: las solicitudes del grupo deben usar el pool_api suministrado, las rutas deben usar release_root y package_root, y las tareas deben emplear los módulos directos mostrados.
Resumen
Has recuperado el sitio anterior real tras un fallo auténtico de descarga ausente. Ambas comprobaciones de restauración controlan la reincorporación, y un fallo explícito detiene los siguientes lotes de Ansible aunque rescue termine correctamente.



