/terraform-aws-ecs-fargate-codepipeline

ecs-fargate-codepipeline creates an end to end fargate cluster with a single task (but can be multiple containers in the task), a CodeDeploy application deployment configuration, a CodePipeline to wrap around it, and all relevant iam roles etc.

Primary LanguageHCLApache License 2.0Apache-2.0

Contact Us | Stratusphere FinOps | StratusGrid Home | Blog

terraform-aws-ecs-fargate-codepipeline

GitHub: StratusGrid/terraform-aws-ecs-fargate-codepipeline

This module creates an end-to-end fargate cluster with a single task (but can be multiple containers in the task), a CodeDeploy application deployment configuration, a CodePipeline to wrap around it, and all relevant iam roles etc.

NOTE:

If you get a Cycle: Error: on destroy, go remove the LB target group that is getting changes first.

terraform apply -target aws_lb_target_group.data_hub_web_http

If you get errors about the artifact.zip files, you must create the resources which get pulled into the file first, by targeting the iam roles and target groups.

terraform apply -target aws_lb_target group.<blue_target_group> -target aws_lb_target_group.<green_target_group>
terraform apply -target module.<ecs_iam_role1> -target module.<ecs_iam_role2>

Example Usage:

Create a cluster with a single service, mapped to a single task, which has a single container:

module "ecs_app_iam_role" {
  source  = "StratusGrid/ecs-iam-role-builder/aws"
  version = "~> 1.0"

  cloudwatch_logs_policy     = true
  cloudwatch_logs_group_path = module.ecs_fargate_app.log_group_path

  ecr_policy = true
  ecr_repos  = [
    aws_ecr_repository.app.arn
  ]

  custom_policy_jsons = [data.aws_iam_policy_document.bucket_access.json, data.aws_iam_policy_document.ssm_parameters.json]
  
  role_name  = "${var.name_prefix}-app${local.name_suffix}"
  input_tags = merge(local.common_tags, {})
}


resource "aws_service_discovery_private_dns_namespace" "discovery_namespace" {
  name        = "discovery.${var.env_name}.mydomain.com"
  description = "My services ${var.env_name} discovery namespace"
  vpc         = data.aws_vpc.vpc_microservices.id

  tags = merge(local.common_tags, {})
}

resource "aws_service_discovery_service" "discovery_service" {
  name = "myapp"

  dns_config {
    namespace_id = aws_service_discovery_private_dns_namespace.discovery_namespace.id

    dns_records {
      ttl  = 10
      type = "A"
    }
  }

  tags = merge(local.common_tags, {})
}

Valid combinations of cpu/memory in task definition is found here

resource "aws_efs_file_system" "my_efs_vol" {
  creation_token = "my-efs-vol"

  tags = {
    Name = "MyEFSVol"
  }
}

module "ecs_fargate_app" {
  source  = "StratusGrid/ecs-fargate-codepipeline/aws"
  # StratusGrid recommends pinning every module to a specific version
  version = "x.x.x"
  # source  = "github.com/StratusGrid/terraform-aws-ecs-fargate-codepipeline"

  ecs_cluster_name   = "${var.name_prefix}-app${local.name_suffix}"
  log_retention_days = 30
  vpc_id             = data.aws_vpc.vpc_microservices.id

  input_tags = merge(local.common_tags, {})

  codebuild_container_duplicator_name = aws_codebuild_project.codebuild_container_duplicator.name

  ecs_services = {
    service_name = local.service_name
  }
}

# example of the locals file 
locals {
  service_name = {
    service_name           = "MyService"
    platform_version       = "1.4.0"
    desired_count          = 3
    security_groups        = ["sg-02f8f5de8655a798f","sg-031232157553fbec9"]
    subnets                = ["subnet-0735beb51e4293b3e","subnet-0fdc8f5dc6d101035"]
    assign_public_ip       = false
    propagate_tags         = "TASK_DEFINITION"
    log_group_path         = "/ecs/cluster_name/service_name"
    enable_execute_command = true

    service_registries = {}

    # volume configs
    efs_volume = {
      name           = "MyEFSVol"
      file_system_id = aws_efs_file_system.my_efs_vol.id
      root_directory = "/"
      transit_encryption = null
      transit_encryption_port = null
    }

    #NOTE: ALBs are not created by the module.
    health_check_grace_period_seconds = 600
    lb_listener_prod_arn              = "arn:aws:elasticloadbalancing:us-east-1:123456789876:listener/app/my_prd_listener/ed9abd4ebab2925a/9176f3bfebd6457f"
    lb_listener_test_arn              = "arn:aws:elasticloadbalancing:us-east-1:123456789876:listener/app/my_test_listener/j6d77m8jttgd4g853/mqo39m4lcj3lfk3"
    lb_target_group_blue_arn          = "arn:aws:elasticloadbalancing:us-east-1:123456789876:targetgroup/my_blue_target_group/f1fec68432dd54c0"
    lb_target_group_blue_name         = my_blue_target_group
    lb_target_group_green_name        = my_green_target_group
    lb_container_name                 = "containername" # has to match name in container definition within task_definition
    lb_container_port                 = 8080       # has to match port in container definition within task_definition

    codedeploy_role_arn              = "arn:aws:iam::123456789876:role/my_codedeploy_role"
    codedeploy_termination_wait_time = "5"
    codebuild_auto_rollback_enabled  = true
    codebuild_auto_rollback_events   = ["DEPLOYMENT_FAILURE"]

    codepipeline_role_arn        = "arn:aws:iam::123456789876:role/my_codepipeline_role"
    codepipeline_source_bucket_id  = "my_codepipeline_source_bucket_name"
    codepipeline_source_object_key = "deployment/ecs/${var.application_name}-artifacts.zip"

    container_repo_name         = "my_ecr_repository"
    container_target_tag        = "latest" #
    container_duplicate_targets = "${var.name_prefix}-${var.application_name}-ecr-repo-prd"

    deployment_manual_approval  = local.ecs_deployment_approval[var.env_name] // boolean for environment to deploy into (prd)
    duplication_manual_approval = local.ecs_duplication_approval[var.env_name] // boolen for environment to duplicate from (dev)

    use_custom_capacity_provider_strategy = true //if false, custom_capacity_provider_strategy needs to be an emtpy block = {}
    custom_capacity_provider_strategy = {
      primary_capacity_provider_base      = 1
      primary_capacity_provider           = "FARGATE"
      primary_capacity_provider_weight    = 10
      secondary_capacity_provider         = "FARGATE_SPOT"
      secondary_capacity_provider_weight  = 1
    }

    taskdef_family             = "MyService"
    taskdef_execution_role_arn = "arn:aws:iam::123456789876:role/my_taskdef_role"
    taskdef_task_role_arn      = "arn:aws:iam::123456789876:role/my_taskdef_role"
    taskdef_network_mode       = "awsvpc"
    taskdef_requires_compatibilities = [
      "FARGATE"
    ]
    taskdef_cpu    = 2048
    taskdef_memory = 4096

    taskdef_container_definitions = <<-TASKDEF
      [
        {
          "name": "containername",
          "image": "IMAGE1_NAME",
          "portMappings": [
            {
              "hostPort": 8080,
              "protocol": "tcp",
              "containerPort": 8080
            }
          ]
        }
      ]
    TASKDEF

    codepipeline_container_definitions = <<-CONTAINERDEF
    [
      {
        "name": "containername",
        "image": "<IMAGE1_NAME>",
        "essential": true,
        "logConfiguration": {
          "logDriver": "awslogs",
          "secretOptions": null,
          "options": {
            "awslogs-group": "log-group",
            "awslogs-region": "us-east-1",
            "awslogs-stream-prefix": "ecs"
          }
        },
        "portMappings": [
          {
            "hostPort": 8080,
            "protocol": "tcp",
            "containerPort": 8080
          }
        ],
        "mountPoints": [
          {
            "sourceVolume": "MyEFSVol",
            "containerPath": "/container-mountpoint/",
            "readOnly": false
          }
        ],
        "environment": [
          { "name": "ENVIRONMENT", "value": "${var.env_name == "prd" ? "production" : "development"}" }
        ]
      }
    ]
    CONTAINERDEF

    postdeploy_codebuild_project_name = ["My_PostDeploy_Project"]

  } # end of service definition
}

Resources

Name Type
aws_cloudwatch_event_rule.this resource
aws_cloudwatch_event_target.this resource
aws_cloudwatch_log_group.this resource
aws_codedeploy_app.this resource
aws_codedeploy_deployment_group.this resource
aws_codepipeline.this resource
aws_ecs_cluster.this resource
aws_ecs_service.this resource
aws_ecs_task_definition.this resource
aws_iam_role.this resource
aws_iam_role_policy.this resource
aws_s3_bucket_object.artifacts_s3 resource

Inputs

Name Description Type Default Required
codebuild_container_duplicator_name Optional variable to be provided when you are pushing containers to another repo after a successful code pipeline string "" no
ecs_cluster_name name to be used for ecs cluster and base log group string n/a yes
ecs_services List of Maps containing all settings which are configured per task definition.
map(object(
{
service_name = string
platform_version = string
desired_count = number
security_groups = list(string)
subnets = list(string)
assign_public_ip = bool
propagate_tags = string
log_group_path = string
enable_execute_command = bool

service_registries = map(string)

use_custom_capacity_provider_strategy = bool
custom_capacity_provider_strategy = map(string)

health_check_grace_period_seconds = number

lb_listener_prod_arn = string
lb_listener_test_arn = string
lb_target_group_blue_arn = string
lb_target_group_blue_name = string
lb_target_group_green_name = string
lb_container_name = string
lb_container_port = number

codedeploy_role_arn = string
codedeploy_termination_wait_time = number
codebuild_auto_rollback_enabled = bool
codebuild_auto_rollback_events = list(string)

codepipeline_role_arn = string
codepipeline_source_bucket_id = string
codepipeline_source_object_key = string

container_repo_name = string
container_target_tag = string
container_duplicate_targets = map(any) #This must have values for target_repo and target_account or an empty map
deployment_manual_approval = list(string)
duplication_manual_approval = list(string)

taskdef_family = string
taskdef_execution_role_arn = string
taskdef_task_role_arn = string
taskdef_network_mode = string
taskdef_requires_compatibilities = list(string)
taskdef_cpu = number
taskdef_memory = number

taskdef_container_definitions = string
codepipeline_container_definitions = string

predeploy_codebuild_project_name = list(string)
postdeploy_codebuild_project_name = list(string)

efs_volume = map(string)

# task_definition = string
}
))
n/a yes
env_name name of environment/stage, passed in from root module string n/a yes
input_tags Map of tags to apply to resources map(string)
{
"Developer": "StratusGrid",
"Provisioner": "Terraform"
}
no
log_retention_days Number of days to retain logs for. Configured on Log Group which all log streams are put under. number n/a yes

Outputs

Name Description
codedeploy_app_arns_map Map of ARNs of CodeDeploy app created by this module.
ecs_cluster_arn ARN of ECS cluster created by this module.
ecs_cluster_name ARN of ECS cluster created by this module.

Contributors

NOTE: Manual changes to the README will be overwritten when the documentation is updated. To update the documentation, run terraform-docs -c .config/.terraform-docs.yml .