Skip to content

Commit

Permalink
add terraform infra -- ECS, networking, logs (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenelleman authored Oct 5, 2024
1 parent 8b7b9c5 commit 70af050
Show file tree
Hide file tree
Showing 10 changed files with 220 additions and 8 deletions.
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
FROM node:22-alpine
# https://stackoverflow.com/questions/65612411/forcing-docker-to-use-linux-amd64-platform-by-default-on-macos/69636473#69636473
FROM --platform=linux/amd64 node:22-alpine
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import express from "express";
import userRoutes from "./routes/user";
import chipRoutes from "./routes/chip";
import healthRoutes from "./routes/health";
import { FRONTEND_URL } from "./constants";

const cors = require("cors");
Expand All @@ -18,6 +19,7 @@ app.use(express.json());
// Routes
app.use("/api/user", userRoutes);
app.use("/api/chip", chipRoutes);
app.use("/api/health", healthRoutes);

const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
Expand Down
27 changes: 27 additions & 0 deletions apps/backend/src/routes/health/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import express, {Request, Response} from "express";
import {ErrorResponse} from "@types";
import {z} from "zod";

export const HealthResponse = z.object({
status: z.string(),
});

export type HealthResponse = z.infer<
typeof HealthResponse
>;

const router = express.Router();

router.get(
"/status",
async (
req: Request<{}, {}, null>,
res: Response<HealthResponse | ErrorResponse>
) => {
return res.status(200).json({
status: "live"
});
}
);

export default router;
13 changes: 8 additions & 5 deletions deployment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,17 @@ docker run -td -p 8080:8080 connections:1
export ACCOUNT_NUMBER="${AWS account number}"
export AWS_REGION="ap-southeast-1"
export ECR_REPO_NAME=${Repo name}
export $IMAGE-ID=${Image ID from docker image build}
export IMAGE_ID=${Image ID from docker image build}
aws ecr get-login-password --profile connections-admin-role --region $AWS_REGION
aws ecr get-login-password --profile connections-admin --region $AWS_REGION | docker login --username AWS --password-stdin $ACCOUNT_NUMBER.dkr.ecr.$AWS_REGION.amazonaws.com
docker login --username AWS --password-stdin $ACCOUNT_NUMBER.dkr.ecr.$AWS_REGION.amazonaws.com
docker tag $IMAGE_ID $ACCOUNT_NUMBER.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO_NAME:1
docker push $ACCOUNT_NUMBER.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO_NAME:1
```

docker tag $IMAGE-ID $ACCOUNT_NUMBER.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO_NAME:1`
docker push $ACCOUNT_NUMBER.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO_NAME:1`
### Manually Apply Specific Tag to Infra
```
terraform apply -var="image_tag=${tag number}" -auto-approve
```

### TODO
Expand Down
File renamed without changes.
96 changes: 96 additions & 0 deletions deployment/ecs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
module "cloudwatch_logs" {
source = "cloudposse/cloudwatch-logs/aws"
version = "0.6.6"

namespace = var.namespace
# stage = var.stage
name = var.name

retention_in_days = 7
}

module "container_definition" {
source = "cloudposse/ecs-container-definition/aws"
version = "0.58.1"

# container_name = "${var.namespace}-${var.stage}-${var.name}"
container_name = "${var.namespace}-${var.name}"
container_image = "${module.ecr.repository_url}:${var.image_tag}"
container_memory = 512 # optional for FARGATE launch type
container_cpu = 256 # optional for FARGATE launch type
essential = true
port_mappings = var.container_port_mappings

# The environment variables to pass to the container.
#environment = [
# {
# name = "ENV_NAME"
# value = "ENV_VALUE"
# },
#]

# Pull secrets from AWS Parameter Store.
# "name" is the name of the env var.
# "valueFrom" is the name of the secret in PS.
secrets = [
# {
# name = "SECRET_ENV_NAME"
# valueFrom = "SECRET_ENV_NAME"
# },
]

log_configuration = {
logDriver = "awslogs"
options = {
"awslogs-region" = var.region
"awslogs-group" = module.cloudwatch_logs.log_group_name
"awslogs-stream-prefix" = var.name
}
secretOptions = null
}
}

resource "aws_ecs_cluster" "ecs_cluster" {
# name = "${var.namespace}-${var.stage}-${var.name}"
name = "${var.namespace}-${var.name}"
tags = {
Namespace = var.namespace
# Stage = var.stage
Name = var.name
}
}

module "ecs_alb_service_task" {
source = "cloudposse/ecs-alb-service-task/aws"
version = "0.66.4"

namespace = var.namespace
# stage = var.stage
name = var.name

use_alb_security_group = true
alb_security_group = module.alb.security_group_id
container_definition_json = module.container_definition.json_map_encoded_list
ecs_cluster_arn = aws_ecs_cluster.ecs_cluster.arn
launch_type = "FARGATE"
vpc_id = module.vpc.vpc_id
security_group_ids = [module.vpc.vpc_default_security_group_id]
subnet_ids = module.subnets.private_subnet_ids # change to "module.subnets.public_subnet_ids" if "nat_gateway_enabled" is false
ignore_changes_task_definition = false
network_mode = "awsvpc"
assign_public_ip = false # change to true if "nat_gateway_enabled" is false
propagate_tags = "TASK_DEFINITION"
desired_count = var.desired_count
task_memory = 512
task_cpu = 256
force_new_deployment = true
container_port = var.container_port_mappings[0].containerPort

ecs_load_balancers = [{
# container_name = "${var.namespace}-${var.stage}-${var.name}"
container_name = "${var.namespace}-${var.name}"
container_port = var.container_port_mappings[0].containerPort
elb_name = ""
target_group_arn = module.alb.default_target_group_arn
}]
}
45 changes: 45 additions & 0 deletions deployment/networking.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
module "vpc" {
source = "cloudposse/vpc/aws"
version = "2.0.0"

namespace = var.namespace
# stage = var.stage
name = var.name

ipv4_primary_cidr_block = "10.0.0.0/16"
}

module "subnets" {
source = "cloudposse/dynamic-subnets/aws"
version = "2.0.4"

namespace = var.namespace
# stage = var.stage
name = var.name

availability_zones = ["ap-southeast-1a", "ap-southeast-1b", "ap-southeast-1c"] # change to your AZs
vpc_id = module.vpc.vpc_id
igw_id = [module.vpc.igw_id]
ipv4_cidr_block = [module.vpc.vpc_cidr_block]
nat_gateway_enabled = true
max_nats = 1
}

module "alb" {
source = "cloudposse/alb/aws"
version = "1.7.0"

namespace = var.namespace
# stage = var.stage
name = var.name

access_logs_enabled = false
vpc_id = module.vpc.vpc_id
ip_address_type = "ipv4"
subnet_ids = module.subnets.public_subnet_ids
security_group_ids = [module.vpc.vpc_default_security_group_id]
# https_enabled = true
# certificate_arn = aws_acm_certificate.cert.arn
# http_redirect = true
health_check_interval = 60
}
10 changes: 10 additions & 0 deletions deployment/outputs.tf
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
output "github_actions_role_arn" {
description = "The ARN of the role to be assumed by the GitHub Actions"
value = aws_iam_role.github_actions_role.arn
}

output "alb_dns_name" {
description = "DNS name of ALB"
value = module.alb.alb_dns_name
}

output "ecr_repository_name" {
description = "The name of the ECR Repository"
value = module.ecr.repository_name
}
2 changes: 1 addition & 1 deletion deployment/provider.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ terraform {
}

provider "aws" {
region = "${var.aws_region}"
region = "${var.region}"

default_tags {
tags = {
Expand Down
30 changes: 29 additions & 1 deletion deployment/variables.tf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
variable "aws_region" {
variable "region" {
type = string
default = "ap-southeast-1" // Singapore
description = "aws region for current resource"
Expand Down Expand Up @@ -26,4 +26,32 @@ variable "stage" {
type = string
default = "prod"
description = "Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'"
}

variable "image_tag" {
type = string
default = "latest"
description = "Docker image tag"
}

variable "container_port_mappings" {
type = list(object({
containerPort = number
hostPort = number
protocol = string
}))
default = [
{
containerPort = 8080
hostPort = 8080
protocol = "tcp"
}
]
description = "The port mappings to configure for the container. This is a list of maps. Each map should contain \"containerPort\", \"hostPort\", and \"protocol\", where \"protocol\" is one of \"tcp\" or \"udp\". If using containers in a task with the awsvpc or host network mode, the hostPort can either be left blank or set to the same value as the containerPort"
}

variable "desired_count" {
type = number
description = "The number of instances of the task definition to place and keep running"
default = 1
}

0 comments on commit 70af050

Please sign in to comment.