Skip to content

[CONTP-1569] [CONTINT-5553] Add podCollectionMode field on kubeStateMetricsCore for node-side pod collection - #3027

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 6 commits into
mainfrom
minyi/ksm-pod-collection-on-node
Aug 28, 2026
Merged

[CONTP-1569] [CONTINT-5553] Add podCollectionMode field on kubeStateMetricsCore for node-side pod collection#3027
gh-worker-dd-mergequeue-cf854d[bot] merged 6 commits into
mainfrom
minyi/ksm-pod-collection-on-node

Conversation

@zhuminyi

@zhuminyi zhuminyi commented May 18, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds features.kubeStateMetricsCore.podCollectionMode options are default and node_kubelet on the
v2alpha1 DatadogAgent CRD.

  apiVersion: datadoghq.com/v2alpha1
  kind: DatadogAgent
  metadata:
    name: datadog
    namespace: datadog
  spec:
    features:
      clusterChecks:
        enabled: true
        useClusterChecksRunners: true  
      kubeStateMetricsCore:
        enabled: true
        podCollectionMode: node_kubelet   # THE SINGLE NEW TOGGLE 

When set to node_kubelet, the operator:

  • Updates the operator-generated cluster-side KSM ConfigMap (unless the user supplied their own conf) by:
    • Setting pod_collection_mode: cluster_unassigned and cluster_aggregates_enabled: true on the primary instance so it collects unscheduled pods while suppressing cluster-aggregate metrics.
    • Adding a second instance with pod_collection_mode: cluster_aggregates_only to collect those aggregate metrics centrally.
  • Generates a separate node-agent ConfigMap containing a pods-only check with pod_collection_mode: node_kubelet, cluster_aggregates_enabled: true, and collectors: [pods].

When a user supplies features.kubeStateMetricsCore.conf, the operator still creates the node-side check but does not modify the user-owned cluster-side YAML. The user must configure both cluster_unassigned and cluster_aggregates_only cluster-side instances themselves.

This is equivalent to the following manual configuration:

spec:
  features:
    kubeStateMetricsCore:
      enabled: true
      conf:
        configData: |-
          cluster_check: true
          init_config:
          instances:
            - skip_leader_election: true
              cluster_aggregates_enabled: true # silence aggregate metrics
              pod_collection_mode: cluster_unassigned # collect only unscheduled pods
            - skip_leader_election: true
              pod_collection_mode: cluster_aggregates_only # collect aggregate metrics centrally

  override:
    nodeAgent:
      extraConfd:
        configDataMap:
          kubernetes_state_core.yaml: |-
            init_config:
            instances:
              - collectors:
                  - pods
                cluster_aggregates_enabled: true # silence aggregate metrics
                pod_collection_mode: node_kubelet # collect scheduled pods from the node's Kubelet

Result: scheduled-pod metrics are emitted by each node agent locally from the Kubelet (no API-server traffic for pods). Unscheduled pods and every other KSM resource continue to be collected by the primary cluster-side instance, while cluster-aggregate metrics are collected by the dedicated cluster_aggregates_only instance. Default behavior is semantically unchanged when the field is unset.

Motivation

In large clusters, pod metrics dominate the cardinality of the monolithic kubernetes_state_core
cluster check, making it the scaling bottleneck. The agent has supported offloading pod collection to
node-agents via pod_collection_mode: node_kubelet since 7.58, but customers had to assemble two
coordinated configs manually (features.kubeStateMetricsCore.conf.configData for the cluster side,
override.nodeAgent.extraConfd for the node side) and carefully avoid double-collection or losing
unscheduled-pod metrics as well as live without .total aggregate metrics. This field collapses that into a single typed toggle.

Describe your test plan

Unit tests added in this PR:

  • TestKsmCheckConfigPodCollectionOnNode

    • When enabled, asserts the cluster-side configuration contains:
      • A cluster_unassigned instance with the full collector list and
        cluster_aggregates_enabled: true.
      • A separate cluster_aggregates_only instance without a collector list or
        cluster_aggregates_enabled.
    • When disabled, asserts there is only one instance and no
      pod_collection_mode is emitted.
  • Test_ksmFeature_buildKSMCorePodsOnNodeConfigMap

    • Asserts the node-side ConfigMap metadata and complete parsed YAML shape.
    • Verifies the check has no cluster_check or skip_leader_election fields.
    • Verifies the pods-only instance contains:
      • pod_collection_mode: node_kubelet
      • cluster_aggregates_enabled: true
      • collectors: [pods]

