Five things deprecated, each verified against the DEPLOYED script before being
deleted rather than assumed superseded:
infra/410_disk_usage_alerts.yml -> disk-usage check (infra/400)
infra/420_system_healthcheck.yml -> liveness check (infra/400)
infra/430_cpu_temp_alerts.yml -> cpu-temp check (infra/400)
32_zfs play 2 (monitoring half) -> zfs-health check (infra/400)
34_nut play 2 (entirely) -> ups-status check (infra/400)
Nothing is lost by the swap. The old system_healthcheck.sh only computed uptime
and pushed, which is exactly a liveness heartbeat. The old disk monitor was
WEAKER than its replacement: it checked "/" alone at 80%, where the new one
walks every real filesystem at 85%.
Deleting the playbooks was not the hard part. The units they installed live on
the hosts, enabled, and keep firing regardless of what the repo says - two of
them were still pushing to uptime.contrapeso.xyz every 15 minutes across nine
machines. A playbook deleted without a cleanup leaves its output running
forever with nothing left to explain it. So infra/409_remove_legacy_monitoring
stops, disables and removes the units, deletes /opt/{disk-monitoring,
system-healthcheck,nodito-monitoring,zfs-monitoring}, and removes the orphaned
hand-written ups-heartbeat.sh. It ends by grepping for any surviving Kuma
reference and reporting it. Kept permanently and idempotent, so a rebuilt or
restored host cannot quietly bring them back.
A trap avoided: the monthly ZFS scrub lived INSIDE 32_zfs play 2. Deleting the
play wholesale would have silently stopped scrubbing the pool - and an
unscrubbed pool makes the health check meaningless, because it would have
nothing true to report. That play is now scrub-only and check-runs ok=5
changed=0.
ZFS pool capacity added as a sixth condition to the zfs-health check. `zpool
status` reports a 95% full pool as perfectly ONLINE, so capacity has to be read
separately with `zpool list` - and it is the failure you get warning of rather
than the one you discover. Threshold 80%, because ZFS allocation degrades badly
past roughly that and fragmentation is hard to undo. The pool is at 47%.
Verified both directions: passes on the real pool, and a simulated 91% exits 1.
Also fixed the waste recorded in c2de6db: the healthcheck role installed its
dependencies once per CHECK rather than per HOST - 29 apt transactions
estate-wide for a curl already present, and the slowest part of every deploy.
It now deduplicates within a play run, and the redundant standalone
daemon_reload is gone (the systemd task already does one).
site.yml updated, which exposed that services/gatus was never in it. It now runs
before the three registration playbooks, since registering endpoints against a
Gatus that is not yet serving would simply fail.
Verified: no legacy timer remains on any host; 83 endpoints, 83 UP, 0 DOWN.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
224 lines
7.7 KiB
YAML
224 lines
7.7 KiB
YAML
- name: Setup ZFS RAID 1 Pool for Proxmox Storage
|
|
hosts: hypervisor
|
|
become: true
|
|
|
|
tasks:
|
|
- name: Verify Proxmox VE is running
|
|
command: pveversion
|
|
register: pve_version_check
|
|
changed_when: false
|
|
failed_when: pve_version_check.rc != 0
|
|
|
|
- name: Update package cache
|
|
apt:
|
|
update_cache: yes
|
|
cache_valid_time: 3600
|
|
|
|
- name: Install ZFS utilities
|
|
package:
|
|
name:
|
|
- zfsutils-linux
|
|
- zfs-initramfs
|
|
state: present
|
|
|
|
- name: Load ZFS kernel module
|
|
modprobe:
|
|
name: zfs
|
|
|
|
- name: Ensure ZFS module loads at boot
|
|
lineinfile:
|
|
path: /etc/modules
|
|
line: zfs
|
|
state: present
|
|
|
|
- name: Check if ZFS pool already exists
|
|
command: zpool list {{ zfs_pool_name }}
|
|
register: zfs_pool_exists
|
|
failed_when: false
|
|
changed_when: false
|
|
|
|
- name: Check if disks are in use
|
|
shell: |
|
|
for disk in {{ zfs_disk_1 }} {{ zfs_disk_2 }}; do
|
|
if mount | grep -q "^$disk"; then
|
|
echo "ERROR: $disk is mounted"
|
|
exit 1
|
|
fi
|
|
if lsblk -n -o MOUNTPOINT "$disk" | grep -v "^$" | grep -q .; then
|
|
echo "ERROR: $disk has mounted partitions"
|
|
exit 1
|
|
fi
|
|
done
|
|
register: disk_usage_check
|
|
failed_when: disk_usage_check.rc != 0
|
|
changed_when: false
|
|
|
|
- name: Create ZFS RAID 1 pool with optimized settings
|
|
command: >
|
|
zpool create {{ zfs_pool_name }}
|
|
-o ashift=12
|
|
-O mountpoint=none
|
|
mirror {{ zfs_disk_1 }} {{ zfs_disk_2 }}
|
|
when: zfs_pool_exists.rc != 0
|
|
register: zfs_pool_create_result
|
|
|
|
- name: Check if ZFS dataset already exists
|
|
command: zfs list {{ zfs_pool_name }}/vm-storage
|
|
register: zfs_dataset_exists
|
|
failed_when: false
|
|
changed_when: false
|
|
|
|
- name: Create ZFS dataset for Proxmox storage
|
|
command: zfs create {{ zfs_pool_name }}/vm-storage
|
|
when: zfs_dataset_exists.rc != 0
|
|
register: zfs_dataset_create_result
|
|
|
|
- name: Set ZFS dataset properties for Proxmox
|
|
command: zfs set {{ item.property }}={{ item.value }} {{ zfs_pool_name }}/vm-storage
|
|
loop:
|
|
- { property: "mountpoint", value: "{{ zfs_pool_mountpoint }}" }
|
|
- { property: "compression", value: "lz4" }
|
|
- { property: "atime", value: "off" }
|
|
- { property: "xattr", value: "sa" }
|
|
- { property: "acltype", value: "posixacl" }
|
|
- { property: "dnodesize", value: "auto" }
|
|
when: zfs_dataset_exists.rc != 0
|
|
|
|
- name: Set ZFS pool properties for Proxmox
|
|
command: zpool set autotrim=off {{ zfs_pool_name }}
|
|
when: zfs_pool_exists.rc != 0
|
|
|
|
- name: Set ZFS pool mountpoint for Proxmox
|
|
command: zfs set mountpoint={{ zfs_pool_mountpoint }} {{ zfs_pool_name }}
|
|
when: zfs_pool_exists.rc == 0
|
|
|
|
- name: Export and re-import ZFS pool for Proxmox compatibility
|
|
shell: |
|
|
zpool export {{ zfs_pool_name }}
|
|
zpool import {{ zfs_pool_name }}
|
|
when: zfs_pool_exists.rc != 0
|
|
register: zfs_pool_import_result
|
|
|
|
|
|
- name: Ensure ZFS services are enabled
|
|
systemd:
|
|
name: "{{ item }}"
|
|
enabled: yes
|
|
state: started
|
|
loop:
|
|
- zfs-import-cache
|
|
- zfs-import-scan
|
|
- zfs-mount
|
|
- zfs-share
|
|
- zfs-zed
|
|
|
|
- name: Check if ZFS pool storage already exists in Proxmox config
|
|
stat:
|
|
path: /etc/pve/storage.cfg
|
|
register: storage_cfg_file
|
|
|
|
- name: Check if storage name exists in Proxmox config
|
|
shell: "grep -q '^zfspool: {{ zfs_pool_name }}' /etc/pve/storage.cfg"
|
|
register: storage_exists_check
|
|
failed_when: false
|
|
changed_when: false
|
|
when: storage_cfg_file.stat.exists
|
|
|
|
- name: Set storage not configured when config file doesn't exist
|
|
set_fact:
|
|
storage_exists_check:
|
|
rc: 1
|
|
when: not storage_cfg_file.stat.exists
|
|
|
|
- name: Debug storage configuration status
|
|
debug:
|
|
msg: |
|
|
Config file exists: {{ storage_cfg_file.stat.exists }}
|
|
Storage check result: {{ storage_exists_check.rc }}
|
|
Pool exists: {{ zfs_pool_exists.rc == 0 }}
|
|
Will add storage: {{ zfs_pool_exists.rc == 0 and storage_exists_check.rc != 0 }}
|
|
|
|
# Registration is add-only on purpose. There used to be a "Remove existing
|
|
# storage if it exists" task here that ran `pvesm remove` whenever the
|
|
# storage WAS present, paired with an add that only ran when it was ABSENT.
|
|
# The two conditions are mutually exclusive, so a real run against a
|
|
# correctly-configured hypervisor removed the storage entry backing every VM
|
|
# and never put it back. It would also have dropped `mountpoint /var/lib/vz`,
|
|
# which the live entry has and which `pvesm add` below does not set.
|
|
#
|
|
# If the storage entry ever needs its options changed, edit
|
|
# /etc/pve/storage.cfg or use `pvesm set` - do not re-register it from here.
|
|
- name: Add ZFS pool storage to Proxmox using pvesm
|
|
command: >
|
|
pvesm add zfspool {{ zfs_pool_name }}
|
|
--pool {{ zfs_pool_name }}
|
|
--content rootdir,images
|
|
--sparse 1
|
|
when:
|
|
- zfs_pool_exists.rc == 0
|
|
- storage_exists_check.rc != 0
|
|
register: pvesm_add_result
|
|
|
|
- name: Verify ZFS pool is healthy
|
|
command: zpool status {{ zfs_pool_name }}
|
|
register: final_zfs_status
|
|
changed_when: false
|
|
|
|
- name: Fail if ZFS pool is not healthy
|
|
fail:
|
|
msg: "ZFS pool {{ zfs_pool_name }} is not in a healthy state"
|
|
when: "'ONLINE' not in final_zfs_status.stdout"
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# The monthly scrub.
|
|
#
|
|
# The ZFS HEALTH CHECK that used to share this play is gone: it is now the
|
|
# zfs-health check in infra/400_host_monitoring.yml, which carries the same five
|
|
# conditions - pool state, device states, resilver in progress, read/write/
|
|
# checksum errors, and errors from the last scan - but reports to Gatus like
|
|
# every other host check instead of owning its own push plumbing.
|
|
#
|
|
# The scrub itself stays here, because it is not monitoring: it is the
|
|
# maintenance that gives the health check something true to report. A pool that
|
|
# is never scrubbed has no idea whether it is healthy.
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
- name: Schedule the monthly ZFS scrub
|
|
hosts: hypervisor
|
|
become: true
|
|
vars_files:
|
|
- ../../infra_vars.yml
|
|
|
|
vars:
|
|
zfs_systemd_scrub_service_name: zfs-monthly-scrub
|
|
|
|
tasks:
|
|
- name: Create systemd service for ZFS monthly scrub
|
|
template:
|
|
src: templates/zfs-monthly-scrub.service.j2
|
|
dest: "/etc/systemd/system/{{ zfs_systemd_scrub_service_name }}.service"
|
|
owner: root
|
|
group: root
|
|
mode: '0644'
|
|
|
|
- name: Create systemd timer for monthly ZFS scrub
|
|
template:
|
|
src: templates/zfs-monthly-scrub.timer.j2
|
|
dest: "/etc/systemd/system/{{ zfs_systemd_scrub_service_name }}.timer"
|
|
owner: root
|
|
group: root
|
|
mode: '0644'
|
|
|
|
- name: Enable and start the monthly scrub timer
|
|
systemd:
|
|
name: "{{ zfs_systemd_scrub_service_name }}.timer"
|
|
enabled: yes
|
|
state: started
|
|
daemon_reload: yes
|
|
|
|
- name: Report the scrub schedule
|
|
debug:
|
|
msg: >-
|
|
Monthly scrub of {{ zfs_pool_name }}:
|
|
last day of each month at 04:00.
|
|
Health is reported separately by the zfs-health check
|
|
(infra/400_host_monitoring.yml).
|