Categorygithub.com/launchbynttdata/tf-aws-module_collection-ecs_appmesh_ingress
module
0.0.0-20240918160804-d4aeecd41e23
Repository: https://github.com/launchbynttdata/tf-aws-module_collection-ecs_appmesh_ingress.git
Documentation: pkg.go.dev

# README

tf-aws-module_collection-ecs_appmesh_ingress

License License: CC BY-NC-ND 4.0

Overview

This terraform module creates an ingress route into the ECS Cluster using App Mesh through an Application Load Balancer and a Virtual Gateway.

This module provisions an Application Load Balancer (ALB) with a HTTPs listener, listening on port 443. All incoming traffic to this App Mesh are routed through this load balancer. The ALB routes traffic to the Virtual Gateway through the envoy proxy running as an ECS Service. Once the traffic reaches the Virtual Gateway listener, there can be multiple gateway routes configured at the Virtual gateway to route the traffic to respective backends (ECS Service) through the configured Virtual Services that uses CloudMap service discovery to discover the ECS task instances. This module just configures a single gateway route /health for the ALB to perform health checks on. It is a single HTTP server configured to return 200 OK.

The following AWS resources are provisioned in this module

  • Application Load Balancer
    • Listeners
    • Target Group
    • Security Group
  • ACM Certificate for Load Balancer
  • Public DNS record for Load Balancer. Optionally, creates a vanity URL (CName record)
  • Virtual Gateway
    • ECS Service
    • Task Definition
    • Security Group
    • IAM roles for Task and Task Execution
  • Health Check ECS application
    • ECS Service
    • Task Definition
    • Virtual Service
    • Virtual Node
    • Service Discovery Service
    • Private Certificate for Service Discovery Endpoint
  • Virtual Gateway route for the above Health Check service

Below are some important considerations for the App Mesh Ingress configuration on ECS cluster

  • All ingress traffic must pass through the Virtual Gateway
  • In order for the ALB health checks to not fail for a deployed Virtual Gateway in ECS (without any actual apps deployed), it must have a backend configured for health-checks. In this module, we deploy a simple HTTP server for health checks of Virtual gateway.
  • The backend services that do not need route from outside the Mesh may not have a route configured in Virtual Gateway.
    • These services can be accessed by other services inside the Mesh using the Service Discovery endpoints (configured using CloudMap)
    • The security group of these services must allow ingress traffic on the application port for these services
  • All the backend services that need a route from outside the Mesh must have a gateway route configured in the Virtual gateway
    • Virtual gateway supports 2 kinds of routing
      • Path based routing
      • Matching Hostname header based routing
  • ALB is configured with HTTPs listener. In order to provision certificates for TLS, a custom domain name must be configured for the ALB. TLS certificates cannot be provisioned for the default AWS URL.
    • We use an internal ALB in this project
    • An HTTP listener is also configured to redirect all traffic at port 80 to 443.
    • An A record is created in the public hosted zone in the AWS account
    • A certificate is provisioned for the A record as the first domain name in the ACM certificate
      • Additional SANs can be added for other CNAME records pointing to the ALB. This can be useful for hostname header based routing.
  • ALB Target Group must be configured for HTTPS at port 443. The health check port should ideally be 443 but can be any other port
    • The health check port must match the application port of the envoy proxy container in the task definition for Virtual Gateway ECS Service. The Virtual Gateway listener port and the health check port should also be the same.
    • The health check path can be anything, defaulting to /. If anything else is selected like /health, the corresponding gateway route must be configured to have the same match_path_prefix
  • ALB Security group should allow ingress traffic on port 443 and optionally 80 (will be auto redirected by HTTP listener)
  • Virtual Gateway Security Group configured on the ECS Service allows ingress traffic only from the ALB Security Group
    • Optionally, it can allow ingress traffic on 9901 port to check stats on the App Mesh.

Usage

