Skip to content
This repository has been archived by the owner on Dec 16, 2024. It is now read-only.

Commit

Permalink
feat: implements backend infra and cd methods
Browse files Browse the repository at this point in the history
  • Loading branch information
jspark2000 committed Feb 23, 2024
1 parent a80928e commit 6cb4e3c
Show file tree
Hide file tree
Showing 25 changed files with 793 additions and 14 deletions.
80 changes: 80 additions & 0 deletions .github/workflows/CD.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: CD - production

on:
workflow_dispatch:

env:
AWS_REGION: ap-northeast-2
ECS_CLUSTER: skku_royals_api
ECS_SERVICE: skku-royals-ecs-service

permissions:
id-token: write
contents: read

jobs:
build-api:
name: Build api image
runs-on: ubuntu-latest
steps:
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3

- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_GITHUB_ACTION_ROLE }}
aws-region: ${{ env.AWS_REGION }}

- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2

- name: Build and push image
uses: docker/build-push-action@v5
with:
file: ./backend/Dockerfile
push: true
tags: ${{ steps.login-ecr.outputs.registry }}/skku-royals/api:latest

- name: Update ECS service
run: aws ecs update-service --cluster ${{ env.ECS_CLUSTER }} --service ${{ env.ECS_SERVICE }} --force-new-deployment

deploy:
name: Deploy
runs-on: ubuntu-latest
needs: [build-api]
environment: production
defaults:
run:
shell: bash

steps:
- uses: actions/checkout@v4

- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_GITHUB_ACTION_ROLE }}
aws-region: ${{ env.AWS_REGION }}

- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.5.2

- name: Create Terraform variable file
working-directory: ./aws
run: |
echo "$TFVARS" >> terraform.tfvars
env:
TFVARS: ${{ secrets.TFVARS }}

- name: Terraform Init
working-directory: ./aws
run: terraform init

- name: Terraform Plan
working-directory: ./aws
run: terraform plan -input=false

- name: Terraform Apply
working-directory: ./aws
run: terraform apply -auto-approve -input=false
3 changes: 3 additions & 0 deletions aws/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ module "royals" {

region = var.region
vercel_origin_dns = var.vercel_origin_dns
postgres_password = var.postgres_password
postgres_username = var.postgres_username
jwt_secret = var.jwt_secret
}
25 changes: 25 additions & 0 deletions aws/royals/ec2.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
resource "aws_instance" "database" {
ami = "ami-0c28dbbd4ed200038"
instance_type = "t4g.small"
subnet_id = aws_subnet.private_1.id
vpc_security_group_ids = [aws_security_group.ec2.id]

user_data = templatefile("${path.module}/postgres-docker.sh.tpl", { postgres_password = var.postgres_password })

tags = {
Name = "PostgrSQL EC2 Instance"
}
}

resource "aws_instance" "cache" {
ami = "ami-0c28dbbd4ed200038"
instance_type = "t4g.micro"
subnet_id = aws_subnet.private_2.id
vpc_security_group_ids = [aws_security_group.ec2.id]

user_data = templatefile("${path.module}/redis-docker.sh.tpl", {})

tags = {
Name = "Redis EC2 Instance"
}
}
42 changes: 42 additions & 0 deletions aws/royals/ecr.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
########## ECR Repository ##########
resource "aws_ecr_repository" "main" {
name = "skku-royals/api"
image_tag_mutability = "MUTABLE"
}

########## ECR Policy ##########
resource "aws_ecr_lifecycle_policy" "main" {
repository = aws_ecr_repository.main.name

policy = <<EOF
{
"rules": [
{
"rulePriority": 1,
"description": "Keep image deployed with tag latest",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": ["latest"],
"countType": "imageCountMoreThan",
"countNumber": 1
},
"action": {
"type": "expire"
}
},
{
"rulePriority": 2,
"description": "Keep last 2 any images",
"selection": {
"tagStatus": "any",
"countType": "imageCountMoreThan",
"countNumber": 2
},
"action": {
"type": "expire"
}
}
]
}
EOF
}
172 changes: 172 additions & 0 deletions aws/royals/ecs-api.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
########## Application Load Balancer ##########
resource "aws_lb" "api" {
name = "skku-royals-elb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.elb.id]
subnets = [aws_subnet.public_1.id, aws_subnet.public_2.id]
enable_http2 = true
}

