Skip to content

Commit cf0d471

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 c1b7f66 commit cf0d471

6 files changed

Lines changed: 114 additions & 1 deletion

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
@@ -348,17 +361,87 @@ processors:
348361
send_batch_max_size: 500
349362
timeout: 10s
350363

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

359439
service:
360440
extensions:
361441
- sigv4auth/cw_k8s_ci_v0_cwotel
442+
{{- if .Values.otelContainerInsights.events.enabled }}
443+
- sigv4auth/cw_k8s_ci_v0_cwlogs
444+
{{- end }}
362445
- nodemetadatacache/cw_k8s_ci_v0
363446
pipelines:
364447
metrics/cw_k8s_ci_v0_apiserver:
@@ -405,5 +488,22 @@ service:
405488
exporters:
406489
- otlphttp/cw_k8s_ci_v0_cwotel
407490
{{- end }}
491+
{{- if .Values.otelContainerInsights.events.enabled }}
492+
logs/cw_k8s_ci_v0_events:
493+
receivers: [k8s_events/cw_k8s_ci_v0]
494+
processors:
495+
- transform/cw_k8s_ci_v0_events_parse
496+
- transform/cw_k8s_ci_v0_events_set_cluster_name
497+
- k8sattributes/cw_k8s_ci_v0_pod
498+
- k8sattributes/cw_k8s_ci_v0_node
499+
- transform/cw_k8s_ci_v0_events_set_workload
500+
- resourcedetection/cw_k8s_ci_v0
501+
- nodemetadataenricher/cw_k8s_ci_v0
502+
- transform/cw_k8s_ci_v0_events_set_scope
503+
- transform/cw_k8s_ci_v0_events_set_cloud_resource_id
504+
- batch/cw_k8s_ci_v0_cwotel
505+
exporters:
506+
- otlphttp/cw_k8s_ci_v0_cwlogs
507+
{{- end }}
408508
{{- end -}}
409509

charts/amazon-cloudwatch-observability/values.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,3 +1135,6 @@ otelContainerInsights:
11351135
metricResolution: "30s"
11361136
# Override the CloudWatch Metrics OTLP endpoint. If empty, defaults to monitoring.<region>.amazonaws.com.
11371137
cloudwatchMetricsEndpoint: ""
1138+
## Kubernetes Events collection. Events are collected on the cluster-scraper agent and exported as logs.
1139+
events:
1140+
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
@@ -122,6 +122,8 @@ func validateOTELConfigRouting(t *testing.T, agentMap map[string]unstructured.Un
122122
"cloudwatch-agent otelConfig should NOT contain apiserver receiver (cluster-level)")
123123
assert.False(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_kube_state_metrics"),
124124
"cloudwatch-agent otelConfig should NOT contain kube_state_metrics receiver (cluster-level)")
125+
assert.False(t, strings.Contains(otelConfig, "k8s_events"),
126+
"cloudwatch-agent otelConfig should NOT contain k8s_events receiver (cluster-level)")
125127
})
126128

127129
// Validate cluster-scraper has cluster-level OTEL config
@@ -147,6 +149,8 @@ func validateOTELConfigRouting(t *testing.T, agentMap map[string]unstructured.Un
147149
"cluster-scraper otelConfig should contain apiserver receiver (cluster-level)")
148150
assert.True(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_kube_state_metrics"),
149151
"cluster-scraper otelConfig should contain kube_state_metrics receiver (cluster-level)")
152+
assert.True(t, strings.Contains(otelConfig, "k8s_events"),
153+
"cluster-scraper otelConfig should contain k8s_events receiver")
150154

151155
// Cluster-level config should NOT contain node-level receivers
152156
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
@@ -134,6 +134,8 @@ func validateCloudWatchAgentFullConfig(t *testing.T, agentMap map[string]unstruc
134134
"cloudwatch-agent otelConfig should contain kubeletstats receiver (node-level)")
135135
assert.False(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_apiserver"),
136136
"cloudwatch-agent otelConfig should NOT contain apiserver receiver (cluster-level)")
137+
assert.False(t, strings.Contains(otelConfig, "k8s_events"),
138+
"cloudwatch-agent otelConfig should NOT contain k8s_events receiver (cluster-level)")
137139
}
138140

139141
// validatePrometheusAgentMinimalConfig verifies prometheus-agent gets minimal config:
@@ -221,6 +223,8 @@ func validateClusterScraperConfig(t *testing.T, agentMap map[string]unstructured
221223
"cluster-scraper otelConfig should contain apiserver receiver (cluster-level)")
222224
assert.True(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_kube_state_metrics"),
223225
"cluster-scraper otelConfig should contain kube_state_metrics receiver (cluster-level)")
226+
assert.True(t, strings.Contains(otelConfig, "k8s_events"),
227+
"cluster-scraper otelConfig should contain k8s_events receiver")
224228
assert.False(t, strings.Contains(otelConfig, "kubeletstats"),
225229
"cluster-scraper otelConfig should NOT contain kubeletstats receiver (node-level)")
226230
}

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
@@ -238,6 +238,8 @@ func validateSplitFeaturesClusterScraperConfig(t *testing.T, agentMap map[string
238238
"cluster-scraper otelConfig should contain apiserver receiver (cluster-level)")
239239
assert.True(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_kube_state_metrics"),
240240
"cluster-scraper otelConfig should contain kube_state_metrics receiver (cluster-level)")
241+
assert.True(t, strings.Contains(otelConfig, "k8s_events"),
242+
"cluster-scraper otelConfig should contain k8s_events receiver")
241243
assert.False(t, strings.Contains(otelConfig, "kubeletstats"),
242244
"cluster-scraper otelConfig should NOT contain kubeletstats receiver (node-level)")
243245
}

0 commit comments

Comments
 (0)