Several sample variable files tfvars are available in the root directory which can be used to test this module. User needs to follow the below steps to execute this module

  1. Update the tfvars file to manually enter values for all fields marked within <> to make the variable file usable
  2. Create a file provider.tf with the below contents
     provider "aws" {
       profile = "<profile_name>"
       region  = "<region_name>"
     }
    
    If using SSO, make sure you are logged in aws sso login --profile <profile_name>
  3. Make sure terraform binary is installed on your local. Use command type terraform to find the installation location. If you are using asdf, you can run asfd install and it will install the correct terraform version for you. .tool-version contains all the dependencies.
  4. Run the terraform to provision infrastructure on AWS
    # Initialize
    terraform init
    # Plan
    terraform plan -var-file example.tfvars
    # Apply (this is create the actual infrastructure)
    terraform apply -var-file example.tfvars -auto-approve
    

Known Issues and Guidelines

  1. The ALB is currently set to only work with TLS listener in this module. Unable to make it work for both HTTP and TLS listener in if/else. The parent module is not supporting
  2. If tls_enforce=true in this module, then ensure that any applications deployed to this cluster that uses this ingress must also have tls_enforce=true, else those modules will fail to talk to the ingress
  3. Sometimes the terraform destroy is unable to destroy all resources and fails with error shown below. But, this is mitigated by running terraform destroy again.
│ Error: deleting App Mesh Virtual Router (a0602f30-46ea-49ce-b545-a9255087d0dd): operation error App Mesh: DeleteVirtualRouter, https response error StatusCode: 409, RequestID: 0f756a18-5adc-43a4-9622-7590885de530, ResourceInUseException: VirtualRouter with name xxxx cannot be deleted because it is a VirtualService provider

Pre-Commit hooks

.pre-commit-config.yaml file defines certain pre-commit hooks that are relevant to terraform, golang and common linting tasks. There are no custom hooks added.

commitlint hook enforces commit message in certain format. The commit contains the following structural elements, to communicate intent to the consumers of your commit messages:

  • fix: a commit of the type fix patches a bug in your codebase (this correlates with PATCH in Semantic Versioning).
  • feat: a commit of the type feat introduces a new feature to the codebase (this correlates with MINOR in Semantic Versioning).
  • BREAKING CHANGE: a commit that has a footer BREAKING CHANGE:, or appends a ! after the type/scope, introduces a breaking API change (correlating with MAJOR in Semantic Versioning). A BREAKING CHANGE can be part of commits of any type. footers other than BREAKING CHANGE: may be provided and follow a convention similar to git trailer format.
  • build: a commit of the type build adds changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
  • chore: a commit of the type chore adds changes that don't modify src or test files
  • ci: a commit of the type ci adds changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
  • docs: a commit of the type docs adds documentation only changes
  • perf: a commit of the type perf adds code change that improves performance
  • refactor: a commit of the type refactor adds code change that neither fixes a bug nor adds a feature
  • revert: a commit of the type revert reverts a previous commit
  • style: a commit of the type style adds code changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
  • test: a commit of the type test adds missing tests or correcting existing tests

Base configuration used for this project is commitlint-config-conventional (based on the Angular convention)

If you are a developer using vscode, this plugin may be helpful.

detect-secrets-hook prevents new secrets from being introduced into the baseline. TODO: INSERT DOC LINK ABOUT HOOKS

In order for pre-commit hooks to work properly

  • You need to have the pre-commit package manager installed. Here are the installation instructions.
  • pre-commit would install all the hooks when commit message is added by default except for commitlint hook. commitlint hook would need to be installed manually using the command below
pre-commit install --hook-type commit-msg

To test the resource group module locally

  1. For development/enhancements to this module locally, you'll need to install all of its components. This is controlled by the configure target in the project's Makefile. Before you can run configure, familiarize yourself with the variables in the Makefile and ensure they're pointing to the right places.
make configure

This adds in several files and directories that are ignored by git. They expose many new Make targets.

  1. The first target you care about is env. This is the common interface for setting up environment variables. The values of the environment variables will be used to authenticate with cloud provider from local development workstation.

