Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import RDS databases from govuk-aws (integration) #1130

Merged
merged 3 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions terraform/deployments/rds/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
terraform {
cloud {
organization = "govuk"
workspaces {
tags = ["rds", "eks", "aws"]
}
}

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}

provider "random" {}

provider "aws" {
region = var.aws_region
default_tags {
tags = {
Product = "GOV.UK"
System = "EKS RDS"
Environment = var.govuk_environment
Owner = "[email protected]"
cluster = "govuk"
repository = "govuk-infrastructure"
terraform_deployment = basename(abspath(path.root))
}
}
}
24 changes: 24 additions & 0 deletions terraform/deployments/rds/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
output "rds_instance_id" {
description = "RDS instance IDs"
value = { for k, v in aws_db_instance.instance : k => v.id }
}

output "rds_resource_id" {
description = "RDS instance resource IDs"
value = { for k, v in aws_db_instance.instance : k => v.resource_id }
}

output "rds_endpoint" {
description = "RDS instance endpoints"
value = { for k, v in aws_db_instance.instance : k => v.endpoint }
}

output "rds_address" {
description = "RDS instance addresses"
value = { for k, v in aws_db_instance.instance : k => v.address }
}

output "sg_rds" {
description = "RDS instance security groups"
value = { for k, v in aws_security_group.rds : k => v.id }
}
147 changes: 147 additions & 0 deletions terraform/deployments/rds/rds.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
resource "random_string" "database_password" {
for_each = var.databases

length = 32
lower = true
}

resource "aws_db_subnet_group" "subnet_group" {
name = "${var.stackname}-govuk-rds-subnet"
subnet_ids = data.terraform_remote_state.infra_networking.outputs.private_subnet_rds_ids

tags = { Name = "${var.stackname}-govuk-rds-subnet" }
}

resource "aws_db_parameter_group" "engine_params" {
for_each = var.databases

name_prefix = "${each.value.name}-${each.value.engine}-"
family = merge({ engine_params_family = "${each.value.engine}${each.value.engine_version}" }, each.value)["engine_params_family"]

dynamic "parameter" {
for_each = each.value.engine_params

content {
name = parameter.key
value = parameter.value.value
apply_method = merge({ apply_method = "immediate" }, parameter.value)["apply_method"]
}
}
}

resource "aws_db_instance" "instance" {
for_each = var.databases

engine = each.value.engine
engine_version = each.value.engine_version
username = var.database_admin_username
password = random_string.database_password[each.key].result
allocated_storage = each.value.allocated_storage
instance_class = each.value.instance_class
identifier = "${each.value.name}-${each.value.engine}"
storage_type = "gp3"
db_subnet_group_name = aws_db_subnet_group.subnet_group.name
multi_az = var.multi_az
parameter_group_name = aws_db_parameter_group.engine_params[each.key].name
maintenance_window = var.maintenance_window
backup_retention_period = var.backup_retention_period
backup_window = var.backup_window
copy_tags_to_snapshot = true
monitoring_interval = 60
monitoring_role_arn = data.terraform_remote_state.infra_monitoring.outputs.rds_enhanced_monitoring_role_arn
vpc_security_group_ids = [aws_security_group.rds[each.key].id]
ca_cert_identifier = "rds-ca-rsa2048-g1"
apply_immediately = var.govuk_environment != "production"

performance_insights_enabled = each.value.performance_insights_enabled
performance_insights_retention_period = each.value.performance_insights_enabled ? 7 : 0

timeouts {
create = var.terraform_create_rds_timeout
delete = var.terraform_delete_rds_timeout
update = var.terraform_update_rds_timeout
}

deletion_protection = var.govuk_environment == "production"
final_snapshot_identifier = "${each.value.name}-final-snapshot"
skip_final_snapshot = var.skip_final_snapshot

tags = { Name = "${var.stackname}-govuk-rds-${each.value.name}-${each.value.engine}" }
}

resource "aws_db_event_subscription" "subscription" {
name = "govuk-rds-event-subscription"
sns_topic = data.terraform_remote_state.infra_monitoring.outputs.sns_topic_rds_events_arn

source_type = "db-instance"
source_ids = [for i in aws_db_instance.instance : i.identifier]
event_categories = ["availability", "deletion", "failure", "low storage"]
}

# Alarm if average CPU utilisation is above the threshold (we use 80% for most of our databases) for 60s
resource "aws_cloudwatch_metric_alarm" "rds_cpuutilization" {
for_each = var.databases

alarm_name = "${each.value.name}-rds-cpuutilization"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = "2"
metric_name = "CPUUtilization"
namespace = "AWS/RDS"
period = "60"
statistic = "Average"
threshold = each.value.cpuutilization_threshold
actions_enabled = true
alarm_actions = [data.terraform_remote_state.infra_monitoring.outputs.sns_topic_cloudwatch_alarms_arn]
alarm_description = "This metric monitors the percentage of CPU utilization."

dimensions = {
DBInstanceIdentifier = aws_db_instance.instance[each.key].id
}
}

# Alarm if free storage space is below the threshold (we use 10GiB for most of our databases) for 60s
resource "aws_cloudwatch_metric_alarm" "rds_freestoragespace" {
for_each = var.databases

alarm_name = "${each.value.name}-rds-freestoragespace"
comparison_operator = "LessThanThreshold"
evaluation_periods = "2"
metric_name = "FreeStorageSpace"
namespace = "AWS/RDS"
period = "60"
statistic = "Average"
threshold = each.value.freestoragespace_threshold
actions_enabled = true
alarm_actions = [data.terraform_remote_state.infra_monitoring.outputs.sns_topic_cloudwatch_alarms_arn]
alarm_description = "This metric monitors the amount of available storage space."

dimensions = {
DBInstanceIdentifier = aws_db_instance.instance[each.key].id
}
}

data "aws_route53_zone" "internal" {
name = var.internal_zone_name
private_zone = true
}

# internal_domain_name is ${var.stackname}.${internal_root_domain_name}
resource "aws_route53_record" "database_internal_domain_name" {
for_each = var.databases

zone_id = data.aws_route53_zone.internal.zone_id
name = "${each.value.name}-${each.value.engine}.${var.internal_domain_name}"
type = "CNAME"
ttl = 300
records = [aws_db_instance.instance[each.key].address]
}

resource "aws_route53_record" "database_internal_root_domain_name" {
for_each = var.databases

zone_id = data.terraform_remote_state.infra_root_dns_zones.outputs.internal_root_zone_id
name = "${each.value.name}-${each.value.engine}.${data.terraform_remote_state.infra_root_dns_zones.outputs.internal_root_domain_name}"
type = "CNAME"
ttl = 300
records = [aws_route53_record.database_internal_domain_name[each.key].fqdn]
}
39 changes: 39 additions & 0 deletions terraform/deployments/rds/remote_state.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
data "terraform_remote_state" "infra_monitoring" {
backend = "s3"

config = {
bucket = var.govuk_aws_state_bucket
key = "${coalesce(var.remote_state_infra_monitoring_key_stack, var.stackname)}/infra-monitoring.tfstate"
region = var.aws_region
}
}

data "terraform_remote_state" "infra_networking" {
backend = "s3"

config = {
bucket = var.govuk_aws_state_bucket
key = "${coalesce(var.remote_state_infra_networking_key_stack, var.stackname)}/infra-networking.tfstate"
region = var.aws_region
}
}

data "terraform_remote_state" "infra_root_dns_zones" {
backend = "s3"

config = {
bucket = var.govuk_aws_state_bucket
key = "${coalesce(var.remote_state_infra_root_dns_zones_key_stack, var.stackname)}/infra-root-dns-zones.tfstate"
region = var.aws_region
}
}

data "terraform_remote_state" "infra_vpc" {
backend = "s3"

config = {
bucket = var.govuk_aws_state_bucket
key = "${coalesce(var.remote_state_infra_vpc_key_stack, var.stackname)}/infra-vpc.tfstate"
region = var.aws_region
}
}
8 changes: 8 additions & 0 deletions terraform/deployments/rds/security_groups.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
resource "aws_security_group" "rds" {
for_each = var.databases

name = "${var.stackname}_${each.value.name}_rds_access"
vpc_id = data.terraform_remote_state.infra_vpc.outputs.vpc_id
description = "Access to ${each.value.name} RDS"
tags = { Name = "${var.stackname}_${each.value.name}_rds_access" }
}
114 changes: 114 additions & 0 deletions terraform/deployments/rds/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
variable "govuk_environment" {
type = string
description = "Environment name"
}

variable "aws_region" {
type = string
description = "AWS region"
default = "eu-west-1"
}

variable "stackname" {
type = string
description = "Stackname"
default = "blue"
}

variable "govuk_aws_state_bucket" {
type = string
description = "Bucket where govuk-aws state is stored"
}

variable "remote_state_infra_monitoring_key_stack" {
type = string
description = "Override path to infra_monitoring remote state"
default = "govuk"
}

variable "remote_state_infra_networking_key_stack" {
type = string
description = "Override path to infra_networking remote state"
default = "govuk"
}

variable "remote_state_infra_root_dns_zones_key_stack" {
type = string
description = "Override path to infra_root_dns_zones remote state"
default = "govuk"
}

variable "remote_state_infra_vpc_key_stack" {
type = string
description = "Override path to infra_vpc remote state"
default = "govuk"
}

variable "databases" {
type = map(any)
description = "Databases to create and their configuration."
}

variable "database_admin_username" {
type = string
default = "aws_db_admin"
description = "RDS root account username."
}

variable "multi_az" {
type = bool
description = "Set to true to deploy the RDS instance in multiple AZs."
default = false
}

variable "maintenance_window" {
type = string
description = "The window to perform maintenance in"
default = "Mon:04:00-Mon:06:00"
}

variable "backup_window" {
type = string
description = "The daily time range during which automated backups are created if automated backups are enabled."
default = "01:00-03:00"
}

variable "backup_retention_period" {
type = number
description = "Backup retention period in days."
default = 7
}

variable "skip_final_snapshot" {
type = bool
description = "Set to true to NOT create a final snapshot when the cluster is deleted."
default = false
}

variable "terraform_create_rds_timeout" {
type = string
description = "Set the timeout time for AWS RDS creation."
default = "2h"
}

variable "terraform_update_rds_timeout" {
type = string
description = "Set the timeout time for AWS RDS modification."
default = "2h"
}

variable "terraform_delete_rds_timeout" {
type = string
description = "Set the timeout time for AWS RDS deletion."
default = "2h"
}

variable "internal_zone_name" {
type = string
description = "The name of the Route53 zone that contains internal records"
}

variable "internal_domain_name" {
type = string
description = "The domain name of the internal DNS records, it could be different from the zone name"
}
2 changes: 1 addition & 1 deletion terraform/deployments/tfc-configuration/rds.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module "rds-integration" {
project_name = "govuk-infrastructure"
vcs_repo = {
identifier = "alphagov/govuk-infrastructure"
branch = "samsimpson1/rds"
branch = "main"
oauth_token_id = data.tfe_oauth_client.github.oauth_token_id
}

Expand Down
Loading
Loading