├── deploy ├── terraform │ ├── README.md │ ├── versions.tf │ ├── ecs.tf │ ├── outputs.tf │ ├── terraform.tfstate │ ├── provider.tf │ ├── webcore-logs.tf │ ├── templates │ │ └── webcore_app.json.tpl │ ├── roles.tf │ ├── webcore-alb.tf │ ├── security.tf │ ├── network.tf │ ├── variables.tf │ ├── webcore-ecs-service.tf │ ├── webcore-auto-scaling.tf │ └── terraform.tfstate.backup └── create_repo_ecr.sh ├── test-app ├── .prettierrc ├── nest-cli.json ├── tsconfig.build.json ├── src │ ├── app.service.ts │ ├── main.ts │ ├── app.module.ts │ ├── app.controller.ts │ └── app.controller.spec.ts ├── test │ ├── jest-e2e.json │ └── app.e2e-spec.ts ├── tsconfig.json ├── Dockerfile ├── tslint.json ├── .gitignore ├── package.json └── README.md ├── README.md └── .gitignore /deploy/terraform/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /deploy/terraform/versions.tf: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | required_version = ">= 0.12" 4 | } 5 | -------------------------------------------------------------------------------- /test-app/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aws-ecs-app 2 | An application example to deploy in ECS with terraform script. 3 | 4 | 5 | -------------------------------------------------------------------------------- /deploy/terraform/ecs.tf: -------------------------------------------------------------------------------- 1 | # ecs.tf 2 | resource "aws_ecs_cluster" "main" { 3 | name = "${var.prefix}-api-cluster" 4 | } 5 | -------------------------------------------------------------------------------- /deploy/terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | # outputs.tf 2 | 3 | output "webcore_alb_hostname" { 4 | value = aws_alb.webcore_lb.dns_name 5 | } 6 | 7 | -------------------------------------------------------------------------------- /test-app/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /test-app/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /deploy/terraform/terraform.tfstate: -------------------------------------------------------------------------------- 1 | { 2 | "version": 4, 3 | "terraform_version": "0.12.21", 4 | "serial": 62, 5 | "lineage": "ee9edfbb-4848-3190-6ab4-3ed9a4ead573", 6 | "outputs": {}, 7 | "resources": [] 8 | } 9 | -------------------------------------------------------------------------------- /test-app/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | 4 | async function bootstrap() { 5 | const app = await NestFactory.create(AppModule); 6 | await app.listen(80); 7 | } 8 | bootstrap(); 9 | -------------------------------------------------------------------------------- /test-app/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /deploy/terraform/provider.tf: -------------------------------------------------------------------------------- 1 | # provider.tf 2 | 3 | # Specify the provider and access details 4 | provider "aws" { 5 | shared_credentials_file = "$HOME/.aws/credentials" 6 | profile = "default" 7 | region = var.aws_region 8 | } 9 | 10 | -------------------------------------------------------------------------------- /test-app/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | @Module({ 6 | imports: [], 7 | controllers: [AppController], 8 | providers: [AppService], 9 | }) 10 | export class AppModule {} 11 | -------------------------------------------------------------------------------- /test-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "target": "es2017", 9 | "sourceMap": true, 10 | "outDir": "./dist", 11 | "baseUrl": "./", 12 | "incremental": true 13 | }, 14 | "exclude": ["node_modules", "dist"] 15 | } 16 | -------------------------------------------------------------------------------- /test-app/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | 13 | @Get('/index') 14 | getIndexHello(): string { 15 | return this.appService.getHello(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test-app/Dockerfile: -------------------------------------------------------------------------------- 1 | # same version as AWS Lambda 2 | # FROM node:13.4.0-buster-slim 3 | FROM node:14.2.0-buster-slim 4 | 5 | RUN mkdir -p /test-app 6 | 7 | WORKDIR /test-app 8 | 9 | COPY package*.json ./ 10 | 11 | RUN npm install 12 | 13 | COPY . . 14 | 15 | RUN npm run build 16 | 17 | WORKDIR ./ 18 | 19 | # EXPOSE 9001 20 | EXPOSE 80 21 | 22 | RUN mkdir /var/logs 23 | # run app 24 | # CMD ["node" , "./dist/main.js"] 25 | 26 | CMD ["node" , "./dist/main.js", "2>server.log"] 27 | -------------------------------------------------------------------------------- /deploy/terraform/webcore-logs.tf: -------------------------------------------------------------------------------- 1 | # logs.tf 2 | # Set up CloudWatch group and log stream and retain logs for 30 days 3 | resource "aws_cloudwatch_log_group" "webcore_log_group" { 4 | name = "/ecs/webcore" 5 | retention_in_days = 30 6 | 7 | tags = { 8 | Name = "webcore-log-group" 9 | } 10 | } 11 | 12 | resource "aws_cloudwatch_log_stream" "cb_log_stream" { 13 | name = "webcore-log-stream" 14 | log_group_name = aws_cloudwatch_log_group.webcore_log_group.name 15 | } 16 | -------------------------------------------------------------------------------- /test-app/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": { 5 | "no-unused-expression": true 6 | }, 7 | "rules": { 8 | "quotemark": [true, "single"], 9 | "member-access": [false], 10 | "ordered-imports": [false], 11 | "max-line-length": [true, 150], 12 | "member-ordering": [false], 13 | "interface-name": [false], 14 | "arrow-parens": false, 15 | "object-literal-sort-keys": false 16 | }, 17 | "rulesDirectory": [] 18 | } 19 | -------------------------------------------------------------------------------- /test-app/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | */dist 4 | /node_modules 5 | */node_modules 6 | 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # OS 16 | .DS_Store 17 | 18 | # Tests 19 | /coverage 20 | /.nyc_output 21 | 22 | # IDEs and editors 23 | /.idea 24 | .project 25 | .classpath 26 | .c9/ 27 | *.launch 28 | .settings/ 29 | *.sublime-workspace 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json 37 | 38 | -------------------------------------------------------------------------------- /deploy/terraform/templates/webcore_app.json.tpl: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "${container_name}", 4 | "image": "${app_image}", 5 | "cpu": ${fargate_cpu}, 6 | "memory": ${fargate_memory}, 7 | "networkMode": "awsvpc", 8 | "logConfiguration": { 9 | "logDriver": "awslogs", 10 | "options": { 11 | "awslogs-group": "/ecs/webcore", 12 | "awslogs-region": "${aws_region}", 13 | "awslogs-stream-prefix": "ecs" 14 | } 15 | }, 16 | "portMappings": [ 17 | { 18 | "containerPort": ${app_port}, 19 | "hostPort": ${app_port} 20 | } 21 | ] 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /test-app/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test-app/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /deploy/terraform/roles.tf: -------------------------------------------------------------------------------- 1 | # ECS task execution role data 2 | data "aws_iam_policy_document" "webcore_ecs_task_execution_role" { 3 | version = "2012-10-17" 4 | statement { 5 | sid = "" 6 | effect = "Allow" 7 | actions = ["sts:AssumeRole"] 8 | 9 | principals { 10 | type = "Service" 11 | identifiers = ["ecs-tasks.amazonaws.com"] 12 | } 13 | } 14 | } 15 | 16 | # ECS task execution role 17 | resource "aws_iam_role" "webcore_ecs_task_execution_role" { 18 | name = var.ecs_task_execution_role_name 19 | assume_role_policy = data.aws_iam_policy_document.webcore_ecs_task_execution_role.json 20 | } 21 | 22 | # ECS task execution role policy attachment 23 | resource "aws_iam_role_policy_attachment" "webcore_task_execution_role" { 24 | role = aws_iam_role.webcore_ecs_task_execution_role.name 25 | policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" 26 | } 27 | -------------------------------------------------------------------------------- /deploy/terraform/webcore-alb.tf: -------------------------------------------------------------------------------- 1 | # alb.tf 2 | resource "aws_alb" "webcore_lb" { 3 | name = "dev-webcore" 4 | subnets = [data.aws_subnet.public_1a.id,data.aws_subnet.public_1b.id,data.aws_subnet.public_1c.id] 5 | security_groups = [aws_security_group.lb.id] 6 | } 7 | 8 | resource "aws_alb_target_group" "webcore_target" { 9 | name = "ops-reader-target-group" 10 | port = 80 11 | protocol = "HTTP" 12 | vpc_id = data.aws_vpc.main.id 13 | target_type = "ip" 14 | 15 | health_check { 16 | healthy_threshold = "3" 17 | interval = "30" 18 | protocol = "HTTP" 19 | matcher = "200" 20 | timeout = "3" 21 | path = var.webcore_health_check_path 22 | unhealthy_threshold = "2" 23 | } 24 | } 25 | 26 | # Redirect all traffic from the ALB to the target group 27 | resource "aws_alb_listener" "front_end_webcore" { 28 | load_balancer_arn = aws_alb.webcore_lb.id 29 | port = var.app_port 30 | protocol = "HTTP" 31 | 32 | default_action { 33 | target_group_arn = aws_alb_target_group.webcore_target.id 34 | type = "forward" 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /deploy/terraform/security.tf: -------------------------------------------------------------------------------- 1 | # security.tf 2 | 3 | # ALB Security Group: Edit to restrict access to the application 4 | resource "aws_security_group" "lb" { 5 | name = "${var.prefix}-load-balancer-security-group" 6 | description = "controls access to the ALB" 7 | vpc_id = data.aws_vpc.main.id 8 | 9 | ingress { 10 | protocol = "tcp" 11 | from_port = var.app_port 12 | to_port = var.app_port 13 | cidr_blocks = ["0.0.0.0/0"] 14 | } 15 | 16 | egress { 17 | protocol = "-1" 18 | from_port = 0 19 | to_port = 0 20 | cidr_blocks = ["0.0.0.0/0"] 21 | } 22 | } 23 | 24 | # Traffic to the ECS cluster should only come from the ALB 25 | resource "aws_security_group" "ecs_tasks" { 26 | name = "${var.prefix}-ecs-tasks-security-group" 27 | description = "allow inbound access from the ALB only" 28 | vpc_id = data.aws_vpc.main.id 29 | 30 | ingress { 31 | protocol = "tcp" 32 | from_port = var.app_port 33 | to_port = var.app_port 34 | security_groups = [aws_security_group.lb.id] 35 | } 36 | 37 | egress { 38 | protocol = "-1" 39 | from_port = 0 40 | to_port = 0 41 | cidr_blocks = ["0.0.0.0/0"] 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /deploy/terraform/network.tf: -------------------------------------------------------------------------------- 1 | # network.tf without elp 2 | 3 | data "aws_vpc" "main" { 4 | filter { 5 | name = "tag:Name" 6 | values = ["dev-data-stream-vpc"] 7 | } 8 | } 9 | 10 | 11 | data "aws_subnet" "public_1a" { 12 | filter { 13 | name = "tag:Name" 14 | values = ["dev-data-stream-public-subnet"] 15 | } 16 | 17 | filter { 18 | name = "availability-zone" 19 | values = ["ap-southeast-1a"] # check zone 20 | } 21 | } 22 | 23 | data "aws_subnet" "public_1b" { 24 | filter { 25 | name = "tag:Name" 26 | values = ["dev-data-stream-public-subnet"] 27 | } 28 | 29 | filter { 30 | name = "availability-zone" 31 | values = ["ap-southeast-1b"] # check zone 32 | } 33 | } 34 | 35 | 36 | data "aws_subnet" "public_1c" { 37 | filter { 38 | name = "tag:Name" 39 | values = ["dev-data-stream-public-subnet"] 40 | } 41 | 42 | filter { 43 | name = "availability-zone" 44 | values = ["ap-southeast-1c"] # check zone 45 | } 46 | } 47 | 48 | # print vpc and subnet ids 49 | output "aws_vpc_name" { 50 | value = data.aws_vpc.main.id 51 | } 52 | 53 | output "aws_subnet_1a" { 54 | value = data.aws_subnet.public_1a.id 55 | } 56 | 57 | output "aws_subnet_1b" { 58 | value = data.aws_subnet.public_1b.id 59 | } 60 | 61 | output "aws_subnet_1c" { 62 | value = data.aws_subnet.public_1c.id 63 | } 64 | -------------------------------------------------------------------------------- /deploy/create_repo_ecr.sh: -------------------------------------------------------------------------------- 1 | cd .. 2 | 3 | # test-app is folder location of application 4 | cd test-app 5 | 6 | PACKAGE_VERSION=$(node -p -e "require('./package.json').version") 7 | 8 | APP_NAME=test-app 9 | ECR_REPO_NAME=test-app-repo 10 | 11 | # aws configuration - SET ACCESS_KEY , ACCESS_SECRET_KEY ,AWS_RETION, and PROFILE 12 | aws configure set aws_access_key_id XXXXXXXXXXXXXXXXXXXXXXXXX --profile dev 13 | aws configure set aws_secret_access_key XXXXXXXXXXXXXXXXXXXXXXXXX --profile dev 14 | aws configure set region ap-southeast-1 --profile dev 15 | aws configure set output json --profile dev 16 | 17 | export AWS_REGION=ap-southeast-1 18 | export AWS_PROFILE=dev 19 | 20 | # create repository 21 | aws ecr create-repository --repository-name "$ECR_REPO_NAME" 22 | 23 | echo "ECR repo created and get the Repo Name from here." 24 | 25 | docker build -t "$APP_NAME":"$PACKAGE_VERSION" . 26 | 27 | # ECR Repository Path = 466551463358.dkr.ecr.ap-southeast-1.amazonaws.com/dev-ops-reader:2.0.0 28 | 29 | echo "docker image created with package version :$PACKAGE_VERSION" 30 | 31 | # XXXXXX relace with ECR Id 32 | docker tag "$APP_NAME:$PACKAGE_VERSION" "XXXXXXXXXXXXXXXXXXXXXXXXX.dkr.ecr.ap-southeast-1.amazonaws.com/$ECR_REPO_NAME:$PACKAGE_VERSION" 33 | 34 | $(aws ecr get-login --no-include-email --no-include-email | sed 's|https://||') 35 | 36 | docker push "XXXXXXXXXXXXXXXXXXXXXXXXX.dkr.ecr.ap-southeast-1.amazonaws.com/$ECR_REPO_NAME:$PACKAGE_VERSION" 37 | -------------------------------------------------------------------------------- /deploy/terraform/variables.tf: -------------------------------------------------------------------------------- 1 | # variables.tf 2 | 3 | variable "prefix" { 4 | description = "Prefix for all envirnoments" 5 | default = "webcore" 6 | } 7 | 8 | variable "aws_region" { 9 | description = "The AWS region things are created in" 10 | default = "ap-southeast-1" 11 | } 12 | 13 | variable "ecs_task_execution_role_name" { 14 | description = "ECS task execution role name" 15 | default = "WebCoreEcsTaskExecutionRole" 16 | } 17 | 18 | variable "az_count" { 19 | description = "Number of AZs to cover in a given region" 20 | default = "3" 21 | } 22 | 23 | variable "app_port" { 24 | description = "Port exposed by the docker image to redirect traffic to" 25 | default = 80 26 | } 27 | 28 | variable "app_count" { 29 | description = "Number of docker containers to run" 30 | default = 1 31 | } 32 | 33 | #### Define ECR Location Path 34 | variable "webcore_image" { 35 | description = "Docker image to run in the ECS cluster" 36 | default = "XXXXXXXXXXX.dkr.ecr.ap-southeast-1.amazonaws.com/test-app-repo:1.0.0" 37 | } 38 | 39 | # Make sure Health Check Path 40 | variable "webcore_health_check_path" { 41 | default = "/index" 42 | } 43 | 44 | variable "fargate_cpu" { 45 | description = "Fargate instance CPU units to provision (1 vCPU = 1024 CPU units)" 46 | default = "1024" 47 | } 48 | 49 | variable "fargate_memory" { 50 | description = "Fargate instance memory to provision (in MiB)" 51 | default = "2048" 52 | } 53 | 54 | variable "webcore_container_name" { 55 | description = "Ops Reader container name" 56 | default = "test-app" 57 | } 58 | -------------------------------------------------------------------------------- /deploy/terraform/webcore-ecs-service.tf: -------------------------------------------------------------------------------- 1 | data "template_file" "webcore_app" { 2 | template = file("./templates/webcore_app.json.tpl") 3 | 4 | vars = { 5 | app_image = var.webcore_image 6 | app_port = var.app_port 7 | fargate_cpu = var.fargate_cpu 8 | fargate_memory = var.fargate_memory 9 | aws_region = var.aws_region 10 | prefix = var.prefix 11 | container_name = var.webcore_container_name 12 | } 13 | } 14 | 15 | 16 | resource "aws_ecs_task_definition" "webcore_task_def" { 17 | family = "webcore-task" 18 | execution_role_arn = aws_iam_role.webcore_ecs_task_execution_role.arn 19 | network_mode = "awsvpc" 20 | requires_compatibilities = [ 21 | "FARGATE"] 22 | cpu = var.fargate_cpu 23 | memory = var.fargate_memory 24 | container_definitions = data.template_file.webcore_app.rendered 25 | } 26 | 27 | 28 | resource "aws_ecs_service" "webcore_main" { 29 | name = "webcore-service" 30 | cluster = aws_ecs_cluster.main.id 31 | task_definition = aws_ecs_task_definition.webcore_task_def.arn 32 | desired_count = var.app_count 33 | launch_type = "FARGATE" 34 | 35 | network_configuration { 36 | security_groups = [ 37 | aws_security_group.ecs_tasks.id] 38 | subnets = [ 39 | data.aws_subnet.public_1a.id, 40 | data.aws_subnet.public_1b.id, 41 | data.aws_subnet.public_1c.id 42 | ] 43 | assign_public_ip = true 44 | } 45 | 46 | load_balancer { 47 | target_group_arn = aws_alb_target_group.webcore_target.id 48 | container_name = var.webcore_container_name 49 | container_port = var.app_port 50 | } 51 | 52 | depends_on = [ 53 | aws_alb_listener.front_end_webcore, 54 | aws_iam_role_policy_attachment.webcore_task_execution_role] 55 | } 56 | -------------------------------------------------------------------------------- /test-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "", 6 | "license": "MIT", 7 | "scripts": { 8 | "prebuild": "rimraf dist", 9 | "build": "nest build", 10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 11 | "start": "nest start", 12 | "start:dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/main", 15 | "lint": "tslint -p tsconfig.json -c tslint.json", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@nestjs/common": "^6.7.2", 24 | "@nestjs/core": "^6.7.2", 25 | "@nestjs/platform-express": "^6.7.2", 26 | "reflect-metadata": "^0.1.13", 27 | "rimraf": "^3.0.0", 28 | "rxjs": "^6.5.3" 29 | }, 30 | "devDependencies": { 31 | "@nestjs/cli": "^6.9.0", 32 | "@nestjs/schematics": "^6.7.0", 33 | "@nestjs/testing": "^6.7.1", 34 | "@types/express": "^4.17.1", 35 | "@types/jest": "^24.0.18", 36 | "@types/node": "^12.7.5", 37 | "@types/supertest": "^2.0.8", 38 | "jest": "^24.9.0", 39 | "prettier": "^1.18.2", 40 | "supertest": "^4.0.2", 41 | "ts-jest": "^24.1.0", 42 | "ts-loader": "^6.1.1", 43 | "ts-node": "^8.4.1", 44 | "tsconfig-paths": "^3.9.0", 45 | "tslint": "^5.20.0", 46 | "typescript": "^3.6.3" 47 | }, 48 | "jest": { 49 | "moduleFileExtensions": [ 50 | "js", 51 | "json", 52 | "ts" 53 | ], 54 | "rootDir": "src", 55 | "testRegex": ".spec.ts$", 56 | "transform": { 57 | "^.+\\.(t|j)s$": "ts-jest" 58 | }, 59 | "coverageDirectory": "./coverage", 60 | "testEnvironment": "node" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /deploy/terraform/webcore-auto-scaling.tf: -------------------------------------------------------------------------------- 1 | # auto_scaling.tf 2 | 3 | resource "aws_appautoscaling_target" "webcore_target" { 4 | service_namespace = "ecs" 5 | resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.webcore_main.name}" 6 | scalable_dimension = "ecs:service:DesiredCount" 7 | min_capacity = 1 8 | max_capacity = 6 9 | } 10 | 11 | # Automatically scale capacity up by one 12 | resource "aws_appautoscaling_policy" "webcore_up" { 13 | name = "webcore_scale_up" 14 | service_namespace = "ecs" 15 | resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.webcore_main.name}" 16 | scalable_dimension = "ecs:service:DesiredCount" 17 | 18 | step_scaling_policy_configuration { 19 | adjustment_type = "ChangeInCapacity" 20 | cooldown = 60 21 | metric_aggregation_type = "Maximum" 22 | 23 | step_adjustment { 24 | metric_interval_lower_bound = 0 25 | scaling_adjustment = 1 26 | } 27 | } 28 | 29 | depends_on = [aws_appautoscaling_target.webcore_target] 30 | } 31 | 32 | # Automatically scale capacity down by one 33 | resource "aws_appautoscaling_policy" "webcore_down" { 34 | name = "webcore_scale_down" 35 | service_namespace = "ecs" 36 | resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.webcore_main.name}" 37 | scalable_dimension = "ecs:service:DesiredCount" 38 | 39 | step_scaling_policy_configuration { 40 | adjustment_type = "ChangeInCapacity" 41 | cooldown = 60 42 | metric_aggregation_type = "Maximum" 43 | 44 | step_adjustment { 45 | metric_interval_upper_bound = 0 46 | scaling_adjustment = -1 47 | } 48 | } 49 | 50 | depends_on = [aws_appautoscaling_target.webcore_target] 51 | } 52 | 53 | # CloudWatch alarm that triggers the autoscaling up policy 54 | resource "aws_cloudwatch_metric_alarm" "webcore_service_cpu_high" { 55 | alarm_name = "webcore_cpu_utilization_high" 56 | comparison_operator = "GreaterThanOrEqualToThreshold" 57 | evaluation_periods = "2" 58 | metric_name = "CPUUtilization" 59 | namespace = "AWS/ECS" 60 | period = "60" 61 | statistic = "Average" 62 | threshold = "85" 63 | 64 | dimensions = { 65 | ClusterName = aws_ecs_cluster.main.name 66 | ServiceName = aws_ecs_service.webcore_main.name 67 | } 68 | 69 | alarm_actions = [aws_appautoscaling_policy.webcore_up.arn] 70 | } 71 | 72 | # CloudWatch alarm that triggers the autoscaling down policy 73 | resource "aws_cloudwatch_metric_alarm" "webcore_service_cpu_low" { 74 | alarm_name = "webcore_cpu_utilization_low" 75 | comparison_operator = "LessThanOrEqualToThreshold" 76 | evaluation_periods = "2" 77 | metric_name = "CPUUtilization" 78 | namespace = "AWS/ECS" 79 | period = "60" 80 | statistic = "Average" 81 | threshold = "10" 82 | 83 | dimensions = { 84 | ClusterName = aws_ecs_cluster.main.name 85 | ServiceName = aws_ecs_service.webcore_main.name 86 | } 87 | 88 | alarm_actions = [aws_appautoscaling_policy.webcore_down.arn] 89 | } 90 | 91 | -------------------------------------------------------------------------------- /test-app/README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master 6 | [travis-url]: https://travis-ci.org/nestjs/nest 7 | [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux 8 | [linux-url]: https://travis-ci.org/nestjs/nest 9 | 10 |