make configure command will bring down aws_env.sh file on local workstation. Developer would need to modify this file, replace the environment variable values with relevant values.

These environment variables are used by terratest integration suit.

Then run this make target to set the environment variables on developer workstation.

make env
  1. The first target you care about is check.

Pre-requisites Before running this target it is important to ensure that, developer has created files mentioned below on local workstation under root directory of git repository that contains code for primitives/segments. Note that these files are aws specific. If primitive/segment under development uses any other cloud provider than AWS, this section may not be relevant.

  • A file named provider.tf with contents below
provider "aws" {
  profile = "<profile_name>"
  region  = "<region_name>"
}
  • A file named terraform.tfvars which contains key value pair of variables used.

Note that since these files are added in gitignore they would not be checked in into primitive/segment's git repo.

After creating these files, for running tests associated with the primitive/segment, run

make check

If make check target is successful, developer is good to commit the code to primitive/segment's git repo.

make check target

  • runs terraform commands to lint,validate and plan terraform code.
  • runs conftests. conftests make sure policy checks are successful.
  • runs terratest. This is integration test suit.
  • runs opa tests

Know Issues

Currently, the encrypt at transit is not supported in terraform. There is an open issue for this logged with Hashicorp - https://github.com/hashicorp/terraform-provider-aws/pull/26987

Requirements

NameVersion
terraform~> 1.0
aws~> 5.0

Providers

NameVersion
aws5.67.0

Modules

NameSourceVersion
resource_namesterraform.registry.launch.nttdata.com/module_library/resource_name/launch~> 2.0
sg_albterraform-aws-modules/security-group/aws~> 4.17.1
alb_logs_s3terraform-aws-modules/s3-bucket/aws~> 3.8.2
albterraform-aws-modules/alb/aws~> 8.7
alb_dns_recordsterraform.registry.launch.nttdata.com/module_primitive/dns_record/aws~> 1.0
acmterraform-aws-modules/acm/aws~> 4.3.2
sdsterraform.registry.launch.nttdata.com/module_primitive/service_discovery_service/aws~> 1.0
private_certsterraform.registry.launch.nttdata.com/module_primitive/acm_private_cert/aws~> 1.0
virtual_gatewayterraform.registry.launch.nttdata.com/module_primitive/virtual_gateway/aws~> 1.0
ecs_task_execution_policycloudposse/iam-policy/aws~> 2.0.1
ecs_task_policycloudposse/iam-policy/aws~> 2.0.1
virtual_gateway_container_definitiongit::https://github.com/cloudposse/terraform-aws-ecs-container-definition.gittags/0.61.1
sg_ecs_service_vgwterraform-aws-modules/security-group/aws~> 4.17.1
virtual_gateway_ecs_servicecloudposse/ecs-alb-service-task/aws~> 0.67.1
ecs_app_heart_beatterraform.registry.launch.nttdata.com/module_collection/ecs_appmesh_app/aws~> 1.0
additional_cnamesterraform.registry.launch.nttdata.com/module_primitive/dns_record/aws~> 1.0

Resources

NameType
aws_route53_zone.dns_zonedata source

Inputs

