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}