feat(zfs): add capacity metrics and Grafana dashboard

Deploy an hourly systemd timer that writes pool and dataset capacity to
node_exporter's textfile collector, since --collector.zfs exposes only
ARC/I-O/state. Add a provisioned 'ZFS - Pools & Datasets' dashboard with
per-pool totals, per-dataset used/quota and daily growth panels.
This commit is contained in:
Clément Désiles
2026-09-18 15:25:23 +02:00
parent 501671770f
commit c127336321
10 changed files with 317 additions and 1 deletions
+25 -1
View File
@@ -1,4 +1,4 @@
# Disks
# ZFS
Ansible community support for ZFS is limited to create filesystems, volumes and snapshots. There is no support for managing zpools, so here it is.
@@ -36,6 +36,30 @@ zfs_datasets:
quota: 500G
```
## Prometheus metrics
The role deploys an hourly systemd timer (`zfs-metrics.timer`) running
`/usr/local/bin/zfs-metrics.sh`, which writes pool and dataset capacity to
node_exporter's textfile collector directory (`zfs_metrics_dir`). The
`node_exporter` role must be deployed on the host; the script exits silently
otherwise.
| Metric | Labels | Description |
| --- | --- | --- |
| `zfs_pool_size_bytes` | `pool` | Total pool size |
| `zfs_pool_alloc_bytes` | `pool` | Allocated bytes |
| `zfs_pool_free_bytes` | `pool` | Free bytes |
| `zfs_pool_capacity_percent` | `pool` | Used capacity |
| `zfs_dataset_used_bytes` | `dataset` | Used bytes (incl. snapshots and children) |
| `zfs_dataset_refer_bytes` | `dataset` | Own referenced bytes |
| `zfs_dataset_quota_bytes` | `dataset` | Quota (0 = none) |
These feed the **ZFS - Pools & Datasets** Grafana dashboard. node_exporter's
built-in `--collector.zfs` exposes ARC, I/O and pool state only, not capacity.
Tunables (`defaults/main.yml`): `zfs_metrics_enabled`, `zfs_metrics_dir`,
`zfs_metrics_script_path`, `zfs_metrics_interval`.
## References
- https://docs.ansible.com/ansible/latest/collections/community/general/zfs_module.html
+18
View File
@@ -0,0 +1,18 @@
---
# ZFS capacity metrics (Prometheus)
# ---------------------------------
# Exposes pool size/used/free and per-dataset used/quota to Prometheus via
# node_exporter's textfile collector. node_exporter's built-in --collector.zfs
# only exposes ARC, I/O and pool state — not capacity.
zfs_metrics_enabled: true
# Directory node_exporter's textfile collector reads `*.prom` files from.
# Must match node_exporter_textfile_dir in roles/node_exporter/defaults/main.yml.
zfs_metrics_dir: /var/lib/node_exporter/textfile_collector
# Where the collector script is installed.
zfs_metrics_script_path: /usr/local/bin/zfs-metrics.sh
# systemd OnCalendar expression for the collection timer. Hourly is plenty to
# track daily growth; Prometheus keeps 90d of history on andromeda.
zfs_metrics_interval: "hourly"
+4
View File
@@ -0,0 +1,4 @@
---
- name: Reload systemd
ansible.builtin.systemd:
daemon_reload: true
+4
View File
@@ -5,3 +5,7 @@
ansible.builtin.include_tasks: pools.yml
- name: "Setup ZFS datasets: filesystems, snapshots, volumes"
ansible.builtin.include_tasks: datasets.yml
- name: Setup ZFS capacity metrics
ansible.builtin.include_tasks: metrics.yml
when: zfs_metrics_enabled | bool
+43
View File
@@ -0,0 +1,43 @@
---
# ZFS capacity metrics, exposed to Prometheus through node_exporter's textfile
# collector (see roles/node_exporter). Runs hourly as a root system timer.
- name: Deploy ZFS metrics script
ansible.builtin.template:
src: zfs-metrics.sh.j2
dest: "{{ zfs_metrics_script_path }}"
owner: root
group: root
mode: "0755"
- name: Deploy ZFS metrics systemd service
ansible.builtin.template:
src: zfs-metrics.service.j2
dest: /etc/systemd/system/zfs-metrics.service
owner: root
group: root
mode: "0644"
notify: Reload systemd
- name: Deploy ZFS metrics systemd timer
ansible.builtin.template:
src: zfs-metrics.timer.j2
dest: /etc/systemd/system/zfs-metrics.timer
owner: root
group: root
mode: "0644"
notify: Reload systemd
- name: Flush handlers before enabling the timer
ansible.builtin.meta: flush_handlers
- name: Enable and start ZFS metrics timer
ansible.builtin.systemd:
name: zfs-metrics.timer
enabled: true
state: started
- name: Collect ZFS metrics now
ansible.builtin.systemd:
name: zfs-metrics.service
state: started
@@ -0,0 +1,7 @@
[Unit]
Description=Collect ZFS pool and dataset capacity metrics
After=zfs.target zfs-import.target
[Service]
Type=oneshot
ExecStart={{ zfs_metrics_script_path }}
+49
View File
@@ -0,0 +1,49 @@
#!/bin/sh
# Managed by Ansible - DO NOT EDIT MANUALLY
#
# Writes ZFS pool and dataset capacity metrics as Prometheus textfile metrics
# into node_exporter's textfile collector directory. node_exporter's built-in
# --collector.zfs exposes ARC, pool I/O and pool state only, not capacity.
set -eu
set -o pipefail
metrics_dir="{{ zfs_metrics_dir }}"
# node_exporter may not be deployed on this host.
[ -d "$metrics_dir" ] || exit 0
out="$metrics_dir/zfs.prom"
tmp="$out.tmp"
# Written atomically (tmp + mv) so the collector never reads a half-written file.
{
echo "# HELP zfs_pool_size_bytes Total size of the ZFS pool."
echo "# TYPE zfs_pool_size_bytes gauge"
echo "# HELP zfs_pool_alloc_bytes Allocated bytes in the ZFS pool."
echo "# TYPE zfs_pool_alloc_bytes gauge"
echo "# HELP zfs_pool_free_bytes Free bytes in the ZFS pool."
echo "# TYPE zfs_pool_free_bytes gauge"
echo "# HELP zfs_pool_capacity_percent Used capacity of the ZFS pool, in percent."
echo "# TYPE zfs_pool_capacity_percent gauge"
echo "# HELP zfs_dataset_used_bytes Used bytes of a ZFS dataset, including snapshots and children."
echo "# TYPE zfs_dataset_used_bytes gauge"
echo "# HELP zfs_dataset_refer_bytes Bytes referenced by a ZFS dataset itself."
echo "# TYPE zfs_dataset_refer_bytes gauge"
echo "# HELP zfs_dataset_quota_bytes Quota of a ZFS dataset in bytes (0 = no quota)."
echo "# TYPE zfs_dataset_quota_bytes gauge"
zpool list -Hp -o name,size,alloc,free,capacity | while read -r pool size alloc free capacity; do
echo "zfs_pool_size_bytes{pool=\"$pool\"} $size"
echo "zfs_pool_alloc_bytes{pool=\"$pool\"} $alloc"
echo "zfs_pool_free_bytes{pool=\"$pool\"} $free"
echo "zfs_pool_capacity_percent{pool=\"$pool\"} $capacity"
done
zfs list -Hp -o name,used,refer,quota -t filesystem | while read -r dataset used refer quota; do
echo "zfs_dataset_used_bytes{dataset=\"$dataset\"} $used"
echo "zfs_dataset_refer_bytes{dataset=\"$dataset\"} $refer"
echo "zfs_dataset_quota_bytes{dataset=\"$dataset\"} $quota"
done
} > "$tmp"
mv "$tmp" "$out"
+10
View File
@@ -0,0 +1,10 @@
[Unit]
Description=Timer for ZFS capacity metrics collection
[Timer]
OnCalendar={{ zfs_metrics_interval }}
Persistent=true
Unit=zfs-metrics.service
[Install]
WantedBy=timers.target