NameDescriptionTypeDefaultRequired
logical_product_family(Required) Name of the product family for which the resource is created.
Example: org_name, department_name.
string"launch"no
logical_product_service(Required) Name of the product service for which the resource is created.
For example, backend, frontend, middleware etc.
string"backend"no
class_env(Required) Environment where resource is going to be deployed. For example. dev, qa, uatstring"dev"no
instance_envNumber that represents the instance of the environment.number0no
instance_resourceNumber that represents the instance of the resource.number0no
regionAWS Region in which the infra needs to be provisionedstring"us-east-2"no
resource_names_mapA map of key to resource_name that will be used by tf-launch-module_library-resource_name to generate resource names
map(object(
{
name = string
max_length = optional(number, 60)
}
))
{
"acm": {
"max_length": 60,
"name": "acm"
},
"alb": {
"max_length": 32,
"name": "alb"
},
"alb_sg": {
"max_length": 60,
"name": "albsg"
},
"alb_tg": {
"max_length": 60,
"name": "albtg"
},
"health_check_app_ecs_sg": {
"max_length": 60,
"name": "hcappsg"
},
"health_check_ecs_app": {
"max_length": 60,
"name": "hcsvc"
},
"health_check_ecs_td": {
"max_length": 60,
"name": "hctd"
},
"s3_logs": {
"max_length": 60,
"name": "alblogs"
},
"sds_vg": {
"max_length": 60,
"name": "sdsvg"
},
"task_exec_policy": {
"max_length": 60,
"name": "execplcy"
},
"task_policy": {
"max_length": 60,
"name": "taskplcy"
},
"vgw_ecs_app": {
"max_length": 60,
"name": "vgwsvc"
},
"vgw_ecs_sg": {
"max_length": 60,
"name": "vgwsg"
},
"vgw_ecs_td": {
"max_length": 60,
"name": "vgwtd"
},
"virtual_gateway": {
"max_length": 60,
"name": "vgw"
}
}
no
vpc_id(Required) The VPC ID of the VPC where infrastructure will be provisionedstringn/ayes
private_subnets(Required) List of private subnets. ECS services provisioned in private subnets would need NAT gateway to access internet.
Internal ALBs must be provisioned in private subnets
list(string)n/ayes
public_subnetsList of public subnets. ECS services provisioned in public subnets can access internet directly. External ALBs must be
provisioned in public subnets
list(string)[]no
subnet_mappingA list of subnet mapping blocks describing subnets to attach to network load balancer. Required if load_balancer_type=network
More details on the various attributes of subnet_mapping can be found at
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb#subnet_mapping
When subnet_mapping is specified the private/public subnets variable is ignored.
list(map(string))[]no
vgw_security_groupSecurity group for the Virtual Gateway ECS application. By default, it allows traffic from ALB on the app_port

More details on how to set the below fields can be found at
https://github.com/terraform-aws-modules/terraform-aws-security-group/blob/master/rules.tf
object({
ingress_rules = optional(list(string))
ingress_cidr_blocks = optional(list(string))
ingress_with_cidr_blocks = optional(list(map(string)))
egress_rules = optional(list(string))
egress_cidr_blocks = optional(list(string))
egress_with_cidr_blocks = optional(list(map(string)))
ingress_with_sg = optional(list(map(string)))
egress_with_sg = optional(list(map(string)))
})
nullno
ecs_cluster_arn(Required) ARN of the ECS Cluster where the ingress service will be createdstringn/ayes
app_mesh_id(Required) ID of the App Mesh where virtual Gateway is to be created. ID and Name are the same for App Meshstringn/ayes
namespace_name(Required) Name of the Cloud Map namespace to be used for Service Discoverystringn/ayes
namespace_id(Required) ID of the Cloud Map namespace to be used for Service Discoverystringn/ayes
load_balancer_typeThe type of the load balancer. Default is 'application'. Can be either application or network.string"application"no
is_internalWhether this load balancer is internal or public facing. If is_internal=false, then var.public_subnets or subnet_mapping must be specifiedbooltrueno
alb_sg(Required) Security Group for the ALB. https://github.com/terraform-aws-modules/terraform-aws-security-group/blob/master/rules.tf
object({
description = optional(string)
ingress_rules = optional(list(string))
ingress_cidr_blocks = optional(list(string))
egress_rules = optional(list(string))
egress_cidr_blocks = optional(list(string))
ingress_with_cidr_blocks = optional(list(map(string)))
egress_with_cidr_blocks = optional(list(map(string)))
})
n/ayes
target_groups(Required) List of target groups for the ALB"
target_type can be ip, instance
health_check must be set for backend_protocol=HTTPS.
Valid health_check attributes are healthy_threshold, unhealthy_threshold, path, port, protocol
- protocol must be HTTP, HTTPS etc.
list(object({
# Need to use name_prefix instead of name as the lifecycle property create_before_destroy is set
name_prefix = optional(string, "albtg-")
backend_protocol = optional(string)
backend_port = optional(number)
target_type = optional(string)
health_check = optional(map(string), {})
}))
n/ayes
subject_alternate_namesAdditional domain names to be added to the certificate created for ALB. Domain names must be FQDN.list(string)[]no
dns_zone_nameName of the Route53 DNS Zone where custom DNS records will be created. Required if use_https_listeners=true. var.private_zone
must also be specified if this variable is not empty.

