at 18.09-beta 20 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.graphite; 7 writeTextOrNull = f: t: mapNullable (pkgs.writeTextDir f) t; 8 9 dataDir = cfg.dataDir; 10 staticDir = cfg.dataDir + "/static"; 11 12 graphiteLocalSettingsDir = pkgs.runCommand "graphite_local_settings" 13 {inherit graphiteLocalSettings;} '' 14 mkdir -p $out 15 ln -s $graphiteLocalSettings $out/graphite_local_settings.py 16 ''; 17 18 graphiteLocalSettings = pkgs.writeText "graphite_local_settings.py" ( 19 "STATIC_ROOT = '${staticDir}'\n" + 20 optionalString (! isNull config.time.timeZone) "TIME_ZONE = '${config.time.timeZone}'\n" 21 + cfg.web.extraConfig 22 ); 23 24 graphiteApiConfig = pkgs.writeText "graphite-api.yaml" '' 25 search_index: ${dataDir}/index 26 ${optionalString (!isNull config.time.timeZone) ''time_zone: ${config.time.timeZone}''} 27 ${optionalString (cfg.api.finders != []) ''finders:''} 28 ${concatMapStringsSep "\n" (f: " - " + f.moduleName) cfg.api.finders} 29 ${optionalString (cfg.api.functions != []) ''functions:''} 30 ${concatMapStringsSep "\n" (f: " - " + f) cfg.api.functions} 31 ${cfg.api.extraConfig} 32 ''; 33 34 seyrenConfig = { 35 SEYREN_URL = cfg.seyren.seyrenUrl; 36 MONGO_URL = cfg.seyren.mongoUrl; 37 GRAPHITE_URL = cfg.seyren.graphiteUrl; 38 } // cfg.seyren.extraConfig; 39 40 pagerConfig = pkgs.writeText "alarms.yaml" cfg.pager.alerts; 41 42 configDir = pkgs.buildEnv { 43 name = "graphite-config"; 44 paths = lists.filter (el: el != null) [ 45 (writeTextOrNull "carbon.conf" cfg.carbon.config) 46 (writeTextOrNull "storage-aggregation.conf" cfg.carbon.storageAggregation) 47 (writeTextOrNull "storage-schemas.conf" cfg.carbon.storageSchemas) 48 (writeTextOrNull "blacklist.conf" cfg.carbon.blacklist) 49 (writeTextOrNull "whitelist.conf" cfg.carbon.whitelist) 50 (writeTextOrNull "rewrite-rules.conf" cfg.carbon.rewriteRules) 51 (writeTextOrNull "relay-rules.conf" cfg.carbon.relayRules) 52 (writeTextOrNull "aggregation-rules.conf" cfg.carbon.aggregationRules) 53 ]; 54 }; 55 56 carbonOpts = name: with config.ids; '' 57 --nodaemon --syslog --prefix=${name} --pidfile /run/${name}/${name}.pid ${name} 58 ''; 59 60 carbonEnv = { 61 PYTHONPATH = let 62 cenv = pkgs.python.buildEnv.override { 63 extraLibs = [ pkgs.python27Packages.carbon ]; 64 }; 65 cenvPack = "${cenv}/${pkgs.python.sitePackages}"; 66 # opt/graphite/lib contains twisted.plugins.carbon-cache 67 in "${cenvPack}/opt/graphite/lib:${cenvPack}"; 68 GRAPHITE_ROOT = dataDir; 69 GRAPHITE_CONF_DIR = configDir; 70 GRAPHITE_STORAGE_DIR = dataDir; 71 }; 72 73in { 74 75 ###### interface 76 77 options.services.graphite = { 78 dataDir = mkOption { 79 type = types.path; 80 default = "/var/db/graphite"; 81 description = '' 82 Data directory for graphite. 83 ''; 84 }; 85 86 web = { 87 enable = mkOption { 88 description = "Whether to enable graphite web frontend."; 89 default = false; 90 type = types.bool; 91 }; 92 93 listenAddress = mkOption { 94 description = "Graphite web frontend listen address."; 95 default = "127.0.0.1"; 96 type = types.str; 97 }; 98 99 port = mkOption { 100 description = "Graphite web frontend port."; 101 default = 8080; 102 type = types.int; 103 }; 104 105 extraConfig = mkOption { 106 type = types.str; 107 default = ""; 108 description = '' 109 Graphite webapp settings. See: 110 <link xlink:href="http://graphite.readthedocs.io/en/latest/config-local-settings.html"/> 111 ''; 112 }; 113 }; 114 115 api = { 116 enable = mkOption { 117 description = '' 118 Whether to enable graphite api. Graphite api is lightweight alternative 119 to graphite web, with api and without dashboard. It's advised to use 120 grafana as alternative dashboard and influxdb as alternative to 121 graphite carbon. 122 123 For more information visit 124 <link xlink:href="http://graphite-api.readthedocs.org/en/latest/"/> 125 ''; 126 default = false; 127 type = types.bool; 128 }; 129 130 finders = mkOption { 131 description = "List of finder plugins to load."; 132 default = []; 133 example = literalExample "[ pkgs.python27Packages.influxgraph ]"; 134 type = types.listOf types.package; 135 }; 136 137 functions = mkOption { 138 description = "List of functions to load."; 139 default = [ 140 "graphite_api.functions.SeriesFunctions" 141 "graphite_api.functions.PieFunctions" 142 ]; 143 type = types.listOf types.str; 144 }; 145 146 listenAddress = mkOption { 147 description = "Graphite web service listen address."; 148 default = "127.0.0.1"; 149 type = types.str; 150 }; 151 152 port = mkOption { 153 description = "Graphite api service port."; 154 default = 8080; 155 type = types.int; 156 }; 157 158 package = mkOption { 159 description = "Package to use for graphite api."; 160 default = pkgs.python27Packages.graphite_api; 161 defaultText = "pkgs.python27Packages.graphite_api"; 162 type = types.package; 163 }; 164 165 extraConfig = mkOption { 166 description = "Extra configuration for graphite api."; 167 default = '' 168 whisper: 169 directories: 170 - ${dataDir}/whisper 171 ''; 172 example = '' 173 allowed_origins: 174 - dashboard.example.com 175 cheat_times: true 176 influxdb: 177 host: localhost 178 port: 8086 179 user: influxdb 180 pass: influxdb 181 db: metrics 182 cache: 183 CACHE_TYPE: 'filesystem' 184 CACHE_DIR: '/tmp/graphite-api-cache' 185 ''; 186 type = types.lines; 187 }; 188 }; 189 190 carbon = { 191 config = mkOption { 192 description = "Content of carbon configuration file."; 193 default = '' 194 [cache] 195 # Listen on localhost by default for security reasons 196 UDP_RECEIVER_INTERFACE = 127.0.0.1 197 PICKLE_RECEIVER_INTERFACE = 127.0.0.1 198 LINE_RECEIVER_INTERFACE = 127.0.0.1 199 CACHE_QUERY_INTERFACE = 127.0.0.1 200 # Do not log every update 201 LOG_UPDATES = False 202 LOG_CACHE_HITS = False 203 ''; 204 type = types.str; 205 }; 206 207 enableCache = mkOption { 208 description = "Whether to enable carbon cache, the graphite storage daemon."; 209 default = false; 210 type = types.bool; 211 }; 212 213 storageAggregation = mkOption { 214 description = "Defines how to aggregate data to lower-precision retentions."; 215 default = null; 216 type = types.uniq (types.nullOr types.string); 217 example = '' 218 [all_min] 219 pattern = \.min$ 220 xFilesFactor = 0.1 221 aggregationMethod = min 222 ''; 223 }; 224 225 storageSchemas = mkOption { 226 description = "Defines retention rates for storing metrics."; 227 default = ""; 228 type = types.uniq (types.nullOr types.string); 229 example = '' 230 [apache_busyWorkers] 231 pattern = ^servers\.www.*\.workers\.busyWorkers$ 232 retentions = 15s:7d,1m:21d,15m:5y 233 ''; 234 }; 235 236 blacklist = mkOption { 237 description = "Any metrics received which match one of the experssions will be dropped."; 238 default = null; 239 type = types.uniq (types.nullOr types.string); 240 example = "^some\.noisy\.metric\.prefix\..*"; 241 }; 242 243 whitelist = mkOption { 244 description = "Only metrics received which match one of the experssions will be persisted."; 245 default = null; 246 type = types.uniq (types.nullOr types.string); 247 example = ".*"; 248 }; 249 250 rewriteRules = mkOption { 251 description = '' 252 Regular expression patterns that can be used to rewrite metric names 253 in a search and replace fashion. 254 ''; 255 default = null; 256 type = types.uniq (types.nullOr types.string); 257 example = '' 258 [post] 259 _sum$ = 260 _avg$ = 261 ''; 262 }; 263 264 enableRelay = mkOption { 265 description = "Whether to enable carbon relay, the carbon replication and sharding service."; 266 default = false; 267 type = types.bool; 268 }; 269 270 relayRules = mkOption { 271 description = "Relay rules are used to send certain metrics to a certain backend."; 272 default = null; 273 type = types.uniq (types.nullOr types.string); 274 example = '' 275 [example] 276 pattern = ^mydata\.foo\..+ 277 servers = 10.1.2.3, 10.1.2.4:2004, myserver.mydomain.com 278 ''; 279 }; 280 281 enableAggregator = mkOption { 282 description = "Whether to enable carbon aggregator, the carbon buffering service."; 283 default = false; 284 type = types.bool; 285 }; 286 287 aggregationRules = mkOption { 288 description = "Defines if and how received metrics will be aggregated."; 289 default = null; 290 type = types.uniq (types.nullOr types.string); 291 example = '' 292 <env>.applications.<app>.all.requests (60) = sum <env>.applications.<app>.*.requests 293 <env>.applications.<app>.all.latency (60) = avg <env>.applications.<app>.*.latency 294 ''; 295 }; 296 }; 297 298 seyren = { 299 enable = mkOption { 300 description = "Whether to enable seyren service."; 301 default = false; 302 type = types.bool; 303 }; 304 305 port = mkOption { 306 description = "Seyren listening port."; 307 default = 8081; 308 type = types.int; 309 }; 310 311 seyrenUrl = mkOption { 312 default = "http://localhost:${toString cfg.seyren.port}/"; 313 description = "Host where seyren is accessible."; 314 type = types.str; 315 }; 316 317 graphiteUrl = mkOption { 318 default = "http://${cfg.web.listenAddress}:${toString cfg.web.port}"; 319 description = "Host where graphite service runs."; 320 type = types.str; 321 }; 322 323 mongoUrl = mkOption { 324 default = "mongodb://${config.services.mongodb.bind_ip}:27017/seyren"; 325 description = "Mongodb connection string."; 326 type = types.str; 327 }; 328 329 extraConfig = mkOption { 330 default = {}; 331 description = '' 332 Extra seyren configuration. See 333 <link xlink:href='https://github.com/scobal/seyren#config' /> 334 ''; 335 type = types.attrsOf types.str; 336 example = literalExample '' 337 { 338 GRAPHITE_USERNAME = "user"; 339 GRAPHITE_PASSWORD = "pass"; 340 } 341 ''; 342 }; 343 }; 344 345 pager = { 346 enable = mkOption { 347 description = '' 348 Whether to enable graphite-pager service. For more information visit 349 <link xlink:href="https://github.com/seatgeek/graphite-pager"/> 350 ''; 351 default = false; 352 type = types.bool; 353 }; 354 355 redisUrl = mkOption { 356 description = "Redis connection string."; 357 default = "redis://localhost:${toString config.services.redis.port}/"; 358 type = types.str; 359 }; 360 361 graphiteUrl = mkOption { 362 description = "URL to your graphite service."; 363 default = "http://${cfg.web.listenAddress}:${toString cfg.web.port}"; 364 type = types.str; 365 }; 366 367 alerts = mkOption { 368 description = "Alerts configuration for graphite-pager."; 369 default = '' 370 alerts: 371 - target: constantLine(100) 372 warning: 90 373 critical: 200 374 name: Test 375 ''; 376 example = '' 377 pushbullet_key: pushbullet_api_key 378 alerts: 379 - target: stats.seatgeek.app.deal_quality.venue_info_cache.hit 380 warning: .5 381 critical: 1 382 name: Deal quality venue cache hits 383 ''; 384 type = types.lines; 385 }; 386 }; 387 388 beacon = { 389 enable = mkEnableOption "graphite beacon"; 390 391 config = mkOption { 392 description = "Graphite beacon configuration."; 393 default = {}; 394 type = types.attrs; 395 }; 396 }; 397 }; 398 399 ###### implementation 400 401 config = mkMerge [ 402 (mkIf cfg.carbon.enableCache { 403 systemd.services.carbonCache = let name = "carbon-cache"; in { 404 description = "Graphite Data Storage Backend"; 405 wantedBy = [ "multi-user.target" ]; 406 after = [ "network.target" ]; 407 environment = carbonEnv; 408 serviceConfig = { 409 RuntimeDirectory = name; 410 ExecStart = "${pkgs.pythonPackages.twisted}/bin/twistd ${carbonOpts name}"; 411 User = "graphite"; 412 Group = "graphite"; 413 PermissionsStartOnly = true; 414 PIDFile="/run/${name}/${name}.pid"; 415 }; 416 preStart = '' 417 install -dm0700 -o graphite -g graphite ${cfg.dataDir} 418 install -dm0700 -o graphite -g graphite ${cfg.dataDir}/whisper 419 ''; 420 }; 421 }) 422 423 (mkIf cfg.carbon.enableAggregator { 424 systemd.services.carbonAggregator = let name = "carbon-aggregator"; in { 425 enable = cfg.carbon.enableAggregator; 426 description = "Carbon Data Aggregator"; 427 wantedBy = [ "multi-user.target" ]; 428 after = [ "network.target" ]; 429 environment = carbonEnv; 430 serviceConfig = { 431 RuntimeDirectory = name; 432 ExecStart = "${pkgs.pythonPackages.twisted}/bin/twistd ${carbonOpts name}"; 433 User = "graphite"; 434 Group = "graphite"; 435 PIDFile="/run/${name}/${name}.pid"; 436 }; 437 }; 438 }) 439 440 (mkIf cfg.carbon.enableRelay { 441 systemd.services.carbonRelay = let name = "carbon-relay"; in { 442 description = "Carbon Data Relay"; 443 wantedBy = [ "multi-user.target" ]; 444 after = [ "network.target" ]; 445 environment = carbonEnv; 446 serviceConfig = { 447 RuntimeDirectory = name; 448 ExecStart = "${pkgs.pythonPackages.twisted}/bin/twistd ${carbonOpts name}"; 449 User = "graphite"; 450 Group = "graphite"; 451 PIDFile="/run/${name}/${name}.pid"; 452 }; 453 }; 454 }) 455 456 (mkIf (cfg.carbon.enableCache || cfg.carbon.enableAggregator || cfg.carbon.enableRelay) { 457 environment.systemPackages = [ 458 pkgs.pythonPackages.carbon 459 ]; 460 }) 461 462 (mkIf cfg.web.enable (let 463 python27' = pkgs.python27.override { 464 packageOverrides = self: super: { 465 django = self.django_1_8; 466 django_tagging = self.django_tagging_0_4_3; 467 }; 468 }; 469 pythonPackages = python27'.pkgs; 470 in { 471 systemd.services.graphiteWeb = { 472 description = "Graphite Web Interface"; 473 wantedBy = [ "multi-user.target" ]; 474 after = [ "network.target" ]; 475 path = [ pkgs.perl ]; 476 environment = { 477 PYTHONPATH = let 478 penv = pkgs.python.buildEnv.override { 479 extraLibs = [ 480 pythonPackages.graphite-web 481 pythonPackages.pysqlite 482 ]; 483 }; 484 penvPack = "${penv}/${pkgs.python.sitePackages}"; 485 in concatStringsSep ":" [ 486 "${graphiteLocalSettingsDir}" 487 "${penvPack}/opt/graphite/webapp" 488 "${penvPack}" 489 # explicitly adding pycairo in path because it cannot be imported via buildEnv 490 "${pkgs.pythonPackages.pycairo}/${pkgs.python.sitePackages}" 491 ]; 492 DJANGO_SETTINGS_MODULE = "graphite.settings"; 493 GRAPHITE_CONF_DIR = configDir; 494 GRAPHITE_STORAGE_DIR = dataDir; 495 LD_LIBRARY_PATH = "${pkgs.cairo.out}/lib"; 496 }; 497 serviceConfig = { 498 ExecStart = '' 499 ${pkgs.python27Packages.waitress-django}/bin/waitress-serve-django \ 500 --host=${cfg.web.listenAddress} --port=${toString cfg.web.port} 501 ''; 502 User = "graphite"; 503 Group = "graphite"; 504 PermissionsStartOnly = true; 505 }; 506 preStart = '' 507 if ! test -e ${dataDir}/db-created; then 508 mkdir -p ${dataDir}/{whisper/,log/webapp/} 509 chmod 0700 ${dataDir}/{whisper/,log/webapp/} 510 511 ${pkgs.pythonPackages.django_1_8}/bin/django-admin.py migrate --noinput 512 513 chown -R graphite:graphite ${dataDir} 514 515 touch ${dataDir}/db-created 516 fi 517 518 # Only collect static files when graphite_web changes. 519 if ! [ "${dataDir}/current_graphite_web" -ef "${pythonPackages.graphite-web}" ]; then 520 mkdir -p ${staticDir} 521 ${pkgs.pythonPackages.django_1_8}/bin/django-admin.py collectstatic --noinput --clear 522 chown -R graphite:graphite ${staticDir} 523 ln -sfT "${pythonPackages.graphite-web}" "${dataDir}/current_graphite_web" 524 fi 525 ''; 526 }; 527 528 environment.systemPackages = [ pythonPackages.graphite-web ]; 529 })) 530 531 (mkIf cfg.api.enable { 532 systemd.services.graphiteApi = { 533 description = "Graphite Api Interface"; 534 wantedBy = [ "multi-user.target" ]; 535 after = [ "network.target" ]; 536 environment = { 537 PYTHONPATH = let 538 aenv = pkgs.python.buildEnv.override { 539 extraLibs = [ cfg.api.package pkgs.cairo pkgs.pythonPackages.cffi ] ++ cfg.api.finders; 540 }; 541 in "${aenv}/${pkgs.python.sitePackages}"; 542 GRAPHITE_API_CONFIG = graphiteApiConfig; 543 LD_LIBRARY_PATH = "${pkgs.cairo.out}/lib"; 544 }; 545 serviceConfig = { 546 ExecStart = '' 547 ${pkgs.python27Packages.waitress}/bin/waitress-serve \ 548 --host=${cfg.api.listenAddress} --port=${toString cfg.api.port} \ 549 graphite_api.app:app 550 ''; 551 User = "graphite"; 552 Group = "graphite"; 553 PermissionsStartOnly = true; 554 }; 555 preStart = '' 556 if ! test -e ${dataDir}/db-created; then 557 mkdir -p ${dataDir}/cache/ 558 chmod 0700 ${dataDir}/cache/ 559 560 chown graphite:graphite ${cfg.dataDir} 561 chown -R graphite:graphite ${cfg.dataDir}/cache 562 563 touch ${dataDir}/db-created 564 fi 565 ''; 566 }; 567 }) 568 569 (mkIf cfg.seyren.enable { 570 systemd.services.seyren = { 571 description = "Graphite Alerting Dashboard"; 572 wantedBy = [ "multi-user.target" ]; 573 after = [ "network.target" "mongodb.service" ]; 574 environment = seyrenConfig; 575 serviceConfig = { 576 ExecStart = "${pkgs.seyren}/bin/seyren -httpPort ${toString cfg.seyren.port}"; 577 WorkingDirectory = dataDir; 578 User = "graphite"; 579 Group = "graphite"; 580 }; 581 preStart = '' 582 if ! test -e ${dataDir}/db-created; then 583 mkdir -p ${dataDir} 584 chown graphite:graphite ${dataDir} 585 fi 586 ''; 587 }; 588 589 services.mongodb.enable = mkDefault true; 590 }) 591 592 (mkIf cfg.pager.enable { 593 systemd.services.graphitePager = { 594 description = "Graphite Pager Alerting Daemon"; 595 wantedBy = [ "multi-user.target" ]; 596 after = [ "network.target" "redis.service" ]; 597 environment = { 598 REDIS_URL = cfg.pager.redisUrl; 599 GRAPHITE_URL = cfg.pager.graphiteUrl; 600 }; 601 serviceConfig = { 602 ExecStart = "${pkgs.pythonPackages.graphitepager}/bin/graphite-pager --config ${pagerConfig}"; 603 User = "graphite"; 604 Group = "graphite"; 605 }; 606 }; 607 608 services.redis.enable = mkDefault true; 609 610 environment.systemPackages = [ pkgs.pythonPackages.graphitepager ]; 611 }) 612 613 (mkIf cfg.beacon.enable { 614 systemd.services.graphite-beacon = { 615 description = "Grpahite Beacon Alerting Daemon"; 616 wantedBy = [ "multi-user.target" ]; 617 serviceConfig = { 618 ExecStart = '' 619 ${pkgs.pythonPackages.graphite_beacon}/bin/graphite-beacon \ 620 --config=${pkgs.writeText "graphite-beacon.json" (builtins.toJSON cfg.beacon.config)} 621 ''; 622 User = "graphite"; 623 Group = "graphite"; 624 }; 625 }; 626 }) 627 628 (mkIf ( 629 cfg.carbon.enableCache || cfg.carbon.enableAggregator || cfg.carbon.enableRelay || 630 cfg.web.enable || cfg.api.enable || 631 cfg.seyren.enable || cfg.pager.enable || cfg.beacon.enable 632 ) { 633 users.users = singleton { 634 name = "graphite"; 635 uid = config.ids.uids.graphite; 636 description = "Graphite daemon user"; 637 home = dataDir; 638 }; 639 users.groups.graphite.gid = config.ids.gids.graphite; 640 }) 641 ]; 642}