diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 6623be753d783..f2f29ee37757f 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -340,6 +340,8 @@ ./services/logging/rsyslogd.nix ./services/logging/syslog-ng.nix ./services/logging/syslogd.nix + ./services/logging/beats/apm-server.nix + ./services/logging/beats/metricbeat.nix ./services/mail/clamsmtp.nix ./services/mail/davmail.nix ./services/mail/dkimproxy-out.nix diff --git a/nixos/modules/services/logging/beats/apm-server.nix b/nixos/modules/services/logging/beats/apm-server.nix new file mode 100644 index 0000000000000..4a3622590a2fc --- /dev/null +++ b/nixos/modules/services/logging/beats/apm-server.nix @@ -0,0 +1,79 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + beatslib = import ./lib.nix { inherit lib pkgs; }; + + mkApmServerConfig = cfg: { + + apm-server.host = "${cfg.listenAddress}:${toString cfg.port}"; + + }; + + +in + +{ + ###### interface + + options = { + + services.beats.apm-server = recursiveUpdate + (beatslib.mkCommonOptions { name = "apm-server"; defaults = { package = pkgs.elastic-apm-server; }; }) + { + + listenAddress = mkOption { + description = "Address on which to listen."; + type = types.str; + default = "localhost"; + }; + + port = mkOption { + description = "Port on which to listen."; + type = types.int; + default = 8200; + }; + + elasticsearch.indices = mkOption { + description = "Array of index selector rules"; + type = with types; listOf attrs; + default = [ + { + index = "apm-%{[beat.version]}-sourcemap"; + when.contains.processor.event = "sourcemap"; + } + { + index = "apm-%{[beat.version]}-error-%{+yyyy.MM.dd}"; + when.contains.processor.event = "error"; + } + { + index = "apm-%{[beat.version]}-transaction-%{+yyyy.MM.dd}"; + when.contains.processor.event = "transaction"; + } + { + index = "apm-%{[beat.version]}-span-%{+yyyy.MM.dd}"; + when.contains.processor.event = "span"; + } + { + index = "apm-%{[beat.version]}-metric-%{+yyyy.MM.dd}"; + when.contains.processor.event = "metric"; + } + { + index = "apm-%{[beat.version]}-onboarding-%{+yyyy.MM.dd}"; + when.contains.processor.event = "onboarding"; + } + ]; + }; + + }; + + }; + + config = beatslib.mkNixosConfig { + mkBeatConfig = mkApmServerConfig; + cfg = config.services.beats.apm-server; + }; + +} diff --git a/nixos/modules/services/logging/beats/lib.nix b/nixos/modules/services/logging/beats/lib.nix new file mode 100644 index 0000000000000..bd37ac1c1da45 --- /dev/null +++ b/nixos/modules/services/logging/beats/lib.nix @@ -0,0 +1,145 @@ +{ lib, pkgs, ... }: + +with lib; + +{ + + mkCommonOptions = { name, defaults }: { + + enable = mkEnableOption "Enable ${name}."; + + package = mkOption { + description = "${name} package to use."; + type = types.package; + default = defaults.package; + }; + + executable = mkOption { + description = "Name of the executable within to run."; + type = types.str; + default = defaults.executable or name; + }; + + name = mkOption { + description = "Name of the beat"; + type = types.str; + default = defaults.name or name; + }; + + tags = mkOption { + description = "Tags to place on the shipped log messages"; + type = types.listOf types.str; + default = []; + }; + + stateDir = mkOption { + description = "The state directory."; + type = types.str; + default = defaults.stateDir or "/var/lib/${name}"; + }; + + loglevel = mkOption { + description = "Logging verbosity level."; + type = types.enum [ "debug" "info" "warning" "error" "fatal" ]; + default = "warning"; + }; + + extraConfig = mkOption { + description = "Additional config to be added to the beats config.json."; + type = types.attrs; + default = {}; + }; + + elasticsearch = { + hosts = mkOption { + description = "Elasticsearch hosts"; + type = with types; listOf str; + default = [ "localhost:9200" ]; + }; + + username = mkOption { + description = "Username for elasticsearch basic auth."; + type = types.nullOr types.str; + default = null; + }; + + password = mkOption { + description = "Password for elasticsearch basic auth."; + type = types.nullOr types.str; + default = null; + }; + }; + + kibana = { + host = mkOption { + description = "Host where kibana is reachable."; + type = types.str; + default = "localhost:5601"; + }; + }; + + }; + + mkNixosConfig = { cfg, mkBeatConfig }: let + + mkCommonBeatConfig = cfg: { + inherit (cfg) tags; + + path = { + data = "${cfg.stateDir}/data"; + logs = "${cfg.stateDir}/logs"; + }; + + logging.level = cfg.loglevel; + + output = { inherit (cfg) elasticsearch; }; + + setup.template = { + fields = "${cfg.package}/share/fields.yml"; + settings.index = { + number_of_shards = 1; + codec = "best_compression"; + }; + }; + + setup.kibana = { + inherit (cfg.kibana) host; + }; + + setup.dashboards = { + enabled = true; + directory ="${cfg.package}/share/kibana"; + }; + }; + + beatConfig = foldl' recursiveUpdate {} [ + (mkCommonBeatConfig cfg) + (mkBeatConfig cfg) + cfg.extraConfig + ]; + beatConfigJSON = pkgs.writeText "${cfg.name}.json" (builtins.toJSON beatConfig); + + in mkIf cfg.enable { + + systemd.services.${cfg.name} = { + wantedBy = [ "multi-user.target" ]; + + after = let + hasLocalKibana = lib.strings.hasInfix "localhost" cfg.kibana.host; + hasLocalElasticsearch = (findSingle (x: lib.strings.hasInfix "localhost" x) null "multiple" cfg.elasticsearch.hosts) != null; + in [] + ++ optional hasLocalKibana "kibana.service" + ++ optional hasLocalElasticsearch "elasticsearch.service"; + + preStart = '' + mkdir -p ${beatConfig.path.data} + mkdir -p ${beatConfig.path.logs} + ''; + + serviceConfig = { + ExecStart = "${cfg.package}/bin/${cfg.executable} -c ${beatConfigJSON}"; + }; + }; + }; + +} diff --git a/nixos/modules/services/logging/beats/metricbeat.nix b/nixos/modules/services/logging/beats/metricbeat.nix new file mode 100644 index 0000000000000..c40ef0cde99e6 --- /dev/null +++ b/nixos/modules/services/logging/beats/metricbeat.nix @@ -0,0 +1,42 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + beatslib = import ./lib.nix { inherit lib pkgs; }; + + mkMetricbeatConfig = cfg: { + + metricbeat.modules = cfg.modules; + + }; + +in +{ + options = { + + services.beats.metricbeat = beatslib.mkCommonOptions { + name = "metricbeat"; + defaults = { package = pkgs.metricbeat; }; + } // { + + modules = mkOption { + description = '' + Modules Configuration. + + See + ''; + type = with types; listOf attrs; + default = []; + }; + + }; + }; + + config = beatslib.mkNixosConfig { + mkBeatConfig = mkMetricbeatConfig; + cfg = config.services.beats.metricbeat; + }; + +} diff --git a/nixos/tests/elk.nix b/nixos/tests/elk.nix index e7ae023f3ff26..68f064887e713 100644 --- a/nixos/tests/elk.nix +++ b/nixos/tests/elk.nix @@ -96,6 +96,16 @@ let unit_count: 1 ''; }; + + beats.apm-server = { + enable = true; + extraConfig = { + setup.dashboards = { + always_kibana = true; + retry.enabled = true; + }; + }; + }; }; }; }; @@ -125,6 +135,10 @@ let $one->waitUntilSucceeds("curl --silent --show-error '${esUrl}/_search' -H 'Content-Type: application/json' -d '{\"query\" : { \"match\" : { \"message\" : \"flowers\"}}}' | jq .hits.total | grep -v 0"); $one->waitUntilSucceeds("curl --silent --show-error '${esUrl}/_search' -H 'Content-Type: application/json' -d '{\"query\" : { \"match\" : { \"message\" : \"dragons\"}}}' | jq .hits.total | grep 0"); + # See if apm-server is healthy. + $one->waitForUnit("apm-server.service"); + $one->waitUntilSucceeds("curl --silent --show-error 'http://localhost:8200/' | grep 'ok'"); + # Test elasticsearch-curator. $one->systemctl("stop logstash"); $one->systemctl("start elasticsearch-curator"); diff --git a/pkgs/servers/misc/elastic-apm-server/default.nix b/pkgs/servers/misc/elastic-apm-server/default.nix new file mode 100644 index 0000000000000..c6c2a18698d57 --- /dev/null +++ b/pkgs/servers/misc/elastic-apm-server/default.nix @@ -0,0 +1,62 @@ +{ elk6Version +, enableUnfree ? true +, stdenv +, fetchurl +, makeWrapper +, autoPatchelfHook +}: + +with stdenv.lib; +let + + inherit (builtins) elemAt; + info = splitString "-" stdenv.hostPlatform.system; + arch = elemAt info 0; + plat = elemAt info 1; + shas = + if enableUnfree + then { + "x86-linux" = "0xa7jfsmg6mzw881k7j8rkph586dpcl7afycdds70n43ixrqsp07"; + "x86_64-linux" = "0cw0xqx4zyanhbh6irpkn9anw00navwqnfhzpwxpmkmn7w4lj64b"; + "x86_64-darwin" = "0b15m4j11qirbkq30i7m5xd51jz39ym8qiah18276vc6i7h13zbf"; + } + else { + "x86-linux" = "0iq34g4ypbdbwbaiyaicj0zj9c0xdryd6b3y0dxcdbhywjn32a5v"; + "x86_64-linux" = "09nb2n4mn1bp4iprgrks3d2chxsd7a5wzys969vfxxc54y9bhy8x"; + "x86_64-darwin" = "1nr75mygiza6l5v3daqsf2cpb47hkjpv0mnajlhacpf24cvvxlkb"; + }; + +in stdenv.mkDerivation (rec { + name = "apm-server-${optionalString (!enableUnfree) "oss-"}${version}"; + version = elk6Version; + + src = fetchurl { + url = "https://artifacts.elastic.co/downloads/apm-server/${name}-${plat}-${arch}.tar.gz"; + sha256 = shas."${stdenv.hostPlatform.system}" or (throw "Unknown architecture"); + }; + + buildInputs = [ makeWrapper ]; + + installPhase = '' + mkdir -p $out/{bin,share} + cp apm-server $out/bin + cp -r apm-server.yml fields.yml kibana $out/share + ''; + + passthru = { inherit enableUnfree; }; + + meta = { + description = "Open Source Application Performance Monitoring"; + license = if enableUnfree then licenses.elastic else licenses.asl20; + platforms = platforms.unix; + }; +} // optionalAttrs enableUnfree { + dontPatchELF = true; + nativeBuildInputs = [ autoPatchelfHook ]; + postFixup = '' + for exe in $(find $out/bin -executable -type f); do + echo "patching $exe..." + patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" "$exe" + done + ''; +}) diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 90daabcf890f8..71a88ec9269fd 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -2534,6 +2534,11 @@ in callPackage ../servers/search/elasticsearch/plugins.nix { } ); + elastic-apm-server = callPackage ../servers/misc/elastic-apm-server { }; + elastic-apm-server-oss = callPackage ../servers/misc/elastic-apm-server { + enableUnfree = false; + }; + embree2 = callPackage ../development/libraries/embree/2.x.nix { }; emem = callPackage ../applications/misc/emem { };