1{ config, pkgs, lib, ... }: 2 3with lib; 4 5let 6 cfg = config.services.prometheus; 7 promUser = "prometheus"; 8 promGroup = "prometheus"; 9 10 # Get a submodule without any embedded metadata: 11 _filter = x: filterAttrs (k: v: k != "_module") x; 12 13 # Pretty-print JSON to a file 14 writePrettyJSON = name: x: 15 pkgs.runCommand name { } '' 16 echo '${builtins.toJSON x}' | ${pkgs.jq}/bin/jq . > $out 17 ''; 18 19 # This becomes the main config file 20 promConfig = { 21 global = cfg.globalConfig; 22 rule_files = cfg.ruleFiles ++ [ 23 (pkgs.writeText "prometheus.rules" (concatStringsSep "\n" cfg.rules)) 24 ]; 25 scrape_configs = cfg.scrapeConfigs; 26 }; 27 28 generatedPrometheusYml = writePrettyJSON "prometheus.yml" promConfig; 29 30 prometheusYml = 31 if cfg.configText != null then 32 pkgs.writeText "prometheus.yml" cfg.configText 33 else generatedPrometheusYml; 34 35 cmdlineArgs = cfg.extraFlags ++ [ 36 "-storage.local.path=${cfg.dataDir}/metrics" 37 "-config.file=${prometheusYml}" 38 "-web.listen-address=${cfg.listenAddress}" 39 "-alertmanager.notification-queue-capacity=${toString cfg.alertmanagerNotificationQueueCapacity}" 40 "-alertmanager.timeout=${toString cfg.alertmanagerTimeout}s" 41 (optionalString (cfg.alertmanagerURL != []) "-alertmanager.url=${concatStringsSep "," cfg.alertmanagerURL}") 42 ]; 43 44 promTypes.globalConfig = types.submodule { 45 options = { 46 scrape_interval = mkOption { 47 type = types.str; 48 default = "1m"; 49 description = '' 50 How frequently to scrape targets by default. 51 ''; 52 }; 53 54 scrape_timeout = mkOption { 55 type = types.str; 56 default = "10s"; 57 description = '' 58 How long until a scrape request times out. 59 ''; 60 }; 61 62 evaluation_interval = mkOption { 63 type = types.str; 64 default = "1m"; 65 description = '' 66 How frequently to evaluate rules by default. 67 ''; 68 }; 69 70 external_labels = mkOption { 71 type = types.attrsOf types.str; 72 description = '' 73 The labels to add to any time series or alerts when 74 communicating with external systems (federation, remote 75 storage, Alertmanager). 76 ''; 77 default = {}; 78 }; 79 }; 80 }; 81 82 promTypes.scrape_config = types.submodule { 83 options = { 84 job_name = mkOption { 85 type = types.str; 86 description = '' 87 The job name assigned to scraped metrics by default. 88 ''; 89 }; 90 scrape_interval = mkOption { 91 type = types.nullOr types.str; 92 default = null; 93 description = '' 94 How frequently to scrape targets from this job. Defaults to the 95 globally configured default. 96 ''; 97 }; 98 scrape_timeout = mkOption { 99 type = types.nullOr types.str; 100 default = null; 101 description = '' 102 Per-target timeout when scraping this job. Defaults to the 103 globally configured default. 104 ''; 105 }; 106 metrics_path = mkOption { 107 type = types.str; 108 default = "/metrics"; 109 description = '' 110 The HTTP resource path on which to fetch metrics from targets. 111 ''; 112 }; 113 honor_labels = mkOption { 114 type = types.bool; 115 default = false; 116 description = '' 117 Controls how Prometheus handles conflicts between labels 118 that are already present in scraped data and labels that 119 Prometheus would attach server-side ("job" and "instance" 120 labels, manually configured target labels, and labels 121 generated by service discovery implementations). 122 123 If honor_labels is set to "true", label conflicts are 124 resolved by keeping label values from the scraped data and 125 ignoring the conflicting server-side labels. 126 127 If honor_labels is set to "false", label conflicts are 128 resolved by renaming conflicting labels in the scraped data 129 to "exported_<original-label>" (for example 130 "exported_instance", "exported_job") and then attaching 131 server-side labels. This is useful for use cases such as 132 federation, where all labels specified in the target should 133 be preserved. 134 ''; 135 }; 136 scheme = mkOption { 137 type = types.enum ["http" "https"]; 138 default = "http"; 139 description = '' 140 The URL scheme with which to fetch metrics from targets. 141 ''; 142 }; 143 params = mkOption { 144 type = types.attrsOf (types.listOf types.str); 145 default = {}; 146 description = '' 147 Optional HTTP URL parameters. 148 ''; 149 }; 150 basic_auth = mkOption { 151 type = types.nullOr (types.submodule { 152 options = { 153 username = mkOption { 154 type = types.str; 155 description = '' 156 HTTP username 157 ''; 158 }; 159 password = mkOption { 160 type = types.str; 161 description = '' 162 HTTP password 163 ''; 164 }; 165 }; 166 }); 167 default = null; 168 apply = x: mapNullable _filter x; 169 description = '' 170 Optional http login credentials for metrics scraping. 171 ''; 172 }; 173 dns_sd_configs = mkOption { 174 type = types.listOf promTypes.dns_sd_config; 175 default = []; 176 apply = x: map _filter x; 177 description = '' 178 List of DNS service discovery configurations. 179 ''; 180 }; 181 consul_sd_configs = mkOption { 182 type = types.listOf promTypes.consul_sd_config; 183 default = []; 184 apply = x: map _filter x; 185 description = '' 186 List of Consul service discovery configurations. 187 ''; 188 }; 189 file_sd_configs = mkOption { 190 type = types.listOf promTypes.file_sd_config; 191 default = []; 192 apply = x: map _filter x; 193 description = '' 194 List of file service discovery configurations. 195 ''; 196 }; 197 static_configs = mkOption { 198 type = types.listOf promTypes.static_config; 199 default = []; 200 apply = x: map _filter x; 201 description = '' 202 List of labeled target groups for this job. 203 ''; 204 }; 205 relabel_configs = mkOption { 206 type = types.listOf promTypes.relabel_config; 207 default = []; 208 apply = x: map _filter x; 209 description = '' 210 List of relabel configurations. 211 ''; 212 }; 213 }; 214 }; 215 216 promTypes.static_config = types.submodule { 217 options = { 218 targets = mkOption { 219 type = types.listOf types.str; 220 description = '' 221 The targets specified by the target group. 222 ''; 223 }; 224 labels = mkOption { 225 type = types.attrsOf types.str; 226 default = {}; 227 description = '' 228 Labels assigned to all metrics scraped from the targets. 229 ''; 230 }; 231 }; 232 }; 233 234 promTypes.dns_sd_config = types.submodule { 235 options = { 236 names = mkOption { 237 type = types.listOf types.str; 238 description = '' 239 A list of DNS SRV record names to be queried. 240 ''; 241 }; 242 refresh_interval = mkOption { 243 type = types.str; 244 default = "30s"; 245 description = '' 246 The time after which the provided names are refreshed. 247 ''; 248 }; 249 }; 250 }; 251 252 promTypes.consul_sd_config = types.submodule { 253 options = { 254 server = mkOption { 255 type = types.str; 256 description = "Consul server to query."; 257 }; 258 token = mkOption { 259 type = types.nullOr types.str; 260 description = "Consul token"; 261 }; 262 datacenter = mkOption { 263 type = types.nullOr types.str; 264 description = "Consul datacenter"; 265 }; 266 scheme = mkOption { 267 type = types.nullOr types.str; 268 description = "Consul scheme"; 269 }; 270 username = mkOption { 271 type = types.nullOr types.str; 272 description = "Consul username"; 273 }; 274 password = mkOption { 275 type = types.nullOr types.str; 276 description = "Consul password"; 277 }; 278 279 services = mkOption { 280 type = types.listOf types.str; 281 description = '' 282 A list of services for which targets are retrieved. 283 ''; 284 }; 285 tag_separator = mkOption { 286 type = types.str; 287 default = ","; 288 description = '' 289 The string by which Consul tags are joined into the tag label. 290 ''; 291 }; 292 }; 293 }; 294 295 promTypes.file_sd_config = types.submodule { 296 options = { 297 files = mkOption { 298 type = types.listOf types.str; 299 description = '' 300 Patterns for files from which target groups are extracted. Refer 301 to the Prometheus documentation for permitted filename patterns 302 and formats. 303 304 ''; 305 }; 306 refresh_interval = mkOption { 307 type = types.str; 308 default = "30s"; 309 description = '' 310 Refresh interval to re-read the files. 311 ''; 312 }; 313 }; 314 }; 315 316 promTypes.relabel_config = types.submodule { 317 options = { 318 source_labels = mkOption { 319 type = types.listOf types.str; 320 description = '' 321 The source labels select values from existing labels. Their content 322 is concatenated using the configured separator and matched against 323 the configured regular expression. 324 ''; 325 }; 326 separator = mkOption { 327 type = types.str; 328 default = ";"; 329 description = '' 330 Separator placed between concatenated source label values. 331 ''; 332 }; 333 target_label = mkOption { 334 type = types.nullOr types.str; 335 default = null; 336 description = '' 337 Label to which the resulting value is written in a replace action. 338 It is mandatory for replace actions. 339 ''; 340 }; 341 regex = mkOption { 342 type = types.str; 343 default = "(.*)"; 344 description = '' 345 Regular expression against which the extracted value is matched. 346 ''; 347 }; 348 replacement = mkOption { 349 type = types.str; 350 default = "$1"; 351 description = '' 352 Replacement value against which a regex replace is performed if the 353 regular expression matches. 354 ''; 355 }; 356 action = mkOption { 357 type = types.enum ["replace" "keep" "drop"]; 358 default = "replace"; 359 description = '' 360 Action to perform based on regex matching. 361 ''; 362 }; 363 }; 364 }; 365 366in { 367 options = { 368 services.prometheus = { 369 370 enable = mkOption { 371 type = types.bool; 372 default = false; 373 description = '' 374 Enable the Prometheus monitoring daemon. 375 ''; 376 }; 377 378 listenAddress = mkOption { 379 type = types.str; 380 default = "0.0.0.0:9090"; 381 description = '' 382 Address to listen on for the web interface, API, and telemetry. 383 ''; 384 }; 385 386 dataDir = mkOption { 387 type = types.path; 388 default = "/var/lib/prometheus"; 389 description = '' 390 Directory to store Prometheus metrics data. 391 ''; 392 }; 393 394 extraFlags = mkOption { 395 type = types.listOf types.str; 396 default = []; 397 description = '' 398 Extra commandline options when launching Prometheus. 399 ''; 400 }; 401 402 configText = mkOption { 403 type = types.nullOr types.lines; 404 default = null; 405 description = '' 406 If non-null, this option defines the text that is written to 407 prometheus.yml. If null, the contents of prometheus.yml is generated 408 from the structured config options. 409 ''; 410 }; 411 412 globalConfig = mkOption { 413 type = promTypes.globalConfig; 414 default = {}; 415 apply = _filter; 416 description = '' 417 Parameters that are valid in all configuration contexts. They 418 also serve as defaults for other configuration sections 419 ''; 420 }; 421 422 rules = mkOption { 423 type = types.listOf types.str; 424 default = []; 425 description = '' 426 Alerting and/or Recording rules to evaluate at runtime. 427 ''; 428 }; 429 430 ruleFiles = mkOption { 431 type = types.listOf types.path; 432 default = []; 433 description = '' 434 Any additional rules files to include in this configuration. 435 ''; 436 }; 437 438 scrapeConfigs = mkOption { 439 type = types.listOf promTypes.scrape_config; 440 default = []; 441 apply = x: map _filter x; 442 description = '' 443 A list of scrape configurations. 444 ''; 445 }; 446 447 alertmanagerURL = mkOption { 448 type = types.listOf types.str; 449 default = []; 450 description = '' 451 List of Alertmanager URLs to send notifications to. 452 ''; 453 }; 454 455 alertmanagerNotificationQueueCapacity = mkOption { 456 type = types.int; 457 default = 10000; 458 description = '' 459 The capacity of the queue for pending alert manager notifications. 460 ''; 461 }; 462 463 alertmanagerTimeout = mkOption { 464 type = types.int; 465 default = 10; 466 description = '' 467 Alert manager HTTP API timeout (in seconds). 468 ''; 469 }; 470 }; 471 }; 472 473 config = mkIf cfg.enable { 474 users.groups.${promGroup}.gid = config.ids.gids.prometheus; 475 users.users.${promUser} = { 476 description = "Prometheus daemon user"; 477 uid = config.ids.uids.prometheus; 478 group = promGroup; 479 home = cfg.dataDir; 480 createHome = true; 481 }; 482 systemd.services.prometheus = { 483 wantedBy = [ "multi-user.target" ]; 484 after = [ "network.target" ]; 485 script = '' 486 #!/bin/sh 487 exec ${pkgs.prometheus}/bin/prometheus \ 488 ${concatStringsSep " \\\n " cmdlineArgs} 489 ''; 490 serviceConfig = { 491 User = promUser; 492 Restart = "always"; 493 WorkingDirectory = cfg.dataDir; 494 }; 495 }; 496 }; 497}