My First Experience Learning Terraform and Amazon ECS + AWS Fargate
Tags: aws, terraform, beginners, devjournal
Published: 2022-04-10 06:13:20

Hello everyone!

How are you today? I hope you're doing great. I'm excited to learn Terraform, so I'm writing this to document my experience and notes.

My name is Bervianto Leo Pratama. I'm a Software Engineer who wants to know more DevOps world. I hope you will enjoy my article, also give advice and comment on my content. I'm not an expert on the DevOps world, especially IAC (Infrastructure as Code). I believe I still need to learn more about DevOps.

## Preparation

You will need the tool (Terraform) if you want to try the code. You may download Terraform CLI [here](https://www.terraform.io/downloads). Anyway, my Terraform CLI version is 1.11.7.

![Terraform Version](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ugh61s57qf3dmaslguif.png)

### Prepare a User

Since I just explore and want to know more about Terraform and AWS. I'm using the highest privilege for now. I use AdministratorAccess. Please always use the least privilege for production use.

![Power User](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d1i54kudpctzle36j1ua.png)


### Additional

You may need to use the Terraform Cloud. When you use the Terraform Cloud, please use [this guide](https://learn.hashicorp.com/tutorials/terraform/github-actions). However, it's not required for now.
 
## Let's Jump In

TLDR about my goal. I use `Nginx` as my service. In addition, I use Amazon ECS Fargate and ALB (Application Load Balancer). In summary, I want to access my services using Load Balancer and use Nginx for sampling.

### 1. Prepare the `main.tf` and add AWS as provider.

```terraform
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}
```

**Note**: You may setup cloud state in there. When I'm exploring, I'm using local state. But, after that I migrate to use cloud state in my Github Repository.

### 2. Configure Our AWS Provider

```terraform
provider "aws" {
  alias  = "ap-southeast-1"
  region = "ap-southeast-1"
}
```

**Note**: Please configure with your desire region. I use ap-southeast-1 (Singapore).

### 3. Setup Networking

![Network GIF](https://media.giphy.com/media/U4FkC2VqpeNRHjTDQ5/giphy-downsized-large.gif)

```terraform
resource "aws_default_vpc" "my-personal-web" {
  provider = aws.ap-southeast-1

  tags = {
    env = "dev"
  }
}

resource "aws_default_subnet" "my-personal-web" {
  provider          = aws.ap-southeast-1
  availability_zone = "ap-southeast-1a"

  tags = {
    env = "dev"
  }
}

resource "aws_default_subnet" "my-personal-web-1" {
  provider          = aws.ap-southeast-1
  availability_zone = "ap-southeast-1b"


  tags = {
    env = "dev"
  }
}

resource "aws_security_group" "my-personal-web" {
  provider = aws.ap-southeast-1

  name        = "allow_http"
  description = "Allow HTTP inbound traffic"
  vpc_id      = aws_default_vpc.my-personal-web.id

  ingress {
    description = "Allow HTTP for all"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
```

**Note**: I use default vpc & subnet and config new security group to open port 80 only. Since, I will use `nginx` as my services. You will need config vpc & subnet correctly for production use.

### 4. Configure Load Balancer

```terraform
resource "aws_lb" "my-personal-web" {
  provider = aws.ap-southeast-1

  name               = "my-personal-web-lb-tf"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.my-personal-web.id]
  subnets            = [aws_default_subnet.my-personal-web.id, aws_default_subnet.my-personal-web-1.id]
  tags = {
    env = "dev"
  }
}


resource "aws_lb_target_group" "my-personal-web" {
  provider = aws.ap-southeast-1

  name        = "tf-my-personal-web-lb-tg"
  port        = 80
  protocol    = "HTTP"
  target_type = "ip"
  vpc_id      = aws_default_vpc.my-personal-web.id
}

resource "aws_lb_listener" "my-personal-web" {
  provider = aws.ap-southeast-1

  load_balancer_arn = aws_lb.my-personal-web.arn
  port              = "80"
  protocol          = "HTTP"
  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.my-personal-web.arn
  }
}
```

**Note**: I just use port 80. Maybe, you will need 443 for production use.

### 5. Configure Amazon ECS

```terraform
resource "aws_ecs_cluster" "my-personal-web" {
  provider = aws.ap-southeast-1
  name     = "my-personal-web-api-cluster"
}

resource "aws_ecs_cluster_capacity_providers" "my-personal-web" {
  provider = aws.ap-southeast-1

  cluster_name = aws_ecs_cluster.my-personal-web.name

  capacity_providers = ["FARGATE"]
}

resource "aws_ecs_task_definition" "my-personal-web" {
  provider = aws.ap-southeast-1

  family                   = "service"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = 1024
  memory                   = 2048
  container_definitions = jsonencode([
    {
      name      = "my-personal-web-api"
      image     = "nginx"
      cpu       = 1024
      memory    = 2048
      essential = true
      portMappings = [
        {
          containerPort = 80
          hostPort      = 80
        }
      ]
    }
  ])
}

resource "aws_ecs_service" "my-personal-web" {
  provider = aws.ap-southeast-1

  name            = "my-personal-web"
  cluster         = aws_ecs_cluster.my-personal-web.id
  task_definition = aws_ecs_task_definition.my-personal-web.arn
  desired_count   = 2
  launch_type     = "FARGATE"

  network_configuration {
    subnets          = [aws_default_subnet.my-personal-web.id, aws_default_subnet.my-personal-web-1.id]
    security_groups  = [aws_security_group.my-personal-web.id]
    assign_public_ip = true
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.my-personal-web.arn
    container_name   = "my-personal-web-api"
    container_port   = 80
  }

  tags = {
    env = "dev"
  }
}
```

### Full Code

```terraform
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

# Configure the AWS Provider
provider "aws" {
  alias  = "ap-southeast-1"
  region = "ap-southeast-1"
}

resource "aws_default_vpc" "my-personal-web" {
  provider = aws.ap-southeast-1

  tags = {
    env = "dev"
  }
}

resource "aws_default_subnet" "my-personal-web" {
  provider          = aws.ap-southeast-1
  availability_zone = "ap-southeast-1a"

  tags = {
    env = "dev"
  }
}

resource "aws_default_subnet" "my-personal-web-1" {
  provider          = aws.ap-southeast-1
  availability_zone = "ap-southeast-1b"


  tags = {
    env = "dev"
  }
}

resource "aws_security_group" "my-personal-web" {
  provider = aws.ap-southeast-1

  name        = "allow_http"
  description = "Allow HTTP inbound traffic"
  vpc_id      = aws_default_vpc.my-personal-web.id

  ingress {
    description = "Allow HTTP for all"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_lb" "my-personal-web" {
  provider = aws.ap-southeast-1

  name               = "my-personal-web-lb-tf"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.my-personal-web.id]
  subnets            = [aws_default_subnet.my-personal-web.id, aws_default_subnet.my-personal-web-1.id]
  tags = {
    env = "dev"
  }
}


resource "aws_lb_target_group" "my-personal-web" {
  provider = aws.ap-southeast-1

  name        = "tf-my-personal-web-lb-tg"
  port        = 80
  protocol    = "HTTP"
  target_type = "ip"
  vpc_id      = aws_default_vpc.my-personal-web.id
}

resource "aws_lb_listener" "my-personal-web" {
  provider = aws.ap-southeast-1

  load_balancer_arn = aws_lb.my-personal-web.arn
  port              = "80"
  protocol          = "HTTP"
  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.my-personal-web.arn
  }
}


resource "aws_ecs_cluster" "my-personal-web" {
  provider = aws.ap-southeast-1
  name     = "my-personal-web-api-cluster"
}

resource "aws_ecs_cluster_capacity_providers" "my-personal-web" {
  provider = aws.ap-southeast-1

  cluster_name = aws_ecs_cluster.my-personal-web.name

  capacity_providers = ["FARGATE"]
}

resource "aws_ecs_task_definition" "my-personal-web" {
  provider = aws.ap-southeast-1

  family                   = "service"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = 1024
  memory                   = 2048
  container_definitions = jsonencode([
    {
      name      = "my-personal-web-api"
      image     = "nginx"
      cpu       = 1024
      memory    = 2048
      essential = true
      portMappings = [
        {
          containerPort = 80
          hostPort      = 80
        }
      ]
    }
  ])
}

resource "aws_ecs_service" "my-personal-web" {
  provider = aws.ap-southeast-1

  name            = "my-personal-web"
  cluster         = aws_ecs_cluster.my-personal-web.id
  task_definition = aws_ecs_task_definition.my-personal-web.arn
  desired_count   = 2
  launch_type     = "FARGATE"

  network_configuration {
    subnets          = [aws_default_subnet.my-personal-web.id, aws_default_subnet.my-personal-web-1.id]
    security_groups  = [aws_security_group.my-personal-web.id]
    assign_public_ip = true
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.my-personal-web.arn
    container_name   = "my-personal-web-api"
    container_port   = 80
  }

  tags = {
    env = "dev"
  }
}
```

{% github berviantoleo/my-aws %}

## Let's Provision It!

![GIF Let's Do This](https://media.giphy.com/media/BpGWitbFZflfSUYuZ9/giphy.gif)

1. Run `terraform init`.
2. Run `terraform plan`. You may check what's the changes in this step.
3. Run `terraform apply`. Type `yes` and Enter.
4. Wait until the deployment of all the resources is done. May take a long time.
5. After finished and successful, try to access it with your public load balancer. As example, here is mine.

![NGINX](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g39dueaph1wdkc83ktm7.png)

## Thank you

Thank you for reading. I really appreciate when you read this long (because the code) article.

![GIF Bring It On](https://media.giphy.com/media/dUHdTk3tvry9NETa67/giphy.gif)