Skip to content

Latest commit

 

History

History
471 lines (365 loc) · 14.9 KB

SAMPLE04-ECR-ECS-ELB-VPC-ECS-Service.md

File metadata and controls

471 lines (365 loc) · 14.9 KB

SAMPLE-04: Provisioning ECR (Elastic Container Repository), Pushing Image to ECR, Provisioning ECS (Elastic Container Service), VPC (Virtual Private Cloud), ELB (Elastic Load Balancer), ECS Tasks and Service on Fargate Cluster

This sample shows:

  • how to create Flask-app Docker image,
  • how to provision ECR and push to image to this ECR,
  • how to provision VPC, Internet Gateway, Route Table, 3 Public Subnets,
  • how to provision ALB (Application Load Balancer), Listener, Target Group,
  • how to provision ECS Fargate Cluster, Task and Service (running container as Service)

There are 5 main parts:

  • 0_ecr.tf: includes private ECR code

  • 1_vpc.tf: includes VPC, IGW, Route Table, Subnets code

  • 2_ecs.tf: includes ECS Cluster, Task Definition, Role and Policy code

  • 3_elb.tf: includes to ALB, Listener, Target Group, Security Group code

  • 4_ecs_service.tf: includes ECS Fargate Service code with linking to loadbalancer, subnets, task definition.

    ecr-ecs

Code: https://github.com/omerbsezer/Fast-Terraform/tree/main/samples/ecr-ecs-elb-vpc-ecsservice-container

ECS Pricing:

  • For the ECS Cluster:
    • Free
  • For the ECS on 1 EC2 Instance (e.g. m5.large => 2 vCPU, 8 GB RAM):
  • For the Fargate:
    • AWS Fargate pricing is calculated based on the vCPU and memory resources used from the time you start to download your container image until the ECS Task (Container) terminate.
      • e.g. 2 x (1vCPU, 4GB RAM) on Linux:
        • Per Hour: 2 x ($0,0665) = $0.133
        • Per Day: $3,18
        • Per 30 Days: $95.67
      • e.g. 2 x (1vCPU, 4GB RAM) on Win:
        • Per Hour: 2 x ($0,199) = $0.398
        • Per Day: $9.55
        • Per 30 Days: $286.56
    • Please have look for fargate pricing: https://aws.amazon.com/fargate/pricing/

Table of Contents

Prerequisite

Steps

Flask App Docker Image Creation

Creating ECR (Elastic Container Repository), Pushing Image into ECR

  • Create 0_ecr.tf:
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.16"
    }
  }
  required_version = ">= 1.2.0"
}

# Creating Elastic Container Repository for application
resource "aws_ecr_repository" "flask_app" {
  name = "flask-app"
}

Code: https://github.com/omerbsezer/Fast-Terraform/blob/main/samples/ecr-ecs-elb-vpc-ecsservice-container/ecr/0_ecr.tf

image

cd /ecr
terraform init
terraform plan
terraform apply
  • On AWS ECR:

    image

  • To see the pushing docker commands, click "View Push Commands"

aws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin <UserID>.dkr.ecr.eu-central-1.amazonaws.com
docker tag flask-app:latest <UserID>.ecr.eu-central-1.amazonaws.com/flask-app:latest
docker push <UserID>.dkr.ecr.eu-central-1.amazonaws.com/flask-app:latest
  • Image was pushed:

    image

  • On AWS ECR:

    image

Creating VPC (Virtual Private Cloud)

  • Create 1_vpc.tf:
# Internet Access -> IGW ->  Route Table -> Subnets
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.16"
    }
  }
  required_version = ">= 1.2.0"
}

provider "aws" {
	region = "eu-central-1"
}

resource "aws_vpc" "my_vpc" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  tags = {
    Name = "My VPC"
  }
}

resource "aws_subnet" "public_subnet_a" {
  availability_zone = "eu-central-1a"
  vpc_id            = aws_vpc.my_vpc.id
  cidr_block        = "10.0.0.0/24"
  tags = {
    Name = "Public Subnet A"
  }
}

