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