--- # ============================================================================= # proxmox_upgrade — drain.yml # Migrate all VMs/LXCs off current_node before upgrading # KVM: community.proxmox.proxmox_kvm (API, delegate_to: localhost) # LXC: pct migrate (SSH on source node) # ============================================================================= # ── Discover guests on this node ────────────────────────────────────────────── - name: "Drain | Get all guests on {{ current_node }}" community.proxmox.proxmox_vm_info: api_host: "{{ api_host }}" api_user: "{{ api_user }}" api_token_id: "{{ api_token_id }}" api_token_secret: "{{ api_token_secret }}" api_port: "{{ api_port }}" node: "{{ current_node }}" register: node_guests delegate_to: localhost - name: "Drain | Get available target nodes" community.proxmox.proxmox_node_info: api_host: "{{ api_host }}" api_user: "{{ api_user }}" api_token_id: "{{ api_token_id }}" api_token_secret: "{{ api_token_secret }}" api_port: "{{ api_port }}" register: all_nodes_info delegate_to: localhost - name: "Drain | Set migration target" ansible.builtin.set_fact: migration_target: >- {{ all_nodes_info.proxmox_nodes | selectattr('status', 'equalto', 'online') | rejectattr('node', 'equalto', current_node) | map(attribute='node') | list | first }} delegate_to: localhost - name: "Drain | Fail if no migration target available" ansible.builtin.fail: msg: "No online nodes available to migrate guests to. Cannot drain {{ current_node }}." when: migration_target is not defined or migration_target == '' delegate_to: localhost - name: "Drain | Log raw guest data (for debugging)" ansible.builtin.debug: msg: >- Guests on {{ current_node }}: vmids={{ node_guests.proxmox_vms | map(attribute='vmid') | list }} first_vm_keys={{ node_guests.proxmox_vms | first | list }} delegate_to: localhost - name: "Drain | Build KVM migration list" ansible.builtin.set_fact: kvm_guests: >- {{ node_guests.proxmox_vms | selectattr('type', 'equalto', 'qemu') | list }} delegate_to: localhost - name: "Drain | Build LXC migration list" ansible.builtin.set_fact: lxc_guests: >- {{ node_guests.proxmox_vms | selectattr('type', 'equalto', 'lxc') | list }} delegate_to: localhost - name: "Drain | Log migration plan" ansible.builtin.debug: msg: >- Drain plan for {{ current_node }} → {{ migration_target }}: KVM ({{ kvm_guests | length }}): {{ kvm_guests | map(attribute='vmid') | list }} LXC ({{ lxc_guests | length }}): {{ lxc_guests | map(attribute='vmid') | list }} delegate_to: localhost # ── KVM migrations ──────────────────────────────────────────────────────────── - name: "Drain | Migrate KVM guests" when: kvm_guests | length > 0 block: - name: "Drain | KVM | Live migrate (sequential)" community.proxmox.proxmox_kvm: api_host: "{{ api_host }}" api_user: "{{ api_user }}" api_token_id: "{{ api_token_id }}" api_token_secret: "{{ api_token_secret }}" api_port: "{{ api_port }}" node: "{{ current_node }}" vmid: "{{ item.vmid }}" migrate: true target_node: "{{ migration_target }}" online: "{{ true if item.status == 'running' else false }}" timeout: "{{ vm_shutdown_timeout }}" loop: "{{ kvm_guests }}" delegate_to: localhost when: not migration_bulk | bool - name: "Drain | KVM | Bulk migrate (fire all simultaneously)" community.proxmox.proxmox_kvm: api_host: "{{ api_host }}" api_user: "{{ api_user }}" api_token_id: "{{ api_token_id }}" api_token_secret: "{{ api_token_secret }}" api_port: "{{ api_port }}" node: "{{ current_node }}" vmid: "{{ item.vmid }}" migrate: true target_node: "{{ migration_target }}" online: "{{ true if item.status == 'running' else false }}" timeout: "{{ vm_shutdown_timeout }}" loop: "{{ kvm_guests }}" delegate_to: localhost async: "{{ vm_shutdown_timeout * 2 }}" poll: 0 register: kvm_bulk_jobs when: migration_bulk | bool - name: "Drain | KVM | Wait for bulk migrations to complete" ansible.builtin.async_status: jid: "{{ item.ansible_job_id }}" register: kvm_job_result until: kvm_job_result.finished retries: 60 delay: 10 loop: "{{ kvm_bulk_jobs.results }}" delegate_to: localhost when: migration_bulk | bool # ── LXC migrations ──────────────────────────────────────────────────────────── - name: "Drain | Migrate LXC guests" when: lxc_guests | length > 0 block: - name: "Drain | LXC | Warn about restart requirement" ansible.builtin.debug: msg: >- LXC {{ item.vmid }} ({{ item.name | default('unknown') }}) will be stopped, migrated to {{ migration_target }}, and restarted. (LXC live migration is not supported by Proxmox) loop: "{{ lxc_guests | selectattr('status', 'equalto', 'running') | list }}" delegate_to: localhost - name: "Drain | LXC | Skip warning" ansible.builtin.debug: msg: >- WARNING — LXC {{ item.vmid }} ({{ item.name | default('unknown') }}) live_migrate_fallback=skip — THIS CONTAINER WILL GO DOWN during node reboot. loop: "{{ lxc_guests | selectattr('status', 'equalto', 'running') | list }}" when: live_migrate_fallback == 'skip' delegate_to: localhost - name: "Drain | LXC | Migrate via pct migrate" ansible.builtin.command: > pct migrate {{ item.vmid }} {{ migration_target }} {% if item.status == 'running' %}--restart{% endif %} --timeout {{ lxc_migrate_timeout }} loop: "{{ lxc_guests }}" when: live_migrate_fallback != 'skip' changed_when: true - name: "Drain | LXC | Log migration results" ansible.builtin.debug: msg: "LXC {{ item.item.vmid }} migrated to {{ migration_target }}" loop: "{{ lxc_migrate_result.results | default([]) }}" when: item.rc is defined and item.rc == 0 - name: "Drain | {{ current_node }} drained successfully" ansible.builtin.debug: msg: >- Node {{ current_node }} drained — {{ kvm_guests | length }} KVM + {{ lxc_guests | length }} LXC guests migrated to {{ migration_target }}