By default, an A record will be created for the ALB with the name as generated by module.resource_names["alb"].standard
In case, additional cnames are required, they can be specified in the additional_cnames variable
stringn/ayes
private_zoneWhether the dns_zone_name provided above is a private or public hosted zone. Required if dns_zone_name is not empty.
private_zone=true means the hosted zone is private and false means it is public.
string""no
additional_cnamesAdditional CNAME records to be created in the DNS zone pointing to the ALB. Must be FQDN in form of .<dns_zone_name>list(string)[]no
idle_timeoutThe time in seconds that the connection is allowed to be idle.number60no
alb_logs_bucket_idS3 bucket ID for ALB logsstring""no
alb_logs_bucket_prefixS3 bucket prefix for ALB logsstringnullno
use_https_listenersWhether to enable HTTPs in the ALBbooltrueno
listener_ssl_policy_defaultThe security policy if using HTTPS externally on the load balancer.
See.
string"ELBSecurityPolicy-TLS13-1-2-2021-06"no
private_ca_arnARN of the Private CA. This is used to sign private certificates used in App Mesh. Required when tls_enforce=truestring""no
tls_enforceWhether to enforce TLS in App Mesh Virtual Gateway and servicesbooltrueno
vgw_logs_text_formatThe text format.stringnullno
vgw_tls_modeThe mode for the listener’s Transport Layer Security (TLS) configuration. Must be one of DISABLED, PERMISSIVE, STRICT.string"DISABLED"no
vgw_tls_portsIf you specify a listener port other than 443, you must specify this field.list(number)[]no
vgw_health_check_pathThe destination path for the health check request.string"/"no
vgw_health_check_protocolThe protocol for the health check request. Must be one of [http http2 grpc].string"http"no
vgw_listener_portThe port mapping information for the listener.numbern/ayes
vgw_listener_protocolThe protocol for the port mapping. Must be one of [http http2 grpc].string"http"no
ecs_exec_role_managed_policy_arnsA Map (ARNs) of AWS managed policies to be attached to the ECS Task Exec role.map(string){}no
ecs_role_managed_policy_arnsA Map (ARNs) of AWS managed policies to be attached to the ECS Task role.map(string){}no
ecs_exec_role_custom_policy_jsonCustom policy to attach to ecs task execution role. Document must be valid json.string""no
ecs_role_custom_policy_jsonCustom policy to attach to ecs task role. Document must be valid json.string""no
envoy_proxy_imageOptional docker image of the envoy proxy in the format <docker_image>:<tag>
Default is 840364872350.dkr.ecr.us-east-2.amazonaws.com/aws-appmesh-envoy:v1.29.6.0-prod
string""no
ignore_changes_task_definitionLifecycle ignore policy for task definition. If true, terraform won't detect changes when task_definition is changed outside of terraformbooltrueno
assign_public_ipIf true, public IP will be assigned to this service task, else private IPboolfalseno
ignore_changes_desired_countLifecycle ignore policy for desired_count. If true, terraform won't detect changes when desired_count is changed outside of terraformbooltrueno
task_cpuAmount of CPU to be allocated to the tasknumber512no
task_memoryAmount of Memory to be allocated to the tasknumber1024no
health_check_grace_period_secondsSeconds to ignore failing load balancer health checks on newly instantiated tasks to prevent premature shutdown, up to 7200. Only valid for services configured to use load balancersnumber0no
deployment_minimum_healthy_percentThe lower limit (as a percentage of desired_count) of the number of tasks that must remain running and healthy in a service during a deploymentnumber100no
deployment_maximum_percentThe upper limit of the number of tasks (as a percentage of desired_count) that can be running in a service during a deploymentnumber200no
desired_countThe number of instances of the task definition to place and keep runningnumber1no
deployment_controller_typeType of deployment controller. Valid values are CODE_DEPLOY and ECSstring"ECS"no
wait_for_steady_stateIf true, it will wait for the service to reach a steady state (like aws ecs wait services-stable) before continuingboolfalseno
redeploy_on_applyRedeploys the service everytime a terraform apply is executed. force_new_deployment should also be true for this flag to workboolfalseno
force_new_deploymentEnable to force a new task deployment of the service when terraform apply is executed.boolfalseno
app_task_cpuAmount of CPU to be allocated to the health check app tasknumber512no
app_task_memoryAmount of Memory to be allocated to the health check app tasknumber1024no
app_desired_countThe number of instances of the health check task definition to place and keep runningnumber1no
app_image_tagDocker image for the heartBeat application, in the format <docker_image><docker_tag>stringn/ayes
app_portThe port at which the health check application is runningnumbern/ayes
app_environmentEnvironment variables to be injected into the heart beat container. Sometimes helpful to set the non standard portmap(string){}no
app_security_groupSecurity group for the health check ECS application. Need to open ports if one wants to access the heart-beat application manually.
object({
ingress_rules = optional(list(string))
ingress_cidr_blocks = optional(list(string))
ingress_with_cidr_blocks = optional(list(map(string)))
egress_rules = optional(list(string))
egress_cidr_blocks = optional(list(string))
egress_with_cidr_blocks = optional(list(map(string)))
})
nullno
match_path_prefixVirtual gateway route path match. Must be same as the ALB health check pathstring"/"no
tagsA map of custom tags to be associated with the resourcesmap(string){}no

