Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.terraform
terraform.tfstate
*.tfstate*
terraform.tfvars
todo.txt
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,38 @@
# terraform-aws-ecs

Terraform module which creates AWS ECS resources

This module focuses purely on ECS and nothing else. Therefore only these resources can be created with this module:

* [ECS](https://www.terraform.io/docs/providers/aws/r/ecs_cluster.html)
* [IAM](https://www.terraform.io/docs/providers/aws/r/iam_instance_profile.html)

However, having said the above to have a proper ECS cluster up and running multiple resources are needed. In most cases creating these resources is heavily opinionated and or context-bound. That is why this module does not create these resources. But you still need them to have a production ready environment. Therefore the example area shows how to create everything needed for a production environment.

## Usage

```hcl
module "ecs" {
source = "terraform-aws-modules/ecs/aws"
name = "my-ecs"
}
```

## Conditional creation

Sometimes you need to have a way to create ECS resources conditionally but Terraform does not allow to use `count` inside `module` block, so the solution is to specify argument `create_ecs`.

```hcl
# ECS cluster will not be created
module "ecs" {
source = "terraform-aws-modules/ecs/aws"
create_ecs = false
# ... omitted
}
```

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a section Examples to describe each kind of example.

## License

**WORK IN PROGRESS**
Apache 2 Licensed. See LICENSE for full details.
39 changes: 39 additions & 0 deletions examples/complete-ecs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Complete ECS

This example uses only verified Terraform modules to create all resources that are needed for an ECS cluster that is sufficient for staging or production environment.

While this example is still in the early stage there are other repositories that show how to create an ECS cluster:

* <https://github.com/anrim/terraform-aws-ecs>
* <https://github.com/arminc/terraform-ecs>
* <https://github.com/alex/ecs-terraform>
* <https://github.com/Capgemini/terraform-amazon-ecs>

## TODO

Things still needed in the example:

* AWS network infrastructure on what is created
* Full explanation on why certain resources are created
* Create EC2 instance specific SecurityGroup instead of using the default one from VPC module
* Push logs of default EC2 stuff (docker, ecs agent, etc...) to CloudWatch logs
* Add an example with ALB
* Add an example with NLB
* Add an example with ELB
* Create a Fargate example

## Usage

To run this example you need to execute:

```bash
terraform init
terraform plan
terraform apply
```

Note that this example may create resources which can cost money (AWS EC2 instances, for example). Run `terraform destroy` when you don't need these resources.

## Explanation

Current version creates an high-available VPC with instances that are attached to ECS. ECS tasks can be run on these instances but they are not exposed to anything.
65 changes: 65 additions & 0 deletions examples/complete-ecs/ec2-instances/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#For now we only use the AWS ECS optimized ami <https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html>
data "aws_ami" "amazon_linux_ecs" {
most_recent = true

filter {
name = "name"
values = ["amzn-ami-*-amazon-ecs-optimized"]
}

filter {
name = "owner-alias"
values = ["amazon"]
}
}

# This is the convention we use to know wht belongs to each other
locals {
name = "${var.ecs_cluster}-${var.environment}"
}

module "this" {
source = "terraform-aws-modules/autoscaling/aws"
version = "v2.2.2"

name = "${local.name}"

# Launch configuration
lc_name = "${local.name}"

image_id = "${data.aws_ami.amazon_linux_ecs.id}"
instance_type = "t2.micro"
security_groups = "${var.security_groups}"
iam_instance_profile = "${var.ec2_profile}"
user_data = "${data.template_file.user_data.rendered}"

# Auto scaling group
asg_name = "${local.name}"
vpc_zone_identifier = "${var.vpc_zone_identifier}"
health_check_type = "EC2"
min_size = 0
max_size = 1
desired_capacity = 1
wait_for_capacity_timeout = 0

tags = [
{
key = "Environment"
value = "${var.environment}"
propagate_at_launch = true
},
{
key = "Cluster"
value = "${var.ecs_cluster}"
propagate_at_launch = true
},
]
}

data "template_file" "user_data" {
template = "${file("${path.module}/templates/user-data.sh")}"

vars {
cluster_name = "${var.ecs_cluster}"
}
}
10 changes: 10 additions & 0 deletions examples/complete-ecs/ec2-instances/templates/user-data.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash

# ECS config
{
echo "ECS_CLUSTER=${cluster_name}"
} >> /etc/ecs/ecs.config

start ecs

echo "Done"
41 changes: 41 additions & 0 deletions examples/complete-ecs/ec2-instances/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
variable "environment" {
description = "Name of the Environment, for example dev"
default = "dev"
}

variable "ecs_cluster" {
description = "Name of the ECS cluster, name to be used on all the resources as identifier"
}

variable "vpc_zone_identifier" {
description = "A list of subnet IDs to launch resources in"
type = "list"
}

variable "security_groups" {
description = "A list of security group IDs to assign to the launch configuration"
type = "list"
}

# ASG
variable "max_size" {
description = "The maximum size of the auto scale group"
default = 1
}

variable "min_size" {
description = "The minimum size of the auto scale group"
default = 0
}

variable "desired_capacity" {
description = "The number of Amazon EC2 instances that should be running in the group"
default = 1
}

variable "wait_for_capacity_timeout" {
description = "A maximum duration that Terraform should wait for ASG instances to be healthy before timing out. (See also Waiting for Capacity below.) Setting this to '0' causes Terraform to skip all Capacity Waiting behavior."
default = "10m"
}

variable "ec2_profile" {}
58 changes: 58 additions & 0 deletions examples/complete-ecs/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
provider "aws" {
region = "eu-west-1"
version = "v1.18.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to specify version here, because we need to be able to run examples using latest automatically.

}

provider "terraform" {}

locals {
name = "my-ecs"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually pass values similar to the name of the example to be able to have several examples at once - complete-ecs in this case.

}

module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "v1.30.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to pin version here. We need to stay on the edge with examples to be able to see that this still works (yes, we lack automated tests for modules).


name = "${local.name}"

cidr = "10.1.0.0/16"

azs = ["eu-west-1a", "eu-west-1b"]
private_subnets = ["10.1.1.0/24", "10.1.2.0/24"]
public_subnets = ["10.1.11.0/24", "10.1.12.0/24"]

enable_nat_gateway = true
single_nat_gateway = true

tags = {
Environment = "${local.name}"
Name = "${local.name}"
}
}

#----- ECS --------
module "ecs" {
source = "../../"
name = "${local.name}"
}

module "ec2-profile" {
source = "../../modules/ecs-instance-profile"
name = "my-ecs"
}

#----- ECS Resources--------
module "ec2" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can probably be simplified by using autoscaling module directly here instead of wrapper module (ec2)

source = "ec2-instances"
ecs_cluster = "${local.name}"
vpc_zone_identifier = ["${module.vpc.private_subnets}"]
security_groups = ["${module.vpc.default_security_group_id}"]
ec2_profile = "${module.ec2-profile.instance_profile_id}"
}

