diff --git a/metricbeat/_meta/beat.full.yml b/metricbeat/_meta/beat.full.yml index f4065b153c0d..96967f3a20d7 100644 --- a/metricbeat/_meta/beat.full.yml +++ b/metricbeat/_meta/beat.full.yml @@ -103,7 +103,7 @@ metricbeat.modules: #------------------------------- Docker Module ------------------------------- #- module: docker - #metricsets: ["cpu", "info", "memory", "network", "diskio", "container"] + #metricsets: ["container", "cpu", "diskio", "healthcheck", "info", "memory", "network"] #hosts: ["unix:///var/run/docker.sock"] #enabled: true #period: 10s diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 1afc271c34d4..194c04690f7b 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -1171,6 +1171,68 @@ type: scaled_float Number of reads and writes combined. +[float] +== healthcheck Fields + +Docker container metrics. + + + +[float] +=== docker.healthcheck.failingstreak + +type: integer + +concurent failed check + + +[float] +=== docker.healthcheck.status + +type: keyword + +Healthcheck status code + + +[float] +== event Fields + +event fields. + + + +[float] +=== docker.healthcheck.event.end_date + +type: date + +Healthcheck end date + + +[float] +=== docker.healthcheck.event.start_date + +type: date + +Healthcheck start date + + +[float] +=== docker.healthcheck.event.output + +type: keyword + +Healthcheck output + + +[float] +=== docker.healthcheck.event.exit_code + +type: integer + +Healthcheck status code + + [float] == info Fields diff --git a/metricbeat/docs/modules/docker.asciidoc b/metricbeat/docs/modules/docker.asciidoc index e743c67e7037..2388f6ea9e0b 100644 --- a/metricbeat/docs/modules/docker.asciidoc +++ b/metricbeat/docs/modules/docker.asciidoc @@ -21,7 +21,7 @@ in <>. Here is an example configuration: ---- metricbeat.modules: #- module: docker - #metricsets: ["cpu", "info", "memory", "network", "diskio", "container"] + #metricsets: ["container", "cpu", "diskio", "healthcheck", "info", "memory", "network"] #hosts: ["unix:///var/run/docker.sock"] #enabled: true #period: 10s @@ -44,6 +44,8 @@ The following metricsets are available: * <> +* <> + * <> * <> @@ -56,6 +58,8 @@ include::docker/cpu.asciidoc[] include::docker/diskio.asciidoc[] +include::docker/healthcheck.asciidoc[] + include::docker/info.asciidoc[] include::docker/memory.asciidoc[] diff --git a/metricbeat/docs/modules/docker/healthcheck.asciidoc b/metricbeat/docs/modules/docker/healthcheck.asciidoc new file mode 100644 index 000000000000..220ba07630d9 --- /dev/null +++ b/metricbeat/docs/modules/docker/healthcheck.asciidoc @@ -0,0 +1,19 @@ +//// +This file is generated! See scripts/docs_collector.py +//// + +[[metricbeat-metricset-docker-healthcheck]] +include::../../../module/docker/healthcheck/_meta/docs.asciidoc[] + + +==== Fields + +For a description of each field in the metricset, see the +<> section. + +Here is an example document generated by this metricset: + +[source,json] +---- +include::../../../module/docker/healthcheck/_meta/data.json[] +---- diff --git a/metricbeat/include/list.go b/metricbeat/include/list.go index 3d88f2937d8d..948863c80dd8 100644 --- a/metricbeat/include/list.go +++ b/metricbeat/include/list.go @@ -18,6 +18,7 @@ import ( _ "github.com/elastic/beats/metricbeat/module/docker/container" _ "github.com/elastic/beats/metricbeat/module/docker/cpu" _ "github.com/elastic/beats/metricbeat/module/docker/diskio" + _ "github.com/elastic/beats/metricbeat/module/docker/healthcheck" _ "github.com/elastic/beats/metricbeat/module/docker/info" _ "github.com/elastic/beats/metricbeat/module/docker/memory" _ "github.com/elastic/beats/metricbeat/module/docker/network" diff --git a/metricbeat/metricbeat.full.yml b/metricbeat/metricbeat.full.yml index 683008110ede..f6bf6e4381f6 100644 --- a/metricbeat/metricbeat.full.yml +++ b/metricbeat/metricbeat.full.yml @@ -103,7 +103,7 @@ metricbeat.modules: #------------------------------- Docker Module ------------------------------- #- module: docker - #metricsets: ["cpu", "info", "memory", "network", "diskio", "container"] + #metricsets: ["container", "cpu", "diskio", "healthcheck", "info", "memory", "network"] #hosts: ["unix:///var/run/docker.sock"] #enabled: true #period: 10s diff --git a/metricbeat/metricbeat.template-es2x.json b/metricbeat/metricbeat.template-es2x.json index f3d85fbb414d..cf971336028a 100644 --- a/metricbeat/metricbeat.template-es2x.json +++ b/metricbeat/metricbeat.template-es2x.json @@ -668,6 +668,36 @@ } } }, + "healthcheck": { + "properties": { + "event": { + "properties": { + "end_date": { + "type": "date" + }, + "exit_code": { + "type": "long" + }, + "output": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + }, + "start_date": { + "type": "date" + } + } + }, + "failingstreak": { + "type": "long" + }, + "status": { + "ignore_above": 1024, + "index": "not_analyzed", + "type": "string" + } + } + }, "info": { "properties": { "containers": { diff --git a/metricbeat/metricbeat.template.json b/metricbeat/metricbeat.template.json index d42d1cd726dd..b270e0188c9f 100644 --- a/metricbeat/metricbeat.template.json +++ b/metricbeat/metricbeat.template.json @@ -670,6 +670,34 @@ } } }, + "healthcheck": { + "properties": { + "event": { + "properties": { + "end_date": { + "type": "date" + }, + "exit_code": { + "type": "long" + }, + "output": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_date": { + "type": "date" + } + } + }, + "failingstreak": { + "type": "long" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, "info": { "properties": { "containers": { diff --git a/metricbeat/module/docker/_meta/config.yml b/metricbeat/module/docker/_meta/config.yml index 745c6c91e590..f2440a78afdf 100644 --- a/metricbeat/module/docker/_meta/config.yml +++ b/metricbeat/module/docker/_meta/config.yml @@ -1,5 +1,5 @@ #- module: docker - #metricsets: ["cpu", "info", "memory", "network", "diskio", "container"] + #metricsets: ["container", "cpu", "diskio", "healthcheck", "info", "memory", "network"] #hosts: ["unix:///var/run/docker.sock"] #enabled: true #period: 10s diff --git a/metricbeat/module/docker/container/_meta/data.json b/metricbeat/module/docker/container/_meta/data.json index 83ca1c4849df..5fec882399e7 100644 --- a/metricbeat/module/docker/container/_meta/data.json +++ b/metricbeat/module/docker/container/_meta/data.json @@ -32,4 +32,4 @@ "rtt": 115 }, "type": "metricsets" -} \ No newline at end of file +} diff --git a/metricbeat/module/docker/container/data.go b/metricbeat/module/docker/container/data.go index 86d4d17dd876..199cb4df2aef 100644 --- a/metricbeat/module/docker/container/data.go +++ b/metricbeat/module/docker/container/data.go @@ -32,6 +32,7 @@ func eventMapping(cont *dc.APIContainers) common.MapStr { } labels := docker.DeDotLabels(cont.Labels) + if len(labels) > 0 { event["labels"] = labels } diff --git a/metricbeat/module/docker/healthcheck/_meta/data.json b/metricbeat/module/docker/healthcheck/_meta/data.json new file mode 100644 index 000000000000..2b33980faa22 --- /dev/null +++ b/metricbeat/module/docker/healthcheck/_meta/data.json @@ -0,0 +1,26 @@ +{ + "@timestamp": "2016-05-23T08:05:34.853Z", + "beat": { + "hostname": "host.example.com", + "name": "host.example.com" + }, + "docker": { + "healthcheck": { + "failingstreak": 0, + "status": "healthy", + "event": { + "end_date": "2017-01-09T20:38:13.080472813+01:00", + "exit_code": 0, + "output": "this is an event output", + "start_date": "2017-01-09T20:38:12.999970865+01:00", + } + } + }, + "metricset": { + "host": "/var/run/docker.sock", + "module": "docker", + "name": "container", + "rtt": 115 + }, + "type": "metricsets" +} diff --git a/metricbeat/module/docker/healthcheck/_meta/docs.asciidoc b/metricbeat/module/docker/healthcheck/_meta/docs.asciidoc new file mode 100644 index 000000000000..bfcf644a1069 --- /dev/null +++ b/metricbeat/module/docker/healthcheck/_meta/docs.asciidoc @@ -0,0 +1,4 @@ +=== Docker healthcheck Metricset + +The Docker `healthcheck` metricset collects information and statistics about +running Docker containers. diff --git a/metricbeat/module/docker/healthcheck/_meta/fields.yml b/metricbeat/module/docker/healthcheck/_meta/fields.yml new file mode 100644 index 000000000000..0b37d42f13b8 --- /dev/null +++ b/metricbeat/module/docker/healthcheck/_meta/fields.yml @@ -0,0 +1,34 @@ +- name: healthcheck + type: group + description: > + Docker container metrics. + fields: + - name: failingstreak + type: integer + description: > + concurent failed check + - name: status + type: keyword + description: > + Healthcheck status code + - name: event + type: group + description: > + event fields. + fields: + - name: end_date + type: date + description: > + Healthcheck end date + - name: start_date + type: date + description: > + Healthcheck start date + - name: output + type: keyword + description: > + Healthcheck output + - name: exit_code + type: integer + description: > + Healthcheck status code diff --git a/metricbeat/module/docker/healthcheck/container_integration_test.go b/metricbeat/module/docker/healthcheck/container_integration_test.go new file mode 100644 index 000000000000..8c1356f7366c --- /dev/null +++ b/metricbeat/module/docker/healthcheck/container_integration_test.go @@ -0,0 +1,25 @@ +// +build integration + +package healthcheck + +import ( + "testing" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" +) + +func TestData(t *testing.T) { + f := mbtest.NewEventsFetcher(t, getConfig()) + err := mbtest.WriteEvents(f, t) + if err != nil { + t.Fatal("write", err) + } +} + +func getConfig() map[string]interface{} { + return map[string]interface{}{ + "module": "docker", + "metricsets": []string{"healthcheck"}, + "hosts": []string{"unix:///var/run/docker.sock"}, + } +} diff --git a/metricbeat/module/docker/healthcheck/data.go b/metricbeat/module/docker/healthcheck/data.go new file mode 100644 index 000000000000..fedce1739c9d --- /dev/null +++ b/metricbeat/module/docker/healthcheck/data.go @@ -0,0 +1,52 @@ +package healthcheck + +import ( + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/metricbeat/mb" + "github.com/elastic/beats/metricbeat/module/docker" + + dc "github.com/fsouza/go-dockerclient" + "strings" +) + +func eventsMapping(containersList []dc.APIContainers, m *MetricSet) []common.MapStr { + myEvents := []common.MapStr{} + for _, container := range containersList { + returnevent := eventMapping(&container, m) + // Compare event to empty event + if returnevent != nil { + myEvents = append(myEvents, returnevent) + } + } + return myEvents +} + +func eventMapping(cont *dc.APIContainers, m *MetricSet) common.MapStr { + event := common.MapStr{} + // Detect if healthcheck is available for container + if strings.Contains(cont.Status, "(") && strings.Contains(cont.Status, ")") { + container, _ := m.dockerClient.InspectContainer(cont.ID) + last_event := len(container.State.Health.Log) - 1 + // Detect if an healthcheck already occured + if last_event >= 0 { + event = common.MapStr{ + mb.ModuleData: common.MapStr{ + "container": common.MapStr{ + "name": docker.ExtractContainerName(cont.Names), + "id": cont.ID, + }, + }, + "status": container.State.Health.Status, + "failingstreak": container.State.Health.FailingStreak, + "event": common.MapStr{ + "start_date": common.Time(container.State.Health.Log[last_event].Start), + "end_date": common.Time(container.State.Health.Log[last_event].End), + "exit_code": container.State.Health.Log[last_event].ExitCode, + "output": container.State.Health.Log[last_event].Output, + }, + } + return event + } + } + return nil +} diff --git a/metricbeat/module/docker/healthcheck/healthcheck.go b/metricbeat/module/docker/healthcheck/healthcheck.go new file mode 100644 index 000000000000..f487e2708bbe --- /dev/null +++ b/metricbeat/module/docker/healthcheck/healthcheck.go @@ -0,0 +1,52 @@ +package healthcheck + +import ( + dc "github.com/fsouza/go-dockerclient" + + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/metricbeat/mb" + "github.com/elastic/beats/metricbeat/module/docker" +) + +func init() { + if err := mb.Registry.AddMetricSet("docker", "healthcheck", New, docker.HostParser); err != nil { + panic(err) + } +} + +type MetricSet struct { + mb.BaseMetricSet + dockerClient *dc.Client +} + +// New creates a new instance of the docker healthcheck MetricSet. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + logp.Warn("EXPERIMENTAL: The docker healthcheck metricset is experimental") + + config := docker.Config{} + if err := base.Module().UnpackConfig(&config); err != nil { + return nil, err + } + + client, err := docker.NewDockerClient(base.HostData().URI, config) + if err != nil { + return nil, err + } + + return &MetricSet{ + BaseMetricSet: base, + dockerClient: client, + }, nil +} + +// Fetch returns a list of all containers as events. +// This is based on https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/list-containers. +func (m *MetricSet) Fetch() ([]common.MapStr, error) { + // Fetch a list of all containers. + containers, err := m.dockerClient.ListContainers(dc.ListContainersOptions{}) + if err != nil { + return nil, err + } + return eventsMapping(containers, m), nil +}