├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Code ├── client │ ├── .browserslistrc │ ├── .eslintrc.js │ ├── Dockerfile │ ├── README.md │ ├── babel.config.js │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.vue │ │ ├── components │ │ │ ├── Login.vue │ │ │ ├── Main.vue │ │ │ └── Nav.vue │ │ ├── main.js │ │ ├── router │ │ │ └── index.js │ │ ├── services │ │ │ └── RestServices.js │ │ ├── tests │ │ │ └── stresstests │ │ │ │ └── stress_client.yml │ │ └── views │ │ │ ├── About.vue │ │ │ └── EasterEgg.vue │ └── vue.config.js └── server │ ├── Dockerfile │ ├── README.md │ ├── package-lock.json │ ├── package.json │ └── src │ ├── app.js │ ├── swagger │ └── swagger.js │ └── tests │ └── stresstests │ └── stress_server.yml ├── Documentation_assets ├── CICD_architecture.png └── Infrastructure_architecture.png ├── Infrastructure ├── Modules │ ├── ALB │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ ├── CodeBuild │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ ├── CodeDeploy │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ ├── CodePipeline │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ ├── Dynamodb │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ ├── ECR │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ ├── ECS │ │ ├── Autoscaling │ │ │ ├── main.tf │ │ │ ├── outputs.tf │ │ │ └── variables.tf │ │ ├── Cluster │ │ │ ├── main.tf │ │ │ ├── outputs.tf │ │ │ └── variable.tf │ │ ├── Service │ │ │ ├── main.tf │ │ │ ├── outputs.tf │ │ │ └── variables.tf │ │ └── TaskDefinition │ │ │ ├── main.tf │ │ │ ├── outputs.tf │ │ │ └── variables.tf │ ├── IAM │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ ├── Networking │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ ├── S3 │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ ├── SNS │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ └── SecurityGroup │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf ├── Templates │ ├── appspec.yaml │ ├── buildspec.yml │ └── taskdef.json ├── main.tf ├── outputs.tf ├── variables.tf └── versions.tf ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Build files 2 | .DS_Store 3 | node_modules 4 | /dist 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | 25 | # Terraform files 26 | .terraform* 27 | terraform.tfstate* -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /Code/client/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /Code/client/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended' 9 | ], 10 | parserOptions: { 11 | parser: 'babel-eslint' 12 | }, 13 | rules: { 14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Code/client/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | # build stage 5 | FROM public.ecr.aws/bitnami/node:16 as build-stage 6 | WORKDIR /app 7 | COPY package*.json ./ 8 | RUN npm install 9 | COPY . . 10 | RUN npm run build 11 | 12 | # production stage 13 | FROM public.ecr.aws/nginx/nginx:latest as production-stage 14 | COPY --from=build-stage /app/dist /usr/share/nginx/html 15 | EXPOSE 80 16 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /Code/client/README.md: -------------------------------------------------------------------------------- 1 | # Frontend of the demo app 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Customize configuration 19 | See [Configuration Reference](https://cli.vuejs.org/config/). 20 | -------------------------------------------------------------------------------- /Code/client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /Code/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-ecs-demo-frontend", 3 | "version": "0.1.0", 4 | "description": "AWS Demo", 5 | "author": "Marina Burkhardt", 6 | "private": true, 7 | "scripts": { 8 | "serve": "vue-cli-service serve", 9 | "build": "vue-cli-service build", 10 | "lint": "vue-cli-service lint" 11 | }, 12 | "dependencies": { 13 | "aws-sdk": "^2.885.0", 14 | "axios": "^0.21.2", 15 | "bootstrap-vue": "^2.15.0", 16 | "core-js": "^3.6.5", 17 | "vue": "^2.6.11", 18 | "vue-router": "^3.2.0" 19 | }, 20 | "devDependencies": { 21 | "@vue/cli-plugin-babel": "~4.4.0", 22 | "@vue/cli-plugin-eslint": "~4.4.0", 23 | "@vue/cli-plugin-router": "~4.4.0", 24 | "@vue/cli-service": "~4.4.0", 25 | "babel-eslint": "^10.1.0", 26 | "eslint": "^6.7.2", 27 | "eslint-plugin-vue": "^6.2.2", 28 | "vue-template-compiler": "^2.6.11" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Code/client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | AWS Demo 11 | 12 | 13 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /Code/client/src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 22 | 23 | 180 | 181 | -------------------------------------------------------------------------------- /Code/client/src/components/Login.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 32 | 33 | 53 | 64 | -------------------------------------------------------------------------------- /Code/client/src/components/Main.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 82 | 83 | 101 | 102 | -------------------------------------------------------------------------------- /Code/client/src/components/Nav.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 58 | 59 | 69 | 70 | -------------------------------------------------------------------------------- /Code/client/src/main.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | import Vue from 'vue' 5 | import App from './App.vue' 6 | import router from './router' 7 | import BootstrapVue from 'bootstrap-vue' 8 | import 'bootstrap/dist/css/bootstrap.css' 9 | import 'bootstrap-vue/dist/bootstrap-vue.css' 10 | 11 | Vue.use(BootstrapVue) 12 | 13 | Vue.config.productionTip = false 14 | 15 | new Vue({ 16 | router, 17 | render: h => h(App) 18 | }).$mount('#app') 19 | -------------------------------------------------------------------------------- /Code/client/src/router/index.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | import Vue from 'vue' 5 | import VueRouter from 'vue-router' 6 | import Router from "vue-router"; 7 | import Main from '../components/Main' 8 | import Login from '../components/Login'; 9 | 10 | Vue.use(Router); 11 | 12 | const routes = [ 13 | { path: "*", redirect: "/" }, 14 | { path: '/', name: 'Login', component: Login }, 15 | { path: '/main', name: 'Main', component: Main }, 16 | { path: '/about', name: 'About', component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') }, 17 | { path: '/search', name: 'Search', component: () => import(/* webpackChunkName: "search" */ '../views/EasterEgg.vue') }, 18 | ] 19 | 20 | export const router = new VueRouter({ 21 | mode: "history", 22 | base: __dirname, 23 | routes 24 | }) 25 | 26 | export default router 27 | -------------------------------------------------------------------------------- /Code/client/src/services/RestServices.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | import axios from 'axios' 5 | 6 | let serverUrl = "http://" //this value is replaced by AWS CodeBuild 7 | 8 | export default { 9 | async getAllProducts() { 10 | return await axios.get(serverUrl+"/api/getAllProducts"); 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /Code/client/src/tests/stresstests/stress_client.yml: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | config: 5 | target: "" # Change me to the right env target 6 | phases: 7 | - duration: 100 8 | arrivalRate: 20 9 | http: 10 | timeout: 5 # Wait 5 sec before aborting the request 11 | pool: 50 # Fixed number of pool connection, to be reused 12 | scenarios: 13 | - name: "Generating load on the client fleet of tasks" 14 | flow: 15 | - loop: 16 | - get: 17 | url: "/" # Change me to "/" if necessary 18 | count: 10 -------------------------------------------------------------------------------- /Code/client/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 27 | -------------------------------------------------------------------------------- /Code/client/src/views/EasterEgg.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 26 | -------------------------------------------------------------------------------- /Code/client/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | publicPath: '/', 3 | chainWebpack: config => { 4 | config.module.rules.delete('eslint'); 5 | }, 6 | devServer: { 7 | port: 3000, 8 | disableHostCheck: true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Code/server/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | FROM public.ecr.aws/bitnami/node:latest 5 | 6 | RUN mkdir -p /home/node/app/node_modules 7 | 8 | WORKDIR /home/node/app 9 | 10 | COPY package*.json ./ 11 | 12 | RUN npm install 13 | 14 | COPY . . 15 | 16 | EXPOSE 3001 17 | 18 | CMD [ "npm", "start" ] -------------------------------------------------------------------------------- /Code/server/README.md: -------------------------------------------------------------------------------- 1 | # Backend of the demo app 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | ### Compiles and hot-reloads for development 8 | ``` 9 | npm start 10 | ``` -------------------------------------------------------------------------------- /Code/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-ecs-demo-backend", 3 | "version": "1.0.0", 4 | "description": "AWS Demo", 5 | "author": "Marina Burkhardt", 6 | "private": true, 7 | "license": "MIT", 8 | "main": "app.js", 9 | "scripts": { 10 | "start": "node ./src/app.js --exec \"npm run lint && node\"" 11 | }, 12 | "keywords": [ 13 | "nodejs", 14 | "express" 15 | ], 16 | "dependencies": { 17 | "artillery": "^1.6.2", 18 | "aws-sdk": "^2.876.0", 19 | "cors": "^2.8.5", 20 | "express": "^4.16.4", 21 | "swagger-jsdoc": "6.0.0", 22 | "swagger-model-validator": "^3.0.20", 23 | "swagger-ui-express": "^4.1.6" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Code/server/src/app.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | const express = require('express'); 5 | const app = express(); 6 | var cors = require('cors'); 7 | const AWS = require('aws-sdk') 8 | const port = 3001; 9 | 10 | app.use(cors()); 11 | 12 | var swagger = require('./swagger/swagger') 13 | app.use('/api/docs', swagger.router) 14 | 15 | /** 16 | * @swagger 17 | * /status: 18 | * get: 19 | * description: Dummy endpoint used as an API health check 20 | * tags: 21 | * - Status 22 | * produces: 23 | * - application/json 24 | * responses: 25 | * 200: 26 | * description: Retrieves a string with a health status 27 | */ 28 | app.get('/status', (req, res) => { 29 | res.send({ 30 | message: 'AWS Demo server is up and running!' 31 | }) 32 | }) 33 | 34 | /** 35 | * @swagger 36 | * /api/getAllProducts: 37 | * get: 38 | * description: Retrieves all products available in the Dynamodb table 39 | * tags: 40 | * - Products 41 | * produces: 42 | * - application/json 43 | * responses: 44 | * 200: 45 | * description: Retrieves a list of products 46 | */ 47 | app.get('/api/getAllProducts', (req, res) => { 48 | AB3_TABLE = "DYNAMODB_TABLE" //DYNAMODB_TABLE value is retrieved from the generated resources created by the terraform code 49 | const docClient = new AWS.DynamoDB.DocumentClient(); 50 | const params = { 51 | TableName: AB3_TABLE 52 | } 53 | 54 | docClient.scan(params, function(err, data) { 55 | if (err) { 56 | res.send({ 57 | code: err.status, 58 | description: err.message 59 | }); 60 | } else { 61 | var products = data.Items 62 | res.send({ 63 | products 64 | }); 65 | } 66 | }); 67 | 68 | }) 69 | 70 | // catch 404 and forward to error handler 71 | app.use(function (req, res, next) { 72 | var err = new Error('Not Found') 73 | err.status = 404 74 | next(err) 75 | }) 76 | 77 | // error handler 78 | app.use(function (err, req, res, next) { 79 | console.error(`Error catched! ${err}`) 80 | 81 | let error = { 82 | code: err.status, 83 | description: err.message 84 | } 85 | status: err.status || 500 86 | 87 | res.status(error.code).send(error) 88 | }) 89 | 90 | app.listen(port) 91 | console.log('Server started on port ' + port) -------------------------------------------------------------------------------- /Code/server/src/swagger/swagger.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | const express = require('express') 5 | const router = express.Router() 6 | 7 | const options = { 8 | swaggerDefinition: { 9 | info: { 10 | title: 'AWS Demo', 11 | version: '1.0.0', 12 | description: 'An AWS Demo for a full stack application with Amazon ECS, applying DevOps practices', 13 | contact: { 14 | email: 'burkhmar@amazon.de' 15 | } 16 | }, 17 | tags: [ 18 | { 19 | name: 'AWS Demo Endpoints', 20 | description: 'Enpoints descriptions' 21 | } 22 | ], 23 | schemes: ['http'], 24 | host: '', 25 | basePath: '/' 26 | }, 27 | apis: [ 28 | './src/app.js', 29 | ], 30 | } 31 | 32 | const swaggerJSDoc = require('swagger-jsdoc') 33 | const swaggerUi = require('swagger-ui-express') 34 | const swaggerSpec = swaggerJSDoc(options) 35 | require('swagger-model-validator')(swaggerSpec) 36 | 37 | router.get('/json', function (req, res) { 38 | res.setHeader('Content-Type', 'application/json') 39 | res.send(swaggerSpec) 40 | }) 41 | 42 | router.use('/', swaggerUi.serve, swaggerUi.setup(swaggerSpec)) 43 | 44 | function validateModel(name, model) { 45 | const responseValidation = swaggerSpec.validateModel(name, model, false, true) 46 | if (!responseValidation.valid) { 47 | console.error(responseValidation.errors) 48 | throw new Error(`Model doesn't match Swagger contract`) 49 | } 50 | } 51 | 52 | module.exports = { 53 | router, 54 | validateModel 55 | } 56 | -------------------------------------------------------------------------------- /Code/server/src/tests/stresstests/stress_server.yml: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | config: 5 | target: "" # Change me to the right env target 6 | phases: 7 | - duration: 100 8 | arrivalRate: 20 9 | http: 10 | timeout: 5 # Wait 5 sec before aborting the request 11 | pool: 50 # Fixed number of pool connection, to be reused 12 | scenarios: 13 | - name: "Generating load on the server fleet of tasks" 14 | flow: 15 | - loop: 16 | - get: 17 | url: "/api/getAllProducts" # Change me to "/" if necessary 18 | count: 10 -------------------------------------------------------------------------------- /Documentation_assets/CICD_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NinjaDevOps0831/amazon-ecs-fullstack-app-terraform/f25b5de6cf26a771429049628fdcd0c06fdcf22b/Documentation_assets/CICD_architecture.png -------------------------------------------------------------------------------- /Documentation_assets/Infrastructure_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NinjaDevOps0831/amazon-ecs-fullstack-app-terraform/f25b5de6cf26a771429049628fdcd0c06fdcf22b/Documentation_assets/Infrastructure_architecture.png -------------------------------------------------------------------------------- /Infrastructure/Modules/ALB/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | /*============================================================== 5 | AWS Application Load Balancer + Target groups 6 | ===============================================================*/ 7 | 8 | resource "aws_alb" "alb" { 9 | count = var.create_alb == true ? 1 : 0 10 | name = "alb-${var.name}" 11 | subnets = [var.subnets[0], var.subnets[1]] 12 | security_groups = [var.security_group] 13 | load_balancer_type = "application" 14 | internal = false 15 | enable_http2 = true 16 | idle_timeout = 30 17 | } 18 | 19 | # ------- ALB Listenet for HTTPS ------- 20 | resource "aws_alb_listener" "https_listener" { 21 | count = var.create_alb == true ? (var.enable_https == true ? 1 : 0) : 0 22 | load_balancer_arn = aws_alb.alb[0].id 23 | port = "443" 24 | protocol = "HTTPS" 25 | 26 | default_action { 27 | target_group_arn = var.target_group 28 | type = "forward" 29 | } 30 | 31 | lifecycle { 32 | // to avoid changes generated by CodeDeploy changes 33 | ignore_changes = [default_action] 34 | } 35 | } 36 | 37 | # ------- ALB Listener for HTTP ------- 38 | resource "aws_alb_listener" "http_listener" { 39 | count = var.create_alb == true ? 1 : 0 40 | load_balancer_arn = aws_alb.alb[0].id 41 | port = "80" 42 | protocol = "HTTP" 43 | 44 | default_action { 45 | target_group_arn = var.target_group 46 | type = "forward" 47 | } 48 | 49 | lifecycle { 50 | // to avoid changes generated by CodeDeploy changes 51 | ignore_changes = [default_action] 52 | } 53 | } 54 | 55 | # ------- Target Groups for ALB ------- 56 | resource "aws_alb_target_group" "target_group" { 57 | count = var.create_target_group == true ? 1 : 0 58 | name = var.name 59 | port = var.port 60 | protocol = var.protocol 61 | vpc_id = var.vpc 62 | target_type = var.tg_type 63 | deregistration_delay = 5 64 | 65 | health_check { 66 | enabled = true 67 | interval = 15 68 | path = var.health_check_path 69 | port = var.health_check_port 70 | protocol = var.protocol 71 | timeout = 10 72 | healthy_threshold = 2 73 | unhealthy_threshold = 3 74 | matcher = "200" 75 | } 76 | 77 | lifecycle { 78 | create_before_destroy = true 79 | } 80 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/ALB/outputs.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | output "arn_alb" { 5 | value = (var.create_alb == true 6 | ? (length(aws_alb.alb) > 0 ? aws_alb.alb[0].arn : "") : "") 7 | } 8 | output "arn_tg" { 9 | value = (var.create_target_group == true 10 | ? (length(aws_alb_target_group.target_group) > 0 ? aws_alb_target_group.target_group[0].arn : "") : "") 11 | } 12 | 13 | output "tg_name" { 14 | value = (var.create_target_group == true 15 | ? (length(aws_alb_target_group.target_group) > 0 ? aws_alb_target_group.target_group[0].name : "") : "") 16 | } 17 | 18 | output "arn_listener" { 19 | value = (var.create_alb == true 20 | ? (length(aws_alb_listener.http_listener) > 0 ? aws_alb_listener.http_listener[0].arn : "") : "") 21 | } 22 | 23 | output "dns_alb" { 24 | value = (var.create_alb == true 25 | ? (length(aws_alb.alb) > 0 ? aws_alb.alb[0].dns_name : "") : "") 26 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/ALB/variables.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | variable "name" { 5 | description = "A name for the target group or ALB" 6 | type = string 7 | } 8 | 9 | variable "target_group" { 10 | description = "The ARN of the created target group" 11 | type = string 12 | default = "" 13 | } 14 | 15 | variable "target_group_green" { 16 | description = "The ANR of the created target group" 17 | type = string 18 | default = "" 19 | } 20 | 21 | variable "create_alb" { 22 | description = "Set to true to create an ALB" 23 | type = bool 24 | default = false 25 | } 26 | 27 | variable "enable_https" { 28 | description = "Set to true to create a HTTPS listener" 29 | type = bool 30 | default = false 31 | } 32 | 33 | variable "create_target_group" { 34 | description = "Set to true to create a Target Group" 35 | type = bool 36 | default = false 37 | } 38 | 39 | variable "subnets" { 40 | description = "Subnets IDs for ALB" 41 | type = list(any) 42 | default = [] 43 | } 44 | 45 | variable "security_group" { 46 | description = "Security group ID for the ALB" 47 | type = string 48 | default = "" 49 | } 50 | 51 | variable "port" { 52 | description = "The port that the targer group will use" 53 | type = number 54 | default = 80 55 | } 56 | 57 | variable "protocol" { 58 | description = "The protocol that the target group will use" 59 | type = string 60 | default = "" 61 | } 62 | 63 | variable "vpc" { 64 | description = "VPC ID for the Target Group" 65 | type = string 66 | default = "" 67 | } 68 | 69 | variable "tg_type" { 70 | description = "Target Group Type (instance, IP, lambda)" 71 | type = string 72 | default = "" 73 | } 74 | 75 | variable "health_check_path" { 76 | description = "The path in which the ALB will send health checks" 77 | type = string 78 | default = "" 79 | } 80 | 81 | variable "health_check_port" { 82 | description = "The port to which the ALB will send health checks" 83 | type = number 84 | default = 80 85 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/CodeBuild/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | /*================================== 5 | AWS CodeBuild Project 6 | ===================================*/ 7 | 8 | resource "aws_codebuild_project" "aws_codebuild" { 9 | name = var.name 10 | description = "Terraform codebuild project" 11 | build_timeout = "10" 12 | service_role = var.iam_role 13 | 14 | artifacts { 15 | type = "CODEPIPELINE" 16 | } 17 | 18 | environment { 19 | compute_type = "BUILD_GENERAL1_SMALL" 20 | image = "aws/codebuild/standard:4.0" 21 | type = "LINUX_CONTAINER" 22 | privileged_mode = true 23 | 24 | environment_variable { 25 | name = "AWS_REGION" 26 | value = var.region 27 | } 28 | 29 | environment_variable { 30 | name = "AWS_ACCOUNT_ID" 31 | value = var.account_id 32 | } 33 | 34 | environment_variable { 35 | name = "REPO_URL" 36 | value = var.ecr_repo_url 37 | } 38 | 39 | environment_variable { 40 | name = "IMAGE_TAG" 41 | value = "latest" 42 | } 43 | 44 | environment_variable { 45 | name = "DYNAMODB_TABLE" 46 | value = var.dynamodb_table_name 47 | } 48 | 49 | environment_variable { 50 | name = "TASK_DEFINITION_FAMILY" 51 | value = var.task_definition_family 52 | } 53 | 54 | environment_variable { 55 | name = "CONTAINER_NAME" 56 | value = var.container_name 57 | } 58 | 59 | environment_variable { 60 | name = "SERVICE_PORT" 61 | value = var.service_port 62 | } 63 | 64 | environment_variable { 65 | name = "FOLDER_PATH" 66 | value = var.folder_path 67 | } 68 | 69 | environment_variable { 70 | name = "ECS_ROLE" 71 | value = var.ecs_role 72 | } 73 | 74 | environment_variable { 75 | name = "ECS_TASK_ROLE" 76 | value = var.ecs_task_role 77 | } 78 | 79 | environment_variable { 80 | name = "SERVER_ALB_URL" 81 | value = var.server_alb_url 82 | } 83 | } 84 | 85 | logs_config { 86 | cloudwatch_logs { 87 | group_name = "log-group" 88 | stream_name = "log-stream" 89 | } 90 | } 91 | 92 | source { 93 | type = "CODEPIPELINE" 94 | buildspec = var.buildspec_path 95 | } 96 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/CodeBuild/outputs.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | output "project_id" { 5 | value = aws_codebuild_project.aws_codebuild.id 6 | } 7 | 8 | output "project_arn" { 9 | value = aws_codebuild_project.aws_codebuild.arn 10 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/CodeBuild/variables.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | variable "name" { 5 | type = string 6 | description = "CodeBuild Project name" 7 | } 8 | 9 | variable "iam_role" { 10 | type = string 11 | description = "IAM role to attach to CodeBuild" 12 | } 13 | variable "region" { 14 | type = string 15 | description = "AWS Region used" 16 | } 17 | variable "account_id" { 18 | description = "AWS Account ID where the solution is being deployed" 19 | type = string 20 | } 21 | variable "ecr_repo_url" { 22 | description = "AWS ECR repository URL where docker images are being stored" 23 | type = string 24 | } 25 | 26 | variable "folder_path" { 27 | description = "Folder path to use to build the docker images/containers" 28 | type = string 29 | } 30 | 31 | variable "buildspec_path" { 32 | description = "Path to for the Buildspec file" 33 | type = string 34 | } 35 | 36 | variable "task_definition_family" { 37 | description = "The family name of the Task definition" 38 | type = string 39 | } 40 | 41 | variable "container_name" { 42 | description = "The name of the Container specified in the Task definition" 43 | type = string 44 | } 45 | 46 | variable "service_port" { 47 | description = "The number of the port used by the ECS Service" 48 | type = number 49 | } 50 | 51 | variable "ecs_role" { 52 | description = "The name of the ECS Task Excecution role to specify in the Task Definition" 53 | type = string 54 | } 55 | 56 | variable "server_alb_url" { 57 | description = "The server ALB DNS. Used to build the code for the frontend layer" 58 | type = string 59 | default = "" 60 | } 61 | 62 | variable "ecs_task_role" { 63 | description = "The name of the ECS Task role to specify in the Task Definition" 64 | type = string 65 | default = "null" 66 | } 67 | 68 | variable "dynamodb_table_name" { 69 | description = "The name of Dynamodb table used by the server application" 70 | type = string 71 | default = "" 72 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/CodeDeploy/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | /*==================================================================== 5 | AWS CodeDeploy integration for Blue/Green Deployments. 6 | ====================================================================*/ 7 | 8 | # ------- AWS CodeDeploy App defintion for each module ------- 9 | resource "aws_codedeploy_app" "main" { 10 | compute_platform = "ECS" 11 | name = var.name 12 | } 13 | 14 | # ------- AWS CodeDeploy Group for each CodeDeploy App created ------- 15 | resource "aws_codedeploy_deployment_group" "main" { 16 | app_name = aws_codedeploy_app.main.name 17 | deployment_config_name = "CodeDeployDefault.ECSAllAtOnce" 18 | deployment_group_name = "deployment-group-${var.name}" 19 | service_role_arn = var.codedeploy_role 20 | 21 | auto_rollback_configuration { 22 | enabled = true 23 | events = ["DEPLOYMENT_FAILURE"] 24 | } 25 | 26 | blue_green_deployment_config { 27 | deployment_ready_option { 28 | action_on_timeout = "CONTINUE_DEPLOYMENT" 29 | } 30 | 31 | terminate_blue_instances_on_deployment_success { 32 | action = "TERMINATE" 33 | termination_wait_time_in_minutes = 5 34 | } 35 | } 36 | 37 | deployment_style { 38 | deployment_option = "WITH_TRAFFIC_CONTROL" 39 | deployment_type = "BLUE_GREEN" 40 | } 41 | 42 | ecs_service { 43 | cluster_name = var.ecs_cluster 44 | service_name = var.ecs_service 45 | } 46 | 47 | load_balancer_info { 48 | target_group_pair_info { 49 | prod_traffic_route { 50 | listener_arns = [ 51 | var.alb_listener 52 | ] 53 | } 54 | 55 | target_group { 56 | name = var.tg_blue 57 | } 58 | 59 | target_group { 60 | name = var.tg_green 61 | } 62 | } 63 | } 64 | 65 | trigger_configuration { 66 | trigger_events = [ 67 | "DeploymentSuccess", 68 | "DeploymentFailure", 69 | ] 70 | 71 | trigger_name = var.trigger_name 72 | trigger_target_arn = var.sns_topic_arn 73 | } 74 | 75 | lifecycle { 76 | ignore_changes = [blue_green_deployment_config] 77 | } 78 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/CodeDeploy/outputs.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | output "deployment_group_name" { 5 | value = aws_codedeploy_deployment_group.main.deployment_group_name 6 | } 7 | 8 | output "deployment_group_arn" { 9 | value = aws_codedeploy_deployment_group.main.arn 10 | } 11 | 12 | output "application_name" { 13 | value = aws_codedeploy_deployment_group.main.app_name 14 | } 15 | 16 | output "application_arn" { 17 | value = aws_codedeploy_app.main.arn 18 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/CodeDeploy/variables.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | variable "name" { 5 | description = "The name of the CodeDeploy application" 6 | type = string 7 | } 8 | 9 | variable "ecs_cluster" { 10 | description = "The name of the ECS cluster where to deploy" 11 | type = string 12 | } 13 | 14 | variable "ecs_service" { 15 | description = "The name of the ECS service to deploy" 16 | type = string 17 | } 18 | 19 | variable "alb_listener" { 20 | description = "The ARN of the ALB listener for production" 21 | type = string 22 | } 23 | 24 | variable "tg_blue" { 25 | description = "The Target group name for the Blue part" 26 | type = string 27 | } 28 | 29 | variable "tg_green" { 30 | description = "The Target group name for the Green part" 31 | type = string 32 | } 33 | 34 | variable "sns_topic_arn" { 35 | description = "The ARN of the SNS topic where to deliver notifications" 36 | type = string 37 | } 38 | 39 | variable "trigger_name" { 40 | description = "The name of the notification trigger" 41 | type = string 42 | default = "CodeDeploy_notification" 43 | } 44 | 45 | variable "codedeploy_role" { 46 | description = "The role to be assumed by CodeDeploy" 47 | type = string 48 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/CodePipeline/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | /*======================================================= 5 | AWS CodePipeline for build and deployment 6 | ========================================================*/ 7 | 8 | resource "aws_codepipeline" "aws_codepipeline" { 9 | name = var.name 10 | role_arn = var.pipe_role 11 | 12 | artifact_store { 13 | location = var.s3_bucket 14 | type = "S3" 15 | } 16 | 17 | stage { 18 | name = "Source" 19 | 20 | action { 21 | name = "Source" 22 | category = "Source" 23 | owner = "ThirdParty" 24 | provider = "GitHub" 25 | version = "1" 26 | output_artifacts = ["SourceArtifact"] 27 | 28 | configuration = { 29 | OAuthToken = var.github_token 30 | Owner = var.repo_owner 31 | Repo = var.repo_name 32 | Branch = var.branch 33 | PollForSourceChanges = true 34 | } 35 | } 36 | } 37 | 38 | stage { 39 | name = "Build" 40 | 41 | action { 42 | name = "Build_server" 43 | category = "Build" 44 | owner = "AWS" 45 | provider = "CodeBuild" 46 | version = "1" 47 | input_artifacts = ["SourceArtifact"] 48 | output_artifacts = ["BuildArtifact_server"] 49 | 50 | configuration = { 51 | ProjectName = var.codebuild_project_server 52 | } 53 | } 54 | 55 | action { 56 | name = "Build_client" 57 | category = "Build" 58 | owner = "AWS" 59 | provider = "CodeBuild" 60 | version = "1" 61 | input_artifacts = ["SourceArtifact"] 62 | output_artifacts = ["BuildArtifact_client"] 63 | configuration = { 64 | ProjectName = var.codebuild_project_client 65 | } 66 | } 67 | } 68 | 69 | stage { 70 | name = "Deploy" 71 | 72 | action { 73 | name = "Deploy_server" 74 | category = "Deploy" 75 | owner = "AWS" 76 | provider = "CodeDeployToECS" 77 | input_artifacts = ["BuildArtifact_server"] 78 | version = "1" 79 | 80 | configuration = { 81 | ApplicationName = var.app_name_server 82 | DeploymentGroupName = var.deployment_group_server 83 | TaskDefinitionTemplateArtifact = "BuildArtifact_server" 84 | TaskDefinitionTemplatePath = "taskdef.json" 85 | AppSpecTemplateArtifact = "BuildArtifact_server" 86 | AppSpecTemplatePath = "appspec.yaml" 87 | } 88 | } 89 | 90 | action { 91 | name = "Deploy_client" 92 | category = "Deploy" 93 | owner = "AWS" 94 | provider = "CodeDeployToECS" 95 | input_artifacts = ["BuildArtifact_client"] 96 | version = "1" 97 | 98 | configuration = { 99 | ApplicationName = var.app_name_client 100 | DeploymentGroupName = var.deployment_group_client 101 | TaskDefinitionTemplateArtifact = "BuildArtifact_client" 102 | TaskDefinitionTemplatePath = "taskdef.json" 103 | AppSpecTemplateArtifact = "BuildArtifact_client" 104 | AppSpecTemplatePath = "appspec.yaml" 105 | } 106 | } 107 | } 108 | 109 | lifecycle { 110 | # prevents github OAuthToken from causing updates, since it's removed from state file 111 | ignore_changes = [stage[0].action[0].configuration] 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/CodePipeline/outputs.tf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NinjaDevOps0831/amazon-ecs-fullstack-app-terraform/f25b5de6cf26a771429049628fdcd0c06fdcf22b/Infrastructure/Modules/CodePipeline/outputs.tf -------------------------------------------------------------------------------- /Infrastructure/Modules/CodePipeline/variables.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | variable "name" { 5 | description = "The CodePipeline pipeline name" 6 | type = string 7 | } 8 | 9 | variable "pipe_role" { 10 | description = "The role assumed by CodePipeline" 11 | type = string 12 | } 13 | 14 | variable "s3_bucket" { 15 | description = "S3 bucket used for the artifact store" 16 | type = string 17 | } 18 | 19 | variable "github_token" { 20 | description = "Personal access token from Github" 21 | type = string 22 | sensitive = true 23 | } 24 | 25 | variable "repo_owner" { 26 | description = "The username of the Github repository owner" 27 | type = string 28 | } 29 | 30 | variable "repo_name" { 31 | description = "Github repository's name" 32 | type = string 33 | } 34 | 35 | variable "branch" { 36 | description = "Github branch used to trigger the CodePipeline" 37 | type = string 38 | } 39 | 40 | variable "codebuild_project_server" { 41 | description = "Server's CodeBuild project name" 42 | type = string 43 | } 44 | 45 | variable "codebuild_project_client" { 46 | description = "Client's CodeBuild project name" 47 | type = string 48 | } 49 | 50 | variable "app_name_server" { 51 | description = "CodeDeploy Application name for the server" 52 | type = string 53 | } 54 | 55 | variable "app_name_client" { 56 | description = "CodeDeploy Application name for the client" 57 | type = string 58 | } 59 | 60 | variable "deployment_group_server" { 61 | description = "CodeDeploy deployment group name for the server" 62 | type = string 63 | } 64 | 65 | variable "deployment_group_client" { 66 | description = "CodeDeploy deployment group name for the client" 67 | type = string 68 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/Dynamodb/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | /*======================================= 5 | Amazon Dynamodb resources 6 | ========================================*/ 7 | 8 | resource "aws_dynamodb_table" "dynamodb_table" { 9 | name = var.name 10 | billing_mode = "PAY_PER_REQUEST" 11 | hash_key = var.hash_key 12 | range_key = var.range_key 13 | 14 | dynamic "attribute" { 15 | for_each = var.attributes 16 | content { 17 | name = attribute.value.name 18 | type = attribute.value.type 19 | } 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/Dynamodb/outputs.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | output "dynamodb_table_arn" { 5 | value = aws_dynamodb_table.dynamodb_table.arn 6 | } 7 | 8 | output "dynamodb_table_name" { 9 | value = aws_dynamodb_table.dynamodb_table.name 10 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/Dynamodb/variables.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | variable "name" { 5 | description = "The name of your Dynamodb table" 6 | type = string 7 | } 8 | 9 | variable "hash_key" { 10 | description = "The identifier of your hash key" 11 | type = string 12 | default = "id" 13 | } 14 | 15 | variable "range_key" { 16 | description = "The identifier of your range key" 17 | type = string 18 | default = null 19 | } 20 | 21 | variable "attributes" { 22 | description = "A set of atributes names and types that compone the table" 23 | type = list(object({ name = string, type = string })) 24 | default = [ 25 | { 26 | name = "id", 27 | type = "N", 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/ECR/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | /*========================================= 5 | AWS Elastic Container Repository 6 | ==========================================*/ 7 | 8 | resource "aws_ecr_repository" "ecr_repository" { 9 | name = var.name 10 | image_tag_mutability = "MUTABLE" 11 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/ECR/outputs.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | output "ecr_repository_url" { 5 | value = aws_ecr_repository.ecr_repository.repository_url 6 | } 7 | 8 | output "ecr_repository_arn" { 9 | value = aws_ecr_repository.ecr_repository.arn 10 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/ECR/variables.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | variable "name" { 5 | description = "The name of your ECR repository" 6 | type = string 7 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/ECS/Autoscaling/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | /*==================================== 5 | AWS ECS Autoscaling 6 | =====================================*/ 7 | 8 | # ------- AWS Autoscaling target to linke the ECS cluster and service ------- 9 | resource "aws_appautoscaling_target" "ecs_target" { 10 | min_capacity = var.min_capacity 11 | max_capacity = var.max_capacity 12 | resource_id = "service/${var.cluster_name}/Service-${var.name}" 13 | scalable_dimension = "ecs:service:DesiredCount" 14 | service_namespace = "ecs" 15 | 16 | lifecycle { 17 | ignore_changes = [ 18 | role_arn, 19 | ] 20 | } 21 | } 22 | 23 | # ------- AWS Autoscaling policy using CPU allocation ------- 24 | resource "aws_appautoscaling_policy" "cpu" { 25 | name = "ecs_scale_cpu_service_${var.name}" 26 | resource_id = aws_appautoscaling_target.ecs_target.resource_id 27 | scalable_dimension = aws_appautoscaling_target.ecs_target.scalable_dimension 28 | service_namespace = aws_appautoscaling_target.ecs_target.service_namespace 29 | policy_type = "TargetTrackingScaling" 30 | 31 | target_tracking_scaling_policy_configuration { 32 | target_value = 50 33 | scale_in_cooldown = 60 34 | scale_out_cooldown = 60 35 | 36 | predefined_metric_specification { 37 | predefined_metric_type = "ECSServiceAverageCPUUtilization" 38 | } 39 | } 40 | } 41 | 42 | # ------- AWS Autoscaling policy using memory allocation ------- 43 | resource "aws_appautoscaling_policy" "memory" { 44 | name = "ecs_scale_memory_service_${var.name}" 45 | resource_id = aws_appautoscaling_target.ecs_target.resource_id 46 | scalable_dimension = aws_appautoscaling_target.ecs_target.scalable_dimension 47 | service_namespace = aws_appautoscaling_target.ecs_target.service_namespace 48 | policy_type = "TargetTrackingScaling" 49 | 50 | target_tracking_scaling_policy_configuration { 51 | target_value = 50 52 | scale_in_cooldown = 60 53 | scale_out_cooldown = 60 54 | 55 | predefined_metric_specification { 56 | predefined_metric_type = "ECSServiceAverageMemoryUtilization" 57 | } 58 | } 59 | } 60 | 61 | /*============================================== 62 | AWS Cloudwatch for ECS Autoscaling 63 | ===============================================*/ 64 | 65 | # ------- High memory alarm ------- 66 | resource "aws_cloudwatch_metric_alarm" "high-memory-policy-alarm" { 67 | alarm_name = "high-memory-ecs-service-${var.name}" 68 | alarm_description = "High Memory for ecs service-${var.name}" 69 | comparison_operator = "GreaterThanOrEqualToThreshold" 70 | evaluation_periods = "2" 71 | metric_name = "MemoryUtilization" 72 | namespace = "AWS/ECS" 73 | period = "60" 74 | statistic = "Maximum" 75 | threshold = 50 76 | 77 | dimensions = { 78 | "ServiceName" = "Service-${var.name}", 79 | "ClusterName" = var.cluster_name 80 | } 81 | 82 | } 83 | 84 | # ------- High CPU alarm ------- 85 | resource "aws_cloudwatch_metric_alarm" "high-cpu-policy-alarm" { 86 | alarm_name = "high-cpu-ecs-service-${var.name}" 87 | alarm_description = "High CPUPolicy Landing Page for ecs service-${var.name}" 88 | comparison_operator = "GreaterThanOrEqualToThreshold" 89 | evaluation_periods = "2" 90 | metric_name = "CPUUtilization" 91 | namespace = "AWS/ECS" 92 | period = "60" 93 | statistic = "Maximum" 94 | threshold = 50 95 | 96 | dimensions = { 97 | "ServiceName" = "Service-${var.name}", 98 | "ClusterName" = var.cluster_name 99 | } 100 | 101 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/ECS/Autoscaling/outputs.tf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NinjaDevOps0831/amazon-ecs-fullstack-app-terraform/f25b5de6cf26a771429049628fdcd0c06fdcf22b/Infrastructure/Modules/ECS/Autoscaling/outputs.tf -------------------------------------------------------------------------------- /Infrastructure/Modules/ECS/Autoscaling/variables.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | variable "min_capacity" { 5 | description = "The minimal number of ECS tasks to run" 6 | type = number 7 | } 8 | 9 | variable "max_capacity" { 10 | description = "The maximal number of ECS tasks to run" 11 | type = number 12 | } 13 | 14 | variable "cluster_name" { 15 | description = "The name of the ECS cluster" 16 | type = string 17 | } 18 | 19 | variable "name" { 20 | description = "The name for the ECS service" 21 | type = string 22 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/ECS/Cluster/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | /*============================= 5 | AWS ECS Cluster 6 | ===============================*/ 7 | 8 | resource "aws_ecs_cluster" "ecs_cluster" { 9 | name = "Cluster-${var.name}" 10 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/ECS/Cluster/outputs.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | output "ecs_cluster_name" { 5 | value = aws_ecs_cluster.ecs_cluster.name 6 | } 7 | 8 | output "ecs_cluster_id" { 9 | value = aws_ecs_cluster.ecs_cluster.id 10 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/ECS/Cluster/variable.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | variable "name" { 5 | description = "The name of the deployed environment" 6 | type = string 7 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/ECS/Service/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | /*========================== 5 | AWS ECS Service 6 | ===========================*/ 7 | 8 | resource "aws_ecs_service" "ecs_service" { 9 | name = "Service-${var.name}" 10 | cluster = var.ecs_cluster_id 11 | task_definition = var.arn_task_definition 12 | desired_count = var.desired_tasks 13 | health_check_grace_period_seconds = 10 14 | launch_type = "FARGATE" 15 | 16 | network_configuration { 17 | security_groups = [var.arn_security_group] 18 | subnets = [var.subnets_id[0], var.subnets_id[1]] 19 | } 20 | 21 | load_balancer { 22 | target_group_arn = var.arn_target_group 23 | container_name = var.container_name 24 | container_port = var.container_port 25 | } 26 | 27 | deployment_controller { 28 | type = "CODE_DEPLOY" 29 | } 30 | 31 | lifecycle { 32 | // to avoid changes generated by autoscaling or new CodeDeploy changes 33 | ignore_changes = [desired_count, task_definition, load_balancer] 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/ECS/Service/outputs.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | output "ecs_service_name" { 5 | value = aws_ecs_service.ecs_service.name 6 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/ECS/Service/variables.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | variable "name" { 5 | description = "The name for the ecs service" 6 | type = string 7 | } 8 | 9 | variable "desired_tasks" { 10 | description = "The minumum number of tasks to run in the service" 11 | type = string 12 | } 13 | 14 | variable "arn_security_group" { 15 | description = "ARN of the security group for the tasks" 16 | type = string 17 | } 18 | 19 | variable "ecs_cluster_id" { 20 | description = "The ECS cluster ID in which the resources will be created" 21 | type = string 22 | } 23 | 24 | variable "arn_target_group" { 25 | description = "The ARN of the AWS Target Group to put the ECS task" 26 | type = string 27 | } 28 | 29 | variable "arn_task_definition" { 30 | description = "The ARN of the Task Definition to use to deploy the tasks" 31 | type = string 32 | } 33 | 34 | variable "subnets_id" { 35 | description = "Subnet ID in which ecs will deploy the tasks" 36 | type = list(string) 37 | } 38 | 39 | variable "container_port" { 40 | description = "The port that the container will listen request" 41 | type = string 42 | } 43 | 44 | variable "container_name" { 45 | description = "The name of the container" 46 | type = string 47 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/ECS/TaskDefinition/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | /*==================================== 5 | AWS ECS Task definition 6 | =====================================*/ 7 | 8 | resource "aws_ecs_task_definition" "ecs_task_definition" { 9 | family = "task-definition-${var.name}" 10 | network_mode = "awsvpc" 11 | requires_compatibilities = ["FARGATE"] 12 | cpu = var.cpu 13 | memory = var.memory 14 | execution_role_arn = var.execution_role_arn 15 | task_role_arn = var.task_role_arn 16 | 17 | container_definitions = < 0 ? 1 : 0 150 | policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" 151 | role = aws_iam_role.ecs_task_excecution_role[0].name 152 | 153 | lifecycle { 154 | create_before_destroy = true 155 | } 156 | } 157 | 158 | resource "aws_iam_role_policy_attachment" "attachment2" { 159 | count = var.create_devops_policy == true ? 1 : 0 160 | policy_arn = aws_iam_policy.policy_for_role[0].arn 161 | role = var.attach_to 162 | 163 | lifecycle { 164 | create_before_destroy = true 165 | } 166 | } 167 | 168 | resource "aws_iam_role_policy_attachment" "codedeploy_attachment" { 169 | count = var.create_codedeploy_role == true ? 1 : 0 170 | policy_arn = "arn:aws:iam::aws:policy/AWSCodeDeployRoleForECS" 171 | role = aws_iam_role.codedeploy_role[0].name 172 | } 173 | 174 | # ------- IAM Policy Documents ------- 175 | data "aws_iam_policy_document" "role_policy_devops_role" { 176 | statement { 177 | sid = "AllowS3Actions" 178 | effect = "Allow" 179 | actions = [ 180 | "s3:PutObject", 181 | "s3:GetObject", 182 | "s3:GetObjectVersion", 183 | "s3:GetBucketAcl", 184 | "s3:List*" 185 | ] 186 | resources = ["*"] 187 | } 188 | statement { 189 | sid = "AllowCodebuildActions" 190 | effect = "Allow" 191 | actions = [ 192 | "codebuild:BatchGetBuilds", 193 | "codebuild:StartBuild", 194 | "codebuild:BatchGetBuildBatches", 195 | "codebuild:StartBuildBatch", 196 | "codebuild:StopBuild" 197 | ] 198 | resources = var.code_build_projects 199 | } 200 | statement { 201 | sid = "AllowCodebuildList" 202 | effect = "Allow" 203 | actions = [ 204 | "codebuild:ListBuilds" 205 | ] 206 | resources = ["*"] 207 | } 208 | statement { 209 | sid = "AllowCodeDeployActions" 210 | effect = "Allow" 211 | actions = [ 212 | "codedeploy:CreateDeployment", 213 | "codedeploy:GetApplication", 214 | "codedeploy:GetApplicationRevision", 215 | "codedeploy:GetDeployment", 216 | "codedeploy:GetDeploymentGroup", 217 | "codedeploy:RegisterApplicationRevision" 218 | ] 219 | resources = var.code_deploy_resources 220 | } 221 | statement { 222 | sid = "AllowCodeDeployConfigs" 223 | effect = "Allow" 224 | actions = [ 225 | "codedeploy:GetDeploymentConfig", 226 | "codedeploy:CreateDeploymentConfig", 227 | "codedeploy:CreateDeploymentGroup", 228 | "codedeploy:GetDeploymentTarget", 229 | "codedeploy:StopDeployment", 230 | "codedeploy:ListApplications", 231 | "codedeploy:ListDeploymentConfigs", 232 | "codedeploy:ListDeploymentGroups", 233 | "codedeploy:ListDeployments" 234 | 235 | ] 236 | resources = ["*"] 237 | } 238 | statement { 239 | sid = "AllowECRActions" 240 | effect = "Allow" 241 | actions = [ 242 | "ecr:BatchCheckLayerAvailability", 243 | "ecr:CompleteLayerUpload", 244 | "ecr:BatchGetImage", 245 | "ecr:GetDownloadUrlForLayer", 246 | "ecr:InitiateLayerUpload", 247 | "ecr:PutImage", 248 | "ecr:UploadLayerPart" 249 | ] 250 | resources = var.ecr_repositories 251 | } 252 | statement { 253 | sid = "AllowECRAuthorization" 254 | effect = "Allow" 255 | actions = [ 256 | "ecr:GetAuthorizationToken", 257 | ] 258 | resources = ["*"] 259 | } 260 | statement { 261 | sid = "AllowCECSServiceActions" 262 | effect = "Allow" 263 | actions = [ 264 | "ecs:ListServices", 265 | "ecs:ListTasks", 266 | "ecs:DescribeServices", 267 | "ecs:DescribeTasks", 268 | "ecs:DescribeTaskDefinition", 269 | "ecs:DescribeTaskSets", 270 | "ecs:DeleteTaskSet", 271 | "ecs:DeregisterContainerInstance", 272 | "ecs:CreateTaskSet", 273 | "ecs:UpdateCapacityProvider", 274 | "ecs:PutClusterCapacityProviders", 275 | "ecs:UpdateServicePrimaryTaskSet", 276 | "ecs:RegisterTaskDefinition", 277 | "ecs:RunTask", 278 | "ecs:StartTask", 279 | "ecs:StopTask", 280 | "ecs:UpdateService", 281 | "ecs:UpdateCluster", 282 | "ecs:UpdateTaskSet" 283 | ] 284 | resources = ["*"] 285 | } 286 | statement { 287 | sid = "AllowIAMPassRole" 288 | effect = "Allow" 289 | actions = [ 290 | "iam:PassRole" 291 | ] 292 | resources = ["*"] 293 | } 294 | statement { 295 | sid = "AllowCloudWatchActions" 296 | effect = "Allow" 297 | actions = [ 298 | "logs:CreateLogGroup", 299 | "logs:CreateLogStream", 300 | "logs:PutLogEvents" 301 | ] 302 | resources = ["*"] 303 | } 304 | } 305 | 306 | data "aws_iam_policy_document" "role_policy_ecs_task_role" { 307 | statement { 308 | sid = "AllowS3Actions" 309 | effect = "Allow" 310 | actions = [ 311 | "s3:GetObject", 312 | "s3:ListBucket" 313 | ] 314 | resources = var.s3_bucket_assets 315 | } 316 | statement { 317 | sid = "AllowIAMPassRole" 318 | effect = "Allow" 319 | actions = [ 320 | "iam:PassRole" 321 | ] 322 | resources = ["*"] 323 | } 324 | statement { 325 | sid = "AllowDynamodbActions" 326 | effect = "Allow" 327 | actions = [ 328 | "dynamodb:BatchGetItem", 329 | "dynamodb:Describe*", 330 | "dynamodb:List*", 331 | "dynamodb:GetItem", 332 | "dynamodb:Query", 333 | "dynamodb:Scan", 334 | ] 335 | resources = var.dynamodb_table 336 | } 337 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/IAM/outputs.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | output "arn_role" { 5 | value = (var.create_ecs_role == true 6 | ? (length(aws_iam_role.ecs_task_excecution_role) > 0 ? aws_iam_role.ecs_task_excecution_role[0].arn : "") 7 | : (length(aws_iam_role.devops_role) > 0 ? aws_iam_role.devops_role[0].arn : "")) 8 | } 9 | 10 | output "name_role" { 11 | value = (var.create_ecs_role == true 12 | ? (length(aws_iam_role.ecs_task_excecution_role) > 0 ? aws_iam_role.ecs_task_excecution_role[0].name : "") 13 | : (length(aws_iam_role.devops_role) > 0 ? aws_iam_role.devops_role[0].name : "")) 14 | } 15 | 16 | output "arn_role_codedeploy" { 17 | value = (var.create_codedeploy_role == true 18 | ? (length(aws_iam_role.codedeploy_role) > 0 ? aws_iam_role.codedeploy_role[0].arn : "") 19 | : "") 20 | } 21 | 22 | output "arn_role_ecs_task_role" { 23 | value = (var.create_ecs_role == true 24 | ? (length(aws_iam_role.ecs_task_role) > 0 ? aws_iam_role.ecs_task_role[0].arn : "") 25 | : "") 26 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/IAM/variables.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | variable "name" { 5 | description = "The name for the Role" 6 | type = string 7 | } 8 | 9 | variable "name_ecs_task_role" { 10 | description = "The name for the Ecs Task Role" 11 | type = string 12 | default = null 13 | } 14 | 15 | variable "create_ecs_role" { 16 | description = "Set this variable to true if you want to create a role for ECS" 17 | type = bool 18 | default = false 19 | } 20 | 21 | 22 | variable "create_devops_role" { 23 | description = "Set this variable to true if you want to create a role for AWS DevOps Tools" 24 | type = bool 25 | default = false 26 | } 27 | 28 | variable "create_codedeploy_role" { 29 | description = "Set this variable to true if you want to create a role for AWS CodeDeploy" 30 | type = bool 31 | default = false 32 | } 33 | 34 | variable "create_devops_policy" { 35 | description = "Set this variable to true if you want to create a policy for AWS DevOps Tools" 36 | type = bool 37 | default = false 38 | } 39 | 40 | variable "create_policy" { 41 | description = "Set this variable to true if you want to create an IAM Policy" 42 | type = bool 43 | default = false 44 | } 45 | 46 | variable "attach_to" { 47 | description = "The ARN or role name to attach the policy created" 48 | type = string 49 | default = "" 50 | } 51 | 52 | variable "ecr_repositories" { 53 | description = "The ECR repositories to which grant IAM access" 54 | type = list(string) 55 | default = ["*"] 56 | } 57 | 58 | variable "code_build_projects" { 59 | description = "The Code Build projects to which grant IAM access" 60 | type = list(string) 61 | default = ["*"] 62 | } 63 | 64 | variable "code_deploy_resources" { 65 | description = "The Code Deploy applications and deployment groups to which grant IAM access" 66 | type = list(string) 67 | default = ["*"] 68 | } 69 | 70 | variable "dynamodb_table" { 71 | description = "The name of the Dynamodb table to which grant IAM access" 72 | type = list(string) 73 | default = ["*"] 74 | } 75 | 76 | variable "s3_bucket_assets" { 77 | description = "The name of the S3 bucket to which grant IAM access" 78 | type = list(string) 79 | default = ["*"] 80 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/Networking/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | /*================================================== 5 | AWS Networking for the whole solution 6 | ===================================================*/ 7 | 8 | # ------- VPC Creation ------- 9 | resource "aws_vpc" "aws_vpc" { 10 | cidr_block = var.cidr[0] 11 | instance_tenancy = "default" 12 | enable_dns_hostnames = true 13 | enable_dns_support = true 14 | tags = { 15 | Name = "vpc_${var.name}" 16 | } 17 | } 18 | 19 | # ------- Get Region Available Zones ------- 20 | data "aws_availability_zones" "az_availables" { 21 | state = "available" 22 | } 23 | 24 | # ------- Subnets Creation ------- 25 | 26 | # ------- Public Subnets ------- 27 | resource "aws_subnet" "public_subnets" { 28 | count = 2 29 | availability_zone = data.aws_availability_zones.az_availables.names[count.index] 30 | vpc_id = aws_vpc.aws_vpc.id 31 | cidr_block = cidrsubnet(aws_vpc.aws_vpc.cidr_block, 7, count.index + 1) 32 | map_public_ip_on_launch = true 33 | tags = { 34 | Name = "public_subnet_${count.index}_${var.name}" 35 | } 36 | } 37 | 38 | # ------- Private Subnets ------- 39 | resource "aws_subnet" "private_subnets_client" { 40 | count = 2 41 | availability_zone = data.aws_availability_zones.az_availables.names[count.index] 42 | vpc_id = aws_vpc.aws_vpc.id 43 | cidr_block = cidrsubnet(aws_vpc.aws_vpc.cidr_block, 7, count.index + 3) 44 | tags = { 45 | Name = "private_subnet_client_${count.index}_${var.name}" 46 | } 47 | } 48 | 49 | resource "aws_subnet" "private_subnets_server" { 50 | count = 2 51 | availability_zone = data.aws_availability_zones.az_availables.names[count.index] 52 | vpc_id = aws_vpc.aws_vpc.id 53 | cidr_block = cidrsubnet(aws_vpc.aws_vpc.cidr_block, 7, count.index + 5) 54 | tags = { 55 | Name = "private_subnet_server_${count.index}_${var.name}" 56 | } 57 | } 58 | 59 | # ------- Internet Gateway ------- 60 | resource "aws_internet_gateway" "igw" { 61 | vpc_id = aws_vpc.aws_vpc.id 62 | tags = { 63 | Name = "igw_${var.name}" 64 | } 65 | } 66 | 67 | # ------- Create Default Route Public Table ------- 68 | resource "aws_default_route_table" "rt_public" { 69 | default_route_table_id = aws_vpc.aws_vpc.default_route_table_id 70 | 71 | # ------- Internet Route ------- 72 | route { 73 | cidr_block = "0.0.0.0/0" 74 | gateway_id = aws_internet_gateway.igw.id 75 | } 76 | 77 | tags = { 78 | Name = "public_rt_${var.name}" 79 | } 80 | } 81 | 82 | # ------- Create EIP ------- 83 | resource "aws_eip" "eip" { 84 | vpc = true 85 | tags = { 86 | Name = "eip-${var.name}" 87 | } 88 | } 89 | 90 | # ------- Attach EIP to Nat Gateway ------- 91 | resource "aws_nat_gateway" "natgw" { 92 | allocation_id = aws_eip.eip.id 93 | subnet_id = aws_subnet.public_subnets[0].id 94 | tags = { 95 | Name = "nat_${var.name}" 96 | } 97 | } 98 | 99 | # ------- Create Private Route Private Table ------- 100 | resource "aws_route_table" "rt_private" { 101 | vpc_id = aws_vpc.aws_vpc.id 102 | 103 | # ------- Internet Route ------- 104 | route { 105 | cidr_block = "0.0.0.0/0" 106 | gateway_id = aws_nat_gateway.natgw.id 107 | } 108 | 109 | tags = { 110 | Name = "private_rt_${var.name}" 111 | } 112 | } 113 | 114 | # ------- Private Subnets Association ------- 115 | resource "aws_route_table_association" "rt_assoc_priv_subnets_client" { 116 | count = 2 117 | subnet_id = aws_subnet.private_subnets_client[count.index].id 118 | route_table_id = aws_route_table.rt_private.id 119 | } 120 | 121 | resource "aws_route_table_association" "rt_assoc_priv_subnets_server" { 122 | count = 2 123 | subnet_id = aws_subnet.private_subnets_server[count.index].id 124 | route_table_id = aws_route_table.rt_private.id 125 | } 126 | 127 | # ------- Public Subnets Association ------- 128 | resource "aws_route_table_association" "rt_assoc_pub_subnets" { 129 | count = 2 130 | subnet_id = aws_subnet.public_subnets[count.index].id 131 | route_table_id = aws_vpc.aws_vpc.main_route_table_id 132 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/Networking/outputs.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | output "aws_vpc" { 5 | value = aws_vpc.aws_vpc.id 6 | } 7 | 8 | output "public_subnets" { 9 | value = [aws_subnet.public_subnets[0].id, aws_subnet.public_subnets[1].id] 10 | 11 | } 12 | output "private_subnets_client" { 13 | value = [aws_subnet.private_subnets_client[0].id, aws_subnet.private_subnets_client[1].id] 14 | } 15 | 16 | output "private_subnets_server" { 17 | value = [aws_subnet.private_subnets_server[0].id, aws_subnet.private_subnets_server[1].id] 18 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/Networking/variables.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | variable "name" { 5 | description = "Provided name used for name concatenation of resources" 6 | type = string 7 | } 8 | 9 | variable "cidr" { 10 | description = "CIDR block" 11 | type = list(any) 12 | } 13 | 14 | variable "subnets_number" { 15 | description = "Number of subnets to create (independent from type)" 16 | default = 2 17 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/S3/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | /*=========================== 5 | AWS S3 resources 6 | ============================*/ 7 | 8 | resource "aws_s3_bucket" "s3_bucket" { 9 | bucket = var.bucket_name 10 | acl = "private" 11 | force_destroy = true 12 | tags = { 13 | Name = var.bucket_name 14 | } 15 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/S3/outputs.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | output "s3_bucket_id" { 5 | value = aws_s3_bucket.s3_bucket.id 6 | } 7 | 8 | output "s3_bucket_arn" { 9 | value = aws_s3_bucket.s3_bucket.arn 10 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/S3/variables.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | variable "bucket_name" { 5 | description = "The name of your S3 bucket" 6 | type = string 7 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/SNS/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | /*==================================================== 5 | AWS SNS topic for deployment notifications 6 | =====================================================*/ 7 | 8 | resource "aws_sns_topic" "sns_notifications" { 9 | name = var.sns_name 10 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/SNS/outputs.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | output "sns_arn" { 5 | value = aws_sns_topic.sns_notifications.arn 6 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/SNS/variables.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | variable "sns_name" { 5 | description = "The name of the SNS topic" 6 | type = string 7 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/SecurityGroup/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | /*================================ 5 | AWS Security group 6 | =================================*/ 7 | 8 | resource "aws_security_group" "sg" { 9 | name = var.name 10 | description = var.description 11 | vpc_id = var.vpc_id 12 | 13 | ingress { 14 | protocol = "tcp" 15 | from_port = var.ingress_port 16 | to_port = var.ingress_port 17 | cidr_blocks = var.cidr_blocks_ingress 18 | security_groups = var.security_groups 19 | } 20 | egress { 21 | from_port = var.egress_port 22 | to_port = var.egress_port 23 | protocol = "-1" 24 | cidr_blocks = var.cidr_blocks_egress 25 | } 26 | 27 | tags = { 28 | Name = var.name 29 | } 30 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/SecurityGroup/outputs.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | output "sg_id" { 5 | value = aws_security_group.sg.id 6 | } -------------------------------------------------------------------------------- /Infrastructure/Modules/SecurityGroup/variables.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | variable "name" { 5 | description = "The name of your security group" 6 | type = string 7 | } 8 | 9 | variable "description" { 10 | description = "A description of the purpose" 11 | type = string 12 | } 13 | 14 | variable "vpc_id" { 15 | description = "The ID of the VPC where the security group will take place" 16 | type = string 17 | } 18 | 19 | variable "ingress_port" { 20 | description = "Number of the port to open in the ingress rules" 21 | type = number 22 | default = 0 23 | } 24 | 25 | variable "egress_port" { 26 | description = "Number of the port to open in the egress rules" 27 | type = number 28 | default = 0 29 | } 30 | 31 | variable "security_groups" { 32 | description = "List of security group Group Names if using EC2-Classic, or Group IDs if using a VPC" 33 | type = list(any) 34 | default = null 35 | } 36 | 37 | variable "cidr_blocks_ingress" { 38 | description = "An ingress block of CIDR to grant access to" 39 | type = list(any) 40 | default = null 41 | } 42 | 43 | variable "cidr_blocks_egress" { 44 | description = "An ingress block of CIDR to grant access to" 45 | type = list(any) 46 | default = ["0.0.0.0/0"] 47 | } -------------------------------------------------------------------------------- /Infrastructure/Templates/appspec.yaml: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | version: 0.0 5 | Resources: 6 | - TargetService: 7 | Type: AWS::ECS::Service 8 | Properties: 9 | TaskDefinition: # Do not change this, the value is updated when your pipeline runs 10 | LoadBalancerInfo: 11 | ContainerName: "" 12 | ContainerPort: 13 | PlatformVersion: "LATEST" -------------------------------------------------------------------------------- /Infrastructure/Templates/buildspec.yml: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | version: 0.2 5 | 6 | phases: 7 | pre_build: 8 | commands: 9 | - echo Logging in to Amazon ECR... 10 | - aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com 11 | - echo Checking for build type 12 | - | 13 | if expr "${FOLDER_PATH}" : ".*client*" ; then 14 | echo "Client build, embedding frontend layer file with ALB backend DNS" 15 | sed -i "s||$SERVER_ALB_URL|g" ./Code/client/src/services/RestServices.js 16 | else 17 | echo "Server build, adding ECS Task Role to the task definition file" 18 | sed -i "3i\"taskRoleArn\": \"arn:aws:iam:::role/\"," ./Infrastructure/Templates/taskdef.json 19 | echo "Replacing table name in the server function" 20 | sed -i "s|DYNAMODB_TABLE|$DYNAMODB_TABLE|g" ./Code/server/src/app.js 21 | echo "Adding Swagger host" 22 | sed -i "s||$SERVER_ALB_URL|g" ./Code/server/src/swagger/swagger.js 23 | fi 24 | build: 25 | commands: 26 | - echo Build started on `date` 27 | - echo Building the Docker image... 28 | - docker build -t $REPO_URL $FOLDER_PATH 29 | post_build: 30 | commands: 31 | - echo Build completed on `date` 32 | - echo Pushing the Docker image... 33 | - docker push $REPO_URL:$IMAGE_TAG 34 | - echo Changing directory to Templates directory 35 | - cd ./Infrastructure/Templates 36 | - echo Preparing spec files in new folder 37 | - mkdir Artifacts 38 | - cp appspec.yaml Artifacts/appspec.yaml && cp taskdef.json Artifacts/taskdef.json 39 | - echo Changing directory to the Artifacts directory 40 | - cd Artifacts 41 | - echo Preparating artifacts 42 | - sed -i "s||$TASK_DEFINITION_FAMILY|g" taskdef.json 43 | - sed -i "s||$CONTAINER_NAME|g" appspec.yaml taskdef.json 44 | - sed -i "s||$SERVICE_PORT|g" appspec.yaml taskdef.json 45 | - sed -i "s||$ECS_ROLE|g" taskdef.json 46 | - sed -i "s||$ECS_TASK_ROLE|g" taskdef.json 47 | - sed -i "s||$REPO_URL|g" taskdef.json 48 | - sed -i "s||$AWS_ACCOUNT_ID|g" taskdef.json 49 | - sed -i "s||$AWS_REGION|g" taskdef.json 50 | 51 | artifacts: 52 | files: 53 | - '**/*' 54 | base-directory: 'Infrastructure/Templates/Artifacts' 55 | discard-paths: yes -------------------------------------------------------------------------------- /Infrastructure/Templates/taskdef.json: -------------------------------------------------------------------------------- 1 | { 2 | "executionRoleArn": "arn:aws:iam:::role/", 3 | "containerDefinitions": [ 4 | { 5 | "name": "", 6 | "image": "", 7 | "essential": true, 8 | "logConfiguration": { 9 | "logDriver": "awslogs", 10 | "secretOptions": null, 11 | "options": { 12 | "awslogs-group": "/ecs/", 13 | "awslogs-region": "", 14 | "awslogs-stream-prefix": "ecs" 15 | } 16 | }, 17 | "portMappings": [ 18 | { 19 | "hostPort": , 20 | "protocol": "tcp", 21 | "containerPort": 22 | } 23 | ] 24 | } 25 | ], 26 | "requiresCompatibilities": [ 27 | "FARGATE" 28 | ], 29 | "networkMode": "awsvpc", 30 | "memory": "512", 31 | "cpu": "256", 32 | "family": "" 33 | } -------------------------------------------------------------------------------- /Infrastructure/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | /*=========================== 5 | Root file 6 | ============================*/ 7 | 8 | # ------- Providers ------- 9 | provider "aws" { 10 | profile = var.aws_profile 11 | region = var.aws_region 12 | 13 | # provider level tags - yet inconsistent when executing 14 | # default_tags { 15 | # tags = { 16 | # Created_by = "Terraform" 17 | # Project = "AWS_demo_fullstack_devops" 18 | # } 19 | # } 20 | } 21 | 22 | # ------- Random numbers intended to be used as unique identifiers for resources ------- 23 | resource "random_id" "RANDOM_ID" { 24 | byte_length = "2" 25 | } 26 | 27 | # ------- Account ID ------- 28 | data "aws_caller_identity" "id_current_account" {} 29 | 30 | # ------- Networking ------- 31 | module "networking" { 32 | source = "./Modules/Networking" 33 | cidr = ["10.120.0.0/16"] 34 | name = var.environment_name 35 | } 36 | 37 | # ------- Creating Target Group for the server ALB blue environment ------- 38 | module "target_group_server_blue" { 39 | source = "./Modules/ALB" 40 | create_target_group = true 41 | name = "tg-${var.environment_name}-s-b" 42 | port = 80 43 | protocol = "HTTP" 44 | vpc = module.networking.aws_vpc 45 | tg_type = "ip" 46 | health_check_path = "/status" 47 | health_check_port = var.port_app_server 48 | } 49 | 50 | # ------- Creating Target Group for the server ALB green environment ------- 51 | module "target_group_server_green" { 52 | source = "./Modules/ALB" 53 | create_target_group = true 54 | name = "tg-${var.environment_name}-s-g" 55 | port = 80 56 | protocol = "HTTP" 57 | vpc = module.networking.aws_vpc 58 | tg_type = "ip" 59 | health_check_path = "/status" 60 | health_check_port = var.port_app_server 61 | } 62 | 63 | # ------- Creating Target Group for the client ALB blue environment ------- 64 | module "target_group_client_blue" { 65 | source = "./Modules/ALB" 66 | create_target_group = true 67 | name = "tg-${var.environment_name}-c-b" 68 | port = 80 69 | protocol = "HTTP" 70 | vpc = module.networking.aws_vpc 71 | tg_type = "ip" 72 | health_check_path = "/" 73 | health_check_port = var.port_app_client 74 | } 75 | 76 | # ------- Creating Target Group for the client ALB green environment ------- 77 | module "target_group_client_green" { 78 | source = "./Modules/ALB" 79 | create_target_group = true 80 | name = "tg-${var.environment_name}-c-g" 81 | port = 80 82 | protocol = "HTTP" 83 | vpc = module.networking.aws_vpc 84 | tg_type = "ip" 85 | health_check_path = "/" 86 | health_check_port = var.port_app_client 87 | } 88 | 89 | # ------- Creating Security Group for the server ALB ------- 90 | module "security_group_alb_server" { 91 | source = "./Modules/SecurityGroup" 92 | name = "alb-${var.environment_name}-server" 93 | description = "Controls access to the server ALB" 94 | vpc_id = module.networking.aws_vpc 95 | cidr_blocks_ingress = ["0.0.0.0/0"] 96 | ingress_port = 80 97 | } 98 | 99 | # ------- Creating Security Group for the client ALB ------- 100 | module "security_group_alb_client" { 101 | source = "./Modules/SecurityGroup" 102 | name = "alb-${var.environment_name}-client" 103 | description = "Controls access to the client ALB" 104 | vpc_id = module.networking.aws_vpc 105 | cidr_blocks_ingress = ["0.0.0.0/0"] 106 | ingress_port = 80 107 | } 108 | 109 | # ------- Creating Server Application ALB ------- 110 | module "alb_server" { 111 | source = "./Modules/ALB" 112 | create_alb = true 113 | name = "${var.environment_name}-ser" 114 | subnets = [module.networking.public_subnets[0], module.networking.public_subnets[1]] 115 | security_group = module.security_group_alb_server.sg_id 116 | target_group = module.target_group_server_blue.arn_tg 117 | } 118 | 119 | # ------- Creating Client Application ALB ------- 120 | module "alb_client" { 121 | source = "./Modules/ALB" 122 | create_alb = true 123 | name = "${var.environment_name}-cli" 124 | subnets = [module.networking.public_subnets[0], module.networking.public_subnets[1]] 125 | security_group = module.security_group_alb_client.sg_id 126 | target_group = module.target_group_client_blue.arn_tg 127 | } 128 | 129 | # ------- ECS Role ------- 130 | module "ecs_role" { 131 | source = "./Modules/IAM" 132 | create_ecs_role = true 133 | name = var.iam_role_name["ecs"] 134 | name_ecs_task_role = var.iam_role_name["ecs_task_role"] 135 | dynamodb_table = [module.dynamodb_table.dynamodb_table_arn] 136 | } 137 | 138 | # ------- Creating a IAM Policy for role ------- 139 | module "ecs_role_policy" { 140 | source = "./Modules/IAM" 141 | name = "ecs-ecr-${var.environment_name}" 142 | create_policy = true 143 | attach_to = module.ecs_role.name_role 144 | } 145 | 146 | # ------- Creating server ECR Repository to store Docker Images ------- 147 | module "ecr_server" { 148 | source = "./Modules/ECR" 149 | name = "repo-server" 150 | } 151 | 152 | # ------- Creating client ECR Repository to store Docker Images ------- 153 | module "ecr_client" { 154 | source = "./Modules/ECR" 155 | name = "repo-client" 156 | } 157 | 158 | # ------- Creating ECS Task Definition for the server ------- 159 | module "ecs_taks_definition_server" { 160 | source = "./Modules/ECS/TaskDefinition" 161 | name = "${var.environment_name}-server" 162 | container_name = var.container_name["server"] 163 | execution_role_arn = module.ecs_role.arn_role 164 | task_role_arn = module.ecs_role.arn_role_ecs_task_role 165 | cpu = 256 166 | memory = "512" 167 | docker_repo = module.ecr_server.ecr_repository_url 168 | region = var.aws_region 169 | container_port = var.port_app_server 170 | } 171 | 172 | # ------- Creating ECS Task Definition for the client ------- 173 | module "ecs_taks_definition_client" { 174 | source = "./Modules/ECS/TaskDefinition" 175 | name = "${var.environment_name}-client" 176 | container_name = var.container_name["client"] 177 | execution_role_arn = module.ecs_role.arn_role 178 | task_role_arn = module.ecs_role.arn_role_ecs_task_role 179 | cpu = 256 180 | memory = "512" 181 | docker_repo = module.ecr_client.ecr_repository_url 182 | region = var.aws_region 183 | container_port = var.port_app_client 184 | } 185 | 186 | # ------- Creating a server Security Group for ECS TASKS ------- 187 | module "security_group_ecs_task_server" { 188 | source = "./Modules/SecurityGroup" 189 | name = "ecs-task-${var.environment_name}-server" 190 | description = "Controls access to the server ECS task" 191 | vpc_id = module.networking.aws_vpc 192 | ingress_port = var.port_app_server 193 | security_groups = [module.security_group_alb_server.sg_id] 194 | } 195 | # ------- Creating a client Security Group for ECS TASKS ------- 196 | module "security_group_ecs_task_client" { 197 | source = "./Modules/SecurityGroup" 198 | name = "ecs-task-${var.environment_name}-client" 199 | description = "Controls access to the client ECS task" 200 | vpc_id = module.networking.aws_vpc 201 | ingress_port = var.port_app_client 202 | security_groups = [module.security_group_alb_client.sg_id] 203 | } 204 | 205 | # ------- Creating ECS Cluster ------- 206 | module "ecs_cluster" { 207 | source = "./Modules/ECS/Cluster" 208 | name = var.environment_name 209 | } 210 | 211 | # ------- Creating ECS Service server ------- 212 | module "ecs_service_server" { 213 | depends_on = [module.alb_server] 214 | source = "./Modules/ECS/Service" 215 | name = "${var.environment_name}-server" 216 | desired_tasks = 1 217 | arn_security_group = module.security_group_ecs_task_server.sg_id 218 | ecs_cluster_id = module.ecs_cluster.ecs_cluster_id 219 | arn_target_group = module.target_group_server_blue.arn_tg 220 | arn_task_definition = module.ecs_taks_definition_server.arn_task_definition 221 | subnets_id = [module.networking.private_subnets_server[0], module.networking.private_subnets_server[1]] 222 | container_port = var.port_app_server 223 | container_name = var.container_name["server"] 224 | } 225 | 226 | # ------- Creating ECS Service client ------- 227 | module "ecs_service_client" { 228 | depends_on = [module.alb_client] 229 | source = "./Modules/ECS/Service" 230 | name = "${var.environment_name}-client" 231 | desired_tasks = 1 232 | arn_security_group = module.security_group_ecs_task_client.sg_id 233 | ecs_cluster_id = module.ecs_cluster.ecs_cluster_id 234 | arn_target_group = module.target_group_client_blue.arn_tg 235 | arn_task_definition = module.ecs_taks_definition_client.arn_task_definition 236 | subnets_id = [module.networking.private_subnets_client[0], module.networking.private_subnets_client[1]] 237 | container_port = var.port_app_client 238 | container_name = var.container_name["client"] 239 | } 240 | 241 | # ------- Creating ECS Autoscaling policies for the server application ------- 242 | module "ecs_autoscaling_server" { 243 | depends_on = [module.ecs_service_server] 244 | source = "./Modules/ECS/Autoscaling" 245 | name = "${var.environment_name}-server" 246 | cluster_name = module.ecs_cluster.ecs_cluster_name 247 | min_capacity = 1 248 | max_capacity = 4 249 | } 250 | 251 | # ------- Creating ECS Autoscaling policies for the client application ------- 252 | module "ecs_autoscaling_client" { 253 | depends_on = [module.ecs_service_client] 254 | source = "./Modules/ECS/Autoscaling" 255 | name = "${var.environment_name}-client" 256 | cluster_name = module.ecs_cluster.ecs_cluster_name 257 | min_capacity = 1 258 | max_capacity = 4 259 | } 260 | 261 | # ------- CodePipeline ------- 262 | 263 | # ------- Creating Bucket to store CodePipeline artifacts ------- 264 | module "s3_codepipeline" { 265 | source = "./Modules/S3" 266 | bucket_name = "codepipeline-${var.aws_region}-${random_id.RANDOM_ID.hex}" 267 | } 268 | 269 | # ------- Creating IAM roles used during the pipeline excecution ------- 270 | module "devops_role" { 271 | source = "./Modules/IAM" 272 | create_devops_role = true 273 | name = var.iam_role_name["devops"] 274 | } 275 | 276 | module "codedeploy_role" { 277 | source = "./Modules/IAM" 278 | create_codedeploy_role = true 279 | name = var.iam_role_name["codedeploy"] 280 | } 281 | 282 | # ------- Creating an IAM Policy for role ------- 283 | module "policy_devops_role" { 284 | source = "./Modules/IAM" 285 | name = "devops-${var.environment_name}" 286 | create_policy = true 287 | attach_to = module.devops_role.name_role 288 | create_devops_policy = true 289 | ecr_repositories = [module.ecr_server.ecr_repository_arn, module.ecr_client.ecr_repository_arn] 290 | code_build_projects = [module.codebuild_client.project_arn, module.codebuild_server.project_arn] 291 | code_deploy_resources = [module.codedeploy_server.application_arn, module.codedeploy_server.deployment_group_arn, module.codedeploy_client.application_arn, module.codedeploy_client.deployment_group_arn] 292 | } 293 | 294 | # ------- Creating a SNS topic ------- 295 | module "sns" { 296 | source = "./Modules/SNS" 297 | sns_name = "sns-${var.environment_name}" 298 | } 299 | 300 | # ------- Creating the server CodeBuild project ------- 301 | module "codebuild_server" { 302 | source = "./Modules/CodeBuild" 303 | name = "codebuild-${var.environment_name}-server" 304 | iam_role = module.devops_role.arn_role 305 | region = var.aws_region 306 | account_id = data.aws_caller_identity.id_current_account.account_id 307 | ecr_repo_url = module.ecr_server.ecr_repository_url 308 | folder_path = var.folder_path_server 309 | buildspec_path = var.buildspec_path 310 | task_definition_family = module.ecs_taks_definition_server.task_definition_family 311 | container_name = var.container_name["server"] 312 | service_port = var.port_app_server 313 | ecs_role = var.iam_role_name["ecs"] 314 | ecs_task_role = var.iam_role_name["ecs_task_role"] 315 | dynamodb_table_name = module.dynamodb_table.dynamodb_table_name 316 | } 317 | 318 | # ------- Creating the client CodeBuild project ------- 319 | module "codebuild_client" { 320 | source = "./Modules/CodeBuild" 321 | name = "codebuild-${var.environment_name}-client" 322 | iam_role = module.devops_role.arn_role 323 | region = var.aws_region 324 | account_id = data.aws_caller_identity.id_current_account.account_id 325 | ecr_repo_url = module.ecr_client.ecr_repository_url 326 | folder_path = var.folder_path_client 327 | buildspec_path = var.buildspec_path 328 | task_definition_family = module.ecs_taks_definition_client.task_definition_family 329 | container_name = var.container_name["client"] 330 | service_port = var.port_app_client 331 | ecs_role = var.iam_role_name["ecs"] 332 | server_alb_url = module.alb_server.dns_alb 333 | } 334 | 335 | # ------- Creating the server CodeDeploy project ------- 336 | module "codedeploy_server" { 337 | source = "./Modules/CodeDeploy" 338 | name = "Deploy-${var.environment_name}-server" 339 | ecs_cluster = module.ecs_cluster.ecs_cluster_name 340 | ecs_service = module.ecs_service_server.ecs_service_name 341 | alb_listener = module.alb_server.arn_listener 342 | tg_blue = module.target_group_server_blue.tg_name 343 | tg_green = module.target_group_server_green.tg_name 344 | sns_topic_arn = module.sns.sns_arn 345 | codedeploy_role = module.codedeploy_role.arn_role_codedeploy 346 | } 347 | 348 | # ------- Creating the client CodeDeploy project ------- 349 | module "codedeploy_client" { 350 | source = "./Modules/CodeDeploy" 351 | name = "Deploy-${var.environment_name}-client" 352 | ecs_cluster = module.ecs_cluster.ecs_cluster_name 353 | ecs_service = module.ecs_service_client.ecs_service_name 354 | alb_listener = module.alb_client.arn_listener 355 | tg_blue = module.target_group_client_blue.tg_name 356 | tg_green = module.target_group_client_green.tg_name 357 | sns_topic_arn = module.sns.sns_arn 358 | codedeploy_role = module.codedeploy_role.arn_role_codedeploy 359 | } 360 | 361 | # ------- Creating CodePipeline ------- 362 | module "codepipeline" { 363 | source = "./Modules/CodePipeline" 364 | name = "pipeline-${var.environment_name}" 365 | pipe_role = module.devops_role.arn_role 366 | s3_bucket = module.s3_codepipeline.s3_bucket_id 367 | github_token = var.github_token 368 | repo_owner = var.repository_owner 369 | repo_name = var.repository_name 370 | branch = var.repository_branch 371 | codebuild_project_server = module.codebuild_server.project_id 372 | codebuild_project_client = module.codebuild_client.project_id 373 | app_name_server = module.codedeploy_server.application_name 374 | app_name_client = module.codedeploy_client.application_name 375 | deployment_group_server = module.codedeploy_server.deployment_group_name 376 | deployment_group_client = module.codedeploy_client.deployment_group_name 377 | 378 | depends_on = [module.policy_devops_role] 379 | } 380 | 381 | # ------- Creating Bucket to store assets accessed by the Back-end ------- 382 | module "s3_assets" { 383 | source = "./Modules/S3" 384 | bucket_name = "assets-${var.aws_region}-${random_id.RANDOM_ID.hex}" 385 | } 386 | 387 | # ------- Creating Dynamodb table by the Back-end ------- 388 | module "dynamodb_table" { 389 | source = "./Modules/Dynamodb" 390 | name = "assets-table-${var.environment_name}" 391 | } -------------------------------------------------------------------------------- /Infrastructure/outputs.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | output "application_url" { 5 | value = module.alb_client.dns_alb 6 | description = "Copy this value in your browser in order to access the deployed app" 7 | } 8 | 9 | output "swagger_endpoint" { 10 | value = "${module.alb_server.dns_alb}/api/docs" 11 | description = "Copy this value in your browser in order to access the swagger documentation" 12 | } -------------------------------------------------------------------------------- /Infrastructure/variables.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | variable "aws_profile" { 5 | description = "The profile name that you have configured in the file .aws/credentials" 6 | type = string 7 | } 8 | 9 | variable "aws_region" { 10 | description = "The AWS Region in which you want to deploy the resources" 11 | type = string 12 | } 13 | 14 | variable "environment_name" { 15 | description = "The name of your environment" 16 | type = string 17 | 18 | validation { 19 | condition = length(var.environment_name) < 23 20 | error_message = "Due the this variable is used for concatenation of names of other resources, the value must have less than 23 characters." 21 | } 22 | } 23 | 24 | variable "github_token" { 25 | description = "Personal access token from Github" 26 | type = string 27 | sensitive = true 28 | } 29 | 30 | variable "port_app_server" { 31 | description = "The port used by your server application" 32 | type = number 33 | default = 3001 34 | } 35 | 36 | variable "port_app_client" { 37 | description = "The port used by your client application" 38 | type = number 39 | default = 80 40 | } 41 | 42 | variable "buildspec_path" { 43 | description = "The location of the buildspec file" 44 | type = string 45 | default = "./Infrastructure/Templates/buildspec.yml" 46 | } 47 | 48 | variable "folder_path_server" { 49 | description = "The location of the server files" 50 | type = string 51 | default = "./Code/server/." 52 | } 53 | 54 | variable "folder_path_client" { 55 | description = "The location of the client files" 56 | type = string 57 | default = "./Code/client/." 58 | } 59 | 60 | variable "container_name" { 61 | description = "The name of the container of each ECS service" 62 | type = map(string) 63 | default = { 64 | server = "Container-server" 65 | client = "Container-client" 66 | } 67 | } 68 | 69 | variable "iam_role_name" { 70 | description = "The name of the IAM Role for each service" 71 | type = map(string) 72 | default = { 73 | devops = "DevOps-Role" 74 | ecs = "ECS-task-excecution-Role" 75 | ecs_task_role = "ECS-task-Role" 76 | codedeploy = "CodeDeploy-Role" 77 | } 78 | } 79 | 80 | variable "repository_owner" { 81 | description = "The name of the owner of the Github repository" 82 | type = string 83 | } 84 | 85 | variable "repository_name" { 86 | description = "The name of the Github repository" 87 | type = string 88 | } 89 | 90 | variable "repository_branch" { 91 | description = "The name of branch the Github repository, which is going to trigger a new CodePipeline excecution" 92 | type = string 93 | default = "main" 94 | } -------------------------------------------------------------------------------- /Infrastructure/versions.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | terraform { 5 | required_version = ">= 0.13" 6 | required_providers { 7 | aws = { 8 | source = "hashicorp/aws" 9 | version = "~> 3.38" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amazon ECS Demo with fullstack app / DevOps practices / Terraform sample 2 | 3 | ## Table of content 4 | 5 | * [Solution overview](#solution-overview) 6 | * [General information](#general-information) 7 | * [Infrastructure](#infrastructure) 8 | * [Infrastructure Architecture](#infrastructure-architecture) 9 | * [Infrastructure considerations due to demo proposals](#infrastructure-considerations-due-to-demo-proposals) 10 | * [CI/CD Architecture](#ci/cd-architecture) 11 | * [Prerequisites](#prerequisites) 12 | * [Usage](#usage) 13 | * [Autoscaling test](#autoscaling-test) 14 | * [Application Code](#application-code) 15 | * [Client app](#client-app) 16 | * [Client considerations due to demo proposal](#client-considerations-due-to-demo-proposals) 17 | * [Server app](#server-app) 18 | * [Cleanup](#cleanup) 19 | * [Security](#security) 20 | * [License](#license) 21 | 22 | 23 | ## Solution overview 24 | 25 | This repository contains Terraform code to deploy a solution that is intended to be used to run a demo. It shows how AWS resources can be used to build an architecture that reduces defects while deploying, eases remediation, mitigates deployment risks and improves the flow into production environments while gaining the advantages of a managed underlying infrastructure for containers. 26 | 27 | ## General information 28 | 29 | The project has been divided into two parts: 30 | - Code: the code for the running application 31 | - client: Vue.js code for the frontend application 32 | - server: Node.js code for the backend application 33 | - Infrastructure: contains the Terraform code to deploy the needed AWS resources for the solution 34 | 35 | ## Infrastructure 36 | 37 | The Infrastructure folder contains the terraform code to deploy the AWS resources. The *Modules* folder has been created to store the Terraform modules used in this project. The *Templates* folder contains the different configuration files needed within the modules. The Terraform state is stored locally in the machine where you execute the terraform commands, but feel free to set a Terraform backend configuration like an AWS S3 Bucket or Terraform Cloud to store the state remotely. The AWS resources created by the script are detailed bellow: 38 | 39 | - AWS Networking resources, following best practices for HA 40 | - 2 ECR Repositories 41 | - 1 ECS Cluster 42 | - 2 ECS Services 43 | - 2 Task definitions 44 | - 4 Autoscaling Policies + Cloudwatch Alarms 45 | - 2 Application Load Balancer (Public facing) 46 | - IAM Roles and policies for ECS Tasks, CodeBuild, CodeDeploy and CodePipeline 47 | - Security Groups for ALBs and ECS tasks 48 | - 2 CodeBuild Projects 49 | - 2 CodeDeploy Applications 50 | - 1 CodePipeline pipeline 51 | - 2 S3 Buckets (1 used by CodePipeline to store the artifacts and another one used to store assets accessible from within the application) 52 | - 1 DynamoDB table (used by the application) 53 | - 1 SNS topic for notifications 54 | 55 | ## Infrastructure Architecture 56 | 57 | The following diagram represents the Infrastructure architecture being deployed with this project: 58 | 59 |