Manual end-to-end validation

# Both ConfigMaps are generated by the operator:
kubectl get cm -n datadog | grep state-metrics
# Expect:
#   <dda>-kube-state-metrics-core-config
#   <dda>-kube-state-metrics-core-pods-on-node-config

# The node agent mounts the node-side check:
kubectl get ds -n datadog datadog-agent \
  -o jsonpath='{.spec.template.spec.containers[?(@.name=="agent")].volumeMounts[?(@.name=="ksm-core-pods-on-node-config")]}'
# Expect: mountPath /etc/datadog-agent/conf.d/kubernetes_state_core.d, readOnly: true

# The node-side KSM check is loaded:
kubectl exec -n datadog ds/datadog-agent -c agent -- agent configcheck \
  | sed -n '/kubernetes_state_core check/,/===/p'
# Expect:
#   pod_collection_mode: node_kubelet
#   cluster_aggregates_enabled: true
#   collectors: [pods]

# The node-side check is running and emitting metrics:
kubectl exec -n datadog ds/datadog-agent -c agent -- agent status \
  | awk '/kubernetes_state_core/,/Status:/' | grep -E "Total Runs|Metric Samples"
# Expect: Total Runs > 0 and Metric Samples > 0 after approximately 30 seconds.

# The cluster-side configuration contains the split collector and aggregator:
kubectl exec -n datadog deploy/datadog-cluster-agent -- agent clusterchecks \
  | sed -n '/===== Checks on .*cluster-checks-runner/,/===== /p' \
  | grep -E "kubernetes_state_core|pod_collection_mode|cluster_aggregates_enabled"
# Expect:
#   cluster_unassigned with cluster_aggregates_enabled: true
#   a separate cluster_aggregates_only instance

Minimum Agent Versions

  • Node Agent: v7.82.0+
  • Cluster Agent: v7.82.0+
  • Cluster Checks Runner: v7.82.0+ when the KSM check runs there

@datadog-official

datadog-official Bot commented May 18, 2026

Copy link
Copy Markdown

Code Coverage

🎯 Code Coverage (details)
Patch Coverage: 83.53%
Overall Coverage: 50.75% (+0.58%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 3e30362 | Docs | View more details | Give us feedback!

@codecov-commenter

codecov-commenter commented May 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.52866% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 41.58%. Comparing base (4e59e1d) to head (c2b6cd3).
⚠️ Report is 223 commits behind head on main.

Files with missing lines Patch % Lines
pkg/testutils/builder.go 0.00% 12 Missing ⚠️
...atadogagent/feature/kubernetesstatecore/feature.go 83.07% 5 Missing and 6 partials ⚠️
...adogagent/feature/kubernetesstatecore/configmap.go 92.50% 3 Missing and 3 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3027      +/-   ##
==========================================
+ Coverage   41.50%   41.58%   +0.07%     
==========================================
  Files         335      334       -1     
  Lines       28714    28795      +81     
==========================================
+ Hits        11919    11975      +56     
- Misses      16001    16019      +18     
- Partials      794      801       +7     
Flag Coverage Δ
unittests 41.58% <81.52%> (+0.07%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
.../datadogagent/feature/kubernetesstatecore/const.go 100.00% <ø> (ø)
...adogagent/feature/kubernetesstatecore/configmap.go 92.07% <92.50%> (-5.15%) ⬇️
...atadogagent/feature/kubernetesstatecore/feature.go 80.00% <83.07%> (+0.74%) ⬆️
pkg/testutils/builder.go 0.00% <0.00%> (ø)

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4e59e1d...c2b6cd3. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@zhuminyi
zhuminyi force-pushed the minyi/ksm-pod-collection-on-node branch from dcdead3 to 9c80b42 Compare May 19, 2026 02:31
@zhuminyi zhuminyi changed the title Minyi/ksm pod collection on node [CONTP-1569] Add podCollectionMode field on kubeStateMetricsCore for node-side pod collection May 19, 2026
@zhuminyi
zhuminyi marked this pull request as ready for review May 19, 2026 02:51
@zhuminyi
zhuminyi requested a review from a team May 19, 2026 02:51
@zhuminyi
zhuminyi requested review from a team as code owners May 19, 2026 02:51
Comment thread api/datadoghq/v2alpha1/datadogagent_types.go Outdated
Comment thread internal/controller/datadogagent/feature/kubernetesstatecore/configmap_test.go Outdated
Comment thread internal/controller/datadogagent/feature/kubernetesstatecore/configmap_test.go Outdated
@khewonc khewonc modified the milestones: v1.28.0, v1.29.0 Jun 10, 2026
Comment thread api/datadoghq/v2alpha1/datadogagent_types.go Outdated
@zhuminyi
zhuminyi force-pushed the minyi/ksm-pod-collection-on-node branch from 09a7e15 to 8d867c3 Compare June 25, 2026 21:01
gh-worker-dd-mergequeue-cf854d Bot pushed a commit to DataDog/datadog-agent that referenced this pull request Jul 8, 2026
…egates_only` check (#53042)

### What does this PR do?
<img width="4452" height="1660" alt="image" src="https://github.com/user-attachments/assets/801431b2-f957-4441-a959-6f53c6b93cc3" />

Fixes under-reporting of the `kubernetes_state.{container}.<cpu|memory|gpu>_{requested,limit}.total`
family when the KSM check runs in `pod_collection_mode: node_kubelet`.

### Motivation
In `node_kubelet` mode every node agent computes the `.total` family from its *local* pod view and emits it with a **reduced tag set** —`[namespace, container, owner_kind, owner_name]`, with **no host/pod/node tag**.
Because every node emits a byte-identical series at (roughly) the same timestamp, the backend's same-tags/same-timestamp collision rule keeps only **one** node's value. Result: the cluster aggregation metrics are total under-reported, sum does not work either. 

Count-style metrics (`pod.count`, etc.) are unaffected — they carry a `node` tag, so per-node series stay distinct and sum correctly. Only the tag-collapsed `.total` family breaks.

A new `pod_collection_mode: cluster_aggregates_only`  watches all** pods and emits only the `.total` family. It will be run on clc runner but aggregate  just the 4 owner-tagged source metrics.

### Describe how you validated your changes
Suppression is enabled per-instance via the `cluster_aggregates_enabled` KSM check option, set on the `node_kubelet` and `cluster_unassigned` instances (the `cluster_aggregates_only` instance omits it).

For operator Datadog CR, related [PR](DataDog/datadog-operator#3027)
```
apiVersion: datadoghq.com/v2alpha1
kind: DatadogAgent
metadata:
  name: datadog
  namespace: datadog
spec:
  global:
    credentials:
      apiSecret:
        secretName: datadog-secret
        keyName: api-key
  features:
    kubeStateMetricsCore:
      enabled: true
      conf:
        configData: |-
          cluster_check: true
          init_config:
          instances:
            # non-pod resources + unassigned pods
            - skip_leader_election: true
              pod_collection_mode: cluster_unassigned
              cluster_aggregates_enabled: true
            - skip_leader_election: true
              pod_collection_mode: cluster_aggregates_only
    clusterChecks:
      enabled: true
  override:
    nodeAgent:
      extraConfd:
        configDataMap:
          kubernetes_state_core.yaml: |-
            init_config:
            instances:
              - collectors:
                  - pods
                pod_collection_mode: node_kubelet
                cluster_aggregates_enabled: true
```
For Helm install
```
datadog:
  clusterChecks:
    enabled: true
  kubeStateMetricsCore:
    enabled: false

  # node agents → node_kubelet (scheduled pods)
  confd:
    kubernetes_state_core.yaml: |-
      init_config:
      instances:
        - collectors:
            - pods
          pod_collection_mode: node_kubelet
          cluster_aggregates_enabled: true

clusterAgent:
  confd:
    kubernetes_state_core.yaml: |-
      cluster_check: true
      init_config:
      instances:
        - skip_leader_election: true
          pod_collection_mode: cluster_unassigned
          cluster_aggregates_enabled: true
        - skip_leader_election: true
          pod_collection_mode: cluster_aggregates_only
```

### Additional Notes


Co-authored-by: minyi.zhu <minyi.zhu@datadoghq.com>
@zhuminyi zhuminyi modified the milestones: v1.29.0, v1.30.0 Jul 8, 2026
@levan-m levan-m modified the milestones: v1.30.0, v1.31.0 Aug 21, 2026
Introduces features.kubeStateMetricsCore.podCollectionMode on the v2alpha1
DatadogAgent CRD (enum: default | node_kubelet).

When set to node_kubelet, the operator:

- injects pod_collection_mode: cluster_unassigned into the operator-generated
  cluster-side KSM ConfigMap (skipped when the user supplies their own .Conf
  override; the operator never mutates user-supplied YAML);
- generates a second ConfigMap with a pods-only check (pod_collection_mode:
  node_kubelet, collectors: [pods]);
- mounts that ConfigMap into every node agent (multi-container and
  single-container) at /etc/datadog-agent/conf.d/kubernetes_state_core.d/.

The resolved mode is part of the default-config checksum, so toggling the
field changes the cluster-agent pod-template annotation and forces a rollout
instead of leaving the in-memory ConfigMap stale.

Version gate: when either the cluster-side component (cluster-checks-runner
if enabled, otherwise cluster-agent) OR the node-agent override image is
parseable AND below 7.60, the operator skips the feature with a warning log
rather than mounting an unsupported file into an older node-agent.
Unparseable tags (:dev, :latest, custom registries) are assumed compatible.

When podCollectionMode=node_kubelet is set alongside
features.kubeStateMetricsCore.conf, the operator still deploys the node-side
check but logs a descriptive warning pointing the user at the two valid ways
to avoid double pod collection (omit pods from collectors, or set
pod_collection_mode: cluster_unassigned themselves).

Default behavior is byte-identical to today when the field is unset.
- Clarify PodCollectionMode godoc to disambiguate snake_case agent check
  options (pod_collection_mode in the rendered YAML) from the camelCase
  CRD field name (podCollectionMode).
- Replace manual if/t.Fatalf checks in Test_ksmFeature_buildKSMCorePodsOnNodeConfigMap
  with require.True / require.NoError for consistency with the rest of the
  package's test style.
- Use deep-equality on the parsed YAML in that test so future accidental
  additions to the generated node-side ConfigMap (e.g. an unwanted
  cluster_check field) fail the test rather than silently passing.
zhuminyi and others added 2 commits August 24, 2026 14:26
The previous godoc said cluster-wide aggregation metrics become "sliced per
host" and could be recovered with `sum by (kube_cluster_name)`. That is wrong
for the `.total` family (kubernetes_state.{container,initcontainer}.<res>_
{requested,limit}.total): those are emitted with a reduced tag set carrying no
host/node, so multiple node agents emit identical series that collide at
ingestion (last-write-wins) and under-report — unrecoverable at query time.
Host-tagged metrics like kubernetes_state.pod.count are unaffected.

Regenerated openapi, CRDs, and docs.
@justin-lesko
justin-lesko force-pushed the minyi/ksm-pod-collection-on-node branch from c2b6cd3 to 98aaa4d Compare August 24, 2026 18:33
@justin-lesko justin-lesko changed the title [CONTP-1569] Add podCollectionMode field on kubeStateMetricsCore for node-side pod collection [CONTP-1569] [CONTINT-5553] Add podCollectionMode field on kubeStateMetricsCore for node-side pod collection Aug 24, 2026
@Mathew-Estafanous Mathew-Estafanous self-assigned this Aug 24, 2026

@Mathew-Estafanous Mathew-Estafanous left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A couple small nits but everything else looks good.

Comment thread internal/controller/datadogagent/feature/kubernetesstatecore/feature.go Outdated
@linear-code

linear-code Bot commented Aug 28, 2026

Copy link
Copy Markdown

CONTP-191

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants