├── ecs-with-codepipeline.png ├── var.tf ├── ecs-service.tf ├── ecs-task-def.tf ├── LICENSE ├── README.md ├── ecr.tf ├── alb.tf ├── ecs.tf ├── launch-instance.tf ├── code-deploy.tf ├── code-build.tf ├── vpc.tf └── code-pipeline.tf /ecs-with-codepipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnokoheat/ecs-with-codepipeline-example-by-terraform/HEAD/ecs-with-codepipeline.png -------------------------------------------------------------------------------- /var.tf: -------------------------------------------------------------------------------- 1 | #example : fill your information 2 | variable "region" { 3 | default = "us-east-1" 4 | } 5 | 6 | provider "aws" { 7 | access_key = "" 8 | secret_key = "" 9 | region = "${var.region}" 10 | } 11 | 12 | variable "ecs_key_pair_name" { 13 | default = "" 14 | } 15 | 16 | variable "aws_account_id" { 17 | default = "" 18 | } 19 | 20 | variable "service_name" { 21 | default = "demo-service" 22 | } 23 | 24 | variable "container_port" { 25 | default = "8080" 26 | } 27 | 28 | variable "memory_reserv" { 29 | default = 100 30 | } -------------------------------------------------------------------------------- /ecs-service.tf: -------------------------------------------------------------------------------- 1 | resource "aws_ecs_service" "this" { 2 | name = "${var.service_name}" 3 | task_definition = "${aws_ecs_task_definition.this.id}" 4 | cluster = "${aws_ecs_cluster.this.arn}" 5 | 6 | load_balancer { 7 | target_group_arn = "${aws_lb_target_group.this.0.arn}" 8 | container_name = "${var.service_name}" 9 | container_port = "${var.container_port}" 10 | } 11 | 12 | launch_type = "EC2" 13 | desired_count = 1 14 | deployment_maximum_percent = 200 15 | deployment_minimum_healthy_percent = 100 16 | 17 | deployment_controller { 18 | type = "CODE_DEPLOY" 19 | } 20 | 21 | depends_on = ["aws_lb_listener.this"] 22 | } 23 | -------------------------------------------------------------------------------- /ecs-task-def.tf: -------------------------------------------------------------------------------- 1 | resource "aws_ecs_task_definition" "this" { 2 | family = "${var.service_name}" 3 | execution_role_arn = "${aws_iam_role.execution_role.arn}" 4 | task_role_arn = "${aws_iam_role.task_role.arn}" 5 | network_mode = "bridge" 6 | requires_compatibilities = ["EC2"] 7 | container_definitions = <> /etc/ecs/ecs.config 39 | EOF 40 | } 41 | 42 | resource "aws_autoscaling_group" "this" { 43 | name = "${var.service_name}-ecs-autoscaling-group" 44 | max_size = 2 45 | min_size = 1 46 | desired_capacity = 1 47 | vpc_zone_identifier = "${aws_subnet.private.*.id}" 48 | launch_configuration = "${aws_launch_configuration.this.name}" 49 | health_check_type = "ELB" 50 | 51 | tag { 52 | key = "Name" 53 | value = "ECS-Instance-${var.service_name}-service" 54 | propagate_at_launch = true 55 | } 56 | } 57 | 58 | resource "aws_iam_role" "ecs-instance-role" { 59 | name = "${var.service_name}-ecs-instance-role" 60 | path = "/" 61 | assume_role_policy = "${data.aws_iam_policy_document.ecs-instance-policy.json}" 62 | } 63 | 64 | data "aws_iam_policy_document" "ecs-instance-policy" { 65 | statement { 66 | actions = ["sts:AssumeRole"] 67 | 68 | principals { 69 | type = "Service" 70 | identifiers = ["ec2.amazonaws.com"] 71 | } 72 | } 73 | } 74 | 75 | resource "aws_iam_role_policy_attachment" "ecs-instance-role-attachment" { 76 | role = "${aws_iam_role.ecs-instance-role.name}" 77 | policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" 78 | } 79 | 80 | resource "aws_iam_instance_profile" "ecs-instance-profile" { 81 | name = "${var.service_name}-ecs-instance-profile" 82 | path = "/" 83 | role = "${aws_iam_role.ecs-instance-role.id}" 84 | } 85 | -------------------------------------------------------------------------------- /code-deploy.tf: -------------------------------------------------------------------------------- 1 | data "aws_iam_policy_document" "assume_by_codedeploy" { 2 | statement { 3 | sid = "" 4 | effect = "Allow" 5 | actions = ["sts:AssumeRole"] 6 | 7 | principals { 8 | type = "Service" 9 | identifiers = ["codedeploy.amazonaws.com"] 10 | } 11 | } 12 | } 13 | 14 | resource "aws_iam_role" "codedeploy" { 15 | name = "${var.service_name}-codedeploy" 16 | assume_role_policy = "${data.aws_iam_policy_document.assume_by_codedeploy.json}" 17 | } 18 | 19 | data "aws_iam_policy_document" "codedeploy" { 20 | statement { 21 | sid = "AllowLoadBalancingAndECSModifications" 22 | effect = "Allow" 23 | 24 | actions = [ 25 | "ecs:CreateTaskSet", 26 | "ecs:DeleteTaskSet", 27 | "ecs:DescribeServices", 28 | "ecs:UpdateServicePrimaryTaskSet", 29 | "elasticloadbalancing:DescribeListeners", 30 | "elasticloadbalancing:DescribeRules", 31 | "elasticloadbalancing:DescribeTargetGroups", 32 | "elasticloadbalancing:ModifyListener", 33 | "elasticloadbalancing:ModifyRule", 34 | "lambda:InvokeFunction", 35 | "cloudwatch:DescribeAlarms", 36 | "sns:Publish", 37 | "s3:GetObject", 38 | "s3:GetObjectMetadata", 39 | "s3:GetObjectVersion" 40 | ] 41 | 42 | resources = ["*"] 43 | } 44 | 45 | statement { 46 | sid = "AllowPassRole" 47 | effect = "Allow" 48 | 49 | actions = ["iam:PassRole"] 50 | 51 | resources = [ 52 | "${aws_iam_role.execution_role.arn}", 53 | "${aws_iam_role.task_role.arn}", 54 | ] 55 | } 56 | } 57 | 58 | resource "aws_iam_role_policy" "codedeploy" { 59 | role = "${aws_iam_role.codedeploy.name}" 60 | policy = "${data.aws_iam_policy_document.codedeploy.json}" 61 | } 62 | 63 | resource "aws_codedeploy_app" "this" { 64 | compute_platform = "ECS" 65 | name = "${var.service_name}-service-deploy" 66 | } 67 | 68 | resource "aws_codedeploy_deployment_group" "this" { 69 | app_name = "${aws_codedeploy_app.this.name}" 70 | deployment_group_name = "${var.service_name}-service-deploy-group" 71 | deployment_config_name = "CodeDeployDefault.ECSAllAtOnce" 72 | service_role_arn = "${aws_iam_role.codedeploy.arn}" 73 | 74 | blue_green_deployment_config { 75 | deployment_ready_option { 76 | action_on_timeout = "CONTINUE_DEPLOYMENT" 77 | } 78 | 79 | terminate_blue_instances_on_deployment_success { 80 | action = "TERMINATE" 81 | termination_wait_time_in_minutes = 60 82 | } 83 | } 84 | 85 | ecs_service { 86 | cluster_name = "${aws_ecs_cluster.this.name}" 87 | service_name = "${aws_ecs_service.this.name}" 88 | } 89 | 90 | deployment_style { 91 | deployment_option = "WITH_TRAFFIC_CONTROL" 92 | deployment_type = "BLUE_GREEN" 93 | } 94 | 95 | load_balancer_info { 96 | target_group_pair_info { 97 | prod_traffic_route { 98 | listener_arns = ["${aws_lb_listener.this.arn}"] 99 | } 100 | 101 | target_group { 102 | name = "${aws_lb_target_group.this.*.name[0]}" 103 | } 104 | 105 | target_group { 106 | name = "${aws_lb_target_group.this.*.name[1]}" 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /code-build.tf: -------------------------------------------------------------------------------- 1 | data "aws_iam_policy_document" "assume_by_codebuild" { 2 | statement { 3 | sid = "AllowAssumeByCodebuild" 4 | effect = "Allow" 5 | actions = ["sts:AssumeRole"] 6 | 7 | principals { 8 | type = "Service" 9 | identifiers = ["codebuild.amazonaws.com"] 10 | } 11 | } 12 | } 13 | 14 | resource "aws_iam_role" "codebuild" { 15 | name = "${var.service_name}-codebuild" 16 | assume_role_policy = "${data.aws_iam_policy_document.assume_by_codebuild.json}" 17 | } 18 | 19 | data "aws_iam_policy_document" "codebuild" { 20 | statement { 21 | sid = "AllowS3" 22 | effect = "Allow" 23 | 24 | actions = [ 25 | "s3:PutObject", 26 | "s3:GetObject", 27 | "s3:GetObjectVersion", 28 | "s3:GetBucketAcl", 29 | "s3:GetBucketLocation" 30 | ] 31 | 32 | resources = ["*"] 33 | } 34 | 35 | statement { 36 | sid = "AllowECR" 37 | effect = "Allow" 38 | 39 | actions = [ 40 | "ecr:*" 41 | ] 42 | 43 | resources = ["*"] 44 | } 45 | 46 | statement { 47 | sid = "AWSKMSUse" 48 | effect = "Allow" 49 | 50 | actions = [ 51 | "kms:DescribeKey", 52 | "kms:GenerateDataKey*", 53 | "kms:Encrypt", 54 | "kms:ReEncrypt*", 55 | "kms:Decrypt" 56 | ] 57 | 58 | resources = ["*"] 59 | } 60 | 61 | statement { 62 | sid = "AllowECSDescribeTaskDefinition" 63 | effect = "Allow" 64 | actions = ["ecs:DescribeTaskDefinition"] 65 | resources = ["*"] 66 | } 67 | 68 | statement { 69 | sid = "AllowLogging" 70 | effect = "Allow" 71 | 72 | actions = [ 73 | "logs:CreateLogGroup", 74 | "logs:CreateLogStream", 75 | "logs:PutLogEvents", 76 | ] 77 | 78 | resources = ["*"] 79 | } 80 | } 81 | 82 | resource "aws_iam_role_policy" "codebuild" { 83 | role = "${aws_iam_role.codebuild.name}" 84 | policy = "${data.aws_iam_policy_document.codebuild.json}" 85 | } 86 | 87 | resource "aws_codebuild_project" "this" { 88 | name = "${var.service_name}-codebuild" 89 | description = "Codebuild for the ECS Green/Blue ${var.service_name} app" 90 | service_role = "${aws_iam_role.codebuild.arn}" 91 | 92 | artifacts { 93 | type = "NO_ARTIFACTS" 94 | } 95 | 96 | environment { 97 | compute_type = "BUILD_GENERAL1_SMALL" 98 | image = "aws/codebuild/docker:18.09.0" 99 | type = "LINUX_CONTAINER" 100 | privileged_mode = true 101 | 102 | environment_variable { 103 | name = "IMAGE_REPO_NAME" 104 | value = "${var.service_name}" 105 | } 106 | 107 | environment_variable { 108 | name = "AWS_ACCOUNT_ID" 109 | value = "${var.aws_account_id}" 110 | } 111 | 112 | environment_variable { 113 | name = "AWS_DEFAULT_REGION" 114 | value = "${var.region}" 115 | } 116 | 117 | environment_variable { 118 | name = "IMAGE_TAG" 119 | value = "latest" 120 | } 121 | 122 | environment_variable { 123 | name = "SERVICE_PORT" 124 | value = "${var.container_port}" 125 | } 126 | 127 | environment_variable { 128 | name = "MEMORY_RESV" 129 | value = "${var.memory_reserv}" 130 | } 131 | } 132 | 133 | source { 134 | type = "GITHUB" 135 | location = "https://github.com/${local.github_owner}/${local.github_repo}.git" 136 | git_clone_depth = 1 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /vpc.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | public_subnets = { 3 | "${var.region}a" = "10.10.101.0/24" 4 | "${var.region}b" = "10.10.102.0/24" 5 | "${var.region}c" = "10.10.103.0/24" 6 | } 7 | private_subnets = { 8 | "${var.region}a" = "10.10.201.0/24" 9 | "${var.region}b" = "10.10.202.0/24" 10 | "${var.region}c" = "10.10.203.0/24" 11 | } 12 | } 13 | 14 | resource "aws_vpc" "this" { 15 | cidr_block = "10.10.0.0/16" 16 | 17 | enable_dns_support = true 18 | enable_dns_hostnames = true 19 | 20 | tags = { 21 | Name = "${var.service_name}-vpc" 22 | } 23 | } 24 | 25 | resource "aws_internet_gateway" "this" { 26 | vpc_id = "${aws_vpc.this.id}" 27 | 28 | tags = { 29 | Name = "${var.service_name}-internet-gateway" 30 | } 31 | } 32 | 33 | resource "aws_subnet" "public" { 34 | count = "${length(local.public_subnets)}" 35 | cidr_block = "${element(values(local.public_subnets), count.index)}" 36 | vpc_id = "${aws_vpc.this.id}" 37 | 38 | map_public_ip_on_launch = true 39 | availability_zone = "${element(keys(local.public_subnets), count.index)}" 40 | 41 | tags = { 42 | Name = "${var.service_name}-service-public" 43 | } 44 | } 45 | 46 | resource "aws_subnet" "private" { 47 | count = "${length(local.private_subnets)}" 48 | cidr_block = "${element(values(local.private_subnets), count.index)}" 49 | vpc_id = "${aws_vpc.this.id}" 50 | 51 | map_public_ip_on_launch = true 52 | availability_zone = "${element(keys(local.private_subnets), count.index)}" 53 | 54 | tags = { 55 | Name = "${var.service_name}-service-private" 56 | } 57 | } 58 | 59 | resource "aws_default_route_table" "public" { 60 | default_route_table_id = "${aws_vpc.this.main_route_table_id}" 61 | 62 | tags = { 63 | Name = "${var.service_name}-public" 64 | } 65 | } 66 | 67 | resource "aws_route" "public_internet_gateway" { 68 | count = "${length(local.public_subnets)}" 69 | route_table_id = "${aws_default_route_table.public.id}" 70 | destination_cidr_block = "0.0.0.0/0" 71 | gateway_id = "${aws_internet_gateway.this.id}" 72 | 73 | timeouts { 74 | create = "5m" 75 | } 76 | } 77 | 78 | resource "aws_route_table_association" "public" { 79 | count = "${length(local.public_subnets)}" 80 | subnet_id = "${element(aws_subnet.public.*.id, count.index)}" 81 | route_table_id = "${aws_default_route_table.public.id}" 82 | } 83 | 84 | resource "aws_route_table" "private" { 85 | vpc_id = "${aws_vpc.this.id}" 86 | 87 | tags = { 88 | Name = "${var.service_name}-private" 89 | } 90 | } 91 | 92 | resource "aws_route_table_association" "private" { 93 | count = "${length(local.private_subnets)}" 94 | subnet_id = "${element(aws_subnet.private.*.id, count.index)}" 95 | route_table_id = "${aws_route_table.private.id}" 96 | } 97 | 98 | resource "aws_eip" "nat" { 99 | vpc = true 100 | 101 | tags = { 102 | Name = "${var.service_name}-eip" 103 | } 104 | } 105 | 106 | resource "aws_nat_gateway" "this" { 107 | allocation_id = "${aws_eip.nat.id}" 108 | subnet_id = "${aws_subnet.public.0.id}" 109 | 110 | tags = { 111 | Name = "${var.service_name}-nat-gw" 112 | } 113 | } 114 | 115 | resource "aws_route" "private_nat_gateway" { 116 | route_table_id = "${aws_route_table.private.id}" 117 | destination_cidr_block = "0.0.0.0/0" 118 | nat_gateway_id = "${aws_nat_gateway.this.id}" 119 | 120 | timeouts { 121 | create = "5m" 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /code-pipeline.tf: -------------------------------------------------------------------------------- 1 | locals { #example : fill your information 2 | github_token = "" 3 | github_owner = "gnokoheat" 4 | github_repo = "ecs-nodejs-app-example" 5 | github_branch = "master" 6 | } 7 | 8 | resource "aws_s3_bucket" "pipeline" { 9 | bucket = "${var.service_name}-codepipeline-bucket" 10 | 11 | policy = <