Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cherry-pick #14823 to 7.x: Add autodiscover for aws_ec2 #16184

Merged
merged 1 commit into from
Feb 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d
- Fingerprint processor adds a new xxhash hashing algorithm {pull}15418[15418]
- Enable DEP (Data Execution Protection) for Windows packages. {pull}15149[15149]
- Add document_id setting to decode_json_fields processor. {pull}15859[15859]

- Add `aws_ec2` provider for autodiscover. {issue}12518[12518] {pull}14823[14823]

*Auditbeat*

Expand Down
45 changes: 45 additions & 0 deletions libbeat/docs/shared-autodiscover.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,51 @@ ifdef::autodiscoverHints[]
include::../../{beatname_lc}/docs/autodiscover-hints.asciidoc[]
endif::autodiscoverHints[]

ifdef::autodiscoverAWSEC2[]
[float]
===== Amazon EC2s

*Note: This provider is experimental*

The Amazon EC2 autodiscover provider discovers https://aws.amazon.com/ec2/[EC2 instances].
This is useful for users to launch Metricbeat modules to monitor services running on AWS EC2 instances.
For example, to gather MySQL metrics from mysql servers running on EC2 instances with specific tag `service: mysql`.

This provider will load AWS credentials using the standard AWS environment variables and shared credentials files
see https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html[Best Practices for Managing AWS Access Keys]
for more information. If you do not wish to use these, you may explicitly set the `access_key_id` and
`secret_access_key` variables.

These are the available fields during within config templating.
The `aws.ec2.*` fields and `cloud.*` fields will be available on each emitted event.

* cloud.availability_zone
* cloud.instance.id
* cloud.machine.type
* cloud.provider
* cloud.region

* aws.ec2.architecture
* aws.ec2.image.id
* aws.ec2.kernel.id
* aws.ec2.monitoring.state
* aws.ec2.private.dns_name
* aws.ec2.private.ip
* aws.ec2.public.dns_name
* aws.ec2.public.ip
* aws.ec2.root_device_name
* aws.ec2.state.code
* aws.ec2.state.name
* aws.ec2.subnet.id
* aws.ec2.tags
* aws.ec2.vpc.id

include::../../{beatname_lc}/docs/autodiscover-aws-ec2-config.asciidoc[]

This autodiscover provider takes our standard <<aws-credentials-config,AWS credentials options>>.

endif::autodiscoverAWSEC2[]

[[configuration-autodiscover-advanced]]
=== Advanced usage

Expand Down
25 changes: 25 additions & 0 deletions metricbeat/docs/autodiscover-aws-ec2-config.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{beatname_uc} supports templates for modules:

["source","yaml",subs="attributes"]
-------------------------------------------------------------------------------------
metricbeat.autodiscover:
providers:
- type: aws_ec2
period: 1m
credential_profile_name: elastic-beats
templates:
- condition:
equals:
aws.ec2.tags.service: "mysql"
config:
- module: mysql
metricsets: ["status", "galera_status"]
period: 10s
hosts: ["root:password@tcp(${data.aws.ec2.public.ip}:3306)/"]
username: root
password: password
-------------------------------------------------------------------------------------

This autodiscover provider takes our standard AWS credentials options.
With this configuration, `mysql` metricbeat module will be launched for all EC2
instances that have `service: mysql` as a tag.
2 changes: 2 additions & 0 deletions metricbeat/docs/configuring-howto.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ include::{libbeat-dir}/shared-env-vars.asciidoc[]

:autodiscoverJolokia:
:autodiscoverHints:
:autodiscoverAWSEC2:
include::{libbeat-dir}/shared-autodiscover.asciidoc[]
:autodiscoverAWSEC2!:

:standalone:
include::{libbeat-dir}/yaml.asciidoc[]
Expand Down
35 changes: 35 additions & 0 deletions x-pack/libbeat/autodiscover/providers/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,38 @@
// you may not use this file except in compliance with the Elastic License.

package aws

import (
"context"

"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/ec2iface"
"github.com/pkg/errors"
)

// SafeString makes handling AWS *string types easier.
// The AWS lib never returns plain strings, always using pointers, probably for memory efficiency reasons.
// This is a bit odd, because strings are just pointers into byte arrays, however this is the choice they've made.
// This will return the plain version of the given string or an empty string if the pointer is null
func SafeString(str *string) string {
if str == nil {
return ""
}

return *str
}

