Skip to content

Commit 8b1aa7d

Browse files
João Taveira Araújobendrucker
andcommitted
feat: support pulling from cloudwatch metrics
This commit introduces a new submodule, `cloudwatch_metrics`. Similar to our API snapshot functionality, we set up a eventbridge rule that periodically triggers the lambda with a custom payload. This payload tells the lambda what metrics to scrape for. Callers of this module can configure metric collection through the `period`, `interval`, `delay` and `filters` variables. Users must provide a list of filters. Each filter must have a namespace, and an optional set of metric names and dimensions. Co-authored-by: Ben Drucker <bvdrucker@gmail.com>
1 parent 6defef0 commit 8b1aa7d

File tree

5 files changed

+243
-0
lines changed

5 files changed

+243
-0
lines changed

modules/cloudwatch_metrics/README.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Observe Lambda CloudWatch Metrics Configuration
2+
3+
This module configures the Observe Lambda to periodically pull metrics from AWS
4+
CloudWatch. While configured in a similar manner to the "snapshot" module, the
5+
CloudWatch Metrics functionality is distinct in that it does not simply log
6+
responses to raw API calls. The Lambda takes a simplified configuration for
7+
querying sets of metrics, and produces a list of metrics more readily consumed
8+
than the raw API response from AWS.
9+
10+
This module sets up an event rule in EventBridge which triggers the Observe
11+
Lambda periodically. Additionally, the module will add a policy to the existing
12+
Lambda to ensure that all requested endpoints are accessible.
13+
14+
## Usage
15+
16+
```hcl
17+
module "observe_lambda" {
18+
source = "observeinc/lambda/aws"
19+
observe_customer = var.observe_customer
20+
observe_token = var.observe_token
21+
observe_domain = var.observe_domain
22+
name = random_pet.run.id
23+
}
24+
25+
module "cloudwatch_metrics" {
26+
source = "observeinc/lambda/aws//modules/cloudwatch_metrics"
27+
lambda = module.observe_lambda
28+
29+
filters = [
30+
{
31+
namespace = "AWS/RDS"
32+
}
33+
]
34+
}
35+
```
36+
37+
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
38+
## Requirements
39+
40+
| Name | Version |
41+
|------|---------|
42+
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.3 |
43+
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 2.68 |
44+
45+
## Providers
46+
47+
| Name | Version |
48+
|------|---------|
49+
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 2.68 |
50+
51+
## Modules
52+
53+
No modules.
54+
55+
## Resources
56+
57+
| Name | Type |
58+
|------|------|
59+
| [aws_cloudwatch_event_rule.trigger](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_rule) | resource |
60+
| [aws_cloudwatch_event_target.target](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_target) | resource |
61+
| [aws_iam_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
62+
| [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
63+
| [aws_lambda_permission.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource |
64+
| [aws_arn.function](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/arn) | data source |
65+
| [aws_arn.role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/arn) | data source |
66+
| [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
67+
68+
## Inputs
69+
70+
| Name | Description | Type | Default | Required |
71+
|------|-------------|------|---------|:--------:|
72+
| <a name="input_delay"></a> [delay](#input\_delay) | Collection delay in seconds. This delay accounts for the lag in metrics availability via Cloudwatch API. | `number` | `300` | no |
73+
| <a name="input_eventbridge_name_prefix"></a> [eventbridge\_name\_prefix](#input\_eventbridge\_name\_prefix) | Prefix used for eventbridge rule | `string` | `"observe-lambda-metrics-"` | no |
74+
| <a name="input_eventbridge_schedule_event_bus_name"></a> [eventbridge\_schedule\_event\_bus\_name](#input\_eventbridge\_schedule\_event\_bus\_name) | Event Bus for EventBridge scheduled events | `string` | `"default"` | no |
75+
| <a name="input_filters"></a> [filters](#input\_filters) | List of filters. | <pre>list(object({<br> namespace = string<br> list_mode = optional(string)<br> metric_names = optional(list(string))<br> dimensions = optional(list(object({<br> name = string<br> value = optional(string)<br> })))<br> }))</pre> | n/a | yes |
76+
| <a name="input_iam_name_prefix"></a> [iam\_name\_prefix](#input\_iam\_name\_prefix) | Prefix used for all created IAM roles and policies | `string` | `""` | no |
77+
| <a name="input_interval"></a> [interval](#input\_interval) | Interval in seconds between collection runs. Use a multiple of period to avoid gaps. | `number` | `300` | no |
78+
| <a name="input_lambda"></a> [lambda](#input\_lambda) | Observe Lambda module | <pre>object({<br> lambda_function = object({<br> arn = string<br> role = string<br> })<br> })</pre> | n/a | yes |
79+
| <a name="input_period"></a> [period](#input\_period) | Period in seconds between metric data points. Must be a multiple of 60. | `number` | `60` | no |
80+
| <a name="input_statement_id_prefix"></a> [statement\_id\_prefix](#input\_statement\_id\_prefix) | Prefix used for Lambda permission statement ID | `string` | `""` | no |
81+
82+
## Outputs
83+
84+
No outputs.
85+
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
86+
87+
## License
88+
89+
Apache 2 Licensed. See LICENSE for full details.

modules/cloudwatch_metrics/main.tf

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
locals {
2+
iam_name_prefix = var.iam_name_prefix != "" ? var.iam_name_prefix : var.eventbridge_name_prefix
3+
statement_id_prefix = var.statement_id_prefix != "" ? var.statement_id_prefix : local.iam_name_prefix
4+
role_resource = split("/", data.aws_arn.role.resource)
5+
role_name = local.role_resource[length(local.role_resource) - 1]
6+
}
7+
8+
data "aws_arn" "role" {
9+
arn = var.lambda.lambda_function.role
10+
}
11+
12+
data "aws_arn" "function" {
13+
arn = var.lambda.lambda_function.arn
14+
}
15+
16+
data "aws_iam_policy_document" "this" {
17+
statement {
18+
actions = [
19+
"cloudwatch:ListMetrics",
20+
"cloudwatch:GetMetricData",
21+
]
22+
23+
resources = [
24+
"*",
25+
]
26+
}
27+
}
28+
29+
resource "aws_iam_policy" "this" {
30+
name_prefix = local.iam_name_prefix
31+
policy = data.aws_iam_policy_document.this.json
32+
}
33+
34+
resource "aws_iam_role_policy_attachment" "this" {
35+
role = local.role_name
36+
policy_arn = aws_iam_policy.this.arn
37+
}
38+
39+
resource "aws_cloudwatch_event_rule" "trigger" {
40+
name_prefix = var.eventbridge_name_prefix
41+
description = "Periodically trigger Observe Lambda to collect CloudWatch metrics"
42+
schedule_expression = "cron(${var.interval / 60} * * * ? *)"
43+
event_bus_name = var.eventbridge_schedule_event_bus_name
44+
}
45+
46+
resource "aws_cloudwatch_event_target" "target" {
47+
arn = var.lambda.lambda_function.arn
48+
rule = aws_cloudwatch_event_rule.trigger.name
49+
input = jsonencode({
50+
metrics = {
51+
period = var.period
52+
interval = var.interval
53+
delay = var.delay
54+
filters = var.filters
55+
}
56+
})
57+
}
58+
59+
resource "aws_lambda_permission" "this" {
60+
statement_id_prefix = local.statement_id_prefix
61+
action = "lambda:InvokeFunction"
62+
principal = "events.amazonaws.com"
63+
function_name = trimprefix(data.aws_arn.function.resource, "function:")
64+
source_arn = aws_cloudwatch_event_rule.trigger.arn
65+
}

modules/cloudwatch_metrics/outputs.tf

Whitespace-only changes.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
variable "lambda" {
2+
description = "Observe Lambda module"
3+
type = object({
4+
lambda_function = object({
5+
arn = string
6+
role = string
7+
})
8+
})
9+
}
10+
11+
variable "iam_name_prefix" {
12+
description = "Prefix used for all created IAM roles and policies"
13+
type = string
14+
nullable = false
15+
default = ""
16+
}
17+
18+
variable "statement_id_prefix" {
19+
description = "Prefix used for Lambda permission statement ID"
20+
type = string
21+
nullable = false
22+
default = ""
23+
}
24+
25+
variable "eventbridge_name_prefix" {
26+
description = "Prefix used for eventbridge rule"
27+
type = string
28+
nullable = false
29+
default = "observe-lambda-metrics-"
30+
}
31+
32+
variable "interval" {
33+
description = "Interval in seconds between collection runs. Use a multiple of period to avoid gaps."
34+
type = number
35+
nullable = false
36+
default = 300
37+
validation {
38+
condition = var.interval >= 60 && var.interval < 3600
39+
error_message = "interval must be greater than or equal to 60 and less than 3600."
40+
}
41+
}
42+
43+
variable "period" {
44+
description = "Period in seconds between metric data points. Must be a multiple of 60."
45+
type = number
46+
nullable = false
47+
default = 60
48+
validation {
49+
condition = var.period >= 60 && var.period % 60 == 0
50+
error_message = "period must be at least 60 seconds, and a multiple of 60."
51+
}
52+
}
53+
54+
variable "delay" {
55+
description = "Collection delay in seconds. This delay accounts for the lag in metrics availability via Cloudwatch API."
56+
type = number
57+
nullable = false
58+
default = 300
59+
}
60+
61+
variable "filters" {
62+
description = "List of filters."
63+
type = list(object({
64+
namespace = string
65+
list_mode = optional(string)
66+
metric_names = optional(list(string))
67+
dimensions = optional(list(object({
68+
name = string
69+
value = optional(string)
70+
})))
71+
}))
72+
}
73+
74+
variable "eventbridge_schedule_event_bus_name" {
75+
description = "Event Bus for EventBridge scheduled events"
76+
type = string
77+
nullable = false
78+
default = "default"
79+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
terraform {
2+
required_version = ">= 1.3"
3+
4+
required_providers {
5+
aws = {
6+
source = "hashicorp/aws"
7+
version = ">= 2.68"
8+
}
9+
}
10+
}

0 commit comments

Comments
 (0)