diff --git a/.ci/packaging.groovy b/.ci/packaging.groovy index 325084ef181..b90182a5d00 100644 --- a/.ci/packaging.groovy +++ b/.ci/packaging.groovy @@ -17,6 +17,7 @@ pipeline { JOB_GCS_BUCKET = 'beats-ci-artifacts' JOB_GCS_BUCKET_STASH = 'beats-ci-temp' JOB_GCS_CREDENTIALS = 'beats-ci-gcs-plugin' + JOB_GCS_EXT_CREDENTIALS = 'beats-ci-gcs-plugin-file-credentials' DOCKERELASTIC_SECRET = 'secret/observability-team/ci/docker-registry/prod' DOCKER_REGISTRY = 'docker.elastic.co' GITHUB_CHECK_E2E_TESTS_NAME = 'E2E Tests' @@ -448,14 +449,11 @@ def publishPackages(baseDir){ uploadPackages("${bucketUri}/${beatsFolderName}", baseDir) } -def uploadPackages(bucketUri, baseDir){ - googleStorageUpload(bucket: bucketUri, - credentialsId: "${JOB_GCS_CREDENTIALS}", - pathPrefix: "${baseDir}/build/distributions/", - pattern: "${baseDir}/build/distributions/**/*", - sharedPublicly: true, - showInline: true - ) +def uploadPackages(bucketUri, beatsFolder){ + googleStorageUploadExt(bucket: bucketUri, + credentialsId: "${JOB_GCS_EXT_CREDENTIALS}", + pattern: "${beatsFolder}/build/distributions/**/*", + sharedPublicly: true) } /** @@ -510,4 +508,4 @@ def fixPermissions() { } } } -} \ No newline at end of file +} diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 8ab8d10c7f2..5f353ec1b2c 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -126,6 +126,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - kubernetes.container.cpu.limit.cores and kubernetes.container.cpu.requests.cores are now floats. {issue}11975[11975] - Change types of numeric metrics from Kubelet summary api to double so as to cover big numbers. {pull}23335[23335] - Add container.image.name and containe.name ECS fields for state_container. {pull}23802[23802] +- Add support for the MemoryPressure, DiskPressure, OutOfDisk and PIDPressure status conditions in state_node. {pull}[23905] *Packetbeat* diff --git a/Jenkinsfile b/Jenkinsfile index 3cee434b6e6..8e8d67c782b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -14,6 +14,7 @@ pipeline { DOCKER_REGISTRY = 'docker.elastic.co' JOB_GCS_BUCKET = 'beats-ci-temp' JOB_GCS_CREDENTIALS = 'beats-ci-gcs-plugin' + JOB_GCS_EXT_CREDENTIALS = 'beats-ci-gcs-plugin-file-credentials' OSS_MODULE_PATTERN = '^[a-z0-9]+beat\\/module\\/([^\\/]+)\\/.*' PIPELINE_LOG_LEVEL = 'INFO' PYTEST_ADDOPTS = "${params.PYTEST_ADDOPTS}" @@ -311,13 +312,10 @@ def publishPackages(beatsFolder){ * @param beatsFolder the beats folder. */ def uploadPackages(bucketUri, beatsFolder){ - googleStorageUpload(bucket: bucketUri, - credentialsId: "${JOB_GCS_CREDENTIALS}", - pathPrefix: "${beatsFolder}/build/distributions/", + googleStorageUploadExt(bucket: bucketUri, + credentialsId: "${JOB_GCS_EXT_CREDENTIALS}", pattern: "${beatsFolder}/build/distributions/**/*", - sharedPublicly: true, - showInline: true - ) + sharedPublicly: true) } /** @@ -693,11 +691,10 @@ def archiveTestOutput(Map args = [:]) { */ def tarAndUploadArtifacts(Map args = [:]) { tar(file: args.file, dir: args.location, archive: false, allowMissing: true) - googleStorageUpload(bucket: "gs://${JOB_GCS_BUCKET}/${env.JOB_NAME}-${env.BUILD_ID}", - credentialsId: "${JOB_GCS_CREDENTIALS}", - pattern: "${args.file}", - sharedPublicly: true, - showInline: true) + googleStorageUploadExt(bucket: "gs://${JOB_GCS_BUCKET}/${env.JOB_NAME}-${env.BUILD_ID}", + credentialsId: "${JOB_GCS_EXT_CREDENTIALS}", + pattern: "${args.file}", + sharedPublicly: true) } /** diff --git a/deploy/kubernetes/elastic-agent-standalone-kubernetes.yml b/deploy/kubernetes/elastic-agent-standalone-kubernetes.yml new file mode 100644 index 00000000000..f99281b6889 --- /dev/null +++ b/deploy/kubernetes/elastic-agent-standalone-kubernetes.yml @@ -0,0 +1,509 @@ +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: elastic-agent + namespace: kube-system + labels: + app: elastic-agent +spec: + selector: + matchLabels: + app: elastic-agent + template: + metadata: + labels: + app: elastic-agent + spec: + tolerations: + - key: node-role.kubernetes.io/master + effect: NoSchedule + serviceAccountName: elastic-agent + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet + containers: + - name: elastic-agent + image: docker.elastic.co/beats/elastic-agent:7.12.0-SNAPSHOT + args: [ + "-c", "/etc/agent.yml", + "-e", "-d", "composable.providers.kubernetes", + ] + env: + - name: ES_USERNAME + value: "elastic" + - name: ES_PASSWORD + value: "" + - name: ES_HOST + value: "" + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + securityContext: + runAsUser: 0 + resources: + limits: + memory: 200Mi + requests: + cpu: 100m + memory: 100Mi + volumeMounts: + - name: datastreams + mountPath: /etc/agent.yml + readOnly: true + subPath: agent.yml + volumes: + - name: datastreams + configMap: + defaultMode: 0640 + name: agent-node-datastreams +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: agent-node-datastreams + namespace: kube-system + labels: + k8s-app: elastic-agent +data: + agent.yml: |- + id: ef9cc740-5bf0-11eb-8b51-39775155c3f5 + revision: 2 + outputs: + default: + type: elasticsearch + hosts: + - >- + ${ES_HOST} + username: ${ES_USERNAME} + password: ${ES_PASSWORD} + agent: + monitoring: + enabled: true + use_output: default + logs: true + metrics: true + providers.kubernetes: + node: ${NODE_NAME} + scope: node + inputs: + - id: 934ef8aa-ed19-405b-8160-ebf62e3d32f8 + name: kubernetes-node-metrics + revision: 1 + type: kubernetes/metrics + use_output: default + meta: + package: + name: kubernetes + version: 0.2.8 + data_stream: + namespace: default + streams: + - id: >- + kubernetes/metrics-kubernetes.controllermanager-3d50c483-2327-40e7-b3e5-d877d4763fe1 + data_stream: + dataset: kubernetes.controllermanager + type: metrics + metricsets: + - controllermanager + hosts: + - '${kubernetes.pod.ip}:10252' + period: 10s + condition: ${kubernetes.pod.labels.component} == 'kube-controller-manager' + - id: >- + kubernetes/metrics-kubernetes.scheduler-3d50c483-2327-40e7-b3e5-d877d4763fe1 + data_stream: + dataset: kubernetes.scheduler + type: metrics + metricsets: + - scheduler + hosts: + - '${kubernetes.pod.ip}:10251' + period: 10s + condition: ${kubernetes.pod.labels.component} == 'kube-scheduler' + - id: >- + kubernetes/metrics-kubernetes.proxy-3d50c483-2327-40e7-b3e5-d877d4763fe1 + data_stream: + dataset: kubernetes.proxy + type: metrics + metricsets: + - proxy + hosts: + - 'localhost:10249' + period: 10s + - id: >- + kubernetes/metrics-kubernetes.container-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.container + type: metrics + metricsets: + - container + add_metadata: true + bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token + hosts: + - 'https://${env.NODE_NAME}:10250' + period: 10s + ssl.verification_mode: none + - id: >- + kubernetes/metrics-kubernetes.node-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.node + type: metrics + metricsets: + - node + add_metadata: true + bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token + hosts: + - 'https://${env.NODE_NAME}:10250' + period: 10s + ssl.verification_mode: none + - id: kubernetes/metrics-kubernetes.pod-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.pod + type: metrics + metricsets: + - pod + add_metadata: true + bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token + hosts: + - 'https://${env.NODE_NAME}:10250' + period: 10s + ssl.verification_mode: none + - id: >- + kubernetes/metrics-kubernetes.system-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.system + type: metrics + metricsets: + - system + add_metadata: true + bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token + hosts: + - 'https://${env.NODE_NAME}:10250' + period: 10s + ssl.verification_mode: none + - id: >- + kubernetes/metrics-kubernetes.volume-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.volume + type: metrics + metricsets: + - volume + add_metadata: true + bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token + hosts: + - 'https://${env.NODE_NAME}:10250' + period: 10s + ssl.verification_mode: none +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: elastic-agent + namespace: kube-system + labels: + app: elastic-agent +spec: + selector: + matchLabels: + app: elastic-agent + template: + metadata: + labels: + app: elastic-agent + spec: + serviceAccountName: elastic-agent + containers: + - name: elastic-agent + image: docker.elastic.co/beats/elastic-agent:7.12.0-SNAPSHOT + args: [ + "-c", "/etc/agent.yml", + "-e", "-d", "composable.providers.kubernetes", + ] + env: + - name: ES_USERNAME + value: "elastic" + - name: ES_PASSWORD + value: "" + - name: ES_HOST + value: "" + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + # this is needed because we cannot use hostNetwork + - name: HOSTNAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + securityContext: + runAsUser: 0 + resources: + limits: + memory: 200Mi + requests: + cpu: 100m + memory: 100Mi + volumeMounts: + - name: datastreams + mountPath: /etc/agent.yml + readOnly: true + subPath: agent.yml + volumes: + - name: datastreams + configMap: + defaultMode: 0640 + name: agent-deployment-datastreams +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: agent-deployment-datastreams + namespace: kube-system + labels: + k8s-app: elastic-agent +data: + # This part requires `kube-state-metrics` up and running under `kube-system` namespace + agent.yml: |- + id: ef9cc740-5bf0-11eb-8b51-39775155c3f5 + revision: 2 + outputs: + default: + type: elasticsearch + hosts: + - >- + ${ES_HOST} + username: ${ES_USERNAME} + password: ${ES_PASSWORD} + agent: + monitoring: + enabled: true + use_output: default + logs: true + metrics: true + inputs: + - id: 934ef8aa-ed19-405b-8160-ebf62e3d32f9 + name: kubernetes-cluster-metrics + revision: 1 + type: kubernetes/metrics + use_output: default + meta: + package: + name: kubernetes + version: 0.2.8 + data_stream: + namespace: default + streams: + - id: >- + kubernetes/metrics-kubernetes.apiserver-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.apiserver + type: metrics + metricsets: + - apiserver + bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token + hosts: + - 'https://${env.KUBERNETES_SERVICE_HOST}:${env.KUBERNETES_SERVICE_PORT}' + period: 30s + ssl.certificate_authorities: + - /var/run/secrets/kubernetes.io/serviceaccount/ca.crt + - id: >- + kubernetes/metrics-kubernetes.event-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.event + type: metrics + metricsets: + - event + period: 10s + add_metadata: true + - id: >- + kubernetes/metrics-kubernetes.state_container-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.state_container + type: metrics + metricsets: + - state_container + add_metadata: true + hosts: + - 'kube-state-metrics:8080' + period: 10s + - id: >- + kubernetes/metrics-kubernetes.state_cronjob-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.state_cronjob + type: metrics + metricsets: + - state_cronjob + add_metadata: true + hosts: + - 'kube-state-metrics:8080' + period: 10s + - id: >- + kubernetes/metrics-kubernetes.state_deployment-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.state_deployment + type: metrics + metricsets: + - state_deployment + add_metadata: true + hosts: + - 'kube-state-metrics:8080' + period: 10s + - id: >- + kubernetes/metrics-kubernetes.state_node-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.state_node + type: metrics + metricsets: + - state_node + add_metadata: true + hosts: + - 'kube-state-metrics:8080' + period: 10s + - id: >- + kubernetes/metrics-kubernetes.state_persistentvolume-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.state_persistentvolume + type: metrics + metricsets: + - state_persistentvolume + add_metadata: true + hosts: + - 'kube-state-metrics:8080' + period: 10s + - id: >- + kubernetes/metrics-kubernetes.state_persistentvolumeclaim-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.state_persistentvolumeclaim + type: metrics + metricsets: + - state_persistentvolumeclaim + add_metadata: true + hosts: + - 'kube-state-metrics:8080' + period: 10s + - id: >- + kubernetes/metrics-kubernetes.state_pod-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.state_pod + type: metrics + metricsets: + - state_pod + add_metadata: true + hosts: + - 'kube-state-metrics:8080' + period: 10s + - id: >- + kubernetes/metrics-kubernetes.state_replicaset-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.state_replicaset + type: metrics + metricsets: + - state_replicaset + add_metadata: true + hosts: + - 'kube-state-metrics:8080' + period: 10s + - id: >- + kubernetes/metrics-kubernetes.state_resourcequota-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.state_resourcequota + type: metrics + metricsets: + - state_resourcequota + add_metadata: true + hosts: + - 'kube-state-metrics:8080' + period: 10s + - id: >- + kubernetes/metrics-kubernetes.state_service-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.state_service + type: metrics + metricsets: + - state_service + add_metadata: true + hosts: + - 'kube-state-metrics:8080' + period: 10s + - id: >- + kubernetes/metrics-kubernetes.state_statefulset-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.state_statefulset + type: metrics + metricsets: + - state_statefulset + add_metadata: true + hosts: + - 'kube-state-metrics:8080' + period: 10s + - id: >- + kubernetes/metrics-kubernetes.state_storageclass-934ef8aa-ed19-405b-8160-ebf62e3d32f8 + data_stream: + dataset: kubernetes.state_storageclass + type: metrics + metricsets: + - state_storageclass + add_metadata: true + hosts: + - 'kube-state-metrics:8080' + period: 10s +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: elastic-agent +subjects: + - kind: ServiceAccount + name: elastic-agent + namespace: kube-system +roleRef: + kind: ClusterRole + name: elastic-agent + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: elastic-agent + labels: + k8s-app: elastic-agent +rules: + - apiGroups: [""] + resources: + - nodes + - namespaces + - events + - pods + - secrets + verbs: ["get", "list", "watch"] + - apiGroups: ["extensions"] + resources: + - replicasets + verbs: ["get", "list", "watch"] + - apiGroups: ["apps"] + resources: + - statefulsets + - deployments + - replicasets + verbs: ["get", "list", "watch"] + - apiGroups: + - "" + resources: + - nodes/stats + verbs: + - get + # required for apiserver + - nonResourceURLs: + - "/metrics" + verbs: + - get +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: elastic-agent + namespace: kube-system + labels: + k8s-app: elastic-agent +--- diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index e3359559097..4af8bbf459f 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -29807,6 +29807,46 @@ type: keyword Node unschedulable status +type: boolean + +-- + +*`kubernetes.node.status.memory_pressure`*:: ++ +-- +Node MemoryPressure status + + +type: boolean + +-- + +*`kubernetes.node.status.disk_pressure`*:: ++ +-- +Node DiskPressure status + + +type: boolean + +-- + +*`kubernetes.node.status.out_of_disk`*:: ++ +-- +Node OutOfDisk status + + +type: boolean + +-- + +*`kubernetes.node.status.pid_pressure`*:: ++ +-- +Node PIDPressure status + + type: boolean -- diff --git a/metricbeat/helper/prometheus/metric.go b/metricbeat/helper/prometheus/metric.go index a040ed65dae..5f3f1199f31 100644 --- a/metricbeat/helper/prometheus/metric.go +++ b/metricbeat/helper/prometheus/metric.go @@ -79,10 +79,11 @@ type MetricOption interface { Process(field string, value interface{}, labels common.MapStr) (string, interface{}, common.MapStr) } -// OpFilter only processes metrics matching the given filter -func OpFilter(filter map[string]string) MetricOption { - return opFilter{ - labels: filter, +// OpFilterMap only processes metrics matching the given filter +func OpFilterMap(label string, filterMap map[string]string) MetricOption { + return opFilterMap{ + label: label, + filterMap: filterMap, } } @@ -331,18 +332,22 @@ func (m *infoMetric) GetField() string { return "" } -type opFilter struct { - labels map[string]string +type opFilterMap struct { + label string + filterMap map[string]string } -// Process will return nil if labels don't match the filter -func (o opFilter) Process(field string, value interface{}, labels common.MapStr) (string, interface{}, common.MapStr) { - for k, v := range o.labels { - if labels[k] != v { - return "", nil, nil +// Called by the Prometheus helper to apply extra options on retrieved metrics +// Check whether the value of the specified label is allowed and, if yes, return the metric via the specified mapped field +// Else, if the specified label does not match the filter, return nil +// This is useful in cases where multiple Metricbeat fields need to be defined per Prometheus metric, based on label values +func (o opFilterMap) Process(field string, value interface{}, labels common.MapStr) (string, interface{}, common.MapStr) { + for k, v := range o.filterMap { + if labels[o.label] == k { + return fmt.Sprintf("%v.%v", field, v), value, labels } } - return field, value, labels + return "", nil, nil } type opLowercaseValue struct{} diff --git a/metricbeat/helper/prometheus/prometheus_test.go b/metricbeat/helper/prometheus/prometheus_test.go index 974f51f1a10..f044c2f422f 100644 --- a/metricbeat/helper/prometheus/prometheus_test.go +++ b/metricbeat/helper/prometheus/prometheus_test.go @@ -397,9 +397,36 @@ func TestPrometheus(t *testing.T) { msg: "Label metrics, filter", mapping: &MetricsMapping{ Metrics: map[string]MetricMap{ - "first_metric": LabelMetric("first.metric", "label4", OpLowercaseValue(), OpFilter(map[string]string{ - "foo": "filtered", - })), + "first_metric": LabelMetric("first.metric", "label4", OpFilterMap( + "label1", + map[string]string{"value1": "foo"}, + )), + }, + Labels: map[string]LabelMap{ + "label1": Label("labels.label1"), + }, + }, + expected: []common.MapStr{ + common.MapStr{ + "first": common.MapStr{ + "metric": common.MapStr{ + "foo": "FOO", + }, + }, + "labels": common.MapStr{ + "label1": "value1", + }, + }, + }, + }, + { + msg: "Label metrics, filter", + mapping: &MetricsMapping{ + Metrics: map[string]MetricMap{ + "first_metric": LabelMetric("first.metric", "label4", OpLowercaseValue(), OpFilterMap( + "foo", + map[string]string{"Filtered": "filtered"}, + )), }, Labels: map[string]LabelMap{ "label1": Label("labels.label1"), diff --git a/metricbeat/module/kubernetes/fields.go b/metricbeat/module/kubernetes/fields.go index aec239344fd..0b87da7ad9c 100644 --- a/metricbeat/module/kubernetes/fields.go +++ b/metricbeat/module/kubernetes/fields.go @@ -32,5 +32,5 @@ func init() { // AssetKubernetes returns asset data. // This is the base64 encoded gzipped contents of module/kubernetes. func AssetKubernetes() string { - return "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" + return "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" } diff --git a/metricbeat/module/kubernetes/state_node/_meta/fields.yml b/metricbeat/module/kubernetes/state_node/_meta/fields.yml index ea0f952089e..b7d5f2a1920 100644 --- a/metricbeat/module/kubernetes/state_node/_meta/fields.yml +++ b/metricbeat/module/kubernetes/state_node/_meta/fields.yml @@ -15,6 +15,22 @@ type: boolean description: > Node unschedulable status + - name: memory_pressure + type: boolean + description: > + Node MemoryPressure status + - name: disk_pressure + type: boolean + description: > + Node DiskPressure status + - name: out_of_disk + type: boolean + description: > + Node OutOfDisk status + - name: pid_pressure + type: boolean + description: > + Node PIDPressure status - name: cpu type: group fields: diff --git a/metricbeat/module/kubernetes/state_node/_meta/test/ksm.v1.3.0.expected b/metricbeat/module/kubernetes/state_node/_meta/test/ksm.v1.3.0.expected index c560b169d77..38a0cc0dae1 100644 --- a/metricbeat/module/kubernetes/state_node/_meta/test/ksm.v1.3.0.expected +++ b/metricbeat/module/kubernetes/state_node/_meta/test/ksm.v1.3.0.expected @@ -29,6 +29,9 @@ } }, "status": { + "disk_pressure": "false", + "memory_pressure": "false", + "out_of_disk": "false", "ready": "true", "unschedulable": false } diff --git a/metricbeat/module/kubernetes/state_node/_meta/test/ksm.v1.8.0.expected b/metricbeat/module/kubernetes/state_node/_meta/test/ksm.v1.8.0.expected index 7b0390a04a2..371c9ca294b 100644 --- a/metricbeat/module/kubernetes/state_node/_meta/test/ksm.v1.8.0.expected +++ b/metricbeat/module/kubernetes/state_node/_meta/test/ksm.v1.8.0.expected @@ -29,6 +29,9 @@ } }, "status": { + "disk_pressure": "false", + "memory_pressure": "false", + "pid_pressure": "false", "ready": "true", "unschedulable": false } diff --git a/metricbeat/module/kubernetes/state_node/state_node.go b/metricbeat/module/kubernetes/state_node/state_node.go index bd334915240..f2a1a6b965e 100644 --- a/metricbeat/module/kubernetes/state_node/state_node.go +++ b/metricbeat/module/kubernetes/state_node/state_node.go @@ -49,12 +49,16 @@ var ( "kube_node_status_allocatable_cpu_cores": p.Metric("cpu.allocatable.cores"), "kube_node_spec_unschedulable": p.BooleanMetric("status.unschedulable"), "kube_node_status_ready": p.LabelMetric("status.ready", "condition"), - "kube_node_status_condition": p.LabelMetric("status.ready", "status", - p.OpFilter(map[string]string{ - "condition": "Ready", - })), + "kube_node_status_condition": p.LabelMetric("status", "status", p.OpFilterMap( + "condition", map[string]string{ + "Ready": "ready", + "MemoryPressure": "memory_pressure", + "DiskPressure": "disk_pressure", + "OutOfDisk": "out_of_disk", + "PIDPressure": "pid_pressure", + }, + )), }, - Labels: map[string]p.LabelMap{ "node": p.KeyLabel("name"), },