Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding apigw-fargate-terraform pattern #2437

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
63 changes: 63 additions & 0 deletions apigw-fargate-terraform/Network-ALB.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Network-ALB.tf

#################################################################
# Network - AWS Application Load Balancer
#################################################################
# Creating the internal AWS Application Load Balancer on private subnets
resource "aws_alb" "MyApp-ALB" {
name = "MyApp-ALB"
internal = true
subnets = [aws_subnet.MySubnet-privateA.id, aws_subnet.MySubnet-privateB.id]
security_groups = [aws_security_group.MyApp-ALB_SG.id]
}

# Defining ALB target group with health check of 5 seconds timeout
resource "aws_alb_target_group" "MyApp-ALB_TG" {
name = "MyApp-ALB-TG"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.MyVPC-VPC.id
target_type = "ip"

health_check {
healthy_threshold = "3"
interval = "30"
protocol = "HTTP"
matcher = "200"
timeout = "5"
path = "/"
unhealthy_threshold = "2"
}
}

# Setting up internal ALB security group allowing only traffic from 80 TCP port
resource "aws_security_group" "MyApp-ALB_SG" {
name = "MyApp-ALB-SG"
vpc_id = aws_vpc.MyVPC-VPC.id

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

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

# Redirecting all traffic from the ALB to the ECS Cluster target group
resource "aws_alb_listener" "MyApp-ALB_Listener" {
load_balancer_arn = aws_alb.MyApp-ALB.id
port = 80
protocol = "HTTP"

default_action {
target_group_arn = aws_alb_target_group.MyApp-ALB_TG.id
type = "forward"
}
}
189 changes: 189 additions & 0 deletions apigw-fargate-terraform/Network-VPC.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# Network-ALB.tf

#################################################################
# Network - Virtual Private Cloud (VPC)
#################################################################
# Creating the VPC with "10.0.0.0/16" CIDR range with feched data info
resource "aws_vpc" "MyVPC-VPC" {
cidr_block = "10.0.0.0/16"

enable_dns_hostnames = true
enable_dns_support = true

tags = {
Name = "MyVPC-VPC"
}
}

# Creating 2 private subnets in each different AZ
resource "aws_subnet" "MySubnet-privateA" {
cidr_block = cidrsubnet(aws_vpc.MyVPC-VPC.cidr_block, 8, 1)
availability_zone = data.aws_availability_zones.available.names[1]
vpc_id = aws_vpc.MyVPC-VPC.id

tags = {
Name = "MySubnet-privateA"
}
}

resource "aws_subnet" "MySubnet-privateB" {
cidr_block = cidrsubnet(aws_vpc.MyVPC-VPC.cidr_block, 8, 2)
availability_zone = data.aws_availability_zones.available.names[2]
vpc_id = aws_vpc.MyVPC-VPC.id

tags = {
Name = "MySubnet-privateB"
}
}

# Creating 2 public subnets in each different AZ
resource "aws_subnet" "MySubnet-publicA" {
cidr_block = cidrsubnet(aws_vpc.MyVPC-VPC.cidr_block, 8, 3)
availability_zone = data.aws_availability_zones.available.names[1]
vpc_id = aws_vpc.MyVPC-VPC.id
map_public_ip_on_launch = true

tags = {
Name = "MySubnet-publicA"
}
}

resource "aws_subnet" "MySubnet-publicB" {
cidr_block = cidrsubnet(aws_vpc.MyVPC-VPC.cidr_block, 8, 4)
availability_zone = data.aws_availability_zones.available.names[2]
vpc_id = aws_vpc.MyVPC-VPC.id
map_public_ip_on_launch = true

tags = {
Name = "MySubnet-publicB"
}
}

# Internet Gateway for the public subnet
resource "aws_internet_gateway" "MyIGW-IG" {
vpc_id = aws_vpc.MyVPC-VPC.id

tags = {
Name = "MyIGW-IG"
}

}

# Routing the public subnet traffic through the IGW
resource "aws_route" "internet_access" {
route_table_id = aws_vpc.MyVPC-VPC.main_route_table_id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.MyIGW-IG.id
}

# Creating one NAT Gateway in each AZ with an Elastic IP for each private subnet to get internet connectivity
resource "aws_eip" "MyEIP-EIPA" {
depends_on = [aws_internet_gateway.MyIGW-IG]

tags = {
Name = "MyEIP-EIPA"
}
}

resource "aws_eip" "MyEIP-EIPB" {
depends_on = [aws_internet_gateway.MyIGW-IG]

tags = {
Name = "MyEIP-EIPB"
}
}

resource "aws_nat_gateway" "MyNATGW-NATGWA" {
subnet_id = element(aws_subnet.MySubnet-publicA.*.id, 1)
allocation_id = element(aws_eip.MyEIP-EIPA.*.id, 1)

tags = {
Name = "MyNATGW-NATGWA"
}
}

resource "aws_nat_gateway" "MyNATGW-NATGWB" {
subnet_id = element(aws_subnet.MySubnet-publicB.*.id, 2)
allocation_id = element(aws_eip.MyEIP-EIPB.*.id, 2)

tags = {
Name = "MyNATGW-NATGWB"
}

}

# Routing public subnets, making non-local traffic pass through Internet Gateway to the internet
resource "aws_route_table" "MyRouteTable-publicA" {
vpc_id = aws_vpc.MyVPC-VPC.id

route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.MyIGW-IG.id
}

tags = {
Name = "MyRouteTable-publicA"
}
}

