|
| 1 | +# Alerting (enabled when alert_email is set): SNS email + CloudWatch alarms |
| 2 | +# on external API health (Route 53 health check), EC2 status, CPU, and RDS |
| 3 | +# free storage. The email subscription must be confirmed once via the link |
| 4 | +# SNS sends. |
| 5 | + |
| 6 | +locals { |
| 7 | + alarms_enabled = var.alert_email != "" |
| 8 | +} |
| 9 | + |
| 10 | +resource "aws_sns_topic" "alerts" { |
| 11 | + count = local.alarms_enabled ? 1 : 0 |
| 12 | + name = "${var.name_prefix}-alerts" |
| 13 | + tags = local.common_tags |
| 14 | +} |
| 15 | + |
| 16 | +resource "aws_sns_topic_subscription" "email" { |
| 17 | + count = local.alarms_enabled ? 1 : 0 |
| 18 | + topic_arn = aws_sns_topic.alerts[0].arn |
| 19 | + protocol = "email" |
| 20 | + endpoint = var.alert_email |
| 21 | +} |
| 22 | + |
| 23 | +# External HTTPS /health probe — catches everything the EC2-level checks |
| 24 | +# can't (container down, caddy broken, cert expired, DNS wrong). |
| 25 | +resource "aws_route53_health_check" "api" { |
| 26 | + count = local.alarms_enabled && var.domain_name != "" ? 1 : 0 |
| 27 | + fqdn = var.domain_name |
| 28 | + port = 443 |
| 29 | + type = "HTTPS" |
| 30 | + resource_path = "/health" |
| 31 | + request_interval = 30 |
| 32 | + failure_threshold = 3 |
| 33 | + tags = merge(local.common_tags, { Name = "${var.name_prefix}-api" }) |
| 34 | +} |
| 35 | + |
| 36 | +# Route 53 health check metrics are only published in us-east-1; this module |
| 37 | +# already runs there by default. |
| 38 | +resource "aws_cloudwatch_metric_alarm" "api_health" { |
| 39 | + count = local.alarms_enabled && var.domain_name != "" ? 1 : 0 |
| 40 | + alarm_name = "${var.name_prefix}-api-health" |
| 41 | + alarm_description = "https://${var.domain_name}/health is failing" |
| 42 | + namespace = "AWS/Route53" |
| 43 | + metric_name = "HealthCheckStatus" |
| 44 | + dimensions = { HealthCheckId = aws_route53_health_check.api[0].id } |
| 45 | + statistic = "Minimum" |
| 46 | + period = 60 |
| 47 | + evaluation_periods = 2 |
| 48 | + threshold = 1 |
| 49 | + comparison_operator = "LessThanThreshold" |
| 50 | + treat_missing_data = "breaching" |
| 51 | + alarm_actions = [aws_sns_topic.alerts[0].arn] |
| 52 | + ok_actions = [aws_sns_topic.alerts[0].arn] |
| 53 | + tags = local.common_tags |
| 54 | +} |
| 55 | + |
| 56 | +resource "aws_cloudwatch_metric_alarm" "status_check" { |
| 57 | + count = local.alarms_enabled ? 1 : 0 |
| 58 | + alarm_name = "${var.name_prefix}-status-check" |
| 59 | + alarm_description = "EC2 status checks failing on the node instance" |
| 60 | + namespace = "AWS/EC2" |
| 61 | + metric_name = "StatusCheckFailed" |
| 62 | + dimensions = { InstanceId = aws_instance.node.id } |
| 63 | + statistic = "Maximum" |
| 64 | + period = 60 |
| 65 | + evaluation_periods = 2 |
| 66 | + threshold = 1 |
| 67 | + comparison_operator = "GreaterThanOrEqualToThreshold" |
| 68 | + alarm_actions = [aws_sns_topic.alerts[0].arn] |
| 69 | + ok_actions = [aws_sns_topic.alerts[0].arn] |
| 70 | + tags = local.common_tags |
| 71 | +} |
| 72 | + |
| 73 | +resource "aws_cloudwatch_metric_alarm" "cpu_high" { |
| 74 | + count = local.alarms_enabled ? 1 : 0 |
| 75 | + alarm_name = "${var.name_prefix}-cpu-high" |
| 76 | + alarm_description = "Node instance CPU above 80% for 15 minutes" |
| 77 | + namespace = "AWS/EC2" |
| 78 | + metric_name = "CPUUtilization" |
| 79 | + dimensions = { InstanceId = aws_instance.node.id } |
| 80 | + statistic = "Average" |
| 81 | + period = 300 |
| 82 | + evaluation_periods = 3 |
| 83 | + threshold = 80 |
| 84 | + comparison_operator = "GreaterThanThreshold" |
| 85 | + alarm_actions = [aws_sns_topic.alerts[0].arn] |
| 86 | + ok_actions = [aws_sns_topic.alerts[0].arn] |
| 87 | + tags = local.common_tags |
| 88 | +} |
| 89 | + |
| 90 | +resource "aws_cloudwatch_metric_alarm" "rds_storage_low" { |
| 91 | + count = local.alarms_enabled && var.use_rds ? 1 : 0 |
| 92 | + alarm_name = "${var.name_prefix}-rds-storage-low" |
| 93 | + alarm_description = "RDS free storage below 2 GB" |
| 94 | + namespace = "AWS/RDS" |
| 95 | + metric_name = "FreeStorageSpace" |
| 96 | + dimensions = { DBInstanceIdentifier = aws_db_instance.node[0].identifier } |
| 97 | + statistic = "Minimum" |
| 98 | + period = 300 |
| 99 | + evaluation_periods = 2 |
| 100 | + threshold = 2147483648 |
| 101 | + comparison_operator = "LessThanThreshold" |
| 102 | + alarm_actions = [aws_sns_topic.alerts[0].arn] |
| 103 | + ok_actions = [aws_sns_topic.alerts[0].arn] |
| 104 | + tags = local.common_tags |
| 105 | +} |
0 commit comments