Get some metrics and a dashboard for dovecot
Some checks failed
/ dev-shell (push) Successful in 4m8s
/ check (push) Failing after 7m12s
/ rust-packages (push) Successful in 7m7s
/ systems (push) Successful in 48m15s
/ terraform-providers (push) Successful in 13m33s

This commit is contained in:
Kaare Hoff Skovgaard 2025-08-02 00:34:01 +02:00
parent 28f4b34cd1
commit 8f6c428305
Signed by: khs
GPG key ID: C7D890804F01E9F0
5 changed files with 1353 additions and 9 deletions

View file

@ -81,6 +81,16 @@ let
]; ];
description = "dns"; description = "dns";
} }
{
direction = "out";
protocol = "udp";
port = 123;
destination_ips = [
"0.0.0.0/0"
"::/0"
];
description = "ntp";
}
{ {
direction = "out"; direction = "out";
protocol = "tcp"; protocol = "tcp";

View file

@ -1,9 +1,108 @@
let
dovecotPromPort = 9900;
in
{ {
config = { config = {
services.prometheus.exporters.postfix = { services.prometheus.exporters.postfix = {
enable = true; enable = true;
}; };
khscodes.infrastructure.vault-prometheus-sender.exporters.enabled = [ "postfix" ]; khscodes.infrastructure.vault-prometheus-sender.exporters.enabled = [
"postfix"
];
# From https://alfaexploit.com/en/posts/monitoring_dovecot_with_prometheus/
services.dovecot2.extraConfig = ''
##
## Statistics and metrics
##
# Dovecot supports gathering statistics from events.
# Currently there are no statistics logged by default, and therefore they must
# be explicitly added using the metric configuration blocks.
#
# Unlike old stats, the new statistics do not require any plugins loaded.
#
# See https://doc.dovecot.org/configuration_manual/stats/ for more details.
##
## Example metrics
##
metric auth_success {
filter = event=auth_request_finished AND success=yes
}
metric auth_failures {
filter = event=auth_request_finished AND NOT success=yes
}
metric imap_command {
filter = event=imap_command_finished
group_by = cmd_name tagged_reply_state
}
metric smtp_command {
filter = event=smtp_server_command_finished
group_by = cmd_name status_code duration:exponential:1:5:10
}
metric mail_delivery {
filter = event=mail_delivery_finished
group_by = duration:exponential:1:5:10
}
##
## Prometheus
##
# To allow access to statistics with Prometheus, enable http listener
# on stats process. Stats will be available on /metrics path.
#
# See https://doc.dovecot.org/configuration_manual/stats/openmetrics/ for more
# details.
service stats {
inet_listener http {
port = ${toString dovecotPromPort}
}
}
##
## Event exporting
##
# You can also export individual events.
#
# See https://doc.dovecot.org/configuration_manual/event_export/ for more
# details.
#event_exporter log {
# format = json
# format_args = time-rfc3339
# transport = log
#}
#
#metric imap_commands {
# exporter = log
# filter = event=imap_command_finished
#}
'';
khscodes.infrastructure.vault-prometheus-sender.exporters.external = [ "dovecot" ];
environment.etc."alloy/dovecot_prometheus.alloy" = {
text = ''
prometheus.scrape "dovecot_exporter" {
scrape_interval = "1m"
targets = [
{
"__address__" = "127.0.0.1:${toString dovecotPromPort}",
"instance" = constants.hostname,
"job" = "dovecot",
},
]
metrics_path = "/metrics"
forward_to = [otelcol.receiver.prometheus.default.receiver]
}
'';
};
}; };
} }

View file

@ -0,0 +1,10 @@
- name: Dovecot
rules:
- alert: DovecotDown
expr: >
dovecot_up{job="dovecot"} == 0
for: 10m
labels:
severity: critical
annotations:
summary: "Dovecot on {{ $labels.instance }} is down"

View file

@ -10,19 +10,58 @@ let
loki = config.services.loki; loki = config.services.loki;
prometheus = config.services.prometheus; prometheus = config.services.prometheus;
nginxExporterSrc = "${pkgs.prometheus-nginx-exporter.src}/grafana/dashboard.json"; nginxExporterSrc = "${pkgs.prometheus-nginx-exporter.src}/grafana/dashboard.json";
promDataSource = {
current = { };
includeAll = false;
label = "Datasource";
name = "DS_PROMETHEUS";
options = [ ];
query = "prometheus";
refresh = 1;
regex = "";
type = "datasource";
};
srcName = src: if lib.isPath src then builtins.baseNameOf src else src.name;
patchPrometheusTemplatingDataSource =
src:
let
name = srcName src;
in
pkgs.stdenvNoCC.mkDerivation {
inherit src;
name = "patched-${name}";
dontUnpack = true;
buildPhase = ''
cat ${src} | ${lib.getExe pkgs.jq} --raw-output --argjson src ${lib.escapeShellArg (builtins.toJSON promDataSource)} '.templating.list = [$src] + .templating.list' > ${lib.escapeShellArg name}
'';
installPhase = ''
mv ${lib.escapeShellArg name} $out
'';
};
postgresqlDashboard = pkgs.fetchurl { postgresqlDashboard = pkgs.fetchurl {
url = "https://grafana.com/api/dashboards/9628/revisions/8/download"; url = "https://grafana.com/api/dashboards/9628/revisions/8/download";
hash = "sha256-UhusNAZbyt7fJV/DhFUK4FKOmnTpG0R15YO2r+nDnMc="; hash = "sha256-UhusNAZbyt7fJV/DhFUK4FKOmnTpG0R15YO2r+nDnMc=";
name = "postgresql.json";
}; };
postfixDashboard = pkgs.fetchurl { postfixDashboard = patchPrometheusTemplatingDataSource (
pkgs.fetchurl {
url = "https://grafana.com/api/dashboards/10013/revisions/2/download"; url = "https://grafana.com/api/dashboards/10013/revisions/2/download";
hash = "sha256-SIKL1V+sJ5F7vPOwp/LuOjrGm8nCsscEX8LcLFMotfc="; hash = "sha256-SIKL1V+sJ5F7vPOwp/LuOjrGm8nCsscEX8LcLFMotfc=";
}; name = "postfix.json";
}
);
oauthCredentialFile = config.khscodes.infrastructure.kanidm-client-application.secretFile;
in in
{ {
imports = [ imports = [
"${inputs.self}/nix/profiles/nixos/khs-openstack-server.nix" "${inputs.self}/nix/profiles/nixos/khs-openstack-server.nix"
]; ];
systemd.services.grafana = {
unitConfig.ConditionPathExists = [
oauthCredentialFile
];
};
services.grafana = { services.grafana = {
enable = true; enable = true;
settings = { settings = {
@ -53,7 +92,7 @@ in
token_url = "https://login.kaareskovgaard.net/oauth2/token"; token_url = "https://login.kaareskovgaard.net/oauth2/token";
api_url = "https://login.kaareskovgaard.net/oauth2/openid/monitoring/userinfo"; api_url = "https://login.kaareskovgaard.net/oauth2/openid/monitoring/userinfo";
client_id = "monitoring"; client_id = "monitoring";
client_secret = "$__file{${config.khscodes.infrastructure.kanidm-client-application.secretFile}}"; client_secret = "$__file{${oauthCredentialFile}}";
scopes = "openid profile email"; scopes = "openid profile email";
use_pkce = true; use_pkce = true;
skip_org_role_sync = false; skip_org_role_sync = false;
@ -81,13 +120,13 @@ in
{ {
url = "http://${loki.configuration.server.http_listen_address}:${toString loki.configuration.server.http_listen_port}"; url = "http://${loki.configuration.server.http_listen_address}:${toString loki.configuration.server.http_listen_port}";
type = "loki"; type = "loki";
name = "Logs"; name = "Loki";
uid = "loki"; uid = "loki";
} }
{ {
url = "http://${prometheus.listenAddress}:${toString prometheus.port}"; url = "http://${prometheus.listenAddress}:${toString prometheus.port}";
type = "prometheus"; type = "prometheus";
name = "Metrics"; name = "Prometheus";
uid = "prometheus"; uid = "prometheus";
jsonData = { jsonData = {
manageAlerts = true; manageAlerts = true;
@ -99,6 +138,10 @@ in
name = "Node Exporter"; name = "Node Exporter";
options.path = ./grafana/dashboards/node_exporter; options.path = ./grafana/dashboards/node_exporter;
} }
{
name = "Dovecot";
options.path = patchPrometheusTemplatingDataSource ./grafana/dashboards/dovecot/dovecot_prometheus.json;
}
{ {
name = "Nginx"; name = "Nginx";
options.path = nginxExporterSrc; options.path = nginxExporterSrc;
@ -127,6 +170,7 @@ in
${builtins.readFile ./alerts/postfix.yaml} ${builtins.readFile ./alerts/postfix.yaml}
${builtins.readFile ./alerts/postgres.yaml} ${builtins.readFile ./alerts/postgres.yaml}
${builtins.readFile ./alerts/systemd.yaml} ${builtins.readFile ./alerts/systemd.yaml}
${builtins.readFile ./alerts/dovecot.yaml}
${import ./alerts/job_up.nix { inherit inputs lib; }} ${import ./alerts/job_up.nix { inherit inputs lib; }}
'' ''
]; ];