diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index fc04997bd2e..0c9a7055aa7 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -594,6 +594,7 @@ ./services/monitoring/loki.nix ./services/monitoring/longview.nix ./services/monitoring/mackerel-agent.nix + ./services/monitoring/metricbeat.nix ./services/monitoring/monit.nix ./services/monitoring/munin.nix ./services/monitoring/nagios.nix diff --git a/nixos/modules/services/monitoring/metricbeat.nix b/nixos/modules/services/monitoring/metricbeat.nix new file mode 100644 index 00000000000..b285559eaa9 --- /dev/null +++ b/nixos/modules/services/monitoring/metricbeat.nix @@ -0,0 +1,152 @@ +{ config, lib, pkgs, ... }: + +let + inherit (lib) + attrValues + literalExample + mkEnableOption + mkIf + mkOption + types + ; + cfg = config.services.metricbeat; + + settingsFormat = pkgs.formats.yaml {}; + +in +{ + options = { + + services.metricbeat = { + + enable = mkEnableOption "metricbeat"; + + package = mkOption { + type = types.package; + default = pkgs.metricbeat; + defaultText = literalExample "pkgs.metricbeat"; + example = literalExample "pkgs.metricbeat7"; + description = '' + The metricbeat package to use + ''; + }; + + modules = mkOption { + description = '' + Metricbeat modules are responsible for reading metrics from the various sources. + + This is like services.metricbeat.settings.metricbeat.modules, + but structured as an attribute set. This has the benefit that multiple + NixOS modules can contribute settings to a single metricbeat module. + + A module can be specified multiple times by choosing a different <name> + for each, but setting to the same value. + + See . + ''; + default = {}; + type = types.attrsOf (types.submodule ({ name, ... }: { + freeformType = settingsFormat.type; + options = { + module = mkOption { + type = types.str; + default = name; + defaultText = literalExample ''''; + description = '' + The name of the module. + + Look for the value after module: on the individual + module pages linked from . + ''; + }; + }; + })); + example = { + system = { + metricsets = ["cpu" "load" "memory" "network" "process" "process_summary" "uptime" "socket_summary"]; + enabled = true; + period = "10s"; + processes = [".*"]; + cpu.metrics = ["percentages" "normalized_percentages"]; + core.metrics = ["percentages"]; + }; + }; + }; + + settings = mkOption { + type = types.submodule { + freeformType = settingsFormat.type; + options = { + + name = mkOption { + type = types.str; + default = ""; + description = '' + Name of the beat. Defaults to the hostname. + See . + ''; + }; + + tags = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Tags to place on the shipped metrics. + See . + ''; + }; + + metricbeat.modules = mkOption { + type = types.listOf settingsFormat.type; + default = []; + internal = true; + description = '' + The metric collecting modules. Use instead. + + See . + ''; + }; + }; + }; + default = {}; + description = '' + Configuration for metricbeat. See for supported values. + ''; + }; + + }; + }; + + config = mkIf cfg.enable { + + assertions = [ + { + # empty modules would cause a failure at runtime + assertion = cfg.settings.metricbeat.modules != []; + message = "services.metricbeat: You must configure one or more modules."; + } + ]; + + services.metricbeat.settings.metricbeat.modules = attrValues cfg.modules; + + systemd.services.metricbeat = { + description = "metricbeat metrics shipper"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = '' + ${cfg.package}/bin/metricbeat \ + -c ${settingsFormat.generate "metricbeat.yml" cfg.settings} \ + --path.data $STATE_DIRECTORY \ + --path.logs $LOGS_DIRECTORY \ + ; + ''; + Restart = "always"; + DynamicUser = true; + ProtectSystem = "strict"; + ProtectHome = "tmpfs"; + StateDirectory = "metricbeat"; + LogsDirectory = "metricbeat"; + }; + }; + }; +} diff --git a/nixos/tests/elk.nix b/nixos/tests/elk.nix index 71d39a647a5..2a1a4cba295 100644 --- a/nixos/tests/elk.nix +++ b/nixos/tests/elk.nix @@ -56,6 +56,24 @@ let ''); }; + metricbeat = { + enable = true; + package = elk.metricbeat; + modules.system = { + metricsets = ["cpu" "load" "memory" "network" "process" "process_summary" "uptime" "socket_summary"]; + enabled = true; + period = "5s"; + processes = [".*"]; + cpu.metrics = ["percentages" "normalized_percentages"]; + core.metrics = ["percentages"]; + }; + settings = { + output.elasticsearch = { + hosts = ["127.0.0.1:9200"]; + }; + }; + }; + logstash = { enable = true; package = elk.logstash; @@ -135,6 +153,16 @@ let ) + def has_metricbeat(): + dictionary = {"query": {"match": {"event.dataset": {"query": "system.cpu"}}}} + return ( + "curl --silent --show-error '${esUrl}/_search' " + + "-H 'Content-Type: application/json' " + + "-d '{}' ".format(json.dumps(dictionary)) + + "| jq '.hits.total > 0'" + ) + + start_all() one.wait_for_unit("elasticsearch.service") @@ -161,6 +189,12 @@ let "curl --silent --show-error 'http://localhost:5601/api/status' | jq .status.overall.state | grep green" ) + with subtest("Metricbeat is running"): + one.wait_for_unit("metricbeat.service") + + with subtest("Metricbeat metrics arrive in elasticsearch"): + one.wait_until_succeeds(has_metricbeat() + " | tee /dev/console | grep 'true'") + with subtest("Logstash messages arive in elasticsearch"): one.wait_until_succeeds(total_hits("flowers") + " | grep -v 0") one.wait_until_succeeds(total_hits("dragons") + " | grep 0") @@ -190,12 +224,14 @@ in pkgs.lib.mapAttrs mkElkTest { logstash = pkgs.logstash6; kibana = pkgs.kibana6; journalbeat = pkgs.journalbeat6; + metricbeat = pkgs.metricbeat6; } else { elasticsearch = pkgs.elasticsearch6-oss; logstash = pkgs.logstash6-oss; kibana = pkgs.kibana6-oss; journalbeat = pkgs.journalbeat6; + metricbeat = pkgs.metricbeat6; }; ELK-7 = if enableUnfree @@ -204,11 +240,13 @@ in pkgs.lib.mapAttrs mkElkTest { logstash = pkgs.logstash7; kibana = pkgs.kibana7; journalbeat = pkgs.journalbeat7; + metricbeat = pkgs.metricbeat7; } else { elasticsearch = pkgs.elasticsearch7-oss; logstash = pkgs.logstash7-oss; kibana = pkgs.kibana7-oss; journalbeat = pkgs.journalbeat7; + metricbeat = pkgs.metricbeat7; }; } diff --git a/pkgs/misc/logging/beats/6.x.nix b/pkgs/misc/logging/beats/6.x.nix index ce80b174d32..81b8ba0ab5b 100644 --- a/pkgs/misc/logging/beats/6.x.nix +++ b/pkgs/misc/logging/beats/6.x.nix @@ -1,4 +1,4 @@ -{ lib, fetchFromGitHub, elk6Version, buildGoPackage, libpcap, systemd }: +{ lib, fetchFromGitHub, elk6Version, buildGoPackage, libpcap, nixosTests, systemd }: let beat = package : extraArgs : buildGoPackage (rec { name = "${package}-${version}"; @@ -22,10 +22,17 @@ let beat = package : extraArgs : buildGoPackage (rec { platforms = platforms.linux; }; } // extraArgs); -in { +in rec { filebeat6 = beat "filebeat" {meta.description = "Lightweight shipper for logfiles";}; heartbeat6 = beat "heartbeat" {meta.description = "Lightweight shipper for uptime monitoring";}; - metricbeat6 = beat "metricbeat" {meta.description = "Lightweight shipper for metrics";}; + metricbeat6 = beat "metricbeat" { + meta.description = "Lightweight shipper for metrics"; + passthru.tests = + assert metricbeat6.drvPath == nixosTests.elk.ELK-6.elkPackages.metricbeat.drvPath; + { + elk = nixosTests.elk.ELK-6; + }; + }; packetbeat6 = beat "packetbeat" { buildInputs = [ libpcap ]; meta.broken = true; diff --git a/pkgs/misc/logging/beats/7.x.nix b/pkgs/misc/logging/beats/7.x.nix index 43ea85508c6..77e14e96c54 100644 --- a/pkgs/misc/logging/beats/7.x.nix +++ b/pkgs/misc/logging/beats/7.x.nix @@ -1,4 +1,4 @@ -{ lib, fetchFromGitHub, elk7Version, buildGoPackage, libpcap, systemd }: +{ lib, fetchFromGitHub, elk7Version, buildGoPackage, libpcap, nixosTests, systemd }: let beat = package : extraArgs : buildGoPackage (rec { name = "${package}-${version}"; @@ -22,10 +22,17 @@ let beat = package : extraArgs : buildGoPackage (rec { platforms = platforms.linux; }; } // extraArgs); -in { +in rec { filebeat7 = beat "filebeat" {meta.description = "Lightweight shipper for logfiles";}; heartbeat7 = beat "heartbeat" {meta.description = "Lightweight shipper for uptime monitoring";}; - metricbeat7 = beat "metricbeat" {meta.description = "Lightweight shipper for metrics";}; + metricbeat7 = beat "metricbeat" { + meta.description = "Lightweight shipper for metrics"; + passthru.tests = + assert metricbeat7.drvPath == nixosTests.elk.ELK-7.elkPackages.metricbeat.drvPath; + { + elk = nixosTests.elk.ELK-7; + }; + }; packetbeat7 = beat "packetbeat" { buildInputs = [ libpcap ]; meta.description = "Network packet analyzer that ships data to Elasticsearch";