grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / monitoring / graphite.nix
blobcb259013c6702e185dfe63299f03627615ebd264
1 { config, lib, options, pkgs, ... }:
2 let
3   cfg = config.services.graphite;
4   opt = options.services.graphite;
5   writeTextOrNull = f: t: lib.mapNullable (pkgs.writeTextDir f) t;
7   dataDir = cfg.dataDir;
8   staticDir = cfg.dataDir + "/static";
10   graphiteLocalSettingsDir = pkgs.runCommand "graphite_local_settings" {
11       inherit graphiteLocalSettings;
12       preferLocalBuild = true;
13     } ''
14     mkdir -p $out
15     ln -s $graphiteLocalSettings $out/graphite_local_settings.py
16   '';
18   graphiteLocalSettings = pkgs.writeText "graphite_local_settings.py" (
19     "STATIC_ROOT = '${staticDir}'\n" +
20     lib.optionalString (config.time.timeZone != null) "TIME_ZONE = '${config.time.timeZone}'\n"
21     + cfg.web.extraConfig
22   );
24   seyrenConfig = {
25     SEYREN_URL = cfg.seyren.seyrenUrl;
26     MONGO_URL = cfg.seyren.mongoUrl;
27     GRAPHITE_URL = cfg.seyren.graphiteUrl;
28   } // cfg.seyren.extraConfig;
30   configDir = pkgs.buildEnv {
31     name = "graphite-config";
32     paths = lib.lists.filter (el: el != null) [
33       (writeTextOrNull "carbon.conf" cfg.carbon.config)
34       (writeTextOrNull "storage-aggregation.conf" cfg.carbon.storageAggregation)
35       (writeTextOrNull "storage-schemas.conf" cfg.carbon.storageSchemas)
36       (writeTextOrNull "blacklist.conf" cfg.carbon.blacklist)
37       (writeTextOrNull "whitelist.conf" cfg.carbon.whitelist)
38       (writeTextOrNull "rewrite-rules.conf" cfg.carbon.rewriteRules)
39       (writeTextOrNull "relay-rules.conf" cfg.carbon.relayRules)
40       (writeTextOrNull "aggregation-rules.conf" cfg.carbon.aggregationRules)
41     ];
42   };
44   carbonOpts = name: with config.ids; ''
45     --nodaemon --syslog --prefix=${name} --pidfile /run/${name}/${name}.pid ${name}
46   '';
48   carbonEnv = {
49     PYTHONPATH = let
50       cenv = pkgs.python3.buildEnv.override {
51         extraLibs = [ pkgs.python3Packages.carbon ];
52       };
53     in "${cenv}/${pkgs.python3.sitePackages}";
54     GRAPHITE_ROOT = dataDir;
55     GRAPHITE_CONF_DIR = configDir;
56     GRAPHITE_STORAGE_DIR = dataDir;
57   };
59 in {
61   imports = [
62     (lib.mkRemovedOptionModule ["services" "graphite" "api"] "")
63     (lib.mkRemovedOptionModule ["services" "graphite" "beacon"] "")
64     (lib.mkRemovedOptionModule ["services" "graphite" "pager"] "")
65   ];
67   ###### interface
69   options.services.graphite = {
70     dataDir = lib.mkOption {
71       type = lib.types.path;
72       default = "/var/db/graphite";
73       description = ''
74         Data directory for graphite.
75       '';
76     };
78     web = {
79       enable = lib.mkOption {
80         description = "Whether to enable graphite web frontend.";
81         default = false;
82         type = lib.types.bool;
83       };
85       listenAddress = lib.mkOption {
86         description = "Graphite web frontend listen address.";
87         default = "127.0.0.1";
88         type = lib.types.str;
89       };
91       port = lib.mkOption {
92         description = "Graphite web frontend port.";
93         default = 8080;
94         type = lib.types.port;
95       };
97       extraConfig = lib.mkOption {
98         type = lib.types.str;
99         default = "";
100         description = ''
101           Graphite webapp settings. See:
102           <https://graphite.readthedocs.io/en/latest/config-local-settings.html>
103         '';
104       };
105     };
107     carbon = {
108       config = lib.mkOption {
109         description = "Content of carbon configuration file.";
110         default = ''
111           [cache]
112           # Listen on localhost by default for security reasons
113           UDP_RECEIVER_INTERFACE = 127.0.0.1
114           PICKLE_RECEIVER_INTERFACE = 127.0.0.1
115           LINE_RECEIVER_INTERFACE = 127.0.0.1
116           CACHE_QUERY_INTERFACE = 127.0.0.1
117           # Do not log every update
118           LOG_UPDATES = False
119           LOG_CACHE_HITS = False
120         '';
121         type = lib.types.str;
122       };
124       enableCache = lib.mkOption {
125         description = "Whether to enable carbon cache, the graphite storage daemon.";
126         default = false;
127         type = lib.types.bool;
128       };
130       storageAggregation = lib.mkOption {
131         description = "Defines how to aggregate data to lower-precision retentions.";
132         default = null;
133         type = lib.types.nullOr lib.types.str;
134         example = ''
135           [all_min]
136           pattern = \.min$
137           xFilesFactor = 0.1
138           aggregationMethod = min
139         '';
140       };
142       storageSchemas = lib.mkOption {
143         description = "Defines retention rates for storing metrics.";
144         default = "";
145         type = lib.types.nullOr lib.types.str;
146         example = ''
147           [apache_busyWorkers]
148           pattern = ^servers\.www.*\.workers\.busyWorkers$
149           retentions = 15s:7d,1m:21d,15m:5y
150         '';
151       };
153       blacklist = lib.mkOption {
154         description = "Any metrics received which match one of the expressions will be dropped.";
155         default = null;
156         type = lib.types.nullOr lib.types.str;
157         example = "^some\\.noisy\\.metric\\.prefix\\..*";
158       };
160       whitelist = lib.mkOption {
161         description = "Only metrics received which match one of the expressions will be persisted.";
162         default = null;
163         type = lib.types.nullOr lib.types.str;
164         example = ".*";
165       };
167       rewriteRules = lib.mkOption {
168         description = ''
169           Regular expression patterns that can be used to rewrite metric names
170           in a search and replace fashion.
171         '';
172         default = null;
173         type = lib.types.nullOr lib.types.str;
174         example = ''
175           [post]
176           _sum$ =
177           _avg$ =
178         '';
179       };
181       enableRelay = lib.mkOption {
182         description = "Whether to enable carbon relay, the carbon replication and sharding service.";
183         default = false;
184         type = lib.types.bool;
185       };
187       relayRules = lib.mkOption {
188         description = "Relay rules are used to send certain metrics to a certain backend.";
189         default = null;
190         type = lib.types.nullOr lib.types.str;
191         example = ''
192           [example]
193           pattern = ^mydata\.foo\..+
194           servers = 10.1.2.3, 10.1.2.4:2004, myserver.mydomain.com
195         '';
196       };
198       enableAggregator = lib.mkOption {
199         description = "Whether to enable carbon aggregator, the carbon buffering service.";
200         default = false;
201         type = lib.types.bool;
202       };
204       aggregationRules = lib.mkOption {
205         description = "Defines if and how received metrics will be aggregated.";
206         default = null;
207         type = lib.types.nullOr lib.types.str;
208         example = ''
209           <env>.applications.<app>.all.requests (60) = sum <env>.applications.<app>.*.requests
210           <env>.applications.<app>.all.latency (60) = avg <env>.applications.<app>.*.latency
211         '';
212       };
213     };
215     seyren = {
216       enable = lib.mkOption {
217         description = "Whether to enable seyren service.";
218         default = false;
219         type = lib.types.bool;
220       };
222       port = lib.mkOption {
223         description = "Seyren listening port.";
224         default = 8081;
225         type = lib.types.port;
226       };
228       seyrenUrl = lib.mkOption {
229         default = "http://localhost:${toString cfg.seyren.port}/";
230         defaultText = lib.literalExpression ''"http://localhost:''${toString config.${opt.seyren.port}}/"'';
231         description = "Host where seyren is accessible.";
232         type = lib.types.str;
233       };
235       graphiteUrl = lib.mkOption {
236         default = "http://${cfg.web.listenAddress}:${toString cfg.web.port}";
237         defaultText = lib.literalExpression ''"http://''${config.${opt.web.listenAddress}}:''${toString config.${opt.web.port}}"'';
238         description = "Host where graphite service runs.";
239         type = lib.types.str;
240       };
242       mongoUrl = lib.mkOption {
243         default = "mongodb://${config.services.mongodb.bind_ip}:27017/seyren";
244         defaultText = lib.literalExpression ''"mongodb://''${config.services.mongodb.bind_ip}:27017/seyren"'';
245         description = "Mongodb connection string.";
246         type = lib.types.str;
247       };
249       extraConfig = lib.mkOption {
250         default = {};
251         description = ''
252           Extra seyren configuration. See
253           <https://github.com/scobal/seyren#config>
254         '';
255         type = lib.types.attrsOf lib.types.str;
256         example = lib.literalExpression ''
257           {
258             GRAPHITE_USERNAME = "user";
259             GRAPHITE_PASSWORD = "pass";
260           }
261         '';
262       };
263     };
264   };
266   ###### implementation
268   config = lib.mkMerge [
269     (lib.mkIf cfg.carbon.enableCache {
270       systemd.services.carbonCache = let name = "carbon-cache"; in {
271         description = "Graphite Data Storage Backend";
272         wantedBy = [ "multi-user.target" ];
273         after = [ "network.target" ];
274         environment = carbonEnv;
275         serviceConfig = {
276           RuntimeDirectory = name;
277           ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}";
278           User = "graphite";
279           Group = "graphite";
280           PermissionsStartOnly = true;
281           PIDFile="/run/${name}/${name}.pid";
282         };
283         preStart = ''
284           install -dm0700 -o graphite -g graphite ${cfg.dataDir}
285           install -dm0700 -o graphite -g graphite ${cfg.dataDir}/whisper
286         '';
287       };
288     })
290     (lib.mkIf cfg.carbon.enableAggregator {
291       systemd.services.carbonAggregator = let name = "carbon-aggregator"; in {
292         enable = cfg.carbon.enableAggregator;
293         description = "Carbon Data Aggregator";
294         wantedBy = [ "multi-user.target" ];
295         after = [ "network.target" ];
296         environment = carbonEnv;
297         serviceConfig = {
298           RuntimeDirectory = name;
299           ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}";
300           User = "graphite";
301           Group = "graphite";
302           PIDFile="/run/${name}/${name}.pid";
303         };
304       };
305     })
307     (lib.mkIf cfg.carbon.enableRelay {
308       systemd.services.carbonRelay = let name = "carbon-relay"; in {
309         description = "Carbon Data Relay";
310         wantedBy = [ "multi-user.target" ];
311         after = [ "network.target" ];
312         environment = carbonEnv;
313         serviceConfig = {
314           RuntimeDirectory = name;
315           ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}";
316           User = "graphite";
317           Group = "graphite";
318           PIDFile="/run/${name}/${name}.pid";
319         };
320       };
321     })
323     (lib.mkIf (cfg.carbon.enableCache || cfg.carbon.enableAggregator || cfg.carbon.enableRelay) {
324       environment.systemPackages = [
325         pkgs.python3Packages.carbon
326       ];
327     })
329     (lib.mkIf cfg.web.enable ({
330       systemd.services.graphiteWeb = {
331         description = "Graphite Web Interface";
332         wantedBy = [ "multi-user.target" ];
333         after = [ "network.target" ];
334         path = [ pkgs.perl ];
335         environment = {
336           PYTHONPATH = let
337               penv = pkgs.python3.buildEnv.override {
338                 extraLibs = [
339                   pkgs.python3Packages.graphite-web
340                 ];
341               };
342               penvPack = "${penv}/${pkgs.python3.sitePackages}";
343             in lib.concatStringsSep ":" [
344                  "${graphiteLocalSettingsDir}"
345                  "${penvPack}"
346                  # explicitly adding pycairo in path because it cannot be imported via buildEnv
347                  "${pkgs.python3Packages.pycairo}/${pkgs.python3.sitePackages}"
348                ];
349           DJANGO_SETTINGS_MODULE = "graphite.settings";
350           GRAPHITE_SETTINGS_MODULE = "graphite_local_settings";
351           GRAPHITE_CONF_DIR = configDir;
352           GRAPHITE_STORAGE_DIR = dataDir;
353           LD_LIBRARY_PATH = "${pkgs.cairo.out}/lib";
354         };
355         serviceConfig = {
356           ExecStart = ''
357             ${pkgs.python3Packages.waitress-django}/bin/waitress-serve-django \
358               --host=${cfg.web.listenAddress} --port=${toString cfg.web.port}
359           '';
360           User = "graphite";
361           Group = "graphite";
362           PermissionsStartOnly = true;
363         };
364         preStart = ''
365           if ! test -e ${dataDir}/db-created; then
366             mkdir -p ${dataDir}/{whisper/,log/webapp/}
367             chmod 0700 ${dataDir}/{whisper/,log/webapp/}
369             ${pkgs.python3Packages.django}/bin/django-admin.py migrate --noinput
371             chown -R graphite:graphite ${dataDir}
373             touch ${dataDir}/db-created
374           fi
376           # Only collect static files when graphite_web changes.
377           if ! [ "${dataDir}/current_graphite_web" -ef "${pkgs.python3Packages.graphite-web}" ]; then
378             mkdir -p ${staticDir}
379             ${pkgs.python3Packages.django}/bin/django-admin.py collectstatic  --noinput --clear
380             chown -R graphite:graphite ${staticDir}
381             ln -sfT "${pkgs.python3Packages.graphite-web}" "${dataDir}/current_graphite_web"
382           fi
383         '';
384       };
386       environment.systemPackages = [ pkgs.python3Packages.graphite-web ];
387     }))
389     (lib.mkIf cfg.seyren.enable {
390       systemd.services.seyren = {
391         description = "Graphite Alerting Dashboard";
392         wantedBy = [ "multi-user.target" ];
393         after = [ "network.target" "mongodb.service" ];
394         environment = seyrenConfig;
395         serviceConfig = {
396           ExecStart = "${pkgs.seyren}/bin/seyren -httpPort ${toString cfg.seyren.port}";
397           WorkingDirectory = dataDir;
398           User = "graphite";
399           Group = "graphite";
400         };
401         preStart = ''
402           if ! test -e ${dataDir}/db-created; then
403             mkdir -p ${dataDir}
404             chown graphite:graphite ${dataDir}
405           fi
406         '';
407       };
409       services.mongodb.enable = lib.mkDefault true;
410     })
412     (lib.mkIf (
413       cfg.carbon.enableCache || cfg.carbon.enableAggregator || cfg.carbon.enableRelay ||
414       cfg.web.enable || cfg.seyren.enable
415      ) {
416       users.users.graphite = {
417         uid = config.ids.uids.graphite;
418         group = "graphite";
419         description = "Graphite daemon user";
420         home = dataDir;
421       };
422       users.groups.graphite.gid = config.ids.gids.graphite;
423     })
424   ];