Refactoring + final touches

This commit is contained in:
Francesco Zimbolo
2026-04-24 12:58:56 +00:00
parent 6f6cc10aae
commit d8b60d10e0
16 changed files with 369 additions and 106 deletions

View File

@@ -1,5 +1,8 @@
---
# tasks file for linstor_cluster
# IMPORTANT: All Linstor node registrations use ansible_hostname (the real system hostname)
# to ensure they match Proxmox node names, which are also derived from the system hostname.
# The Ansible inventory_hostname (e.g. "pve1") may differ from the real hostname (e.g. "pve-node-01").
- name: Safely manage LINSTOR cluster nodes in /etc/hosts
ansible.builtin.blockinfile:
@@ -8,12 +11,12 @@
block: |
# Linstor Cluster Nodes (Primary: Private Network)
{% for host in groups['proxmox'] %}
{{ hostvars[host].priv_ip }} {{ host }}
{{ hostvars[host]['priv_ip'] | default(hostvars[host]['ansible_host']) }} {{ hostvars[host]['ansible_facts']['hostname'] }}
{% endfor %}
# Linstor Cluster Nodes (Fallback: Public Network)
{% for host in groups['proxmox'] %}
{{ hostvars[host].ansible_host }} {{ host }}-public
{{ hostvars[host]['ansible_host'] }} {{ hostvars[host]['ansible_facts']['hostname'] }}-public
{% endfor %}
- name: Ensure /etc/linstor directory exists
@@ -30,13 +33,17 @@
# ---------------------------------------------------------
# LINSTOR NODE REGISTRATION (Executed ONLY on Node 1)
# Uses ansible_hostname (real system hostname) for Linstor node names
# to guarantee compatibility with Proxmox node names.
# ---------------------------------------------------------
- name: Register nodes in LINSTOR
ansible.builtin.shell: |
set -o pipefail
if ! linstor node list | grep -q " {{ item }} "; then
linstor node create "{{ item }}" "{{ hostvars[item].priv_ip }}" --node-type Combined > /dev/null
NODE_NAME="{{ hostvars[item]['ansible_facts']['hostname'] }}"
NODE_IP="{{ hostvars[item]['priv_ip'] | default(hostvars[item]['ansible_host']) }}"
if ! linstor node list | grep -q " ${NODE_NAME} "; then
linstor node create "${NODE_NAME}" "${NODE_IP}" --node-type Combined > /dev/null
echo "changed"
fi
args:
@@ -49,8 +56,10 @@
- name: Configure Multipath Fallback Interfaces
ansible.builtin.shell: |
set -o pipefail
if ! linstor node interface list "{{ item }}" | grep -q " fallback "; then
linstor node interface create "{{ item }}" fallback "{{ hostvars[item].ansible_host }}" > /dev/null
NODE_NAME="{{ hostvars[item]['ansible_facts']['hostname'] }}"
NODE_PUBLIC_IP="{{ hostvars[item]['ansible_host'] }}"
if ! linstor node interface list "${NODE_NAME}" | grep -q " fallback "; then
linstor node interface create "${NODE_NAME}" fallback "${NODE_PUBLIC_IP}" > /dev/null
echo "changed"
fi
args:
@@ -59,3 +68,22 @@
changed_when: "'changed' in linstor_cluster_int_create.stdout"
loop: "{{ groups['proxmox'] }}"
when: inventory_hostname == groups['proxmox'][0]
- name: Wait for all Linstor satellite nodes to appear as Online
ansible.builtin.shell: |
set -o pipefail
if linstor node list | sed 's/\x1b\[[0-9;]*m//g' | grep " {{ hostvars[item]['ansible_facts']['hostname'] }} " | grep -q "Online"; then
echo "online"
else
echo "not_ready"
exit 1
fi
args:
executable: /bin/bash
register: linstor_cluster_node_status
until: "'online' in linstor_cluster_node_status.stdout"
retries: 12
delay: 10
changed_when: false
loop: "{{ groups['proxmox'] }}"
when: inventory_hostname == groups['proxmox'][0]

View File

@@ -1,3 +1,5 @@
[global]
# Ansible dynamically grabs all hosts in the 'proxmox' group and joins them with commas
controllers={{ groups['proxmox'] | join(',') }}
# Controllers list uses private IPs of all nodes (tries each in order).
# After linstor_ha setup, the VIP becomes the active controller endpoint.
# The drbd-reactor automatically manages controller failover via the VIP.
controllers={% for host in groups['proxmox'] %}{{ hostvars[host]['priv_ip'] | default(hostvars[host]['ansible_host']) }}{% if not loop.last %},{% endif %}{% endfor %}

