How to build a microservices infrastructure on AWS using Terraform, ECS Fargate, and CloudMap | part one

Stefano Monti
AWS Infrastructure
Published in
8 min readDec 31, 2022

Introduction

In this blog post, we will learn how to build a microservices infrastructure on AWS using Terraform, ECS Fargate, and CloudMap.

Microservices is an architectural style that structures an application as a collection of small, independent services that communicate with each other through well-defined interfaces. This approach has several benefits, including increased scalability, flexibility, and maintainability.

AWS provides several services that can be used to build a microservices infrastructure, including Amazon Elastic Container Service (ECS) and Amazon CloudMap. ECS is a fully managed container orchestration service that makes it easy to run, scale, and maintain containerized applications. CloudMap is a service discovery service that enables service instances to be registered and discovered within a virtual private cloud (VPC).

To automate the infrastructure deployment process, we will use Terraform, an open-source infrastructure as a code tool. Terraform allows us to define our infrastructure in a declarative configuration language, which can then be used to create and manage AWS resources.

Prerequisites

Here are the initials steps to build a microservices infrastructure on AWS using Terraform, ECS Fargate, and CloudMap:

  1. Install Terraform (check this if you need it).
  2. Have an AWS Account with admin permissions.
  3. Define the provider for AWS (I’ve already explained how to proceed here) in every main.tf file we will create during this tutorial so, make sure to understand how to do it (it is also possible to not repeat this code in every file with the use of Terragrunt but, I’ll discuss this topic in another post).

This time we will use the remote Terraform state to communicate between stacks using outputs.
In Terraform, a remote state is a way to store the state of your infrastructure in a central location, such as on a remote server or in a version control system. This can be useful in several scenarios, including collaboration, separation of duties, and environment isolation.
One option for storing Terraform state remotely is to use Amazon S3, which is a cloud storage service provided by AWS. To use S3 as a remote state backend in Terraform, you can configure the s3 backend in your Terraform configuration.
Here’s an example of how to configure the s3 backend in Terraform:

terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "state.tfstate"
region = "my-region"
}
}

In this example, the s3 backend is configured to use an S3 bucket named "my-terraform-state" and to store the state file at the key "state.tfstate". The region parameter specifies the region where the S3 bucket is located.

Well, there’s a lot of work to do to have our microservices architecture up and running so, let’s jump into it!! 🤾🏼‍♂️

Source Code

Download the source code from the repo here.

You can find base folders:

services-apps where you can find the microservices code; this is a basic nodejs express application that will be discussed later in the post (our three applications are called uno, due, tre; yes, this is an Italian way to count from one to three 🍕🇮🇹);
— stack that contains all the Terraform files for creating our infrastructure: VPC, DNS, ECS, ALB, ECR, and, SERVICES (TASK DEFINITION, ECS SERVICE)

AWS Infrastructure

This section will describe every component and how to build the infrastructure. At the end of every paragraph follow the sh command to deploy the infrastructure.
The following commands are the ones used to initialize and provide infrastructures with Terraform:
terraform init command is used to initialize a Terraform configuration directory. It is the first command that should be run after writing a new Terraform configuration or cloning an existing one from version control;
terraform plan command is used to create an execution plan for Terraform. It is used to preview the actions that Terraform will take when you apply your configuration;
terraform apply command is used to apply the changes required to reach the desired state of the configuration, or the pre-determined set of actions generated by a Terraform plan.

VPC

AWS Virtual Private Cloud (VPC) is a cloud computing service that enables users to launch AWS resources in a virtual network that is isolated from the rest of the internet. VPCs allow users to define and customize their network architecture, including IP address range, subnets, and security policies.
In this example, we are using a terraform module for providing a vpc infrastructure within 10 lines of code.

