diff --git a/terraform/.header.md b/terraform/.header.md new file mode 100644 index 0000000000..fb57088117 --- /dev/null +++ b/terraform/.header.md @@ -0,0 +1,21 @@ +This module provides a basic Fleet setup. This assumes that you bring nothing to the installation. +If you want to bring your own VPC/database/cache nodes/ECS cluster, then use one of the submodules provided. + +The following is the module layout so you can navigate to the module that you want: + +* Root module (use this to get a Fleet instance ASAP with minimal setup) + * BYO-VPC (use this if you want to install Fleet inside an existing VPC) + * BYO-database (use this if you want to use an existing database and cache node) + * BYO-ECS (use this if you want to bring your own everything but Fleet ECS services) + +# How to improve this module +If this module somehow doesn't fit your needs, feel free to contact us by +opening a ticket, or contacting your contact at Fleet. Our goal is to make this module +fit all needs within AWS, so we will try to find a solution so that this module fits your needs. + +If you want to make the changes yourself, simply make a PR into main with your additions. +We would ask that you make sure that variables are defined as null if there is +no default that makes sense and that variable changes are reflected all the way up the stack. + +# How to update this readme +Edit .header.md and run `terraform-docs markdown . > README.md` diff --git a/terraform/.terraform-docs.yml b/terraform/.terraform-docs.yml new file mode 100644 index 0000000000..1d139ddb40 --- /dev/null +++ b/terraform/.terraform-docs.yml @@ -0,0 +1 @@ +header-from: .header.md diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000000..f31b5a11f8 --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,59 @@ +This module provides a basic Fleet setup. This assumes that you bring nothing to the installation. +If you want to bring your own VPC/database/cache nodes/ECS cluster, then use one of the submodules provided. + +The following is the module layout so you can navigate to the module that you want: + +* Root module (use this to get a Fleet instance ASAP with minimal setup) + * BYO-VPC (use this if you want to install Fleet inside an existing VPC) + * BYO-database (use this if you want to use an existing database and cache node) + * BYO-ECS (use this if you want to bring your own everything but Fleet ECS services) + +# How to improve this module +If this module somehow doesn't fit your needs, feel free to contact us by +opening a ticket, or contacting your contact at Fleet. Our goal is to make this module +fit all needs within AWS, so we will try to find a solution so that this module fits your needs. + +If you want to make the changes yourself, simply make a PR into main with your additions. +We would ask that you make sure that variables are defined as null if there is +no default that makes sense and that variable changes are reflected all the way up the stack. + +# How to update this readme +Edit .header.md and run `terraform-docs markdown . > README.md` + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [byo-vpc](#module\_byo-vpc) | ./byo-vpc | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [alb\_config](#input\_alb\_config) | n/a |
object({
name = optional(string, "fleet")
security_groups = optional(list(string), [])
access_logs = optional(map(string), {})
})
| `{}` | no | +| [certificate\_arn](#input\_certificate\_arn) | n/a | `string` | n/a | yes | +| [ecs\_cluster](#input\_ecs\_cluster) | The config for the terraform-aws-modules/ecs/aws module |
object({
autoscaling_capacity_providers = any
cluster_configuration = any
cluster_name = string
cluster_settings = map(string)
create = bool
default_capacity_provider_use_fargate = bool
fargate_capacity_providers = any
tags = map(string)
})
|
{
"autoscaling_capacity_providers": {},
"cluster_configuration": {
"execute_command_configuration": {
"log_configuration": {
"cloud_watch_log_group_name": "/aws/ecs/aws-ec2"
},
"logging": "OVERRIDE"
}
},
"cluster_name": "fleet",
"cluster_settings": {
"name": "containerInsights",
"value": "enabled"
},
"create": true,
"default_capacity_provider_use_fargate": true,
"fargate_capacity_providers": {
"FARGATE": {
"default_capacity_provider_strategy": {
"weight": 100
}
},
"FARGATE_SPOT": {
"default_capacity_provider_strategy": {
"weight": 0
}
}
},
"tags": {}
}
| no | +| [fleet\_config](#input\_fleet\_config) | The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified. |
object({
mem = optional(number, 512)
cpu = optional(number, 256)
image = optional(string, "fleetdm/fleet:v4.22.1")
extra_environment_variables = optional(map(string), {})
extra_secrets = optional(map(string), {})
security_groups = optional(list(string), null)
iam_role_arn = optional(string, null)
database = object({
password_secret_arn = string
user = string
database = string
address = string
rr_address = optional(string, null)
})
redis = object({
address = string
use_tls = optional(bool, true)
})
awslogs = optional(object({
name = optional(string, null)
region = optional(string, null)
prefix = optional(string, "fleet")
retention = optional(number, 5)
}), {
name = null
region = null
prefix = "fleet"
retention = 5
})
loadbalancer = object({
arn = string
})
networking = object({
subnets = list(string)
security_groups = optional(list(string), null)
})
autoscaling = optional(object({
max_capacity = optional(number, 5)
min_capacity = optional(number, 1)
memory_tracking_target_value = optional(number, 80)
cpu_tracking_target_value = optional(number, 80)
}), {
max_capacity = 5
min_capacity = 1
memory_tracking_target_value = 80
cpu_tracking_target_value = 80
})
})
|
{
"autoscaling": {
"cpu_tracking_target_value": 80,
"max_capacity": 5,
"memory_tracking_target_value": 80,
"min_capacity": 1
},
"awslogs": {
"name": null,
"prefix": "fleet",
"region": null,
"retention": 5
},
"cpu": 256,
"database": {
"address": null,
"database": null,
"password_secret_arn": null,
"rr_address": null,
"user": null
},
"extra_environment_variables": {},
"extra_secrets": {},
"iam_role_arn": null,
"image": "fleetdm/fleet:v4.22.1",
"loadbalancer": {
"arn": null
},
"mem": 512,
"networking": {
"security_groups": null,
"subnets": null
},
"redis": {
"address": null,
"use_tls": true
},
"security_groups": null
}
| no | +| [migration\_config](#input\_migration\_config) | The configuration object for Fleet's migration task. |
object({
mem = number
cpu = number
})
|
{
"cpu": 1024,
"mem": 2048
}
| no | +| [rds\_config](#input\_rds\_config) | The config for the terraform-aws-modules/rds-aurora/aws module |
object({
name = optional(string, "fleet")
engine_version = optional(string, "8.0.mysql_aurora.3.02.2")
instance_class = optional(string, "db.t4g.large")
subnets = optional(list(string), [])
allowed_security_groups = optional(list(string), [])
allowed_cidr_blocks = optional(list(string), [])
apply_immediately = optional(bool, true)
monitoring_interval = optional(number, 10)
db_parameter_group_name = optional(string)
db_cluster_parameter_group_name = optional(string)
enabled_cloudwatch_logs_exports = optional(list(string), [])
master_username = optional(string, "fleet")
})
|
{
"allowed_cidr_blocks": [],
"allowed_security_groups": [],
"apply_immediately": true,
"db_cluster_parameter_group_name": null,
"db_parameter_group_name": null,
"enabled_cloudwatch_logs_exports": [],
"engine_version": "8.0.mysql_aurora.3.02.2",
"instance_class": "db.t4g.large",
"master_username": "fleet",
"monitoring_interval": 10,
"name": "fleet",
"subnets": []
}
| no | +| [redis\_config](#input\_redis\_config) | n/a |
object({
name = optional(string, "fleet")
replication_group_id = optional(string)
elasticache_subnet_group_name = optional(string)
allowed_security_group_ids = optional(list(string), [])
subnets = list(string)
availability_zones = list(string)
cluster_size = optional(number, 3)
instance_type = optional(string, "cache.m5.large")
apply_immediately = optional(bool, true)
automatic_failover_enabled = optional(bool, false)
engine_version = optional(string, "6.x")
family = optional(string, "redis6.x")
at_rest_encryption_enabled = optional(bool, true)
transit_encryption_enabled = optional(bool, true)
parameter = optional(list(object({
name = string
value = string
})), [])
})
|
{
"allowed_security_group_ids": [],
"apply_immediately": true,
"at_rest_encryption_enabled": true,
"automatic_failover_enabled": false,
"availability_zones": null,
"cluster_size": 3,
"elasticache_subnet_group_name": null,
"engine_version": "6.x",
"family": "redis6.x",
"instance_type": "cache.m5.large",
"name": "fleet",
"parameter": [],
"replication_group_id": null,
"subnets": null,
"transit_encryption_enabled": true
}
| no | +| [vpc](#input\_vpc) | n/a |
object({
name = string
cidr = string
azs = list(string)
private_subnets = list(string)
public_subnets = list(string)
database_subnets = list(string)
elasticache_subnets = list(string)

create_database_subnet_group = bool
create_database_subnet_route_table = bool
create_elasticache_subnet_group = bool
create_elasticache_subnet_route_table = bool
enable_vpn_gateway = bool
one_nat_gateway_per_az = bool
single_nat_gateway = bool
enable_nat_gateway = bool
})
|
{
"azs": [
"us-east-2a",
"us-east-2b",
"us-east-2c"
],
"cidr": "10.10.0.0/16",
"create_database_subnet_group": true,
"create_database_subnet_route_table": true,
"create_elasticache_subnet_group": true,
"create_elasticache_subnet_route_table": true,
"database_subnets": [
"10.10.21.0/24",
"10.10.22.0/24",
"10.10.23.0/24"
],
"elasticache_subnets": [
"10.10.31.0/24",
"10.10.32.0/24",
"10.10.33.0/24"
],
"enable_nat_gateway": true,
"enable_vpn_gateway": false,
"name": "fleet",
"one_nat_gateway_per_az": false,
"private_subnets": [
"10.10.1.0/24",
"10.10.2.0/24",
"10.10.3.0/24"
],
"public_subnets": [
"10.10.11.0/24",
"10.10.12.0/24",
"10.10.13.0/24"
],
"single_nat_gateway": true
}
| no | + +## Outputs + +| Name | Description | +|------|-------------| +| [byo-vpc](#output\_byo-vpc) | n/a | diff --git a/terraform/byo-vpc/README.md b/terraform/byo-vpc/README.md new file mode 100644 index 0000000000..2d6e3962dd --- /dev/null +++ b/terraform/byo-vpc/README.md @@ -0,0 +1,46 @@ +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 4.42.0 | +| [random](#provider\_random) | 3.4.3 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [byo-db](#module\_byo-db) | ./byo-db | n/a | +| [rds](#module\_rds) | terraform-aws-modules/rds-aurora/aws | 7.6.0 | +| [redis](#module\_redis) | cloudposse/elasticache-redis/aws | 0.48.0 | +| [secrets-manager-1](#module\_secrets-manager-1) | lgallard/secrets-manager/aws | 0.6.1 | + +## Resources + +| Name | Type | +|------|------| +| [aws_db_parameter_group.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_parameter_group) | resource | +| [aws_rds_cluster_parameter_group.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster_parameter_group) | resource | +| [random_password.rds](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | +| [aws_subnet.redis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [alb\_config](#input\_alb\_config) | n/a |
object({
name = optional(string, "fleet")
subnets = list(string)
security_groups = optional(list(string), [])
access_logs = optional(map(string), {})
certificate_arn = string
})
| n/a | yes | +| [ecs\_cluster](#input\_ecs\_cluster) | The config for the terraform-aws-modules/ecs/aws module |
object({
autoscaling_capacity_providers = any
cluster_configuration = any
cluster_name = string
cluster_settings = map(string)
create = bool
default_capacity_provider_use_fargate = bool
fargate_capacity_providers = any
tags = map(string)
})
|
{
"autoscaling_capacity_providers": {},
"cluster_configuration": {
"execute_command_configuration": {
"log_configuration": {
"cloud_watch_log_group_name": "/aws/ecs/aws-ec2"
},
"logging": "OVERRIDE"
}
},
"cluster_name": "fleet",
"cluster_settings": {
"name": "containerInsights",
"value": "enabled"
},
"create": true,
"default_capacity_provider_use_fargate": true,
"fargate_capacity_providers": {
"FARGATE": {
"default_capacity_provider_strategy": {
"weight": 100
}
},
"FARGATE_SPOT": {
"default_capacity_provider_strategy": {
"weight": 0
}
}
},
"tags": {}
}
| no | +| [fleet\_config](#input\_fleet\_config) | The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified. |
object({
mem = optional(number, 512)
cpu = optional(number, 256)
image = optional(string, "fleetdm/fleet:v4.22.1")
extra_environment_variables = optional(map(string), {})
extra_secrets = optional(map(string), {})
security_groups = optional(list(string), null)
iam_role_arn = optional(string, null)
database = object({
password_secret_arn = string
user = string
database = string
address = string
rr_address = optional(string, null)
})
redis = object({
address = string
use_tls = optional(bool, true)
})
awslogs = optional(object({
name = optional(string, null)
region = optional(string, null)
prefix = optional(string, "fleet")
retention = optional(number, 5)
}), {
name = null
region = null
prefix = "fleet"
retention = 5
})
loadbalancer = object({
arn = string
})
networking = object({
subnets = list(string)
security_groups = optional(list(string), null)
})
autoscaling = optional(object({
max_capacity = optional(number, 5)
min_capacity = optional(number, 1)
memory_tracking_target_value = optional(number, 80)
cpu_tracking_target_value = optional(number, 80)
}), {
max_capacity = 5
min_capacity = 1
memory_tracking_target_value = 80
cpu_tracking_target_value = 80
})
})
|
{
"autoscaling": {
"cpu_tracking_target_value": 80,
"max_capacity": 5,
"memory_tracking_target_value": 80,
"min_capacity": 1
},
"awslogs": {
"name": null,
"prefix": "fleet",
"region": null,
"retention": 5
},
"cpu": 256,
"database": {
"address": null,
"database": null,
"password_secret_arn": null,
"rr_address": null,
"user": null
},
"extra_environment_variables": {},
"extra_secrets": {},
"iam_role_arn": null,
"image": "fleetdm/fleet:v4.22.1",
"loadbalancer": {
"arn": null
},
"mem": 512,
"networking": {
"security_groups": null,
"subnets": null
},
"redis": {
"address": null,
"use_tls": true
},
"security_groups": null
}
| no | +| [migration\_config](#input\_migration\_config) | The configuration object for Fleet's migration task. |
object({
mem = number
cpu = number
})
|
{
"cpu": 1024,
"mem": 2048
}
| no | +| [rds\_config](#input\_rds\_config) | The config for the terraform-aws-modules/rds-aurora/aws module |
object({
name = optional(string, "fleet")
engine_version = optional(string, "8.0.mysql_aurora.3.02.2")
instance_class = optional(string, "db.t4g.large")
subnets = optional(list(string), [])
allowed_security_groups = optional(list(string), [])
allowed_cidr_blocks = optional(list(string), [])
apply_immediately = optional(bool, true)
monitoring_interval = optional(number, 10)
db_parameter_group_name = optional(string)
db_cluster_parameter_group_name = optional(string)
enabled_cloudwatch_logs_exports = optional(list(string), [])
master_username = optional(string, "fleet")
})
|
{
"allowed_cidr_blocks": [],
"allowed_security_groups": [],
"apply_immediately": true,
"db_cluster_parameter_group_name": null,
"db_parameter_group_name": null,
"enabled_cloudwatch_logs_exports": [],
"engine_version": "8.0.mysql_aurora.3.02.2",
"instance_class": "db.t4g.large",
"master_username": "fleet",
"monitoring_interval": 10,
"name": "fleet",
"subnets": []
}
| no | +| [redis\_config](#input\_redis\_config) | n/a |
object({
name = optional(string, "fleet")
replication_group_id = optional(string)
elasticache_subnet_group_name = optional(string)
allowed_security_group_ids = optional(list(string), [])
subnets = list(string)
availability_zones = list(string)
cluster_size = optional(number, 3)
instance_type = optional(string, "cache.m5.large")
apply_immediately = optional(bool, true)
automatic_failover_enabled = optional(bool, false)
engine_version = optional(string, "6.x")
family = optional(string, "redis6.x")
at_rest_encryption_enabled = optional(bool, true)
transit_encryption_enabled = optional(bool, true)
parameter = optional(list(object({
name = string
value = string
})), [])
})
|
{
"allowed_security_group_ids": [],
"apply_immediately": true,
"at_rest_encryption_enabled": true,
"automatic_failover_enabled": false,
"availability_zones": null,
"cluster_size": 3,
"elasticache_subnet_group_name": null,
"engine_version": "6.x",
"family": "redis6.x",
"instance_type": "cache.m5.large",
"name": "fleet",
"parameter": [],
"replication_group_id": null,
"subnets": null,
"transit_encryption_enabled": true
}
| no | +| [vpc\_config](#input\_vpc\_config) | n/a |
object({
vpc_id = string
networking = object({
subnets = list(string)
})
})
| n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [byo-db](#output\_byo-db) | n/a | diff --git a/terraform/byo-vpc/byo-db/README.md b/terraform/byo-vpc/byo-db/README.md new file mode 100644 index 0000000000..bbab816c84 --- /dev/null +++ b/terraform/byo-vpc/byo-db/README.md @@ -0,0 +1,40 @@ +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 4.40.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [alb](#module\_alb) | terraform-aws-modules/alb/aws | 8.2.1 | +| [cluster](#module\_cluster) | terraform-aws-modules/ecs/aws | 4.1.2 | +| [ecs](#module\_ecs) | ./byo-ecs | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_security_group.alb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [alb\_config](#input\_alb\_config) | n/a |
object({
name = optional(string, "fleet")
subnets = list(string)
security_groups = optional(list(string), [])
access_logs = optional(map(string), {})
certificate_arn = string
})
| n/a | yes | +| [ecs\_cluster](#input\_ecs\_cluster) | The config for the terraform-aws-modules/ecs/aws module |
object({
autoscaling_capacity_providers = any
cluster_configuration = any
cluster_name = string
cluster_settings = map(string)
create = bool
default_capacity_provider_use_fargate = bool
fargate_capacity_providers = any
tags = map(string)
})
|
{
"autoscaling_capacity_providers": {},
"cluster_configuration": {
"execute_command_configuration": {
"log_configuration": {
"cloud_watch_log_group_name": "/aws/ecs/aws-ec2"
},
"logging": "OVERRIDE"
}
},
"cluster_name": "fleet",
"cluster_settings": {
"name": "containerInsights",
"value": "enabled"
},
"create": true,
"default_capacity_provider_use_fargate": true,
"fargate_capacity_providers": {
"FARGATE": {
"default_capacity_provider_strategy": {
"weight": 100
}
},
"FARGATE_SPOT": {
"default_capacity_provider_strategy": {
"weight": 0
}
}
},
"tags": {}
}
| no | +| [fleet\_config](#input\_fleet\_config) | The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified. |
object({
mem = optional(number, 512)
cpu = optional(number, 256)
image = optional(string, "fleetdm/fleet:v4.22.1")
extra_environment_variables = optional(map(string), {})
extra_secrets = optional(map(string), {})
security_groups = optional(list(string), null)
iam_role_arn = optional(string, null)
database = object({
password_secret_arn = string
user = string
database = string
address = string
rr_address = optional(string, null)
})
redis = object({
address = string
use_tls = optional(bool, true)
})
awslogs = optional(object({
name = optional(string, null)
region = optional(string, null)
prefix = optional(string, "fleet")
retention = optional(number, 5)
}), {
name = null
region = null
prefix = "fleet"
retention = 5
})
loadbalancer = object({
arn = string
})
networking = object({
subnets = list(string)
security_groups = optional(list(string), null)
})
autoscaling = optional(object({
max_capacity = optional(number, 5)
min_capacity = optional(number, 1)
memory_tracking_target_value = optional(number, 80)
cpu_tracking_target_value = optional(number, 80)
}), {
max_capacity = 5
min_capacity = 1
memory_tracking_target_value = 80
cpu_tracking_target_value = 80
})
})
|
{
"autoscaling": {
"cpu_tracking_target_value": 80,
"max_capacity": 5,
"memory_tracking_target_value": 80,
"min_capacity": 1
},
"awslogs": {
"name": null,
"prefix": "fleet",
"region": null,
"retention": 5
},
"cpu": 256,
"database": {
"address": null,
"database": null,
"password_secret_arn": null,
"rr_address": null,
"user": null
},
"extra_environment_variables": {},
"extra_secrets": {},
"iam_role_arn": null,
"image": "fleetdm/fleet:v4.22.1",
"loadbalancer": {
"arn": null
},
"mem": 512,
"networking": {
"security_groups": null,
"subnets": null
},
"redis": {
"address": null,
"use_tls": true
},
"security_groups": null
}
| no | +| [migration\_config](#input\_migration\_config) | The configuration object for Fleet's migration task. |
object({
mem = number
cpu = number
})
|
{
"cpu": 1024,
"mem": 2048
}
| no | +| [vpc\_id](#input\_vpc\_id) | n/a | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [alb](#output\_alb) | n/a | +| [byo-ecs](#output\_byo-ecs) | n/a | diff --git a/terraform/byo-vpc/byo-db/byo-ecs/README.md b/terraform/byo-vpc/byo-db/byo-ecs/README.md new file mode 100644 index 0000000000..b00c75cdb9 --- /dev/null +++ b/terraform/byo-vpc/byo-db/byo-ecs/README.md @@ -0,0 +1,51 @@ +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 4.39.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_appautoscaling_policy.ecs_policy_cpu](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource | +| [aws_appautoscaling_policy.ecs_policy_memory](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource | +| [aws_appautoscaling_target.ecs_target](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_target) | resource | +| [aws_cloudwatch_log_group.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_ecs_service.fleet](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | +| [aws_ecs_task_definition.backend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource | +| [aws_iam_policy.execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.role_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_security_group.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_iam_policy_document.assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.fleet](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.fleet-execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [ecs\_cluster](#input\_ecs\_cluster) | The name of the ECS cluster to use | `string` | n/a | yes | +| [fleet\_config](#input\_fleet\_config) | The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified. |
object({
mem = optional(number, 512)
cpu = optional(number, 256)
image = optional(string, "fleetdm/fleet:v4.22.1")
extra_environment_variables = optional(map(string), {})
extra_secrets = optional(map(string), {})
security_groups = optional(list(string), null)
iam_role_arn = optional(string, null)
database = object({
password_secret_arn = string
user = string
database = string
address = string
rr_address = optional(string, null)
})
redis = object({
address = string
use_tls = optional(bool, true)
})
awslogs = optional(object({
name = optional(string, null)
region = optional(string, null)
prefix = optional(string, "fleet")
retention = optional(number, 5)
}), {
name = null
region = null
prefix = "fleet"
retention = 5
})
loadbalancer = object({
arn = string
})
networking = object({
subnets = list(string)
security_groups = optional(list(string), null)
})
autoscaling = optional(object({
max_capacity = optional(number, 5)
min_capacity = optional(number, 1)
memory_tracking_target_value = optional(number, 80)
cpu_tracking_target_value = optional(number, 80)
}), {
max_capacity = 5
min_capacity = 1
memory_tracking_target_value = 80
cpu_tracking_target_value = 80
})
})
|
{
"autoscaling": {
"cpu_tracking_target_value": 80,
"max_capacity": 5,
"memory_tracking_target_value": 80,
"min_capacity": 1
},
"awslogs": {
"name": null,
"prefix": "fleet",
"region": null,
"retention": 5
},
"cpu": 256,
"database": {
"address": null,
"database": null,
"password_secret_arn": null,
"rr_address": null,
"user": null
},
"extra_environment_variables": {},
"extra_secrets": {},
"iam_role_arn": null,
"image": "fleetdm/fleet:v4.22.1",
"loadbalancer": {
"arn": null
},
"mem": 512,
"networking": {
"security_groups": null,
"subnets": null
},
"redis": {
"address": null,
"use_tls": true
},
"security_groups": null
}
| no | +| [migration\_config](#input\_migration\_config) | The configuration object for Fleet's migration task. |
object({
mem = number
cpu = number
})
|
{
"cpu": 1024,
"mem": 2048
}
| no | +| [vpc\_id](#input\_vpc\_id) | n/a | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [security\_groups](#output\_security\_groups) | n/a | diff --git a/terraform/byo-vpc/byo-db/byo-ecs/iam.tf b/terraform/byo-vpc/byo-db/byo-ecs/iam.tf new file mode 100644 index 0000000000..b876e38063 --- /dev/null +++ b/terraform/byo-vpc/byo-db/byo-ecs/iam.tf @@ -0,0 +1,70 @@ +data "aws_iam_policy_document" "fleet" { + statement { + effect = "Allow" + actions = ["cloudwatch:PutMetricData"] + resources = ["*"] + } + +} + +data "aws_iam_policy_document" "assume_role" { + statement { + effect = "Allow" + actions = ["sts:AssumeRole"] + principals { + identifiers = ["ecs.amazonaws.com", "ecs-tasks.amazonaws.com"] + type = "Service" + } + } +} + +data "aws_iam_policy_document" "fleet-execution" { + // allow fleet application to obtain the database password from secrets manager + statement { + effect = "Allow" + actions = ["secretsmanager:GetSecretValue"] + resources = [var.fleet_config.database.password_secret_arn] + } +} + +resource "aws_iam_role" "main" { + count = var.fleet_config.iam_role_arn == null ? 1 : 0 + name = "fleetdm-role" + description = "IAM role that Fleet application assumes when running in ECS" + assume_role_policy = data.aws_iam_policy_document.assume_role.json +} + +resource "aws_iam_policy" "main" { + count = var.fleet_config.iam_role_arn == null ? 1 : 0 + name = "fleet-iam-policy" + description = "IAM policy that Fleet application uses to define access to AWS resources" + policy = data.aws_iam_policy_document.fleet.json +} + +resource "aws_iam_role_policy_attachment" "main" { + count = var.fleet_config.iam_role_arn == null ? 1 : 0 + policy_arn = aws_iam_policy.main[0].arn + role = aws_iam_role.main[0].name +} + +resource "aws_iam_policy" "execution" { + name = "fleet-iam-policy-execution" + description = "IAM policy that Fleet application uses to define access to AWS resources" + policy = data.aws_iam_policy_document.fleet-execution.json +} + +resource "aws_iam_role_policy_attachment" "execution" { + policy_arn = aws_iam_policy.execution.arn + role = aws_iam_role.execution.name +} + +resource "aws_iam_role" "execution" { + name = "fleetdm-execution-role" + description = "The execution role for Fleet in ECS" + assume_role_policy = data.aws_iam_policy_document.assume_role.json +} + +resource "aws_iam_role_policy_attachment" "role_attachment" { + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" + role = aws_iam_role.execution.name +} diff --git a/terraform/byo-vpc/byo-db/byo-ecs/main.tf b/terraform/byo-vpc/byo-db/byo-ecs/main.tf new file mode 100644 index 0000000000..72eac1e58c --- /dev/null +++ b/terraform/byo-vpc/byo-db/byo-ecs/main.tf @@ -0,0 +1,183 @@ +data "aws_region" "current" {} + +resource "aws_ecs_service" "fleet" { + name = "fleet" + launch_type = "FARGATE" + cluster = var.ecs_cluster + task_definition = aws_ecs_task_definition.backend.arn + desired_count = 1 + deployment_minimum_healthy_percent = 100 + deployment_maximum_percent = 200 + health_check_grace_period_seconds = 30 + + load_balancer { + target_group_arn = var.fleet_config.loadbalancer.arn + container_name = "fleet" + container_port = 8080 + } + + lifecycle { + ignore_changes = [desired_count] + } + + network_configuration { + subnets = var.fleet_config.networking.subnets + security_groups = var.fleet_config.networking.security_groups == null ? aws_security_group.main.*.id : var.fleet_config.networking.security_groups + } +} + +resource "aws_ecs_task_definition" "backend" { + family = "fleet" + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + task_role_arn = var.fleet_config.iam_role_arn == null ? aws_iam_role.main[0].arn : var.fleet_config.iam_role_arn + execution_role_arn = aws_iam_role.execution.arn + cpu = var.fleet_config.cpu + memory = var.fleet_config.mem + container_definitions = jsonencode( + [ + { + name = "fleet" + image = var.fleet_config.image + cpu = var.fleet_config.cpu + memory = var.fleet_config.mem + mountPoints = [] + volumesFrom = [] + essential = true + portMappings = [ + { + # This port is the same that the contained application also uses + containerPort = 8080 + protocol = "tcp" + } + ] + networkMode = "awsvpc" + logConfiguration = { + logDriver = "awslogs" + options = { + awslogs-group = var.fleet_config.awslogs.name == null ? aws_cloudwatch_log_group.main[0].name : var.fleet_config.awslogs.name + awslogs-region = var.fleet_config.awslogs.name == null ? data.aws_region.current.name : var.fleet_config.awslogs.region + awslogs-stream-prefix = var.fleet_config.awslogs.prefix + } + }, + ulimits = [ + { + name = "nofile" + softLimit = 999999 + hardLimit = 999999 + } + ], + secrets = [ + { + name = "FLEET_MYSQL_PASSWORD" + valueFrom = var.fleet_config.database.password_secret_arn + }, + { + name = "FLEET_MYSQL_READ_REPLICA_PASSWORD" + valueFrom = var.fleet_config.database.password_secret_arn + } + ] + environment = [ + { + name = "FLEET_MYSQL_USERNAME" + value = var.fleet_config.database.user + }, + { + name = "FLEET_MYSQL_DATABASE" + value = var.fleet_config.database.database + }, + { + name = "FLEET_MYSQL_ADDRESS" + value = var.fleet_config.database.address + }, + { + name = "FLEET_MYSQL_READ_REPLICA_USERNAME" + value = var.fleet_config.database.user + }, { + name = "FLEET_MYSQL_READ_REPLICA_DATABASE" + value = var.fleet_config.database.database + }, + { + name = "FLEET_MYSQL_READ_REPLICA_ADDRESS" + value = var.fleet_config.database.rr_address == null ? var.fleet_config.database.address : var.fleet_config.database.rr_address + }, + { + name = "FLEET_REDIS_ADDRESS" + value = var.fleet_config.redis.address + }, + { + name = "FLEET_REDIS_USE_TLS" + value = tostring(var.fleet_config.redis.use_tls) + }, + { + name = "FLEET_SERVER_TLS" + value = "false" + }, + ] + } + ]) +} + +resource "aws_appautoscaling_target" "ecs_target" { + max_capacity = var.fleet_config.autoscaling.max_capacity + min_capacity = var.fleet_config.autoscaling.min_capacity + resource_id = "service/${var.ecs_cluster}/${aws_ecs_service.fleet.name}" + scalable_dimension = "ecs:service:DesiredCount" + service_namespace = "ecs" +} + +resource "aws_appautoscaling_policy" "ecs_policy_memory" { + name = "fleet-memory-autoscaling" + policy_type = "TargetTrackingScaling" + resource_id = aws_appautoscaling_target.ecs_target.resource_id + scalable_dimension = aws_appautoscaling_target.ecs_target.scalable_dimension + service_namespace = aws_appautoscaling_target.ecs_target.service_namespace + + target_tracking_scaling_policy_configuration { + predefined_metric_specification { + predefined_metric_type = "ECSServiceAverageMemoryUtilization" + } + target_value = var.fleet_config.autoscaling.memory_tracking_target_value + } +} + +resource "aws_appautoscaling_policy" "ecs_policy_cpu" { + name = "fleet-cpu-autoscaling" + policy_type = "TargetTrackingScaling" + resource_id = aws_appautoscaling_target.ecs_target.resource_id + scalable_dimension = aws_appautoscaling_target.ecs_target.scalable_dimension + service_namespace = aws_appautoscaling_target.ecs_target.service_namespace + + target_tracking_scaling_policy_configuration { + predefined_metric_specification { + predefined_metric_type = "ECSServiceAverageCPUUtilization" + } + + target_value = var.fleet_config.autoscaling.cpu_tracking_target_value + } +} + +resource "aws_cloudwatch_log_group" "main" { #tfsec:ignore:aws-cloudwatch-log-group-customer-key:exp:2022-07-01 + count = var.fleet_config.awslogs.name == null ? 1 : 0 + name = "fleetdm" + retention_in_days = var.fleet_config.awslogs.retention +} + +resource "aws_security_group" "main" { + count = var.fleet_config.security_groups == null ? 1 : 0 + name = "fleet" + vpc_id = var.vpc_id + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + } + ingress { + from_port = 8080 + to_port = 8080 + protocol = "TCP" + cidr_blocks = ["10.0.0.0/8"] + } +} diff --git a/terraform/byo-vpc/byo-db/byo-ecs/outputs.tf b/terraform/byo-vpc/byo-db/byo-ecs/outputs.tf new file mode 100644 index 0000000000..67e62cf55d --- /dev/null +++ b/terraform/byo-vpc/byo-db/byo-ecs/outputs.tf @@ -0,0 +1,3 @@ +output "security_groups" { + value = var.fleet_config.networking.security_groups == null ? aws_security_group.main.*.id : var.fleet_config.networking.security_groups +} diff --git a/terraform/byo-vpc/byo-db/byo-ecs/variables.tf b/terraform/byo-vpc/byo-db/byo-ecs/variables.tf new file mode 100644 index 0000000000..18701cbdf9 --- /dev/null +++ b/terraform/byo-vpc/byo-db/byo-ecs/variables.tf @@ -0,0 +1,116 @@ +variable "ecs_cluster" { + type = string + description = "The name of the ECS cluster to use" + nullable = false +} + +variable "vpc_id" { + type = string + default = null +} + +variable "fleet_config" { + type = object({ + mem = optional(number, 512) + cpu = optional(number, 256) + image = optional(string, "fleetdm/fleet:v4.22.1") + extra_environment_variables = optional(map(string), {}) + extra_secrets = optional(map(string), {}) + security_groups = optional(list(string), null) + iam_role_arn = optional(string, null) + database = object({ + password_secret_arn = string + user = string + database = string + address = string + rr_address = optional(string, null) + }) + redis = object({ + address = string + use_tls = optional(bool, true) + }) + awslogs = optional(object({ + name = optional(string, null) + region = optional(string, null) + prefix = optional(string, "fleet") + retention = optional(number, 5) + }), { + name = null + region = null + prefix = "fleet" + retention = 5 + }) + loadbalancer = object({ + arn = string + }) + networking = object({ + subnets = list(string) + security_groups = optional(list(string), null) + }) + autoscaling = optional(object({ + max_capacity = optional(number, 5) + min_capacity = optional(number, 1) + memory_tracking_target_value = optional(number, 80) + cpu_tracking_target_value = optional(number, 80) + }), { + max_capacity = 5 + min_capacity = 1 + memory_tracking_target_value = 80 + cpu_tracking_target_value = 80 + }) + }) + default = { + mem = 512 + cpu = 256 + image = "fleetdm/fleet:v4.22.1" + extra_environment_variables = {} + extra_secrets = {} + security_groups = null + iam_role_arn = null + database = { + password_secret_arn = null + user = null + database = null + address = null + rr_address = null + } + redis = { + address = null + use_tls = true + } + awslogs = { + name = null + region = null + prefix = "fleet" + retention = 5 + } + loadbalancer = { + arn = null + } + networking = { + subnets = null + security_groups = null + } + autoscaling = { + max_capacity = 5 + min_capacity = 1 + memory_tracking_target_value = 80 + cpu_tracking_target_value = 80 + } + } + description = "The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified." + nullable = false +} + +variable "migration_config" { + type = object({ + mem = number + cpu = number + }) + default = { + mem = 2048 + cpu = 1024 + } + description = "The configuration object for Fleet's migration task." + nullable = false +} diff --git a/terraform/byo-vpc/byo-db/main.tf b/terraform/byo-vpc/byo-db/main.tf new file mode 100644 index 0000000000..e1470acce6 --- /dev/null +++ b/terraform/byo-vpc/byo-db/main.tf @@ -0,0 +1,92 @@ +module "ecs" { + source = "./byo-ecs" + ecs_cluster = module.cluster.cluster_name + fleet_config = merge(var.fleet_config, { + loadbalancer = { + arn = module.alb.target_group_arns[0] + } + }) + migration_config = var.migration_config + vpc_id = var.vpc_id +} + +module "cluster" { + source = "terraform-aws-modules/ecs/aws" + version = "4.1.2" + + autoscaling_capacity_providers = var.ecs_cluster.autoscaling_capacity_providers + cluster_configuration = var.ecs_cluster.cluster_configuration + cluster_name = var.ecs_cluster.cluster_name + cluster_settings = var.ecs_cluster.cluster_settings + create = var.ecs_cluster.create + default_capacity_provider_use_fargate = var.ecs_cluster.default_capacity_provider_use_fargate + fargate_capacity_providers = var.ecs_cluster.fargate_capacity_providers + tags = var.ecs_cluster.tags +} + +module "alb" { + source = "terraform-aws-modules/alb/aws" + version = "8.2.1" + + name = var.alb_config.name + + load_balancer_type = "application" + + vpc_id = var.vpc_id + subnets = var.alb_config.subnets + security_groups = concat(var.alb_config.security_groups, [aws_security_group.alb.id]) + + target_groups = [ + { + name_prefix = var.alb_config.name + backend_protocol = "HTTP" + backend_port = 80 + target_type = "ip" + health_check = { + path = "/healthz" + matcher = "200" + timeout = 10 + interval = 15 + healthy_threshold = 5 + unhealthy_threshold = 5 + } + } + ] + + https_listeners = [ + { + port = 443 + protocol = "HTTPS" + certificate_arn = var.alb_config.certificate_arn + target_group_index = 0 + } + ] + + http_tcp_listeners = [ + { + port = 80 + protocol = "HTTP" + target_group_index = 0 + } + ] +} + +resource "aws_security_group" "alb" { + vpc_id = var.vpc_id + ingress { + description = "TLS from VPC" + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + } +} diff --git a/terraform/byo-vpc/byo-db/outputs.tf b/terraform/byo-vpc/byo-db/outputs.tf new file mode 100644 index 0000000000..9e94cbb361 --- /dev/null +++ b/terraform/byo-vpc/byo-db/outputs.tf @@ -0,0 +1,7 @@ +output "byo-ecs" { + value = module.ecs +} + +output "alb" { + value = module.alb +} diff --git a/terraform/byo-vpc/byo-db/variables.tf b/terraform/byo-vpc/byo-db/variables.tf new file mode 100644 index 0000000000..47b90936d1 --- /dev/null +++ b/terraform/byo-vpc/byo-db/variables.tf @@ -0,0 +1,165 @@ +variable "vpc_id" { + type = string +} + +variable "ecs_cluster" { + type = object({ + autoscaling_capacity_providers = any + cluster_configuration = any + cluster_name = string + cluster_settings = map(string) + create = bool + default_capacity_provider_use_fargate = bool + fargate_capacity_providers = any + tags = map(string) + }) + default = { + autoscaling_capacity_providers = {} + cluster_configuration = { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + cloud_watch_log_group_name = "/aws/ecs/aws-ec2" + } + } + } + cluster_name = "fleet" + cluster_settings = { + "name" : "containerInsights", + "value" : "enabled", + } + create = true + default_capacity_provider_use_fargate = true + fargate_capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = 100 + } + } + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = 0 + } + } + } + tags = {} + } + description = "The config for the terraform-aws-modules/ecs/aws module" + nullable = false +} + +variable "fleet_config" { + type = object({ + mem = optional(number, 512) + cpu = optional(number, 256) + image = optional(string, "fleetdm/fleet:v4.22.1") + extra_environment_variables = optional(map(string), {}) + extra_secrets = optional(map(string), {}) + security_groups = optional(list(string), null) + iam_role_arn = optional(string, null) + database = object({ + password_secret_arn = string + user = string + database = string + address = string + rr_address = optional(string, null) + }) + redis = object({ + address = string + use_tls = optional(bool, true) + }) + awslogs = optional(object({ + name = optional(string, null) + region = optional(string, null) + prefix = optional(string, "fleet") + retention = optional(number, 5) + }), { + name = null + region = null + prefix = "fleet" + retention = 5 + }) + loadbalancer = object({ + arn = string + }) + networking = object({ + subnets = list(string) + security_groups = optional(list(string), null) + }) + autoscaling = optional(object({ + max_capacity = optional(number, 5) + min_capacity = optional(number, 1) + memory_tracking_target_value = optional(number, 80) + cpu_tracking_target_value = optional(number, 80) + }), { + max_capacity = 5 + min_capacity = 1 + memory_tracking_target_value = 80 + cpu_tracking_target_value = 80 + }) + }) + default = { + mem = 512 + cpu = 256 + image = "fleetdm/fleet:v4.22.1" + extra_environment_variables = {} + extra_secrets = {} + security_groups = null + iam_role_arn = null + database = { + password_secret_arn = null + user = null + database = null + address = null + rr_address = null + } + redis = { + address = null + use_tls = true + } + awslogs = { + name = null + region = null + prefix = "fleet" + retention = 5 + } + loadbalancer = { + arn = null + } + networking = { + subnets = null + security_groups = null + } + autoscaling = { + max_capacity = 5 + min_capacity = 1 + memory_tracking_target_value = 80 + cpu_tracking_target_value = 80 + } + } + description = "The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified." + nullable = false +} + +variable "migration_config" { + type = object({ + mem = number + cpu = number + }) + default = { + mem = 2048 + cpu = 1024 + } + description = "The configuration object for Fleet's migration task." + nullable = false +} + +variable "alb_config" { + type = object({ + name = optional(string, "fleet") + subnets = list(string) + security_groups = optional(list(string), []) + access_logs = optional(map(string), {}) + certificate_arn = string + }) +} diff --git a/terraform/byo-vpc/main.tf b/terraform/byo-vpc/main.tf new file mode 100644 index 0000000000..5e64be58ff --- /dev/null +++ b/terraform/byo-vpc/main.tf @@ -0,0 +1,123 @@ +module "byo-db" { + source = "./byo-db" + vpc_id = var.vpc_config.vpc_id + fleet_config = merge(var.fleet_config, { + database = { + address = module.rds.cluster_endpoint + database = "fleet" + user = "fleet" + password_secret_arn = module.secrets-manager-1.secret_arns["database-password"] + } + redis = { + address = "${module.redis.endpoint}:${module.redis.port}" + } + networking = { + subnets = var.vpc_config.networking.subnets + } + }) + ecs_cluster = var.ecs_cluster + migration_config = var.migration_config + alb_config = var.alb_config +} + +resource "random_password" "rds" { + length = 16 + special = true + override_special = "!#$%&*()-_=+[]{}<>:?" +} + +module "rds" { + source = "terraform-aws-modules/rds-aurora/aws" + version = "7.6.0" + + name = var.rds_config.name + engine = "aurora-mysql" + engine_version = var.rds_config.engine_version + instance_class = var.rds_config.instance_class + + instances = { + one = {} + two = {} + } + + vpc_id = var.vpc_config.vpc_id + subnets = var.rds_config.subnets + + allowed_security_groups = concat(module.byo-db.byo-ecs.security_groups, var.rds_config.allowed_security_groups) + allowed_cidr_blocks = var.rds_config.allowed_cidr_blocks + + storage_encrypted = true + apply_immediately = var.rds_config.apply_immediately + monitoring_interval = var.rds_config.monitoring_interval + + db_parameter_group_name = var.rds_config.db_parameter_group_name == null ? aws_db_parameter_group.main[0].id : var.rds_config.db_parameter_group_name + db_cluster_parameter_group_name = var.rds_config.db_cluster_parameter_group_name == null ? aws_rds_cluster_parameter_group.main[0].id : var.rds_config.db_cluster_parameter_group_name + + enabled_cloudwatch_logs_exports = var.rds_config.enabled_cloudwatch_logs_exports + master_username = var.rds_config.master_username + master_password = random_password.rds.result + database_name = "fleet" + skip_final_snapshot = true +} + +data "aws_subnet" "redis" { + for_each = toset(var.redis_config.subnets) + id = each.value +} + +module "redis" { + source = "cloudposse/elasticache-redis/aws" + version = "0.48.0" + + name = var.redis_config.name + replication_group_id = var.redis_config.replication_group_id == null ? var.redis_config.name : var.redis_config.replication_group_id + elasticache_subnet_group_name = var.redis_config.elasticache_subnet_group_name == null ? var.redis_config.name : var.redis_config.elasticache_subnet_group_name + availability_zones = var.redis_config.availability_zones + vpc_id = var.vpc_config.vpc_id + description = "lsjdfldjlfjds" + #allowed_security_group_ids = concat(var.redis_config.allowed_security_group_ids, module.byo-db.ecs.security_group) + subnets = var.redis_config.subnets + cluster_size = var.redis_config.cluster_size + instance_type = var.redis_config.instance_type + apply_immediately = var.redis_config.apply_immediately + automatic_failover_enabled = var.redis_config.automatic_failover_enabled + engine_version = var.redis_config.engine_version + family = var.redis_config.family + at_rest_encryption_enabled = var.redis_config.at_rest_encryption_enabled + transit_encryption_enabled = var.redis_config.transit_encryption_enabled + parameter = var.redis_config.parameter + additional_security_group_rules = [{ + type = "ingress" + from_port = 0 + to_port = 65535 + protocol = "tcp" + cidr_blocks = ["10.0.0.0/8"] + }] +} + +module "secrets-manager-1" { + source = "lgallard/secrets-manager/aws" + version = "0.6.1" + + secrets = { + database-password = { + description = "fleet-database-password" + recovery_window_in_days = 0 + secret_string = module.rds.cluster_master_password + }, + } +} + +resource "aws_db_parameter_group" "main" { + count = var.rds_config.db_parameter_group_name == null ? 1 : 0 + name = "fleet" + family = "aurora-mysql8.0" + description = "fleet" +} + +resource "aws_rds_cluster_parameter_group" "main" { + count = var.rds_config.db_cluster_parameter_group_name == null ? 1 : 0 + name = "fleet" + family = "aurora-mysql8.0" + description = "fleet" +} diff --git a/terraform/byo-vpc/outputs.tf b/terraform/byo-vpc/outputs.tf new file mode 100644 index 0000000000..98ae9b41f7 --- /dev/null +++ b/terraform/byo-vpc/outputs.tf @@ -0,0 +1,3 @@ +output "byo-db" { + value = module.byo-db +} diff --git a/terraform/byo-vpc/variables.tf b/terraform/byo-vpc/variables.tf new file mode 100644 index 0000000000..034133e0cb --- /dev/null +++ b/terraform/byo-vpc/variables.tf @@ -0,0 +1,243 @@ +variable "vpc_config" { + type = object({ + vpc_id = string + networking = object({ + subnets = list(string) + }) + }) +} + +variable "rds_config" { + type = object({ + name = optional(string, "fleet") + engine_version = optional(string, "8.0.mysql_aurora.3.02.2") + instance_class = optional(string, "db.t4g.large") + subnets = optional(list(string), []) + allowed_security_groups = optional(list(string), []) + allowed_cidr_blocks = optional(list(string), []) + apply_immediately = optional(bool, true) + monitoring_interval = optional(number, 10) + db_parameter_group_name = optional(string) + db_cluster_parameter_group_name = optional(string) + enabled_cloudwatch_logs_exports = optional(list(string), []) + master_username = optional(string, "fleet") + }) + default = { + name = "fleet" + engine_version = "8.0.mysql_aurora.3.02.2" + instance_class = "db.t4g.large" + subnets = [] + allowed_security_groups = [] + allowed_cidr_blocks = [] + apply_immediately = true + monitoring_interval = 10 + db_parameter_group_name = null + db_cluster_parameter_group_name = null + enabled_cloudwatch_logs_exports = [] + master_username = "fleet" + } + description = "The config for the terraform-aws-modules/rds-aurora/aws module" + nullable = false +} + +variable "redis_config" { + type = object({ + name = optional(string, "fleet") + replication_group_id = optional(string) + elasticache_subnet_group_name = optional(string) + allowed_security_group_ids = optional(list(string), []) + subnets = list(string) + availability_zones = list(string) + cluster_size = optional(number, 3) + instance_type = optional(string, "cache.m5.large") + apply_immediately = optional(bool, true) + automatic_failover_enabled = optional(bool, false) + engine_version = optional(string, "6.x") + family = optional(string, "redis6.x") + at_rest_encryption_enabled = optional(bool, true) + transit_encryption_enabled = optional(bool, true) + parameter = optional(list(object({ + name = string + value = string + })), []) + }) + default = { + name = "fleet" + replication_group_id = null + elasticache_subnet_group_name = null + allowed_security_group_ids = [] + subnets = null + availability_zones = null + cluster_size = 3 + instance_type = "cache.m5.large" + apply_immediately = true + automatic_failover_enabled = false + engine_version = "6.x" + family = "redis6.x" + at_rest_encryption_enabled = true + transit_encryption_enabled = true + parameter = [] + } +} + +variable "ecs_cluster" { + type = object({ + autoscaling_capacity_providers = any + cluster_configuration = any + cluster_name = string + cluster_settings = map(string) + create = bool + default_capacity_provider_use_fargate = bool + fargate_capacity_providers = any + tags = map(string) + }) + default = { + autoscaling_capacity_providers = {} + cluster_configuration = { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + cloud_watch_log_group_name = "/aws/ecs/aws-ec2" + } + } + } + cluster_name = "fleet" + cluster_settings = { + "name" : "containerInsights", + "value" : "enabled", + } + create = true + default_capacity_provider_use_fargate = true + fargate_capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = 100 + } + } + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = 0 + } + } + } + tags = {} + } + description = "The config for the terraform-aws-modules/ecs/aws module" + nullable = false +} + +variable "fleet_config" { + type = object({ + mem = optional(number, 512) + cpu = optional(number, 256) + image = optional(string, "fleetdm/fleet:v4.22.1") + extra_environment_variables = optional(map(string), {}) + extra_secrets = optional(map(string), {}) + security_groups = optional(list(string), null) + iam_role_arn = optional(string, null) + database = object({ + password_secret_arn = string + user = string + database = string + address = string + rr_address = optional(string, null) + }) + redis = object({ + address = string + use_tls = optional(bool, true) + }) + awslogs = optional(object({ + name = optional(string, null) + region = optional(string, null) + prefix = optional(string, "fleet") + retention = optional(number, 5) + }), { + name = null + region = null + prefix = "fleet" + retention = 5 + }) + loadbalancer = object({ + arn = string + }) + networking = object({ + subnets = list(string) + security_groups = optional(list(string), null) + }) + autoscaling = optional(object({ + max_capacity = optional(number, 5) + min_capacity = optional(number, 1) + memory_tracking_target_value = optional(number, 80) + cpu_tracking_target_value = optional(number, 80) + }), { + max_capacity = 5 + min_capacity = 1 + memory_tracking_target_value = 80 + cpu_tracking_target_value = 80 + }) + }) + default = { + mem = 512 + cpu = 256 + image = "fleetdm/fleet:v4.22.1" + extra_environment_variables = {} + extra_secrets = {} + security_groups = null + iam_role_arn = null + database = { + password_secret_arn = null + user = null + database = null + address = null + rr_address = null + } + redis = { + address = null + use_tls = true + } + awslogs = { + name = null + region = null + prefix = "fleet" + retention = 5 + } + loadbalancer = { + arn = null + } + networking = { + subnets = null + security_groups = null + } + autoscaling = { + max_capacity = 5 + min_capacity = 1 + memory_tracking_target_value = 80 + cpu_tracking_target_value = 80 + } + } + description = "The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified." + nullable = false +} + +variable "migration_config" { + type = object({ + mem = number + cpu = number + }) + default = { + mem = 2048 + cpu = 1024 + } + description = "The configuration object for Fleet's migration task." + nullable = false +} + +variable "alb_config" { + type = object({ + name = optional(string, "fleet") + subnets = list(string) + security_groups = optional(list(string), []) + access_logs = optional(map(string), {}) + certificate_arn = string + }) +} diff --git a/terraform/example/main.tf b/terraform/example/main.tf new file mode 100644 index 0000000000..ec616a3dd8 --- /dev/null +++ b/terraform/example/main.tf @@ -0,0 +1,31 @@ +module "main" { + source = "../" + certificate_arn = module.acm.acm_certificate_arn +} + +module "acm" { + source = "terraform-aws-modules/acm/aws" + version = "4.3.1" + + domain_name = "fleet.loadtest.fleetdm.com" + zone_id = data.aws_route53_zone.main.id + + wait_for_validation = true +} + +resource "aws_route53_record" "main" { + zone_id = data.aws_route53_zone.main.id + name = "fleet.loadtest.fleetdm.com" + type = "A" + + alias { + name = module.main.byo-vpc.byo-db.alb.lb_dns_name + zone_id = module.main.byo-vpc.byo-db.alb.lb_zone_id + evaluate_target_health = true + } +} + +data "aws_route53_zone" "main" { + name = "loadtest.fleetdm.com." + private_zone = false +} diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000000..5f44d2b180 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,41 @@ +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + + name = var.vpc.name + cidr = var.vpc.cidr + + azs = var.vpc.azs + private_subnets = var.vpc.private_subnets + public_subnets = var.vpc.public_subnets + database_subnets = var.vpc.database_subnets + elasticache_subnets = var.vpc.elasticache_subnets + create_database_subnet_group = var.vpc.create_database_subnet_group + create_database_subnet_route_table = var.vpc.create_database_subnet_route_table + create_elasticache_subnet_group = var.vpc.create_elasticache_subnet_group + create_elasticache_subnet_route_table = var.vpc.create_elasticache_subnet_route_table + enable_vpn_gateway = var.vpc.enable_vpn_gateway + one_nat_gateway_per_az = var.vpc.one_nat_gateway_per_az + single_nat_gateway = var.vpc.single_nat_gateway + enable_nat_gateway = var.vpc.enable_nat_gateway +} + +module "byo-vpc" { + source = "./byo-vpc" + vpc_config = { + vpc_id = module.vpc.vpc_id + networking = { + subnets = module.vpc.private_subnets + } + } + rds_config = merge(var.rds_config, { + subnets = module.vpc.database_subnets + }) + redis_config = merge(var.redis_config, { + subnets = module.vpc.elasticache_subnets + availability_zones = var.vpc.azs + }) + alb_config = merge(var.alb_config, { + subnets = module.vpc.public_subnets + certificate_arn = var.certificate_arn + }) +} diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000000..c939e4f7ac --- /dev/null +++ b/terraform/outputs.tf @@ -0,0 +1,3 @@ +output "byo-vpc" { + value = module.byo-vpc +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000000..82105860e4 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,276 @@ +variable "vpc" { + type = object({ + name = string + cidr = string + azs = list(string) + private_subnets = list(string) + public_subnets = list(string) + database_subnets = list(string) + elasticache_subnets = list(string) + + create_database_subnet_group = bool + create_database_subnet_route_table = bool + create_elasticache_subnet_group = bool + create_elasticache_subnet_route_table = bool + enable_vpn_gateway = bool + one_nat_gateway_per_az = bool + single_nat_gateway = bool + enable_nat_gateway = bool + }) + default = { + name = "fleet" + cidr = "10.10.0.0/16" + azs = ["us-east-2a", "us-east-2b", "us-east-2c"] + private_subnets = ["10.10.1.0/24", "10.10.2.0/24", "10.10.3.0/24"] + public_subnets = ["10.10.11.0/24", "10.10.12.0/24", "10.10.13.0/24"] + database_subnets = ["10.10.21.0/24", "10.10.22.0/24", "10.10.23.0/24"] + elasticache_subnets = ["10.10.31.0/24", "10.10.32.0/24", "10.10.33.0/24"] + + create_database_subnet_group = true + create_database_subnet_route_table = true + create_elasticache_subnet_group = true + create_elasticache_subnet_route_table = true + enable_vpn_gateway = false + one_nat_gateway_per_az = false + single_nat_gateway = true + enable_nat_gateway = true + } +} + +variable "certificate_arn" { + type = string +} + +variable "rds_config" { + type = object({ + name = optional(string, "fleet") + engine_version = optional(string, "8.0.mysql_aurora.3.02.2") + instance_class = optional(string, "db.t4g.large") + subnets = optional(list(string), []) + allowed_security_groups = optional(list(string), []) + allowed_cidr_blocks = optional(list(string), []) + apply_immediately = optional(bool, true) + monitoring_interval = optional(number, 10) + db_parameter_group_name = optional(string) + db_cluster_parameter_group_name = optional(string) + enabled_cloudwatch_logs_exports = optional(list(string), []) + master_username = optional(string, "fleet") + }) + default = { + name = "fleet" + engine_version = "8.0.mysql_aurora.3.02.2" + instance_class = "db.t4g.large" + subnets = [] + allowed_security_groups = [] + allowed_cidr_blocks = [] + apply_immediately = true + monitoring_interval = 10 + db_parameter_group_name = null + db_cluster_parameter_group_name = null + enabled_cloudwatch_logs_exports = [] + master_username = "fleet" + } + description = "The config for the terraform-aws-modules/rds-aurora/aws module" + nullable = false +} + +variable "redis_config" { + type = object({ + name = optional(string, "fleet") + replication_group_id = optional(string) + elasticache_subnet_group_name = optional(string) + allowed_security_group_ids = optional(list(string), []) + subnets = list(string) + availability_zones = list(string) + cluster_size = optional(number, 3) + instance_type = optional(string, "cache.m5.large") + apply_immediately = optional(bool, true) + automatic_failover_enabled = optional(bool, false) + engine_version = optional(string, "6.x") + family = optional(string, "redis6.x") + at_rest_encryption_enabled = optional(bool, true) + transit_encryption_enabled = optional(bool, true) + parameter = optional(list(object({ + name = string + value = string + })), []) + }) + default = { + name = "fleet" + replication_group_id = null + elasticache_subnet_group_name = null + allowed_security_group_ids = [] + subnets = null + availability_zones = null + cluster_size = 3 + instance_type = "cache.m5.large" + apply_immediately = true + automatic_failover_enabled = false + engine_version = "6.x" + family = "redis6.x" + at_rest_encryption_enabled = true + transit_encryption_enabled = true + parameter = [] + } +} + +variable "ecs_cluster" { + type = object({ + autoscaling_capacity_providers = any + cluster_configuration = any + cluster_name = string + cluster_settings = map(string) + create = bool + default_capacity_provider_use_fargate = bool + fargate_capacity_providers = any + tags = map(string) + }) + default = { + autoscaling_capacity_providers = {} + cluster_configuration = { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + cloud_watch_log_group_name = "/aws/ecs/aws-ec2" + } + } + } + cluster_name = "fleet" + cluster_settings = { + "name" : "containerInsights", + "value" : "enabled", + } + create = true + default_capacity_provider_use_fargate = true + fargate_capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = 100 + } + } + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = 0 + } + } + } + tags = {} + } + description = "The config for the terraform-aws-modules/ecs/aws module" + nullable = false +} + +variable "fleet_config" { + type = object({ + mem = optional(number, 512) + cpu = optional(number, 256) + image = optional(string, "fleetdm/fleet:v4.22.1") + extra_environment_variables = optional(map(string), {}) + extra_secrets = optional(map(string), {}) + security_groups = optional(list(string), null) + iam_role_arn = optional(string, null) + database = object({ + password_secret_arn = string + user = string + database = string + address = string + rr_address = optional(string, null) + }) + redis = object({ + address = string + use_tls = optional(bool, true) + }) + awslogs = optional(object({ + name = optional(string, null) + region = optional(string, null) + prefix = optional(string, "fleet") + retention = optional(number, 5) + }), { + name = null + region = null + prefix = "fleet" + retention = 5 + }) + loadbalancer = object({ + arn = string + }) + networking = object({ + subnets = list(string) + security_groups = optional(list(string), null) + }) + autoscaling = optional(object({ + max_capacity = optional(number, 5) + min_capacity = optional(number, 1) + memory_tracking_target_value = optional(number, 80) + cpu_tracking_target_value = optional(number, 80) + }), { + max_capacity = 5 + min_capacity = 1 + memory_tracking_target_value = 80 + cpu_tracking_target_value = 80 + }) + }) + default = { + mem = 512 + cpu = 256 + image = "fleetdm/fleet:v4.22.1" + extra_environment_variables = {} + extra_secrets = {} + security_groups = null + iam_role_arn = null + database = { + password_secret_arn = null + user = null + database = null + address = null + rr_address = null + } + redis = { + address = null + use_tls = true + } + awslogs = { + name = null + region = null + prefix = "fleet" + retention = 5 + } + loadbalancer = { + arn = null + } + networking = { + subnets = null + security_groups = null + } + autoscaling = { + max_capacity = 5 + min_capacity = 1 + memory_tracking_target_value = 80 + cpu_tracking_target_value = 80 + } + } + description = "The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified." + nullable = false +} + +variable "migration_config" { + type = object({ + mem = number + cpu = number + }) + description = "The configuration object for Fleet's migration task." + nullable = false + default = { + mem = 2048 + cpu = 1024 + } +} + +variable "alb_config" { + type = object({ + name = optional(string, "fleet") + security_groups = optional(list(string), []) + access_logs = optional(map(string), {}) + }) + default = {} +}