Skip to content

[amazon-cloudwatch-observability] feat: add ServiceMonitor/PodMonitor v2 OTLP scraping path - #329

Open
wenegiemepraise wants to merge 7 commits into
aws-observability:mainfrom
wenegiemepraise:otel-ci-smpm-scrape-path
Open

[amazon-cloudwatch-observability] feat: add ServiceMonitor/PodMonitor v2 OTLP scraping path#329
wenegiemepraise wants to merge 7 commits into
aws-observability:mainfrom
wenegiemepraise:otel-ci-smpm-scrape-path

Conversation

@wenegiemepraise

Copy link
Copy Markdown

Summary

Add the ServiceMonitor/PodMonitor v2 OTLP scraping path to the chart: a prometheuscr
receiver + pipeline driven by the Target Allocator's PrometheusCR discovery, plus the
enablement flags and gating helper. This is the foundation that lets the CloudWatch Agent
scrape community monitoring.coreos.com ServiceMonitor/PodMonitor CRs into CloudWatch via
the OTEL Container Insights path.

What this enables

When otelContainerInsights.enabled is set, the chart wires the Target Allocator +
prometheuscr receiver so SM/PM CRs are discovered and scraped (default consistent-hashing
allocation). ServiceMonitor and PodMonitor discovery can each be toggled via
otelContainerInsights.serviceMonitor.enabled / podMonitor.enabled (default on).

Scope

  • prometheuscr receiver + metrics pipeline in the OTEL Container Insights config
  • otelCIScrape gating helper
  • otelContainerInsights.serviceMonitor/podMonitor.enabled values (only take effect when
    otelContainerInsights.enabled)
  • Read-only clusterrole additions for the discovery path

This is the base of a small stack; per-node allocation + node-enrichment transforms land
in a follow-up that builds on this path.

Testing

helm template:

  • otelContainerInsights.enabled=true → the prometheuscr receiver/pipeline renders.
  • default (disabled) → the path does not render (0 references).

helm lint clean (pre-existing fluent-bit-windows region warning is unrelated).

…path

Route Prometheus ServiceMonitor/PodMonitor metrics through the
otelContainerInsights v2 pipeline to CloudWatch using the Target Allocator's
prometheusCR discovery:

- _otel-container-insights-config.tpl: add the prometheus/cw_k8s_ci_v0_prometheuscr
  receiver (Target Allocator endpoint, mTLS) and the metrics/cw_k8s_ci_v0_prometheuscr
  pipeline (filter/scrape_metadata, metricstarttime, set_cluster_name,
  set_scope_prometheuscr, resourcedetection, batch) exported via otlphttp.
- _helpers.tpl: add the cloudwatch-agent.otelCIScrapeEnabled helper
  (otelContainerInsights.enabled AND targetAgent AND serviceMonitor/podMonitor on).
- cloudwatch-agent-custom-resource.yaml: render targetAllocator.enabled and
  prometheusCR.enabled on the AmazonCloudWatchAgent CR when otelCIScrape is on.
- target-allocator-clusterrole.yaml / -clusterrolebinding.yaml: grant the TA RBAC
  to get/list/watch podmonitors and servicemonitors when otelCIScrape is on.
- values.yaml: add otelContainerInsights.serviceMonitor.enabled and
  .podMonitor.enabled (default true).
- operator-deployment.yaml: supporting wiring.
The prior comment attributed a $-collapsing pass to Helm. Helm passes the text
through unchanged; the two $$->$ collapses happen in the agent's OTel confmap
stack (legacy expandconverter + the resolver's built-in ${...} expansion), so
$$$1 lands as the literal $1 regex backreference. Comment-only; render unchanged.
- "--auto-instrumentation-nodejs-image={{ template "auto-instrumentation-nodejs.image" . }}"
- "--target-allocator-image={{ template "target-allocator.image" (merge .Values.agent.prometheus.targetAllocator.image (dict "region" $.Values.region)) }}"
- "--feature-gates=operator.autoinstrumentation.multiinstrumentation,operator.autoinstrumentation.multiinstrumentation.skipcontainervalidation"
- "--feature-gates=operator.autoinstrumentation.multi-instrumentation,operator.autoinstrumentation.multi-instrumentation.skip-container-validation"

@musa-asad musa-asad Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

These --feature-gates values are hyphenated (multi-instrumentation), but the pinned operator only registers the unhyphenated forms, so it rejects the gate and CrashLoopBackOffs and nothing reconciles. This also looks out of scope here. Can we restore the unhyphenated line?

## Allocator's prometheusCR discovery. Defaults to true: when
## otelContainerInsights is enabled, ServiceMonitor scraping is on unless
## explicitly disabled here. Only takes effect when otelContainerInsights.enabled is true.
serviceMonitor:

@musa-asad musa-asad Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

serviceMonitor.enabled and podMonitor.enabled default to true, so the moment someone turns on otelContainerInsights they silently get prometheusCR discovery, whose CRD and RBAC this PR doesn't set up yet. Could we default them to false, or gate the path on the CRDs being present?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The default-on behavior is intentional, the product goal is that enabling otelContainerInsights gives you working SM/PM scraping out of the box, and the surrounding work is built to make that safe: the operator's CRD-resilience change(#394) makes missing CRDs non-fatal (no crash/loop), and CRD bundling (#331) puts the CRDs in-cluster. So I'd like to keep SM/PM default-on rather than invert to opt-in.

- set(attributes["cloudwatch.solution"], "k8s-otel-container-insights")
- set(attributes["cloudwatch.pipeline"], "efa")

transform/cw_k8s_ci_v0_set_scope_prometheuscr:

@musa-asad musa-asad Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The scraping receiver and pipeline are guarded on serviceMonitor.enabled/podMonitor.enabled, but the transform/cw_k8s_ci_v0_set_scope_prometheuscr processor isn't, so turning both monitors off leaves it orphaned and the collector warns. Can we guard it the same way via otelCIScrapeEnabled?

{{- if .Values.agent.enabled }}
{{- range $i, $customAgent := .Values.agents }}
{{- if and (and (hasKey ($customAgent.prometheus) "targetAllocator") (hasKey ($customAgent.prometheus.targetAllocator) "enabled")) $customAgent.prometheus.targetAllocator.enabled }}
{{- $otelCIScrape := eq (include "cloudwatch-agent.otelCIScrapeEnabled" (dict "agentName" $customAgent.name "context" $)) "true" }}

@musa-asad musa-asad Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This renders the ClusterRole inside range .Values.agents with a static name, so two matching agents produce duplicate ClusterRoles and Kubernetes just keeps the last write. Could we namespace the name per agent (same for the ClusterRoleBinding), or document that only one agent is supported?

{{- define "cloudwatch-agent.otelCIScrapeEnabled" -}}
{{- $ctx := .context -}}
{{- $agentName := .agentName -}}
{{- if and $ctx.Values.otelContainerInsights.enabled (eq $agentName $ctx.Values.otelContainerInsights.targetAgent) (or $ctx.Values.otelContainerInsights.serviceMonitor.enabled $ctx.Values.otelContainerInsights.podMonitor.enabled) -}}

@musa-asad musa-asad Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This helper reads otelContainerInsights.serviceMonitor.enabled directly. Fine today since the PR ships those keys as defaults, but it goes nil if a user replaces the whole map, so a dig with a default fallback might be safer.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Since the subkeys ship as chart defaults and Helm deep-merges them, they're present under any partial override, so this is safe as-is I'll leave the direct access

prometheus/cw_k8s_ci_v0_prometheuscr:
target_allocator:
endpoint: https://{{ .Values.otelContainerInsights.targetAgent }}-target-allocator-service:80
interval: {{ .Values.otelContainerInsights.metricResolution }}

@musa-asad musa-asad Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

interval here is the collector to Target Allocator refresh cadence, but it reuses otelContainerInsights.metricResolution, which is really the scrape cadence (prometheusCR.scrapeInterval). Worth a comment or a separate field so the two can diverge later?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Agreed they're distinct knobs. I'd rather not add a second field preemptively, but the shared value being non-obvious is fair

The Target Allocator watches the ServiceMonitor/PodMonitor CRDs to start/stop
informers as CRDs appear or disappear. Under the otelContainerInsights scraping
path the TA ClusterRole granted list/watch on the monitors but not on
customresourcedefinitions, so the operator-side CRD watch was denied, the CRD
gate never opened, and SM/PM discovery failed quietly. Add the read-only
get/list/watch grant on customresourcedefinitions beside the monitoring.coreos.com
rule under the same $otelCIScrape gate. Comment-only render change; gated off by
default.
This branch had flipped the operator --feature-gates to the hyphenated
multi-instrumentation / multi-instrumentation.skip-container-validation form.
The pinned operator (cloudwatch-agent-operator:3.5.0) registers the
unhyphenated IDs (operator.autoinstrumentation.multiinstrumentation and
.multiinstrumentation.skipcontainervalidation), so the hyphenated forms are
unrecognized; the manager parses gates with ExitOnError and CrashLoopBackOffs,
which stops all CR reconciliation and breaks the whole chart on install/upgrade.
Out of scope for the scraping work; restore the unhyphenated IDs to match main.
…ment

The transform/cw_k8s_ci_v0_set_scope_prometheuscr processor was defined
unconditionally, while its receiver and pipeline are guarded by
serviceMonitor.enabled OR podMonitor.enabled. With otelContainerInsights
enabled but both monitors disabled, the processor rendered orphaned (declared,
referenced by no pipeline), which the collector warns about. Wrap it in the
same guard so the receiver, pipeline and processor stay in sync.
The TA ClusterRole and ClusterRoleBinding rendered inside range .Values.agents
with a static object name, so if more than one agent satisfied the gate (which
$otelCIScrape widens), two identically named objects rendered and Kubernetes
applied last-write-wins, silently clobbering one. All TAs share one
ServiceAccount (target-allocator-service-acct), so a single ClusterRole/Binding
suffices: pre-scan agents into needsTA/needsCR and render exactly once, with the
SM/PM + customresourcedefinitions rules included when any agent needs discovery.
Renders once even with multiple matching agents; verified 1 object each.
@wenegiemepraise

Copy link
Copy Markdown
Author

Makes sense. The pinned cloudwatch-agent-target-allocator:1.0.0 predates the discovery path, and enabling scraping against it CrashLoops the TA rather than degrading, so a discovery-capable TA image must be pinned before this path is enabled (landing in the same release that exposes it, or the path stays off until then).

wenegiemepraise added a commit to wenegiemepraise/helm-charts that referenced this pull request Jul 22, 2026
…node-allocation

Re-sync aws-observability#330 onto the updated scraping-path branch: inherit the feature-gate ID
fix, co-gated CRD RBAC render-once ClusterRole/Binding, the $$$1 comment
correction, and the prometheuscr scope-processor guard, alongside aws-observability#330's per-node
work.
wenegiemepraise added a commit to wenegiemepraise/helm-charts that referenced this pull request Jul 22, 2026
…bundling

Re-sync onto the updated scraping-path branch to inherit the feature-gate ID
fix, the co-gated customresourcedefinitions RBAC rule (render-once ClusterRole/
Binding), the $$$1 comment correction, and the prometheuscr scope-processor
guard. Resolves the CRD-RBAC matrix assertion (now 11/0) and the feature-gate
blocker on this branch.
wenegiemepraise added a commit to wenegiemepraise/helm-charts that referenced this pull request Jul 22, 2026
…bundling

Re-sync aws-observability#331 onto aws-observability#330 so the stack is linear (aws-observability#329 -> aws-observability#330 -> aws-observability#331): inherit
the per-node allocationStrategy scoping/gating, the SM/PM node-stamping removal,
and the context-aware allocationStrategy default, on top of aws-observability#331's CRD bundling.
wenegiemepraise added a commit to wenegiemepraise/helm-charts that referenced this pull request Jul 23, 2026
…cture

Re-sync aws-observability#332 onto the updated stack (aws-observability#329->aws-observability#330->aws-observability#331). Resolutions favor the
settled older-PR fixes applied onto aws-observability#332's prometheusScrape.* restructure:
- prometheusCRDsEnabled: keep aws-observability#331's explicit auto branch + invalid-value fail,
  gated on aws-observability#332's otelContainerInsights.enabled && prometheusScrape.enabled.
- allocationStrategy: use aws-observability#332's precomputed $allocationStrategy (per-node on the
  otelCI path via prometheusScrape.allocationStrategy, consistent-hashing for
  direct-TA) -- supersedes aws-observability#330's ternary while preserving its intent.
- Fix a stale old-key guard (otelContainerInsights.serviceMonitor.enabled) left by
  auto-merge in the prometheuscr scope-processor to prometheusScrape.enabled.
- Keep aws-observability#330's SM/PM node-stamping removal; port aws-observability#331's GitOps opt-out caveat into
  the new prometheusScrape.crds doc.
Verified: lint clean, CRD matrix 21/0, flag matrix 8/8.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants