Skip to content

Commit

Permalink
feat: Blue/green deployment for postgres (#517)
Browse files Browse the repository at this point in the history
  • Loading branch information
magreenbaum committed Nov 13, 2023
1 parent 9c264b9 commit 9c18851
Show file tree
Hide file tree
Showing 41 changed files with 530 additions and 53 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ Users have the ability to:
- [Replica RDS example for MySQL](https://github.com/terraform-aws-modules/terraform-aws-rds/tree/master/examples/replica-mysql)
- [Replica RDS example for PostgreSQL](https://github.com/terraform-aws-modules/terraform-aws-rds/tree/master/examples/replica-postgres)
- [S3 import example for MySQL](https://github.com/terraform-aws-modules/terraform-aws-rds/tree/master/examples/s3-import-mysql)
- [Blue/Green Deployment example for MySQL and PostgreSQL](https://github.com/terraform-aws-modules/terraform-aws-rds/tree/master/examples/blue-green-deployment)

## Notes

Expand All @@ -209,7 +210,7 @@ Users have the ability to:
| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 5.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 5.25 |

## Providers

Expand Down
98 changes: 98 additions & 0 deletions examples/blue-green-deployment/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Blue/Green deployment example for PostgreSQL

Configuration in this directory creates a set of RDS resources including DB instance, DB subnet group and DB parameter group.

## Usage

To run this example you need to execute:

```bash
$ terraform init
$ terraform plan
$ terraform apply
```

To see blue/green deployment, update the `engine_version` argument in `module.postgres` to 15.4 and/or `engine_version` in module.mysql to 8.0.34 after initial apply then execute:

```bash
$ terraform plan
$ terraform apply
```

Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources.

<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 5.25 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 5.25 |

## Modules

| Name | Source | Version |
|------|--------|---------|
| <a name="module_mysql"></a> [mysql](#module\_mysql) | ../../ | n/a |
| <a name="module_mysql_security_group"></a> [mysql\_security\_group](#module\_mysql\_security\_group) | terraform-aws-modules/security-group/aws | ~> 5.0 |
| <a name="module_postgres"></a> [postgres](#module\_postgres) | ../../ | n/a |
| <a name="module_postgres_security_group"></a> [postgres\_security\_group](#module\_postgres\_security\_group) | terraform-aws-modules/security-group/aws | ~> 5.0 |
| <a name="module_vpc"></a> [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 |

## Resources

| Name | Type |
|------|------|
| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source |

## Inputs

No inputs.

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_db_instance_status"></a> [db\_instance\_status](#output\_db\_instance\_status) | The RDS instance status |
| <a name="output_mysql_db_instance_address"></a> [mysql\_db\_instance\_address](#output\_mysql\_db\_instance\_address) | The address of the RDS instance |
| <a name="output_mysql_db_instance_arn"></a> [mysql\_db\_instance\_arn](#output\_mysql\_db\_instance\_arn) | The ARN of the RDS instance |
| <a name="output_mysql_db_instance_availability_zone"></a> [mysql\_db\_instance\_availability\_zone](#output\_mysql\_db\_instance\_availability\_zone) | The availability zone of the RDS instance |
| <a name="output_mysql_db_instance_cloudwatch_log_groups"></a> [mysql\_db\_instance\_cloudwatch\_log\_groups](#output\_mysql\_db\_instance\_cloudwatch\_log\_groups) | Map of CloudWatch log groups created and their attributes |
| <a name="output_mysql_db_instance_endpoint"></a> [mysql\_db\_instance\_endpoint](#output\_mysql\_db\_instance\_endpoint) | The connection endpoint |
| <a name="output_mysql_db_instance_engine"></a> [mysql\_db\_instance\_engine](#output\_mysql\_db\_instance\_engine) | The database engine |
| <a name="output_mysql_db_instance_engine_version_actual"></a> [mysql\_db\_instance\_engine\_version\_actual](#output\_mysql\_db\_instance\_engine\_version\_actual) | The running version of the database |
| <a name="output_mysql_db_instance_hosted_zone_id"></a> [mysql\_db\_instance\_hosted\_zone\_id](#output\_mysql\_db\_instance\_hosted\_zone\_id) | The canonical hosted zone ID of the DB instance (to be used in a Route 53 Alias record) |
| <a name="output_mysql_db_instance_identifier"></a> [mysql\_db\_instance\_identifier](#output\_mysql\_db\_instance\_identifier) | The RDS instance identifier |
| <a name="output_mysql_db_instance_name"></a> [mysql\_db\_instance\_name](#output\_mysql\_db\_instance\_name) | The database name |
| <a name="output_mysql_db_instance_port"></a> [mysql\_db\_instance\_port](#output\_mysql\_db\_instance\_port) | The database port |
| <a name="output_mysql_db_instance_resource_id"></a> [mysql\_db\_instance\_resource\_id](#output\_mysql\_db\_instance\_resource\_id) | The RDS Resource ID of this instance |
| <a name="output_mysql_db_instance_username"></a> [mysql\_db\_instance\_username](#output\_mysql\_db\_instance\_username) | The master username for the database |
| <a name="output_mysql_db_parameter_group_arn"></a> [mysql\_db\_parameter\_group\_arn](#output\_mysql\_db\_parameter\_group\_arn) | The ARN of the db parameter group |
| <a name="output_mysql_db_parameter_group_id"></a> [mysql\_db\_parameter\_group\_id](#output\_mysql\_db\_parameter\_group\_id) | The db parameter group id |
| <a name="output_mysql_db_subnet_group_arn"></a> [mysql\_db\_subnet\_group\_arn](#output\_mysql\_db\_subnet\_group\_arn) | The ARN of the db subnet group |
| <a name="output_mysql_db_subnet_group_id"></a> [mysql\_db\_subnet\_group\_id](#output\_mysql\_db\_subnet\_group\_id) | The db subnet group name |
| <a name="output_postgres_db_instance_address"></a> [postgres\_db\_instance\_address](#output\_postgres\_db\_instance\_address) | The address of the RDS instance |
| <a name="output_postgres_db_instance_arn"></a> [postgres\_db\_instance\_arn](#output\_postgres\_db\_instance\_arn) | The ARN of the RDS instance |
| <a name="output_postgres_db_instance_availability_zone"></a> [postgres\_db\_instance\_availability\_zone](#output\_postgres\_db\_instance\_availability\_zone) | The availability zone of the RDS instance |
| <a name="output_postgres_db_instance_cloudwatch_log_groups"></a> [postgres\_db\_instance\_cloudwatch\_log\_groups](#output\_postgres\_db\_instance\_cloudwatch\_log\_groups) | Map of CloudWatch log groups created and their attributes |
| <a name="output_postgres_db_instance_endpoint"></a> [postgres\_db\_instance\_endpoint](#output\_postgres\_db\_instance\_endpoint) | The connection endpoint |
| <a name="output_postgres_db_instance_engine"></a> [postgres\_db\_instance\_engine](#output\_postgres\_db\_instance\_engine) | The database engine |
| <a name="output_postgres_db_instance_engine_version_actual"></a> [postgres\_db\_instance\_engine\_version\_actual](#output\_postgres\_db\_instance\_engine\_version\_actual) | The running version of the database |
| <a name="output_postgres_db_instance_hosted_zone_id"></a> [postgres\_db\_instance\_hosted\_zone\_id](#output\_postgres\_db\_instance\_hosted\_zone\_id) | The canonical hosted zone ID of the DB instance (to be used in a Route 53 Alias record) |
| <a name="output_postgres_db_instance_identifier"></a> [postgres\_db\_instance\_identifier](#output\_postgres\_db\_instance\_identifier) | The RDS instance identifier |
| <a name="output_postgres_db_instance_name"></a> [postgres\_db\_instance\_name](#output\_postgres\_db\_instance\_name) | The database name |
| <a name="output_postgres_db_instance_port"></a> [postgres\_db\_instance\_port](#output\_postgres\_db\_instance\_port) | The database port |
| <a name="output_postgres_db_instance_resource_id"></a> [postgres\_db\_instance\_resource\_id](#output\_postgres\_db\_instance\_resource\_id) | The RDS Resource ID of this instance |
| <a name="output_postgres_db_instance_status"></a> [postgres\_db\_instance\_status](#output\_postgres\_db\_instance\_status) | The RDS instance status |
| <a name="output_postgres_db_instance_username"></a> [postgres\_db\_instance\_username](#output\_postgres\_db\_instance\_username) | The master username for the database |
| <a name="output_postgres_db_parameter_group_arn"></a> [postgres\_db\_parameter\_group\_arn](#output\_postgres\_db\_parameter\_group\_arn) | The ARN of the db parameter group |
| <a name="output_postgres_db_parameter_group_id"></a> [postgres\_db\_parameter\_group\_id](#output\_postgres\_db\_parameter\_group\_id) | The db parameter group id |
| <a name="output_postgres_db_subnet_group_arn"></a> [postgres\_db\_subnet\_group\_arn](#output\_postgres\_db\_subnet\_group\_arn) | The ARN of the db subnet group |
| <a name="output_postgres_db_subnet_group_id"></a> [postgres\_db\_subnet\_group\_id](#output\_postgres\_db\_subnet\_group\_id) | The db subnet group name |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
188 changes: 188 additions & 0 deletions examples/blue-green-deployment/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
provider "aws" {
region = local.region
}

data "aws_availability_zones" "available" {}

locals {
name = "blue-green-example"
region = "eu-west-1"

vpc_cidr = "10.0.0.0/16"
azs = slice(data.aws_availability_zones.available.names, 0, 3)

tags = {
Name = local.name
Example = local.name
Repository = "https://github.com/terraform-aws-modules/terraform-aws-rds"
}
}

################################################################################
# Postgres
################################################################################

module "postgres" {
source = "../../"

identifier = "${local.name}-postgres"

# All blue/green deployment compatible versions: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RDS_Fea_Regions_DB-eng.Feature.BlueGreenDeployments.html
engine = "postgres"
engine_version = "14.9"
family = "postgres14" # DB parameter group
major_engine_version = "14" # DB option group
instance_class = "db.t4g.large"

allocated_storage = 20
max_allocated_storage = 100

# NOTE: Do NOT use 'user' as the value for 'username' as it throws:
# "Error creating DB Instance: InvalidParameterValue: MasterUsername
# user cannot be used as it is a reserved word used by the engine"
db_name = "blueGreenExamplePostgresql"
username = "blue_green_example_postgresql"
port = 5432

multi_az = true
db_subnet_group_name = module.vpc.database_subnet_group
vpc_security_group_ids = [module.postgres_security_group.security_group_id]

maintenance_window = "Mon:00:00-Mon:03:00"
backup_window = "03:00-06:00"
enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]
create_cloudwatch_log_group = true
blue_green_update = {
enabled = true
}

password = "UberSecretPassword"
# Not supported with blue/green deployment
manage_master_user_password = false

backup_retention_period = 1
skip_final_snapshot = true
deletion_protection = false

parameters = [
# required for blue-green deployment
{
name = "rds.logical_replication"
value = 1
apply_method = "pending-reboot"
}
]

tags = local.tags
}

################################################################################
# MySQL
################################################################################

module "mysql" {
source = "../../"

identifier = "${local.name}-mysql"

# All blue/green deployment compatible versions: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RDS_Fea_Regions_DB-eng.Feature.BlueGreenDeployments.html
engine = "mysql"
engine_version = "8.0.33"
family = "mysql8.0" # DB parameter group
major_engine_version = "8.0" # DB option group
instance_class = "db.t4g.large"

allocated_storage = 20
max_allocated_storage = 100

db_name = "blueGreenExampleMysql"
username = "blue_green_example_mysql"
port = 3306

password = "UberSecretPassword"
# Not supported with blue/green deployment
manage_master_user_password = false

multi_az = true
db_subnet_group_name = module.vpc.database_subnet_group
vpc_security_group_ids = [module.mysql_security_group.security_group_id]

maintenance_window = "Mon:00:00-Mon:03:00"
backup_window = "03:00-06:00"
enabled_cloudwatch_logs_exports = ["general"]
create_cloudwatch_log_group = true
blue_green_update = {
enabled = true
}

skip_final_snapshot = true
deletion_protection = false

tags = local.tags
}

################################################################################
# Supporting Resources
################################################################################

module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"

name = local.name
cidr = local.vpc_cidr

azs = local.azs
public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k)]
private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 3)]
database_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 6)]

create_database_subnet_group = true

tags = local.tags
}

module "postgres_security_group" {
source = "terraform-aws-modules/security-group/aws"
version = "~> 5.0"

name = "${local.name}-postgresql"
description = "Blue/Green deployment PostgreSQL example security group"
vpc_id = module.vpc.vpc_id

# ingress
ingress_with_cidr_blocks = [
{
from_port = 5432
to_port = 5432
protocol = "tcp"
description = "PostgreSQL access from within VPC"
cidr_blocks = module.vpc.vpc_cidr_block
},
]

tags = local.tags
}


module "mysql_security_group" {
source = "terraform-aws-modules/security-group/aws"
version = "~> 5.0"

name = "${local.name}-mysql"
description = "Blue/Green deployment MySQL example security group"
vpc_id = module.vpc.vpc_id

# ingress
ingress_with_cidr_blocks = [
{
from_port = 3306
to_port = 3306
protocol = "tcp"
description = "MySQL access from within VPC"
cidr_blocks = module.vpc.vpc_cidr_block
},
]

tags = local.tags
}

0 comments on commit 9c18851

Please sign in to comment.