A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.

11 |

12 | NPM Version 13 | Package License 14 | NPM Downloads 15 | Travis 16 | Linux 17 | Coverage 18 | Gitter 19 | Backers on Open Collective 20 | Sponsors on Open Collective 21 | 22 | 23 |

24 | 26 | 27 | ## Description 28 | 29 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 30 | 31 | ## Installation 32 | 33 | ```bash 34 | $ npm install 35 | ``` 36 | 37 | ## Running the app 38 | 39 | ```bash 40 | # development 41 | $ npm run start 42 | 43 | # watch mode 44 | $ npm run start:dev 45 | 46 | # production mode 47 | $ npm run start:prod 48 | ``` 49 | 50 | ## Test 51 | 52 | ```bash 53 | # unit tests 54 | $ npm run test 55 | 56 | # e2e tests 57 | $ npm run test:e2e 58 | 59 | # test coverage 60 | $ npm run test:cov 61 | ``` 62 | 63 | ## Support 64 | 65 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 66 | 67 | ## Stay in touch 68 | 69 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 70 | - Website - [https://nestjs.com](https://nestjs.com/) 71 | - Twitter - [@nestframework](https://twitter.com/nestframework) 72 | 73 | ## License 74 | 75 | Nest is [MIT licensed](LICENSE). 76 | -------------------------------------------------------------------------------- /deploy/terraform/terraform.tfstate.backup: -------------------------------------------------------------------------------- 1 | { 2 | "version": 4, 3 | "terraform_version": "0.12.21", 4 | "serial": 41, 5 | "lineage": "ee9edfbb-4848-3190-6ab4-3ed9a4ead573", 6 | "outputs": { 7 | "aws_subnet_1a": { 8 | "value": "subnet-0ac749aa53aa8dab9", 9 | "type": "string" 10 | }, 11 | "aws_subnet_1b": { 12 | "value": "subnet-04872f88b4d25314a", 13 | "type": "string" 14 | }, 15 | "aws_subnet_1c": { 16 | "value": "subnet-0e607f14ce3ca93d3", 17 | "type": "string" 18 | }, 19 | "aws_vpc_name": { 20 | "value": "vpc-0a52713ccd32ed5f3", 21 | "type": "string" 22 | }, 23 | "webcore_alb_hostname": { 24 | "value": "dev-webcore-1294550696.ap-southeast-1.elb.amazonaws.com", 25 | "type": "string" 26 | } 27 | }, 28 | "resources": [ 29 | { 30 | "mode": "data", 31 | "type": "aws_iam_policy_document", 32 | "name": "webcore_ecs_task_execution_role", 33 | "provider": "provider.aws", 34 | "instances": [ 35 | { 36 | "schema_version": 0, 37 | "attributes": { 38 | "id": "320642683", 39 | "json": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"sts:AssumeRole\",\n \"Principal\": {\n \"Service\": \"ecs-tasks.amazonaws.com\"\n }\n }\n ]\n}", 40 | "override_json": null, 41 | "policy_id": null, 42 | "source_json": null, 43 | "statement": [ 44 | { 45 | "actions": [ 46 | "sts:AssumeRole" 47 | ], 48 | "condition": [], 49 | "effect": "Allow", 50 | "not_actions": [], 51 | "not_principals": [], 52 | "not_resources": [], 53 | "principals": [ 54 | { 55 | "identifiers": [ 56 | "ecs-tasks.amazonaws.com" 57 | ], 58 | "type": "Service" 59 | } 60 | ], 61 | "resources": [], 62 | "sid": "" 63 | } 64 | ], 65 | "version": "2012-10-17" 66 | } 67 | } 68 | ] 69 | }, 70 | { 71 | "mode": "data", 72 | "type": "aws_subnet", 73 | "name": "public_1a", 74 | "provider": "provider.aws", 75 | "instances": [ 76 | { 77 | "schema_version": 0, 78 | "attributes": { 79 | "arn": "arn:aws:ec2:ap-southeast-1:466551463358:subnet/subnet-0ac749aa53aa8dab9", 80 | "assign_ipv6_address_on_creation": false, 81 | "availability_zone": "ap-southeast-1a", 82 | "availability_zone_id": "apse1-az1", 83 | "cidr_block": "172.10.3.0/24", 84 | "default_for_az": false, 85 | "filter": [ 86 | { 87 | "name": "availability-zone", 88 | "values": [ 89 | "ap-southeast-1a" 90 | ] 91 | }, 92 | { 93 | "name": "tag:Name", 94 | "values": [ 95 | "dev-data-stream-public-subnet" 96 | ] 97 | } 98 | ], 99 | "id": "subnet-0ac749aa53aa8dab9", 100 | "ipv6_cidr_block": null, 101 | "ipv6_cidr_block_association_id": null, 102 | "map_public_ip_on_launch": true, 103 | "outpost_arn": "", 104 | "owner_id": "466551463358", 105 | "state": "available", 106 | "tags": { 107 | "Name": "dev-data-stream-public-subnet" 108 | }, 109 | "vpc_id": "vpc-0a52713ccd32ed5f3" 110 | } 111 | } 112 | ] 113 | }, 114 | { 115 | "mode": "data", 116 | "type": "aws_subnet", 117 | "name": "public_1b", 118 | "provider": "provider.aws", 119 | "instances": [ 120 | { 121 | "schema_version": 0, 122 | "attributes": { 123 | "arn": "arn:aws:ec2:ap-southeast-1:466551463358:subnet/subnet-04872f88b4d25314a", 124 | "assign_ipv6_address_on_creation": false, 125 | "availability_zone": "ap-southeast-1b", 126 | "availability_zone_id": "apse1-az2", 127 | "cidr_block": "172.10.4.0/24", 128 | "default_for_az": false, 129 | "filter": [ 130 | { 131 | "name": "availability-zone", 132 | "values": [ 133 | "ap-southeast-1b" 134 | ] 135 | }, 136 | { 137 | "name": "tag:Name", 138 | "values": [ 139 | "dev-data-stream-public-subnet" 140 | ] 141 | } 142 | ], 143 | "id": "subnet-04872f88b4d25314a", 144 | "ipv6_cidr_block": null, 145 | "ipv6_cidr_block_association_id": null, 146 | "map_public_ip_on_launch": true, 147 | "outpost_arn": "", 148 | "owner_id": "466551463358", 149 | "state": "available", 150 | "tags": { 151 | "Name": "dev-data-stream-public-subnet" 152 | }, 153 | "vpc_id": "vpc-0a52713ccd32ed5f3" 154 | } 155 | } 156 | ] 157 | }, 158 | { 159 | "mode": "data", 160 | "type": "aws_subnet", 161 | "name": "public_1c", 162 | "provider": "provider.aws", 163 | "instances": [ 164 | { 165 | "schema_version": 0, 166 | "attributes": { 167 | "arn": "arn:aws:ec2:ap-southeast-1:466551463358:subnet/subnet-0e607f14ce3ca93d3", 168 | "assign_ipv6_address_on_creation": false, 169 | "availability_zone": "ap-southeast-1c", 170 | "availability_zone_id": "apse1-az3", 171 | "cidr_block": "172.10.5.0/24", 172 | "default_for_az": false, 173 | "filter": [ 174 | { 175 | "name": "availability-zone", 176 | "values": [ 177 | "ap-southeast-1c" 178 | ] 179 | }, 180 | { 181 | "name": "tag:Name", 182 | "values": [ 183 | "dev-data-stream-public-subnet" 184 | ] 185 | } 186 | ], 187 | "id": "subnet-0e607f14ce3ca93d3", 188 | "ipv6_cidr_block": null, 189 | "ipv6_cidr_block_association_id": null, 190 | "map_public_ip_on_launch": true, 191 | "outpost_arn": "", 192 | "owner_id": "466551463358", 193 | "state": "available", 194 | "tags": { 195 | "Name": "dev-data-stream-public-subnet" 196 | }, 197 | "vpc_id": "vpc-0a52713ccd32ed5f3" 198 | } 199 | } 200 | ] 201 | }, 202 | { 203 | "mode": "data", 204 | "type": "aws_vpc", 205 | "name": "main", 206 | "provider": "provider.aws", 207 | "instances": [ 208 | { 209 | "schema_version": 0, 210 | "attributes": { 211 | "arn": "arn:aws:ec2:ap-southeast-1:466551463358:vpc/vpc-0a52713ccd32ed5f3", 212 | "cidr_block": "172.10.0.0/16", 213 | "cidr_block_associations": [ 214 | { 215 | "association_id": "vpc-cidr-assoc-0124e5082cbfa95c1", 216 | "cidr_block": "172.10.0.0/16", 217 | "state": "associated" 218 | } 219 | ], 220 | "default": false, 221 | "dhcp_options_id": "dopt-e7ca3c80", 222 | "enable_dns_hostnames": false, 223 | "enable_dns_support": true, 224 | "filter": [ 225 | { 226 | "name": "tag:Name", 227 | "values": [ 228 | "dev-data-stream-vpc" 229 | ] 230 | } 231 | ], 232 | "id": "vpc-0a52713ccd32ed5f3", 233 | "instance_tenancy": "default", 234 | "ipv6_association_id": null, 235 | "ipv6_cidr_block": null, 236 | "main_route_table_id": "rtb-03da5a17ac036e683", 237 | "owner_id": "466551463358", 238 | "state": "available", 239 | "tags": { 240 | "Name": "dev-data-stream-vpc" 241 | } 242 | } 243 | } 244 | ] 245 | }, 246 | { 247 | "mode": "data", 248 | "type": "template_file", 249 | "name": "webcore_app", 250 | "provider": "provider.template", 251 | "instances": [ 252 | { 253 | "schema_version": 0, 254 | "attributes": { 255 | "filename": null, 256 | "id": "0f5b45ddad18ee3a4441ea18cb943ffcaf044493c7f266bd7cfa2377d8ddfa26", 257 | "rendered": "[\n {\n \"name\": \"test-app\",\n \"image\": \"466551463358.dkr.ecr.ap-southeast-1.amazonaws.com/test-app-repo:1.0.0\",\n \"cpu\": 1024,\n \"memory\": 2048,\n \"networkMode\": \"awsvpc\",\n \"logConfiguration\": {\n \"logDriver\": \"awslogs\",\n \"options\": {\n \"awslogs-group\": \"/ecs/webcore\",\n \"awslogs-region\": \"ap-southeast-1\",\n \"awslogs-stream-prefix\": \"ecs\"\n }\n },\n \"portMappings\": [\n {\n \"containerPort\": 80,\n \"hostPort\": 80\n }\n ]\n }\n]\n", 258 | "template": "[\n {\n \"name\": \"${container_name}\",\n \"image\": \"${app_image}\",\n \"cpu\": ${fargate_cpu},\n \"memory\": ${fargate_memory},\n \"networkMode\": \"awsvpc\",\n \"logConfiguration\": {\n \"logDriver\": \"awslogs\",\n \"options\": {\n \"awslogs-group\": \"/ecs/webcore\",\n \"awslogs-region\": \"${aws_region}\",\n \"awslogs-stream-prefix\": \"ecs\"\n }\n },\n \"portMappings\": [\n {\n \"containerPort\": ${app_port},\n \"hostPort\": ${app_port}\n }\n ]\n }\n]\n", 259 | "vars": { 260 | "app_image": "466551463358.dkr.ecr.ap-southeast-1.amazonaws.com/test-app-repo:1.0.0", 261 | "app_port": "80", 262 | "aws_region": "ap-southeast-1", 263 | "container_name": "test-app", 264 | "fargate_cpu": "1024", 265 | "fargate_memory": "2048", 266 | "prefix": "webcore" 267 | } 268 | } 269 | } 270 | ] 271 | }, 272 | { 273 | "mode": "managed", 274 | "type": "aws_alb", 275 | "name": "webcore_lb", 276 | "provider": "provider.aws", 277 | "instances": [ 278 | { 279 | "schema_version": 0, 280 | "attributes": { 281 | "access_logs": [ 282 | { 283 | "bucket": "", 284 | "enabled": false, 285 | "prefix": "" 286 | } 287 | ], 288 | "arn": "arn:aws:elasticloadbalancing:ap-southeast-1:466551463358:loadbalancer/app/dev-webcore/e14c04aa9d4ebab1", 289 | "arn_suffix": "app/dev-webcore/e14c04aa9d4ebab1", 290 | "dns_name": "dev-webcore-1294550696.ap-southeast-1.elb.amazonaws.com", 291 | "drop_invalid_header_fields": false, 292 | "enable_cross_zone_load_balancing": null, 293 | "enable_deletion_protection": false, 294 | "enable_http2": true, 295 | "id": "arn:aws:elasticloadbalancing:ap-southeast-1:466551463358:loadbalancer/app/dev-webcore/e14c04aa9d4ebab1", 296 | "idle_timeout": 60, 297 | "internal": false, 298 | "ip_address_type": "ipv4", 299 | "load_balancer_type": "application", 300 | "name": "dev-webcore", 301 | "name_prefix": null, 302 | "security_groups": [ 303 | "sg-01ca703c77be695d8" 304 | ], 305 | "subnet_mapping": [ 306 | { 307 | "allocation_id": "", 308 | "subnet_id": "subnet-04872f88b4d25314a" 309 | }, 310 | { 311 | "allocation_id": "", 312 | "subnet_id": "subnet-0ac749aa53aa8dab9" 313 | }, 314 | { 315 | "allocation_id": "", 316 | "subnet_id": "subnet-0e607f14ce3ca93d3" 317 | } 318 | ], 319 | "subnets": [ 320 | "subnet-04872f88b4d25314a", 321 | "subnet-0ac749aa53aa8dab9", 322 | "subnet-0e607f14ce3ca93d3" 323 | ], 324 | "tags": {}, 325 | "timeouts": null, 326 | "vpc_id": "vpc-0a52713ccd32ed5f3", 327 | "zone_id": "Z1LMS91P8CMLE5" 328 | }, 329 | "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6NjAwMDAwMDAwMDAwLCJ1cGRhdGUiOjYwMDAwMDAwMDAwMH19", 330 | "dependencies": [ 331 | "aws_security_group.lb" 332 | ] 333 | } 334 | ] 335 | }, 336 | { 337 | "mode": "managed", 338 | "type": "aws_alb_listener", 339 | "name": "front_end_webcore", 340 | "provider": "provider.aws", 341 | "instances": [ 342 | { 343 | "schema_version": 0, 344 | "attributes": { 345 | "arn": "arn:aws:elasticloadbalancing:ap-southeast-1:466551463358:listener/app/dev-webcore/e14c04aa9d4ebab1/7b23d0ca0e4d8995", 346 | "certificate_arn": null, 347 | "default_action": [ 348 | { 349 | "authenticate_cognito": [], 350 | "authenticate_oidc": [], 351 | "fixed_response": [], 352 | "order": 1, 353 | "redirect": [], 354 | "target_group_arn": "arn:aws:elasticloadbalancing:ap-southeast-1:466551463358:targetgroup/ops-reader-target-group/03b9b0e1701da8c8", 355 | "type": "forward" 356 | } 357 | ], 358 | "id": "arn:aws:elasticloadbalancing:ap-southeast-1:466551463358:listener/app/dev-webcore/e14c04aa9d4ebab1/7b23d0ca0e4d8995", 359 | "load_balancer_arn": "arn:aws:elasticloadbalancing:ap-southeast-1:466551463358:loadbalancer/app/dev-webcore/e14c04aa9d4ebab1", 360 | "port": 80, 361 | "protocol": "HTTP", 362 | "ssl_policy": "", 363 | "timeouts": null 364 | }, 365 | "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsicmVhZCI6NjAwMDAwMDAwMDAwfX0=", 366 | "dependencies": [ 367 | "aws_alb.webcore_lb", 368 | "aws_alb_target_group.webcore_target", 369 | "aws_security_group.lb" 370 | ] 371 | } 372 | ] 373 | }, 374 | { 375 | "mode": "managed", 376 | "type": "aws_alb_target_group", 377 | "name": "webcore_target", 378 | "provider": "provider.aws", 379 | "instances": [ 380 | { 381 | "schema_version": 0, 382 | "attributes": { 383 | "arn": "arn:aws:elasticloadbalancing:ap-southeast-1:466551463358:targetgroup/ops-reader-target-group/03b9b0e1701da8c8", 384 | "arn_suffix": "targetgroup/ops-reader-target-group/03b9b0e1701da8c8", 385 | "deregistration_delay": 300, 386 | "health_check": [ 387 | { 388 | "enabled": true, 389 | "healthy_threshold": 3, 390 | "interval": 30, 391 | "matcher": "200", 392 | "path": "/index", 393 | "port": "traffic-port", 394 | "protocol": "HTTP", 395 | "timeout": 3, 396 | "unhealthy_threshold": 2 397 | } 398 | ], 399 | "id": "arn:aws:elasticloadbalancing:ap-southeast-1:466551463358:targetgroup/ops-reader-target-group/03b9b0e1701da8c8", 400 | "lambda_multi_value_headers_enabled": false, 401 | "load_balancing_algorithm_type": "round_robin", 402 | "name": "ops-reader-target-group", 403 | "name_prefix": null, 404 | "port": 80, 405 | "protocol": "HTTP", 406 | "proxy_protocol_v2": false, 407 | "slow_start": 0, 408 | "stickiness": [ 409 | { 410 | "cookie_duration": 86400, 411 | "enabled": false, 412 | "type": "lb_cookie" 413 | } 414 | ], 415 | "tags": {}, 416 | "target_type": "ip", 417 | "vpc_id": "vpc-0a52713ccd32ed5f3" 418 | }, 419 | "private": "bnVsbA==" 420 | } 421 | ] 422 | }, 423 | { 424 | "mode": "managed", 425 | "type": "aws_appautoscaling_policy", 426 | "name": "webcore_down", 427 | "provider": "provider.aws", 428 | "instances": [ 429 | { 430 | "schema_version": 0, 431 | "attributes": { 432 | "adjustment_type": null, 433 | "arn": "arn:aws:autoscaling:ap-southeast-1:466551463358:scalingPolicy:b748f6a3-e692-4b2e-bcc6-08b547832678:resource/ecs/service/webcore-api-cluster/webcore-service:policyName/webcore_scale_down", 434 | "cooldown": null, 435 | "id": "webcore_scale_down", 436 | "metric_aggregation_type": null, 437 | "min_adjustment_magnitude": null, 438 | "name": "webcore_scale_down", 439 | "policy_type": "StepScaling", 440 | "resource_id": "service/webcore-api-cluster/webcore-service", 441 | "scalable_dimension": "ecs:service:DesiredCount", 442 | "service_namespace": "ecs", 443 | "step_adjustment": [], 444 | "step_scaling_policy_configuration": [ 445 | { 446 | "adjustment_type": "ChangeInCapacity", 447 | "cooldown": 60, 448 | "metric_aggregation_type": "Maximum", 449 | "min_adjustment_magnitude": 0, 450 | "step_adjustment": [ 451 | { 452 | "metric_interval_lower_bound": "", 453 | "metric_interval_upper_bound": "0", 454 | "scaling_adjustment": -1 455 | } 456 | ] 457 | } 458 | ], 459 | "target_tracking_scaling_policy_configuration": [] 460 | }, 461 | "private": "bnVsbA==", 462 | "dependencies": [ 463 | "aws_alb_listener.front_end_webcore", 464 | "aws_alb_target_group.webcore_target", 465 | "aws_appautoscaling_target.webcore_target", 466 | "aws_ecs_cluster.main", 467 | "aws_ecs_service.webcore_main", 468 | "aws_ecs_task_definition.webcore_task_def", 469 | "aws_iam_role.webcore_ecs_task_execution_role", 470 | "aws_iam_role_policy_attachment.webcore_task_execution_role", 471 | "aws_security_group.ecs_tasks" 472 | ] 473 | } 474 | ] 475 | }, 476 | { 477 | "mode": "managed", 478 | "type": "aws_appautoscaling_policy", 479 | "name": "webcore_up", 480 | "provider": "provider.aws", 481 | "instances": [ 482 | { 483 | "schema_version": 0, 484 | "attributes": { 485 | "adjustment_type": null, 486 | "arn": "arn:aws:autoscaling:ap-southeast-1:466551463358:scalingPolicy:b748f6a3-e692-4b2e-bcc6-08b547832678:resource/ecs/service/webcore-api-cluster/webcore-service:policyName/webcore_scale_up", 487 | "cooldown": null, 488 | "id": "webcore_scale_up", 489 | "metric_aggregation_type": null, 490 | "min_adjustment_magnitude": null, 491 | "name": "webcore_scale_up", 492 | "policy_type": "StepScaling", 493 | "resource_id": "service/webcore-api-cluster/webcore-service", 494 | "scalable_dimension": "ecs:service:DesiredCount", 495 | "service_namespace": "ecs", 496 | "step_adjustment": [], 497 | "step_scaling_policy_configuration": [ 498 | { 499 | "adjustment_type": "ChangeInCapacity", 500 | "cooldown": 60, 501 | "metric_aggregation_type": "Maximum", 502 | "min_adjustment_magnitude": 0, 503 | "step_adjustment": [ 504 | { 505 | "metric_interval_lower_bound": "0", 506 | "metric_interval_upper_bound": "", 507 | "scaling_adjustment": 1 508 | } 509 | ] 510 | } 511 | ], 512 | "target_tracking_scaling_policy_configuration": [] 513 | }, 514 | "private": "bnVsbA==", 515 | "dependencies": [ 516 | "aws_alb_listener.front_end_webcore", 517 | "aws_alb_target_group.webcore_target", 518 | "aws_appautoscaling_target.webcore_target", 519 | "aws_ecs_cluster.main", 520 | "aws_ecs_service.webcore_main", 521 | "aws_ecs_task_definition.webcore_task_def", 522 | "aws_iam_role.webcore_ecs_task_execution_role", 523 | "aws_iam_role_policy_attachment.webcore_task_execution_role", 524 | "aws_security_group.ecs_tasks" 525 | ] 526 | } 527 | ] 528 | }, 529 | { 530 | "mode": "managed", 531 | "type": "aws_appautoscaling_target", 532 | "name": "webcore_target", 533 | "provider": "provider.aws", 534 | "instances": [ 535 | { 536 | "schema_version": 0, 537 | "attributes": { 538 | "id": "service/webcore-api-cluster/webcore-service", 539 | "max_capacity": 6, 540 | "min_capacity": 1, 541 | "resource_id": "service/webcore-api-cluster/webcore-service", 542 | "role_arn": "arn:aws:iam::466551463358:role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService", 543 | "scalable_dimension": "ecs:service:DesiredCount", 544 | "service_namespace": "ecs" 545 | }, 546 | "private": "bnVsbA==", 547 | "dependencies": [ 548 | "aws_alb_listener.front_end_webcore", 549 | "aws_alb_target_group.webcore_target", 550 | "aws_ecs_cluster.main", 551 | "aws_ecs_service.webcore_main", 552 | "aws_ecs_task_definition.webcore_task_def", 553 | "aws_iam_role.webcore_ecs_task_execution_role", 554 | "aws_iam_role_policy_attachment.webcore_task_execution_role", 555 | "aws_security_group.ecs_tasks" 556 | ] 557 | } 558 | ] 559 | }, 560 | { 561 | "mode": "managed", 562 | "type": "aws_cloudwatch_log_group", 563 | "name": "webcore_log_group", 564 | "provider": "provider.aws", 565 | "instances": [ 566 | { 567 | "schema_version": 0, 568 | "attributes": { 569 | "arn": "arn:aws:logs:ap-southeast-1:466551463358:log-group:/ecs/webcore:*", 570 | "id": "/ecs/webcore", 571 | "kms_key_id": "", 572 | "name": "/ecs/webcore", 573 | "name_prefix": null, 574 | "retention_in_days": 30, 575 | "tags": { 576 | "Name": "webcore-log-group" 577 | } 578 | }, 579 | "private": "bnVsbA==" 580 | } 581 | ] 582 | }, 583 | { 584 | "mode": "managed", 585 | "type": "aws_cloudwatch_log_stream", 586 | "name": "cb_log_stream", 587 | "provider": "provider.aws", 588 | "instances": [ 589 | { 590 | "schema_version": 0, 591 | "attributes": { 592 | "arn": "arn:aws:logs:ap-southeast-1:466551463358:log-group:/ecs/webcore:log-stream:webcore-log-stream", 593 | "id": "webcore-log-stream", 594 | "log_group_name": "/ecs/webcore", 595 | "name": "webcore-log-stream" 596 | }, 597 | "private": "bnVsbA==", 598 | "dependencies": [ 599 | "aws_cloudwatch_log_group.webcore_log_group" 600 | ] 601 | } 602 | ] 603 | }, 604 | { 605 | "mode": "managed", 606 | "type": "aws_cloudwatch_metric_alarm", 607 | "name": "webcore_service_cpu_high", 608 | "provider": "provider.aws", 609 | "instances": [ 610 | { 611 | "schema_version": 1, 612 | "attributes": { 613 | "actions_enabled": true, 614 | "alarm_actions": [ 615 | "arn:aws:autoscaling:ap-southeast-1:466551463358:scalingPolicy:b748f6a3-e692-4b2e-bcc6-08b547832678:resource/ecs/service/webcore-api-cluster/webcore-service:policyName/webcore_scale_up" 616 | ], 617 | "alarm_description": "", 618 | "alarm_name": "webcore_cpu_utilization_high", 619 | "arn": "arn:aws:cloudwatch:ap-southeast-1:466551463358:alarm:webcore_cpu_utilization_high", 620 | "comparison_operator": "GreaterThanOrEqualToThreshold", 621 | "datapoints_to_alarm": 0, 622 | "dimensions": { 623 | "ClusterName": "webcore-api-cluster", 624 | "ServiceName": "webcore-service" 625 | }, 626 | "evaluate_low_sample_count_percentiles": "", 627 | "evaluation_periods": 2, 628 | "extended_statistic": "", 629 | "id": "webcore_cpu_utilization_high", 630 | "insufficient_data_actions": [], 631 | "metric_name": "CPUUtilization", 632 | "metric_query": [], 633 | "namespace": "AWS/ECS", 634 | "ok_actions": [], 635 | "period": 60, 636 | "statistic": "Average", 637 | "tags": {}, 638 | "threshold": 85, 639 | "threshold_metric_id": "", 640 | "treat_missing_data": "missing", 641 | "unit": "" 642 | }, 643 | "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ==", 644 | "dependencies": [ 645 | "aws_alb_listener.front_end_webcore", 646 | "aws_alb_target_group.webcore_target", 647 | "aws_appautoscaling_policy.webcore_up", 648 | "aws_appautoscaling_target.webcore_target", 649 | "aws_ecs_cluster.main", 650 | "aws_ecs_service.webcore_main", 651 | "aws_ecs_task_definition.webcore_task_def", 652 | "aws_iam_role.webcore_ecs_task_execution_role", 653 | "aws_iam_role_policy_attachment.webcore_task_execution_role", 654 | "aws_security_group.ecs_tasks" 655 | ] 656 | } 657 | ] 658 | }, 659 | { 660 | "mode": "managed", 661 | "type": "aws_cloudwatch_metric_alarm", 662 | "name": "webcore_service_cpu_low", 663 | "provider": "provider.aws", 664 | "instances": [ 665 | { 666 | "schema_version": 1, 667 | "attributes": { 668 | "actions_enabled": true, 669 | "alarm_actions": [ 670 | "arn:aws:autoscaling:ap-southeast-1:466551463358:scalingPolicy:b748f6a3-e692-4b2e-bcc6-08b547832678:resource/ecs/service/webcore-api-cluster/webcore-service:policyName/webcore_scale_down" 671 | ], 672 | "alarm_description": "", 673 | "alarm_name": "webcore_cpu_utilization_low", 674 | "arn": "arn:aws:cloudwatch:ap-southeast-1:466551463358:alarm:webcore_cpu_utilization_low", 675 | "comparison_operator": "LessThanOrEqualToThreshold", 676 | "datapoints_to_alarm": 0, 677 | "dimensions": { 678 | "ClusterName": "webcore-api-cluster", 679 | "ServiceName": "webcore-service" 680 | }, 681 | "evaluate_low_sample_count_percentiles": "", 682 | "evaluation_periods": 2, 683 | "extended_statistic": "", 684 | "id": "webcore_cpu_utilization_low", 685 | "insufficient_data_actions": [], 686 | "metric_name": "CPUUtilization", 687 | "metric_query": [], 688 | "namespace": "AWS/ECS", 689 | "ok_actions": [], 690 | "period": 60, 691 | "statistic": "Average", 692 | "tags": {}, 693 | "threshold": 10, 694 | "threshold_metric_id": "", 695 | "treat_missing_data": "missing", 696 | "unit": "" 697 | }, 698 | "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ==", 699 | "dependencies": [ 700 | "aws_alb_listener.front_end_webcore", 701 | "aws_alb_target_group.webcore_target", 702 | "aws_appautoscaling_policy.webcore_down", 703 | "aws_appautoscaling_target.webcore_target", 704 | "aws_ecs_cluster.main", 705 | "aws_ecs_service.webcore_main", 706 | "aws_ecs_task_definition.webcore_task_def", 707 | "aws_iam_role.webcore_ecs_task_execution_role", 708 | "aws_iam_role_policy_attachment.webcore_task_execution_role", 709 | "aws_security_group.ecs_tasks" 710 | ] 711 | } 712 | ] 713 | }, 714 | { 715 | "mode": "managed", 716 | "type": "aws_ecs_cluster", 717 | "name": "main", 718 | "provider": "provider.aws", 719 | "instances": [ 720 | { 721 | "schema_version": 0, 722 | "attributes": { 723 | "arn": "arn:aws:ecs:ap-southeast-1:466551463358:cluster/webcore-api-cluster", 724 | "capacity_providers": [], 725 | "default_capacity_provider_strategy": [], 726 | "id": "arn:aws:ecs:ap-southeast-1:466551463358:cluster/webcore-api-cluster", 727 | "name": "webcore-api-cluster", 728 | "setting": [ 729 | { 730 | "name": "containerInsights", 731 | "value": "disabled" 732 | } 733 | ], 734 | "tags": {} 735 | }, 736 | "private": "bnVsbA==" 737 | } 738 | ] 739 | }, 740 | { 741 | "mode": "managed", 742 | "type": "aws_ecs_service", 743 | "name": "webcore_main", 744 | "provider": "provider.aws", 745 | "instances": [ 746 | { 747 | "schema_version": 0, 748 | "attributes": { 749 | "capacity_provider_strategy": [], 750 | "cluster": "arn:aws:ecs:ap-southeast-1:466551463358:cluster/webcore-api-cluster", 751 | "deployment_controller": [ 752 | { 753 | "type": "ECS" 754 | } 755 | ], 756 | "deployment_maximum_percent": 200, 757 | "deployment_minimum_healthy_percent": 100, 758 | "desired_count": 1, 759 | "enable_ecs_managed_tags": false, 760 | "health_check_grace_period_seconds": 0, 761 | "iam_role": "aws-service-role", 762 | "id": "arn:aws:ecs:ap-southeast-1:466551463358:service/webcore-service", 763 | "launch_type": "FARGATE", 764 | "load_balancer": [ 765 | { 766 | "container_name": "test-app", 767 | "container_port": 80, 768 | "elb_name": "", 769 | "target_group_arn": "arn:aws:elasticloadbalancing:ap-southeast-1:466551463358:targetgroup/ops-reader-target-group/03b9b0e1701da8c8" 770 | } 771 | ], 772 | "name": "webcore-service", 773 | "network_configuration": [ 774 | { 775 | "assign_public_ip": true, 776 | "security_groups": [ 777 | "sg-025925c397376e620" 778 | ], 779 | "subnets": [ 780 | "subnet-04872f88b4d25314a", 781 | "subnet-0ac749aa53aa8dab9", 782 | "subnet-0e607f14ce3ca93d3" 783 | ] 784 | } 785 | ], 786 | "ordered_placement_strategy": [], 787 | "placement_constraints": [], 788 | "placement_strategy": [], 789 | "platform_version": "LATEST", 790 | "propagate_tags": "NONE", 791 | "scheduling_strategy": "REPLICA", 792 | "service_registries": [], 793 | "tags": null, 794 | "task_definition": "arn:aws:ecs:ap-southeast-1:466551463358:task-definition/webcore-task:2" 795 | }, 796 | "private": "bnVsbA==", 797 | "dependencies": [ 798 | "aws_alb_listener.front_end_webcore", 799 | "aws_alb_target_group.webcore_target", 800 | "aws_ecs_cluster.main", 801 | "aws_ecs_task_definition.webcore_task_def", 802 | "aws_iam_role.webcore_ecs_task_execution_role", 803 | "aws_iam_role_policy_attachment.webcore_task_execution_role", 804 | "aws_security_group.ecs_tasks" 805 | ] 806 | } 807 | ] 808 | }, 809 | { 810 | "mode": "managed", 811 | "type": "aws_ecs_task_definition", 812 | "name": "webcore_task_def", 813 | "provider": "provider.aws", 814 | "instances": [ 815 | { 816 | "schema_version": 1, 817 | "attributes": { 818 | "arn": "arn:aws:ecs:ap-southeast-1:466551463358:task-definition/webcore-task:2", 819 | "container_definitions": "[{\"cpu\":1024,\"environment\":[],\"essential\":true,\"image\":\"466551463358.dkr.ecr.ap-southeast-1.amazonaws.com/test-app-repo:1.0.0\",\"logConfiguration\":{\"logDriver\":\"awslogs\",\"options\":{\"awslogs-group\":\"/ecs/webcore\",\"awslogs-region\":\"ap-southeast-1\",\"awslogs-stream-prefix\":\"ecs\"}},\"memory\":2048,\"mountPoints\":[],\"name\":\"test-app\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80,\"protocol\":\"tcp\"}],\"volumesFrom\":[]}]", 820 | "cpu": "1024", 821 | "execution_role_arn": "arn:aws:iam::466551463358:role/WebCoreEcsTaskExecutionRole", 822 | "family": "webcore-task", 823 | "id": "webcore-task", 824 | "inference_accelerator": [], 825 | "ipc_mode": "", 826 | "memory": "2048", 827 | "network_mode": "awsvpc", 828 | "pid_mode": "", 829 | "placement_constraints": [], 830 | "proxy_configuration": [], 831 | "requires_compatibilities": [ 832 | "FARGATE" 833 | ], 834 | "revision": 2, 835 | "tags": null, 836 | "task_role_arn": "", 837 | "volume": [] 838 | }, 839 | "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ==", 840 | "dependencies": [ 841 | "aws_iam_role.webcore_ecs_task_execution_role" 842 | ] 843 | } 844 | ] 845 | }, 846 | { 847 | "mode": "managed", 848 | "type": "aws_iam_role", 849 | "name": "webcore_ecs_task_execution_role", 850 | "provider": "provider.aws", 851 | "instances": [ 852 | { 853 | "schema_version": 0, 854 | "attributes": { 855 | "arn": "arn:aws:iam::466551463358:role/WebCoreEcsTaskExecutionRole", 856 | "assume_role_policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ecs-tasks.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}", 857 | "create_date": "2020-05-08T08:31:18Z", 858 | "description": "", 859 | "force_detach_policies": false, 860 | "id": "WebCoreEcsTaskExecutionRole", 861 | "max_session_duration": 3600, 862 | "name": "WebCoreEcsTaskExecutionRole", 863 | "name_prefix": null, 864 | "path": "/", 865 | "permissions_boundary": null, 866 | "tags": {}, 867 | "unique_id": "AROAWZIFCLW7FKOB6XB4L" 868 | }, 869 | "private": "bnVsbA==" 870 | } 871 | ] 872 | }, 873 | { 874 | "mode": "managed", 875 | "type": "aws_iam_role_policy_attachment", 876 | "name": "webcore_task_execution_role", 877 | "provider": "provider.aws", 878 | "instances": [ 879 | { 880 | "schema_version": 0, 881 | "attributes": { 882 | "id": "WebCoreEcsTaskExecutionRole-20200508083120781000000001", 883 | "policy_arn": "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy", 884 | "role": "WebCoreEcsTaskExecutionRole" 885 | }, 886 | "private": "bnVsbA==", 887 | "dependencies": [ 888 | "aws_iam_role.webcore_ecs_task_execution_role" 889 | ] 890 | } 891 | ] 892 | }, 893 | { 894 | "mode": "managed", 895 | "type": "aws_security_group", 896 | "name": "ecs_tasks", 897 | "provider": "provider.aws", 898 | "instances": [ 899 | { 900 | "schema_version": 1, 901 | "attributes": { 902 | "arn": "arn:aws:ec2:ap-southeast-1:466551463358:security-group/sg-025925c397376e620", 903 | "description": "allow inbound access from the ALB only", 904 | "egress": [ 905 | { 906 | "cidr_blocks": [ 907 | "0.0.0.0/0" 908 | ], 909 | "description": "", 910 | "from_port": 0, 911 | "ipv6_cidr_blocks": [], 912 | "prefix_list_ids": [], 913 | "protocol": "-1", 914 | "security_groups": [], 915 | "self": false, 916 | "to_port": 0 917 | } 918 | ], 919 | "id": "sg-025925c397376e620", 920 | "ingress": [ 921 | { 922 | "cidr_blocks": [], 923 | "description": "", 924 | "from_port": 80, 925 | "ipv6_cidr_blocks": [], 926 | "prefix_list_ids": [], 927 | "protocol": "tcp", 928 | "security_groups": [ 929 | "sg-01ca703c77be695d8" 930 | ], 931 | "self": false, 932 | "to_port": 80 933 | } 934 | ], 935 | "name": "webcore-ecs-tasks-security-group", 936 | "name_prefix": null, 937 | "owner_id": "466551463358", 938 | "revoke_rules_on_delete": false, 939 | "tags": {}, 940 | "timeouts": null, 941 | "vpc_id": "vpc-0a52713ccd32ed5f3" 942 | }, 943 | "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6NjAwMDAwMDAwMDAwfSwic2NoZW1hX3ZlcnNpb24iOiIxIn0=", 944 | "dependencies": [ 945 | "aws_security_group.lb" 946 | ] 947 | } 948 | ] 949 | }, 950 | { 951 | "mode": "managed", 952 | "type": "aws_security_group", 953 | "name": "lb", 954 | "provider": "provider.aws", 955 | "instances": [ 956 | { 957 | "schema_version": 1, 958 | "attributes": { 959 | "arn": "arn:aws:ec2:ap-southeast-1:466551463358:security-group/sg-01ca703c77be695d8", 960 | "description": "controls access to the ALB", 961 | "egress": [ 962 | { 963 | "cidr_blocks": [ 964 | "0.0.0.0/0" 965 | ], 966 | "description": "", 967 | "from_port": 0, 968 | "ipv6_cidr_blocks": [], 969 | "prefix_list_ids": [], 970 | "protocol": "-1", 971 | "security_groups": [], 972 | "self": false, 973 | "to_port": 0 974 | } 975 | ], 976 | "id": "sg-01ca703c77be695d8", 977 | "ingress": [ 978 | { 979 | "cidr_blocks": [ 980 | "0.0.0.0/0" 981 | ], 982 | "description": "", 983 | "from_port": 80, 984 | "ipv6_cidr_blocks": [], 985 | "prefix_list_ids": [], 986 | "protocol": "tcp", 987 | "security_groups": [], 988 | "self": false, 989 | "to_port": 80 990 | } 991 | ], 992 | "name": "webcore-load-balancer-security-group", 993 | "name_prefix": null, 994 | "owner_id": "466551463358", 995 | "revoke_rules_on_delete": false, 996 | "tags": {}, 997 | "timeouts": null, 998 | "vpc_id": "vpc-0a52713ccd32ed5f3" 999 | }, 1000 | "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6NjAwMDAwMDAwMDAwfSwic2NoZW1hX3ZlcnNpb24iOiIxIn0=" 1001 | } 1002 | ] 1003 | } 1004 | ] 1005 | } 1006 | --------------------------------------------------------------------------------