Skip to content

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.

  • 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 port4317 (gRPC) or 4318 (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: v1alpha1
sts-region: eu-central-1 # region for the STS endpoint
discovery:
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):

Terminal window
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.1

Drop 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:

Terminal window
curl -s http://yace:5000/metrics | grep -E '^aws_(ec2_cpuutilization|rds_free_storage_space|sqs_approximate_age)'

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.

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:

Demand slug
aws-cloudwatch-otel-demand-essential

Dashboard — 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)

AlertSeverityFires when
EC2 status check failedcriticalany failed status check for 10m
ALB high 5xx ratiowarning5xx > 5% of requests for 10m
RDS low free storagecritical< 5 GiB free for 15m
Lambda high error ratiowarningerrors > 5% of invocations for 10m
SQS message backlog agingwarningoldest message > 10m old, for 10m

Review the bundle JSON →

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.

Thresholds are conservative starting points — tune them to your workload.

Was this page helpful?