module "vpc" {
source = "terraform-aws-modules/vpc/aws"

name = "fgms-vpc"
cidr = "10.0.0.0/16"

azs = ["eu-west-1a", "eu-west-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]

enable_dns_hostnames = true
enable_dns_support = true

enable_nat_gateway = true
single_nat_gateway = true

tags = {
group = "fgms"
Environment = "dev"
}
}

If you want to deep dive into the vpc world I have a post for you: here you can find a detailed way to create manually all the vpc resources. For this project, we will use this simple module to avoid wasting time.

Pay attention to the output section, is important to export the following value to permit other stacks to use them.

output "fgms_vpc_id" {
description = "fgms subnet private 1"
value = module.vpc.vpc_id
}

output "fgms_private_subnets_ids" {
description = "fgms private subnets ids"
value = module.vpc.private_subnets
}

output "fgms_public_subnets_ids" {
description = "fgms public subnets ids"
value = module.vpc.public_subnets
}

Deploy

$ cd stacks/vpc
$ terraform init
$ terraform plan
$ terraform apply

DNS

What is CloudMap?
Amazon Cloud Map (Cloud Map) is a service provided by Amazon Web Services (AWS) that allows you to discover and connect to the resources and services that you use in your cloud-based applications. It provides a simple, scalable way to manage and discover the resources and services that your applications depend on, whether they are running in the cloud or on-premises. With Cloud Map, you can create custom namespaces, which are logical groups of services, and register the resources and services that you want to discover within these namespaces. You can then use Cloud Map’s API to discover and connect to these resources and services from your applications. Cloud Map uses Domain Name System (DNS) and Service Discovery (SD) protocols to enable resource discovery and connection.

The code is the following:

resource "aws_service_discovery_private_dns_namespace" "fgms_dns_discovery" {
name = var.fgms_private_dns_namespace
description = "fgms dns discovery"
vpc = data.terraform_remote_state.vpc.outputs.fgms_vpc_id
}

var.fgms_private_dns_namespace is a variable declared inside the variable.tf file:

variable "fgms_private_dns_namespace" {
description = "fgms private dns namespace"
default = "fgms-app.test"
}

fgms-app.test will be the name for our private internal domain; every microservice name will be a subdomain like my-custom-microservice.fgms-app.test declared inside the infrastructure code of every microservice inside the stacks/services/<service name> folder.

Please note that this stack depends on the vpc stack, you cannot deploy these resources if the vpc infrastructure is not already deployed inside your AWS account.


data "terraform_remote_state" "vpc" {
backend = "s3"
config = {
bucket = "fgms-infra"
key = "vpc.tfstate"
region = "eu-west-1"
}
}

This Terraform configuration defines a data source for a remote state stored in an Amazon Simple Storage Service (S3) bucket.

The terraform_remote_state data source with the name "vpc" retrieves the state of resources that are defined in a Terraform configuration stored in an S3 bucket. The state is stored in a file called "vpc.tfstate" in the bucket "fgms-infra" in the "eu-west-1" region.

The purpose of the data source is to allow other resources and modules in the configuration to reference the outputs of the resources defined in the remote state. This can be useful when you want to share resources or configurations between multiple Terraform configurations, or when you want to use modules that are defined in a separate configuration.

To use the data source, you can reference the outputs of the remote state using the data.terraform_remote_state.vpc.OUTPUT_NAME syntax, where "OUTPUT_NAME" is the name of the output that you want to reference.

Deploy

$ cd stacks/dns
$ terraform init
$ terraform plan
$ terraform apply

ECS

Amazon Elastic Container Service (ECS) Fargate is a fully managed compute engine for Amazon ECS that enables you to run containers without having to manage the underlying EC2 instances. With Fargate, you can focus on building and running your applications, rather than the infrastructure that runs them.
In Terraform, the aws_ecs_cluster resource is used to create an Amazon Elastic Container Service (ECS) cluster. An ECS cluster is a logical grouping of tasks or services that you run on Amazon ECS.

resource "aws_ecs_cluster" "fgms_ecs_cluster" {
name = "fgms_ecs_cluster"
}

For now, let’s create only the ECS cluster, services, task definitions, and, relatives roles that will be created inside every service stack.

Deploy

$ cd stacks/ecs
$ terraform init
$ terraform plan
$ terraform apply

ALB

Amazon Web Services (AWS) Application Load Balancer (ALB) is a load balancing service that automatically distributes incoming application traffic across multiple targets, such as EC2 instances, containers, and IP addresses, in one or more Availability Zones. A public ALB is an ALB that is accessible from the internet. It listens for incoming traffic on a public IP address, which is associated with a DNS name that can be accessed from the internet.

resource "aws_lb" "fgms_alb" {
load_balancer_type = "application"
subnets = data.terraform_remote_state.vpc.outputs.fgms_public_subnets_ids
security_groups = ["${aws_security_group.fgms_alb_sg.id}"]
}

resource "aws_security_group" "fgms_alb_sg" {
description = "controls access to the ALB"
vpc_id = data.terraform_remote_state.vpc.outputs.fgms_vpc_id

ingress {
protocol = "tcp"
from_port = 80
to_port = 80
cidr_blocks = ["0.0.0.0/0"]
}

ingress {
protocol = "tcp"
from_port = 443
to_port = 443
cidr_blocks = ["0.0.0.0/0"]
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}

This Terraform configuration creates an AWS Application Load Balancer (ALB) and an Amazon Elastic Compute Cloud (EC2) security group for the ALB.

The aws_lb resource with the name “fgms_alb” creates an ALB with the specified load balancer type, subnets, and security groups. The load balancer type is set to “application”, which indicates that this is an ALB. The subnets and security groups are specified using data sources and resources that are defined elsewhere in the configuration.

The aws_security_group resource with the name “fgms_alb_sg” creates an EC2 security group for the ALB. The security group has inbound rules that allow incoming traffic on TCP ports 80 and 443 from any IP address, and an outbound rule that allows all outbound traffic.

VPC data is imported from the VPC stack as described before:

data "terraform_remote_state" "vpc" {
backend = "s3"
config = {
bucket = "fgms-infra"
key = "vpc.tfstate"
region = "eu-west-1"
}
}

Deploy

$ cd stacks/alb
$ terraform init
$ terraform plan
$ terraform apply

Great work!! 🥳

For now, we have deployed all the static infrastructure; in part two we’ll deploy the ECR Repos for storing our docker images and we’ll create the service to handle the HTTP requests routed from the load balancer to the microservices cluster.

You can find the second part here,

see you in the next post.

…to be continued…

--

--