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