resource "aws_subnet" "public_subnet_b" {
  availability_zone = "eu-central-1b"
  vpc_id            = aws_vpc.my_vpc.id
  cidr_block        = "10.0.1.0/24"
  tags = {
    Name = "Public Subnet B"
  }
}

resource "aws_subnet" "public_subnet_c" {
  availability_zone = "eu-central-1c"
  vpc_id            = aws_vpc.my_vpc.id
  cidr_block        = "10.0.2.0/24"
  tags = {
    Name = "Public Subnet C"
  }
}

resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.my_vpc.id
  tags = {
    Name = "My VPC - Internet Gateway"
  }
}

resource "aws_route_table" "route_table" {
    vpc_id = aws_vpc.my_vpc.id
    route {
        cidr_block = "0.0.0.0/0"
        gateway_id = aws_internet_gateway.igw.id
    }
    tags = {
        Name = "Public Subnet Route Table"
    }
}

resource "aws_route_table_association" "route_table_association1" {
    subnet_id      = aws_subnet.public_subnet_a.id
    route_table_id = aws_route_table.route_table.id
}

resource "aws_route_table_association" "route_table_association2" {
    subnet_id      = aws_subnet.public_subnet_b.id
    route_table_id = aws_route_table.route_table.id
}

resource "aws_route_table_association" "route_table_association3" {
    subnet_id      = aws_subnet.public_subnet_c.id
    route_table_id = aws_route_table.route_table.id
}

Code: https://github.com/omerbsezer/Fast-Terraform/blob/main/samples/ecr-ecs-elb-vpc-ecsservice-container/1_vpc.tf

image

Creating ECS (Elastic Container Service)

  • Create 2_ecs.tf:
# Getting data existed ECR
data "aws_ecr_repository" "flask_app" {
  name = "flask-app"
}

# Creating ECS Cluster
resource "aws_ecs_cluster" "my_cluster" {
  name = "my-cluster" # Naming the cluster
}

# Creating ECS Task
resource "aws_ecs_task_definition" "flask_app_task" {
  family                   = "flask-app-task" 
  container_definitions    = <<DEFINITION
  [
    {
      "name": "flask-app-task",
      "image": "${data.aws_ecr_repository.flask_app.repository_url}",
      "essential": true,
      "portMappings": [
        {
          "containerPort": 5000,
          "hostPort": 5000
        }
      ],
      "memory": 512,
      "cpu": 256
    }
  ]
  DEFINITION
  requires_compatibilities = ["FARGATE"] # Stating that we are using ECS Fargate
  network_mode             = "awsvpc"    # Using awsvpc as our network mode as this is required for Fargate
  memory                   = 512         # Specifying the memory our container requires
  cpu                      = 256         # Specifying the CPU our container requires
  execution_role_arn       = "${aws_iam_role.ecsTaskExecutionRole.arn}"
}

# Creating Role for ECS
resource "aws_iam_role" "ecsTaskExecutionRole" {
  name               = "ecsTaskExecutionRole"
  assume_role_policy = "${data.aws_iam_policy_document.assume_role_policy.json}"
}

data "aws_iam_policy_document" "assume_role_policy" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["ecs-tasks.amazonaws.com"]
    }
  }
}

