Ansible for Proxmox VE · Module 03
community.proxmox collectionOne shared configuration filesystem (/etc/pve), one GUI for all three, quorum.
Works everywhere · you own the guards · parsing output is on you
We use the collection module. The CLI command path is in the handout as a fallback.
community.proxmox ships around sixty modules plus an inventory plugin.
The Proxmox modules used to live in community.general. They moved to their own collection and were removed from community.general in version 11. Old examples on the internet still say community.general.proxmox_kvm.
proxmoxer and requests must exist where the task runsdelegate_to: localhostThe play still iterates over pve02 and pve03. You keep the per-host variables and only move the execution.
In inventory/group_vars/pve.yml:
playbooks/20-cluster.yml
- name: Create the cluster on the primary node
hosts: pve_primary
gather_facts: false
tasks:
- name: Ensure the cluster exists
community.proxmox.proxmox_cluster:
state: present
api_host: "{{ ansible_host }}"
api_user: root@pam
api_password: "{{ pve_root_password }}"
validate_certs: false
cluster_name: "{{ pve_cluster_name }}"
link0: "{{ ansible_host }}"
delegate_to: localhostlink0 pins corosync to the management network. Be explicit; do not let it guess.
- name: Join the remaining nodes
hosts: pve_secondary
gather_facts: false
serial: 1 # one node after the other
vars:
primary: "{{ groups['pve_primary'][0] }}"
tasks:
- name: Read the join information from the primary
community.proxmox.proxmox_cluster_join_info:
api_host: "{{ hostvars[primary].ansible_host }}"
api_user: root@pam
api_password: "{{ pve_root_password }}"
validate_certs: false
delegate_to: localhost
register: join_info - name: Join this node to the cluster
community.proxmox.proxmox_cluster:
state: present
api_host: "{{ ansible_host }}"
api_user: root@pam
api_password: "{{ pve_root_password }}"
validate_certs: false
master_ip: "{{ hostvars[primary].ansible_host }}"
fingerprint: "{{ (join_info.cluster_join.nodelist
| selectattr('name', 'equalto', primary)
| first).pve_fp }}"
link0: "{{ ansible_host }}"
delegate_to: localhostThe fingerprint is how the joining node checks it is talking to the right cluster.
serial: 1 mattersWithout it, all plays run in parallel and two nodes try to join the same cluster at the same moment.
serial: 1 turns a race condition into a queue. It costs a few seconds and removes a class of bug that is unpleasant to debug later.
The API accepts the join and returns immediately. The node is not a member yet.
- name: Wait until this node is a quorate cluster member
community.proxmox.proxmox_cluster_status_info:
api_host: "{{ ansible_host }}"
api_user: root@pam
api_password: "{{ pve_root_password }}"
validate_certs: false
delegate_to: localhost
register: cl
until: cl.cluster_status | default([])
| selectattr('type', 'equalto', 'cluster')
| selectattr('quorate', 'equalto', true) | list | count > 0
retries: 30
delay: 5default([]) matters: the node’s API restarts during the join, and without it a single failed call aborts the loop instead of retrying.
- name: Wait until the primary reports a quorate cluster
community.proxmox.proxmox_cluster_status_info:
...
register: primary_status
until: primary_status.cluster_status | default([])
| selectattr('type', 'equalto', 'cluster')
| selectattr('quorate', 'equalto', true) | list | count > 0
retries: 30
delay: 5Skip this and the first join fails with cluster not ready - no quorum?. Waiting for port 8006 does not help: pveproxy never went away.
Then check, from Ansible and from the node:
Run the playbook a second time: everything ok, nothing changed.
Three nodes, three votes, quorum at 2.
| Situation | Quorate | Effect |
|---|---|---|
| 3 of 3 online | yes | normal operation |
| 2 of 3 online | yes | full operation, no redundancy left |
| 1 of 3 online | no | /etc/pve becomes read-only |
A non-quorate node cannot start guests or change configuration. That is a feature: it prevents two halves of a split cluster from both believing they are in charge.
/etc/pveRoot’s authorized keys are not a per-node file. They live in pmxcfs, which makes them cluster-wide.
A joining node receives the primary’s /etc/pve. Keys that existed only on that node are gone afterwards.
Your key is on all three nodes since deploy.sh, so the join changes nothing for you. On a cluster you build for someone else, it decides whether you can still log in.
Install the collection, set variables in group_vars/pve.yml, write 20-cluster.yml, run it, verify.
→ Exercise sheet: Forming the cluster
credativ · Ansible for Proxmox VE