@@ -3,6 +3,18 @@ extensions:
33 sigv4auth/cw_k8s_ci_v0_metrics_dest:
44 region: {{ .Values.region }}
55 service: monitoring
6+ {{- if .Values.otelContainerInsights.logs }}
7+ sigv4auth/cw_k8s_ci_v0_logs_dest:
8+ region: {{ .Values.region }}
9+ service: logs
10+ cwlogsprovision/cw_k8s_ci_v0_logs:
11+ region: {{ .Values.region }}
12+ log_groups:
13+ - name: " /aws/otel/containerinsights/{{ .Values.clusterName }}/application"
14+ streams: [" ${env:K8S_NODE_NAME}-application" ]
15+ - name: " /aws/otel/containerinsights/{{ .Values.clusterName }}/host"
16+ streams: [" ${env:K8S_NODE_NAME}-host" ]
17+ {{- end }}
618
719receivers:
820{{- if .Values.nodeExporter.enabled }}
@@ -126,6 +138,52 @@ receivers:
126138 k8s.node.uptime :
127139 enabled: true
128140
141+ {{- if .Values.otelContainerInsights.logs }}
142+ # ── CI Logs receivers ──
143+ filelog/cw_k8s_ci_v0_app:
144+ include:
145+ - /var/log/containers/* .log
146+ exclude:
147+ - /var/log/containers/cloudwatch-agent*
148+ - /var/log/containers/fluent-bit*
149+ # aws-node and kube-proxy are dataplane components — excluded to match
150+ # FluentBit's existing Exclude_Path behavior.
151+ - /var/log/containers/aws-node*
152+ - /var/log/containers/kube-proxy*
153+ start_at: end
154+ include_file_path: true
155+ include_file_name: false
156+ max_concurrent_files: 100
157+ operators:
158+ - type: regex_parser
159+ id: extract_metadata_from_filepath
160+ regex: '^.*\/(?P<pod>[^_]+)_(?P<namespace>[^_]+)_(?P<container>.+)-[a-f0-9]{64}\.log$'
161+ parse_from: attributes["log.file.path"]
162+ parse_to: resource
163+ - type: move
164+ from: resource.pod
165+ to: resource["k8s.pod.name"]
166+ - type: move
167+ from: resource.namespace
168+ to: resource["k8s.namespace.name"]
169+ - type: move
170+ from: resource.container
171+ to: resource["k8s.container.name"]
172+ - id: parse_container_log
173+ type: container_log_parser
174+
175+ filelog/cw_k8s_ci_v0_node:
176+ include:
177+ - /var/log/messages
178+ - /var/log/dmesg
179+ - /var/log/secure
180+ start_at: end
181+ include_file_path: true
182+ include_file_name: false
183+ max_concurrent_files: 100
184+
185+ {{- end }}
186+
129187processors:
130188 filter/cw_k8s_ci_v0_scrape_metadata:
131189 error_mode: ignore
@@ -537,6 +595,103 @@ processors:
537595 - delete_key(attributes, "instance_id") where attributes["instance_id"] != nil
538596 - delete_key(attributes, "volume_id") where attributes["volume_id"] != nil
539597
598+ {{- if .Values.otelContainerInsights.logs }}
599+ # ── CI Logs processors ──
600+ transform/cw_k8s_ci_v0_logs_set_workload:
601+ error_mode: ignore
602+ log_statements:
603+ - context: resource
604+ statements:
605+ # Derive k8s.workload.name and k8s.workload.type — matches metrics'
606+ # transform/cw_k8s_ci_v0_set_workload exactly so metrics and logs use
607+ # the same workload identity for the same pod.
608+ - set(attributes["k8s.workload.name"], attributes["k8s.deployment.name"]) where attributes["k8s.deployment.name"] != nil
609+ - set(attributes["k8s.workload.type"], "Deployment") where attributes["k8s.deployment.name"] != nil
610+ - set(attributes["k8s.workload.name"], attributes["k8s.statefulset.name"]) where attributes["k8s.workload.name"] == nil and attributes["k8s.statefulset.name"] != nil
611+ - set(attributes["k8s.workload.type"], "StatefulSet") where attributes["k8s.statefulset.name"] != nil and attributes["k8s.workload.type"] == nil
612+ - set(attributes["k8s.workload.name"], attributes["k8s.daemonset.name"]) where attributes["k8s.workload.name"] == nil and attributes["k8s.daemonset.name"] != nil
613+ - set(attributes["k8s.workload.type"], "DaemonSet") where attributes["k8s.daemonset.name"] != nil and attributes["k8s.workload.type"] == nil
614+ - set(attributes["k8s.workload.name"], attributes["k8s.job.name"]) where attributes["k8s.workload.name"] == nil and attributes["k8s.job.name"] != nil
615+ - set(attributes["k8s.workload.type"], "Job") where attributes["k8s.job.name"] != nil and attributes["k8s.workload.type"] == nil
616+ - set(attributes["k8s.workload.name"], attributes["k8s.cronjob.name"]) where attributes["k8s.workload.name"] == nil and attributes["k8s.cronjob.name"] != nil
617+ - set(attributes["k8s.workload.type"], "CronJob") where attributes["k8s.cronjob.name"] != nil and attributes["k8s.workload.type"] == nil
618+ - set(attributes["k8s.workload.name"], attributes["k8s.replicaset.name"]) where attributes["k8s.workload.name"] == nil and attributes["k8s.replicaset.name"] != nil
619+ - set(attributes["k8s.workload.type"], "ReplicaSet") where attributes["k8s.replicaset.name"] != nil and attributes["k8s.workload.type"] == nil
620+ # Derive service.name from k8s.workload.name (OTEL logs semconv).
621+ # Logs need service.name; metrics use k8s.workload.name directly.
622+ - set(attributes["service.name"], attributes["k8s.workload.name"]) where attributes["service.name"] == nil and attributes["k8s.workload.name"] != nil
623+
624+ transform/cw_k8s_ci_v0_logs_set_cluster_name:
625+ error_mode: ignore
626+ log_statements:
627+ - context: resource
628+ statements:
629+ - set(attributes["k8s.cluster.name"], "{{ .Values.clusterName }}")
630+ - set(attributes["k8s.node.name"], "${env:K8S_NODE_NAME}")
631+
632+ resourcedetection/cw_k8s_ci_v0_logs:
633+ detectors: [eks, ec2]
634+ ec2:
635+ resource_attributes:
636+ host.id: { enabled: true }
637+ host.type: { enabled: true }
638+ host.name: { enabled: true }
639+ host.image.id: { enabled: true }
640+ cloud.provider: { enabled: true }
641+ cloud.platform: { enabled: true }
642+ cloud.region: { enabled: true }
643+ cloud.availability_zone: { enabled: true }
644+ cloud.account.id: { enabled: true }
645+
646+ transform/cw_k8s_ci_v0_logs_set_cloud_resource_id:
647+ error_mode: ignore
648+ log_statements:
649+ - context: resource
650+ statements:
651+ - set(attributes["cloud.resource_id"], Concat(["arn:aws:eks:", attributes["cloud.region"], ":", attributes["cloud.account.id"], ":cluster/", attributes["k8s.cluster.name"]], ""))
652+ where attributes["cloud.region"] != nil and attributes["cloud.account.id"] != nil and attributes["k8s.cluster.name"] != nil
653+
654+ transform/cw_k8s_ci_v0_logs_clear_schema_url:
655+ error_mode: ignore
656+ log_statements:
657+ - context: resource
658+ statements:
659+ - set(resource.schema_url, "")
660+
661+ # Scope transforms — tag each logs pipeline with cloudwatch.source/solution/pipeline
662+ # for backend attribution. Matches the metrics pipeline's transform/set_scope_*
663+ # processors so logs are attributed identically to metrics.
664+ # scope.name is intentionally not set — the metrics pipeline sets it only for
665+ # pipelines with a well-known source library (e.g., github.com/google/cadvisor).
666+ # filelog receivers don't have an equivalent upstream library, so scope.name is
667+ # omitted (matches metrics' set_scope_efa / set_scope_ebs_csi /
668+ # set_scope_kubeletstats which also omit scope.name).
669+ transform/cw_k8s_ci_v0_logs_set_scope_app:
670+ error_mode: ignore
671+ log_statements:
672+ - context: scope
673+ statements:
674+ - set(scope.schema_url, "")
675+ - set(attributes["cloudwatch.source"], "cloudwatch-agent")
676+ - set(attributes["cloudwatch.solution"], "k8s-otel-container-insights")
677+ - set(attributes["cloudwatch.pipeline"], "application-logs")
678+
679+ transform/cw_k8s_ci_v0_logs_set_scope_host:
680+ error_mode: ignore
681+ log_statements:
682+ - context: scope
683+ statements:
684+ - set(scope.schema_url, "")
685+ - set(attributes["cloudwatch.source"], "cloudwatch-agent")
686+ - set(attributes["cloudwatch.solution"], "k8s-otel-container-insights")
687+ - set(attributes["cloudwatch.pipeline"], "host-logs")
688+
689+ batch/cw_k8s_ci_v0_logs_dest:
690+ send_batch_size: 500
691+ send_batch_max_size: 500
692+ timeout: 5s
693+ {{- end }}
694+
540695exporters:
541696 otlphttp/cw_k8s_ci_v0_metrics_dest:
542697 endpoint: {{ if .Values.otelContainerInsights.cloudwatchMetricsEndpoint }}{{ .Values.otelContainerInsights.cloudwatchMetricsEndpoint | quote }}{{ else }}"https://monitoring.{{ .Values.region }}.amazonaws.com:443"{{ end }}
@@ -545,9 +700,52 @@ exporters:
545700 auth:
546701 authenticator: sigv4auth/cw_k8s_ci_v0_metrics_dest
547702
703+ {{- if .Values.otelContainerInsights.logs }}
704+ otlphttp/cw_k8s_ci_v0_app_logs_dest:
705+ endpoint: {{ if .Values.otelContainerInsights.cloudwatchLogsEndpoint }}{{ .Values.otelContainerInsights.cloudwatchLogsEndpoint | quote }}{{ else }}"https://logs.{{ .Values.region }}.amazonaws.com:443"{{ end }}
706+ # compression: none matches FluentBit's current behavior (the aws-for-fluent-bit
707+ # cloudwatch_logs plugin does not compress by default), so customers migrating
708+ # from FluentBit see no bandwidth bill change. Enabling compression: gzip is
709+ # available as an opt-in bandwidth optimization (~5–10× reduction for typical
710+ # container logs) at the cost of +25% agent CPU — customers who raise the CPU
711+ # limit accordingly can improve on FluentBit's bandwidth cost. See OTELify CI
712+ # Logs Pipeline Optimizations doc for details.
713+ compression: none
714+ headers:
715+ x-aws-log-group: "/aws/otel/containerinsights/{{ .Values.clusterName }}/application"
716+ x-aws-log-stream: "${env:K8S_NODE_NAME}-application"
717+ sending_queue:
718+ queue_size: 500
719+ num_consumers: 10
720+ tls:
721+ insecure: false
722+ auth:
723+ authenticator: sigv4auth/cw_k8s_ci_v0_logs_dest
724+
725+ otlphttp/cw_k8s_ci_v0_node_logs_dest:
726+ endpoint: {{ if .Values.otelContainerInsights.cloudwatchLogsEndpoint }}{{ .Values.otelContainerInsights.cloudwatchLogsEndpoint | quote }}{{ else }}"https://logs.{{ .Values.region }}.amazonaws.com:443"{{ end }}
727+ # See app_logs_dest comment for compression tradeoff rationale.
728+ compression: none
729+ headers:
730+ x-aws-log-group: "/aws/otel/containerinsights/{{ .Values.clusterName }}/host"
731+ x-aws-log-stream: "${env:K8S_NODE_NAME}-host"
732+ sending_queue:
733+ queue_size: 500
734+ num_consumers: 10
735+ tls:
736+ insecure: false
737+ auth:
738+ authenticator: sigv4auth/cw_k8s_ci_v0_logs_dest
739+
740+ {{- end }}
741+
548742service:
549743 extensions:
550744 - sigv4auth/cw_k8s_ci_v0_metrics_dest
745+ {{- if .Values.otelContainerInsights.logs }}
746+ - sigv4auth/cw_k8s_ci_v0_logs_dest
747+ - cwlogsprovision/cw_k8s_ci_v0_logs
748+ {{- end }}
551749 pipelines:
552750{{- if .Values.nodeExporter.enabled }}
553751 metrics/cw_k8s_ci_v0_node_exporter:
@@ -693,4 +891,42 @@ service:
693891 exporters:
694892 - otlphttp/cw_k8s_ci_v0_metrics_dest
695893
894+ {{- if .Values.otelContainerInsights.logs }}
895+ # ── CI Logs pipelines ──
896+ logs/cw_k8s_ci_v0_app:
897+ receivers: [filelog/cw_k8s_ci_v0_app]
898+ processors:
899+ - transform/cw_k8s_ci_v0_logs_set_cluster_name
900+ - resourcedetection/cw_k8s_ci_v0_logs
901+ - transform/cw_k8s_ci_v0_logs_set_cloud_resource_id
902+ - k8sattributes/cw_k8s_ci_v0_node
903+ - k8sattributes/cw_k8s_ci_v0_pod
904+ - transform/cw_k8s_ci_v0_logs_set_scope_app
905+ - transform/cw_k8s_ci_v0_logs_clear_schema_url
906+ - transform/cw_k8s_ci_v0_logs_set_workload
907+ - batch/cw_k8s_ci_v0_logs_dest
908+ exporters:
909+ - otlphttp/cw_k8s_ci_v0_app_logs_dest
910+
911+ # ── CI Logs: Host pipeline ──
912+ # Intentionally omits k8sattributes/pod and set_workload — host logs
913+ # (/var/log/messages, /var/log/dmesg, /var/log/secure) come from the node OS
914+ # and have no pod/workload context to enrich from. k8sattributes/node adds
915+ # node-level labels; cluster + cloud attributes apply as with other pipelines.
916+ # service.name is intentionally not set — host logs are node-level, not
917+ # service-level. Customers query host logs by k8s.node.name + log group.
918+ logs/cw_k8s_ci_v0_node:
919+ receivers: [filelog/cw_k8s_ci_v0_node]
920+ processors:
921+ - transform/cw_k8s_ci_v0_logs_set_cluster_name
922+ - resourcedetection/cw_k8s_ci_v0_logs
923+ - transform/cw_k8s_ci_v0_logs_set_cloud_resource_id
924+ - k8sattributes/cw_k8s_ci_v0_node
925+ - transform/cw_k8s_ci_v0_logs_set_scope_host
926+ - transform/cw_k8s_ci_v0_logs_clear_schema_url
927+ - batch/cw_k8s_ci_v0_logs_dest
928+ exporters:
929+ - otlphttp/cw_k8s_ci_v0_node_logs_dest
930+ {{- end }}
931+
696932{{- end -}}
0 commit comments