Outputs

NameDescription
resource_namesA map of resource_name_types to generated resource names used in this module
app_sg_idThe ID of the VPC Endpoint Security Group
alb_dnsAWS provided DNS record of the ALB
alb_arnThe ID and ARN of the application load balancer created by the module
alb_idThe ID and ARN of the application load balancer created by the module
alb_sg_idID of the ALB Security Group
alb_https_listener_arnsARNs of the HTTPS Listeners attached to the ALB
alb_http_listener_arnsARNs of the HTTP Listeners attached to the ALB
dns_zone_idZone ID of the hosted zone for ALB records provided as input.
dns_zone_nameName of the Route53 DNS Zone where custom DNS records will be created. Required if use_https_listeners=true
alb_dns_recordsCustom DNS record for the ALB
alb_additional_dns_namesAdditional DNS records for the ALB
private_ca_arnARN of the Private CA. This is used to sign private certificates used in App Mesh. Required when TLS is enabled in App Mesh
alb_cert_arnARN of the certificate provisioned for ALB by ACM
virtual_gateway_cert_arnARN of the certificate provisioned for the virtual gateway when var.tls_enforce is true
virtual_gateway_arnARN of the Virtual Gateway
virtual_gateway_task_definition_nameRevision of the Virtual Gateway ECS app task definition.
virtual_gateway_task_definition_versionRevision of the Virtual Gateway ECS app task definition.
virtual_gateway_nameName of the Virtual Gateway
tls_enforceWhether TLS is enforced on the Virtual Gateway. If true, all the Virtual Nodes should also enable TLS
namespace_idID of the Cloud Map namespace to be used for Service Discovery
namespace_nameName of the Cloud Map namespace to be used for Service Discovery
heartbeat_app_task_definition_nameTask Definition Version of the HeartBeat application
heartbeat_app_task_definition_versionTask Definition Version of the HeartBeat application

# Packages

No description provided by the author