{"id":1398,"date":"2026-09-01T14:15:01","date_gmt":"2026-09-01T20:15:01","guid":{"rendered":"https:\/\/folken-it.com\/?p=1398"},"modified":"2026-09-01T14:15:03","modified_gmt":"2026-09-01T20:15:03","slug":"playbook-de-ansible-para-aplicar-patching-a-100-servidores-linux-por-lotes-sin-provocar-una-caida-total-del-servicio","status":"publish","type":"post","link":"https:\/\/folken-it.com\/?p=1398","title":{"rendered":"Playbook de Ansible para aplicar patching a 100 servidores Linux por lotes sin provocar una ca\u00edda total del servicio"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">En la entrada anterior vimos c\u00f3mo utilizar los principales m\u00f3dulos y comandos de <strong>Ansible<\/strong> para administrar un inventario de 100 servidores Linux.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pero ejecutar comandos sobre 100 servidores es solamente el comienzo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En ambientes productivos existe una pregunta mucho m\u00e1s importante:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u00bfC\u00f3mo podemos aplicar actualizaciones a 100 servidores sin detener todo el servicio?<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Actualizar simult\u00e1neamente todos los nodos de un cl\u00faster puede provocar una interrupci\u00f3n completa. Por ello, una estrategia m\u00e1s segura consiste en dividir el mantenimiento en <strong>lotes<\/strong>, validar cada grupo antes y despu\u00e9s del cambio y detener autom\u00e1ticamente el procedimiento cuando algo sale mal.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En este ejemplo construiremos un Playbook utilizando:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>serial\npre-checks\nhealth checks\nhandlers\npatching\nreboot\nrollback\npost-checks\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La idea ser\u00e1 implementar el siguiente flujo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>100 SERVIDORES\n      \u2502\n      \u25bc\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502  PRE-CHECKS   \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n        \u2502\n        \u25bc\n   LOTE DE 10\n        \u2502\n        \u25bc\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502   PATCHING    \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n        \u2502\n        \u25bc\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502    REBOOT     \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n        \u2502\n        \u25bc\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 HEALTH CHECK  \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n        \u2502\n   OK \u2500\u2500\u2534\u2500\u2500 ERROR\n   \u2502          \u2502\n   \u25bc          \u25bc\nSIGUIENTE    ROLLBACK\n  LOTE\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">1. Nuestro inventario de 100 servidores<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Supongamos que tenemos 100 application servers:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;appservers]\napp001\napp002\napp003\n...\napp098\napp099\napp100\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Podemos comprobar el inventario antes de iniciar:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ansible-inventory -i inventory.ini --graph\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Y validar conectividad:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ansible appservers \\\n-i inventory.ini \\\n-m ansible.builtin.ping\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nunca deber\u00edamos comenzar una ventana de mantenimiento sin comprobar primero qu\u00e9 hosts ser\u00e1n afectados.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">2. El problema de actualizar 100 servidores simult\u00e1neamente<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Podr\u00edamos crear un Playbook con:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>hosts: appservers\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Pero si los 100 servidores forman parte de una misma plataforma, realizar operaciones disruptivas simult\u00e1neamente puede provocar una ca\u00edda completa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nuestro objetivo ser\u00e1 mantener siempre servidores disponibles mientras otros reciben mantenimiento.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Por ejemplo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Hosts totales:       100\nTama\u00f1o del lote:      10\nLotes necesarios:     10\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Mientras actualizamos:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app001 \u2192 app010    PATCHING\n\napp011 \u2192 app100    DISPONIBLES\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Despu\u00e9s continuamos:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app001 \u2192 app010    OK\n\napp011 \u2192 app020    PATCHING\n\napp021 \u2192 app100    DISPONIBLES\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Y repetimos hasta completar los 100 servidores.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">3. <code>serial<\/code>: la clave para controlar los lotes<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Ansible permite controlar cu\u00e1ntos hosts procesa simult\u00e1neamente mediante:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>serial: 10\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nuestra estructura inicial ser\u00eda:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>---\n- name: Rolling patching Linux servers\n  hosts: appservers\n  become: true\n  serial: 10\n\n  tasks:\n\n    - name: Check connectivity\n      ansible.builtin.ping:\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Aunque nuestro inventario tenga:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>100 hosts\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ansible procesar\u00e1 el Play aproximadamente como:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Batch 01 \u2192 10 hosts\nBatch 02 \u2192 10 hosts\nBatch 03 \u2192 10 hosts\n...\nBatch 10 \u2192 10 hosts\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Esto reduce considerablemente el riesgo frente a actualizar todos los nodos simult\u00e1neamente.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">4. PRE-CHECKS antes del mantenimiento<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Antes de modificar un servidor debemos verificar que se encuentra en condiciones adecuadas.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Algunos checks habituales son:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2713 Conectividad\n\u2713 Filesystem\n\u2713 Memoria\n\u2713 Servicio activo\n\u2713 Aplicaci\u00f3n respondiendo\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Podemos empezar comprobando espacio:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Check filesystem\n  ansible.builtin.command:\n    cmd: df -P \/\n  register: filesystem_status\n  changed_when: false\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Tambi\u00e9n podemos verificar nuestro servicio:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Check application service\n  ansible.builtin.command:\n    cmd: systemctl is-active myapp\n  register: app_status\n  changed_when: false\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Y detener el mantenimiento de ese host si la aplicaci\u00f3n no estaba funcionando antes del cambio:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Validate application status\n  ansible.builtin.assert:\n    that:\n      - app_status.stdout == \"active\"\n    fail_msg: \"Application was not healthy before patching.\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Esto es importante porque evita atribuir al patching un problema que ya exist\u00eda antes de comenzar.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">5. Health check HTTP antes del patching<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Si nuestra aplicaci\u00f3n proporciona un endpoint de health check, podemos comprobarlo directamente.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Por ejemplo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>http:&#47;&#47;localhost:8080\/health\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Con Ansible:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Pre-patching application health check\n  ansible.builtin.uri:\n    url: http:\/\/localhost:8080\/health\n    method: GET\n    status_code: 200\n  register: pre_health\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Si el endpoint no devuelve:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>HTTP 200\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">el servidor no deber\u00eda continuar autom\u00e1ticamente con el mantenimiento.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">6. Crear informaci\u00f3n para rollback<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Antes de modificar paquetes conviene registrar su estado.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Por ejemplo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Get installed package information\n  ansible.builtin.package_facts:\n    manager: auto\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Tambi\u00e9n podemos respaldar configuraciones cr\u00edticas.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Backup application configuration\n  ansible.builtin.copy:\n    src: \/etc\/myapp\/myapp.conf\n    dest: \/etc\/myapp\/myapp.conf.prepatch\n    remote_src: true\n    backup: true\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Dependiendo de la aplicaci\u00f3n, el rollback puede requerir mucho m\u00e1s:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Package downgrade\nSnapshot de VM\nBackup\nRestauraci\u00f3n de configuraci\u00f3n\nRollback de aplicaci\u00f3n\nRollback de base de datos\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Por eso el rollback real debe dise\u00f1arse espec\u00edficamente para cada plataforma.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">7. Aplicando los patches<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Para sistemas Red Hat\/Oracle Linux podemos utilizar el m\u00f3dulo DNF.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Apply operating system updates\n  ansible.builtin.dnf:\n    name: \"*\"\n    state: latest\n  notify:\n    - Restart application\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Para una estrategia m\u00e1s conservadora tambi\u00e9n podemos actualizar solamente determinados paquetes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Por ejemplo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Update OpenSSL\n  ansible.builtin.dnf:\n    name: openssl\n    state: latest\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Esto permite reducir el alcance del cambio.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">8. Handlers<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Los handlers son \u00fatiles para ejecutar acciones solamente cuando una tarea genera un cambio.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Por ejemplo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>handlers:\n\n  - name: Restart application\n    ansible.builtin.service:\n      name: myapp\n      state: restarted\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La tarea de actualizaci\u00f3n puede utilizar:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>notify:\n  - Restart application\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">De esta manera el restart solamente ser\u00e1 solicitado cuando exista un cambio relevante.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">9. Reiniciar el servidor<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Algunos patches requieren reiniciar el sistema operativo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ansible proporciona:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ansible.builtin.reboot\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Podemos utilizar:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Reboot server after patching\n  ansible.builtin.reboot:\n    reboot_timeout: 900\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ansible esperar\u00e1 a que el servidor vuelva a estar disponible.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Podemos comprobar nuevamente la conectividad:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Verify server connectivity\n  ansible.builtin.ping:\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">10. Esperar a que la aplicaci\u00f3n vuelva<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Que Linux responda por SSH no significa necesariamente que la aplicaci\u00f3n est\u00e9 lista.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Podemos esperar a que el puerto est\u00e9 disponible:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Wait for application port\n  ansible.builtin.wait_for:\n    host: \"{{ inventory_hostname }}\"\n    port: 8080\n    delay: 5\n    timeout: 120\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">O ejecutar directamente un health check:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Application health check\n  ansible.builtin.uri:\n    url: http:\/\/localhost:8080\/health\n    method: GET\n    status_code: 200\n  register: post_health\n  retries: 12\n  delay: 10\n  until: post_health.status == 200\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Aqu\u00ed estamos dando a la aplicaci\u00f3n hasta aproximadamente:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>12 \u00d7 10 segundos = 120 segundos\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">para responder correctamente.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">11. Rollback autom\u00e1tico<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Aqu\u00ed comienza una de las partes m\u00e1s interesantes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Podemos utilizar:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>block:\nrescue:\nalways:\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La idea es:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>BLOCK\n  \u2502\n  \u251c\u2500\u2500 Backup\n  \u251c\u2500\u2500 Patching\n  \u251c\u2500\u2500 Reboot\n  \u2514\u2500\u2500 Health Check\n          \u2502\n          \u251c\u2500\u2500 SUCCESS \u2192 continuar\n          \u2502\n          \u2514\u2500\u2500 FAILED\n                 \u2502\n                 \u25bc\n              RESCUE\n                 \u2502\n                 \u25bc\n              ROLLBACK\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Por ejemplo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Patching operation\n  block:\n\n    - name: Apply updates\n      ansible.builtin.dnf:\n        name: \"*\"\n        state: latest\n\n    - name: Reboot server\n      ansible.builtin.reboot:\n        reboot_timeout: 900\n\n    - name: Validate application\n      ansible.builtin.uri:\n        url: http:\/\/localhost:8080\/health\n        status_code: 200\n\n  rescue:\n\n    - name: Restore application configuration\n      ansible.builtin.copy:\n        src: \/etc\/myapp\/myapp.conf.prepatch\n        dest: \/etc\/myapp\/myapp.conf\n        remote_src: true\n\n    - name: Restart application after rollback\n      ansible.builtin.service:\n        name: myapp\n        state: restarted\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Importante:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Restaurar un archivo de configuraci\u00f3n no equivale necesariamente a revertir un patch del sistema operativo.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En producci\u00f3n, el rollback podr\u00eda depender de snapshots, repositorios versionados, downgrade de paquetes o mecanismos propios de nuestra plataforma.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">12. Detener el procedimiento cuando existen demasiados errores<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">No queremos que Ansible contin\u00fae actualizando servidores si comenzamos a detectar fallos.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Podemos utilizar:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>max_fail_percentage: 10\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Por ejemplo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Rolling patching\n  hosts: appservers\n\n  serial: 10\n  max_fail_percentage: 10\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Esto introduce otro mecanismo de protecci\u00f3n para evitar que una ejecuci\u00f3n problem\u00e1tica contin\u00fae propag\u00e1ndose por toda la infraestructura.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para cambios cr\u00edticos incluso podemos adoptar una pol\u00edtica m\u00e1s estricta.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">13. POST-CHECKS<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Despu\u00e9s del mantenimiento debemos comprobar nuevamente el servidor.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Algunos post-checks habituales:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u2713 SSH funcionando\n\u2713 Filesystem correcto\n\u2713 Memoria disponible\n\u2713 Servicios activos\n\u2713 Puertos escuchando\n\u2713 Health endpoint = HTTP 200\n\u2713 Aplicaci\u00f3n respondiendo\n\u2713 Logs sin errores cr\u00edticos\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Podemos comprobar el servicio:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Verify application service\n  ansible.builtin.command:\n    cmd: systemctl is-active myapp\n  register: final_service\n  changed_when: false\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Validarlo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Validate service\n  ansible.builtin.assert:\n    that:\n      - final_service.stdout == \"active\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Y realizar el health check final:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Final application health check\n  ansible.builtin.uri:\n    url: http:\/\/localhost:8080\/health\n    status_code: 200\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">14. Playbook completo<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Uniendo los conceptos anteriores podemos crear una base como \u00e9sta:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>---\n- name: Rolling patching for production Linux servers\n  hosts: appservers\n  become: true\n\n  serial: 10\n  max_fail_percentage: 10\n\n  vars:\n    application_service: myapp\n    health_url: http:\/\/localhost:8080\/health\n\n  pre_tasks:\n\n    - name: Verify connectivity\n      ansible.builtin.ping:\n\n    - name: Verify application service before maintenance\n      ansible.builtin.command:\n        cmd: \"systemctl is-active {{ application_service }}\"\n      register: pre_service\n      changed_when: false\n\n    - name: Validate application before maintenance\n      ansible.builtin.assert:\n        that:\n          - pre_service.stdout == \"active\"\n\n    - name: Pre-maintenance health check\n      ansible.builtin.uri:\n        url: \"{{ health_url }}\"\n        status_code: 200\n\n  tasks:\n\n    - name: Execute patching operation\n      block:\n\n        - name: Backup application configuration\n          ansible.builtin.copy:\n            src: \/etc\/myapp\/myapp.conf\n            dest: \/etc\/myapp\/myapp.conf.prepatch\n            remote_src: true\n\n        - name: Apply operating system updates\n          ansible.builtin.dnf:\n            name: \"*\"\n            state: latest\n          notify:\n            - Restart application\n\n        - name: Reboot server\n          ansible.builtin.reboot:\n            reboot_timeout: 900\n\n        - name: Wait for application port\n          ansible.builtin.wait_for:\n            port: 8080\n            delay: 5\n            timeout: 120\n\n        - name: Validate application after patching\n          ansible.builtin.uri:\n            url: \"{{ health_url }}\"\n            status_code: 200\n          register: health_result\n          retries: 12\n          delay: 10\n          until: health_result.status == 200\n\n      rescue:\n\n        - name: Report patching failure\n          ansible.builtin.debug:\n            msg: \"Patching failed on {{ inventory_hostname }}. Starting rollback.\"\n\n        - name: Restore configuration\n          ansible.builtin.copy:\n            src: \/etc\/myapp\/myapp.conf.prepatch\n            dest: \/etc\/myapp\/myapp.conf\n            remote_src: true\n\n        - name: Restart application\n          ansible.builtin.service:\n            name: \"{{ application_service }}\"\n            state: restarted\n\n      always:\n\n        - name: Report processed host\n          ansible.builtin.debug:\n            msg: \"Maintenance processing completed for {{ inventory_hostname }}\"\n\n  post_tasks:\n\n    - name: Verify final service status\n      ansible.builtin.command:\n        cmd: \"systemctl is-active {{ application_service }}\"\n      register: final_service\n      changed_when: false\n\n    - name: Validate final service\n      ansible.builtin.assert:\n        that:\n          - final_service.stdout == \"active\"\n\n    - name: Final application health check\n      ansible.builtin.uri:\n        url: \"{{ health_url }}\"\n        status_code: 200\n\n  handlers:\n\n    - name: Restart application\n      ansible.builtin.service:\n        name: \"{{ application_service }}\"\n        state: restarted\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Este ejemplo es una <strong>base de dise\u00f1o<\/strong>. Antes de utilizarlo en producci\u00f3n debemos adaptarlo al sistema operativo, aplicaci\u00f3n, arquitectura, balanceadores, cl\u00fasteres y procedimiento real de rollback.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">15. Validar antes de ejecutar<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Antes de una ventana de mantenimiento podemos revisar la sintaxis:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ansible-playbook \\\n-i inventory.ini \\\npatching.yml \\\n--syntax-check\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Comprobar los hosts:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ansible-playbook \\\n-i inventory.ini \\\npatching.yml \\\n--list-hosts\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Listar las tareas:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ansible-playbook \\\n-i inventory.ini \\\npatching.yml \\\n--list-tasks\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Y cuando las tareas utilizadas soporten Check Mode:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ansible-playbook \\\n-i inventory.ini \\\npatching.yml \\\n--check\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">16. Primera ejecuci\u00f3n: no empieces con 100 servidores<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Aunque nuestro objetivo sean 100 hosts, una buena estrategia consiste en comenzar con un subconjunto controlado.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Por ejemplo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ansible-playbook \\\n-i inventory.ini \\\npatching.yml \\\n--limit app001\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Despu\u00e9s podr\u00edamos utilizar un grupo piloto:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;canary]\napp001\napp002\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Y ejecutar:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ansible-playbook \\\n-i inventory.ini \\\npatching.yml \\\n--limit canary\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Una vez comprobado el resultado podemos continuar con el resto.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">17. Una arquitectura todav\u00eda mejor: Load Balancer + Ansible<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">En sistemas realmente cr\u00edticos, <code>serial<\/code> es solamente una parte de la soluci\u00f3n.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Una estrategia m\u00e1s robusta ser\u00eda:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"> <code>                 LOAD BALANCER\n                       \u2502\n         \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n         \u2502                           \u2502\n   SERVIDORES                  SERVIDORES\n   DISPONIBLES                 EN PATCHING\n         \u2502                           \u2502\n         \u2502                      1. Drain node\n         \u2502                      2. Stop app\n         \u2502                      3. Patch\n         \u2502                      4. Reboot\n         \u2502                      5. Start app\n         \u2502                      6. Health check\n         \u2502                      7. Enable node\n         \u2502                           \u2502\n         \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25c4\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">El procedimiento ser\u00eda:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>1. Seleccionar lote\n        \u2193\n2. Sacar nodos del Load Balancer\n        \u2193\n3. Esperar a que terminen conexiones\n        \u2193\n4. Ejecutar pre-checks\n        \u2193\n5. Aplicar patches\n        \u2193\n6. Reiniciar\n        \u2193\n7. Ejecutar health checks\n        \u2193\n8. Regresar nodos al Load Balancer\n        \u2193\n9. Ejecutar post-checks\n        \u2193\n10. Continuar con siguiente lote\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Este modelo resulta mucho m\u00e1s apropiado para aplicaciones que deben mantenerse disponibles durante toda la ventana de mantenimiento.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">18. De 100 servidores a una estrategia de Rolling Maintenance<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">El concepto importante de este ejercicio no es solamente aprender:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>serial: 10\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Es cambiar nuestra forma de pensar sobre las actividades de mantenimiento.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En lugar de:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PATCH 100 SERVERS\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">podemos dise\u00f1ar:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>DISCOVER\n   \u2193\nPRE-CHECK\n   \u2193\nDRAIN\n   \u2193\nPATCH\n   \u2193\nREBOOT\n   \u2193\nHEALTH CHECK\n   \u2193\nRESTORE SERVICE\n   \u2193\nPOST-CHECK\n   \u2193\nNEXT BATCH\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">De esta forma Ansible no solamente ejecuta comandos.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ansible orquesta todo el procedimiento operacional.<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Conclusi\u00f3n<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Administrar 100 servidores Linux no significa que debamos modificar los 100 simult\u00e1neamente.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Utilizando:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>serial\npre_tasks\nhealth checks\nhandlers\nblock\/rescue\/always\nrollback\npost_tasks\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">podemos construir procedimientos de mantenimiento mucho m\u00e1s controlados.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>serial<\/code> nos permite dividir nuestra infraestructura en lotes; los pre-checks nos ayudan a comprobar que partimos de un estado saludable; los health checks verifican que la aplicaci\u00f3n realmente regres\u00f3 al servicio; los handlers controlan acciones derivadas de cambios; <code>rescue<\/code> proporciona una ruta para manejar errores y los post-checks confirman el estado final.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pero existe un principio todav\u00eda m\u00e1s importante:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Automatizar una mala estrategia no la convierte en una buena estrategia.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Antes de automatizar un procedimiento de patching debemos comprender las dependencias de la aplicaci\u00f3n, cl\u00faster, balanceadores, bases de datos, mecanismos de failover y procedimiento real de rollback.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Cuando combinamos ese conocimiento operacional con Ansible podemos pasar de simplemente ejecutar comandos sobre servidores a construir verdaderas estrategias de <strong>Rolling Maintenance automatizado para infraestructura de producci\u00f3n<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>En la entrada anterior vimos c\u00f3mo utilizar los principales m\u00f3dulos y comandos de Ansible para administrar un inventario de 100 servidores Linux. [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":1399,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"advanced_seo_description":"Playbook de Ansible para aplicar patching a 100 servidores Linux por lotes sin provocar una ca\u00edda total del servicio","jetpack_seo_html_title":"Playbook de Ansible para aplicar patching a 100 servidores Linux por lotes sin provocar una ca\u00edda total del servicio","jetpack_seo_noindex":false,"jetpack_seo_schema_type":"","_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[62,63],"tags":[],"class_list":["post-1398","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-automatizacion","category-configuracion"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"https:\/\/i0.wp.com\/folken-it.com\/wp-content\/uploads\/2026\/09\/rolling_patching.png?fit=1536%2C1024&ssl=1","_links":{"self":[{"href":"https:\/\/folken-it.com\/index.php?rest_route=\/wp\/v2\/posts\/1398","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/folken-it.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/folken-it.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/folken-it.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/folken-it.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1398"}],"version-history":[{"count":1,"href":"https:\/\/folken-it.com\/index.php?rest_route=\/wp\/v2\/posts\/1398\/revisions"}],"predecessor-version":[{"id":1400,"href":"https:\/\/folken-it.com\/index.php?rest_route=\/wp\/v2\/posts\/1398\/revisions\/1400"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/folken-it.com\/index.php?rest_route=\/wp\/v2\/media\/1399"}],"wp:attachment":[{"href":"https:\/\/folken-it.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1398"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/folken-it.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1398"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/folken-it.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1398"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}