apt_updates: textfile metrics of pending apt updates for the node exporter collector

This commit is contained in:
2026-10-26 14:47:17 +00:00
parent 5318997b73
commit ec64ace8f4
3 changed files with 106 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
# Directory the node exporter reads textfile metrics from (the Debian default).
apt_updates_textfile_dir: /var/lib/prometheus/node-exporter
+41
View File
@@ -0,0 +1,41 @@
# The metrics run after apt-daily (update) and apt-daily-upgrade (upgrade), so they show the state apt
# just reached without a timer of their own. The "-" lets a failing metrics run leave apt's units green.
- name: Create the textfile directory
ansible.builtin.file:
path: "{{ apt_updates_textfile_dir }}"
state: directory
mode: "0755"
- name: Install the apt metrics script
ansible.builtin.template:
src: apt-updates-metrics.j2
dest: /usr/local/sbin/apt-updates-metrics
mode: "0755"
- name: Create the apt timers' drop-in directories
ansible.builtin.file:
path: /etc/systemd/system/{{ item }}.service.d
state: directory
mode: "0755"
loop:
- apt-daily
- apt-daily-upgrade
- name: Run the apt metrics after the apt timers' services
ansible.builtin.copy:
dest: /etc/systemd/system/{{ item }}.service.d/apt-updates-metrics.conf
mode: "0644"
content: |
[Service]
ExecStartPost=-/usr/local/sbin/apt-updates-metrics
loop:
- apt-daily
- apt-daily-upgrade
register: apt_updates_dropins
- name: Reload systemd after the apt metrics drop-ins
ansible.builtin.systemd:
daemon_reload: true
when: apt_updates_dropins.changed
# Check mode does not write the drop-ins.
ignore_errors: "{{ ansible_check_mode }}"
@@ -0,0 +1,63 @@
#!/bin/bash
# Pending upgrades, held packages, reboot flag and age of the package lists as node exporter textfile
# metrics. apt-daily and apt-daily-upgrade run it after their update and upgrade (drop-ins).
set -euo pipefail
host={{ inventory_hostname }}
textfile_dir={{ apt_updates_textfile_dir }}
out=$textfile_dir/apt_updates.prom
tmp=$(mktemp "$textfile_dir/.apt_updates.XXXXXX")
trap 'rm -f "$tmp"' EXIT
arch=$(dpkg --print-architecture)
# The simulated upgrade lists "Inst <package> [<installed>] (<candidate> <origin> [<arch>])" per package.
# An origin containing "security" counts as security, everything else as regular.
pending=$(apt-get -s upgrade | awk -v host="$host" -v arch="$arch" '
/^Inst / {
line = $0
sub(/^[^(]*\(/, "", line)
sub(/\).*$/, "", line)
n = split(line, f, " ")
pkg_arch = f[n]
gsub(/\[|\]/, "", pkg_arch)
origin = (n >= 3) ? f[2] : ""
kind = (tolower(origin) ~ /security/) ? "security" : "regular"
count[kind, pkg_arch]++
}
END {
if (!(("security", arch) in count)) count["security", arch] = 0
if (!(("regular", arch) in count)) count["regular", arch] = 0
for (key in count) {
split(key, k, SUBSEP)
printf "apt_upgrades_pending{host=\"%s\",origin=\"%s\",arch=\"%s\"} %d\n", host, k[1], k[2], count[key]
}
}')
held=$(apt-mark showhold | wc -l)
reboot=0
if [[ -e /run/reboot-required ]]; then
reboot=1
fi
# Debian writes update-stamp, Ubuntu update-success-stamp; both are touched by a successful update.
stamp=/var/lib/apt/periodic/update-success-stamp
if [[ ! -e $stamp ]]; then
stamp=/var/lib/apt/periodic/update-stamp
fi
cache=$(stat -c %Y "$stamp")
{
echo '# TYPE apt_upgrades_pending gauge'
echo "$pending"
echo '# TYPE apt_upgrades_held gauge'
echo "apt_upgrades_held{host=\"$host\"} $held"
echo '# TYPE node_reboot_required gauge'
echo "node_reboot_required{host=\"$host\"} $reboot"
echo '# TYPE apt_package_cache_timestamp_seconds gauge'
echo "apt_package_cache_timestamp_seconds{host=\"$host\"} $cache"
} > "$tmp"
chmod 0644 "$tmp"
mv -f "$tmp" "$out"