60 | 61 |

62 | 63 | ### Infrastructure considerations due to demo proposals 64 | The task definition template (Infrastructure/Templates/taskdef.json) that enables the CodePipeline to execute a Blue/Green deployment in ECS has hardcoded values for the memory and CPU values for the server and client application. 65 | 66 | Feel free to change it, by adding for example a set of "sed" commands in CodeBuild (following the ones already provided as example) to replace the values dynamically. 67 | 68 | Feel free to create a subscriptor for the SNS topic created by this code, in order to get informed of the status of each finished CodeDeploy deployment. 69 | 70 | ## CI/CD Architecture 71 | 72 | The following diagram represents the CI/CD architecture being deployed with this project: 73 | 74 |

75 | 76 |

77 | 78 | ## Prerequisites 79 | There are general steps that you must follow in order to launch the infrastructure resources. 80 | 81 | Before launching the solution please follow the next steps: 82 | 83 | 1) Install Terraform, use Terraform v0.13 or above. You can visit [this](https://releases.hashicorp.com/terraform/) Terraform official webpage to download it. 84 | 2) Configure the AWS credentials into your machine (~/.aws/credentials). You need to use the following format: 85 | 86 | ```shell 87 | [AWS_PROFILE_NAME] 88 | aws_access_key_id = Replace_with_the_correct_access_Key 89 | aws_secret_access_key = Replace_with_the_correct_secret_Key 90 | ``` 91 | 92 | 3) Generate a GitHub token. You can follow [this](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) steps to generate it. 93 | 94 | ## Usage 95 | 96 | **1.** Fork this repository and create the GitHub token granting access to this new repository in your account. 97 | 98 | **2.** Clone that recently forked repository from your account (not the one from the aws-sample organization) and change the directory to the appropriate one as shown below: 99 | 100 | ```bash 101 | cd Infrastructure/ 102 | ``` 103 | 104 | **3.** Run Terraform init to download the providers and install the modules 105 | 106 | ```shell 107 | terraform init 108 | ``` 109 | **4.** Run the terraform plan command, feel free to use a tfvars file to specify the variables. 110 | You need to set at least the following variables: 111 | + **aws_profile** = according to the profiles name in ~/.aws/credentials 112 | + **aws_region** = the AWS region in which you want to create the resources 113 | + **environment_name** = a unique name used for concatenation to give place to the resources names 114 | + **github_token** = your GitHub token, the one generated a few steps above 115 | + **repository_name** = your GitHub repository name 116 | + **repository_owner** = the owner of the GitHub repository used 117 | 118 | ```shell 119 | terraform plan -var aws_profile="your-profile" -var aws_region="your-region" -var environment_name="your-env" -var github_token="your-personal-token" -var repository_name="your-github-repository" -var repository_owner="the-github-repository-owner" 120 | ``` 121 | 122 | Example of the previous command with replaced dummy values: 123 | 124 | ```shell 125 | terraform plan -var aws_profile="development" -var aws_region="eu-central-1" -var environment_name="developmentenv" -var github_token="your-personal-token" -var repository_name="your-github-repository" -var repository_owner="the-github-repository-owner" 126 | ``` 127 | 128 | **5.** Review the terraform plan, take a look at the changes that terraform will execute: 129 | 130 | ```shell 131 | terraform apply -var aws_profile="your-profile" -var aws_region="your-region" -var environment_name="your-env" -var github_token="your-personal-token" -var repository_name="your-github-repository" -var repository_owner="the-github-repository-owner" 132 | ``` 133 | 134 | **6.** Once Terraform finishes the deployment, open the AWS Management Console and go to the AWS CodePipeline service. You will see that the pipeline, which was created by this Terraform code, is in progress. Add some files and DynamoDB items as mentioned [here](#client-considerations-due-to-demo-proposals). Once the pipeline finished successfully and the before assets were added, go back to the console where Terraform was executed, copy the *application_url* value from the output and open it in a browser. 135 | 136 | **7.** In order to access the also implemented Swagger endpoint, copy the *swagger_endpoint* value from the Terraform output and open it in a browser. 137 | 138 | ## Autoscaling test 139 | 140 | To test how your application will perform under a peak of traffic, a stress test configuration file is provided. 141 | 142 | For this stress test [Artillery](https://artillery.io/) is being used. Please be sure to install it following [these](https://artillery.io/docs/guides/getting-started/installing-artillery.html) steps. 143 | 144 | Once installed, please change the ALB DNS to the desired layer to test (front/backend) in the **target** attribute, which you can copy from the generated Terraform output, or you can also search it in the AWS Management Console. 145 | 146 | To execute it, run the following commands: 147 | 148 | *Frontend layer:* 149 | ```bash 150 | artillery run Code/client/src/tests/stresstests/stress_client.yml 151 | ``` 152 | 153 | *Backend layer:* 154 | ```bash 155 | artillery run Code/server/src/tests/stresstests/stress_server.yml 156 | ``` 157 | 158 | To learn more about Amazon ECS Autoscaling, please take a look to [this](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-auto-scaling.html) documentation. 159 | ## Application Code 160 | 161 | ### Client app 162 | 163 | The Client folder contains the code to run the frontend. This code is written in Vue.js and uses the port 80 in the deployed version, but when run localy it uses port 3000. 164 | 165 | The application folder structure is separeted in components, views and services, despite the router and the assets. 166 | 167 | ### Client considerations due to demo proposals 168 | 1) The assets used by the client application are going to be requested from the S3 bucket created with this code. Please add 3 images to the created S3 bucket. 169 | 170 | 2) The DynamoDB structure used by the client application is the following one: 171 | 172 | ```shell 173 | - id: N (HASH) 174 | - path: S 175 | - title: S 176 | ``` 177 | Feel free to change the structure as needed. But in order to have full demo experience, please add 3 DynamoDB Items with the specified structure from above. Below is an example. 178 | 179 | *Note: The path attribute correspondes to the S3 Object URL of each added asset from the previous step.* 180 | 181 | Example of a DynamoDB Item: 182 | 183 | ```json 184 | { 185 | "id": { 186 | "N": "1" 187 | }, 188 | "path": { 189 | "S": "https://mybucket.s3.eu-central-1.amazonaws.com/MyImage.jpeg" 190 | }, 191 | "title": { 192 | "S": "My title" 193 | } 194 | } 195 | ``` 196 | 197 | ### Server app 198 | 199 | The Server folder contains the code to run the backend. This code is written in Node.js and uses the port 80 in the deployed version, but when run localy it uses port 3001. 200 | 201 | Swagger was also implemented in order to document the APIs. The Swagger endpoint is provided as part of the Terraform output, you can grab the output link and access it through a browser. 202 | 203 | The server exposes 3 endpoints: 204 | - /status: serves as a dummy endpoint to know if the server is up and running. This one is used as the health check endpoint by the AWS ECS resources 205 | - /api/getAllProducts: main endpoint, which returns all the Items from an AWS DynamoDB table 206 | - /api/docs: the Swagger endpoint for the API documentation 207 | 208 | ## Cleanup 209 | 210 | Run the following command if you want to delete all the resources created before: 211 | 212 | ```shell 213 | terraform destroy -var aws_profile="your-profile" -var AWS_REGION="your-region" -var environment_name="your-env" -var github_token="your-personal-token" -var repository_name="your-github-repository" - var repository_owner="the-github-repository-owner" 214 | ``` 215 | 216 | ## Security 217 | 218 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 219 | 220 | ## License 221 | This library is licensed under the MIT-0 License. See the [LICENSE](LICENSE) file. --------------------------------------------------------------------------------