resource "aws_lb_listener" "api" {
load_balancer_arn = aws_lb.api.arn
port = "80"
protocol = "HTTP"

default_action {
type = "forward"
target_group_arn = aws_lb_target_group.api.arn
}
}

resource "aws_lb_target_group" "api" {
name = "skku-royals-elb-tg"
target_type = "ip"
port = 4000
protocol = "HTTP"
vpc_id = aws_vpc.main.id

health_check {
interval = 30
path = "/api"
healthy_threshold = 3
unhealthy_threshold = 3
matcher = "200-404"
}

lifecycle {
create_before_destroy = true
}
}

########## ECS Service ##########
resource "aws_ecs_service" "main" {
name = "skku-royals-ecs-service"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.api.arn
desired_count = 2
launch_type = "FARGATE"
health_check_grace_period_seconds = 300

network_configuration {
assign_public_ip = true
security_groups = [aws_security_group.ecs.id]
subnets = [aws_subnet.public_1.id, aws_subnet.public_2.id]
}

load_balancer {
target_group_arn = aws_lb_target_group.api.arn
container_name = "skku-royals-api"
container_port = 4000
}

depends_on = [
aws_lb_listener.api
]
}

###################### ECS Task Definition ######################
resource "aws_ecs_task_definition" "api" {
family = "skku-royals-api"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = 256
memory = 512

container_definitions = templatefile("${path.module}/task-definition.tftpl", {
task_name = "skku-royals-api",
database_url = "postgresql://${var.postgres_username}:${var.postgres_password}@${aws_instance.database.private_ip}:${var.postgres_port}/royals?schema=public",
ecr_uri = aws_ecr_repository.main.repository_url,
container_port = 4000,
cloudwatch_region = var.region,
redis_host = aws_instance.cache.private_ip,
redis_port = var.redis_port,
jwt_secret = var.jwt_secret
})

execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
task_role_arn = aws_iam_role.ecs_task_role.arn
}

########## ECS Service Scaling ##########
resource "aws_appautoscaling_target" "api" {
max_capacity = 2
min_capacity = 2
resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.main.name}"
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
}

resource "aws_appautoscaling_policy" "ecs_policy_up" {
name = "ecs-auto-scaling-policy-up"
policy_type = "StepScaling"
resource_id = aws_appautoscaling_target.api.resource_id
scalable_dimension = aws_appautoscaling_target.api.scalable_dimension
service_namespace = aws_appautoscaling_target.api.service_namespace

step_scaling_policy_configuration {
adjustment_type = "ChangeInCapacity"
cooldown = 60
metric_aggregation_type = "Average"

step_adjustment {
metric_interval_lower_bound = 0
scaling_adjustment = 1
}
}
}

resource "aws_cloudwatch_metric_alarm" "ecs_cpu_utilization_high" {
alarm_name = "ecs-cpu-utilization-high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "CPUUtilization"
namespace = "AWS/ECS"
period = "60"
statistic = "Average"
threshold = "80"

dimensions = {
ClusterName = aws_ecs_cluster.main.name
ServiceName = aws_ecs_service.main.name
}

alarm_actions = [aws_appautoscaling_policy.ecs_policy_up.arn]
}

resource "aws_appautoscaling_policy" "ecs_policy_down" {
name = "ecs-auto-scaling-policy-down"
policy_type = "StepScaling"
resource_id = aws_appautoscaling_target.api.resource_id
scalable_dimension = aws_appautoscaling_target.api.scalable_dimension
service_namespace = aws_appautoscaling_target.api.service_namespace

step_scaling_policy_configuration {
adjustment_type = "ChangeInCapacity"
cooldown = 60
metric_aggregation_type = "Average"

step_adjustment {
metric_interval_upper_bound = 0
scaling_adjustment = -1
}
}
}

resource "aws_cloudwatch_metric_alarm" "ecs_cpu_utilization_low" {
alarm_name = "ecs-cpu-utilization-low"
comparison_operator = "LessThanThreshold"
evaluation_periods = "2"
metric_name = "CPUUtilization"
namespace = "AWS/ECS"
period = "60"
statistic = "Average"
threshold = "30"

dimensions = {
ClusterName = aws_ecs_cluster.main.name
ServiceName = aws_ecs_service.main.name
}

alarm_actions = [aws_appautoscaling_policy.ecs_policy_down.arn]
}
Loading

0 comments on commit 6cb4e3c

Please sign in to comment.