# Role - Policy Attachment for ECS
resource "aws_iam_role_policy_attachment" "ecsTaskExecutionRole_policy" {
  role       = "${aws_iam_role.ecsTaskExecutionRole.name}"
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

Code: https://github.com/omerbsezer/Fast-Terraform/blob/main/samples/ecr-ecs-elb-vpc-ecsservice-container/2_ecs.tf

image

Creating ELB (Elastic Load Balancer)

  • Create 3_elb.tf:
# Internet Access -> IGW -> LB Security Groups -> Application Load Balancer  (Listener 80) -> Target Groups  -> ECS Service -> ECS SG -> Tasks on each subnets 

# Creating Load Balancer (LB)
resource "aws_alb" "application_load_balancer" {
  name               = "test-lb-tf" # Naming our load balancer
  load_balancer_type = "application"
  subnets = [ 
    "${aws_subnet.public_subnet_a.id}",
    "${aws_subnet.public_subnet_b.id}",
    "${aws_subnet.public_subnet_c.id}"
  ]
  # Referencing the security group
  security_groups = ["${aws_security_group.load_balancer_security_group.id}"]
}

# Creating a security group for LB
resource "aws_security_group" "load_balancer_security_group" {
  vpc_id      = aws_vpc.my_vpc.id
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"] # Allowing traffic in from all sources
  }

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

# Creating LB Target Group
resource "aws_lb_target_group" "target_group" {
  name        = "target-group"
  port        = 80
  protocol    = "HTTP"
  target_type = "ip"
  vpc_id      = "${aws_vpc.my_vpc.id}" 
}

# Creating LB Listener
resource "aws_lb_listener" "listener" {
  load_balancer_arn = "${aws_alb.application_load_balancer.arn}" # Referencing our load balancer
  port              = "80"
  protocol          = "HTTP"
  default_action {
    type             = "forward"
    target_group_arn = "${aws_lb_target_group.target_group.arn}" # Referencing our target group
  }
}

Code: https://github.com/omerbsezer/Fast-Terraform/blob/main/samples/ecr-ecs-elb-vpc-ecsservice-container/3_elb.tf

image

Creating ECS Service

  • Create 4_ecs_service.tf:
# Creating ECS Service
resource "aws_ecs_service" "my_first_service" {
  name            = "my-first-service"                             # Naming our first service
  cluster         = "${aws_ecs_cluster.my_cluster.id}"             # Referencing our created Cluster
  task_definition = "${aws_ecs_task_definition.flask_app_task.arn}" # Referencing the task our service will spin up
  launch_type     = "FARGATE"
  desired_count   = 3 # Setting the number of containers to 3

  load_balancer {
    target_group_arn = "${aws_lb_target_group.target_group.arn}" # Referencing our target group
    container_name   = "${aws_ecs_task_definition.flask_app_task.family}"
    container_port   = 5000 # Specifying the container port
  }

  network_configuration {
    subnets          = ["${aws_subnet.public_subnet_a.id}", "${aws_subnet.public_subnet_b.id}", "${aws_subnet.public_subnet_c.id}"]
    assign_public_ip = true                                                # Providing our containers with public IPs
    security_groups  = ["${aws_security_group.service_security_group.id}"] # Setting the security group
  }
}

# Creating SG for ECS Container Service, referencing the load balancer security group
resource "aws_security_group" "service_security_group" {
  vpc_id      = aws_vpc.my_vpc.id
  ingress {
    from_port = 0
    to_port   = 0
    protocol  = "-1"
    # Only allowing traffic in from the load balancer security group
    security_groups = ["${aws_security_group.load_balancer_security_group.id}"]
  }

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

#Log the load balancer app URL
output "app_url" {
  value = aws_alb.application_load_balancer.dns_name
}

Code: https://github.com/omerbsezer/Fast-Terraform/blob/main/samples/ecr-ecs-elb-vpc-ecsservice-container/4_ecs_service.tf

image

Demo: Terraform Run

  • Run:
terraform init
terraform validate
terraform plan
terraform apply

image

image

  • On AWS ECS Cluster:

    image

  • On AWS ECS Service (service runs on the cluster):

    image

  • ECS Service Tasks (task includes running 3 containers):

    image

  • Running Container Details (CPU, Memory Usage, Network, Environment Variable, Volume Configuration, Logs):

    image

  • On AWS EC2 > LoadBalancer:

    image

  • Target Groups:

    image

  • On AWS VPC:

    image

  • When go to the output of the ELB DNS: http://test-lb-tf-634023821.eu-central-1.elb.amazonaws.com/

    image

  • LB redirects 3 different containers. Each container has own SQLlite. So, each refresh to LB page, it shows 3 different page:

  • Container 0:

    image

  • Container 1:

    image

  • Container 2:

    image

  • Destroy the infra:

terraform destroy

image

  • Delete the ECR Repo:
cd ecr
terraform destroy

image