Ansible for Proxmox VE · Module 02
--check --diff before you mean itBefore three Proxmox VE nodes can become a cluster, they need:
| Requirement | Why |
|---|---|
| Working package sources | the enterprise repo fails without a subscription |
| Name resolution for each other | corosync and pvecm resolve node names |
| Synchronised time | corosync membership is time-sensitive |
| The same base state | “it works on pve01” is not a plan |
Four requirements, four groups of tasks. That is the playbook.
playbooks/10-node-prep.yml
---
- name: Prepare the Proxmox VE nodes
hosts: pve # which machines (a group from the inventory)
gather_facts: true # collect facts first (distribution, IPs, ...)
tasks:
- name: Install the admin tools we want everywhere
ansible.builtin.apt:
name: "{{ pve_packages }}"
state: present
update_cache: true
cache_valid_time: 3600name: on every task. It is what you read in the output and in the logs.group_varspve{ pve_packages }{ } is JinjaAnything inside { } is an expression, evaluated on the control node before the task is sent anywhere.
| pipes a value through a filter — here: this group, minus myself{{, or YAML reads the brace as a dictgather_facts: true collects a few hundred variables per host before the first task:
Use them instead of hard-coding:
Your lab runs Proxmox VE 9 on Debian 13. We use the fact instead of hardcoding trixie.
loop: takes a list; the task runs once per entryitemloop_control: {label: "{{ item.name }}"} keeps the output readable when the entries are longLoops · offline: ansible-doc -t lookup items
- name: Disable the enterprise repositories
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- /etc/apt/sources.list.d/pve-enterprise.sources
- /etc/apt/sources.list.d/ceph.sources
notify: Update apt cache
- name: Enable the no-subscription repository
ansible.builtin.deb822_repository:
name: pve-no-subscription
types: [deb]
uris: http://download.proxmox.com/debian/pve
suites: "{{ ansible_distribution_release }}"
components: [pve-no-subscription]
signed_by: /usr/share/keyrings/proxmox-archive-keyring.gpg
notify: Update apt cacheWe need the cache before installing packages, so we force it:
It comes from the provisioning, with DNS and /etc/hosts. The playbook confirms it instead of rewriting it:
register: stores the result in a variable — the next line reads its exit codechanged_when: false because reading changes nothing, and command would otherwise report changed every rungroups['pve'] | difference([inventory_hostname]): the inventory at runtime, minus this node/etc/hosts?Because something else already owns it.
Automating a file that another system owns is how you break a platform quietly. Check the state you depend on; configure only what is yours.
- name: Install the admin tools
ansible.builtin.apt:
name: "{{ pve_packages }}"
state: present
- name: Check whether the clock is synchronised
ansible.builtin.command: timedatectl show -p NTPSynchronized --value
register: ntp_state
changed_when: false # this only reads, so never report a change
check_mode: false # ... so it is safe to run it in --check too
- name: Fail early if time is not in sync
ansible.builtin.assert:
that: ntp_state.stdout == 'yes'
fail_msg: "Time is not synchronised - corosync will be unhappy"Your 10-node-prep.yml is still a skeleton — filling it in is the exercise:
changed=0 on the second run is your proof that the playbook is idempotent. One task fewer, too: the handler only runs when something notifies it.
--check is not perfect: a task that depends on an earlier change cannot know the future. It still catches most mistakes before they reach three nodes at once.
Optional block at the end if you get there. → Exercise sheet: Preparing the nodes
credativ · Ansible for Proxmox VE