DEV Community

Cover image for My First Experience Learning Terraform and Amazon ECS + AWS Fargate
Bervianto Leo Pratama for AWS Community Builders

Posted on • Updated on

My First Experience Learning Terraform and Amazon ECS + AWS Fargate

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. Anyway, my Terraform CLI version is 1.11.7.

Terraform Version

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

Additional

You may need to use the Terraform Cloud. When you use the Terraform Cloud, please use this guide. 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 {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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

provider "aws" {
  alias  = "ap-southeast-1"
  region = "ap-southeast-1"
}
Enter fullscreen mode Exit fullscreen mode

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

3. Setup Networking

Network GIF

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"]
  }
}
Enter fullscreen mode Exit fullscreen mode

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

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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

5. Configure Amazon ECS

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

Full Code

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's Provision It!

GIF Let's Do This

  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

Thank you

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

GIF Bring It On

Top comments (0)