Streaming CloudWatch Metrics to Cardinal Cloud
This guide streams AWS CloudWatch metrics into Cardinal Cloud (SaaS) over HTTPS. A CloudWatch Metric Stream writes to an Amazon Data Firehose stream, and Firehose POSTs each batch to Cardinal’s CloudWatch intake endpoint. Nothing runs in your account except the two AWS-managed streams: no collector, no polling agent, no per-metric GetMetricData charges.
The moving parts are the metric stream, the Firehose stream, and your organization ingest API key. Around them you provision what Firehose itself requires: an S3 bucket that receives only failed batches, two IAM roles, and a CloudWatch Logs group for delivery errors. The setup section covers both the AWS console and Terraform; pick the tab you want.
Which path is this? Cardinal can also read Metric Streams that Firehose delivers to your own S3 bucket, which is the path for self-hosted Cardinal Data Lake and customer-account deployments. That path is described in the blog post Getting CloudWatch Metrics into Cardinal Data Lake . This page covers the HTTP endpoint path: Firehose sends metrics directly to Cardinal, so there is no bucket notification, ingest queue, or prefix mapping to configure, and the only bucket on your side holds failed batches. This is the right choice for Cardinal Cloud.
Prerequisites
awsfirehose-cwmetrics.us-east-2.aws.cardinalhq.io on port 443. Firehose is AWS-managed, so no VPC or firewall change is needed.How it works
| Setting | Value |
|---|---|
| Endpoint URL | https://awsfirehose-cwmetrics.us-east-2.aws.cardinalhq.io |
| Authentication | Your organization ingest API key, sent by Firehose as the X-Amz-Firehose-Access-Key header (the Firehose Access key field) |
| Payload | CloudWatch Metric Streams JSON output format |
| Content encoding | GZIP (recommended) or none |
| Delivery | At least once. Firehose retries any non-2xx response for the configured retry duration, then writes the failed batch to your backup bucket |
Cardinal identifies your organization from the API key alone. Every metric stream in every AWS account and region that uses that key lands in the same Cardinal organization; the records themselves carry account_id and region, which become the cloud_account_id and cloud_region attributes.
The endpoint is hosted in us-east-2, but it is a plain HTTPS URL. A Firehose stream in any AWS region can deliver to it. Metric Streams are regional, so you create one metric stream and one Firehose stream per region you want to monitor.
Setup
AWS console
Firehose requires an S3 bucket for HTTP endpoint destinations, used for batches that Cardinal could not accept. Create or pick a bucket before you start; it stays empty in normal operation.
Have your organization ingest API key ready
Request the key from Cardinal support if you do not have it yet. It is the same kind of key an OpenTelemetry collector uses to send telemetry to your Cardinal organization.
Treat this key like a password. Anyone holding it can write metrics into your organization. The Firehose console stores it as a secret and never shows it again after creation.
Create the Firehose stream
Open the Amazon Data Firehose console in the region whose metrics you want, and click Create Firehose stream.
- Source:
Direct PUT. - Destination:
HTTP Endpoint. - Firehose stream name:
cardinal-cwmetrics(any name works; this guide uses it throughout). - Under Destination settings:
- HTTP endpoint name:
Cardinal - HTTP endpoint URL:
https://awsfirehose-cwmetrics.us-east-2.aws.cardinalhq.io - Access key: paste your organization ingest API key
- Content encoding:
GZIP - Retry duration:
300seconds (the default). Keep it at least 60; a short retry window sends transient failures to the backup bucket instead of retrying. - Parameters: add key
cardinalhq.formatwith valuecwmetrics. This is optional today, sincecwmetricsis the default, but it makes the stream’s purpose explicit.
- HTTP endpoint name:
- Under Buffer hints:
- Buffer size:
5MiB - Buffer interval:
60seconds Firehose flushes when either limit is reached. A 60-second interval gives metrics close to the freshness CloudWatch itself offers; a longer interval sends fewer, larger requests. Keep the buffer size at or below 48 MiB: Firehose base64-encodes records into the request body, which grows it by about a third, and Cardinal rejects bodies over 64 MiB.
- Buffer size:
- Under Backup settings:
- Source record backup in Amazon S3:
Failed data only - S3 backup bucket: your backup bucket
- S3 backup bucket error output prefix:
cardinal-cwmetrics-errors/(optional, keeps failures easy to find)
- Source record backup in Amazon S3:
- Under Advanced settings, leave Amazon CloudWatch error logging enabled. Firehose writes Cardinal’s rejection reasons there, which is where you look if nothing arrives.
- Let the console create a new IAM role for the stream, or pick an existing one that can write to the backup bucket and to CloudWatch Logs.
- Click Create Firehose stream and wait for the status to become Active.
Create the CloudWatch metric stream
Open the CloudWatch console in the same region, go to Metrics → Streams, and click Create metric stream.
- Metrics to be streamed: choose Select metrics and pick the namespaces you want, such as
AWS/EC2,AWS/RDS,AWS/ApplicationELB,AWS/Lambda,AWS/SQS, andAWS/ECS. Choosing All metrics also works; addAWS/Usageand other high-cardinality namespaces to the exclude list to control cost. - Configuration: choose Custom setup with Firehose, then Select your Firehose stream and pick
cardinal-cwmetrics. - Service role: let the console create a new role, or pick one that allows
firehose:PutRecordandfirehose:PutRecordBatchon that stream. - Change output format: choose JSON. This matters: Cardinal accepts the Metric Streams JSON format and rejects the OpenTelemetry 0.7 and 1.0 formats with a
400that names the problem. - Metric stream name:
cardinal-cwmetrics. - Click Create metric stream.
CloudWatch starts streaming within a couple of minutes.
Verify delivery
In the Firehose console, open the stream’s Monitoring tab. Within five minutes you should see:
Incoming recordsrising, which means CloudWatch is sending.Delivery to HTTP endpoint successat1, which means Cardinal is accepting every request.
In Cardinal Cloud, open Explore → Metrics and search for a metric from a namespace you streamed. Names are the CloudWatch namespace and metric name, lowercased, joined with _:
| CloudWatch | Cardinal metric |
|---|---|
AWS/EC2 / CPUUtilization | aws_ec2_cpuutilization |
AWS/RDS / DatabaseConnections | aws_rds_databaseconnections |
AWS/ApplicationELB / TargetResponseTime | aws_applicationelb_targetresponsetime |
Filter by cloud_account_id, cloud_region, aws_namespace, or any CloudWatch dimension such as instanceid.
Repeat per region and account
Metric streams are regional. For each additional region, repeat the Firehose and metric stream steps in that region with the same API key. For additional AWS accounts, do the same in each account. Everything lands in one Cardinal organization, separated by the cloud_account_id and cloud_region attributes.
What arrives in Cardinal
Each Metric Streams record is one datapoint carrying {min, max, sum, count} for a period. Cardinal stores those four statistics exactly as rollups, so counts, sums, averages, minimums, and maximums are correct and aggregate correctly across dimensions. CloudWatch does not send the individual samples, so percentiles cannot be recovered from a streamed metric.
| Record field | Cardinal |
|---|---|
namespace + metric_name | metric name, e.g. aws_elb_latency, type gauge |
each dimensions key | attribute, lowercased, e.g. loadbalancername |
account_id | cloud_account_id |
region | cloud_region |
namespace | aws_namespace |
metric_stream_name | metric_stream_name |
unit | passed through verbatim (Seconds, Count, Percent) |
Troubleshooting
Firehose reports every rejection in the stream’s CloudWatch Logs group (/aws/kinesisfirehose/<stream name> when created by the console, or the group you configured) and in the Monitoring tab. Cardinal’s response body carries an errorMessage that says why.
| Symptom | Cause | Fix |
|---|---|---|
Delivery to HTTP endpoint success is 0 and the error log shows 401 with invalid API key | The Access key is not a valid organization ingest API key, or the organization is disabled | Console path: re-enter the key under the Firehose stream’s Destination settings. Terraform path: update the Secrets Manager secret’s value; Firehose refreshes its cached secret within 10 minutes. Cardinal accepts a newly issued key within a minute |
Error log shows 400 with record is not CloudWatch Metric Streams JSON | The metric stream’s output format is OpenTelemetry 0.7 or 1.0, or the Firehose source is a CloudWatch Logs subscription rather than a metric stream | Set the metric stream’s output format to JSON |
Error log shows 400 with unsupported cardinalhq.format | The cardinalhq.format parameter is set to something other than cwmetrics | Set it to cwmetrics or remove it |
Error log shows 413 | A single request body exceeded 64 MiB. Base64 encoding makes the body about a third larger than the buffer, so a buffer size near Firehose’s 64 MiB maximum always fails. Firehose treats this as permanent and writes the batch to the backup bucket | Lower the Buffer size to 48 MiB or less |
| Objects appearing in the backup bucket | Cardinal rejected a batch or was unreachable for longer than the retry duration | Read the error log for the reason. Backup objects are not re-sent automatically |
Incoming records is 0 | The metric stream is not running, or its role cannot write to Firehose | Check the metric stream’s status in CloudWatch → Metrics → Streams, and the role’s firehose:PutRecord permission |
| Delivery succeeds but a namespace is missing in Cardinal | The namespace is not in the metric stream’s include list, or is in its exclude list | Edit the metric stream’s filters. Changes take effect within minutes |
| Metrics arrive several minutes late | CloudWatch publishes many service metrics minutes after the fact, and Firehose buffers up to the configured interval | Expected. Lowering the buffer interval below 60 s changes little, because CloudWatch’s own publication delay dominates |
Delivery is at least once. If Firehose retries a batch that Cardinal already stored, the retry is deduplicated for about an hour; a much later retry is ingested again, which can double-count that batch’s datapoints for the affected minute.
Cost notes
- CloudWatch bills per metric update sent to the stream. Include only the namespaces you query, and exclude
AWS/Usage. - Firehose bills per GiB ingested. GZIP content encoding does not change the Firehose charge (it is billed before compression) but shrinks the transfer.
- S3 charges for the backup bucket are negligible unless deliveries fail; the lifecycle rule above expires failed batches after 14 days.
- There is no
GetMetricDatapolling and no agent fleet to run.
Reach out to support@cardinalhq.io for support or to ask questions not answered in our documentation.