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

Testing template-infra#474 #65

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
52 changes: 44 additions & 8 deletions infra/app/service/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ data "aws_subnets" "default" {

locals {
# The prefix key/value pair is used for Terraform Workspaces, which is useful for projects with multiple infrastructure developers.
# By default, Terraform creates a workspace named “default.” If a non-default workspace is not created this prefix will equal “default”,
# if you choose not to use workspaces set this value to "dev"
# By default, Terraform creates a workspace named “default.” If a non-default workspace is not created this prefix will equal “default”,
# if you choose not to use workspaces set this value to "dev"
prefix = terraform.workspace == "default" ? "" : "${terraform.workspace}-"

# Add environment specific tags
Expand Down Expand Up @@ -62,6 +62,19 @@ module "app_config" {
source = "../app-config"
}

data "aws_security_groups" "aws_services" {
filter {
name = "group-name"
values = ["${module.project_config.aws_services_security_group_name_prefix}*"]
}

filter {
name = "vpc-id"
values = [data.aws_vpc.default.id]
}
}


data "aws_rds_cluster" "db_cluster" {
count = module.app_config.has_database ? 1 : 0
cluster_identifier = local.database_config.cluster_name
Expand All @@ -84,13 +97,27 @@ data "aws_ssm_parameter" "incident_management_service_integration_url" {
name = local.incident_management_service_integration_config.integration_url_param_name
}

# Customizations: Retrieve passwords created elsewhere (by other modules, manually)

resource "aws_ssm_parameter" "custom_secret" {
name = "/custom_secret"
type = "SecureString"
value = "200"
}

module "service" {
source = "../../modules/service"
service_name = local.service_name
image_repository_name = module.app_config.image_repository_name
image_tag = local.image_tag
vpc_id = data.aws_vpc.default.id
subnet_ids = data.aws_subnets.default.ids
source = "../../modules/service"
service_name = local.service_name
image_repository_name = module.app_config.image_repository_name
image_tag = "latest"
external_image_url = "docker.io/rocketnovadockerhub/tiny-env-test"
container_read_only = false
container_port = 8000
healthcheck_start_period = 0
healthcheck_path = "customhealth"
healthcheck_matcher = "200-302"
vpc_id = data.aws_vpc.default.id
subnet_ids = data.aws_subnets.default.ids

db_vars = module.app_config.has_database ? {
security_group_ids = data.aws_rds_cluster.db_cluster[0].vpc_security_group_ids
Expand All @@ -104,6 +131,15 @@ module "service" {
schema_name = local.database_config.schema_name
}
} : null

container_env_vars = [
{ name : "CUSTOM_ENV_VAR", value : "100" },
]
container_secrets = [
{ name : "CUSTOM_SECRET", valueFrom : aws_ssm_parameter.custom_secret.arn },
]
aws_services_security_group_id = data.aws_security_groups.aws_services.ids[0]

}

module "monitoring" {
Expand Down
33 changes: 25 additions & 8 deletions infra/modules/service/access-control.tf
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,31 @@ data "aws_iam_policy_document" "task_executor" {
}

# Allow ECS to download images.
statement {
sid = "ECRPullAccess"
actions = [
"ecr:BatchCheckLayerAvailability",
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
]
resources = [data.aws_ecr_repository.app.arn]
dynamic "statement" {
for_each = var.external_image_url == "" ? [true] : []
content {
sid = "ECRPullAccess"
actions = [
"ecr:BatchCheckLayerAvailability",
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
]
resources = [data.aws_ecr_repository.app[0].arn]
}
}

# Allow ECS to access Parameter Store for specific resources
# But only include the statement if var.container_secrets is not empty
# Strip any non-alphanumeric values from the secret name
dynamic "statement" {
for_each = var.container_secrets
content {
sid = "SSMAccess${replace(statement.value.name, "/[^0-9A-Za-z]/", "")}"
actions = [
"ssm:GetParameters",
]
resources = [statement.value.valueFrom]
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions infra/modules/service/load-balancer.tf
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,13 @@ resource "aws_lb_target_group" "app_tg" {
deregistration_delay = "30"

health_check {
path = "/health"
path = "/${local.healthcheck_path}"
port = var.container_port
healthy_threshold = 2
unhealthy_threshold = 10
interval = 30
timeout = 29
matcher = "200-299"
matcher = var.healthcheck_matcher
}

lifecycle {
Expand Down
34 changes: 22 additions & 12 deletions infra/modules/service/main.tf
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
data "aws_ecr_repository" "app" {
name = var.image_repository_name
count = var.external_image_url == "" ? 1 : 0
name = var.image_repository_name
}

locals {
Expand All @@ -10,20 +11,25 @@ locals {
log_group_name = "service/${var.service_name}"
log_stream_prefix = var.service_name
task_executor_role_name = "${var.service_name}-task-executor"
image_url = "${data.aws_ecr_repository.app.repository_url}:${var.image_tag}"
image_url = var.external_image_url != "" ? "${var.external_image_url}:${var.image_tag}" : "${data.aws_ecr_repository.app[0].repository_url}:${var.image_tag}"
healthcheck_path = trimprefix(var.healthcheck_path, "/")
read_only = var.container_read_only

base_environment_variables = [
{ name : "PORT", value : tostring(var.container_port) },
{ name : "AWS_REGION", value : data.aws_region.current.name },
]

db_environment_variables = var.db_vars == null ? [] : [
{ name : "DB_HOST", value : var.db_vars.connection_info.host },
{ name : "DB_PORT", value : var.db_vars.connection_info.port },
{ name : "DB_USER", value : var.db_vars.connection_info.user },
{ name : "DB_NAME", value : var.db_vars.connection_info.db_name },
{ name : "DB_SCHEMA", value : var.db_vars.connection_info.schema_name },
]
environment_variables = concat(local.base_environment_variables, local.db_environment_variables)

environment_variables = concat(local.base_environment_variables, local.db_environment_variables, var.container_env_vars)
container_secrets = var.container_secrets
}

#-------------------
Expand Down Expand Up @@ -71,20 +77,23 @@ resource "aws_ecs_task_definition" "app" {
cpu = var.cpu,
networkMode = "awsvpc",
essential = true,
readonlyRootFilesystem = true,
readonlyRootFilesystem = local.read_only,

# Need to define all parameters in the healthCheck block even if we want
# to use AWS's defaults, otherwise the terraform plan will show a diff
# that will force a replacement of the task definition
healthCheck = {
interval = 30,
retries = 3,
timeout = 5,
command = ["CMD-SHELL",
"wget --no-verbose --tries=1 --spider http://localhost:${var.container_port}/health || exit 1"
]
},
healthCheck = var.enable_container_healthcheck ? {
interval = 30,
retries = 3,
timeout = 5,
startPeriod = var.healthcheck_start_period,
command = [
"CMD-SHELL",
var.healthcheck_type == "curl" ? "curl --fail http://localhost:${var.container_port}/${local.healthcheck_path} || exit 1" : "wget --no-verbose --tries=1 --spider http://localhost:${var.container_port}/${local.healthcheck_path} || exit 1",
],
} : null,
environment = local.environment_variables,
secrets = local.container_secrets,
portMappings = [
{
containerPort = var.container_port,
Expand Down Expand Up @@ -124,3 +133,4 @@ resource "aws_ecs_cluster" "cluster" {
value = "enabled"
}
}

10 changes: 10 additions & 0 deletions infra/modules/service/networking.tf
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,13 @@ resource "aws_security_group" "app" {
cidr_blocks = ["0.0.0.0/0"]
}
}

resource "aws_vpc_security_group_ingress_rule" "vpc_endpoints_ingress_from_app" {
security_group_id = var.aws_services_security_group_id
description = "Allow inbound requests to VPC endpoints from application ${var.service_name}"

from_port = 443
to_port = 443
ip_protocol = "tcp"
referenced_security_group_id = aws_security_group.app.id
}
68 changes: 68 additions & 0 deletions infra/modules/service/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ variable "image_repository_name" {
description = "The name of the container image repository"
}

variable "external_image_url" {
type = string
description = "A non-AWS container image repository. If this is not empty, this takes precedence over image_repository_name"
default = ""
}

variable "desired_instance_count" {
type = number
description = "Number of instances of the task definition to place and keep running."
Expand Down Expand Up @@ -67,3 +73,65 @@ variable "db_vars" {
})
default = null
}

variable "container_env_vars" {
type = list(map(string))
description = "Additional environment variables to pass to the container definition"
default = []
}

variable "container_secrets" {
type = list(map(string))
description = "AWS secrets to pass to the container definition"
default = []
}

variable "aws_services_security_group_id" {
type = string
description = "Security group ID for VPC endpoints that access AWS Services"
}


variable "container_read_only" {
type = bool
description = "Whether the container root filesystem should be read-only"
default = true
}

#-------------------
# Healthcheck
#-------------------

variable "healthcheck_path" {
type = string
description = "The path to the application healthcheck"
default = "/health"
}

variable "healthcheck_type" {
type = string
description = "Whether to configure a curl or wget healthcheck. curl is more common. use wget for alpine-based images"
default = "wget"
validation {
condition = contains(["curl", "wget"], var.healthcheck_type)
error_message = "choose either: curl or wget"
}
}

variable "healthcheck_matcher" {
type = string
description = "The response codes that indicate healthy to the ALB"
default = "200-299"
}

variable "healthcheck_start_period" {
type = number
description = "The optional grace period to provide containers time to bootstrap in before failed health checks count towards the maximum number of retries"
default = 0
}

variable "enable_container_healthcheck" {
type = bool
description = "The ALB healthcheck is mandatory, but the container healthcheck can be disabled if desired"
default = true
}