From c0e0bc169e7d84880d94eba86ae3d1e416f79a78 Mon Sep 17 00:00:00 2001 From: "pulsar89.5" Date: Tue, 17 Mar 2026 02:07:45 +0100 Subject: [PATCH] refacto: Partial rewrite to manage nodes --- defaults/main.yml | 50 +++++--- handlers/main.yml | 41 +----- tasks/get_images.yml | 24 ---- tasks/ha.yml | 41 ------ ...tion.yml => instance_vm_configuration.yml} | 4 +- tasks/instance_vm_ha.yml | 44 +++++++ ..._template.yml => instance_vm_template.yml} | 0 tasks/main.yml | 37 +++--- tasks/node_configuration.yml | 120 ++++++++++++++++++ tasks/node_update.yml | 113 +++++++++++++++++ tasks/update_pve.yml | 73 ----------- 11 files changed, 330 insertions(+), 217 deletions(-) delete mode 100644 tasks/get_images.yml delete mode 100644 tasks/ha.yml rename tasks/{vm_configuration.yml => instance_vm_configuration.yml} (98%) create mode 100644 tasks/instance_vm_ha.yml rename tasks/{vm_template.yml => instance_vm_template.yml} (100%) create mode 100644 tasks/node_configuration.yml create mode 100644 tasks/node_update.yml delete mode 100644 tasks/update_pve.yml diff --git a/defaults/main.yml b/defaults/main.yml index 8866ff0..e631ec5 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,25 +1,39 @@ --- # vars file for proxmox +# Informations to connect to API +proxmox_api_host: "" +proxmox_api_user: "" +proxmox_api_token_id: "" +proxmox_api_token_secret: "" +proxmox_api_validate_certs: false + +# Proxmox VE node +## Node +proxmox_node_interfaces: [] + +## Cluster +proxmox_cluster_name: "" +# proxmox_cluster_link0: "" +# proxmox_cluster_link1: "" + +## Storage +proxmox_storage: [] + # Images list to download proxmox_images: [] # Example: # - url: "https://cloud.debian.org/images/cloud/bookworm-backports/latest/debian-12-backports-genericcloud-amd64.qcow2" # dest: /mnt/pve/pumbaa/disk_image/bookworm.qcow2 -# Delegate tasks to Proxmox VE host (local API call !) +# Proxmox VE instance +## Delegate tasks to Proxmox VE host (local API call !) proxmox_delegate_to: "" -# Informations to connect to API -proxmox_api_host: "" -proxmox_api_user: "" -proxmox_api_token_id: "" -proxmox_api_token_secret: "" - -# Type of instance (ct or vm) +## Type of instance (ct or vm) proxmox_instance_type: "" -# Instance configuration +## Instance configuration proxmox_instance_node: "" proxmox_instance_autostart: true proxmox_instance_args: "" @@ -60,23 +74,21 @@ proxmox_instance_cloudinit_nameservers: "" proxmox_instance_cloudinit_searchdomains: null proxmox_instance_cloudinit_sshkeys: "" -# HA configuration +## HA configuration proxmox_instance_ha: -# Example: -# group: pve1 -# max_restart: 3 -# max_relocate: 2 -# state: started + max_restart: 3 + max_relocate: 2 + state: started -# Configuration to wait SSH +## Configuration to wait SSH proxmox_instance_ssh_ip: "" proxmox_instance_ssh_port: "" -# Start instance after installation +## Start instance after installation proxmox_start_instance: true -# Reboot instance when changed +## Reboot instance when changed proxmox_reboot_instance: true -# Start qemu-guest-agent.service at the end +## Start qemu-guest-agent.service at the end proxmox_start_agent: true diff --git a/handlers/main.yml b/handlers/main.yml index 7790106..c73c72b 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -49,46 +49,13 @@ delegate_to: "{{ proxmox_delegate_to | default(omit) }}" - name: Wait cloud-init - ansible.builtin.command: # noqa: no-changed-when + ansible.builtin.command: cmd: cloud-init status when: proxmox_instance_cloudinit register: cloudinit_status - until: cloudinit_status.stdout.find("done") != -1 + changed_when: cloudinit_status.stdout_lines | select("search", "done") | list | length > 0 + failed_when: cloudinit_status.rc == 1 + until: cloudinit_status.stdout_lines | select("search", "done") | list | length > 0 retries: 100 delay: 36 ignore_errors: true - -- name: Enable maintenance mode - ansible.builtin.command: # noqa: no-changed-when - argv: - - ha-manager - - crm-command - - node-maintenance - - enable - - "{{ inventory_hostname_short }}" - become: true - listen: Upgrade the node - -- name: Wait instances migration - ansible.builtin.command: # noqa: no-changed-when - argv: - - ha-manager - - status - become: true - register: ha_manager_status - retries: 10 - delay: 60 - until: not ha_manager_status.stdout_lines | regex_search('migrate', ignorecase=true) - listen: Upgrade the node - -- name: Run the full-upgrade - ansible.builtin.apt: - upgrade: dist - update_cache: true - become: true - listen: Upgrade the node - -- name: Reboot - ansible.builtin.reboot: - become: true - listen: Upgrade the node diff --git a/tasks/get_images.yml b/tasks/get_images.yml deleted file mode 100644 index d40d454..0000000 --- a/tasks/get_images.yml +++ /dev/null @@ -1,24 +0,0 @@ ---- -# tasks file for proxmox - -- name: Create images storage directory - ansible.builtin.file: - path: "{{ item.dest | ansible.builtin.dirname }}" - state: directory - mode: u=rwX,g=rX,o=rX - become: true - delegate_to: "{{ proxmox_delegate_to | default(omit) }}" - loop: "{{ proxmox_images }}" - loop_control: - label: "{{ item.dest | ansible.builtin.dirname }}" - -- name: Download images - ansible.builtin.get_url: - url: "{{ item.url }}" - dest: "{{ item.dest }}" - mode: u=rw,g=r,o=r - become: true - delegate_to: "{{ proxmox_delegate_to | default(omit) }}" - loop: "{{ proxmox_images }}" - loop_control: - label: "{{ item.dest | ansible.builtin.basename }}" diff --git a/tasks/ha.yml b/tasks/ha.yml deleted file mode 100644 index 9792610..0000000 --- a/tasks/ha.yml +++ /dev/null @@ -1,41 +0,0 @@ ---- -# tasks file for proxmox - -- name: Add Proxmox VE host as HA group - ansible.builtin.blockinfile: - path: /etc/pve/ha/groups.cfg - append_newline: true - prepend_newline: true - block: | - group: {{ proxmox_instance_node }} - nodes {{ proxmox_instance_node }} - nofailback 0 - restricted 0 - marker: "# {mark} {{ proxmox_instance_node }}" - become: true - delegate_to: "{{ proxmox_delegate_to }}" - -- name: Add VM to HA group - community.proxmox.proxmox_cluster_ha_resources: - api_host: "{{ proxmox_api_host }}" - api_token_id: "{{ proxmox_api_token_id }}" - api_token_secret: "{{ proxmox_api_token_secret }}" - api_user: "{{ proxmox_api_user }}" - group: "{{ proxmox_instance_ha.group }}" - hastate: "{{ proxmox_instance_ha.state | default('started') }}" - max_relocate: "{{ proxmox_instance_ha.max_relocate | default(2) }}" - max_restart: "{{ proxmox_instance_ha.max_restart | default(3) }}" - name: "{{ inventory_hostname }}" - state: present - delegate_to: "{{ proxmox_delegate_to | default(omit) }}" - -- name: Add ansible managed header - ansible.builtin.lineinfile: - path: "{{ item }}" - line: "# Ansible managed" - insertafter: BOF - become: true - loop: - - /etc/pve/ha/groups.cfg - - /etc/pve/ha/resources.cfg - delegate_to: "{{ proxmox_delegate_to }}" diff --git a/tasks/vm_configuration.yml b/tasks/instance_vm_configuration.yml similarity index 98% rename from tasks/vm_configuration.yml rename to tasks/instance_vm_configuration.yml index b8c8a5d..0aab83e 100644 --- a/tasks/vm_configuration.yml +++ b/tasks/instance_vm_configuration.yml @@ -127,7 +127,7 @@ - not create_instance.changed - instance_current_infos.proxmox_vms[0].config != instance_pending_infos.proxmox_vms[0].config delegate_to: "{{ proxmox_delegate_to | default(omit) }}" - notify: Attendre que le port SSH soit ouvert + notify: Wait SSH port is open register: rebooted - name: Flush handlers @@ -143,7 +143,7 @@ node: "{{ proxmox_instance_node }}" state: started delegate_to: "{{ proxmox_delegate_to | default(omit) }}" - notify: Attendre que le port SSH soit ouvert + notify: Wait SSH port is open - name: Flush handlers ansible.builtin.meta: flush_handlers diff --git a/tasks/instance_vm_ha.yml b/tasks/instance_vm_ha.yml new file mode 100644 index 0000000..648f580 --- /dev/null +++ b/tasks/instance_vm_ha.yml @@ -0,0 +1,44 @@ +--- +# tasks file for proxmox + +- name: Build HA resources (vm:) for node + set_fact: + ha_resources_for_node: >- + {{ ['vm:'] | product( + query('inventory_hostnames', 'type_proxmox_kvm:&zone_gaia') | + sort | + map('extract', hostvars) | + selectattr('proxmox_instance_node', 'equalto', proxmox_instance_node) | + map(attribute='proxmox_instance_vmid') + ) | map('join') | list }} + +- name: Ensure HA services exist for VMs + community.proxmox.proxmox_cluster_ha_resources: + api_host: "{{ proxmox_api_host }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + api_user: "{{ proxmox_api_user }}" + comment: "Managed by Ansible" + hastate: "{{ proxmox_instance_ha.state }}" + max_relocate: "{{ proxmox_instance_ha.max_relocate }}" + max_restart: "{{ proxmox_instance_ha.max_restart }}" + name: "vm:{{ proxmox_instance_vmid }}" + state: present + become: true + delegate_to: "{{ proxmox_delegate_to }}" + +- name: Create HA node affinity rule + community.proxmox.proxmox_cluster_ha_rules: + api_host: "{{ proxmox_api_host }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + api_user: "{{ proxmox_api_user }}" + comment: Managed by ansible + disable: false + name: "{{ proxmox_instance_node }}" + nodes: ["{{ proxmox_instance_node }}"] + resources: "{{ ha_resources_for_node }}" + state: present + type: node-affinity + become: true + delegate_to: "{{ proxmox_delegate_to }}" diff --git a/tasks/vm_template.yml b/tasks/instance_vm_template.yml similarity index 100% rename from tasks/vm_template.yml rename to tasks/instance_vm_template.yml diff --git a/tasks/main.yml b/tasks/main.yml index 501d28b..4734590 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,31 +1,26 @@ --- # tasks file for proxmox -- name: Install prerequisite - ansible.builtin.apt: - name: python3-proxmoxer - update_cache: true - become: true - run_once: true - delegate_to: "{{ proxmox_delegate_to | default(omit) }}" - -- name: Import image download tasks - ansible.builtin.import_tasks: - file: get_images.yml - when: proxmox_instance_type == "vm" +# Node +- name: Import Proxmox VE configuration tasks + ansible.builtin.include_tasks: + file: node_configuration.yml + when: proxmox_instance_type | length == 0 +# Instance: vm - name: Import instance creation tasks - ansible.builtin.import_tasks: - file: vm_template.yml + ansible.builtin.include_tasks: + file: instance_vm_template.yml when: proxmox_instance_type == "vm" - name: Import instance configuration tasks - ansible.builtin.import_tasks: - file: vm_configuration.yml + ansible.builtin.include_tasks: + file: instance_vm_configuration.yml when: proxmox_instance_type == "vm" -# Disabled until the community.proxmox module is compatible with Proxmox VE 9 -# - name: Import HA configuration tasks -# ansible.builtin.import_tasks: -# file: ha.yml -# when: proxmox_instance_ha | length > 0 +- name: Import HA configuration tasks + ansible.builtin.include_tasks: + file: instance_vm_ha.yml + when: + - proxmox_instance_type == "vm" + - proxmox_instance_ha | length > 0 diff --git a/tasks/node_configuration.yml b/tasks/node_configuration.yml new file mode 100644 index 0000000..10c1a7a --- /dev/null +++ b/tasks/node_configuration.yml @@ -0,0 +1,120 @@ +--- +# tasks file for proxmox + +- name: Install prerequisite + ansible.builtin.apt: + name: python3-proxmoxer + update_cache: true + become: true + +- name: Manage Proxmox VE node bridge interface + community.proxmox.proxmox_node_network: + api_host: "{{ proxmox_api_host }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + api_user: "{{ proxmox_api_user }}" + autostart: "{{ item.autostart | default(true) }}" + cidr: "{{ item.cidr | default(omit) }}" + cidr6: "{{ item.cidr6 | default(omit) }}" + comments: "{{ item.comments | default(omit) }}" + gateway: "{{ item.gateway | default(omit) }}" + gateway6: "{{ item.gateway6 | default(omit) }}" + iface_type: bridge + iface: "{{ item.iface }}" + mtu: "{{ item.mtu | default(omit) }}" + node: "{{ inventory_hostname_short }}" + state: "{{ item.states | default('present') }}" + validate_certs: "{{ proxmox_api_validate_certs }}" + become: true + loop: "{{ proxmox_node_interfaces | selectattr('type', 'equalto', 'bridge') }}" + loop_control: + label: "{{ item.iface }}" + +- name: Manage Proxmox VE node eth interface + community.proxmox.proxmox_node_network: + api_host: "{{ proxmox_api_host }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + api_user: "{{ proxmox_api_user }}" + autostart: "{{ item.autostart | default(true) }}" + cidr: "{{ item.cidr | default(omit) }}" + cidr6: "{{ item.cidr6 | default(omit) }}" + comments: "{{ item.comments | default(omit) }}" + gateway: "{{ item.gateway | default(omit) }}" + gateway6: "{{ item.gateway6 | default(omit) }}" + iface_type: eth + iface: "{{ item.iface }}" + mtu: "{{ item.mtu | default(omit) }}" + node: "{{ inventory_hostname_short }}" + state: "{{ item.states | default('present') }}" + validate_certs: "{{ proxmox_api_validate_certs }}" + become: true + loop: "{{ proxmox_node_interfaces | selectattr('type', 'equalto', 'eth') }}" + loop_control: + label: "{{ item.iface }}" + +- name: Proxmox VE cluster management tasks + when: proxmox_cluster_name | length > 0 + block: + - name: List existing Proxmox VE cluster join information + community.proxmox.proxmox_cluster_join_info: + api_host: "{{ proxmox_api_host }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + api_user: "{{ proxmox_api_user }}" + validate_certs: "{{ proxmox_api_validate_certs }}" + become: true + register: proxmox_cluster_join + + - name: Define variables to join an existing Proxmox VE cluster + ansible.builtin.set_fact: + proxmox_cluster_name: "{{ proxmox_cluster_join.cluster_join.totem.cluster_name }}" + proxmox_cluster_master_ip: "{{ proxmox_cluster_join.cluster_join.nodelist[0].pve_addr }}" + proxmox_cluster_fingerprint: "{{ proxmox_cluster_join.cluster_join.nodelist[0].pve_fp }}" + when: proxmox_cluster_join.cluster_join.nodelist | length > 0 + + - name: Manage Proxmox VE Cluster + community.proxmox.proxmox_cluster: + api_host: "{{ proxmox_api_host }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + api_user: "{{ proxmox_api_user }}" + cluster_name: "{{ proxmox_cluster_name }}" + fingerprint: "{{ proxmox_cluster_fingerprint | default(omit) }}" + link0: "{{ proxmox_cluster_link0 | default(omit) }}" + link1: "{{ proxmox_cluster_link1 | default(omit) }}" + master_ip: "{{ proxmox_cluster_master_ip | default(omit) }}" + state: "{{ item.states | default('present') }}" + validate_certs: "{{ proxmox_api_validate_certs }}" + become: true + when: >- + proxmox_cluster_join.cluster_join.nodelist | length == 0 + or + proxmox_cluster_join.cluster_join.nodelist | selectattr('name', 'equalto', inventory_hostname_short) | list | length == 0 + +- name: Manage Proxmox VE storage + community.proxmox.proxmox_storage: + api_host: "{{ proxmox_api_host }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + api_user: "{{ proxmox_api_user }}" + content: "{{ item.content }}" + name: "{{ item.name }}" + nfs_options: "{{ item.nfs_options | default(omit) }}" + nodes: "{{ item.nodes }}" + state: "{{ item.states | default('present') }}" + type: "{{ item.type }}" + validate_certs: "{{ proxmox_api_validate_certs }}" + loop: "{{ proxmox_storage }}" + loop_control: + label: "{{ item.name }}" + +- name: Download images + ansible.builtin.get_url: + url: "{{ item.url }}" + dest: "{{ item.dest }}" + mode: u=rw,g=r,o=r + become: true + loop: "{{ proxmox_images }}" + loop_control: + label: "{{ item.dest | ansible.builtin.basename }}" diff --git a/tasks/node_update.yml b/tasks/node_update.yml new file mode 100644 index 0000000..8164142 --- /dev/null +++ b/tasks/node_update.yml @@ -0,0 +1,113 @@ +--- + +- name: Update apt cache + ansible.builtin.apt: + update_cache: true + become: true + +- name: List upgradable packages + ansible.builtin.command: + argv: + - apt + - list + - --upgradable + register: apt_upgradable + changed_when: apt_upgradable.stdout_lines | length > 1 + +- name: Upgrade tasks + when: apt_upgradable.stdout_lines | length > 1 + block: + - name: Enable maintenance node + ansible.builtin.command: + argv: + - ha-manager + - crm-command + - node-maintenance + - enable + - "{{ inventory_hostname_short }}" + become: true + register: proxmox_maintenance_enabled + changed_when: proxmox_maintenance_enabled.rc == 0 + + - name: Wait for the instances to be migrated + community.proxmox.proxmox_vm_info: + api_host: "{{ proxmox_api_host }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + api_user: "{{ proxmox_api_user }}" + node: "{{ inventory_hostname_short }}" + type: all + validate_certs: "{{ proxmox_api_validate_certs }}" + become: true + register: proxmox_current_instances + retries: 20 + delay: 30 + until: proxmox_current_instances.proxmox_vms | selectattr('hastate', 'equalto', 'started') | list | length == 0 + + - name: Run the full-upgrade + ansible.builtin.apt: + upgrade: dist + become: true + + - name: Reboot + ansible.builtin.reboot: + become: true + + - name: Clean apt things + ansible.builtin.apt: + clean: true + become: true + + - name: List installed packages + ansible.builtin.command: # noqa: no-changed-when + argv: + - apt + - list + - --installed + register: apt_list + check_mode: false + + - name: Remove old kernels + ansible.builtin.apt: + name: "{{ installed_kernels[:-2] }}" + state: absent + when: installed_kernels | length > 2 + become: true + vars: + installed_kernels: + apt_list.stdout_lines | + select('search', '^proxmox-kernel') | + select('search', 'automatic') | + split('/') | first | list + check_mode: true + +- name: Disable maintenance node + ansible.builtin.command: + argv: + - ha-manager + - crm-command + - node-maintenance + - disable + - "{{ inventory_hostname_short }}" + become: true + register: proxmox_maintenance_disabled + changed_when: proxmox_maintenance_disabled.rc == 0 + +- name: Wait for the node to come out of maintenance + ansible.builtin.command: # noqa: no-changed-when + argv: + - ha-manager + - status + become: true + register: proxmox_ha_manager_status + changed_when: >- + proxmox_ha_manager_status.stdout_lines | + select('search', 'lrm ' + inventory_hostname_short) | + regex_search('active', ignorecase=true) + retries: 20 + delay: 30 + until: >- + not + proxmox_ha_manager_status.stdout_lines | + select('search', 'lrm ' + inventory_hostname_short) | + regex_search('active', ignorecase=true) diff --git a/tasks/update_pve.yml b/tasks/update_pve.yml deleted file mode 100644 index e0d9ad1..0000000 --- a/tasks/update_pve.yml +++ /dev/null @@ -1,73 +0,0 @@ ---- - -- name: Update apt cache - ansible.builtin.apt: - update_cache: true - become: true - -- name: List upgradable packages - ansible.builtin.command: - argv: - - apt - - list - - --upgradable - register: apt_upgradable - changed_when: apt_upgradable.stdout_lines | length > 1 - notify: Upgrade the node - check_mode: false - -- name: Flush handlers - ansible.builtin.meta: flush_handlers - -- name: Clean apt things - ansible.builtin.apt: - clean: true - become: true - -- name: List installed packages - ansible.builtin.command: # noqa: no-changed-when - argv: - - apt - - list - - --installed - register: apt_list - check_mode: false - -- name: Remove old kernels - ansible.builtin.apt: - name: "{{ installed_kernels[:-2] }}" - state: absent - when: installed_kernels | length > 2 - become: true - vars: - installed_kernels: - apt_list.stdout_lines | - select('search', '^proxmox-kernel') | - select('search', 'automatic') | - split('/') | first | list - check_mode: true - -- name: Disable maintenance mode - ansible.builtin.command: # noqa: no-changed-when - argv: - - ha-manager - - crm-command - - node-maintenance - - disable - - "{{ inventory_hostname_short }}" - become: true - -- name: Wait node is active in ha-manager - ansible.builtin.command: # noqa: no-changed-when - argv: - - ha-manager - - status - become: true - register: ha_manager_status - retries: 10 - delay: 60 - until: > - not - ha_manager_status.stdout_lines | - select('search', 'lrm ' + inventory_hostname_short) | - regex_search('active', ignorecase=true)