Problema

En un homelab o en una pequeña infraestructura de producción, es frecuente mezclar máquinas virtuales (VMs), contenedores LXC y stacks Docker bajo un mismo host. Cada capa tiene su propio mecanismo de actualización: apt upgrade && apt update para sistemas Debian/Ubuntu, scripts de Proxmox para LXC, docker compose pull && docker compose up -d para contenedores, o descargas manuales de binarios desde GitHub. Cuando el número de unidades supera la decena, el proceso manual de conectar por SSH, ejecutar comandos, responder a prompts y reiniciar se vuelve una carga de varias horas cada semana. La necesidad es una solución que coordine esas tareas, preserve la interactividad requerida y sea reutilizable en cualquier entorno similar.

Causa

  1. Diversidad de orígenes – No todos los nodos usan el mismo gestor de paquetes. LXC gestionado por Proxmox depende de plantillas, mientras que Docker depende de imágenes y compose files.
  2. Falta de orquestador central – Cada VM/LXC se trata como una entidad aislada; no existe un inventario que permita ejecutar acciones en lote.
  3. Prompts interactivosdpkg puede preguntar si sobrescribir archivos de configuración, y los scripts de actualización pueden requerir confirmaciones. Sin una capa que capture y responda, la automatización se detiene.
  4. Reinicios y dependencias – Algunas actualizaciones requieren reboot; otros servicios deben reiniciarse en orden. La ausencia de lógica de post‑update genera fallos silenciosos.

Solución

Una estrategia basada en Ansible combinada con hooks de Proxmox API y Docker Compose cubre la mayoría de los casos. Ansible actúa como motor de orquestación: mantiene un inventario de VMs, LXC y hosts Docker, ejecuta módulos específicos y gestiona prompts mediante debconf o expect. Los pasos clave son:

  1. Inventario dinámico – Usa el módulo proxmox para extraer automáticamente la lista de VMs y contenedores desde el nodo Proxmox.
  2. Roles por tipo – Define roles vm_update, lxc_update y docker_update. Cada rol contiene tareas idempotentes que:
    • Actualizan paquetes con apt en modo no interactivo (DEBIAN_FRONTEND=noninteractive).
    • Ejecutan scripts personalizados para LXC (por ejemplo, pct exec <id> -- /usr/local/bin/update-helper.sh).
    • Actualizan stacks Docker con docker compose pull && docker compose up -d.
  3. Manejo de prompts – Configura debconf antes de la actualización o usa el módulo expect para capturar preguntas específicas.
  4. Reboots controlados – Después de aplicar actualizaciones críticas, registra la necesidad de reboot en una variable y, al final del playbook, ejecuta reboot solo en los nodos marcados.
  5. Post‑update hooks – Añade tareas que ejecuten comandos personalizados (por ejemplo, recargar iptables) usando command o shell.
  6. Programación – Programa el playbook con systemd timer o cron para que se ejecute semanalmente, enviando un reporte por correo o a un webhook de Discord.

Flujo de trabajo resumido

  1. Recopilación – Ansible consulta Proxmox y genera el inventario.
  2. Actualización – Cada host ejecuta su rol correspondiente.
  3. Validación – Se verifica que no haya paquetes retenidos (apt list --upgradable).
  4. Reboot – Si se detecta kernel o libc actualizado, se programa el reboot.
  5. Reporte – Se envía un resumen con estado de cada unidad.

Cuándo aplicar esta solución

  • Entornos mixtos con al menos una decena de VMs/LXC/Docker bajo un mismo control de red.
  • Política de parches semanal donde la ventana de mantenimiento es limitada.
  • Necesidad de auditoría: el playbook genera logs estructurados que facilitan la trazabilidad.
  • No aplica cuando todas las unidades usan exactamente el mismo gestor de paquetes y no requieren prompts; en ese caso un simple apt-get upgrade -y en cron puede ser suficiente.

Código

# inventory/proxmox.yml (dinámico)
plugin: community.general.proxmox
url: https://proxmox.example.com:8006/api2/json
user: root@pam
password: "{{ vault_proxmox_password }}"
validate_certs: false
# playbook/update_all.yml
- hosts: all
  become: true
  vars:
    reboot_required: false
  roles:
    - { role: vm_update, when: "'vm' in group_names" }
    - { role: lxc_update, when: "'lxc' in group_names" }
    - { role: docker_update, when: "'docker' in group_names" }

- hosts: all
  become: true
  tasks:
    - name: Reboot if needed
      reboot:
        reboot_timeout: 600
      when: reboot_required
# roles/vm_update/tasks/main.yml
- name: Set non‑interactive frontend
  ansible.builtin.environment:
    DEBIAN_FRONTEND: noninteractive

- name: Update apt cache
  apt:
    update_cache: yes
    cache_valid_time: 3600

- name: Perform safe upgrade
  apt:
    upgrade: safe
  register: apt_result

- name: Detect reboot requirement
  ansible.builtin.stat:
    path: /var/run/reboot-required
  register: reboot_file
  when: apt_result.changed

- name: Flag reboot
  set_fact:
    reboot_required: true
  when: reboot_file.stat.exists
# roles/docker_update/tasks/main.yml
- name: Pull latest images
  community.docker.docker_compose:
    project_src: /srv/docker/project
    pull: yes
    state: present

- name: Recreate containers
  community.docker.docker_compose:
    project_src: /srv/docker/project
    recreate: always
    state: present

Verificación

  1. Ejecuta ansible-playbook -i inventory/proxmox.yml playbook/update_all.yml --check para validar que todas las tareas se resuelven sin cambios inesperados.
  2. Revisa el log generado en /var/log/ansible/update_all.log; busca líneas con changed=true y failed=false.
  3. Confirma que los nodos que requerían reboot se reiniciaron y están accesibles (ping -c 3 <host>).
  4. En Docker hosts, verifica que docker ps muestra los contenedores en estado Up y que la versión de la imagen corresponde a la última disponible (docker images).

Notas adicionales

  • Bloqueos de apt: si un nodo tiene dpkg bloqueado, añade una tarea que elimine /var/lib/dpkg/lock-frontend solo después de confirmar que no hay procesos apt activos.
  • Orden de reinicio: en clústers con dependencias, usa el atributo serial en el playbook para reiniciar nodos uno a la vez y evitar caídas simultáneas.
  • Gestión de secretos: almacena contraseñas de Proxmox y tokens de Docker en Ansible Vault; nunca las incluyas en texto plano.
  • Extensibilidad: para casos especiales (por ejemplo, Cloudflared LXC que necesita recargar iptables), crea un handler en el rol lxc_update que se dispare al final de la actualización.
  • Monitoreo: integra el reporte con Prometheus Alertmanager o Grafana para visualizar el estado de cada unidad después del ciclo de actualización.