From 58d53d3aafde858ab4dc0ab59fc60fa041a0f6b8 Mon Sep 17 00:00:00 2001 From: Thorsten Dralle Date: Mon, 27 Jan 2025 20:57:19 +0100 Subject: [PATCH 1/4] Added a node collector to export statistics to prometheus --- README.md | 61 ++++++ dockcheck.sh | 34 ++- grafana/grafana_dashboard.json | 382 +++++++++++++++++++++++++++++++++ grafana/grafana_dashboard.png | Bin 0 -> 51198 bytes 4 files changed, 476 insertions(+), 1 deletion(-) create mode 100644 grafana/grafana_dashboard.json create mode 100644 grafana/grafana_dashboard.png diff --git a/README.md b/README.md index 377b6be..1627db3 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Example: dockcheck.sh -y -d 10 -e nextcloud,heimdall Options:" -a|y Automatic updates, without interaction. +-c Exports metrics as prom file for the prometheus node_exporter. Provide the collector textfile directory. -d N Only update to new images that are N+ days old. Lists too recent with +prefix and age. 2xSlower. -e X Exclude containers, separated by comma. -f Force stack restart after update. Caution: restarts once for every updated container within stack. @@ -135,6 +136,66 @@ nginx -> https://github.com/docker-library/official-images/blob/master/library ``` The `urls.list` file is just an example and I'd gladly see that people contribute back when they add their preferred URLs to their lists. +## Prometheus +Dockcheck is capable to export metrics to prometheus via the text file collector provided by the node_exporter. In order to do so the -c flag has to be specified followed by the file path that is configured in the text file collector of the node_exporter. A simple cron job can be configured to export these metrics on a regular interval as shown in the sample below: + +``` +0 1 * * * /root/dockcheck.sh -n -c /var/lib/node_exporter/textfile_collector +``` + +The following metrics are exported to prometheus + +``` +# HELP dockcheck_images_analyzed Docker images that have been analyzed +# TYPE dockcheck_images_analyzed gauge +dockcheck_images_analyzed 22 +# HELP dockcheck_images_outdated Docker images that are outdated +# TYPE dockcheck_images_outdated gauge +dockcheck_images_outdated 7 +# HELP dockcheck_images_latest Docker images that are outdated +# TYPE dockcheck_images_latest gauge +dockcheck_images_latest 14 +# HELP dockcheck_images_error Docker images with analysis errors +# TYPE dockcheck_images_error gauge +dockcheck_images_error 1 +# HELP dockcheck_images_analyze_timestamp_seconds Last dockercheck run time +# TYPE dockcheck_images_analyze_timestamp_seconds gauge +dockcheck_images_analyze_timestamp_seconds 1737924029 +``` + +Once those metrics are exported they can be used to define alarms as shown below + +``` +- alert: dockcheck_images_outdated + expr: sum by(instance) (dockcheck_images_outdated) > 0 + for: 15s + labels: + severity: warning + annotations: + summary: "{{ $labels.instance }} has {{ $value }} outdated docker images." + description: "{{ $labels.instance }} has {{ $value }} outdated docker images." +- alert: dockcheck_images_error + expr: sum by(instance) (dockcheck_images_error) > 0 + for: 15s + labels: + severity: warning + annotations: + summary: "{{ $labels.instance }} has {{ $value }} docker images having an error." + description: "{{ $labels.instance }} has {{ $value }} docker images having an error." +- alert: dockercheck_image_last_analyze + expr: (time() - dockcheck_images_analyze_timestamp_seconds) > (3600 * 24 * 3) + for: 15s + labels: + severity: warning + annotations: + summary: "{{ $labels.instance }} has not updated the dockcheck statistics for more than 3 days." + description: "{{ $labels.instance }} has not updated the dockcheck statistics for more than 3 days." +``` + +There is a reference Grafana dashboard in [grafana/grafana_dashboard.json](./grafana/grafana_dashboard.json). + +![](./grafana/grafana_dashboard.png) + ## :bookmark: Labels Optionally add labels to compose-files. Currently these are the usable labels: ``` diff --git a/dockcheck.sh b/dockcheck.sh index 7ab7280..d182da8 100755 --- a/dockcheck.sh +++ b/dockcheck.sh @@ -20,6 +20,7 @@ Help() { echo echo "Options:" echo "-a|y Automatic updates, without interaction." + echo "-c Exports metrics as prom file for the prometheus node_exporter. Provide the collector textfile directory." echo "-d N Only update to new images that are N+ days old. Lists too recent with +prefix and age. 2xSlower." echo "-e X Exclude containers, separated by comma." echo "-f Force stack restart after update. Caution: restarts once for every updated container within stack." @@ -47,9 +48,11 @@ c_reset="\033[0m" Timeout=10 Stopped="" -while getopts "aynpfrhlisvme:d:t:" options; do +while getopts "aynpfrhlisvmc:e:d:t:" options; do case "${options}" in a|y) AutoUp="yes" ;; + c) CollectorTextFileDirectory="${OPTARG}" + if ! [[ -d $CollectorTextFileDirectory ]] ; then { printf "The directory (%s) does not exist.\n" "${CollectorTextFileDirectory}" ; exit 2; } fi ;; n) AutoUp="no" ;; r) DRunUp="yes" ;; p) AutoPrune="yes" ;; @@ -309,6 +312,35 @@ NoUpdates=($(sort <<<"${NoUpdates[*]}")) GotUpdates=($(sort <<<"${GotUpdates[*]}")) unset IFS +if [ -n "$CollectorTextFileDirectory" ] ; then + checkedImages=$((${#NoUpdates[@]} + ${#GotUpdates[@]} + ${#GotErrors[@]})) + checkTimestamp=$(date +%s) + + promFileContent=() + promFileContent+=("# HELP dockcheck_images_analyzed Docker images that have been analyzed") + promFileContent+=("# TYPE dockcheck_images_analyzed gauge") + promFileContent+=("dockcheck_images_analyzed $checkedImages") + + promFileContent+=("# HELP dockcheck_images_outdated Docker images that are outdated") + promFileContent+=("# TYPE dockcheck_images_outdated gauge") + promFileContent+=("dockcheck_images_outdated ${#GotUpdates[@]}") + + promFileContent+=("# HELP dockcheck_images_latest Docker images that are outdated") + promFileContent+=("# TYPE dockcheck_images_latest gauge") + promFileContent+=("dockcheck_images_latest ${#NoUpdates[@]}") + + promFileContent+=("# HELP dockcheck_images_error Docker images with analysis errors") + promFileContent+=("# TYPE dockcheck_images_error gauge") + promFileContent+=("dockcheck_images_error ${#GotErrors[@]}") + + promFileContent+=("# HELP dockcheck_images_analyze_timestamp_seconds Last dockercheck run time") + promFileContent+=("# TYPE dockcheck_images_analyze_timestamp_seconds gauge") + promFileContent+=("dockcheck_images_analyze_timestamp_seconds $checkTimestamp") + + printf "%s\n" "${promFileContent[@]}" > "$CollectorTextFileDirectory/dockcheck_info.prom\$\$" + mv -f "$CollectorTextFileDirectory/dockcheck_info.prom\$\$" "$CollectorTextFileDirectory/dockcheck_info.prom" +fi + # Define how many updates are available UpdCount="${#GotUpdates[@]}" diff --git a/grafana/grafana_dashboard.json b/grafana/grafana_dashboard.json new file mode 100644 index 0000000..ad308bb --- /dev/null +++ b/grafana/grafana_dashboard.json @@ -0,0 +1,382 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "11.4.0" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "last_analyze_timestamp" + }, + "properties": [ + { + "id": "unit", + "value": "dateTimeAsIso" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "last_analyze_since" + }, + "properties": [ + { + "id": "unit", + "value": "s" + }, + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 259200 + } + ] + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "images_outdated" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "images_error" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + } + } + ] + } + ] + }, + "gridPos": { + "h": 14, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 1, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": "sum by(instance) (dockcheck_images_analyzed)", + "format": "table", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": true, + "interval": "", + "legendFormat": "{{instance}}", + "range": false, + "refId": "dockcheck_images_analyzed", + "useBackend": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + } + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": "sum by(instance) (dockcheck_images_outdated)", + "format": "table", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": true, + "legendFormat": "{{instance}}", + "range": false, + "refId": "dockcheck_images_outdated", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": "sum by(instance) (dockcheck_images_latest)", + "format": "table", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": true, + "legendFormat": "{{instance}}", + "range": false, + "refId": "dockcheck_images_latest", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum by(instance) (dockcheck_images_error)", + "format": "table", + "hide": false, + "instant": true, + "legendFormat": "{{instance}}", + "range": false, + "refId": "dockcheck_images_error" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "dockcheck_images_analyze_timestamp_seconds * 1000", + "format": "table", + "hide": false, + "instant": true, + "legendFormat": "{{instance}}", + "range": false, + "refId": "dockcheck_images_analyze_timestamp_seconds" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "time() - dockcheck_images_analyze_timestamp_seconds", + "format": "table", + "hide": false, + "instant": true, + "legendFormat": "{{instance}}", + "range": false, + "refId": "dockcheck_images_last_analyze" + } + ], + "title": "Dockcheck Status", + "transformations": [ + { + "id": "merge", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "__name__": true, + "job": true + }, + "includeByName": {}, + "indexByName": { + "Time": 0, + "Value #dockcheck_images_analyze_timestamp_seconds": 2, + "Value #dockcheck_images_analyzed": 4, + "Value #dockcheck_images_error": 7, + "Value #dockcheck_images_last_analyze": 3, + "Value #dockcheck_images_latest": 5, + "Value #dockcheck_images_outdated": 6, + "instance": 1, + "job": 8 + }, + "renameByName": { + "Value #A": "analyze_timestamp", + "Value #dockcheck_images_analyze_timestamp_seconds": "last_analyze_timestamp", + "Value #dockcheck_images_analyzed": "images_analyzed", + "Value #dockcheck_images_error": "images_error", + "Value #dockcheck_images_last_analyze": "last_analyze_since", + "Value #dockcheck_images_latest": "images_latest", + "Value #dockcheck_images_outdated": "images_outdated" + } + } + } + ], + "type": "table" + } + ], + "schemaVersion": 40, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Dockcheck Status", + "uid": "feb4pv3kv1hxca", + "version": 17, + "weekStart": "" +} \ No newline at end of file diff --git a/grafana/grafana_dashboard.png b/grafana/grafana_dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..c3878df7814c6a276155436c5ddb7aea5fe81f69 GIT binary patch literal 51198 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 Date: Fri, 31 Jan 2025 23:52:10 +0100 Subject: [PATCH 2/4] restructure, moved to addon-subdir and split code to sourced snippet --- README.md | 63 ++---------------- addons/prometheus/README_prom.md | 63 ++++++++++++++++++ .../grafana}/grafana_dashboard.json | 0 .../prometheus/grafana}/grafana_dashboard.png | Bin addons/prometheus/prometheus_collector.sh | 28 ++++++++ dockcheck.sh | 32 ++------- 6 files changed, 102 insertions(+), 84 deletions(-) create mode 100644 addons/prometheus/README_prom.md rename {grafana => addons/prometheus/grafana}/grafana_dashboard.json (100%) rename {grafana => addons/prometheus/grafana}/grafana_dashboard.png (100%) create mode 100644 addons/prometheus/prometheus_collector.sh diff --git a/README.md b/README.md index bc723ac..d3769c2 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ ___ ## :bell: Changelog +- **v0.5.4**: Added support for a Prometheus+node_exporter metric collection through a file collector. - **v0.5.3**: Local image check changed (use imageId instead of name) and Gotify-template fixed (whale icon removed). - **v0.5.2.1**: Rewrite of dependency downloads, jq can be installed with package manager or static binary. - **v0.5.1**: DEPENDENCY WARNING: now requires **jq**. + Upstreaming changes from [sudo-kraken/podcheck](https://github.com/sudo-kraken/podcheck) @@ -136,65 +137,15 @@ nginx -> https://github.com/docker-library/official-images/blob/master/library ``` The `urls.list` file is just an example and I'd gladly see that people contribute back when they add their preferred URLs to their lists. -## Prometheus -Dockcheck is capable to export metrics to prometheus via the text file collector provided by the node_exporter. In order to do so the -c flag has to be specified followed by the file path that is configured in the text file collector of the node_exporter. A simple cron job can be configured to export these metrics on a regular interval as shown in the sample below: - +## [Prometheus](https://github.com/prometheus/prometheus) and [node_exporter](https://github.com/prometheus/node_exporter) +Dockcheck can be used together with Prometheus and node_exporter to export metrics via the file collector, scheduled with cron or likely. +This is done with the `-c` option, like this: ``` -0 1 * * * /root/dockcheck.sh -n -c /var/lib/node_exporter/textfile_collector +dockcheck.sh -c /path/to/exporter/directory ``` + +See the [README_prom.md](./addons/prometheus/README_prom.md) for more detailed information on how to set it up! -The following metrics are exported to prometheus - -``` -# HELP dockcheck_images_analyzed Docker images that have been analyzed -# TYPE dockcheck_images_analyzed gauge -dockcheck_images_analyzed 22 -# HELP dockcheck_images_outdated Docker images that are outdated -# TYPE dockcheck_images_outdated gauge -dockcheck_images_outdated 7 -# HELP dockcheck_images_latest Docker images that are outdated -# TYPE dockcheck_images_latest gauge -dockcheck_images_latest 14 -# HELP dockcheck_images_error Docker images with analysis errors -# TYPE dockcheck_images_error gauge -dockcheck_images_error 1 -# HELP dockcheck_images_analyze_timestamp_seconds Last dockercheck run time -# TYPE dockcheck_images_analyze_timestamp_seconds gauge -dockcheck_images_analyze_timestamp_seconds 1737924029 -``` - -Once those metrics are exported they can be used to define alarms as shown below - -``` -- alert: dockcheck_images_outdated - expr: sum by(instance) (dockcheck_images_outdated) > 0 - for: 15s - labels: - severity: warning - annotations: - summary: "{{ $labels.instance }} has {{ $value }} outdated docker images." - description: "{{ $labels.instance }} has {{ $value }} outdated docker images." -- alert: dockcheck_images_error - expr: sum by(instance) (dockcheck_images_error) > 0 - for: 15s - labels: - severity: warning - annotations: - summary: "{{ $labels.instance }} has {{ $value }} docker images having an error." - description: "{{ $labels.instance }} has {{ $value }} docker images having an error." -- alert: dockercheck_image_last_analyze - expr: (time() - dockcheck_images_analyze_timestamp_seconds) > (3600 * 24 * 3) - for: 15s - labels: - severity: warning - annotations: - summary: "{{ $labels.instance }} has not updated the dockcheck statistics for more than 3 days." - description: "{{ $labels.instance }} has not updated the dockcheck statistics for more than 3 days." -``` - -There is a reference Grafana dashboard in [grafana/grafana_dashboard.json](./grafana/grafana_dashboard.json). - -![](./grafana/grafana_dashboard.png) ## :bookmark: Labels Optionally add labels to compose-files. Currently these are the usable labels: diff --git a/addons/prometheus/README_prom.md b/addons/prometheus/README_prom.md new file mode 100644 index 0000000..6377e50 --- /dev/null +++ b/addons/prometheus/README_prom.md @@ -0,0 +1,63 @@ +## [Prometheus](https://github.com/prometheus/prometheus) and [node_exporter](https://github.com/prometheus/node_exporter) +Dockcheck is capable to export metrics to prometheus via the text file collector provided by the node_exporter. +In order to do so the -c flag has to be specified followed by the file path that is configured in the text file collector of the node_exporter. +A simple cron job can be configured to export these metrics on a regular interval as shown in the sample below: + +``` +0 1 * * * /root/dockcheck.sh -n -c /var/lib/node_exporter/textfile_collector +``` + +The following metrics are exported to prometheus + +``` +# HELP dockcheck_images_analyzed Docker images that have been analyzed +# TYPE dockcheck_images_analyzed gauge +dockcheck_images_analyzed 22 +# HELP dockcheck_images_outdated Docker images that are outdated +# TYPE dockcheck_images_outdated gauge +dockcheck_images_outdated 7 +# HELP dockcheck_images_latest Docker images that are outdated +# TYPE dockcheck_images_latest gauge +dockcheck_images_latest 14 +# HELP dockcheck_images_error Docker images with analysis errors +# TYPE dockcheck_images_error gauge +dockcheck_images_error 1 +# HELP dockcheck_images_analyze_timestamp_seconds Last dockercheck run time +# TYPE dockcheck_images_analyze_timestamp_seconds gauge +dockcheck_images_analyze_timestamp_seconds 1737924029 +``` + +Once those metrics are exported they can be used to define alarms as shown below + +``` +- alert: dockcheck_images_outdated + expr: sum by(instance) (dockcheck_images_outdated) > 0 + for: 15s + labels: + severity: warning + annotations: + summary: "{{ $labels.instance }} has {{ $value }} outdated docker images." + description: "{{ $labels.instance }} has {{ $value }} outdated docker images." +- alert: dockcheck_images_error + expr: sum by(instance) (dockcheck_images_error) > 0 + for: 15s + labels: + severity: warning + annotations: + summary: "{{ $labels.instance }} has {{ $value }} docker images having an error." + description: "{{ $labels.instance }} has {{ $value }} docker images having an error." +- alert: dockercheck_image_last_analyze + expr: (time() - dockcheck_images_analyze_timestamp_seconds) > (3600 * 24 * 3) + for: 15s + labels: + severity: warning + annotations: + summary: "{{ $labels.instance }} has not updated the dockcheck statistics for more than 3 days." + description: "{{ $labels.instance }} has not updated the dockcheck statistics for more than 3 days." +``` + +There is a reference Grafana dashboard in [grafana/grafana_dashboard.json](./grafana/grafana_dashboard.json). + +![](./grafana/grafana_dashboard.png) + + diff --git a/grafana/grafana_dashboard.json b/addons/prometheus/grafana/grafana_dashboard.json similarity index 100% rename from grafana/grafana_dashboard.json rename to addons/prometheus/grafana/grafana_dashboard.json diff --git a/grafana/grafana_dashboard.png b/addons/prometheus/grafana/grafana_dashboard.png similarity index 100% rename from grafana/grafana_dashboard.png rename to addons/prometheus/grafana/grafana_dashboard.png diff --git a/addons/prometheus/prometheus_collector.sh b/addons/prometheus/prometheus_collector.sh new file mode 100644 index 0000000..c98af2b --- /dev/null +++ b/addons/prometheus/prometheus_collector.sh @@ -0,0 +1,28 @@ +prometheus_exporter() { + checkedImages=$(($1 + $2 + $3)) + checkTimestamp=$(date +%s) + + promFileContent=() + promFileContent+=("# HELP dockcheck_images_analyzed Docker images that have been analyzed") + promFileContent+=("# TYPE dockcheck_images_analyzed gauge") + promFileContent+=("dockcheck_images_analyzed $checkedImages") + + promFileContent+=("# HELP dockcheck_images_outdated Docker images that are outdated") + promFileContent+=("# TYPE dockcheck_images_outdated gauge") + promFileContent+=("dockcheck_images_outdated ${#GotUpdates[@]}") + + promFileContent+=("# HELP dockcheck_images_latest Docker images that are outdated") + promFileContent+=("# TYPE dockcheck_images_latest gauge") + promFileContent+=("dockcheck_images_latest ${#NoUpdates[@]}") + + promFileContent+=("# HELP dockcheck_images_error Docker images with analysis errors") + promFileContent+=("# TYPE dockcheck_images_error gauge") + promFileContent+=("dockcheck_images_error ${#GotErrors[@]}") + + promFileContent+=("# HELP dockcheck_images_analyze_timestamp_seconds Last dockercheck run time") + promFileContent+=("# TYPE dockcheck_images_analyze_timestamp_seconds gauge") + promFileContent+=("dockcheck_images_analyze_timestamp_seconds $checkTimestamp") + + printf "%s\n" "${promFileContent[@]}" > "$CollectorTextFileDirectory/dockcheck_info.prom\$\$" + mv -f "$CollectorTextFileDirectory/dockcheck_info.prom\$\$" "$CollectorTextFileDirectory/dockcheck_info.prom" +} diff --git a/dockcheck.sh b/dockcheck.sh index 1cf943b..f42febf 100755 --- a/dockcheck.sh +++ b/dockcheck.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -VERSION="v0.5.3.0" -### ChangeNotes: Bugfixes - local image check changed, Gotify-template fixed +VERSION="v0.5.4.0" +### ChangeNotes: Added support for a Prometheus+node_exporter metric collection through a file collector. Github="https://github.com/mag37/dockcheck" RawUrl="https://raw.githubusercontent.com/mag37/dockcheck/main/dockcheck.sh" @@ -313,33 +313,9 @@ NoUpdates=($(sort <<<"${NoUpdates[*]}")) GotUpdates=($(sort <<<"${GotUpdates[*]}")) unset IFS +# Run the prometheus exporter function if [ -n "$CollectorTextFileDirectory" ] ; then - checkedImages=$((${#NoUpdates[@]} + ${#GotUpdates[@]} + ${#GotErrors[@]})) - checkTimestamp=$(date +%s) - - promFileContent=() - promFileContent+=("# HELP dockcheck_images_analyzed Docker images that have been analyzed") - promFileContent+=("# TYPE dockcheck_images_analyzed gauge") - promFileContent+=("dockcheck_images_analyzed $checkedImages") - - promFileContent+=("# HELP dockcheck_images_outdated Docker images that are outdated") - promFileContent+=("# TYPE dockcheck_images_outdated gauge") - promFileContent+=("dockcheck_images_outdated ${#GotUpdates[@]}") - - promFileContent+=("# HELP dockcheck_images_latest Docker images that are outdated") - promFileContent+=("# TYPE dockcheck_images_latest gauge") - promFileContent+=("dockcheck_images_latest ${#NoUpdates[@]}") - - promFileContent+=("# HELP dockcheck_images_error Docker images with analysis errors") - promFileContent+=("# TYPE dockcheck_images_error gauge") - promFileContent+=("dockcheck_images_error ${#GotErrors[@]}") - - promFileContent+=("# HELP dockcheck_images_analyze_timestamp_seconds Last dockercheck run time") - promFileContent+=("# TYPE dockcheck_images_analyze_timestamp_seconds gauge") - promFileContent+=("dockcheck_images_analyze_timestamp_seconds $checkTimestamp") - - printf "%s\n" "${promFileContent[@]}" > "$CollectorTextFileDirectory/dockcheck_info.prom\$\$" - mv -f "$CollectorTextFileDirectory/dockcheck_info.prom\$\$" "$CollectorTextFileDirectory/dockcheck_info.prom" + source "$ScriptWorkDir"/addons/prometheus/prometheus_collector.sh && prometheus_exporter ${#NoUpdates[@]} ${#GotUpdates[@]} ${#GotError[@]} fi # Define how many updates are available From 43307350cac86cbd741afef75ee84be45233d484 Mon Sep 17 00:00:00 2001 From: mag37 Date: Fri, 31 Jan 2025 23:58:05 +0100 Subject: [PATCH 3/4] formatting of header --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d3769c2..a4f3bb9 100644 --- a/README.md +++ b/README.md @@ -137,8 +137,8 @@ nginx -> https://github.com/docker-library/official-images/blob/master/library ``` The `urls.list` file is just an example and I'd gladly see that people contribute back when they add their preferred URLs to their lists. -## [Prometheus](https://github.com/prometheus/prometheus) and [node_exporter](https://github.com/prometheus/node_exporter) -Dockcheck can be used together with Prometheus and node_exporter to export metrics via the file collector, scheduled with cron or likely. +## :chart_with_upwards_trend: Prometheus and node_exporter +Dockcheck can be used together with [Prometheus](https://github.com/prometheus/prometheus) and [node_exporter](https://github.com/prometheus/node_exporter) to export metrics via the file collector, scheduled with cron or likely. This is done with the `-c` option, like this: ``` dockcheck.sh -c /path/to/exporter/directory From 9ed2a0bad082ae067476df75955acb00fe30f348 Mon Sep 17 00:00:00 2001 From: mag37 Date: Fri, 7 Feb 2025 19:27:14 +0100 Subject: [PATCH 4/4] finalized prometheus addon --- README.md | 35 ++++++++++--------- .../prometheus/{README_prom.md => README.md} | 6 ++-- 2 files changed, 20 insertions(+), 21 deletions(-) rename addons/prometheus/{README_prom.md => README.md} (97%) diff --git a/README.md b/README.md index a4f3bb9..5251e02 100644 --- a/README.md +++ b/README.md @@ -10,22 +10,23 @@ Github Sponsor

-

CLI tool to automate docker image updates.
No pre-pull, selective, optional notifications and prune when done.

-

Now with simple notification integrations!

-

With features like excluding specific containers, custom container labels, auto-prune when done and more.

-

Also see the fresh Podman fork sudo-kraken/podcheck!

+

CLI tool to automate docker image updates or notifying when updates are available.

+

Features:

+

selective updates, exclude containers, custom labels, notification plugins, prune when done and more.

+ +

For Podman - see the fork sudo-kraken/podcheck!

___ ## :bell: Changelog -- **v0.5.4**: Added support for a Prometheus+node_exporter metric collection through a file collector. -- **v0.5.3**: Local image check changed (use imageId instead of name) and Gotify-template fixed (whale icon removed). +- **v0.5.4.0**: Added support for a Prometheus+node_exporter metric collection through a file collector. +- **v0.5.3.0**: Local image check changed (use imageId instead of name) and Gotify-template fixed (whale icon removed). - **v0.5.2.1**: Rewrite of dependency downloads, jq can be installed with package manager or static binary. - **v0.5.1**: DEPENDENCY WARNING: now requires **jq**. + Upstreaming changes from [sudo-kraken/podcheck](https://github.com/sudo-kraken/podcheck) - **v0.5.0**: Rewritten notify logic - all templates are adjusted and should be migrated! - Copy the custom settings from your current template to the new version of the same template. - Look into, copy and customize the `urls.list` file if that's of interest. - - Other changes: + - Other changes: - Added Discord notify template. - Verbosity changed of `regctl`. - **v0.4.9**: Added a function to enrich the notify-message with release note URLs. See [Release notes addon](https://github.com/mag37/dockcheck#date-release-notes-addon-to-notifications) @@ -42,7 +43,7 @@ Example: dockcheck.sh -y -d 10 -e nextcloud,heimdall Options:" -a|y Automatic updates, without interaction. --c Exports metrics as prom file for the prometheus node_exporter. Provide the collector textfile directory. +-c D Exports metrics as prom file for the prometheus node_exporter. Provide the collector textfile directory. -d N Only update to new images that are N+ days old. Lists too recent with +prefix and age. 2xSlower. -e X Exclude containers, separated by comma. -f Force stack restart after update. Caution: restarts once for every updated container within stack. @@ -82,7 +83,7 @@ ___ ## :nut_and_bolt: Dependencies - Running docker (duh) and compose, either standalone or plugin. (see [Podman fork](https://github.com/sudo-kraken/podcheck) - Bash shell or compatible shell of at least v4.3 -- [jq](https://github.com/jqlang/jq) +- [jq](https://github.com/jqlang/jq) - User will be prompted to install with package manager or download static binary. - [regclient/regctl](https://github.com/regclient/regclient) (Licensed under [Apache-2.0 License](http://www.apache.org/licenses/LICENSE-2.0)) - User will be prompted to download `regctl` if not in `PATH` or `PWD`. @@ -125,8 +126,8 @@ Further additions are welcome - suggestions or PR! Initiated and first contributed by [yoyoma2](https://github.com/yoyoma2). ### :date: Release notes addon to Notifications -There's a function to use a lookup-file to add release note URL's to the notification message. -Copy the notify_templates/`urls.list` file to the script directory, it will be used automatically if it's there. Modify it as necessary, the names of interest in the left column needs to match your container names. +There's a function to use a lookup-file to add release note URL's to the notification message. +Copy the notify_templates/`urls.list` file to the script directory, it will be used automatically if it's there. Modify it as necessary, the names of interest in the left column needs to match your container names. The output of the notification will look something like this: ``` Containers on hostname with updates available: @@ -138,14 +139,15 @@ nginx -> https://github.com/docker-library/official-images/blob/master/library The `urls.list` file is just an example and I'd gladly see that people contribute back when they add their preferred URLs to their lists. ## :chart_with_upwards_trend: Prometheus and node_exporter -Dockcheck can be used together with [Prometheus](https://github.com/prometheus/prometheus) and [node_exporter](https://github.com/prometheus/node_exporter) to export metrics via the file collector, scheduled with cron or likely. -This is done with the `-c` option, like this: +Dockcheck can be used together with [Prometheus](https://github.com/prometheus/prometheus) and [node_exporter](https://github.com/prometheus/node_exporter) to export metrics via the file collector, scheduled with cron or likely. +This is done with the `-c` option, like this: ``` dockcheck.sh -c /path/to/exporter/directory ``` - -See the [README_prom.md](./addons/prometheus/README_prom.md) for more detailed information on how to set it up! +See the [README_prom.md](./addons/prometheus/README.md) for more detailed information on how to set it up! + +Contributed by [tdralle](https://github.com/tdralle). ## :bookmark: Labels Optionally add labels to compose-files. Currently these are the usable labels: @@ -198,7 +200,7 @@ function dchk { Containers need to be manually stopped, removed and created again to run on the new image. ## :wrench: Debugging -If you hit issues, you could check the output of the `extras/errorCheck.sh` script for clues. +If you hit issues, you could check the output of the `extras/errorCheck.sh` script for clues. Another option is to run the main script with debugging in a subshell `bash -x dockcheck.sh` - if there's a particular container/image that's causing issues you can filter for just that through `bash -x dockcheck.sh nginx`. ## :scroll: License @@ -211,4 +213,3 @@ dockcheck is created and released under the [GNU GPL v3.0](https://www.gnu.org/l ___ ### :floppy_disk: The [story](https://mag37.org/posts/project_dockcheck/) behind it. 1 year in retrospect. - diff --git a/addons/prometheus/README_prom.md b/addons/prometheus/README.md similarity index 97% rename from addons/prometheus/README_prom.md rename to addons/prometheus/README.md index 6377e50..0bb84b8 100644 --- a/addons/prometheus/README_prom.md +++ b/addons/prometheus/README.md @@ -1,6 +1,6 @@ ## [Prometheus](https://github.com/prometheus/prometheus) and [node_exporter](https://github.com/prometheus/node_exporter) -Dockcheck is capable to export metrics to prometheus via the text file collector provided by the node_exporter. -In order to do so the -c flag has to be specified followed by the file path that is configured in the text file collector of the node_exporter. +Dockcheck is capable to export metrics to prometheus via the text file collector provided by the node_exporter. +In order to do so the -c flag has to be specified followed by the file path that is configured in the text file collector of the node_exporter. A simple cron job can be configured to export these metrics on a regular interval as shown in the sample below: ``` @@ -59,5 +59,3 @@ Once those metrics are exported they can be used to define alarms as shown below There is a reference Grafana dashboard in [grafana/grafana_dashboard.json](./grafana/grafana_dashboard.json). ![](./grafana/grafana_dashboard.png) - -