// GetRegions makes DescribeRegions API call to list all regions from AWS
func GetRegions(svc ec2iface.ClientAPI) (completeRegionsList []string, err error) {
input := &ec2.DescribeRegionsInput{}
req := svc.DescribeRegionsRequest(input)
output, err := req.Send(context.TODO())
if err != nil {
err = errors.Wrap(err, "Failed DescribeRegions")
return
}
for _, region := range output.Regions {
completeRegionsList = append(completeRegionsList, *region.RegionName)
}
return
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,31 @@
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package elb
package aws

import (
"time"

"github.com/elastic/beats/x-pack/libbeat/common/aws"

"github.com/elastic/beats/libbeat/autodiscover/template"
"github.com/elastic/beats/libbeat/common"
)

// Config for the aws_elb autodiscover provider.
// Config for all aws autodiscover providers.
type Config struct {
Type string `config:"type"`

// Standard autodiscover fields.

// Hints are currently not supported, but may be implemented in a later release
HintsEnabled bool `config:"hints.enabled"`
Builders []*common.Config `config:"builders"`
Appenders []*common.Config `config:"appenders"`
Templates template.MapperSettings `config:"templates"`
Type string `config:"type"`
Templates template.MapperSettings `config:"templates"`

// Period defines how often to poll the AWS API.
Period time.Duration `config:"period" validate:"nonzero,required"`

// AWS Specific autodiscover fields

Regions []string `config:"regions" validate:"required"`
Regions []string `config:"regions"`
AWSConfig aws.ConfigAWS `config:",inline"`
}

func defaultConfig() *Config {
// DefaultConfig for all aws autodiscover providers.
func DefaultConfig() *Config {
return &Config{
Period: time.Minute,
}
Expand Down
11 changes: 11 additions & 0 deletions x-pack/libbeat/autodiscover/providers/aws/ec2/_meta/fields.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
- key: ec2_listener
title: "EC2 Listener"
description: >
AWS EC2 Listeners
short_config: false
release: experimental
fields:
- name: ec2_listener
type: group
description: >
Represents an AWS EC2 Listener, e.g. state of an EC2.
134 changes: 134 additions & 0 deletions x-pack/libbeat/autodiscover/providers/aws/ec2/ec2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package ec2

import (
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/pkg/errors"

"github.com/elastic/beats/libbeat/common"
"github.com/elastic/beats/libbeat/logp"
awsauto "github.com/elastic/beats/x-pack/libbeat/autodiscover/providers/aws"
)

type ec2Instance struct {
ec2Instance ec2.Instance
}

// toMap converts this ec2Instance into the form consumed as metadata in the autodiscovery process.
func (i *ec2Instance) toMap() common.MapStr {
architecture, err := i.ec2Instance.Architecture.MarshalValue()
if err != nil {
logp.Error(errors.Wrap(err, "MarshalValue failed for architecture: "))
}

m := common.MapStr{
"image": i.toImage(),
"vpc": i.toVpc(),
"subnet": i.toSubnet(),
"private": i.toPrivate(),
"public": i.toPublic(),
"monitoring": i.toMonitoringState(),
"kernel": i.toKernel(),
"state": i.stateMap(),
"architecture": architecture,
"root_device_name": awsauto.SafeString(i.ec2Instance.RootDeviceName),
}

for _, tag := range i.ec2Instance.Tags {
m.Put("tags."+awsauto.SafeString(tag.Key), awsauto.SafeString(tag.Value))
}
return m
}

func (i *ec2Instance) instanceID() string {
return awsauto.SafeString(i.ec2Instance.InstanceId)
}

func (i *ec2Instance) toImage() common.MapStr {
m := common.MapStr{}
m["id"] = awsauto.SafeString(i.ec2Instance.ImageId)
return m
}

func (i *ec2Instance) toMonitoringState() common.MapStr {
monitoringState, err := i.ec2Instance.Monitoring.State.MarshalValue()
if err != nil {
logp.Error(errors.Wrap(err, "MarshalValue failed for monitoring state: "))
}

m := common.MapStr{}
m["state"] = monitoringState
return m
}

func (i *ec2Instance) toPrivate() common.MapStr {
m := common.MapStr{}
m["ip"] = awsauto.SafeString(i.ec2Instance.PrivateIpAddress)
m["dns_name"] = awsauto.SafeString(i.ec2Instance.PrivateDnsName)
return m
}

func (i *ec2Instance) toPublic() common.MapStr {
m := common.MapStr{}
m["ip"] = awsauto.SafeString(i.ec2Instance.PublicIpAddress)
m["dns_name"] = awsauto.SafeString(i.ec2Instance.PublicDnsName)
return m
}

func (i *ec2Instance) toVpc() common.MapStr {
m := common.MapStr{}
m["id"] = awsauto.SafeString(i.ec2Instance.VpcId)
return m
}

func (i *ec2Instance) toSubnet() common.MapStr {
m := common.MapStr{}
m["id"] = awsauto.SafeString(i.ec2Instance.SubnetId)
return m
}

func (i *ec2Instance) toKernel() common.MapStr {
m := common.MapStr{}
m["id"] = awsauto.SafeString(i.ec2Instance.KernelId)
return m
}

func (i *ec2Instance) toCloudMap() common.MapStr {
m := common.MapStr{}
availabilityZone := awsauto.SafeString(i.ec2Instance.Placement.AvailabilityZone)
m["availability_zone"] = availabilityZone
m["provider"] = "aws"

// The region is just an AZ with the last character removed
m["region"] = availabilityZone[:len(availabilityZone)-1]

instance := common.MapStr{}
instance["id"] = i.instanceID()
m["instance"] = instance

instanceType, err := i.ec2Instance.InstanceType.MarshalValue()
if err != nil {
logp.Error(errors.Wrap(err, "MarshalValue failed for instance type: "))
}
machine := common.MapStr{}
machine["type"] = instanceType
m["machine"] = machine
return m
}

// stateMap converts the State part of the ec2 struct into a friendlier map with 'reason' and 'code' fields.
func (i *ec2Instance) stateMap() (stateMap common.MapStr) {
state := i.ec2Instance.State
stateMap = common.MapStr{}
nameString, err := state.Name.MarshalValue()
if err != nil {
logp.Error(errors.Wrap(err, "MarshalValue failed for instance state name: "))
}

stateMap["name"] = nameString
stateMap["code"] = state.Code
return stateMap
}
Loading