At Elastic we use elastic-package on a daily basis to create and maintain Integrations. Today we’ll learn how to quickly bootstrap a new Elastic Integration using a built-in creator, and how to start observing the service.
Elastic Integrations are a new way of using Elastic Agents to observe logs and metrics. By using Internet of Things smart devices, you can track data like electricity consumption, gas, or water usage in your household. However, there are countless IoT devices out there, and they all have proprietary apps or web pages to monitor data. What if you want to have a dashboard with all your IoT data in one place? That’s where Integrations come in.
Say you want to monitor your home’s water usage for leaks. An Elastic Integration makes this easy. Your IoT device has a REST endpoint with a data feed. By using an Elastic integration to create a smart pipeline to Kibana Alerts, you’ve got your service. Repeat as needed and you’ve got a dashboard to monitor your home. Further, with a small investment you can build IoT detectors on your own using any prototyping platform (for example Arduino) and basic electronic components (like water sensors).
These Integrations don’t contain any Go code — all functionality is provided using YAML or JSON configuration. If writing configuration files sounds like an exhausting challenge, that’s where the built-in creator comes in, shortening the initial time for bootstrapping the package using an embedded package archetype.
If you have never heard of Elastic Agent or Elastic Integrations and want to learn more before jumping in, this blog post is a great place to start. Otherwise, let's dive in!
Review observed service
The candidate for implementing a new package will be the IPFS Node application, a client to the decentralized peer-to-peer (P2P) network for storing and sharing data in a distributed file system. It uses content-addressing to uniquely identify each file. The application can run in both, daemon and console modes, and it exposes few network ports:
4001 - default libp2p swarm port
5001 - API internal port, shouldn’t be exposed publicly
8080 - Gateway to serve content
The node application is also distributed as Docker image, which can be easily started:
$ docker run --rm --name ipfs-node -p 127.0.0.1:5001:5001 ipfs/go-ipfs@sha256:f7e30972e35a839ea8ce00c060412face29aa31624fd2dc87a5e696f99835a91
Changing user to ipfs
ipfs version 0.9.1
generating ED25519 keypair...done
peer identity:12D3KooWR6NcsnsW7bzaMnfYWL9D8f411P351TnfTX2D9pxRY74t
initializing IPFS node at /data/ipfs
to get started, enter:
ipfs cat /ipfs/QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc/readme
Initializing daemon...
go-ipfs version:0.9.1-dc2715a
Repo version:11System version: amd64/linux
Golang version: go1.15.22021/09/2713:01:00 failed to sufficiently increase receive buffer size (was:208 kiB, wanted:2048 kiB, got:416 kiB).See https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size for details.Swarm listening on /ip4/127.0.0.1/tcp/4001Swarm listening on /ip4/127.0.0.1/udp/4001/quic
Swarm listening on /ip4/172.17.0.2/tcp/4001Swarm listening on /ip4/172.17.0.2/udp/4001/quic
Swarm listening on /p2p-circuit
Swarm announcing /ip4/127.0.0.1/tcp/4001Swarm announcing /ip4/127.0.0.1/udp/4001/quic
Swarm announcing /ip4/172.17.0.2/tcp/4001Swarm announcing /ip4/172.17.0.2/udp/4001/quic
API server listening on /ip4/0.0.0.0/tcp/5001WebUI: http://0.0.0.0:5001/webuiGateway(readonly) server listening on /ip4/0.0.0.0/tcp/8080Daemonis ready
In this tutorial we will be interested in the API running on port 5001, which exposes following methods:
/api/v0/stats/bw - get IPFS bandwidth information
/api/v0/repo/stat - get statistics for currently used repository
Both API methods respond to POST calls:
$ curl -X POST http://127.0.0.1:5001/api/v0/stats/bw{"TotalIn":18136337,"TotalOut":944694,"RateIn":34125.006805975005,"RateOut":4088.311294056906}
$ curl -X POST http://127.0.0.1:5001/api/v0/repo/stat{"RepoSize":9321761,"StorageMax":10000000000,"NumObjects":95,"RepoPath":"/data/ipfs","Version":"fs-repo@11"}
The goal of this exercise is to create an integration which scraps metrics from the described API and collects standard application logs from the ipfs-node.
Bootstrap new "ipfs_node" package
Let's start with creating a new repository (or you can select your own place) to store the integration. Next, use the built-in creator and your new package:
$ elastic-package create packageCreate a newpackage?Package name: ipfs_node
?Version:0.0.1?Package title: IPFS Node?Description:Collect logs and metrics from IPFS node.?Categories: custom, network
?Release: experimental
?Kibana version constraint:^7.15.0?Github owner: mtojek
Newpackage has been created: ipfs_node
Done
The package has been created, but we also need three additional data streams - traffic (metrics), repository (metrics) and application (logs):
$ cd ipfs_node
$ elastic-package create data-stream
Create a new data stream
?Data stream name: traffic
?Data stream title:Traffic?Type: metrics
New data stream has been created: traffic
Done
$ elastic-package create data-stream
Create a new data stream
?Data stream name: repository
?Data stream title:Repository?Type: metrics
New data stream has been created: repository
Done
$ elastic-package create data-stream
Create a new data stream
?Data stream name: application
?Data stream title:Application logs
?Type: logs
New data stream has been created: application
Done
Once the creator finished its job, let’s check all files in the directory:
The package root contains 3 different data streams (traffic, repository and application), a basic README file, a changelog file, and sample graphics (icon and screenshot). Every data stream contains a manifest, an agent's stream definition, field definitions and an optional stub for ingest pipeline.
Adjust configuration of data streams
Now it's time to fill all the templates. Let's modify the package manifest (manifest.yml) and replace default policy templates with following:
policy_templates:- name: application
title: IPFS node logs and metrics
description:Collect IPFS node logs and metrics
inputs:- type: logfile
title:Collect application logs
description:Collecting application logs from IPFS node
- type: http/metrics
title:Collect application metrics
description:Collecting repository and traffic metrics from IPFS node
vars:- name: hosts
type: text
title:Hosts
description:Base URL of the internal endpoint
required:truedefault: http://localhost:5001
The package manifest describes two kinds of inputs - logfile and http/metrics. The logfile input is a standard filebeat's input, which allows for reading entries from files, but HTTP metrics is a Beats module, which fetches data from external HTTP endpoints (it supports JSON format). In the package manifest there can be defined common variables, which apply to multiple data streams - in this case we can keep the base URL to the IPFS node.
Let's define data streams:
application - read standard application logs
repository - read IPFS repository statistics
traffic - read bandwidth metrics for the node
Here is the data stream manifest for "application" data stream:
It's relatively small and defines one variable - paths (location of log files). For the purpose of this exercise we will not introduce more variables and only focus on the basic lifecycle of an integration. Once you modified the "application" data stream manifest, adjust manifests for "repository" and "traffic".
"Repository" data stream manifest:
title:"Repository"
type: metrics
streams:- input: http/metrics
title:Repository metrics
description:Collect repository metrics from IPFS node
vars:- name: period
type: text
title:Perioddefault:10s
"Traffic" data stream manifest:
title:"Traffic"
type: metrics
streams:- input: http/metrics
title:Traffic metrics
description:Collect bandwidth metrics from IPFS node
vars:- name: period
type: text
title:Perioddefault:10s
Both manifests define a single variable - period, which defines the delay between consecutive metrics fetch operations. All manifest files are used by Fleet UI (Kibana) to render configuration forms of the Integration:
Let's adjust the agent's stream configuration files. It's the configuration which is passed down to the Elastic Agent instance to reconfigure supervised filebeat and metricbeat processes. The agent stream for the "application" uses the standard file input:
paths:{{#each paths as|path i|}}-{{path}}{{/each}}
exclude_files:[".gz$"]
processors:- add_locale:~The agent stream for the “repository” uses the HTTP modulewith enabled JSON metricset:
metricsets:["json"]
hosts:{{#each hosts}}-{{this}}/api/v0/repo/stat
{{/each}}
period:{{period}}
method:"POST"namespace:"repository"
The "application" data stream pushes logs to an ingest pipeline which runs in Elasticsearch. It can transform logs in multiple ways - skip fields, add new fields, trim content, replace values conditionally, etc. For the purpose of this exercise we will not introduce a complex processing:
---
description:Pipelinefor processing sample logs
processors:-set:
field: ecs.version
value:'1.11.0'- trim:
field: message
- drop:
description:'Drop if the log message is empty'if: ctx.message ==''
on_failure:-set:
field: error.message
value:'{{ _ingest.on_failure_message }}'
The core package files are ready now, so it's a good moment to run few extra commands:
elastic-package format - to format the package source code elastic-package lint - to double-check if all files are inline with package-spec
elastic-package build - to build the integration package (mind that this will create the build directory with a built package)
Once every command passed successfully we can switch to testing. The elastic-package tool can boot up locally the Elastic stack for development and testing purposes. The stack consists of Docker containers for Elasticsearch, Kibana, Fleet Server and Elastic Agent. As all contantainers run in the same network, it's a good idea to run the IPFS node in a container belonging to the same Docker network.
Create _dev/deploy/docker directory in the package root and add place following files:
#!/bin/sh/usr/local/bin/start_ipfs daemon --migrate=true| tee /var/log/ipfs/ipfs-node-0.log
Dockerfile
FROM ipfs/go-ipfs@sha256:f7e30972e35a839ea8ce00c060412face29aa31624fd2dc87a5e696f99835a91
RUN mkdir -p /var/log/ipfs
ADD docker-entrypoint.sh /
ENV IPFS_LOGGING "info"
ENTRYPOINT ["/docker-entrypoint.sh"]
We will use them in system tests to boot up an instance of an IPFS node in the Docker network and make it observable by Elastic Agent. Now it’s the time to boot the Elastic stack. Navigate to the package root and run the command:
elastic-package stack up -d
The tool will discover the locally built package and include it in the Package Registry - see command output:
Custom build packages directory found:/Users/marcin.tojek/go/src/github.com/mtojek/elastic-blog-posts/build/integrations
Packagesfrom the following directories will be loaded into the package-registry:- built-in packages (package-storage:snapshot Docker image)-/Users/marcin.tojek/go/src/github.com/mtojek/elastic-blog-posts/build/integrations
[{"name":"ipfs_node","title":"IPFS Node","version":"0.0.1","release":"experimental","description":"Collect logs and metrics from IPFS node.","type":"integration","download":"/epr/ipfs_node/ipfs_node-0.0.1.zip","path":"/package/ipfs_node/0.0.1","icons":[{"src":"/img/sample-logo.svg","path":"/package/ipfs_node/0.0.1/img/sample-logo.svg","title":"Sample logo","size":"32x32","type":"image/svg+xml"}],"policy_templates":[{"name":"application","title":"IPFS node logs and metrics","description":"Collect IPFS node logs and metrics"}]}]
Navigate to the local Kibana panel: http://localhost:5601 (login: elastic, password: changeme), visit the Integrations page and confirm that the IPFS node package is present. Click on the “Add IPFS node” button to see the configuration form (rendered from manifests):
Let's prepare a few system tests to verify the package life. A system test consists of multiple steps (automatically executed by the test runner):
Create a new policy defining the Integration.
Assign the policy to the agent.
Wait until the agent pushes events to Elasticsearch.
Verify if events are correct.
Create one system test definition for "application" data stream in data_stream/application/_dev/test/system/test-default-config.yml:
This system test definition defines the value for "paths" to observe. The SERVICE_LOGS_DIR env is a directory mounted in the Elastic Agent (source: service container with IPFS node).
Create two system test definitions for "repository" data stream in data_stream/repository/_dev/test/system/test-default-config.yml and data_stream/traffic/_dev/test/system/test-default-config.yml (same content):
It will take some time to run all tests, but the tool reports progress and current activity. After a few minutes it will end up with … a failure similar to this:
2021/09/2816:57:28 DEBUG deleting data in data stream...---Test results forpackage: ipfs_node - START ---
FAILURE DETAILS:
ipfs_node/traffic default:[0] field "ecs.version"isundefined[1] field "http.traffic.RateIn"isundefined[2] field "http.traffic.RateOut"isundefined[3] field "http.traffic.TotalIn"isundefined[4] field "http.traffic.TotalOut"isundefined[5] field "service.address"isundefined[6] field "service.type"isundefined
We haven't added any field definition in data streams, so let's start with enabling the dependency manager to pull in ECS fields. Create the _dev/build/build.yml file in the package root:
As we proved that our integration collects data and even stored a few sample events, it’s recommended to present them in the README. The elastic-package provides basic rendering options to render the table with fields and sample events. Let’s create a README template (_dev/build/docs/README.md) in the package root:
# IPFS NodeThisis a new integration created using the [elastic-package](https://github.com/elastic/elastic-package) tool.## Application logs{{event"application"}}{{fields "application"}}## Repository metrics{{event"repository"}}{{fields "repository"}}## Traffic metrics{{event"traffic"}}{{fields "traffic"}}
Rebuild the package with elastic-packagedocs/README.md.
Congratulations, you have just built your first integration.
Troubleshooting the integration
It may happen that complex ingest configuration will require a few minutes of debugging. The elastic-package exposes a dedicated feature to dump all logs from the Elastic stack. With the stack up and running, run the following command:
elastic-package stack dump
The tool will pull logs from Docker containers including internal files and place together in the directory:
Look around for possible errors, configuration issues or stack problems. If you notice that the Elastic Agent doesn't process any logs, it's worth looking into filebeat and metricbeat logs.
Browse metrics in Elasticsearch
There is an option to check documents processed by the Agent running during system tests. Restart system tests with an extra flag to defer cleanup by 5 minutes: elastic-package test system --data-streams repository --defer-cleanup 5m -v
Observe the output of elastic-package until you notice the "waiting" confirmation:
2021/09/2818:19:07 DEBUG Policy revision assigned to the agent (ID: cae28981-0d96-434d-aa3f-55a63e37f6f9)...2021/09/2818:19:07 DEBUG checking for expected data in data stream...2021/09/2818:19:07 DEBUG found 0 hits in metrics-ipfs_node.repository-ep data stream
2021/09/2818:19:08 DEBUG found 0 hits in metrics-ipfs_node.repository-ep data stream
2021/09/2818:19:09 DEBUG found 0 hits in metrics-ipfs_node.repository-ep data stream
2021/09/2818:19:10 DEBUG found 0 hits in metrics-ipfs_node.repository-ep data stream
2021/09/2818:19:11 DEBUG found 1 hits in metrics-ipfs_node.repository-ep data stream
2021/09/2818:19:11 DEBUG waiting for5m0s before tearing down...
Feel free to review existing indices using Elasticsearch endpoint - http://localhost:9200/_cat/indices (login: elastic, password: changeme):
green open .tasks aB4Wp5GtRGWtMomVKVIKvQ 102013.8kb13.8kb
yellow open .ds-metrics-system.process.summary-default-2021.09.28-000001XRf77R7XS2KKrqnffegb6w11108101.2mb1.2mb
green open .security-74VEaX_a0TEuNDDRWr4W14g 101176292.8kb292.8kb
yellow open .ds-metrics-ipfs_node.repository-ep-2021.09.28-000001GlmMwE9-SAipRygU4sI88Q1180237.7kb237.7kb
yellow open .ds-logs-ipfs_node.application-ep-2021.09.28-000001-BWSONvSQxmJZCbF1GtmnQ110067.4kb67.4kb
yellow open .ds-metrics-elastic_agent.metricbeat-default-2021.09.28-000001 vWNedZJeQaeG35bO5UorDQ 11216202mb2mb
yellow open .ds-metrics-system.uptime-default-2021.09.28-0000017kfIHzAqTdSLQEhPc72sMw11108101.1mb1.1mb
green open .kibana-event-log-7.15.0-snapshot-000001 vqx_yozOSeekpJ_h2zdiCQ 102011.9kb11.9kb
green open .fleet-policies-7
Let's review documents in the .ds-metrics-ipfs_node.repository-ep-2021.09.28-000001 index:
That's all folks! You managed to create your first integration, cover it with system tests and verify integrity with a real service (not mocked files). Feel free to build more Elastic Integrations and submit them to our repository, Elastic Integrations.
The source code for this tutorial can be also found here.