diff --git a/README.md b/README.md index fd2ddb5..49b7913 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,17 @@ | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | + | [aws](#requirement\_aws) | >= 5.73.0 | +| [awscc](#requirement\_awscc) | >= 0.67.0 | + ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | 5.82.2 | +| [aws](#provider\_aws) | 5.82.0 | +| [awscc](#provider\_awscc) | 1.24.0 | ## Modules @@ -22,9 +26,13 @@ No modules. |------|------| | [aws_cloudwatch_metric_alarm.cache_cpu](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm) | resource | | [aws_cloudwatch_metric_alarm.cache_memory](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm) | resource | +| [aws_cloudwatch_metric_alarm.cache_serverless_data](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm) | resource | +| [aws_cloudwatch_metric_alarm.cache_serverless_ecpu](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm) | resource | +| [aws_cloudwatch_metric_alarm.cache_serverless_throttled_commands](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm) | resource | | [aws_elasticache_parameter_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_parameter_group) | resource | | [aws_elasticache_replication_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_replication_group) | resource | | [aws_elasticache_subnet_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_subnet_group) | resource | +| [awscc_elasticache_serverless_cache.this](https://registry.terraform.io/providers/hashicorp/awscc/latest/docs/resources/elasticache_serverless_cache) | resource | ## Inputs @@ -32,13 +40,17 @@ No modules. |------|-------------|------|---------|:--------:| | [alarm\_actions](#input\_alarm\_actions) | The list of actions to execute when this alarm transitions into an ALARM state from any other state. | `list(string)` | `[]` | no | | [alarm\_cpu\_threshold\_percent](#input\_alarm\_cpu\_threshold\_percent) | CPU threshold alarm level | `number` | `75` | no | +| [alarm\_data\_threshold\_percent](#input\_alarm\_data\_threshold\_percent) | Data threshold alarm level for elasticache serverless | `number` | `75` | no | +| [alarm\_ecpu\_threshold\_percent](#input\_alarm\_ecpu\_threshold\_percent) | ECPU threshold alarm level for elasticache serverless | `number` | `75` | no | | [alarm\_memory\_threshold\_bytes](#input\_alarm\_memory\_threshold\_bytes) | Alarm memory threshold bytes | `number` | `10000000` | no | | [apply\_immediately](#input\_apply\_immediately) | Specifies whether any database modifications are applied immediately, or during the next maintenance window | `bool` | `true` | no | +| [at\_rest\_encryption\_enabled](#input\_at\_rest\_encryption\_enabled) | Specifies whether the encryption at rest is enabled | `bool` | `true` | no | | [auth\_token](#input\_auth\_token) | Password used to access a password protected server. Can be specified only if `transit_encryption_enabled = true` | `string` | `null` | no | | [cluster\_id](#input\_cluster\_id) | Cluster ID | `string` | `null` | no | | [cluster\_mode\_enabled](#input\_cluster\_mode\_enabled) | Set to false to diable cluster module | `bool` | `false` | no | | [cluster\_size](#input\_cluster\_size) | Cluster size | `number` | `1` | no | | [create\_elasticache\_subnet\_group](#input\_create\_elasticache\_subnet\_group) | Create Elasticache Subnet Group | `bool` | `true` | no | +| [daily\_snapshot\_time](#input\_daily\_snapshot\_time) | The daily time range (in UTC) during which the service takes automatic snapshot of the Serverless Cache | `string` | `"18:00"` | no | | [elasticache\_parameter\_group\_family](#input\_elasticache\_parameter\_group\_family) | ElastiCache parameter group family | `string` | `"redis7"` | no | | [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `true` | no | | [engine](#input\_engine) | Engine of the elasticache (valkey or redis) | `string` | `"redis"` | no | @@ -46,22 +58,31 @@ No modules. | [instance\_type](#input\_instance\_type) | Elastic cache instance type | `string` | `"cache.t2.micro"` | no | | [kms\_key\_id](#input\_kms\_key\_id) | The ARN of the key that you wish to use if encrypting at rest. If not supplied, uses service managed encryption. Can be specified only if `at_rest_encryption_enabled = true` | `string` | `null` | no | | [maintenance\_window](#input\_maintenance\_window) | Maintenance window | `string` | `"wed:03:00-wed:04:00"` | no | +| [max\_data\_storage](#input\_max\_data\_storage) | The maximun cached data capacity of the Serverless Cache in GB | `number` | `10` | no | +| [max\_ecpu\_per\_second](#input\_max\_ecpu\_per\_second) | The maximum ECPU per second of the Serverless Cache | `number` | `1000` | no | | [name](#input\_name) | Name of the application | `string` | `"value"` | no | | [notification\_topic\_arn](#input\_notification\_topic\_arn) | ARN of an SNS topic to send ElastiCache notifications | `string` | `""` | no | | [num\_node\_groups](#input\_num\_node\_groups) | Number of node groups (shards) for this Redis replication group. Changing this number will trigger an online resizing operation before other settings modifications. Required unless `global_replication_group_id` is set | `number` | `2` | no | | [ok\_actions](#input\_ok\_actions) | The list of actions to execute when this alarm transitions into an OK state from any other state. | `list(string)` | `[]` | no | -| [parameter\_group\_name](#input\_parameter\_group\_name) | Excisting Parameter Group name | `string` | `""` | no | -| [parameters](#input\_parameters) | A list of Redis parameters to apply. Note that parameters may differ from one Redis family to another |
list(object({
name = string
value = string
}))
| `[]` | no | +| [parameter\_group\_name](#input\_parameter\_group\_name) | Existing Parameter Group name | `string` | `""` | no | +| [parameters](#input\_parameters) | A list of Redis parameters to apply. Note that parameters may differ from one Redis family to another |
list(object({
name = string
value = string
}))
| `[]` | no | | [port](#input\_port) | Redis port | `number` | `6379` | no | -| [preferred\_cache\_cluster\_azs](#input\_preferred\_cache\_cluster\_azs) | List of EC2 availability zones in which the replication group's cache clusters will be created. The order of the availability zones in the list is considered. The first item in the list will be the primary node. Ignored when updating | `list(string)` |
[
"ap-southeast-1a",
"ap-southeast-1b"
]
| no | +| [preferred\_cache\_cluster\_azs](#input\_preferred\_cache\_cluster\_azs) | List of EC2 availability zones in which the replication group's cache clusters will be created. The order of the availability zones in the list is considered. The first item in the list will be the primary node. Ignored when updating | `list(string)` |
[
"ap-southeast-1a",
"ap-southeast-1b"
]
| no | | [replicas\_per\_node\_group](#input\_replicas\_per\_node\_group) | Number of replica nodes in each node group. Valid values are 0 to 5. Changing this number will trigger an online resizing operation before other settings modifications. | `number` | `1` | no | | [replication\_enabled](#input\_replication\_enabled) | Set to false to diable replication in redis cluster | `bool` | `false` | no | | [replication\_group\_id](#input\_replication\_group\_id) | ElastiCache replication\_group\_id | `string` | `""` | no | | [security\_groups](#input\_security\_groups) | List of Security Group IDs to place the cluster into | `list(string)` | `[]` | no | +| [snapshot\_arns](#input\_snapshot\_arns) | The ARN of the snapshot from which to restore data into the new node group (shard) | `list(string)` | `[]` | no | +| [snapshot\_arns\_to\_restore](#input\_snapshot\_arns\_to\_restore) | The ARN's of snapshot to restore Serverless Cache | `list(string)` | `[]` | no | +| [snapshot\_name](#input\_snapshot\_name) | The name of the snapshot from which to restore data into the new node group (shard) | `string` | `""` | no | | [snapshot\_retention\_limit](#input\_snapshot\_retention\_limit) | Number of days for which ElastiCache will retain automatic cache cluster snapshots before deleting them. For example, if you set SnapshotRetentionLimit to 5, then a snapshot that was taken today will be retained for 5 days before being deleted. If the value of snapshot\_retention\_limit is set to zero (0), backups are turned off. Please note that setting a snapshot\_retention\_limit is not supported on cache.t1.micro cache nodes | `number` | `5` | no | +| [snapshot\_window](#input\_snapshot\_window) | The daily time range (in UTC) during which ElastiCache begins taking a daily snapshot of the node group (shard) specified by SnapshottingClusterId | `string` | `"00:00-01:00"` | no | | [subnet\_group\_name](#input\_subnet\_group\_name) | Subnet group name for the ElastiCache instance | `string` | `""` | no | | [subnets](#input\_subnets) | AWS subnet ids | `list(string)` | `[]` | no | | [tags](#input\_tags) | Additional tags (\_e.g.\_ map("BusinessUnit","ABC") | `map(string)` | `{}` | no | +| [transit\_encryption\_enabled](#input\_transit\_encryption\_enabled) | Specifies whether the encryption at transit is enabled | `bool` | `true` | no | +| [use\_serverless](#input\_use\_serverless) | Use serverless ElastiCache service | `bool` | `false` | no | +| [user\_group\_id](#input\_user\_group\_id) | The ID of the user group Elasticache | `string` | `""` | no | ## Outputs diff --git a/alarms.tf b/alarms.tf index 6181132..566ed7c 100644 --- a/alarms.tf +++ b/alarms.tf @@ -1,5 +1,5 @@ resource "aws_cloudwatch_metric_alarm" "cache_cpu" { - count = var.enabled ? local.num_nodes : 0 + count = var.enabled && !var.use_serverless ? local.num_nodes : 0 alarm_name = "${tolist(aws_elasticache_replication_group.this[0].member_clusters)[count.index]}-cpu-utilization" alarm_description = "${var.engine} cluster CPU utilization" @@ -30,7 +30,7 @@ resource "aws_cloudwatch_metric_alarm" "cache_cpu" { } resource "aws_cloudwatch_metric_alarm" "cache_memory" { - count = var.enabled ? local.num_nodes : 0 + count = var.enabled && !var.use_serverless ? local.num_nodes : 0 alarm_name = "${tolist(aws_elasticache_replication_group.this[0].member_clusters)[count.index]}-freeable-memory" alarm_description = "${var.engine} cluster freeable memory" @@ -59,3 +59,96 @@ resource "aws_cloudwatch_metric_alarm" "cache_memory" { aws_elasticache_replication_group.this ] } + +# ElastiCache Serverless +resource "aws_cloudwatch_metric_alarm" "cache_serverless_ecpu" { + count = var.enabled && var.use_serverless ? 1 : 0 + + alarm_name = "${awscc_elasticache_serverless_cache.this[0].serverless_cache_name}-ecpu-utilization" + alarm_description = "Redis serverless ECPU utilization" + + comparison_operator = "GreaterThanThreshold" + evaluation_periods = 1 + + metric_name = "ElastiCacheProcessingUnits" + namespace = "AWS/ElastiCache" + + period = 300 + statistic = "Average" + + tags = var.tags + + threshold = ceil(var.max_ecpu_per_second * var.alarm_ecpu_threshold_percent / 100) + + dimensions = { + CacheClusterId = awscc_elasticache_serverless_cache.this[0].serverless_cache_name + } + + alarm_actions = var.alarm_actions + ok_actions = var.ok_actions + + depends_on = [ + awscc_elasticache_serverless_cache.this + ] +} + +resource "aws_cloudwatch_metric_alarm" "cache_serverless_data" { + count = var.enabled && var.use_serverless ? 1 : 0 + + alarm_name = "${awscc_elasticache_serverless_cache.this[0].serverless_cache_name}-data-storage" + alarm_description = "Redis serverless data storage" + + comparison_operator = "GreaterThanThreshold" + evaluation_periods = 1 + + metric_name = "BytesUsedForCache" + namespace = "AWS/ElastiCache" + + period = 60 + statistic = "Average" + + threshold = ceil((var.max_data_storage * 1000 * 1000 * 1000) * var.alarm_data_threshold_percent / 100) + + tags = var.tags + + dimensions = { + CacheClusterId = awscc_elasticache_serverless_cache.this[0].serverless_cache_name + } + + alarm_actions = var.alarm_actions + ok_actions = var.ok_actions + + depends_on = [ + aws_elasticache_replication_group.this + ] +} + +resource "aws_cloudwatch_metric_alarm" "cache_serverless_throttled_commands" { + count = var.enabled && var.use_serverless ? 1 : 0 + + alarm_name = "${awscc_elasticache_serverless_cache.this[0].serverless_cache_name}-throttled-commands" + alarm_description = "Redis serverless throttled commands" + + comparison_operator = "GreaterThanThreshold" + evaluation_periods = 1 + + metric_name = "ThrottledCmds" + namespace = "AWS/ElastiCache" + + period = 60 + statistic = "Average" + + threshold = 0 + + tags = var.tags + dimensions = { + CacheClusterId = awscc_elasticache_serverless_cache.this[0].serverless_cache_name + } + + alarm_actions = var.alarm_actions + ok_actions = var.ok_actions + + depends_on = [ + awscc_elasticache_serverless_cache.this + ] +} diff --git a/main.tf b/main.tf index 737963a..084f77a 100644 --- a/main.tf +++ b/main.tf @@ -15,7 +15,7 @@ locals { } resource "aws_elasticache_parameter_group" "this" { - count = var.enabled && var.parameter_group_name == "" || var.parameter_group_name == null ? 1 : 0 + count = var.enabled && var.parameter_group_name == "" && !var.use_serverless || var.parameter_group_name == null ? 1 : 0 name = var.name family = var.elasticache_parameter_group_family @@ -38,7 +38,7 @@ resource "aws_elasticache_subnet_group" "this" { } resource "aws_elasticache_replication_group" "this" { - count = var.enabled ? 1 : 0 + count = var.enabled && !var.use_serverless ? 1 : 0 replication_group_id = var.replication_group_id == "" ? local.cluster_id : var.replication_group_id description = "${var.engine} Cluster Rep" @@ -56,8 +56,9 @@ resource "aws_elasticache_replication_group" "this" { subnet_group_name = try(aws_elasticache_subnet_group.this[0].name, var.subnet_group_name) security_group_ids = var.security_groups - multi_az_enabled = var.replication_enabled ? true : false - at_rest_encryption_enabled = true + multi_az_enabled = var.replication_enabled ? true : false + + at_rest_encryption_enabled = var.at_rest_encryption_enabled transit_encryption_enabled = var.transit_encryption_enabled automatic_failover_enabled = var.replication_enabled ? true : false @@ -65,11 +66,55 @@ resource "aws_elasticache_replication_group" "this" { apply_immediately = var.apply_immediately - auth_token = var.auth_token - kms_key_id = var.kms_key_id + auth_token = var.transit_encryption_enabled ? var.auth_token : null + kms_key_id = var.at_rest_encryption_enabled ? var.kms_key_id : null num_node_groups = var.cluster_mode_enabled ? var.num_node_groups : null replicas_per_node_group = var.cluster_mode_enabled ? var.replicas_per_node_group : null + user_group_ids = [var.user_group_id] + + snapshot_retention_limit = var.instance_type != "cache.t1.micro" ? var.snapshot_retention_limit : 0 + snapshot_window = var.snapshot_window + snapshot_arns = var.snapshot_arns + snapshot_name = var.snapshot_name + tags = var.tags } + +resource "awscc_elasticache_serverless_cache" "this" { + count = var.enabled && var.use_serverless ? 1 : 0 + + serverless_cache_name = var.name + description = "${var.name} ElastiCache Redis Serverless" + engine = "redis" + major_engine_version = var.engine_version + + cache_usage_limits = { + data_storage = { + maximum = var.max_data_storage + unit = "GB" + } + ecpu_per_second = { + maximum = var.max_ecpu_per_second + } + } + + user_group_id = var.user_group_id + + final_snapshot_name = "${var.name}-elasticache-serverless-final-snapshot" + kms_key_id = var.kms_key_id + security_group_ids = var.security_groups + subnet_ids = var.subnets + + daily_snapshot_time = var.daily_snapshot_time + snapshot_arns_to_restore = var.snapshot_arns_to_restore + snapshot_retention_limit = var.snapshot_retention_limit + + tags = [ + for key, value in var.tags : { + key = key + value = value + } + ] +} diff --git a/outputs.tf b/outputs.tf index 8d5de23..005c23a 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,6 +1,6 @@ output "endpoint" { - description = "primary or configuration endpoint, whichever is appropriate for the given cluster mode" - value = try(aws_elasticache_replication_group.this[0].primary_endpoint_address, null) + description = "Redis primary or configuration endpoint, whichever is appropriate for the given cluster mode" + value = var.use_serverless ? try(awscc_elasticache_serverless_cache.this[0].endpoint.address, null) : try(aws_elasticache_replication_group.this[0].primary_endpoint_address, null) } output "reader_endpoint_address" { @@ -15,7 +15,7 @@ output "member_clusters" { output "arn" { description = "Elasticache Replication Group ARN" - value = try(aws_elasticache_replication_group.this[0].arn, null) + value = var.use_serverless ? try(awscc_elasticache_serverless_cache.this[0].arn, null) : try(aws_elasticache_replication_group.this[0].arn, null) } output "cluster_enabled" { diff --git a/variables.tf b/variables.tf index 0d3ece7..790f8e2 100644 --- a/variables.tf +++ b/variables.tf @@ -71,12 +71,24 @@ variable "alarm_cpu_threshold_percent" { default = 75 } +variable "alarm_ecpu_threshold_percent" { + description = "ECPU threshold alarm level for elasticache serverless" + type = number + default = 75 +} + variable "alarm_memory_threshold_bytes" { description = "Alarm memory threshold bytes" type = number default = 10000000 # 10MB } +variable "alarm_data_threshold_percent" { + description = "Data threshold alarm level for elasticache serverless" + type = number + default = 75 +} + variable "notification_topic_arn" { description = "ARN of an SNS topic to send ElastiCache notifications" type = string @@ -162,7 +174,7 @@ variable "preferred_cache_cluster_azs" { } variable "parameter_group_name" { - description = "Excisting Parameter Group name" + description = "Existing Parameter Group name" type = string default = "" } @@ -179,6 +191,12 @@ variable "auth_token" { default = null } +variable "at_rest_encryption_enabled" { + description = "Whether to enable encryption at rest" + type = string + default = true +} + variable "kms_key_id" { description = "The ARN of the key that you wish to use if encrypting at rest. If not supplied, uses service managed encryption. Can be specified only if `at_rest_encryption_enabled = true`" type = string @@ -202,3 +220,68 @@ variable "transit_encryption_enabled" { type = bool default = true } + +# ElastiCache Serverless +variable "use_serverless" { + description = "Use serverless ElastiCache service" + type = bool + default = false +} + +variable "max_data_storage" { + type = number + description = "The maximun cached data capacity of the Serverless Cache in GB" + default = 10 + + validation { + condition = var.max_data_storage >= 1 && var.max_data_storage <= 5000 + error_message = "The max_data_storage in GB value must be between 1 and 5,000." + } +} + +variable "max_ecpu_per_second" { + type = number + description = "The maximum ECPU per second of the Serverless Cache" + default = 1000 + + validation { + condition = var.max_ecpu_per_second >= 1000 && var.max_ecpu_per_second <= 15000000 + error_message = "The max_ecpu_per_second value must be between 1,000 and 15,000,000." + } +} + +variable "daily_snapshot_time" { + type = string + description = "The daily time range (in UTC) during which the service takes automatic snapshot of the Serverless Cache" + default = "18:00" +} + +variable "snapshot_arns_to_restore" { + type = list(string) + description = "The ARN's of snapshot to restore Serverless Cache" + default = [] +} + +variable "user_group_id" { + type = string + description = "The ID of the user group Elasticache" + default = "" +} + +variable "snapshot_window" { + type = string + description = "The daily time range (in UTC) during which ElastiCache begins taking a daily snapshot of the node group (shard) specified by SnapshottingClusterId" + default = "00:00-01:00" +} + +variable "snapshot_arns" { + type = list(string) + description = "The ARN of the snapshot from which to restore data into the new node group (shard)" + default = [] +} + +variable "snapshot_name" { + type = string + description = "The name of the snapshot from which to restore data into the new node group (shard)" + default = "" +} diff --git a/versions.tf b/versions.tf index fcf71c0..23bdcc8 100644 --- a/versions.tf +++ b/versions.tf @@ -5,5 +5,9 @@ terraform { source = "hashicorp/aws" version = ">= 5.73.0" } + awscc = { + source = "hashicorp/awscc" + version = ">= 0.67.0" + } } }