Recently AWS introduced a service called Fargate, which alows you to run containers without having to manage servers or clusters. It can currently be used on top of AWS Elastic Container Service (ECS) with support for Kubernetes (EKS) coming later this year. Please notice, that at the moment of writing, Fargate is only available in N.Virginia (us-east) region.
Terraform added support for a new Fargate launch type in their ECS module, but documentation is very scarse and there are a lot of things that need to be configured differently compared to a classic ECS task.
In this post I would like to share a minimal working example of deploying an image hosted on AWS Elastic Container Repository (ECR) to ECS Fargate.
First part of the tutorial includes basic details about how to use terraform and ECR. If you are just looking for specifics of configuration of aws_ecs_task_definition and aws_ecs_service resources, scroll down to the last section.
Setup
This tutorial is accompanied by an example of setting up bare-bone web server running on AWS Fargate and publicly accessible via an Application Load Balancer (ALB) DNS name: https://github.com/afedulov/terraform-fargate.git
git clone https://github.com/afedulov/terraform-fargate.git
cd terraform-fargate/
Easiest way to give terraform access to your AWS account is to export AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY into the shell that you are using to run terraform. This is fine for the purposes of this tutorial, but there are approaches with better security available, such as using aws-vault
tool. If you use aws-vault, add --no-session parameter to avoid errors with tokens:
aws-vault exec <profile name> --no-session -- terraform plan
If you do not want to make use of aws_vault
, proceed as follows:
set +o history # disable history
export AWS_ACCESS_KEY_ID=<insert key_id>
export AWS_SECRET_ACCESS_KEY=<insert secret_access_key>
set -o history # enable history
export AWS_DEFAULT_REGION=us-east-1
First you need to create a backend for Terraform state. Easiest option is to use S3. You can either create it manually via AWS UI, or use aws cli
:
aws s3 mb s3://<terraform-fargate>
Bucket names in s3 have to be globally unique. I will use terraform-fargate
, but make sure you change your backend configuration in vars.tf
to whatever bucket you've created. Enabling versioning on this bucket might be a good idea.
Next step is to run
terraform-fargate/terraform$ terraform init
In case something went wrong during the initialization (for instance bucket created in a wrong region), rm -rf .terraform/
and run init again.
I will be using AWS Elastic Container registry (ECR) to store docker images. In order to apply the whole terraform plan, including container deployment, Docker image has to be first made available. Therefore first create a repository by using partial configuration (--target
option)
terraform-fargate/terraform$ terraform apply --target aws_ecr_repository.myapp
Notice repository URL in the output:
myapp-repo = <generated_ecr_repo_url>
For simplicity let's just export it to the environment:
export REPO_URL=$(terraform output myapp-repo)
Docker => ECR
Now that the ECR repository has been created we can build and push Docker image to AWS.
You'll need AWS CLI tools to push images to ECR. Refer to this page for installation details. Execute the following (including $ and brackets) to login docker to ECR:
terraform-fargate/terraform$ $(aws ecr get-login --region us-east-1 --no-include-email)
Now go to your Docker image folder, build it, label and upload it to ECR (do it in the same shell or manually substitute ${REPO_URL}
by <generated_ecr_repo_url>
from before).
terraform_fargate/docker/myapp $ docker build -t myapp .
terraform_fargate/docker/myapp $ docker tag myapp ${REPO_URL}:latest
terraform_fargate/docker/myapp $ docker push ${REPO_URL}:latest
You shall now see upload progress.
If you get errors related to missing credentials, make sure that you've used the same region in 'ecr get-login' as the one where your ECR repos was created.
Deploying
terraform-fargate/terraform$ terraform apply
After all changes are applied, you should see a DNS name of an ALB. Just open it in your Web browser to check a greeting message from your container deployed on AWS Fargate.
Outputs:
alb_dns_name = myapp-*********.us-east-1.elb.amazonaws.com
You will probably want to play around with the configuration and maybe apply terraform destroy
at some point. Before you do that, I recommend to prevent Terraform from deleting your ECR repositories. This will save you a lot of time needed to reinitialize ECR and re-upload your Docker images. To do this, go to ECS console -> Repositories ->
Following sections will contain some details and peculiarities (as compared to classic ECS) of applied configuration.
Fargate access to ECR
ECR images are being pulled into the cluster via public Internet. There are two options to allow this: either assign your task a public IP directly (only available starting Terraform AWS Provider 1.9.0: https://github.com/terraform-providers/terraform-provider-aws/issues/3098#issuecomment-359552995) or configure networking using NAT and internet gateways (IGW). Here are some details: https://github.com/aws/amazon-ecs-agent/issues/1204. If you are just starting, it might take you a while to figure out all the missing pieces. Important part is the following: you'll have to configure 2 subnets: one private, with traffic to 0.0.0.0/0 being forwarded to a NAT gateway and a public one, with traffic to 0.0.0.0/0 being forwarded to an IGW. You'll then place your Fargate tasks into a private subnet. NAT + IGW is the configuration that I am using in the accompanying source code https://github.com/afedulov/terraform-fargate.git .
Peculiarities of Fargate configuration
Let's now take a look at configuration and emphasize what is specific to Fargate.
resource "aws_ecs_task_definition" "myapp" {
family = "myapp"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = 256
memory = 512
container_definitions = "${data.template_file.myapp.rendered}"
execution_role_arn = "${aws_iam_role.ecs_task_assume.arn}"
}
resource "aws_ecs_service" "myapp" {
name = "myapp"
cluster = "${aws_ecs_cluster.fargate.id}"
launch_type = "FARGATE"
task_definition = "${aws_ecs_task_definition.myapp.arn}"
desired_count = 1
network_configuration = {
subnets = ["${module.base_vpc.private_subnets[0]}"]
security_groups = ["${aws_security_group.ecs.id}"]
}
load_balancer {
target_group_arn = "${aws_alb_target_group.myapp.arn}"
container_name = "myapp"
container_port = 3000
}
depends_on = [
"aws_alb_listener.myapp"
]
}
resource "aws_alb_target_group" "myapp" {
name = "myapp"
protocol = "HTTP"
port = "3000"
vpc_id = "${module.base_vpc.vpc_id}"
target_type = "ip"
health_check {
path = "/"
}
}
- aws_ecs_task_definition:
network_mode
must be set to"awsvpc"
cpu
andmemory
have to be specified in the aws_ecs_task_definition blockcpu
andmemory
have to be picked from a list of allowed combinations (see here)requires_compatibilities
must contain"FARGATE"
- aws_ecs_service:
launch_type
must be set to"FARGATE"
- Fargate service won't deploy without explicit
network_configuration
block
- aws_alb_target_group:
- ALB
target_type
is by default"instance"
and it has to be set explicitly to"ip"
(there are no"instances"
, at least from the user perspective)
- ALB
I hope this tutorial will help you save time making first steps in using AWS Fargate + ECR managed by Terraform.