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

Add builder support for autodiscover and annotations builder #6408

Merged
merged 5 commits into from
Feb 23, 2018

Conversation

vjsamuel
Copy link
Contributor

@vjsamuel vjsamuel commented Feb 19, 2018

This PR adds builder support for Beats autodiscover along with an annotations builder for filebeat and metricbeat.

Prefix for logs would be co.elastic.logs/ and co.elastic.metrics/ for metrics. Sample config:

filebeat.autodiscover:
  providers:
    - type: kubernetes
      in_cluster: false
      kube_config: config
      builders:
        - type: log.annotations

the log.annotations takes a list of templatized configurations that can be used to build input configs. The default is a simple docker input.

Annotations that are accepted for logs are /disable which would disable the input creation and hence log tailing, /include_lines, /exclude_lines, /multiline.* where * would be pattern, negate, match etc.

If co.elastic.logs/* is used then the annotation is honored at a pod level. If `co.elastic.logs./* is used then it is honored at the container level. The latter always takes precedence.

The metric.annotations builder can be used to auto discover metric endpoints. Sample annotation set:

      co.elastic.metrics/hosts: ${data.host}:9090
      co.elastic.metrics/module: prometheus
      co.elastic.metrics/namespace: test

Sample config:

metricbeat.autodiscover:
  providers:
    - type: kubernetes
      in_cluster: false
      kube_config: config
      builders:
        - type: metrics.annotations

This would spin up a prometheus collector metricset. Other supported annotations are:

/timeout
/period
/ssl.* #to set things like insecure skip verify

co.elastic.metrics.<container>/* can be used to configure entries at a container level.

The annotations builder are provider agnostic hence both docker and kubernetes can leverage the same. Both providers will pass to the builder a provider agnostic bus.Event which has:

host
port
annotations #kubernetes annotations/docker labels
container #container information like id and name

Test cases are still WIP. Kindly review the overall structure as it is a big change. The above said will be added to the same PR to complete the builder flow for polling use-cases.

@elasticmachine
Copy link
Collaborator

Since this is a community submitted pull request, a Jenkins build has not been kicked off automatically. Can an Elastic organization member please verify the contents of this patch and then kick off a build manually?

return result
}

func ApplyConfigTemplate(event bus.Event, configs []*common.Config) []*common.Config {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported function ApplyConfigTemplate should have comment or be unexported

var Registry = NewRegistry()

// NewRegistry creates and returns a new Registry
func NewRegistry() *registry {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported func NewRegistry returns unexported type *autodiscover.registry, which can be annoying to use

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im not sure what to do about this :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could return an interface implemented by *register, wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

register doesnt implement any interfaces at the moment. i think even mb registry and few others will have the same hound violation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

works for me 😄

return result
}

func GetContainerAnnotationsWithPrefix(annotations map[string]string, prefix, container, key string) map[string]string {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported function GetContainerAnnotationsWithPrefix should have comment or be unexported

return IsNoOp(annotations, fmt.Sprintf("%s.%s", prefix, container))
}

func GetAnnotationsWithPrefix(annotations map[string]string, prefix, key string) map[string]string {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported function GetAnnotationsWithPrefix should have comment or be unexported

return noop
}

func IsContainerNoOp(annotations map[string]string, prefix, container string) bool {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported function IsContainerNoOp should have comment or be unexported

return builder(c)
}

func (b Builders) GetConfig(event bus.Event) []*common.Config {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported method Builders.GetConfig should have comment or be unexported


type Builders []Builder

type BuilderConstructor func(*common.Config) (Builder, error)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported type BuilderConstructor should have comment or be unexported

CreateConfig(event bus.Event) []*common.Config
}

type Builders []Builder

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported type Builders should have comment or be unexported

"github.com/elastic/beats/libbeat/logp"
)

type Builder interface {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported type Builder should have comment or be unexported

package autodiscover

import (
_ "github.com/elastic/beats/filebeat/autodiscover/builder/log_annotations"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a blank import should be only in a main or test package, or have a comment justifying it

@vjsamuel vjsamuel force-pushed the autodiscover_builders branch from 105041b to e9e3a84 Compare February 19, 2018 17:22
package autodiscover

import (
_ "github.com/elastic/beats/metricbeat/autodiscover/builder/metric_annotations"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a blank import should be only in a main or test package, or have a comment justifying it

func (m *metricAnnotations) getTimeout(annotations map[string]string, container string) string {
if tout := builder.GetContainerAnnotationAsString(annotations, m.Prefix, container, timeout); tout != "" {
return tout
} else {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)

func (m *metricAnnotations) getPeriod(annotations map[string]string, container string) string {
if ival := builder.GetContainerAnnotationAsString(annotations, m.Prefix, container, period); ival != "" {
return ival
} else {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)

// Prometheus stats can be explicitly configured if need be.
if module == "prometheus" {
return []string{"collector"}
} else {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if block ends with a return statement, so drop this else and outdent its block

Prefix string
}

func NewMetricAnnotations(cfg *common.Config) (autodiscover.Builder, error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported function NewMetricAnnotations should have comment or be unexported

ssl = "ssl"

default_timeout = "3s"
default_interval = "1m"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't use underscores in Go names; const default_interval should be defaultInterval

timeout = "timeout"
ssl = "ssl"

default_timeout = "3s"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't use underscores in Go names; const default_timeout should be defaultTimeout

@@ -0,0 +1,141 @@
package metric_annotations

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't use an underscore in package name

@@ -0,0 +1,11 @@
package metric_annotations

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't use an underscore in package name

@exekias
Copy link
Contributor

exekias commented Feb 20, 2018

Thank you for working on this @vjsamuel! 🎉

Sorry for the delay answering here. Builders interface is looking great! I have some thoughts/questions:

  • Do you have more builders in mind? I'm wondering if we should make the configuration simpler (like use_annotations: true)?
  • We need builders to support several providers (both Docker and Kubernetes at the moment). Perhaps in the future, even more things outside autodiscover. Current implementation relays in a hardcoded structure of annotations.

That's why I think we should introduce the concept of hints to Autodiscover events. The main idea is that each provider can fill a set of well-known hint fields, that builders can then use to build configurations from events coming from any provider. Example of a Docker event:

{
  "host": "10.4.15.9",
  "port": 2379,
  "hints": {
    "metrics": {
      "enabled": true,
      "hosts": ["${data.host}:9090"],
      "module": "prometheus",
    }
  },
  "docker": {
    "container": {
      "id": "13a2...d716"
      "name": "etcd",
      "image": "quay.io/coreos/etcd:v3.0.0",
      "labels": {
          "co.elastic.metrics/hosts": "${data.host}:9090",
          "co.elastic.metrics/module": "prometheus",
          "co.elastic.metrics/namespace": "test",
          ...
      }
    }
  }
}

Putting the responsibility of extracting hints in the provider sounds good, as it will allow us to implement the hinting system in many platforms.

What do you think?

@vjsamuel
Copy link
Contributor Author

@exekias i have the graphite annotations builder coming up in a next PR once this structure is finalized and merged. hence simply use_annotations: true may not fully work as graphite has more configuration that it will take from the yml file. im not sure how hints would work in a very ephemeral environment like Kubernetes where a user has deployed all sorts of applications requiring to bootstrap multiple kinds of modules. the reason why i generate a new event and place platform agnostic information is to make the builders provider independent.

@exekias
Copy link
Contributor

exekias commented Feb 20, 2018

We had a chat about this, code is looking great, we agreed on doing some changes:

  • Introduce a hints field in autodiscover events
  • Providers are in charge of extracting hints from their domain into the event
  • Builders are in charge of taking hints and creating new configurations based on them
  • Builders will keep guessing the right base conf (ie prospector) by now, we may find a more agnostic way in the future

func (m *metricAnnotations) getTimeout(hints common.MapStr) string {
if tout := builder.GetHintString(hints, m.Key, timeout); tout != "" {
return tout
} else {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)

func (m *metricAnnotations) getPeriod(hints common.MapStr) string {
if ival := builder.GetHintString(hints, m.Key, period); ival != "" {
return ival
} else {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)

Key string
}

// Build a new metrics annotation builder

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment on exported function NewMetricAnnotations should be of the form "NewMetricAnnotations ..."

return cID
}

// GetContainerID parses the container ID to get the actual ID string

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment on exported method PodContainerStatus.GetContainerIDWithRuntime should be of the form "GetContainerIDWithRuntime ..."

}
}

func (c *Config) Validate() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported method Config.Validate should have comment or be unexported

}
}

func (c *Config) Validate() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported method Config.Validate should have comment or be unexported

hIface, ok := event["hints"]
if !ok {
return config
} else {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if block ends with a return statement, so drop this else and outdent its block

Config []*common.Config
}

// Construct a log annotations builder

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment on exported function NewLogAnnotations should be of the form "NewLogAnnotations ..."

@@ -0,0 +1,98 @@
package log_annotations

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't use an underscore in package name

Copy link
Contributor

@exekias exekias left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is getting in shape!, I left a few comments, let's discuss them.

@@ -10,6 +10,8 @@ import (
"github.com/elastic/beats/libbeat/common/bus"

"github.com/stretchr/testify/assert"

"github.com/elastic/beats/libbeat/autodiscover/template"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this import should go with the rest of github.com/elastic/beats/libbeat/

)

func init() {
autodiscover.Registry.AddBuilder("log.annotations", NewLogAnnotations)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps it should be just logs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i thought of the same. will change it as well.

}

// ConstructBuilder reads provider configuration and instatiate one
func (r *registry) ConstructBuilder(c *common.Config) (Builder, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we normally use Create word for constructors

Copy link
Contributor Author

@vjsamuel vjsamuel Feb 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the previous constructor for provider was BuildProvider. It seemed redundant to have BuildBuilder. Thats why I made this as Construct. I ll change.

providers: make(map[string]ProviderBuilder, 0),
}
}
type ProviderBuilder func(bus.Bus, *template.Mapper, Builders, *common.Config) (Provider, error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the provider doesn't support builders? (for instance, it doesn't give you any hints). I'm more comfortable with the current approach, letting the provider decide what it supports (templates, builders...) and plug it in.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. sure. i will revert this guy. makes it easier for submitting more PRs in parallel :)

"id": containerIDs[c.Name],
"name": c.Name,
"image": c.Image,
// Without this check there would be overlapping configurations with and without ports.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with this change but could you open it in a separate PR? this one could grow a lot

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i have removed this. i will add it in once this PR is merged.

@@ -154,6 +155,44 @@ func (p *Provider) publish(event bus.Event) {
// Try to match a config
if config := p.templates.GetConfig(event); config != nil {
event["config"] = config
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code path should be similar p.templates.GetConfig, I would extract all the logic to generate hints from the event, and then call p.builders.GetConfig

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it may be a bit difficult as handling is provider specific unlike templates. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@vjsamuel vjsamuel force-pushed the autodiscover_builders branch 2 times, most recently from df2ed5e to df06112 Compare February 21, 2018 08:19
@vjsamuel vjsamuel force-pushed the autodiscover_builders branch from df06112 to dd889e9 Compare February 21, 2018 10:50
@vjsamuel vjsamuel changed the title [WIP] Add builder support for autodiscover and annotations builder Add builder support for autodiscover and annotations builder Feb 21, 2018
labelMap := common.MapStr{}
for k, v := range container.Labels {
labelMap[k] = v
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you remove this? this was a fix introduced a few days ago, if labels is not a MapStr autodiscover conditions won't work on it

Copy link
Contributor

@exekias exekias left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much @vjsamuel! 🎉 🎉 let's get this in, we may want to rethink some config options, but we can do it in followup PRs

@exekias
Copy link
Contributor

exekias commented Feb 22, 2018

jenkins, test it please

Copy link
Contributor

@ruflin ruflin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should create a follow up issue to add docs for this awesome feature.

@exekias @vjsamuel Sounds also like a blog post to me ;-)

@exekias exekias merged commit 326c68f into elastic:master Feb 23, 2018
@vjsamuel vjsamuel deleted the autodiscover_builders branch February 23, 2018 11:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants