Skip to content

Commit c5cbfd7

Browse files
miconeilawslouisall
authored andcommitted
feat(container-insights): Add K8s events pipeline to cluster-scraper
Add a logs pipeline on the cluster-scraper that watches Kubernetes events via the k8s_events receiver and exports them through the OTLP exporter to CloudWatch Logs. Events are enriched with pod labels, workload identity, node metadata via nodemetadataenricher, and cluster-level attributes. Changes: - New k8s_events/cw_k8s_ci_v0 receiver (gated by events.enabled) - Transform processors for parsing involvedObject fields, setting workload, scope, and cloud_resource_id (all using log_statements) - Shared processors (k8sattributes, k8snodemetadata) re-gated to be available when either KSM or events is enabled - RBAC: added events to cluster-scraper ClusterRole - values.yaml: otelContainerInsights.events.enabled (default: true) - Integration test assertions for k8s_events routing
1 parent 4a36c50 commit c5cbfd7

7 files changed

Lines changed: 115 additions & 2 deletions

File tree

charts/amazon-cloudwatch-observability/templates/cloudwatch-agent-clusterrole.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ rules:
2626
verbs: [ "list", "watch", "get" ]
2727
- apiGroups: [ "" ]
2828
resources: [ "nodes/stats", "events" ]
29-
verbs: [ "create", "get" ]
29+
verbs: [ "create", "get", "list", "watch" ]
3030
{{- if .Values.otelContainerInsights.enabled }}
3131
- apiGroups: [ "" ]
3232
resources: [ "nodes/metrics" ]

charts/amazon-cloudwatch-observability/templates/linux/_otel-container-insights-cluster-scraper-config.tpl

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ extensions:
33
sigv4auth/cw_k8s_ci_v0_cwotel:
44
region: {{ .Values.region }}
55
service: monitoring
6+
{{- if .Values.otelContainerInsights.events.enabled }}
7+
sigv4auth/cw_k8s_ci_v0_cwlogs:
8+
region: {{ .Values.region }}
9+
service: logs
10+
{{- end }}
611
nodemetadatacache/cw_k8s_ci_v0:
712
namespace: {{ .Release.Namespace }}
813

@@ -48,6 +53,12 @@ receivers:
4853
- {{ include "kube-state-metrics.name" . }}.{{ .Release.Namespace }}.svc:{{ .Values.kubeStateMetrics.service.port }}
4954
{{- end }}
5055

56+
{{- if .Values.otelContainerInsights.events.enabled }}
57+
k8s_events/cw_k8s_ci_v0:
58+
auth_type: serviceAccount
59+
namespaces: []
60+
{{- end }}
61+
5162
processors:
5263
filter/cw_k8s_ci_v0_scrape_metadata:
5364
error_mode: ignore
@@ -216,7 +227,9 @@ processors:
216227
- set(attributes["cronjob"], resource.attributes["cronjob"]) where resource.attributes["cronjob"] != nil
217228
- set(attributes["owner_name"], resource.attributes["owner_name"]) where resource.attributes["owner_name"] != nil
218229
- set(attributes["owner_kind"], resource.attributes["owner_kind"]) where resource.attributes["owner_kind"] != nil
230+
{{- end }}
219231

232+
{{- if or .Values.kubeStateMetrics.enabled .Values.otelContainerInsights.events.enabled }}
220233
k8sattributes/cw_k8s_ci_v0_pod:
221234
auth_type: serviceAccount
222235
passthrough: false
@@ -350,17 +363,87 @@ processors:
350363
send_batch_max_size: 500
351364
timeout: 10s
352365

366+
{{- if .Values.otelContainerInsights.events.enabled }}
367+
transform/cw_k8s_ci_v0_events_parse:
368+
error_mode: ignore
369+
log_statements:
370+
- context: log
371+
statements:
372+
- set(resource.attributes["k8s.namespace.name"], attributes["k8s.namespace.name"]) where attributes["k8s.namespace.name"] != nil
373+
- set(resource.attributes["k8s.pod.name"], resource.attributes["k8s.object.name"]) where resource.attributes["k8s.object.kind"] == "Pod" and resource.attributes["k8s.object.name"] != nil
374+
375+
transform/cw_k8s_ci_v0_events_set_cluster_name:
376+
error_mode: ignore
377+
log_statements:
378+
- context: log
379+
statements:
380+
- set(resource.attributes["k8s.cluster.name"], "{{ .Values.clusterName }}")
381+
- set(resource.attributes["aws.log.group.names"], "/aws/containerinsights/{{ .Values.clusterName }}/events")
382+
383+
transform/cw_k8s_ci_v0_events_set_workload:
384+
error_mode: ignore
385+
log_statements:
386+
- context: log
387+
statements:
388+
- set(resource.attributes["k8s.workload.name"], resource.attributes["k8s.deployment.name"]) where resource.attributes["k8s.workload.name"] == nil and resource.attributes["k8s.deployment.name"] != nil
389+
- set(resource.attributes["k8s.workload.type"], "Deployment") where resource.attributes["k8s.workload.type"] == nil and resource.attributes["k8s.deployment.name"] != nil
390+
- set(resource.attributes["k8s.workload.name"], resource.attributes["k8s.statefulset.name"]) where resource.attributes["k8s.workload.name"] == nil and resource.attributes["k8s.statefulset.name"] != nil
391+
- set(resource.attributes["k8s.workload.type"], "StatefulSet") where resource.attributes["k8s.workload.type"] == nil and resource.attributes["k8s.statefulset.name"] != nil
392+
- set(resource.attributes["k8s.workload.name"], resource.attributes["k8s.daemonset.name"]) where resource.attributes["k8s.workload.name"] == nil and resource.attributes["k8s.daemonset.name"] != nil
393+
- set(resource.attributes["k8s.workload.type"], "DaemonSet") where resource.attributes["k8s.workload.type"] == nil and resource.attributes["k8s.daemonset.name"] != nil
394+
- set(resource.attributes["k8s.workload.name"], resource.attributes["k8s.job.name"]) where resource.attributes["k8s.workload.name"] == nil and resource.attributes["k8s.job.name"] != nil
395+
- set(resource.attributes["k8s.workload.type"], "Job") where resource.attributes["k8s.workload.type"] == nil and resource.attributes["k8s.job.name"] != nil
396+
- set(resource.attributes["k8s.workload.name"], resource.attributes["k8s.cronjob.name"]) where resource.attributes["k8s.workload.name"] == nil and resource.attributes["k8s.cronjob.name"] != nil
397+
- set(resource.attributes["k8s.workload.type"], "CronJob") where resource.attributes["k8s.workload.type"] == nil and resource.attributes["k8s.cronjob.name"] != nil
398+
- set(resource.attributes["k8s.workload.name"], resource.attributes["k8s.replicaset.name"]) where resource.attributes["k8s.workload.name"] == nil and resource.attributes["k8s.replicaset.name"] != nil
399+
- set(resource.attributes["k8s.workload.type"], "ReplicaSet") where resource.attributes["k8s.workload.type"] == nil and resource.attributes["k8s.replicaset.name"] != nil
400+
401+
transform/cw_k8s_ci_v0_events_set_scope:
402+
error_mode: ignore
403+
log_statements:
404+
- context: scope
405+
statements:
406+
- set(scope.name, "k8s.io/events")
407+
- set(attributes["cloudwatch.source"], "cloudwatch-agent")
408+
- set(attributes["cloudwatch.solution"], "k8s-otel-container-insights")
409+
- set(attributes["cloudwatch.pipeline"], "events")
410+
411+
transform/cw_k8s_ci_v0_events_set_cloud_resource_id:
412+
error_mode: ignore
413+
log_statements:
414+
- context: resource
415+
statements:
416+
- set(resource.attributes["cloud.resource_id"], Concat(["arn:aws:eks:", resource.attributes["cloud.region"], ":", resource.attributes["cloud.account.id"], ":cluster/", resource.attributes["k8s.cluster.name"]], ""))
417+
where resource.attributes["cloud.region"] != nil and resource.attributes["cloud.account.id"] != nil and resource.attributes["k8s.cluster.name"] != nil
418+
{{- end }}
419+
353420
exporters:
354421
otlphttp/cw_k8s_ci_v0_cwotel:
355422
endpoint: {{ if .Values.otelContainerInsights.cloudwatchMetricsEndpoint }}{{ .Values.otelContainerInsights.cloudwatchMetricsEndpoint | quote }}{{ else }}"https://monitoring.{{ .Values.region }}.amazonaws.com:443"{{ end }}
356423
tls:
357424
insecure: false
358425
auth:
359426
authenticator: sigv4auth/cw_k8s_ci_v0_cwotel
427+
{{- if .Values.otelContainerInsights.events.enabled }}
428+
otlphttp/cw_k8s_ci_v0_cwlogs:
429+
endpoint: "https://logs.{{ .Values.region }}.amazonaws.com:443"
430+
headers:
431+
x-aws-log-group: "/aws/containerinsights/{{ .Values.clusterName }}/events"
432+
x-aws-log-stream: "events"
433+
x-aws-log-group-create: "true"
434+
x-aws-log-stream-create: "true"
435+
tls:
436+
insecure: false
437+
auth:
438+
authenticator: sigv4auth/cw_k8s_ci_v0_cwlogs
439+
{{- end }}
360440

361441
service:
362442
extensions:
363443
- sigv4auth/cw_k8s_ci_v0_cwotel
444+
{{- if .Values.otelContainerInsights.events.enabled }}
445+
- sigv4auth/cw_k8s_ci_v0_cwlogs
446+
{{- end }}
364447
- nodemetadatacache/cw_k8s_ci_v0
365448
pipelines:
366449
metrics/cw_k8s_ci_v0_apiserver:
@@ -408,5 +491,22 @@ service:
408491
exporters:
409492
- otlphttp/cw_k8s_ci_v0_cwotel
410493
{{- end }}
494+
{{- if .Values.otelContainerInsights.events.enabled }}
495+
logs/cw_k8s_ci_v0_events:
496+
receivers: [k8s_events/cw_k8s_ci_v0]
497+
processors:
498+
- transform/cw_k8s_ci_v0_events_parse
499+
- transform/cw_k8s_ci_v0_events_set_cluster_name
500+
- k8sattributes/cw_k8s_ci_v0_pod
501+
- k8sattributes/cw_k8s_ci_v0_node
502+
- transform/cw_k8s_ci_v0_events_set_workload
503+
- resourcedetection/cw_k8s_ci_v0
504+
- nodemetadataenricher/cw_k8s_ci_v0
505+
- transform/cw_k8s_ci_v0_events_set_scope
506+
- transform/cw_k8s_ci_v0_events_set_cloud_resource_id
507+
- batch/cw_k8s_ci_v0_cwotel
508+
exporters:
509+
- otlphttp/cw_k8s_ci_v0_cwlogs
510+
{{- end }}
411511
{{- end -}}
412512

charts/amazon-cloudwatch-observability/templates/linux/otel-container-insights-cluster-scraper-clusterrole.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ metadata:
1515
{{- include "amazon-cloudwatch-observability.labels" . | nindent 4 }}
1616
rules:
1717
- apiGroups: [""]
18-
resources: ["pods", "nodes", "namespaces", "endpoints", "services"]
18+
resources: ["pods", "nodes", "namespaces", "endpoints", "services", "events"]
1919
verbs: ["get", "list", "watch"]
2020
- apiGroups: ["discovery.k8s.io"]
2121
resources: ["endpointslices"]

charts/amazon-cloudwatch-observability/values.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,3 +1294,6 @@ otelContainerInsights:
12941294
metricResolution: "30s"
12951295
# Override the CloudWatch Metrics OTLP endpoint. If empty, defaults to monitoring.<region>.amazonaws.com.
12961296
cloudwatchMetricsEndpoint: ""
1297+
## Kubernetes Events collection. Events are collected on the cluster-scraper agent and exported as logs.
1298+
events:
1299+
enabled: true

integration-tests/amazon-cloudwatch-observability/validations/minikube/scenarios/feature_targeted_default_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ func validateOTELConfigRouting(t *testing.T, agentMap map[string]unstructured.Un
129129
"cloudwatch-agent otelConfig should NOT contain apiserver receiver (cluster-level)")
130130
assert.False(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_kube_state_metrics"),
131131
"cloudwatch-agent otelConfig should NOT contain kube_state_metrics receiver (cluster-level)")
132+
assert.False(t, strings.Contains(otelConfig, "k8s_events"),
133+
"cloudwatch-agent otelConfig should NOT contain k8s_events receiver (cluster-level)")
132134
})
133135

134136
// Validate cluster-scraper has cluster-level OTEL config
@@ -154,6 +156,8 @@ func validateOTELConfigRouting(t *testing.T, agentMap map[string]unstructured.Un
154156
"cluster-scraper otelConfig should contain apiserver receiver (cluster-level)")
155157
assert.True(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_kube_state_metrics"),
156158
"cluster-scraper otelConfig should contain kube_state_metrics receiver (cluster-level)")
159+
assert.True(t, strings.Contains(otelConfig, "k8s_events"),
160+
"cluster-scraper otelConfig should contain k8s_events receiver")
157161

158162
// Cluster-level config should NOT contain node-level receivers
159163
assert.False(t, strings.Contains(otelConfig, "kubeletstats"),

integration-tests/amazon-cloudwatch-observability/validations/minikube/scenarios/feature_targeted_multi_agent_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ func validateCloudWatchAgentFullConfig(t *testing.T, agentMap map[string]unstruc
143143
"cloudwatch-agent otelConfig should contain kubelet receiver (node-level)")
144144
assert.False(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_apiserver"),
145145
"cloudwatch-agent otelConfig should NOT contain apiserver receiver (cluster-level)")
146+
assert.False(t, strings.Contains(otelConfig, "k8s_events"),
147+
"cloudwatch-agent otelConfig should NOT contain k8s_events receiver (cluster-level)")
146148
}
147149

148150
// validatePrometheusAgentMinimalConfig verifies prometheus-agent gets minimal config:
@@ -234,6 +236,8 @@ func validateClusterScraperConfig(t *testing.T, agentMap map[string]unstructured
234236
"cluster-scraper otelConfig should contain apiserver receiver (cluster-level)")
235237
assert.True(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_kube_state_metrics"),
236238
"cluster-scraper otelConfig should contain kube_state_metrics receiver (cluster-level)")
239+
assert.True(t, strings.Contains(otelConfig, "k8s_events"),
240+
"cluster-scraper otelConfig should contain k8s_events receiver")
237241
assert.False(t, strings.Contains(otelConfig, "kubeletstats"),
238242
"cluster-scraper otelConfig should NOT contain kubeletstats receiver (node-level)")
239243
assert.False(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_kubelet"),

integration-tests/amazon-cloudwatch-observability/validations/minikube/scenarios/feature_targeted_split_features_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,8 @@ func validateSplitFeaturesClusterScraperConfig(t *testing.T, agentMap map[string
253253
"cluster-scraper otelConfig should contain apiserver receiver (cluster-level)")
254254
assert.True(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_kube_state_metrics"),
255255
"cluster-scraper otelConfig should contain kube_state_metrics receiver (cluster-level)")
256+
assert.True(t, strings.Contains(otelConfig, "k8s_events"),
257+
"cluster-scraper otelConfig should contain k8s_events receiver")
256258
assert.False(t, strings.Contains(otelConfig, "kubeletstats"),
257259
"cluster-scraper otelConfig should NOT contain kubeletstats receiver (node-level)")
258260
assert.False(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_kubelet"),

0 commit comments

Comments
 (0)