resource "aws_route_table" "MyRouteTable-publicB" {
vpc_id = aws_vpc.MyVPC-VPC.id

route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.MyIGW-IG.id
}

tags = {
Name = "MyRouteTable-publicB"
}
}

# Routing private subnets, making non-local traffic pass through each NAT gateway to the internet
resource "aws_route_table" "MyRouteTable-privateA" {
vpc_id = aws_vpc.MyVPC-VPC.id

route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = element(aws_nat_gateway.MyNATGW-NATGWA.*.id, 1)
}

tags = {
Name = "MyRouteTable-privateA"
}

}

resource "aws_route_table" "MyRouteTable-privateB" {
vpc_id = aws_vpc.MyVPC-VPC.id

route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = element(aws_nat_gateway.MyNATGW-NATGWB.*.id, 2)
}

tags = {
Name = "MyRouteTable-privateB"
}
}

# Associating the previously created routes to the public and private subnets (so they don't default to the main MyVPC-VPC route table)
resource "aws_route_table_association" "privateA" {
subnet_id = element(aws_subnet.MySubnet-privateA.*.id, 1)
route_table_id = element(aws_route_table.MyRouteTable-privateA.*.id, 1)
}

resource "aws_route_table_association" "privateB" {
subnet_id = element(aws_subnet.MySubnet-privateB.*.id, 2)
route_table_id = element(aws_route_table.MyRouteTable-privateB.*.id, 2)
}

resource "aws_route_table_association" "publicA" {
subnet_id = element(aws_subnet.MySubnet-publicA.*.id, 1)
route_table_id = element(aws_route_table.MyRouteTable-publicA.*.id, 1)
}

resource "aws_route_table_association" "publicB" {
subnet_id = element(aws_subnet.MySubnet-publicB.*.id, 2)
route_table_id = element(aws_route_table.MyRouteTable-publicB.*.id, 2)
}
115 changes: 115 additions & 0 deletions apigw-fargate-terraform/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Amazon API Gateway to AWS Fargate

This project contains a terraform template for deploying an AWS Fargate service running on an Amazon Elastic Container Service (ECS) cluster with a private Application Load Balancer in-front. The Application Load Balanced Fargate Service is integrated with Amazon API Gateway HTTP API to expose the endpoint. This template uses public standard nginx image without having to pre-push the image to Amazon Elastic Container Registry (ECR) or another container library.

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/apigw-fargate-terraform.

Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.

## Requirements

* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [Terraform](https://learn.hashicorp.com/tutorials/terraform/install-cli?in=terraform/aws-get-started) installed


## Deployment Instructions

1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
```bash
git clone https://github.com/aws-samples/serverless-patterns
```
2. Change directory to the pattern directory:
```bash
cd serverless-patterns/apigw-fargate-terraform
```
3. From the command line, initialize terraform to download and install the providers defined in the configuration:
```
terraform init
```
4. From the command line, apply the configuration in the main.tf file:
```
terraform apply
```
5. During the prompts:
* Enter yes
6. Note the outputs from the deployment process. These contain the resource names and/or URLs which are used for testing.

## How it works

- The VPC and subnets are created
- The ECS cluster is created
- The Task Definitions are created
- The API Gateway Integration, Route, and VPC Link are created
- The Fargate Service is created

## Testing

Retrieve the API Gateway URL from the `cdk deploy` output. Example of the output is:

```
APIGatewayUrl = "https://abcd123efg.execute-api.eu-west-1.amazonaws.com"
```

For reference:

```bash
Outputs:
APIGatewayUrl = "https://abcd123efg.execute-api.eu-west-1.amazonaws.com"
MyFargateServiceLoadBalancer = "internal-MyApp-ALB-XXXXX.eu-west-1.elb.amazonaws.com"
MyFargateServiceServiceURL = "http://internal-MyApp-ALB-XXXXX.eu-west-1.elb.amazonaws.com"
```

The API Gateway allows a GET request to `/`. To call it, run the following:

```bash
curl --location --request GET '<REPLACE WITH API GATEWAY URL>'
# Example
curl --location --request GET 'https://abcd123efg.execute-api.ap-southeast-2.amazonaws.com/'
```

Running the request above should produce the following output:

```bash
~ % curl --location --request GET 'https://abcd123efg.execute-api.ap-southeast-2.amazonaws.com/'
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
(...)
````

## Cleanup

1. Change directory to the pattern directory:
```
cd apigw-fargate-terraform
```
2. Delete all created resources by terraform
```bash
terraform destroy
```
3. During the prompts:
* Enter yes
4. Confirm all created resources has been deleted
```bash
terraform show
```
5. Navigate to ECR in the AWS console and delete the container images created

## Documentation and useful references

- [GitHub issue where one of the contributors provided an example with a pre-built image and any type of request](https://github.com/aws/aws-cdk/issues/8066)
- [CDK documentation for ApplicationLoadBalancedFargateService](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ecs-patterns.ApplicationLoadBalancedFargateService.html)
- [CDK documentation for APIGatewayv2 CfnIntegration](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigatewayv2.CfnIntegration.html)
- [CDK documentation for APIGatewayv2 CfnRoute](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigatewayv2.CfnRoute.html)
- [CDK documentation for APIGatewayv2 HttpApi](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigatewayv2.HttpApi.html)
- [CDK documentation for CDK Core CfnResource](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.CfnResource.html)

---

Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0

Loading