View File

@@ -0,0 +1,5 @@
---
- name: Reload drbd-reactor
ansible.builtin.systemd:
name: drbd-reactor
state: reloaded

View File

@@ -65,6 +65,7 @@
src: linstor_db.toml.j2
dest: /etc/drbd-reactor.d/linstor_db.toml
mode: '0644'
notify: Reload drbd-reactor
# Notice how the stat task is gone, replaced by 'creates' and 'removes'
- name: Move existing local database to .orig safely

View File

@@ -0,0 +1,4 @@
---
- name: Update APT cache
ansible.builtin.apt:
update_cache: true

View File

@@ -1,24 +1,35 @@
---
# tasks file for linstor_repo
# Target: Proxmox 9 (Debian 13 / Trixie) with DRBD 9
# linbit_proxmox_version defaults to: Debian major version minus 4
# (Debian 13 = PVE 9, Debian 12 = PVE 8, etc.)
# Override this variable in group_vars if LINBIT releases a new repo name.
- name: Download LINBIT keyring package
ansible.builtin.get_url:
url: https://packages.linbit.com/public/linbit-keyring.deb
dest: /tmp/linbit-keyring.deb
mode: '0644'
check_mode: false
- name: Install LINBIT keyring
ansible.builtin.apt:
deb: /tmp/linbit-keyring.deb
state: present
- name: Add LINBIT APT repository for Proxmox
ansible.builtin.apt_repository:
repo: >-
deb [signed-by=/etc/apt/trusted.gpg.d/linbit-keyring.gpg]
https://packages.linbit.com/public/
proxmox-{{ ansible_facts['distribution_major_version'] | int - 4 }}
drbd-9
filename: linbit
- name: Remove legacy LINBIT .list file (old APT format, prevents duplicates)
ansible.builtin.file:
path: /etc/apt/sources.list.d/linbit.list
state: absent
- name: Add LINBIT APT repository for Proxmox (DEB822 format)
ansible.builtin.deb822_repository:
name: linbit
types: deb
uris: https://packages.linbit.com/public/
suites: "proxmox-{{ linbit_proxmox_version | default(ansible_facts['distribution_major_version'] | int - 4) }}"
components: drbd-9
signed_by: /etc/apt/trusted.gpg.d/linbit-keyring.gpg
state: present
update_cache: true
notify: Update APT cache

View File

