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

[Metricbeat][Kubernetes] Add metricset for state_namespace #36406

Merged
merged 21 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from 18 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
1 change: 1 addition & 0 deletions CHANGELOG-developer.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ The list below covers the major changes between 7.0.0-rc2 and main only.

==== Added

- Add new metricset in Kubernetes module, `state_namespace`. {pull}36406[36406]
- Add configuration for APM instrumentation and expose the tracer trough the Beat object. {pull}17938[17938]
- Make the behavior of clientWorker and netClientWorker consistent when error is returned from publisher pipeline
- Metricset generator generates beta modules by default now. {pull}10657[10657]
Expand Down
36 changes: 36 additions & 0 deletions metricbeat/docs/fields.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -46035,6 +46035,42 @@ type: keyword

--

[float]
=== namespace_state

Kubernetes namespace metrics.



*`kubernetes.namespace_state.created.sec`*::
+
--
Unix creation timestamp.


type: double

--


*`kubernetes.namespace_state.status.active`*::
+
--
Whether the namespace is active (true or false).

type: boolean

--

*`kubernetes.namespace_state.status.terminating`*::
+
--
Whether the namespace is terminating (true or false).

type: boolean

--

[float]
=== node

Expand Down
26 changes: 21 additions & 5 deletions metricbeat/helper/kubernetes/state_metricset.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ var lock sync.RWMutex
// Init registers the MetricSet with the central registry.
// The New method will be called after the setup of the module and before starting to fetch data
func Init(name string, mapping *prometheus.MetricsMapping) {
name = prefix + name
if name != util.NamespaceResource {
name = prefix + name
}
lock.Lock()
mappings[name] = mapping
lock.Unlock()
Expand Down Expand Up @@ -77,11 +79,16 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) {
mapping := mappings[base.Name()]
lock.Unlock()

resourceName := base.Name()
if resourceName != util.NamespaceResource {
resourceName = strings.ReplaceAll(resourceName, prefix, "")
}

return &MetricSet{
BaseMetricSet: base,
prometheusClient: prometheusClient,
prometheusMapping: mapping,
enricher: util.NewResourceMetadataEnricher(base, strings.ReplaceAll(base.Name(), prefix, ""), mod.GetMetricsRepo(), false),
enricher: util.NewResourceMetadataEnricher(base, resourceName, mod.GetMetricsRepo(), false),
mod: mod,
}, nil
}
Expand All @@ -90,6 +97,17 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) {
// format. It publishes the event which is then forwarded to the output. In case
// of an error set the Error field of mb.Event or simply call report.Error().
func (m *MetricSet) Fetch(reporter mb.ReporterV2) {
// The name of the metric state can be obtained by using m.BaseMetricSet.Name(). However, names that start with state_* (e.g. state_cronjob)
// need to have that prefix removed. So, for example, strings.ReplaceAll("state_cronjob", "state_", "") would result in just cronjob.
// Exception is state_namespace, as field kubernetes.namespace already exists, and we need to create a new object
// for the state_namespace metricset.
resourceName := m.BaseMetricSet.Name()
if resourceName != util.NamespaceResource {
resourceName = strings.ReplaceAll(resourceName, prefix, "")
} else {
resourceName = "namespace_state"
Copy link
Member

Choose a reason for hiding this comment

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

I think the resource name should be namespace so as to be used in event.dataset properly. Potentially we could extend the metricset to also add other fields like namespace_foo so just using namespace_state for the dataset I think is not so correct.

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 am afraid that will break again because kubernetes.state already exists 🤔

Copy link
Member

Choose a reason for hiding this comment

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

I mean that the value of event.dataset should be set to namespace. I think this would work since it's the value that we set so I don't see how it would be in conflict with anything else, right?

Copy link
Contributor Author

@constanca-m constanca-m Sep 1, 2023

Choose a reason for hiding this comment

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

It would create a conflict, because that field is setting the object kubernetes.namespace_state. And if we change it to kubernetes.namespace, this is the document that is generated (I just ran this):

 {
        "event": {
            "dataset": "kubernetes.namespace",
            "duration": 115000,
            "module": "kubernetes"
        },
        "kubernetes": {
            "namespace": {
                "created": {
                    "sec": 1691566342
                },
                "status": {
                    "active": true,
                    "terminating": false
                }
            }
        },
        "metricset": {
            "name": "state_namespace",
            "period": 10000
        },
        "service": {
            "address": "127.0.0.1:55555",
            "type": "kubernetes"
        }
    },
    

Which will cause the conflict with the string field kubernetes.namespace @ChrsMark

Copy link
Member

Choose a reason for hiding this comment

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

We don't need to change the kubernetes.namespace_state part. This can be as is.
I only suggest to change the value of event.dataset. I guess it's doable to just set this specifically within the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could try that, but how do I set that field? I had a look at the code and:

e, err := util.CreateEvent(event, "kubernetes."+resourceName)

if err != nil {
	m.Logger().Error(err)
}

between these lines, there is still not event.dataset field.

The event is {"_module":{"namespace":"kube-node-lease"},"created":{"sec":1693554126},"status":{"active":true,"terminating":false}} (example) and e is {{} {"namespace":"kube-node-lease"} {"created":{"sec":1693554126},"status":{"active":true,"terminating":false}} kubernetes.namespace_state 0001-01-01 00:00:00 +0000 UTC <nil> 0s 0s false} (example).

Copy link
Contributor

Choose a reason for hiding this comment

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

I know that we have change our minds here so many times, just changing event.dataset = kubernetes.namespace_state, breaks also the user experience that metrics that come from kube state metrics have the pattern:kubernetes.state_*

Just retested those and wanted to bring this to discussion once more. @elastic/obs-cloudnative-monitoring team any thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe it's best to have this discussion over a meeting? Otherwise it seems it will take some time until we all align. @gizas @ChrsMark

}

m.enricher.Start()

families, err := m.mod.GetStateMetricsFamilies(m.prometheusClient)
Expand All @@ -107,9 +125,7 @@ func (m *MetricSet) Fetch(reporter mb.ReporterV2) {

m.enricher.Enrich(events)
for _, event := range events {
// The name of the metric state can be obtained by using m.BaseMetricSet.Name(). However, names that start with state_* (e.g. state_cronjob)
// need to have that prefix removed. So, for example, strings.ReplaceAll("state_cronjob", "state_", "") would result in just cronjob.
e, err := util.CreateEvent(event, "kubernetes."+strings.ReplaceAll(m.BaseMetricSet.Name(), "state_", ""))
e, err := util.CreateEvent(event, "kubernetes."+resourceName)
if err != nil {
m.Logger().Error(err)
}
Expand Down
1 change: 1 addition & 0 deletions metricbeat/include/list_docker.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,7 @@ data:
period: 10s
add_metadata: true
metricsets:
- state_node
- state_deployment
- state_daemonset
- state_replicaset
- state_pod
- state_container
- state_cronjob
- state_resourcequota
- state_statefulset
- state_service
- state_persistentvolume
- state_persistentvolumeclaim
- state_storageclass
- state_namespace
Copy link
Contributor

Choose a reason for hiding this comment

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

seems you pushed local changes for this file, could you please revert?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Is it ok now with just state_namespace added?

- module: kubernetes
metricsets:
- apiserver
Expand All @@ -64,9 +52,11 @@ data:
cloud.auth: ${ELASTIC_CLOUD_AUTH}

output.elasticsearch:
hosts: ['${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']
hosts: ["https://elasticsearch:9200"] #['${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']
username: ${ELASTICSEARCH_USERNAME}
password: ${ELASTICSEARCH_PASSWORD}
ssl.verification_mode: "none"
allow_older_versions: true
---
apiVersion: v1
kind: ConfigMap
Expand Down
2 changes: 1 addition & 1 deletion metricbeat/module/kubernetes/fields.go

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions metricbeat/module/kubernetes/state_namespace/_meta/data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"@timestamp": "2019-03-01T08:05:34.853Z",
"event": {
"dataset": "kubernetes.namespace_state",
"duration": 115000,
"module": "kubernetes"
},
"kubernetes": {
"namespace": "kube-node-lease",
"namespace_state": {
"created": {
"sec": 1691566337
},
"status": {
"active": true,
"terminating": false
}
}
},
"metricset": {
"name": "state_namespace",
"period": 10000
},
"service": {
"address": "127.0.0.1:55555",
"type": "kubernetes"
}
}
19 changes: 19 additions & 0 deletions metricbeat/module/kubernetes/state_namespace/_meta/fields.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
- name: namespace_state
type: group
release: ga
description: >
Kubernetes namespace metrics.
fields:
- name: created.sec
type: double
description: >
Unix creation timestamp.
- name: status
type: group
fields:
- name: active
type: boolean
description: Whether the namespace is active (true or false).
- name: terminating
type: boolean
description: Whether the namespace is terminating (true or false).
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
[
{
"RootFields": null,
"ModuleFields": {
"namespace": "kube-node-lease"
},
"MetricSetFields": {
"created": {
"sec": 1673879559
},
"status": {
"active": true,
"terminating": false
}
},
"Index": "",
"ID": "",
"Namespace": "kubernetes.namespace_state",
"Timestamp": "0001-01-01T00:00:00Z",
"Error": null,
"Host": "",
"Service": "",
"Took": 0,
"Period": 0,
"DisableTimeSeries": false
},
{
"RootFields": null,
"ModuleFields": {
"namespace": "default"
},
"MetricSetFields": {
"created": {
"sec": 1673879561
},
"status": {
"active": true,
"terminating": false
}
},
"Index": "",
"ID": "",
"Namespace": "kubernetes.namespace_state",
"Timestamp": "0001-01-01T00:00:00Z",
"Error": null,
"Host": "",
"Service": "",
"Took": 0,
"Period": 0,
"DisableTimeSeries": false
},
{
"RootFields": null,
"ModuleFields": {
"namespace": "local-path-storage"
},
"MetricSetFields": {
"created": {
"sec": 1673879567
},
"status": {
"active": true,
"terminating": false
}
},
"Index": "",
"ID": "",
"Namespace": "kubernetes.namespace_state",
"Timestamp": "0001-01-01T00:00:00Z",
"Error": null,
"Host": "",
"Service": "",
"Took": 0,
"Period": 0,
"DisableTimeSeries": false
},
{
"RootFields": null,
"ModuleFields": {
"namespace": "kube-public"
},
"MetricSetFields": {
"created": {
"sec": 1673879559
},
"status": {
"active": true,
"terminating": false
}
},
"Index": "",
"ID": "",
"Namespace": "kubernetes.namespace_state",
"Timestamp": "0001-01-01T00:00:00Z",
"Error": null,
"Host": "",
"Service": "",
"Took": 0,
"Period": 0,
"DisableTimeSeries": false
},
{
"RootFields": null,
"ModuleFields": {
"namespace": "kube-system"
},
"MetricSetFields": {
"created": {
"sec": 1673879559
},
"status": {
"active": true,
"terminating": false
}
},
"Index": "",
"ID": "",
"Namespace": "kubernetes.namespace_state",
"Timestamp": "0001-01-01T00:00:00Z",
"Error": null,
"Host": "",
"Service": "",
"Took": 0,
"Period": 0,
"DisableTimeSeries": false
}
]
Loading