feat(phase-2): workstream 8 — Multi-Region Terraform Deployment
AWS environment: - VPC (3-AZ, public + private subnets, NAT gateways, VPC endpoints for ECR/SM/CW) - ECS Fargate service (sentryagent/agentidp) — secrets from Secrets Manager - RDS PostgreSQL 14 (Multi-AZ, encrypted, VPC-internal, storage autoscaling) - ElastiCache Redis 7 (primary + replica, at-rest + in-transit encryption) - ALB with HTTPS/443, HTTP→HTTPS redirect, ACM certificate - Route 53 alias record GCP environment: - VPC + private services access + Serverless VPC connector - Cloud Run service — secrets from Secret Manager - Cloud SQL PostgreSQL 14 (private IP, no public endpoint) - Cloud Memorystore Redis 7 (VPC-internal, AUTH enabled) Shared: - 4 reusable modules: agentidp (dual AWS/GCP), rds, redis, lb - No hardcoded secrets; all sensitive vars marked sensitive=true - terraform.tfvars.example for both environments - docs/devops/deployment.md — AWS + GCP step-by-step walkthrough, rollback procedures Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
176
terraform/modules/redis/main.tf
Normal file
176
terraform/modules/redis/main.tf
Normal file
@@ -0,0 +1,176 @@
|
||||
################################################################################
|
||||
# Module: redis
|
||||
# Main — AWS ElastiCache Redis 7
|
||||
#
|
||||
# - Single shard (cluster mode disabled): one primary + one replica
|
||||
# - Encryption at rest and in transit (TLS)
|
||||
# - AUTH token required when transit encryption is enabled
|
||||
# - VPC-internal only — no public access
|
||||
# - Access restricted to explicitly allowed security groups (app only)
|
||||
# - Slow log + engine log delivery to CloudWatch
|
||||
################################################################################
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.6.0"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.40.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
identifier = "${var.project}-${var.environment}"
|
||||
|
||||
common_tags = {
|
||||
environment = var.environment
|
||||
project = var.project
|
||||
managed_by = "terraform"
|
||||
}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# CloudWatch Log Group for Redis logs
|
||||
################################################################################
|
||||
|
||||
resource "aws_cloudwatch_log_group" "redis" {
|
||||
count = var.log_delivery_enabled ? 1 : 0
|
||||
|
||||
name = var.log_group_name
|
||||
retention_in_days = 30
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Security Group — only the app SGs may connect on 6379
|
||||
################################################################################
|
||||
|
||||
resource "aws_security_group" "redis" {
|
||||
name = "${local.identifier}-redis-sg"
|
||||
description = "Controls inbound access to ElastiCache Redis — allow only app SG on 6379"
|
||||
vpc_id = var.vpc_id
|
||||
|
||||
egress {
|
||||
description = "All outbound"
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
protocol = "-1"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Name = "${local.identifier}-redis-sg"
|
||||
})
|
||||
}
|
||||
|
||||
resource "aws_security_group_rule" "redis_ingress_from_app" {
|
||||
for_each = toset(var.allowed_security_group_ids)
|
||||
|
||||
type = "ingress"
|
||||
description = "Redis from app security group"
|
||||
from_port = 6379
|
||||
to_port = 6379
|
||||
protocol = "tcp"
|
||||
source_security_group_id = each.value
|
||||
security_group_id = aws_security_group.redis.id
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# ElastiCache Subnet Group
|
||||
################################################################################
|
||||
|
||||
resource "aws_elasticache_subnet_group" "main" {
|
||||
name = "${local.identifier}-redis-subnet-group"
|
||||
description = "Private subnets for AgentIdP ElastiCache Redis"
|
||||
subnet_ids = var.subnet_ids
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# ElastiCache Parameter Group — Redis 7.x defaults are fine; custom group
|
||||
# allows future tuning without recreating the replication group.
|
||||
################################################################################
|
||||
|
||||
resource "aws_elasticache_parameter_group" "main" {
|
||||
name = "${local.identifier}-redis7-params"
|
||||
family = "redis7"
|
||||
description = "AgentIdP Redis 7 parameter group"
|
||||
|
||||
# Disable dangerous commands that could truncate data in production
|
||||
parameter {
|
||||
name = "lazyfree-lazy-eviction"
|
||||
value = "yes"
|
||||
}
|
||||
|
||||
parameter {
|
||||
name = "lazyfree-lazy-expire"
|
||||
value = "yes"
|
||||
}
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# ElastiCache Replication Group (cluster mode disabled)
|
||||
#
|
||||
# cluster_mode = 0 (disabled) gives a single-shard setup:
|
||||
# - 1 primary node
|
||||
# - num_cache_clusters - 1 replica nodes
|
||||
# This matches the application usage: token revocation (SET/GET/DEL),
|
||||
# rate limiting (INCR/EXPIRE), and monthly counters (INCR) — no sharding needed.
|
||||
################################################################################
|
||||
|
||||
resource "aws_elasticache_replication_group" "main" {
|
||||
replication_group_id = local.identifier
|
||||
description = "AgentIdP Redis 7 — token revocation, rate limiting, counters"
|
||||
|
||||
# Engine
|
||||
engine = "redis"
|
||||
engine_version = var.engine_version
|
||||
node_type = var.node_type
|
||||
parameter_group_name = aws_elasticache_parameter_group.main.name
|
||||
port = 6379
|
||||
|
||||
# Topology — single shard, primary + replica
|
||||
num_cache_clusters = var.num_cache_clusters
|
||||
automatic_failover_enabled = var.automatic_failover_enabled
|
||||
multi_az_enabled = var.multi_az_enabled
|
||||
|
||||
# Network — VPC-internal, no public endpoints
|
||||
subnet_group_name = aws_elasticache_subnet_group.main.name
|
||||
security_group_ids = [aws_security_group.redis.id]
|
||||
|
||||
# Security
|
||||
at_rest_encryption_enabled = var.at_rest_encryption_enabled
|
||||
transit_encryption_enabled = var.transit_encryption_enabled
|
||||
auth_token = var.transit_encryption_enabled && var.auth_token != "" ? var.auth_token : null
|
||||
|
||||
# Maintenance and snapshots
|
||||
maintenance_window = var.maintenance_window
|
||||
snapshot_retention_limit = var.snapshot_retention_limit
|
||||
snapshot_window = var.snapshot_window
|
||||
apply_immediately = var.apply_immediately
|
||||
|
||||
# Log delivery to CloudWatch
|
||||
dynamic "log_delivery_configuration" {
|
||||
for_each = var.log_delivery_enabled ? [
|
||||
{ log_type = "slow-log", log_format = "json" },
|
||||
{ log_type = "engine-log", log_format = "json" }
|
||||
] : []
|
||||
|
||||
content {
|
||||
destination = var.log_delivery_enabled ? aws_cloudwatch_log_group.redis[0].name : ""
|
||||
destination_type = "cloudwatch-logs"
|
||||
log_format = log_delivery_configuration.value.log_format
|
||||
log_type = log_delivery_configuration.value.log_type
|
||||
}
|
||||
}
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Name = local.identifier
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user