AWS CloudWatch
Monitor your AWS estate — EC2 instances, Application Load Balancers, RDS databases, Lambda functions, and SQS queues — through CloudWatch, with curated dashboards and alerts you import in one step.
CloudWatch is a polled API, not a scrape endpoint, so a small adapter sits in
between: YACE (Yet Another CloudWatch Exporter),
the prometheus-community CloudWatch exporter, polls GetMetricData with
read-only IAM credentials, discovers resources by tag, and exposes everything as
Prometheus metrics. An OpenTelemetry Collector scrapes YACE with the
Prometheus receiver and forwards OTLP to your LeanSignal agent gateway. The
gateway keeps full fidelity locally and forwards only the demanded subset to
your central dataplane — so polling broadly stays cheap, and importing a demand
is what decides which CloudWatch series are stored centrally.
The panels and alerts cover the five services almost every AWS account runs on — EC2, ALB, RDS, Lambda, and SQS — with the thresholds AWS operators actually page on: failed status checks, 5xx ratios, storage and memory headroom, error ratios, throttling, and queue backlog age.
Prerequisites
Section titled “Prerequisites”- A LeanSignal agent gateway deployed and connected — one per cluster or
environment. If you haven’t deployed it yet, follow
Install the agent (source:
LeanSignal/leansignal-agent). It should show as Connected under Agents in the LeanSignal app. - Network reachability to the gateway’s OTLP port —
4317(gRPC) or4318(HTTP). See Agent configuration. - An IAM identity for read-only metric access (
cloudwatch:GetMetricData,cloudwatch:ListMetrics,tag:GetResources) and a YACE exporter that polls CloudWatch and exposes the metrics for the collector to scrape — both configured in Setup. - Editor or admin role in the LeanSignal app (importing a demand creates dashboards and alert rules).
Three pieces: an IAM identity that can read metrics, a YACE instance that polls CloudWatch and exposes them, and a collector that scrapes YACE and forwards OTLP to the gateway.
1. IAM — read-only metric access. Attach this policy to the identity YACE runs as (an instance profile, IRSA service account, or access key):
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "cloudwatch:GetMetricData", "cloudwatch:ListMetrics", "tag:GetResources" ], "Resource": "*" } ]}2. YACE — poll CloudWatch, expose Prometheus metrics. One job per AWS
namespace; discovery finds resources by tag, so new instances/functions/queues
appear without config changes. Save as yace-config.yml:
apiVersion: v1alpha1sts-region: eu-central-1 # region for the STS endpointdiscovery: jobs: - type: AWS/EC2 regions: [eu-central-1] # list every region you run in period: 300 length: 300 metrics: - name: CPUUtilization statistics: [Average] - name: StatusCheckFailed statistics: [Maximum] - name: NetworkIn statistics: [Average] - name: NetworkOut statistics: [Average] - name: CPUCreditBalance statistics: [Average] - type: AWS/ApplicationELB regions: [eu-central-1] period: 300 length: 300 metrics: - name: RequestCount statistics: [Sum] - name: HTTPCode_Target_5XX_Count statistics: [Sum] - name: HTTPCode_ELB_5XX_Count statistics: [Sum] - name: TargetResponseTime statistics: [p90] - name: HealthyHostCount statistics: [Maximum] - name: UnHealthyHostCount statistics: [Maximum] - name: ActiveConnectionCount statistics: [Sum] - name: NewConnectionCount statistics: [Sum] - type: AWS/RDS regions: [eu-central-1] period: 300 length: 300 metrics: - name: CPUUtilization statistics: [Average] - name: FreeStorageSpace statistics: [Average] - name: DatabaseConnections statistics: [Average] - name: FreeableMemory statistics: [Average] - name: ReadLatency statistics: [Average] - name: WriteLatency statistics: [Average] - name: ReadIOPS statistics: [Average] - name: WriteIOPS statistics: [Average] - type: AWS/Lambda regions: [eu-central-1] period: 300 length: 300 metrics: - name: Invocations statistics: [Sum] - name: Errors statistics: [Sum] - name: Throttles statistics: [Sum] - name: Duration statistics: [Average] - name: ConcurrentExecutions statistics: [Maximum] - type: AWS/SQS regions: [eu-central-1] period: 300 length: 300 metrics: - name: ApproximateAgeOfOldestMessage statistics: [Maximum] - name: ApproximateNumberOfMessagesVisible statistics: [Average] - name: NumberOfMessagesSent statistics: [Sum] - name: NumberOfMessagesReceived statistics: [Sum]Run it (pin the current release from the releases page):
docker run -d --name yace \ -p 5000:5000 \ -v $PWD/yace-config.yml:/tmp/config.yml \ -v $HOME/.aws:/exporter/.aws:ro \ quay.io/prometheus-community/yet-another-cloudwatch-exporter:v0.62.1Drop the ~/.aws mount when credentials come from an instance profile or IRSA.
3. Collector — scrape YACE, forward OTLP to the gateway (not a SaaS endpoint):
receivers: prometheus: config: scrape_configs: - job_name: aws-cloudwatch scrape_interval: 60s # YACE serves cached values; no need to scrape faster static_configs: - targets: [yace:5000]
processors: batch: {}
exporters: otlp: endpoint: leansignal-agent:4317 # per-cluster gateway (use :4318 for HTTP) tls: insecure: true # in-cluster plaintext; provide ca_file for TLS
service: pipelines: metrics: receivers: [prometheus] processors: [batch] exporters: [otlp]To confirm YACE is polling and the names match what the dashboards query:
curl -s http://yace:5000/metrics | grep -E '^aws_(ec2_cpuutilization|rds_free_storage_space|sqs_approximate_age)'Logs and traces
Section titled “Logs and traces”CloudWatch Logs is a first-class source: the gateway bundles the
OpenTelemetry awscloudwatch receiver, which polls the log groups you name
and forwards them like any other log stream — no Lambda forwarders, no
subscription filters. Add to the collector (or the gateway’s own config):
receivers: awscloudwatch: region: eu-central-1 logs: poll_interval: 1m groups: named: /aws/lambda/my-function: {} /aws/rds/instance/my-db/error: {}The same read-only IAM identity needs logs:DescribeLogGroups and
logs:FilterLogEvents for the groups you poll. Traces are an application
concern, not a CloudWatch one — instrument the workloads running on AWS with
the OpenTelemetry SDKs (see the runtime integrations) and
point them at the gateway; X-Ray-instrumented services can export OTLP via ADOT
the same way.
Demands
Section titled “Demands”AWS CloudWatch ships as a ready-made demand in three variants. You don’t copy any JSON — in the LeanSignal app go to Demands, press the ˅ arrow on the Add Demand button (the arrow opens the import menu), choose Import from catalog…, and pick the variant. Each variant below lists the slug that identifies its published bundle. See Integrations for the full import flow.
The variants are nested — Standard is a superset of Essential, Extended a superset of Standard. Pick whichever is closest to what you need and treat it as a starting point: after import everything is a normal, editable copy, so retune thresholds, add or drop panels, and adjust queries for your environment — your edits reshape the demand automatically. Start small and re-import a larger variant later if you outgrow it.
The failure signals. 1 dashboard · 8 panels · 5 alerts — status checks, 5xx responses, storage headroom, function errors, and queue backlog at a glance.
Import this variant from the catalog. Its demand slug:
aws-cloudwatch-otel-demand-essentialDashboard — CPU utilization, Status checks failed, Requests (per 5m), 5xx responses (per 5m), CPU utilization, Free storage, Errors & throttles (per 5m), and Oldest message age.
Alerts (5)
| Alert | Severity | Fires when |
|---|---|---|
| EC2 status check failed | critical | any failed status check for 10m |
| ALB high 5xx ratio | warning | 5xx > 5% of requests for 10m |
| RDS low free storage | critical | < 5 GiB free for 15m |
| Lambda high error ratio | warning | errors > 5% of invocations for 10m |
| SQS message backlog aging | warning | oldest message > 10m old, for 10m |
Well-rounded coverage. 1 dashboard · 16 panels · 9 alerts — adds traffic, latency, target health, connections, and queue depth. The recommended default.
Import this variant from the catalog. Its demand slug:
aws-cloudwatch-otel-demand-standardDashboard — everything in Essential, plus Network in / out (bytes per 5m), Target response time (p90), Healthy / unhealthy targets, Database connections, Freeable memory, Invocations (per 5m), Duration (avg), and Messages visible.
Alerts (9)
| Alert | Severity | Fires when |
|---|---|---|
| EC2 status check failed | critical | any failed status check for 10m |
| ALB high 5xx ratio | warning | 5xx > 5% of requests for 10m |
| RDS low free storage | critical | < 5 GiB free for 15m |
| Lambda high error ratio | warning | errors > 5% of invocations for 10m |
| SQS message backlog aging | warning | oldest message > 10m old, for 10m |
| EC2 high CPU | warning | > 90% CPU for 30m |
| RDS high CPU | warning | > 90% CPU for 15m |
| ALB unhealthy targets | critical | any unhealthy target for 10m |
| Lambda throttling | warning | any throttled invocation for 10m |
Deep account visibility. 1 dashboard · 22 panels · 11 alerts — CPU credits, connection churn, RDS I/O, Lambda concurrency, and queue throughput for accounts you need to diagnose, not just watch.
Import this variant from the catalog. Its demand slug:
aws-cloudwatch-otel-demand-extendedDashboard — everything in Standard, plus CPU credit balance (T-class), Active / new connections (per 5m), Read / write latency, Read / write IOPS, Concurrent executions, and Sent / received (per 5m).
Alerts (11)
| Alert | Severity | Fires when |
|---|---|---|
| EC2 status check failed | critical | any failed status check for 10m |
| ALB high 5xx ratio | warning | 5xx > 5% of requests for 10m |
| RDS low free storage | critical | < 5 GiB free for 15m |
| Lambda high error ratio | warning | errors > 5% of invocations for 10m |
| SQS message backlog aging | warning | oldest message > 10m old, for 10m |
| EC2 high CPU | warning | > 90% CPU for 30m |
| RDS high CPU | warning | > 90% CPU for 15m |
| ALB unhealthy targets | critical | any unhealthy target for 10m |
| Lambda throttling | warning | any throttled invocation for 10m |
| EC2 CPU credits low | warning | T-class credit balance < 20 for 15m |
| RDS low freeable memory | warning | < 256 MiB freeable for 15m |
Dashboards
Section titled “Dashboards”Each variant imports one AWS CloudWatch dashboard, its panels grouped into
collapsible sections. Every dashboard carries an Instance filter (on the
region label), so one import covers the whole fleet — view every instance
at once, or focus on one. Legends are sortable tables showing last/mean/max per
series, so the outlier instance stands out. Dashboards are demand-driven:
importing one tells your agents to forward exactly the timeseries its panels
query, and nothing else. Panels are ordinary Perses panels — edit queries, add
panels, or retune them after import, and your changes reshape the demand
automatically.
Alerts
Section titled “Alerts”Thresholds are conservative starting points — tune them to your workload.
Next steps
Section titled “Next steps”- Demand-driven observability — why importing a demand also shapes what your agents collect.
- Agent configuration — the full agent config surface.
Thanks for your feedback!
Report sent — thank you!