#----- ECS Services--------

module "hello-world" {
source = "service-hello-world"
cluser_id = "${module.ecs.ecs_cluster_id}"
}
38 changes: 38 additions & 0 deletions examples/complete-ecs/service-hello-world/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
resource "aws_cloudwatch_log_group" "hello-world" {
name = "hello-world"
retention_in_days = 1
}

resource "aws_ecs_task_definition" "hello-world" {
family = "hello-world"

container_definitions = <<EOF
[
{
"name": "hello-world",
"image": "hello-world",
"cpu": 0,
"memory": 128,
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-region": "eu-west-1",
"awslogs-group": "hello-world",
"awslogs-stream-prefix": "my-ecs"
}
}
}
]
EOF
}

resource "aws_ecs_service" "hello-world" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use snake case for resource's names everywhere

name = "hello-world"
cluster = "${var.cluser_id}"
task_definition = "${aws_ecs_task_definition.hello-world.arn}"

desired_count = 1

deployment_maximum_percent = 100
deployment_minimum_healthy_percent = 0
}
3 changes: 3 additions & 0 deletions examples/complete-ecs/service-hello-world/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
variable "cluser_id" {
description = "The ECS cluster ID"
}
13 changes: 13 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
terraform {
required_version = ">= 0.11.7"
}

provider "template" {
version = ">= 1.0.0"
}

resource "aws_ecs_cluster" "ecs" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"ecs" => "this"

count = "${var.create_ecs ? 1 : 0}"

name = "${var.name}"
}
7 changes: 7 additions & 0 deletions modules/ecs-instance-profile/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# ECS instance policy

For an EC2 instance to connect it self to ECS it needs rights to do so.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

itself


* [Why do we need ECS instance policies?](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/instance_IAM_role.html)
* [ECS roles explained](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs_managed_policies.html)
* [More ECS policy examples explained](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/IAMPolicyExamples.html)
34 changes: 34 additions & 0 deletions modules/ecs-instance-profile/instance-policy.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
resource "aws_iam_role" "this" {
name = "${var.name}_ecs_instance_role"
path = "/ecs/"

assume_role_policy = <<EOF
{
"Version": "2008-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": ["ec2.amazonaws.com"]
},
"Effect": "Allow"
}
]
}
EOF
}

resource "aws_iam_instance_profile" "this" {
name = "${var.name}_ecs_instance_profile"
role = "${aws_iam_role.this.name}"
}

resource "aws_iam_role_policy_attachment" "ecs_ec2_role" {
role = "${aws_iam_role.this.id}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
}

resource "aws_iam_role_policy_attachment" "ecs_ec2_cloudwatch_role" {
role = "${aws_iam_role.this.id}"
policy_arn = "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess"
}
3 changes: 3 additions & 0 deletions modules/ecs-instance-profile/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "instance_profile_id" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Name it this_iam_instance_profile_id

value = "${aws_iam_instance_profile.this.id}"
}
3 changes: 3 additions & 0 deletions modules/ecs-instance-profile/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
variable "name" {
description = "Name to be used on all the resources as identifier"
}
3 changes: 3 additions & 0 deletions outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "ecs_cluster_id" {
value = "${aws_ecs_cluster.ecs.0.id}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a replacement to concat when create_ecs = false?

}
8 changes: 8 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
variable "create_ecs" {
description = "Controls if ECS should be created"
default = true
}

variable "name" {
description = "Name to be used on all the resources as identifier, also the name of the ECS cluster"
}