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