-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Automatically enrich Kubernetes module events #7470
Conversation
return str | ||
} | ||
|
||
func BuildMetadataEnricher( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
exported function BuildMetadataEnricher should have comment or be unexported
return kubernetes.NewWatcher(client, resource, options) | ||
} | ||
|
||
func NewResourceMetadataEnricher( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
exported function NewResourceMetadataEnricher should have comment or be unexported
// PodMetadata generates metadata for the given pod taking to account certain filters | ||
PodMetadata(pod *Pod) common.MapStr | ||
|
||
// Containermetadata generates metadata for the given container of a pod | ||
ContainerMetadata(pod *Pod, container string) common.MapStr | ||
} | ||
|
||
type metaGenerator struct { | ||
// Config for MetaGenerator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment on exported type MetaGeneratorConfig should be of the form "MetaGeneratorConfig ..." (with optional leading article)
a4feab7
to
a5db15b
Compare
return strings.Join(fields, ":") | ||
} | ||
|
||
func BuildMetadataEnricher( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
exported function BuildMetadataEnricher should have comment or be unexported
return enricher | ||
} | ||
|
||
func NewContainerMetadataEnricher( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
exported function NewContainerMetadataEnricher should have comment or be unexported
d79ab0b
to
ac1c226
Compare
5f2e824
to
1513b76
Compare
2ac2ea6
to
18258a9
Compare
metricbeat/docs/fields.asciidoc
Outdated
@@ -6890,12 +6890,12 @@ Kubernetes pod name | |||
|
|||
-- | |||
|
|||
*`kubernetes.pod.uid`*:: | |||
*`kubernetes.uid`*:: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Breaking change? should we keep both fields till 7.0?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kubernetes.pod.uid
is still unreleased, so this change should be ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh ok :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, it's not released yet, then my comment above may apply :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For my education: This is unique for 1 kubernetes "cluster". Is there also such a thing as a unique pod it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UIDs are assigned to all resources, by implementation, they are UUIDs. I struggled to either put them under kubernetes.uid
or create kubernetes.*.uid
for all resources. My reasoning was towards using the global namespace, as we do with labels or annotations. On the other hand, fields like name
are not globally unique, hence pod.name
makes sense for sure.
I'm open for opinions here, as this is semantically tricky
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can there be cases where more then one of the id's from 2 different resources is in the same event? For example having the pod.uid
and the kubernetes.uid
which describes the overall cluster in one event? If yes, I would probably put each uid
under it's resource. Also correlation wise it probably only makes sense to correlate pod.uid
but not correlated it with any other resources?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That case can happen, since #7231
But same would apply to labels/annotations. There is always a main resource for the event, and we put the labels & annotations for that one. Would it make sense to move those under pod.labels
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I so far assume labels
is a more generic thing and similar labels apply to different resources. A query could be "give me all resources with label a
". Based on this assumption I think the current handling of labels and tags make still sense. But if the labels don't overlap like in the case of the uid
the above would seem to make more sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pushed b6d8f76 to move it back to pod.uid
IncludeLabels []string `config:"include_labels"` | ||
ExcludeLabels []string `config:"exclude_labels"` | ||
IncludeAnnotations []string `config:"include_annotations"` | ||
IncludePodUID bool `config:"include_pod_uid"` | ||
IncludeUID bool `config:"include_uid"` | ||
IncludeCreatorMetadata bool `config:"include_creator_metadata"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these config settings documented?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I struggle a bit with these, I wonder if that means we should just hardcode them in. They were kept just in case someone wants to disable them, but I'm not sure
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it currently disabled by default?
As this doesn't look so costly to collect, maybe we can just add them, and they can be removed with drop_fields
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, so far I have left them undocumented to see how much pushback they get once released, I can leave a note here to say we want to remove these settings
|
||
# Enriching parameters: | ||
add_metadata: true | ||
in_cluster: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented out or removed here as this is the default?
@@ -82,5 +89,7 @@ func (m *MetricSet) Fetch() (common.MapStr, error) { | |||
return nil, err | |||
} | |||
|
|||
m.enricher.Enrich([]common.MapStr{event}) | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With current abstractions, metric sets have basically the same Fetch
(and New
) implementation for all resources, maybe we could have an only metricset for them, parametrized with the eventMapper and the enricher.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I had the same feeling, I think it should be done in a follow up PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps time to also convert it to v2 API.
} | ||
m.watcherStarted = true | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And a Stop()
to release the watcher resources?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added, thank you for bringing this in, it was in my TODO 👍
"github.com/elastic/beats/metricbeat/mb" | ||
) | ||
|
||
var nilEnricher = &enricher{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could define a nilEnricher type that implements the interface with empty methods, to avoid needing the if e == nilEnricher
in all methods of enricher
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pushed
func(m map[string]common.MapStr, r kubernetes.Resource) { | ||
pod := r.(*kubernetes.Pod) | ||
for _, container := range append(pod.GetSpec().GetContainers(), pod.GetSpec().GetInitContainers()...) { | ||
id := join(r.GetMetadata().GetNamespace(), r.GetMetadata().GetName(), container.GetName()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could also have a struct type for the ids, so the map is map[resourceID]common.MapStr
, and id := resourceID{r.GetMetadata().GetNamespace(), ...
.
I have had bad experiences in the past building map keys by joining strings 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say in general I can do the same mistakes with both approaches, join
method is syntactically similar to your resourceID
, in both cases I can pass parameters in the wrong order.
Thank you for the review @jsoriano!! It should be ready for a second pass. I'll wait for that before rebasing master |
4e9a754
to
3c74a22
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
This also moves current (unreleased) `pod.uid` to `uid`, as this is a unique id across the kubernetes cluster.
3c74a22
to
7d3d613
Compare
👍 rebased master |
"node": common.MapStr{"name": "test"}, | ||
"labels": common.MapStr{"a": common.MapStr{"value": "bar", "key": "foo"}}, | ||
"pod": common.MapStr{"name": ""}, | ||
"uid": "005f3b90-4b9d-12f8-acf0-31020a840133", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As uid
has be already around before, we should not change anything here so this is only a note from my side.
I often use id
instead of uuid
or uid
for consistency. And as long as there is only one id
I expect it to be unique.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UID
is a well-known field in Kubernetes, changing it to id
could confuse some users IMO
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a few questions. Most can also be addressed in a follow up PR.
metricbeat/docs/fields.asciidoc
Outdated
@@ -6890,12 +6890,12 @@ Kubernetes pod name | |||
|
|||
-- | |||
|
|||
*`kubernetes.pod.uid`*:: | |||
*`kubernetes.uid`*:: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For my education: This is unique for 1 kubernetes "cluster". Is there also such a thing as a unique pod it?
return events, nil | ||
} | ||
|
||
// Close stops this metricset | ||
func (m *MetricSet) Close() error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you test if Close is called?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, surprisingly it's called 😺 here: https://github.com/elastic/beats/blob/master/metricbeat/mb/module/wrapper.go#L132
@@ -82,5 +89,7 @@ func (m *MetricSet) Fetch() (common.MapStr, error) { | |||
return nil, err | |||
} | |||
|
|||
m.enricher.Enrich([]common.MapStr{event}) | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps time to also convert it to v2 API.
return kubernetes.NewWatcher(client, resource, options) | ||
} | ||
|
||
// NewResourceMetadataEnricher returns a Enricher configured for kubernetes resource events |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: an Enricher
|
||
watcher, err := GetWatcher(base, resource, nodeScope) | ||
if err != nil { | ||
logp.Warn("Error initializing Kubernetes metadata enricher: %s", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer not to use Warn
but either Info
or Error
. Seems more like Info
in this case as even if the enricher fails, the metricset still starts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It starts, but something is wrong. I used warn to call user attention on this, as it's unexpected. Can switch to info or error if you prefer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We tried to get rid of all Warn messages in the past. The reason is that for Warn it's not clear if users have to take again or not. It sounds like in the above case they have to take again so probably move it to error
.
|
||
metaConfig := kubernetes.MetaGeneratorConfig{} | ||
if err := base.Module().UnpackConfig(&metaConfig); err != nil { | ||
logp.Warn("Error initializing Kubernetes metadata enricher: %s", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment above.
|
||
watcher, err := GetWatcher(base, &kubernetes.Pod{}, nodeScope) | ||
if err != nil { | ||
logp.Warn("Error initializing Kubernetes metadata enricher: %s", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment above, applies also further below.
@@ -28,6 +37,8 @@ | |||
# - state_container | |||
# period: 10s | |||
# hosts: ["kube-state-metrics:8080"] | |||
# add_metadata: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not introduced in this PR, but I think commenting with #
should be smae as first part of config (line 16 etc).
@ruflin let me know if this one is good to go, I can rebase again once reviewed |
}, nil | ||
} | ||
|
||
// Fetch methods implements the data gathering and data conversion to the right format | ||
// It returns the event which is then forward to the output. In case of an error, a | ||
// descriptive error must be returned. | ||
func (m *MetricSet) Fetch() ([]common.MapStr, error) { | ||
m.enricher.Start() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this should be moved to New
instead of start. Like this you would not needed the bool isStarted
inside start and it would be called only once.
Also I'm a bit worried about potential race conditions for the isStarted
variable. What if Fetch() and Stop are called almost at the same time? Or Fetch is for some reason called twice in a very short period?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving it to new is not probably a bad option, some processes instantiate a module without launching it. I will add a mutex for the variable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What modules are these?
} | ||
|
||
func (m *enricher) Start() { | ||
if !m.watcherStarted { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think combined with Stop watcherStarted
could lead to a race condition.
96125fd
to
c282e67
Compare
c282e67
to
d582e8b
Compare
Should be ready for another look |
}, nil | ||
} | ||
|
||
// Fetch methods implements the data gathering and data conversion to the right format | ||
// It returns the event which is then forward to the output. In case of an error, a | ||
// descriptive error must be returned. | ||
func (m *MetricSet) Fetch() ([]common.MapStr, error) { | ||
m.enricher.Start() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What modules are these?
This PR adds automatically enriching of
kubernetes
module metricsets. It will behave asadd_kubernetes_metadata
, but enrich all events coming out of this module by default. This will not only add labels annotations to Pods but any other Resource, like nodes, containers, deployments...It will be on by default, some configurations are allowed:
Closes #7148
TODO: