@@ -157,69 +157,54 @@ func (f *ksmFeature) Configure(dda metav1.Object, ddaSpec *v2alpha1.DatadogAgent
157157 }
158158 }
159159
160+ // Capture the user-supplied custom config (if any) so PodCollectionMode
161+ // resolution below can decide whether to mutate the cluster-side YAML.
160162 if ddaSpec .Features .KubeStateMetricsCore .Conf != nil {
161163 f .customConfig = ddaSpec .Features .KubeStateMetricsCore .Conf
162- hash , err := comparison .GenerateMD5ForSpec (f .customConfig )
163- if err != nil {
164- f .logger .Error (err , "couldn't generate hash for ksm core custom config" )
165- } else {
166- f .logger .V (2 ).Info ("built ksm core from custom config" , "hash" , hash )
167- }
168- f .customConfigAnnotationValue = hash
169- f .customConfigAnnotationKey = object .GetChecksumAnnotationKey (feature .KubernetesStateCoreIDType )
170- } else {
171- // Generate dynamic checksum for default configuration (based on user provided collectCrMetrics field and whether or not APIServices/CRD metrics are collected)
172- defaultConfigData := map [string ]any {
173- "collect_crds" : f .collectCRDMetrics ,
174- "collect_apiservices" : f .collectAPIServiceMetrics ,
175- "collect_cr_metrics" : f .collectCrMetrics ,
176- }
177-
178- hash , err := comparison .GenerateMD5ForSpec (defaultConfigData )
179- if err != nil {
180- f .logger .Error (err , "couldn't generate hash for default ksm core config" )
181- } else {
182- f .logger .V (2 ).Info ("generated default ksm core config hash" , "hash" , hash , "config" , defaultConfigData )
183- }
184- f .customConfigAnnotationValue = hash
185- f .customConfigAnnotationKey = object .GetChecksumAnnotationKey (feature .KubernetesStateCoreIDType )
186164 }
187165
188- f .configConfigMapName = constants .GetConfName (dda , f .customConfig , defaultKubeStateMetricsCoreConf )
189-
190- // Resolve PodCollectionMode. When node_kubelet is requested AND the
191- // agent version supports it, switch the cluster-side instance to
192- // pod_collection_mode: cluster_unassigned (only when the operator owns
193- // the cluster-side config) and deploy a pods-only check to every node
194- // agent. When the user supplies their own .Conf, the operator still
195- // deploys the node-side check but does not mutate the user's YAML;
196- // they are responsible for setting cluster_unassigned themselves.
166+ // Resolve PodCollectionMode. When node_kubelet is requested AND every
167+ // component that loads the check is version-compatible, switch the
168+ // cluster-side instance to pod_collection_mode: cluster_unassigned
169+ // (only when the operator owns the cluster-side config) and deploy a
170+ // pods-only check to every node agent. When the user supplies their
171+ // own .Conf, the operator still deploys the node-side check but does
172+ // not mutate the user's YAML; they are responsible for setting
173+ // cluster_unassigned themselves.
197174 if mode := ddaSpec .Features .KubeStateMetricsCore .PodCollectionMode ; mode != nil &&
198175 * mode == v2alpha1 .KSMPodCollectionModeNodeKubelet {
199176 f .podCollectionOnNode = true
200- // Version compatibility check on the component that runs the
201- // cluster-side check (CCR if cluster-checks-runners are enabled,
202- // otherwise the cluster-agent). Unparseable tags (`:dev`, custom
203- // registries, etc.) are assumed compatible — matches the existing
204- // pattern for the apiservices/CRD checks above.
205- var override * v2alpha1.DatadogAgentComponentOverride
206- if ovr , ok := ddaSpec .Override [v2alpha1 .ClusterAgentComponentName ]; ok {
207- override = ovr
208- }
177+ // Version compatibility check on BOTH sides: the cluster-side
178+ // component that runs the cluster_unassigned check (CCR if cluster-
179+ // checks-runners are enabled, otherwise the cluster-agent) AND the
180+ // node-agent that runs the node_kubelet check. If either image tag
181+ // is parseable AND below the supported floor, skip the feature so
182+ // the operator doesn't mount an unsupported file into a node-agent
183+ // that would silently fall back to default mode and double-collect.
184+ // Unparseable tags (`:dev`, custom registries, etc.) are assumed
185+ // compatible — matches the existing pattern for the apiservices/CRD
186+ // checks above.
187+ componentsToCheck := []v2alpha1.ComponentName {v2alpha1 .NodeAgentComponentName }
209188 if f .runInClusterChecksRunner {
210- if ovr , ok := ddaSpec . Override [ v2alpha1 .ClusterChecksRunnerComponentName ]; ok {
211- override = ovr
212- }
189+ componentsToCheck = append ( componentsToCheck , v2alpha1 .ClusterChecksRunnerComponentName )
190+ } else {
191+ componentsToCheck = append ( componentsToCheck , v2alpha1 . ClusterAgentComponentName )
213192 }
214- if override != nil && override .Image != nil {
215- agentVersion := common .GetAgentVersionFromImage (* override .Image )
193+ for _ , comp := range componentsToCheck {
194+ ovr , ok := ddaSpec .Override [comp ]
195+ if ! ok || ovr == nil || ovr .Image == nil {
196+ continue
197+ }
198+ agentVersion := common .GetAgentVersionFromImage (* ovr .Image )
216199 fallback := true // assume compatible when unparseable
217200 if ! utils .IsAboveMinVersion (agentVersion , podCollectionOnNodeMinVersion , & fallback ) {
218201 f .logger .Info (
219- "PodCollectionMode=node_kubelet requires agent >= 7.58; falling back to default" ,
202+ "PodCollectionMode=node_kubelet requires agent >= 7.60; falling back to default" ,
203+ "component" , string (comp ),
220204 "version" , agentVersion ,
221205 )
222206 f .podCollectionOnNode = false
207+ break
223208 }
224209 }
225210 if f .podCollectionOnNode {
@@ -237,6 +222,41 @@ func (f *ksmFeature) Configure(dda metav1.Object, ddaSpec *v2alpha1.DatadogAgent
237222 }
238223 }
239224 }
225+
226+ // Compute the checksum annotation. With f.podCollectionOnNode resolved
227+ // above, toggling the field changes the input here, which propagates
228+ // to the cluster-agent pod-template annotation and forces a rollout.
229+ if f .customConfig != nil {
230+ hash , err := comparison .GenerateMD5ForSpec (f .customConfig )
231+ if err != nil {
232+ f .logger .Error (err , "couldn't generate hash for ksm core custom config" )
233+ } else {
234+ f .logger .V (2 ).Info ("built ksm core from custom config" , "hash" , hash )
235+ }
236+ f .customConfigAnnotationValue = hash
237+ f .customConfigAnnotationKey = object .GetChecksumAnnotationKey (feature .KubernetesStateCoreIDType )
238+ } else {
239+ // Dynamic checksum for the default configuration. Includes every
240+ // input that affects the rendered cluster-side ConfigMap so that
241+ // toggling any of them forces a rollout of the consumer.
242+ defaultConfigData := map [string ]any {
243+ "collect_crds" : f .collectCRDMetrics ,
244+ "collect_apiservices" : f .collectAPIServiceMetrics ,
245+ "collect_cr_metrics" : f .collectCrMetrics ,
246+ "pod_collection_on_node" : f .podCollectionOnNode ,
247+ }
248+
249+ hash , err := comparison .GenerateMD5ForSpec (defaultConfigData )
250+ if err != nil {
251+ f .logger .Error (err , "couldn't generate hash for default ksm core config" )
252+ } else {
253+ f .logger .V (2 ).Info ("generated default ksm core config hash" , "hash" , hash , "config" , defaultConfigData )
254+ }
255+ f .customConfigAnnotationValue = hash
256+ f .customConfigAnnotationKey = object .GetChecksumAnnotationKey (feature .KubernetesStateCoreIDType )
257+ }
258+
259+ f .configConfigMapName = constants .GetConfName (dda , f .customConfig , defaultKubeStateMetricsCoreConf )
240260 }
241261
242262 return output
0 commit comments