diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix index 1d06893bf1d5..b42362429052 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters.nix @@ -58,6 +58,7 @@ let "nut" "openldap" "openvpn" + "pgbouncer" "php-fpm" "pihole" "postfix" @@ -312,6 +313,25 @@ in Please specify either 'services.prometheus.exporters.nextcloud.passwordFile' or 'services.prometheus.exporters.nextcloud.tokenFile' ''; + } { + assertion = cfg.pgbouncer.enable -> ( + (cfg.pgbouncer.connectionStringFile != null || cfg.pgbouncer.connectionString != "") + ); + message = '' + PgBouncer exporter needs either connectionStringFile or connectionString configured" + ''; + } { + assertion = cfg.pgbouncer.enable -> ( + config.services.pgbouncer.ignoreStartupParameters != null && builtins.match ".*extra_float_digits.*" config.services.pgbouncer.ignoreStartupParameters != null + ); + message = '' + Prometheus PgBouncer exporter requires including `extra_float_digits` in services.pgbouncer.ignoreStartupParameters + + Example: + services.pgbouncer.ignoreStartupParameters = extra_float_digits; + + See https://github.com/prometheus-community/pgbouncer_exporter#pgbouncer-configuration + ''; } { assertion = cfg.sql.enable -> ( (cfg.sql.configFile == null) != (cfg.sql.configuration == null) @@ -350,12 +370,24 @@ in `openFirewall' is set to `true'! ''; })) ++ config.services.prometheus.exporters.assertions; - warnings = [(mkIf (config.services.prometheus.exporters.idrac.enable && config.services.prometheus.exporters.idrac.configurationPath != null) '' - Configuration file in `services.prometheus.exporters.idrac.configurationPath` may override - `services.prometheus.exporters.idrac.listenAddress` and/or `services.prometheus.exporters.idrac.port`. - Consider using `services.prometheus.exporters.idrac.configuration` instead. - '' - )] ++ config.services.prometheus.exporters.warnings; + warnings = [ + (mkIf (config.services.prometheus.exporters.idrac.enable && config.services.prometheus.exporters.idrac.configurationPath != null) '' + Configuration file in `services.prometheus.exporters.idrac.configurationPath` may override + `services.prometheus.exporters.idrac.listenAddress` and/or `services.prometheus.exporters.idrac.port`. + Consider using `services.prometheus.exporters.idrac.configuration` instead. + '' + ) + (mkIf + (cfg.pgbouncer.enable && cfg.pgbouncer.connectionString != "") '' + config.services.prometheus.exporters.pgbouncer.connectionString is insecure. Use connectionStringFile instead. + '' + ) + (mkIf + (cfg.pgbouncer.enable && config.services.pgbouncer.authType != "any") '' + Admin user (with password or passwordless) MUST exist in the services.pgbouncer.authFile if authType other than any is used. + '' + ) + ] ++ config.services.prometheus.exporters.warnings; }] ++ [(mkIf config.services.minio.enable { services.prometheus.exporters.minio.minioAddress = mkDefault "http://localhost:9000"; services.prometheus.exporters.minio.minioAccessKey = mkDefault config.services.minio.accessKey; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/pgbouncer.nix b/nixos/modules/services/monitoring/prometheus/exporters/pgbouncer.nix new file mode 100644 index 000000000000..9e55cadae523 --- /dev/null +++ b/nixos/modules/services/monitoring/prometheus/exporters/pgbouncer.nix @@ -0,0 +1,145 @@ +{ config, lib, pkgs, options }: + +with lib; + +let + cfg = config.services.prometheus.exporters.pgbouncer; +in +{ + port = 9127; + extraOpts = { + + telemetryPath = mkOption { + type = types.str; + default = "/metrics"; + description = lib.mdDoc '' + Path under which to expose metrics. + ''; + }; + + connectionString = mkOption { + type = types.str; + default = ""; + example = "postgres://admin:@localhost:6432/pgbouncer?sslmode=require"; + description = lib.mdDoc '' + Connection string for accessing pgBouncer. + + NOTE: You MUST keep pgbouncer as database name (special internal db)!!! + + NOTE: Admin user (with password or passwordless) MUST exist + in the services.pgbouncer.authFile if authType other than any is used. + + WARNING: this secret is stored in the world-readable Nix store! + Use {option}`connectionStringFile` instead. + ''; + }; + + connectionStringFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/keys/pgBouncer-connection-string"; + description = lib.mdDoc '' + File that contains pgBouncer connection string in format: + postgres://admin:@localhost:6432/pgbouncer?sslmode=require + + NOTE: You MUST keep pgbouncer as database name (special internal db)!!! + + NOTE: Admin user (with password or passwordless) MUST exist + in the services.pgbouncer.authFile if authType other than any is used. + + {option}`connectionStringFile` takes precedence over {option}`connectionString` + ''; + }; + + pidFile = mkOption { + type = types.nullOr types.str; + default = null; + description = lib.mdDoc '' + Path to PgBouncer pid file. + + If provided, the standard process metrics get exported for the PgBouncer + process, prefixed with 'pgbouncer_process_...'. The pgbouncer_process exporter + needs to have read access to files owned by the PgBouncer process. Depends on + the availability of /proc. + + https://prometheus.io/docs/instrumenting/writing_clientlibs/#process-metrics. + + ''; + }; + + webSystemdSocket = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Use systemd socket activation listeners instead of port listeners (Linux only). + ''; + }; + + logLevel = mkOption { + type = types.enum ["debug" "info" "warn" "error" ]; + default = "info"; + description = lib.mdDoc '' + Only log messages with the given severity or above. + ''; + }; + + logFormat = mkOption { + type = types.enum ["logfmt" "json"]; + default = "logfmt"; + description = lib.mdDoc '' + Output format of log messages. One of: [logfmt, json] + ''; + }; + + webConfigFile = mkOption { + type = types.nullOr types.path; + default = null; + description = lib.mdDoc '' + Path to configuration file that can enable TLS or authentication. + ''; + }; + + extraFlags = mkOption { + type = types.listOf types.str; + default = [ ]; + description = lib.mdDoc '' + Extra commandline options when launching Prometheus. + ''; + }; + + }; + + serviceOpts = { + after = [ "pgbouncer.service" ]; + serviceConfig = let + startScript = pkgs.writeShellScriptBin "pgbouncer-start" "${concatStringsSep " " ([ + "${pkgs.prometheus-pgbouncer-exporter}/bin/pgbouncer_exporter" + "--web.listen-address ${cfg.listenAddress}:${toString cfg.port}" + "--pgBouncer.connectionString ${if cfg.connectionStringFile != null then + "$(head -n1 ${cfg.connectionStringFile})" else "${escapeShellArg cfg.connectionString}"}" + ] + ++ optionals (cfg.telemetryPath != null) [ + "--web.telemetry-path ${escapeShellArg cfg.telemetryPath}" + ] + ++ optionals (cfg.pidFile != null) [ + "--pgBouncer.pid-file= ${escapeShellArg cfg.pidFile}" + ] + ++ optionals (cfg.logLevel != null) [ + "--log.level ${escapeShellArg cfg.logLevel}" + ] + ++ optionals (cfg.logFormat != null) [ + "--log.format ${escapeShellArg cfg.logFormat}" + ] + ++ optionals (cfg.webSystemdSocket != false) [ + "--web.systemd-socket ${escapeShellArg cfg.webSystemdSocket}" + ] + ++ optionals (cfg.webConfigFile != null) [ + "--web.config.file ${escapeShellArg cfg.webConfigFile}" + ] + ++ cfg.extraFlags)}"; + in + { + ExecStart = "${startScript}/bin/pgbouncer-start"; + }; + }; +} diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix index 7db7fdf13eb1..8369d6a497ac 100644 --- a/nixos/tests/prometheus-exporters.nix +++ b/nixos/tests/prometheus-exporters.nix @@ -966,6 +966,36 @@ let ''; }; + pgbouncer = { + exporterConfig = { + enable = true; + connectionString = "postgres://admin:@localhost:6432/pgbouncer?sslmode=disable"; + }; + + metricProvider = { + services.postgresql.enable = true; + services.pgbouncer = { + # https://github.com/prometheus-community/pgbouncer_exporter#pgbouncer-configuration + ignoreStartupParameters = "extra_float_digits"; + enable = true; + listenAddress = "*"; + databases = { postgres = "host=/run/postgresql/ port=5432 auth_user=postgres dbname=postgres"; }; + authType = "any"; + maxClientConn = 99; + }; + }; + exporterTest = '' + wait_for_unit("postgresql.service") + wait_for_unit("pgbouncer.service") + wait_for_unit("prometheus-pgbouncer-exporter.service") + wait_for_open_port(9127) + succeed("curl -sSf http://localhost:9127/metrics | grep 'pgbouncer_up 1'") + succeed( + "curl -sSf http://localhost:9127/metrics | grep 'pgbouncer_config_max_client_connections 99'" + ) + ''; + }; + php-fpm = { nodeName = "php_fpm"; exporterConfig = { diff --git a/pkgs/servers/monitoring/prometheus/pgbouncer-exporter.nix b/pkgs/servers/monitoring/prometheus/pgbouncer-exporter.nix new file mode 100644 index 000000000000..2eba38f65b1e --- /dev/null +++ b/pkgs/servers/monitoring/prometheus/pgbouncer-exporter.nix @@ -0,0 +1,23 @@ +{ lib, buildGoModule, fetchFromGitHub }: + +buildGoModule rec { + pname = "pgbouncer-exporter"; + version = "0.7.0"; + + src = fetchFromGitHub { + owner = "prometheus-community"; + repo = "pgbouncer_exporter"; + rev = "v${version}"; + sha256 = "sha256-2N8FaGk6AU39j4q22B2Om5E7BeR7iw9drl3PTOBO2kg="; + }; + + vendorSha256 = "sha256-2aaUlOokqYkjMpcM12mU+O+N09/mDPlIrJ4Z1iXJAyk="; + + meta = with lib; { + description = "Prometheus exporter for PgBouncer"; + homepage = "https://github.com/prometheus-community/pgbouncer_exporter"; + license = licenses.mit; + maintainers = with maintainers; [ _1000101 ]; + platforms = platforms.linux; + }; +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 600517900c39..4106ade2e09a 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -27339,6 +27339,7 @@ with pkgs; prometheus-nut-exporter = callPackage ../servers/monitoring/prometheus/nut-exporter.nix { }; prometheus-openldap-exporter = callPackage ../servers/monitoring/prometheus/openldap-exporter.nix { } ; prometheus-openvpn-exporter = callPackage ../servers/monitoring/prometheus/openvpn-exporter.nix { }; + prometheus-pgbouncer-exporter = callPackage ../servers/monitoring/prometheus/pgbouncer-exporter.nix { }; prometheus-php-fpm-exporter = callPackage ../servers/monitoring/prometheus/php-fpm-exporter.nix { }; prometheus-pihole-exporter = callPackage ../servers/monitoring/prometheus/pihole-exporter.nix { }; prometheus-postfix-exporter = callPackage ../servers/monitoring/prometheus/postfix-exporter.nix { };