@@ -1,22 +1,66 @@
---
# tasks file for linstor_storage
# NOTE: linstor_storage_pools[].targets keys are Ansible inventory_hostname values.
# All Linstor registrations use ansible_hostname (real system hostname) instead,
# to match Proxmox node names. The mapping is done per-host at registration time.
- name: Configure LVM global_filter for DRBD safely
ansible.builtin.lineinfile:
path: /etc/lvm/lvm.conf
insertafter: '^[ \t]*devices[ \t]*\{'
# This regex ensures we only ever have ONE DRBD filter line, replacing the old one if it exists
regexp: '^[ \t]*global_filter[ \t]*=.*r\|\^/dev/drbd\|'
regexp: '^[ \t]*global_filter[ \t]*='
line: ' global_filter = [ "r|^/dev/drbd|", "r|^/dev/mapper/[lL]instor|" ]'
state: present
- name: Remove duplicate global_filter entries from lvm.conf
ansible.builtin.shell: |
count=$(grep -c '^[[:space:]]*global_filter[[:space:]]*=' /etc/lvm/lvm.conf)
if [ "$count" -gt 1 ]; then
awk '/^[[:space:]]*global_filter[[:space:]]*=/{if(++n>1)next}1' /etc/lvm/lvm.conf > /tmp/lvm.conf.dedup
mv /tmp/lvm.conf.dedup /etc/lvm/lvm.conf
echo "changed: removed $((count - 1)) duplicate(s)"
fi
args:
executable: /bin/bash
register: lvm_dedup_result
changed_when: "'changed' in lvm_dedup_result.stdout"
- name: Wipe existing signatures and partition tables from raw disks
ansible.builtin.shell: |
DEV="{{ item.targets[inventory_hostname] }}"
VG="{{ item.vg_name }}"
# Deactivate VG properly if LVM still knows about it
vgchange -an "$VG" 2>/dev/null || true
# Forcibly remove any stale dm-mapper devices for this VG (handles the case
# where the VG metadata was already wiped but kernel devices are still active)
dmsetup ls 2>/dev/null \
| awk -v vg="$VG" 'index($1, vg"-") == 1 { print $1 }' \
| xargs -r dmsetup remove --force 2>/dev/null || true
wipefs -a "$DEV"
sgdisk --zap-all "$DEV"
partprobe "$DEV" 2>/dev/null || true
args:
executable: /bin/bash
changed_when: true
loop: "{{ linstor_storage_pools }}"
when:
- wipe_linstor_disks | default(false) | bool
- item.targets[inventory_hostname] is defined
- item.targets[inventory_hostname].startswith('/dev/')
- name: Ensure Volume Groups exist for raw disks
community.general.lvg:
vg: "{{ item.vg_name }}"
pvs: "{{ item.targets[inventory_hostname] }}"
pv_options: "{{ '--force --force' if wipe_linstor_disks | default(false) | bool else '--force' }}"
loop: "{{ linstor_storage_pools }}"
# Jinja2 magic: Only run this LVM task if the target string starts with '/dev/'
when: item.targets[inventory_hostname].startswith('/dev/')
# Only run for pools that target this node AND use a raw block device
when:
- item.targets[inventory_hostname] is defined
- item.targets[inventory_hostname].startswith('/dev/')
- name: Ensure LVM Thin Pools exist for raw disks
community.general.lvol:
@@ -26,25 +70,31 @@
opts: "-T -Zn"
shrink: false
loop: "{{ linstor_storage_pools }}"
when: item.targets[inventory_hostname].startswith('/dev/')
when:
- item.targets[inventory_hostname] is defined
- item.targets[inventory_hostname].startswith('/dev/')
# ---------------------------------------------------------
# LINSTOR STORAGE REGISTRATION (Executed ONLY on Node 1)
# Uses ansible_hostname (real system hostname) for Linstor node names
# ---------------------------------------------------------
- name: Register Storage Pools in LINSTOR
ansible.builtin.shell: |
set -o pipefail
# We use Jinja2 to write a custom bash loop for exactly the nodes in our inventory
# Iterate over all inventory hosts for this pool.
# Use ansible_hostname (real system hostname) as Linstor node name.
{% for host in groups['proxmox'] %}
{% if item.targets[host] is defined %}
{% set target = item.targets[host] %}
{% set lvm_path = (item.vg_name + '/' + item.thin_name) if target.startswith('/dev/') else target %}
if ! linstor storage-pool list | grep -q " {{ host }} .* {{ item.pool_name }} "; then
linstor storage-pool create lvmthin "{{ host }}" "{{ item.pool_name }}" "{{ lvm_path }}" > /dev/null
NODE_NAME="{{ hostvars[host]['ansible_facts']['hostname'] }}"
if ! linstor storage-pool list | grep -q " ${NODE_NAME} .* {{ item.pool_name }} "; then
linstor storage-pool create lvmthin "${NODE_NAME}" "{{ item.pool_name }}" "{{ lvm_path }}" > /dev/null
echo "changed"
fi
{% endif %}
{% endfor %}
args:
executable: /bin/bash
@@ -57,7 +107,9 @@
ansible.builtin.shell: |
set -o pipefail
if ! linstor resource-group list | grep -q " {{ item.rg_name }} "; then
linstor resource-group create "{{ item.rg_name }}" --storage-pool "{{ item.pool_name }}" --place-count 3 > /dev/null
linstor resource-group create "{{ item.rg_name }}" \
--storage-pool "{{ item.pool_name }}" \
--place-count {{ groups['proxmox'] | length }} > /dev/null
linstor volume-group create "{{ item.rg_name }}" > /dev/null
echo "changed"
fi

View File

@@ -0,0 +1 @@
---

View File

@@ -0,0 +1,28 @@
---
- name: Validate p2p NICs exist on this node
ansible.builtin.assert:
that:
- priv_nic_1 in ansible_facts['interfaces']
- priv_nic_2 in ansible_facts['interfaces']
fail_msg: >-
One or both p2p NICs not found on {{ inventory_hostname }}.
Expected: {{ priv_nic_1 }}, {{ priv_nic_2 }}.
Available interfaces: {{ ansible_facts['interfaces'] | join(', ') }}
- name: Configure p2p bridge in /etc/network/interfaces
ansible.builtin.blockinfile:
path: /etc/network/interfaces
marker: "# {mark} ANSIBLE MANAGED BLOCK - p2p bridge"
block: |
auto p2p
iface p2p inet static
address {{ priv_ip }}/{{ ha_vip_cidr }}
bridge-ports {{ priv_nic_1 }} {{ priv_nic_2 }}
bridge-stp on
bridge-fd 0
register: p2p_network_p2p_bridge_config
- name: Apply network config # noqa: no-handler
ansible.builtin.command: ifreload -a
changed_when: true
when: p2p_network_p2p_bridge_config is changed