vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / services / monitoring / graphite.nix
blob7987f34f1278be2f627be836f1bb3d058eef1b90
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           Slice = "system-graphite.slice";
277           RuntimeDirectory = name;
278           ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}";
279           User = "graphite";
280           Group = "graphite";
281           PermissionsStartOnly = true;
282           PIDFile="/run/${name}/${name}.pid";
283         };
284         preStart = ''
285           install -dm0700 -o graphite -g graphite ${cfg.dataDir}
286           install -dm0700 -o graphite -g graphite ${cfg.dataDir}/whisper
287         '';
288       };
289     })
291     (lib.mkIf cfg.carbon.enableAggregator {
292       systemd.services.carbonAggregator = let name = "carbon-aggregator"; in {
293         enable = cfg.carbon.enableAggregator;
294         description = "Carbon Data Aggregator";
295         wantedBy = [ "multi-user.target" ];
296         after = [ "network.target" ];
297         environment = carbonEnv;
298         serviceConfig = {
299           Slice = "system-graphite.slice";
300           RuntimeDirectory = name;
301           ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}";
302           User = "graphite";
303           Group = "graphite";
304           PIDFile="/run/${name}/${name}.pid";
305         };
306       };
307     })
309     (lib.mkIf cfg.carbon.enableRelay {
310       systemd.services.carbonRelay = let name = "carbon-relay"; in {
311         description = "Carbon Data Relay";
312         wantedBy = [ "multi-user.target" ];
313         after = [ "network.target" ];
314         environment = carbonEnv;
315         serviceConfig = {
316           Slice = "system-graphite.slice";
317           RuntimeDirectory = name;
318           ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}";
319           User = "graphite";
320           Group = "graphite";
321           PIDFile="/run/${name}/${name}.pid";
322         };
323       };
324     })
326     (lib.mkIf (cfg.carbon.enableCache || cfg.carbon.enableAggregator || cfg.carbon.enableRelay) {
327       environment.systemPackages = [
328         pkgs.python3Packages.carbon
329       ];
330     })
332     (lib.mkIf cfg.web.enable ({
333       systemd.services.graphiteWeb = {
334         description = "Graphite Web Interface";
335         wantedBy = [ "multi-user.target" ];
336         after = [ "network.target" ];
337         path = [ pkgs.perl ];
338         environment = {
339           PYTHONPATH = let
340               penv = pkgs.python3.buildEnv.override {
341                 extraLibs = [
342                   pkgs.python3Packages.graphite-web
343                 ];
344               };
345               penvPack = "${penv}/${pkgs.python3.sitePackages}";
346             in lib.concatStringsSep ":" [
347                  "${graphiteLocalSettingsDir}"
348                  "${penvPack}"
349                  # explicitly adding pycairo in path because it cannot be imported via buildEnv
350                  "${pkgs.python3Packages.pycairo}/${pkgs.python3.sitePackages}"
351                ];
352           DJANGO_SETTINGS_MODULE = "graphite.settings";
353           GRAPHITE_SETTINGS_MODULE = "graphite_local_settings";
354           GRAPHITE_CONF_DIR = configDir;
355           GRAPHITE_STORAGE_DIR = dataDir;
356           LD_LIBRARY_PATH = "${pkgs.cairo.out}/lib";
357         };
358         serviceConfig = {
359           ExecStart = ''
360             ${pkgs.python3Packages.waitress-django}/bin/waitress-serve-django \
361               --host=${cfg.web.listenAddress} --port=${toString cfg.web.port}
362           '';
363           User = "graphite";
364           Group = "graphite";
365           PermissionsStartOnly = true;
366           Slice = "system-graphite.slice";
367         };
368         preStart = ''
369           if ! test -e ${dataDir}/db-created; then
370             mkdir -p ${dataDir}/{whisper/,log/webapp/}
371             chmod 0700 ${dataDir}/{whisper/,log/webapp/}
373             ${pkgs.python3Packages.django}/bin/django-admin.py migrate --noinput
375             chown -R graphite:graphite ${dataDir}
377             touch ${dataDir}/db-created
378           fi
380           # Only collect static files when graphite_web changes.
381           if ! [ "${dataDir}/current_graphite_web" -ef "${pkgs.python3Packages.graphite-web}" ]; then
382             mkdir -p ${staticDir}
383             ${pkgs.python3Packages.django}/bin/django-admin.py collectstatic  --noinput --clear
384             chown -R graphite:graphite ${staticDir}
385             ln -sfT "${pkgs.python3Packages.graphite-web}" "${dataDir}/current_graphite_web"
386           fi
387         '';
388       };
390       environment.systemPackages = [ pkgs.python3Packages.graphite-web ];
391     }))
393     (lib.mkIf cfg.seyren.enable {
394       systemd.services.seyren = {
395         description = "Graphite Alerting Dashboard";
396         wantedBy = [ "multi-user.target" ];
397         after = [ "network.target" "mongodb.service" ];
398         environment = seyrenConfig;
399         serviceConfig = {
400           ExecStart = "${pkgs.seyren}/bin/seyren -httpPort ${toString cfg.seyren.port}";
401           WorkingDirectory = dataDir;
402           User = "graphite";
403           Group = "graphite";
404           Slice = "system-graphite.slice";
405         };
406         preStart = ''
407           if ! test -e ${dataDir}/db-created; then
408             mkdir -p ${dataDir}
409             chown graphite:graphite ${dataDir}
410           fi
411         '';
412       };
414       services.mongodb.enable = lib.mkDefault true;
415     })
417     (lib.mkIf (
418       cfg.carbon.enableCache || cfg.carbon.enableAggregator || cfg.carbon.enableRelay ||
419       cfg.web.enable || cfg.seyren.enable
420      ) {
421       systemd.slices.system-graphite = {
422         description = "Graphite Graphing System Slice";
423         documentation = [ "https://graphite.readthedocs.io/en/latest/overview.html" ];
424       };
426       users.users.graphite = {
427         uid = config.ids.uids.graphite;
428         group = "graphite";
429         description = "Graphite daemon user";
430         home = dataDir;
431       };
432       users.groups.graphite.gid = config.ids.gids.graphite;
433     })
434   ];