Some of the company's applications recently moved from Rancher to Fargate, which is understandable as the cloud resource and traffic will be very intensive only during a certain period (HSC exam), hence AWS serverless with Fargate can be a better option for such business mode so the rest of the year without the exam we can save costs significantly.
Hosting our blog on Fargate? Why not!In the past, I used to try different methods to host this blog:
Here I will use AWS Fargate, together with AWS ECR, Docker, Terraform and Github Action workflow to move this blog to AWS serverless compute for containers.
Terraform Provisioning
# Provider Configuration "provider.tf"
provider "aws" {
region = "ap-southeast-2"
}
# Create an ECR Repository "ecr.tf"
resource "aws_ecr_repository" "zackblog_repo" {
name = "zackblog-repo"
}
# Fargate Task Definition "task_definition.tf"
resource "aws_ecs_task_definition" "zackblog_task" {
family = "zackblog-task"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = "256"
memory = "512"
container_definitions = jsonencode([
{
name = "zackblog-container",
image = "${aws_ecr_repository.zackblog_repo.repository_url}:latest",
essential = true,
portMappings = [
{
containerPort = 80,
hostPort = 80,
protocol = "tcp"
}
]
}
])
}
# Create an ECS Cluster "cluster.tf"
resource "aws_ecs_cluster" "zackblog_cluster" {
name = "zackblog-cluster"
}
# Configure Networking to Use Default VPC - save cost haha
# use the data block to fetch existing resources
data "aws_vpc" "default" {
default = true
}
data "aws_subnet" "default" {
filter {
name = "vpc-id"
values = [data.aws_vpc.default.id]
}
}
resource "aws_security_group" "zackblog_sg" {
name_prefix = "zackblog-sg"
vpc_id = data.aws_vpc.default.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Define the ECS Service "service.tf"
resource "aws_ecs_service" "zackblog_service" {
name = "zackblog-service"
cluster = aws_ecs_cluster.zackblog_cluster.id
task_definition = aws_ecs_task_definition.zackblog_task.arn
desired_count = 1
launch_type = "FARGATE"
network_configuration {
subnets = [for subnet in data.aws_subnet.default : subnet.id]
security_groups = [aws_security_group.zackblog_sg.id]
assign_public_ip = true
}
}
# Configure Load Balancer and attach to Fargate service "load_balancer.tf"
resource "aws_lb" "zackblog_lb" {
name = "zackblog-lb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.zackblog_sg.id]
subnets = [for subnet in data.aws_subnet.default : subnet.id]
}
resource "aws_lb_target_group" "zackblog_tg" {
name = "zackblog-tg"
port = 80
protocol = "HTTP"
vpc_id = data.aws_vpc.default.id
}
resource "aws_lb_listener" "zackblog_listener" {
load_balancer_arn = aws_lb.zackblog_lb.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.zackblog_tg.arn
}
}
resource "aws_lb_target_group_attachment" "zackblog_tg_attachment" {
target_group_arn = aws_lb_target_group.zackblog_tg.arn
target_id = aws_ecs_service.zackblog_service.id
port = 80
}
Github Action Workflow for CICD
1. First we need to create Github Secret to contain dockerhub and aws credentials and some other vars:
AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION # xxx.dkr.ecr.ap-southeast-2.amazonaws.com ECR_REGISTRY # zackblog-repo ECR_REPOSITORY # zackblog-cluster ECS_CLUSTER # zackblog-service ECS_SERVICE
2. Then define the workflow to create /.github/workflows/zackblog-fargate.yaml, in this configure Github runner, it will:
Log in to Amazon ECR
Build and push Docker Image to the ECR repository
Deploy to ECS by updating the ECS service to use the new image by forcing a new deployment
name: Deploy to AWS Fargate
on:
push:
branches:
- editing # not main branch
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Amazon ECR
env:
AWS_REGION: ${{ secrets.AWS_REGION }}
run: |
aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin ${{ secrets.ECR_REGISTRY }}
- name: Build and push Docker image
env:
IMAGE_TAG: ${{ github.sha }}
ECR_REGISTRY: ${{ secrets.ECR_REGISTRY }}
ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
- name: Deploy to ECS
env:
AWS_REGION: ${{ secrets.AWS_REGION }}
ECS_CLUSTER: ${{ secrets.ECS_CLUSTER }}
ECS_SERVICE: ${{ secrets.ECS_SERVICE }}
ECR_REGISTRY: ${{ secrets.ECR_REGISTRY }}
ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }}
IMAGE_TAG: ${{ github.sha }}
run: |
aws ecs update-service --cluster $ECS_CLUSTER --service $ECS_SERVICE --force-new-deployment --region $AWS_REGION
Conclusion
Now we have a seamless incurvature as a code together with CICD pipeline to ensure that the "Zack's Blog" can be moved to AWS serverless container service Fargate. Every time I update the blog by committing changes to the "zack-gitops-project" editing branch, a new Docker image will be built, pushed to ECR, and the AWS Fargate service is automatically updated.