├── app ├── backend2 │ ├── project │ │ ├── api │ │ │ ├── __init__.py │ │ │ ├── migrations │ │ │ │ └── __init__.py │ │ │ ├── urls.py │ │ │ ├── apps.py │ │ │ └── views.py │ │ ├── .gitignore │ │ ├── project │ │ │ ├── __init__.py │ │ │ ├── asgi.py │ │ │ ├── wsgi.py │ │ │ ├── utils.py │ │ │ ├── urls.py │ │ │ └── settings.py │ │ └── manage.py │ ├── requirements.txt │ └── Dockerfile ├── backend1 │ ├── requirements.txt │ ├── Dockerfile │ └── app.py ├── frontend │ ├── Dockerfile │ ├── index.html │ └── default.conf └── bff │ ├── Dockerfile │ ├── package.json │ └── server.js ├── docs └── images │ ├── architecture.drawio.png │ ├── aws-xray-console-example.png │ ├── frontend-screen-example.png │ └── aws-xray-distributed-tracing-amazon-ecs.png ├── CODE_OF_CONDUCT.md ├── envs └── default.conf ├── LICENSE ├── templates ├── 2-svc-frontend.yml ├── 2-svc-bff.yml ├── 2-svc-backend1.yml ├── 2-svc-backend2.yml ├── 0-vpc.yml └── 1-svc-base.yml ├── scripts ├── get_url.sh ├── uninstall.sh └── install.sh ├── CONTRIBUTING.md ├── .gitignore └── README.md /app/backend2/project/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/backend2/project/.gitignore: -------------------------------------------------------------------------------- 1 | config.ini -------------------------------------------------------------------------------- /app/backend2/project/project/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/backend2/project/api/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/backend1/requirements.txt: -------------------------------------------------------------------------------- 1 | aws-xray-sdk==2.9.0 2 | boto3==1.23.10 3 | Flask==2.3.2 4 | Flask-Cors==3.0.10 5 | -------------------------------------------------------------------------------- /app/backend2/requirements.txt: -------------------------------------------------------------------------------- 1 | aws-xray-sdk==2.9.0 2 | boto3==1.23.10 3 | Django==4.1.13 4 | djangorestframework==3.13.1 5 | gunicorn==20.1.0 6 | -------------------------------------------------------------------------------- /docs/images/architecture.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/example-for-aws-xray-in-amazon-ecs/HEAD/docs/images/architecture.drawio.png -------------------------------------------------------------------------------- /docs/images/aws-xray-console-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/example-for-aws-xray-in-amazon-ecs/HEAD/docs/images/aws-xray-console-example.png -------------------------------------------------------------------------------- /docs/images/frontend-screen-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/example-for-aws-xray-in-amazon-ecs/HEAD/docs/images/frontend-screen-example.png -------------------------------------------------------------------------------- /app/backend2/project/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | path('health', views.health_check), 6 | path('', views.main), 7 | ] 8 | -------------------------------------------------------------------------------- /docs/images/aws-xray-distributed-tracing-amazon-ecs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/example-for-aws-xray-in-amazon-ecs/HEAD/docs/images/aws-xray-distributed-tracing-amazon-ecs.png -------------------------------------------------------------------------------- /app/backend2/project/api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'api' 7 | -------------------------------------------------------------------------------- /app/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.21.6 2 | 3 | RUN apt update 4 | 5 | COPY default.conf /etc/nginx/conf.d/default.conf 6 | COPY index.html /usr/share/nginx/html/index.html 7 | 8 | EXPOSE 80 9 | -------------------------------------------------------------------------------- /app/bff/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.15.0-buster-slim 2 | 3 | RUN apt update 4 | 5 | WORKDIR /usr/src/app 6 | 7 | COPY package.json . 8 | RUN npm install 9 | 10 | COPY server.js . 11 | 12 | CMD [ "node", "server.js" ] 13 | EXPOSE 80 -------------------------------------------------------------------------------- /app/backend1/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9.13-slim-buster 2 | 3 | RUN apt update && apt install -y curl 4 | 5 | WORKDIR /usr/src/app 6 | 7 | COPY requirements.txt . 8 | RUN pip install --no-cache-dir -r requirements.txt 9 | 10 | COPY app.py . 11 | 12 | CMD ["python", "app.py"] 13 | EXPOSE 80 -------------------------------------------------------------------------------- /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. 5 | -------------------------------------------------------------------------------- /app/backend2/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9.13-slim-buster 2 | 3 | RUN apt update && apt install -y curl 4 | 5 | WORKDIR /usr/src/app 6 | 7 | ENV PYTHONDONTWRITEBYTECODE 1 8 | ENV PYTHONUNBUFFERED 1 9 | 10 | COPY ./requirements.txt . 11 | RUN pip install --no-cache-dir -r requirements.txt 12 | 13 | COPY project . 14 | 15 | CMD ["python", "manage.py", "runserver", "0.0.0.0:80"] 16 | EXPOSE 80 -------------------------------------------------------------------------------- /app/bff/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bff", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "aws-sdk": "^2.1145.0", 14 | "aws-xray-sdk": "^3.3.5", 15 | "axios": "^0.27.2", 16 | "express": "^4.18.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/backend2/project/project/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for project project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /app/backend2/project/project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /envs/default.conf: -------------------------------------------------------------------------------- 1 | export APP_ID=xray-ecs # 8 characters or less with numbers, lowercase letters and hyphens 2 | export ENV_ID=dev # 4 characters or less with numbers, lowercase letters and hyphens 3 | export AWS_PROFILE=default 4 | export AWS_REGION=ap-northeast-1 5 | export VPC_CIDR=10.192.0.0/16 6 | export PUBLIC_SUBNET_1_CIDR=10.192.10.0/24 7 | export PUBLIC_SUBNET_2_CIDR=10.192.11.0/24 8 | export PRIVATE_SUBNET_1_CIDR=10.192.20.0/24 9 | export PRIVATE_SUBNET_2_CIDR=10.192.21.0/24 10 | -------------------------------------------------------------------------------- /app/backend2/project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /app/backend2/project/project/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | from configparser import ConfigParser 3 | from django.utils.crypto import get_random_string 4 | 5 | 6 | def load_secret_key_from_config(config_filepath): 7 | config = None 8 | 9 | if not os.path.exists(config_filepath): 10 | config = ConfigParser() 11 | config.add_section('django') 12 | config['django']['secret_key'] = get_random_string(64) 13 | with open(config_filepath, 'w') as config_file: 14 | config.write(config_file) 15 | 16 | if not config: 17 | config = ConfigParser() 18 | config.read_file(open(config_filepath)) 19 | 20 | if not config.has_section('django') or not config.get('django', 'secret_key', fallback=None): 21 | raise KeyError('`django.secret_key` is missing in the config file.') 22 | 23 | return config['django']['secret_key'] 24 | -------------------------------------------------------------------------------- /app/backend2/project/project/urls.py: -------------------------------------------------------------------------------- 1 | """project URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/4.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | path('', include('api.urls')), 22 | ] 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 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. 15 | 16 | -------------------------------------------------------------------------------- /app/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |Current Date (from Backend #1) loading...
30 |Current Time (from Backend #2) loading...
31 | 32 | 33 | -------------------------------------------------------------------------------- /app/frontend/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | 5 | #access_log /var/log/nginx/host.access.log main; 6 | 7 | location / { 8 | root /usr/share/nginx/html; 9 | index index.html index.htm; 10 | } 11 | 12 | location /health { 13 | access_log off; 14 | default_type application/json; 15 | return 200 '{"status": "ok"}'; 16 | } 17 | 18 | #error_page 404 /404.html; 19 | 20 | # redirect server error pages to the static page /50x.html 21 | # 22 | error_page 500 502 503 504 /50x.html; 23 | location = /50x.html { 24 | root /usr/share/nginx/html; 25 | } 26 | 27 | # proxy the PHP scripts to Apache listening on 127.0.0.1:80 28 | # 29 | #location ~ \.php$ { 30 | # proxy_pass http://127.0.0.1; 31 | #} 32 | 33 | # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 34 | # 35 | #location ~ \.php$ { 36 | # root html; 37 | # fastcgi_pass 127.0.0.1:9000; 38 | # fastcgi_index index.php; 39 | # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; 40 | # include fastcgi_params; 41 | #} 42 | 43 | # deny access to .htaccess files, if Apache's document root 44 | # concurs with nginx's one 45 | # 46 | #location ~ /\.ht { 47 | # deny all; 48 | #} 49 | } 50 | -------------------------------------------------------------------------------- /app/backend2/project/api/views.py: -------------------------------------------------------------------------------- 1 | from django.http import JsonResponse 2 | from rest_framework.decorators import api_view 3 | import time, datetime, os, math, logging 4 | import boto3 5 | 6 | logger = logging.getLogger() 7 | logger.setLevel(logging.INFO) 8 | 9 | #------------------------------------------------------------# 10 | # Apply patches to Python libraries for tracing downstream HTTP requests 11 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-patching.html 12 | #------------------------------------------------------------# 13 | from aws_xray_sdk.core import patch_all 14 | patch_all() 15 | #------------------------------------------------------------# 16 | 17 | 18 | @api_view(["GET"]) 19 | def health_check(request): 20 | content = { 21 | "status": "ok" 22 | } 23 | return JsonResponse(content) 24 | 25 | 26 | @api_view(["GET"]) 27 | def main(request): 28 | # Write to DynamoDB table 29 | dyname_db_table_name = os.environ.get('DYNAMO_DB_TABLE_NAME', '') 30 | region_name = os.environ.get('AWS_DEFAULT_REGION', '') 31 | try: 32 | dynamodb = boto3.resource('dynamodb', region_name=region_name) 33 | table = dynamodb.Table(dyname_db_table_name) 34 | table.put_item(Item={ 35 | "SubAppId": "backend2", 36 | "LastAccessed": str(math.floor(time.time())) 37 | }) 38 | except Exception as e: 39 | logger.exception(e) 40 | 41 | # Return current time 42 | current_datetime = datetime.datetime.fromtimestamp(time.time()).astimezone(datetime.timezone(datetime.timedelta(hours=9))) 43 | content = { 44 | "currentTime": current_datetime.strftime('%H:%M:%S') 45 | } 46 | return JsonResponse(content) 47 | -------------------------------------------------------------------------------- /app/backend1/app.py: -------------------------------------------------------------------------------- 1 | from flask import * 2 | from flask_cors import CORS 3 | import time, datetime, os, math, logging 4 | import boto3 5 | 6 | logger = logging.getLogger() 7 | logger.setLevel(logging.INFO) 8 | 9 | #------------------------------------------------------------# 10 | # Add X-Ray SDK for Python with the middleware (Flask) 11 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-middleware.html#xray-sdk-python-adding-middleware-flask 12 | #------------------------------------------------------------# 13 | from aws_xray_sdk.core import xray_recorder 14 | from aws_xray_sdk.ext.flask.middleware import XRayMiddleware 15 | #------------------------------------------------------------# 16 | 17 | #------------------------------------------------------------# 18 | # Apply patches to Python libraries for tracing downstream HTTP requests 19 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-patching.html 20 | #------------------------------------------------------------# 21 | from aws_xray_sdk.core import patch_all 22 | patch_all() 23 | #------------------------------------------------------------# 24 | 25 | app = Flask(__name__) 26 | CORS(app) 27 | 28 | #------------------------------------------------------------# 29 | # Setup X-Ray SDK and apply patch to Flask application 30 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-middleware.html#xray-sdk-python-adding-middleware-flask 31 | #------------------------------------------------------------# 32 | xray_recorder.configure(service='Backend #1') 33 | XRayMiddleware(app, xray_recorder) 34 | #------------------------------------------------------------# 35 | 36 | 37 | @app.route('/health', methods=['GET']) 38 | def health_check(): 39 | return jsonify({ 40 | "status": "ok" 41 | }) 42 | 43 | 44 | @app.route("/", methods=["GET"]) 45 | def main(): 46 | # Write to DynamoDB table 47 | dyname_db_table_name = os.environ.get('DYNAMO_DB_TABLE_NAME', '') 48 | region_name = os.environ.get('AWS_DEFAULT_REGION', '') 49 | try: 50 | dynamodb = boto3.resource('dynamodb', region_name=region_name) 51 | table = dynamodb.Table(dyname_db_table_name) 52 | table.put_item(Item={ 53 | "SubAppId": "backend1", 54 | "LastAccessed": str(math.floor(time.time())) 55 | }) 56 | except Exception as e: 57 | logger.exception(e) 58 | 59 | # Return current date 60 | current_datetime = datetime.datetime.fromtimestamp(time.time()).astimezone(datetime.timezone(datetime.timedelta(hours=9))) 61 | return jsonify({ 62 | "currentDate": current_datetime.strftime('%Y/%m/%d') 63 | }) 64 | 65 | 66 | if __name__ == '__main__': 67 | app.run(host="0.0.0.0", port=80) 68 | -------------------------------------------------------------------------------- /app/bff/server.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------ 2 | // Add X-Ray SDK for Node.js with the middleware (Express) 3 | // - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-middleware.html#xray-sdk-nodejs-middleware-express 4 | //------------------------------------------------------------ 5 | const AWSXRay = require('aws-xray-sdk'); 6 | //------------------------------------------------------------ 7 | 8 | const express = require('express'); 9 | 10 | //------------------------------------------------------------ 11 | // Apply patches to Node.js libraries for tracing downstream HTTP requests 12 | // - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-httpclients.html 13 | // - https://github.com/aws-samples/aws-xray-sdk-node-sample/blob/master/index.js 14 | //------------------------------------------------------------ 15 | // HTTP Client 16 | AWSXRay.captureHTTPsGlobal(require('http')); 17 | AWSXRay.capturePromise(); 18 | const axios = require('axios'); 19 | 20 | // // AWS SDK (not used in this sample) 21 | // const AWS = AWSXRay.captureAWS(require('aws-sdk')); 22 | //------------------------------------------------------------ 23 | 24 | const app = express(); 25 | 26 | //------------------------------------------------------------ 27 | // Open segment for X-Ray with the middleware (Express) 28 | // - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-middleware.html#xray-sdk-nodejs-middleware-express 29 | //------------------------------------------------------------ 30 | app.use(AWSXRay.express.openSegment('BFF')); 31 | //------------------------------------------------------------ 32 | 33 | app.get('/api', (req, res) => { 34 | // Set variables 35 | const backend1Url = process.env.BACKEND_1_URL || ''; 36 | const backend2Url = process.env.BACKEND_2_URL || ''; 37 | 38 | // Call Backend #1 39 | axios.get(backend1Url).then(result1 => { 40 | 41 | // Call Backend #2 42 | axios.get(backend2Url).then(result2 => { 43 | res.json({ 44 | 'currentDate': result1.data.currentDate, 45 | 'currentTime': result2.data.currentTime, 46 | }); 47 | }) 48 | .catch(err => { 49 | console.log(err); 50 | }); 51 | 52 | }) 53 | .catch(err => { 54 | console.log(err); 55 | }); 56 | }); 57 | 58 | app.get('/health', (req, res) => { 59 | res.json({ 60 | 'status': 'ok' 61 | }); 62 | }); 63 | 64 | //------------------------------------------------------------ 65 | // Close segment for X-Ray with the middleware (Express) 66 | // - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-middleware.html#xray-sdk-nodejs-middleware-express 67 | //------------------------------------------------------------ 68 | app.use(AWSXRay.express.closeSegment()); 69 | //------------------------------------------------------------ 70 | 71 | app.listen('80', () => { 72 | console.log(`Running on http://0.0.0.0:80`) 73 | }); -------------------------------------------------------------------------------- /templates/2-svc-frontend.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Parameters: 4 | AppId: 5 | Description: An application ID that is prefixed to resource names 6 | Type: String 7 | 8 | EnvId: 9 | Description: An environment ID that is prefixed to resource names 10 | Type: String 11 | 12 | ImageTag: 13 | Description: An image tag that is used in ECS task definition 14 | Type: String 15 | 16 | Resources: 17 | #----------------------------------------------------------# 18 | # Frontend 19 | #----------------------------------------------------------# 20 | FrontendEcsService: 21 | Type: AWS::ECS::Service 22 | Properties: 23 | ServiceName: !Sub "${AppId}-${EnvId}-frontend-ecs-service" 24 | Cluster: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-cluster-id"} 25 | LaunchType: FARGATE 26 | DesiredCount: 1 27 | EnableExecuteCommand: true 28 | DeploymentController: 29 | Type: ECS 30 | NetworkConfiguration: 31 | AwsvpcConfiguration: 32 | AssignPublicIp: DISABLED 33 | Subnets: 34 | - {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-private-subnet-1"} 35 | - {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-private-subnet-2"} 36 | SecurityGroups: 37 | - {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-frontend-layer-ecs-sg-id"} 38 | TaskDefinition: !Ref FrontendEcsTaskDefinition 39 | LoadBalancers: 40 | - TargetGroupArn: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-frontend-alb-tg-arn"} 41 | ContainerPort: 80 42 | ContainerName: "frontend-app" 43 | 44 | FrontendEcsTaskDefinition: 45 | Type: AWS::ECS::TaskDefinition 46 | Properties: 47 | ContainerDefinitions: 48 | - Name: "frontend-app" 49 | Image: !Sub 50 | - "${EcrRepositoryUri}:${ImageTag}" 51 | - EcrRepositoryUri: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-frontend-ecr-repository-uri"} 52 | ImageTag: !Ref ImageTag 53 | LinuxParameters: 54 | InitProcessEnabled: True 55 | PortMappings: 56 | - ContainerPort: 80 57 | Protocol: tcp 58 | LogConfiguration: 59 | LogDriver: awslogs 60 | Options: 61 | awslogs-create-group: True 62 | awslogs-group: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-log-group-name"} 63 | awslogs-region: !Ref AWS::Region 64 | awslogs-stream-prefix: frontend 65 | Family: !Sub "${AppId}-${EnvId}-frontend" 66 | Cpu: 1024 67 | Memory: 2048 68 | NetworkMode: awsvpc 69 | RequiresCompatibilities: 70 | - FARGATE 71 | ExecutionRoleArn: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-task-execution-role-arn"} 72 | TaskRoleArn: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-task-role-arn"} 73 | #----------------------------------------------------------# 74 | -------------------------------------------------------------------------------- /scripts/get_url.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #------------------------------------------------------------# 4 | # Init 5 | #------------------------------------------------------------# 6 | set -e 7 | trap fail ERR 8 | 9 | SCRIPT_NAME=`basename ${0}` 10 | 11 | start() { 12 | echo "" 13 | echo -e "\033[0;33mScript [${SCRIPT_NAME}] started\033[0m" # print it in yellow 14 | echo "" 15 | } 16 | 17 | success() { 18 | echo "" 19 | echo -e "\033[0;32mScript [${SCRIPT_NAME}] completed\033[0m" # print it in green 20 | echo "" 21 | exit 0 22 | } 23 | 24 | fail() { 25 | echo "" 26 | echo -e "\033[0;31mScript [${SCRIPT_NAME}] failed\033[0m" # print it in red 27 | echo "" 28 | exit 1 29 | } 30 | 31 | check_option_value() { 32 | if [ -z "$2" -o "${2:0:2}" == "--" ]; then 33 | echo "" 34 | echo "Option $1 was passed an invalid value: $2. Perhaps you passed in an empty env var?" 35 | fail 36 | fi 37 | } 38 | 39 | usage() { 40 | echo "Usage: $(basename "$0") [-h] [--config CONFIG_FILE_PATH]" 41 | echo "" 42 | echo "Options: 43 | -h, --help Display this help message. 44 | --config CONFIG_FILE_PATH Set a config file path explicitly other than default." 45 | } 46 | 47 | CONFIG_FILE_PATH="" 48 | while :; do 49 | case "$1" in 50 | -h | --help) 51 | usage 52 | exit 0 53 | ;; 54 | --config) 55 | check_option_value "$1" "$2" 56 | CONFIG_FILE_PATH="$2" 57 | shift 2 58 | ;; 59 | *) 60 | [ -z "$1" ] && break 61 | echo "" 62 | echo "Invalid option: [$1]." 63 | fail 64 | ;; 65 | esac 66 | done 67 | 68 | if [ -z "${CONFIG_FILE_PATH}" ]; then 69 | CONFIG_FILE_PATH="$(dirname $0)/../envs/default.conf" 70 | fi 71 | 72 | if [ ! -e "${CONFIG_FILE_PATH}" ]; then 73 | echo "" 74 | echo "Config file does not exist on [${CONFIG_FILE_PATH}]." 75 | fail 76 | fi 77 | 78 | start 79 | #------------------------------------------------------------# 80 | 81 | 82 | #------------------------------------------------------------# 83 | # Load Variables 84 | #------------------------------------------------------------# 85 | source ${CONFIG_FILE_PATH} 86 | echo "-------------------------------------" 87 | echo "APP_ID=${APP_ID}" 88 | echo "ENV_ID=${ENV_ID}" 89 | echo "AWS_PROFILE=${AWS_PROFILE}" 90 | echo "AWS_REGION=${AWS_REGION}" 91 | echo "-------------------------------------" 92 | #------------------------------------------------------------# 93 | 94 | 95 | #------------------------------------------------------------# 96 | # Get Access URL 97 | #------------------------------------------------------------# 98 | URL=http://$(aws cloudformation list-exports --region ${AWS_REGION} --query 'Exports[?Name==`'${APP_ID}-${ENV_ID}-alb-dns-name'`].Value' --output text) 99 | echo "" 100 | echo -e "\033[0;33m------------------------------------------------------------------------------------\033[0m" 101 | echo -e "\033[0;32mURL ${URL}\033[0m" # print it in green 102 | echo -e "\033[0;33m------------------------------------------------------------------------------------\033[0m" 103 | #------------------------------------------------------------# 104 | 105 | 106 | success 107 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /scripts/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #------------------------------------------------------------# 4 | # Init 5 | #------------------------------------------------------------# 6 | set -e 7 | trap fail ERR 8 | 9 | SCRIPT_NAME=`basename ${0}` 10 | 11 | start() { 12 | echo "" 13 | echo -e "\033[0;33mScript [${SCRIPT_NAME}] started\033[0m" # print it in yellow 14 | echo "" 15 | } 16 | 17 | success() { 18 | echo "" 19 | echo -e "\033[0;32mScript [${SCRIPT_NAME}] completed\033[0m" # print it in green 20 | echo "" 21 | exit 0 22 | } 23 | 24 | fail() { 25 | echo "" 26 | echo -e "\033[0;31mScript [${SCRIPT_NAME}] failed\033[0m" # print it in red 27 | echo "" 28 | exit 1 29 | } 30 | 31 | check_option_value() { 32 | if [ -z "$2" -o "${2:0:2}" == "--" ]; then 33 | echo "" 34 | echo "Option $1 was passed an invalid value: $2. Perhaps you passed in an empty env var?" 35 | fail 36 | fi 37 | } 38 | 39 | usage() { 40 | echo "Usage: $(basename "$0") [-h] [--config CONFIG_FILE_PATH]" 41 | echo "" 42 | echo "Options: 43 | -h, --help Display this help message. 44 | --config CONFIG_FILE_PATH Set a config file path explicitly other than default." 45 | } 46 | 47 | CONFIG_FILE_PATH="" 48 | while :; do 49 | case "$1" in 50 | -h | --help) 51 | usage 52 | exit 0 53 | ;; 54 | --config) 55 | check_option_value "$1" "$2" 56 | CONFIG_FILE_PATH="$2" 57 | shift 2 58 | ;; 59 | *) 60 | [ -z "$1" ] && break 61 | echo "" 62 | echo "Invalid option: [$1]." 63 | fail 64 | ;; 65 | esac 66 | done 67 | 68 | if [ -z "${CONFIG_FILE_PATH}" ]; then 69 | CONFIG_FILE_PATH="$(dirname $0)/../envs/default.conf" 70 | fi 71 | 72 | if [ ! -e "${CONFIG_FILE_PATH}" ]; then 73 | echo "" 74 | echo "Config file does not exist on [${CONFIG_FILE_PATH}]." 75 | fail 76 | fi 77 | 78 | start 79 | #------------------------------------------------------------# 80 | 81 | 82 | #------------------------------------------------------------# 83 | # Load / Define Variables 84 | #------------------------------------------------------------# 85 | source ${CONFIG_FILE_PATH} 86 | echo "-------------------------------------" 87 | echo "APP_ID=${APP_ID}" 88 | echo "ENV_ID=${ENV_ID}" 89 | echo "AWS_PROFILE=${AWS_PROFILE}" 90 | echo "AWS_REGION=${AWS_REGION}" 91 | echo "-------------------------------------" 92 | 93 | ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text) 94 | SUB_APP_IDS=( 95 | "frontend" 96 | "bff" 97 | "backend1" 98 | "backend2" 99 | ) 100 | #------------------------------------------------------------# 101 | 102 | 103 | #------------------------------------------------------------# 104 | # Delete Service Individual Stack 105 | #------------------------------------------------------------# 106 | for SUB_APP_ID in "${SUB_APP_IDS[@]}" ; do 107 | CFN_TEMPLATE_TYPE="svc-${SUB_APP_ID}" 108 | printf "\nDeleting ${APP_ID}-${ENV_ID}-${CFN_TEMPLATE_TYPE}-stack.. \n" 109 | sam delete \ 110 | --stack-name ${APP_ID}-${ENV_ID}-${CFN_TEMPLATE_TYPE}-stack \ 111 | --region ${AWS_REGION} \ 112 | --no-prompts 113 | done 114 | #------------------------------------------------------------# 115 | 116 | 117 | #------------------------------------------------------------# 118 | # Delete Container Images and Service Common Stack 119 | #------------------------------------------------------------# 120 | for SUB_APP_ID in "${SUB_APP_IDS[@]}" ; do 121 | # Delete container images 122 | ECR_REPOSITORY_NAME=${APP_ID}-${ENV_ID}-${SUB_APP_ID} 123 | if [ $(aws ecr describe-repositories --query 'length(repositories[?repositoryName==`'${ECR_REPOSITORY_NAME}'`])') -ne 0 ]; then 124 | printf "\nDeleting image: ${ECR_REPOSITORY_NAME}.. \n" 125 | aws ecr delete-repository --repository-name "${ECR_REPOSITORY_NAME}" --force 126 | fi 127 | done 128 | 129 | CFN_TEMPLATE_TYPE="svc-base" 130 | printf "\nDeleting ${APP_ID}-${ENV_ID}-${CFN_TEMPLATE_TYPE}-stack.. \n" 131 | sam delete \ 132 | --stack-name ${APP_ID}-${ENV_ID}-${CFN_TEMPLATE_TYPE}-stack \ 133 | --region ${AWS_REGION} \ 134 | --no-prompts 135 | #------------------------------------------------------------# 136 | 137 | 138 | #------------------------------------------------------------# 139 | # Delete VPC Stack 140 | #------------------------------------------------------------# 141 | CFN_TEMPLATE_TYPE="vpc" 142 | printf "\nDeleting ${APP_ID}-${ENV_ID}-${CFN_TEMPLATE_TYPE}-stack.. \n" 143 | sam delete \ 144 | --stack-name ${APP_ID}-${ENV_ID}-${CFN_TEMPLATE_TYPE}-stack \ 145 | --region ${AWS_REGION} \ 146 | --no-prompts 147 | #------------------------------------------------------------# 148 | 149 | 150 | success 151 | -------------------------------------------------------------------------------- /app/backend2/project/project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for project project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.0.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.0/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | from .utils import load_secret_key_from_config 15 | 16 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 17 | BASE_DIR = Path(__file__).resolve().parent.parent 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = load_secret_key_from_config('config.ini') 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = ['*'] 29 | 30 | 31 | # Application definition 32 | 33 | #------------------------------------------------------------# 34 | # Add X-Ray SDK for Python with the middleware (Django) 35 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-middleware.html#xray-sdk-python-adding-middleware-django 36 | #------------------------------------------------------------# 37 | INSTALLED_APPS = [ 38 | 'aws_xray_sdk.ext.django', # Added 39 | 'django.contrib.admin', 40 | 'django.contrib.auth', 41 | 'django.contrib.contenttypes', 42 | 'django.contrib.sessions', 43 | 'django.contrib.messages', 44 | 'django.contrib.staticfiles', 45 | ] 46 | 47 | MIDDLEWARE = [ 48 | 'aws_xray_sdk.ext.django.middleware.XRayMiddleware', # Added 49 | 'django.middleware.security.SecurityMiddleware', 50 | 'django.contrib.sessions.middleware.SessionMiddleware', 51 | 'django.middleware.common.CommonMiddleware', 52 | 'django.middleware.csrf.CsrfViewMiddleware', 53 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 54 | 'django.contrib.messages.middleware.MessageMiddleware', 55 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 56 | ] 57 | #------------------------------------------------------------# 58 | 59 | ROOT_URLCONF = 'project.urls' 60 | 61 | TEMPLATES = [ 62 | { 63 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 64 | 'DIRS': [], 65 | 'APP_DIRS': True, 66 | 'OPTIONS': { 67 | 'context_processors': [ 68 | 'django.template.context_processors.debug', 69 | 'django.template.context_processors.request', 70 | 'django.contrib.auth.context_processors.auth', 71 | 'django.contrib.messages.context_processors.messages', 72 | ], 73 | }, 74 | }, 75 | ] 76 | 77 | WSGI_APPLICATION = 'project.wsgi.application' 78 | 79 | 80 | # Database 81 | # https://docs.djangoproject.com/en/4.0/ref/settings/#databases 82 | 83 | DATABASES = { 84 | 'default': { 85 | 'ENGINE': 'django.db.backends.sqlite3', 86 | 'NAME': BASE_DIR / 'db.sqlite3', 87 | } 88 | } 89 | 90 | 91 | # Password validation 92 | # https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators 93 | 94 | AUTH_PASSWORD_VALIDATORS = [ 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 100 | }, 101 | { 102 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 103 | }, 104 | { 105 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 106 | }, 107 | ] 108 | 109 | 110 | # Internationalization 111 | # https://docs.djangoproject.com/en/4.0/topics/i18n/ 112 | 113 | LANGUAGE_CODE = 'en-us' 114 | 115 | TIME_ZONE = 'UTC' 116 | 117 | USE_I18N = True 118 | 119 | USE_TZ = True 120 | 121 | 122 | # Static files (CSS, JavaScript, Images) 123 | # https://docs.djangoproject.com/en/4.0/howto/static-files/ 124 | 125 | STATIC_URL = 'static/' 126 | 127 | # Default primary key field type 128 | # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field 129 | 130 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 131 | 132 | #------------------------------------------------------------# 133 | # Recorder configuration for X-Ray 134 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-configuration.html#xray-sdk-python-middleware-configuration-django 135 | #------------------------------------------------------------# 136 | XRAY_RECORDER = { 137 | 'AWS_XRAY_TRACING_NAME': 'Backend #2', 138 | 'AWS_XRAY_CONTEXT_MISSING': 'LOG_ERROR', 139 | } 140 | #------------------------------------------------------------# 141 | -------------------------------------------------------------------------------- /templates/2-svc-bff.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Parameters: 4 | AppId: 5 | Description: An application ID that is prefixed to resource names 6 | Type: String 7 | 8 | EnvId: 9 | Description: An environment ID that is prefixed to resource names 10 | Type: String 11 | 12 | ImageTag: 13 | Description: An image tag that is used in ECS task definition 14 | Type: String 15 | 16 | Resources: 17 | #----------------------------------------------------------# 18 | # BFF 19 | #----------------------------------------------------------# 20 | BffEcsService: 21 | Type: AWS::ECS::Service 22 | Properties: 23 | ServiceName: !Sub "${AppId}-${EnvId}-bff-ecs-service" 24 | Cluster: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-cluster-id"} 25 | LaunchType: FARGATE 26 | DesiredCount: 1 27 | EnableExecuteCommand: true 28 | DeploymentController: 29 | Type: ECS 30 | NetworkConfiguration: 31 | AwsvpcConfiguration: 32 | AssignPublicIp: DISABLED 33 | Subnets: 34 | - {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-private-subnet-1"} 35 | - {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-private-subnet-2"} 36 | SecurityGroups: 37 | - {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-frontend-layer-ecs-sg-id"} 38 | TaskDefinition: !Ref BffEcsTaskDefinition 39 | LoadBalancers: 40 | - TargetGroupArn: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-bff-alb-tg-arn"} 41 | ContainerPort: 80 42 | ContainerName: "bff-app" 43 | ServiceConnectConfiguration: 44 | Enabled: True 45 | LogConfiguration: 46 | LogDriver: awslogs 47 | Options: 48 | awslogs-create-group: True 49 | awslogs-group: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-log-group-name"} 50 | awslogs-region: !Ref AWS::Region 51 | awslogs-stream-prefix: backend1 52 | Namespace: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-cloud-map-namespace-name"} 53 | 54 | BffEcsTaskDefinition: 55 | Type: AWS::ECS::TaskDefinition 56 | Properties: 57 | ContainerDefinitions: 58 | - Name: "bff-app" 59 | Image: !Sub 60 | - "${EcrRepositoryUri}:${ImageTag}" 61 | - EcrRepositoryUri: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-bff-ecr-repository-uri"} 62 | ImageTag: !Ref ImageTag 63 | Environment: 64 | - Name: BACKEND_1_URL 65 | Value: !Sub 66 | - http://${Backend1ServiceName}.${Namespace} 67 | - Backend1ServiceName: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-backend1-cloud-map-service-name"} 68 | Namespace: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-cloud-map-namespace-name"} 69 | - Name: BACKEND_2_URL 70 | Value: !Sub 71 | - http://${Backend2ServiceName}.${Namespace} 72 | - Backend2ServiceName: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-backend2-cloud-map-service-name"} 73 | Namespace: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-cloud-map-namespace-name"} 74 | LinuxParameters: 75 | InitProcessEnabled: True 76 | PortMappings: 77 | - ContainerPort: 80 78 | Name: web 79 | Protocol: tcp 80 | LogConfiguration: 81 | LogDriver: awslogs 82 | Options: 83 | awslogs-create-group: True 84 | awslogs-group: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-log-group-name"} 85 | awslogs-region: !Ref AWS::Region 86 | awslogs-stream-prefix: bff 87 | #------------------------------------------------------------# 88 | # X-Ray daemon container (side-car) definition for ECS Task 89 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-daemon-ecs.html 90 | # - https://gallery.ecr.aws/xray/aws-xray-daemon 91 | #------------------------------------------------------------# 92 | - Name: "bff-xray-daemon" 93 | Image: public.ecr.aws/xray/aws-xray-daemon 94 | PortMappings: 95 | - ContainerPort: 2000 96 | Protocol: udp 97 | LogConfiguration: 98 | LogDriver: awslogs 99 | Options: 100 | awslogs-create-group: True 101 | awslogs-group: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-log-group-name"} 102 | awslogs-region: !Ref AWS::Region 103 | awslogs-stream-prefix: bff 104 | #------------------------------------------------------------# 105 | Family: !Sub "${AppId}-${EnvId}-bff" 106 | Cpu: 1024 107 | Memory: 2048 108 | NetworkMode: awsvpc 109 | RequiresCompatibilities: 110 | - FARGATE 111 | ExecutionRoleArn: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-task-execution-role-arn"} 112 | TaskRoleArn: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-task-role-arn"} 113 | #----------------------------------------------------------# 114 | -------------------------------------------------------------------------------- /templates/2-svc-backend1.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Parameters: 4 | AppId: 5 | Description: An application ID that is prefixed to resource names 6 | Type: String 7 | 8 | EnvId: 9 | Description: An environment ID that is prefixed to resource names 10 | Type: String 11 | 12 | ImageTag: 13 | Description: An image tag that is used in ECS task definition 14 | Type: String 15 | 16 | Resources: 17 | #----------------------------------------------------------# 18 | # Backend1 19 | #----------------------------------------------------------# 20 | Backend1EcsService: 21 | Type: AWS::ECS::Service 22 | Properties: 23 | ServiceName: !Sub "${AppId}-${EnvId}-backend1-ecs-service" 24 | Cluster: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-cluster-id"} 25 | LaunchType: FARGATE 26 | DesiredCount: 1 27 | EnableExecuteCommand: true 28 | DeploymentController: 29 | Type: ECS 30 | NetworkConfiguration: 31 | AwsvpcConfiguration: 32 | AssignPublicIp: DISABLED 33 | Subnets: 34 | - {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-private-subnet-1"} 35 | - {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-private-subnet-2"} 36 | SecurityGroups: 37 | - {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-backend-layer-ecs-sg-id"} 38 | TaskDefinition: !Ref Backend1EcsTaskDefinition 39 | ServiceConnectConfiguration: 40 | Enabled: True 41 | LogConfiguration: 42 | LogDriver: awslogs 43 | Options: 44 | awslogs-create-group: True 45 | awslogs-group: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-log-group-name"} 46 | awslogs-region: !Ref AWS::Region 47 | awslogs-stream-prefix: backend1 48 | Namespace: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-cloud-map-namespace-name"} 49 | Services: 50 | - ClientAliases: 51 | - DnsName: !Sub 52 | - ${Backend1ServiceName}.${Namespace} 53 | - Backend1ServiceName: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-backend1-cloud-map-service-name"} 54 | Namespace: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-cloud-map-namespace-name"} 55 | Port: 80 56 | DiscoveryName: backend1 57 | PortName: web 58 | 59 | Backend1EcsTaskDefinition: 60 | Type: AWS::ECS::TaskDefinition 61 | Properties: 62 | ContainerDefinitions: 63 | - Name: "backend1-app" 64 | Image: !Sub 65 | - "${EcrRepositoryUri}:${ImageTag}" 66 | - EcrRepositoryUri: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-backend1-ecr-repository-uri"} 67 | ImageTag: !Ref ImageTag 68 | Environment: 69 | - Name: DYNAMO_DB_TABLE_NAME 70 | Value: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-dynamodb-table-name"} 71 | - Name: AWS_DEFAULT_REGION 72 | Value: !Ref AWS::Region 73 | LinuxParameters: 74 | InitProcessEnabled: True 75 | PortMappings: 76 | - ContainerPort: 80 77 | Name: web 78 | Protocol: tcp 79 | HealthCheck: 80 | Command: 81 | - "CMD-SHELL" 82 | - "curl -f http://localhost/health || exit 1" 83 | Interval: 10 84 | Timeout: 5 85 | StartPeriod: 10 86 | Retries: 2 87 | LogConfiguration: 88 | LogDriver: awslogs 89 | Options: 90 | awslogs-create-group: True 91 | awslogs-group: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-log-group-name"} 92 | awslogs-region: !Ref AWS::Region 93 | awslogs-stream-prefix: backend1 94 | #------------------------------------------------------------# 95 | # X-Ray daemon container (side-car) definition for ECS Task 96 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-daemon-ecs.html 97 | # - https://gallery.ecr.aws/xray/aws-xray-daemon 98 | #------------------------------------------------------------# 99 | - Name: "backend1-xray-daemon" 100 | Image: public.ecr.aws/xray/aws-xray-daemon 101 | PortMappings: 102 | - ContainerPort: 2000 103 | Protocol: udp 104 | LogConfiguration: 105 | LogDriver: awslogs 106 | Options: 107 | awslogs-create-group: True 108 | awslogs-group: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-log-group-name"} 109 | awslogs-region: !Ref AWS::Region 110 | awslogs-stream-prefix: backend1 111 | #------------------------------------------------------------# 112 | Family: !Sub "${AppId}-${EnvId}-backend1" 113 | Cpu: 1024 114 | Memory: 2048 115 | NetworkMode: awsvpc 116 | RequiresCompatibilities: 117 | - FARGATE 118 | ExecutionRoleArn: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-task-execution-role-arn"} 119 | TaskRoleArn: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-task-role-arn"} 120 | #----------------------------------------------------------# 121 | -------------------------------------------------------------------------------- /templates/2-svc-backend2.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Parameters: 4 | AppId: 5 | Description: An application ID that is prefixed to resource names 6 | Type: String 7 | 8 | EnvId: 9 | Description: An environment ID that is prefixed to resource names 10 | Type: String 11 | 12 | ImageTag: 13 | Description: An image tag that is used in ECS task definition 14 | Type: String 15 | 16 | Resources: 17 | #----------------------------------------------------------# 18 | # Backend2 19 | #----------------------------------------------------------# 20 | Backend2EcsService: 21 | Type: AWS::ECS::Service 22 | Properties: 23 | ServiceName: !Sub "${AppId}-${EnvId}-backend2-ecs-service" 24 | Cluster: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-cluster-id"} 25 | LaunchType: FARGATE 26 | DesiredCount: 1 27 | EnableExecuteCommand: true 28 | DeploymentController: 29 | Type: ECS 30 | NetworkConfiguration: 31 | AwsvpcConfiguration: 32 | AssignPublicIp: DISABLED 33 | Subnets: 34 | - {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-private-subnet-1"} 35 | - {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-private-subnet-2"} 36 | SecurityGroups: 37 | - {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-backend-layer-ecs-sg-id"} 38 | TaskDefinition: !Ref Backend2EcsTaskDefinition 39 | ServiceConnectConfiguration: 40 | Enabled: True 41 | LogConfiguration: 42 | LogDriver: awslogs 43 | Options: 44 | awslogs-create-group: True 45 | awslogs-group: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-log-group-name"} 46 | awslogs-region: !Ref AWS::Region 47 | awslogs-stream-prefix: backend2 48 | Namespace: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-cloud-map-namespace-name"} 49 | Services: 50 | - ClientAliases: 51 | - DnsName: !Sub 52 | - ${Backend2ServiceName}.${Namespace} 53 | - Backend2ServiceName: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-backend2-cloud-map-service-name"} 54 | Namespace: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-cloud-map-namespace-name"} 55 | Port: 80 56 | DiscoveryName: backend2 57 | PortName: web 58 | 59 | Backend2EcsTaskDefinition: 60 | Type: AWS::ECS::TaskDefinition 61 | Properties: 62 | ContainerDefinitions: 63 | - Name: "backend2-app" 64 | Image: !Sub 65 | - "${EcrRepositoryUri}:${ImageTag}" 66 | - EcrRepositoryUri: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-backend2-ecr-repository-uri"} 67 | ImageTag: !Ref ImageTag 68 | Environment: 69 | - Name: DYNAMO_DB_TABLE_NAME 70 | Value: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-dynamodb-table-name"} 71 | - Name: AWS_DEFAULT_REGION 72 | Value: !Ref AWS::Region 73 | LinuxParameters: 74 | InitProcessEnabled: True 75 | PortMappings: 76 | - ContainerPort: 80 77 | Name: web 78 | Protocol: tcp 79 | HealthCheck: 80 | Command: 81 | - "CMD-SHELL" 82 | - "curl -f http://localhost/health || exit 1" 83 | Interval: 10 84 | Timeout: 5 85 | StartPeriod: 10 86 | Retries: 2 87 | LogConfiguration: 88 | LogDriver: awslogs 89 | Options: 90 | awslogs-create-group: True 91 | awslogs-group: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-log-group-name"} 92 | awslogs-region: !Ref AWS::Region 93 | awslogs-stream-prefix: backend2 94 | #------------------------------------------------------------# 95 | # X-Ray daemon container (side-car) definition for ECS Task 96 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-daemon-ecs.html 97 | # - https://gallery.ecr.aws/xray/aws-xray-daemon 98 | #------------------------------------------------------------# 99 | - Name: "backend2-xray-daemon" 100 | Image: public.ecr.aws/xray/aws-xray-daemon 101 | PortMappings: 102 | - ContainerPort: 2000 103 | Protocol: udp 104 | LogConfiguration: 105 | LogDriver: awslogs 106 | Options: 107 | awslogs-create-group: True 108 | awslogs-group: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-log-group-name"} 109 | awslogs-region: !Ref AWS::Region 110 | awslogs-stream-prefix: backend2 111 | #------------------------------------------------------------# 112 | Family: !Sub "${AppId}-${EnvId}-backend2" 113 | Cpu: 1024 114 | Memory: 2048 115 | NetworkMode: awsvpc 116 | RequiresCompatibilities: 117 | - FARGATE 118 | ExecutionRoleArn: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-task-execution-role-arn"} 119 | TaskRoleArn: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-task-role-arn"} 120 | #----------------------------------------------------------# 121 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | # Logs 163 | logs 164 | *.log 165 | npm-debug.log* 166 | yarn-debug.log* 167 | yarn-error.log* 168 | lerna-debug.log* 169 | .pnpm-debug.log* 170 | 171 | # Diagnostic reports (https://nodejs.org/api/report.html) 172 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 173 | 174 | # Runtime data 175 | pids 176 | *.pid 177 | *.seed 178 | *.pid.lock 179 | 180 | # Directory for instrumented libs generated by jscoverage/JSCover 181 | lib-cov 182 | 183 | # Coverage directory used by tools like istanbul 184 | coverage 185 | *.lcov 186 | 187 | # nyc test coverage 188 | .nyc_output 189 | 190 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 191 | .grunt 192 | 193 | # Bower dependency directory (https://bower.io/) 194 | bower_components 195 | 196 | # node-waf configuration 197 | .lock-wscript 198 | 199 | # Compiled binary addons (https://nodejs.org/api/addons.html) 200 | build/Release 201 | 202 | # Dependency directories 203 | node_modules/ 204 | jspm_packages/ 205 | 206 | # Snowpack dependency directory (https://snowpack.dev/) 207 | web_modules/ 208 | 209 | # TypeScript cache 210 | *.tsbuildinfo 211 | 212 | # Optional npm cache directory 213 | .npm 214 | 215 | # Optional eslint cache 216 | .eslintcache 217 | 218 | # Optional stylelint cache 219 | .stylelintcache 220 | 221 | # Microbundle cache 222 | .rpt2_cache/ 223 | .rts2_cache_cjs/ 224 | .rts2_cache_es/ 225 | .rts2_cache_umd/ 226 | 227 | # Optional REPL history 228 | .node_repl_history 229 | 230 | # Output of 'npm pack' 231 | *.tgz 232 | 233 | # Yarn Integrity file 234 | .yarn-integrity 235 | 236 | # dotenv environment variable files 237 | .env 238 | .env.development.local 239 | .env.test.local 240 | .env.production.local 241 | .env.local 242 | 243 | # parcel-bundler cache (https://parceljs.org/) 244 | .cache 245 | .parcel-cache 246 | 247 | # Next.js build output 248 | .next 249 | out 250 | 251 | # Nuxt.js build / generate output 252 | .nuxt 253 | dist 254 | 255 | # Gatsby files 256 | .cache/ 257 | # Comment in the public line in if your project uses Gatsby and not Next.js 258 | # https://nextjs.org/blog/next-9-1#public-directory-support 259 | # public 260 | 261 | # vuepress build output 262 | .vuepress/dist 263 | 264 | # vuepress v2.x temp and cache directory 265 | .temp 266 | .cache 267 | 268 | # Docusaurus cache and generated files 269 | .docusaurus 270 | 271 | # Serverless directories 272 | .serverless/ 273 | 274 | # FuseBox cache 275 | .fusebox/ 276 | 277 | # DynamoDB Local files 278 | .dynamodb/ 279 | 280 | # TernJS port file 281 | .tern-port 282 | 283 | # Stores VSCode versions used for testing VSCode extensions 284 | .vscode-test 285 | 286 | # yarn v2 287 | .yarn/cache 288 | .yarn/unplugged 289 | .yarn/build-state.yml 290 | .yarn/install-state.gz 291 | .pnp.* 292 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #------------------------------------------------------------# 4 | # Init 5 | #------------------------------------------------------------# 6 | set -e 7 | trap fail ERR 8 | 9 | SCRIPT_NAME=`basename ${0}` 10 | 11 | start() { 12 | echo "" 13 | echo -e "\033[0;33mScript [${SCRIPT_NAME}] started\033[0m" # print it in yellow 14 | echo "" 15 | } 16 | 17 | success() { 18 | echo "" 19 | echo -e "\033[0;32mScript [${SCRIPT_NAME}] completed\033[0m" # print it in green 20 | echo "" 21 | exit 0 22 | } 23 | 24 | fail() { 25 | echo "" 26 | echo -e "\033[0;31mScript [${SCRIPT_NAME}] failed\033[0m" # print it in red 27 | echo "" 28 | exit 1 29 | } 30 | 31 | check_option_value() { 32 | if [ -z "$2" -o "${2:0:2}" == "--" ]; then 33 | echo "" 34 | echo "Option $1 was passed an invalid value: $2. Perhaps you passed in an empty env var?" 35 | fail 36 | fi 37 | } 38 | 39 | usage() { 40 | echo "Usage: $(basename "$0") [-h] [--config CONFIG_FILE_PATH]" 41 | echo "" 42 | echo "Options: 43 | -h, --help Display this help message. 44 | --config CONFIG_FILE_PATH Set a config file path explicitly other than default." 45 | } 46 | 47 | CONFIG_FILE_PATH="" 48 | while :; do 49 | case "$1" in 50 | -h | --help) 51 | usage 52 | exit 0 53 | ;; 54 | --config) 55 | check_option_value "$1" "$2" 56 | CONFIG_FILE_PATH="$2" 57 | shift 2 58 | ;; 59 | *) 60 | [ -z "$1" ] && break 61 | echo "" 62 | echo "Invalid option: [$1]." 63 | fail 64 | ;; 65 | esac 66 | done 67 | 68 | if [ -z "${CONFIG_FILE_PATH}" ]; then 69 | CONFIG_FILE_PATH="$(dirname $0)/../envs/default.conf" 70 | fi 71 | 72 | if [ ! -e "${CONFIG_FILE_PATH}" ]; then 73 | echo "" 74 | echo "Config file does not exist on [${CONFIG_FILE_PATH}]." 75 | fail 76 | fi 77 | 78 | start 79 | #------------------------------------------------------------# 80 | 81 | 82 | #------------------------------------------------------------# 83 | # Load / Define Variables 84 | #------------------------------------------------------------# 85 | source ${CONFIG_FILE_PATH} 86 | echo "-------------------------------------" 87 | echo "APP_ID=${APP_ID}" 88 | echo "ENV_ID=${ENV_ID}" 89 | echo "AWS_PROFILE=${AWS_PROFILE}" 90 | echo "AWS_REGION=${AWS_REGION}" 91 | echo "VPC_CIDR=${VPC_CIDR}" 92 | echo "PUBLIC_SUBNET_1_CIDR=${PUBLIC_SUBNET_1_CIDR}" 93 | echo "PUBLIC_SUBNET_2_CIDR=${PUBLIC_SUBNET_2_CIDR}" 94 | echo "PRIVATE_SUBNET_1_CIDR=${PRIVATE_SUBNET_1_CIDR}" 95 | echo "PRIVATE_SUBNET_2_CIDR=${PRIVATE_SUBNET_2_CIDR}" 96 | echo "-------------------------------------" 97 | 98 | ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text) 99 | CFN_TEMPLATES_DIR=$(dirname $0)/../templates 100 | APP_DIR=$(dirname $0)/../app 101 | SUB_APP_IDS=( 102 | "backend2" 103 | "backend1" 104 | "bff" 105 | "frontend" 106 | ) 107 | declare -A IMAGE_TAGS 108 | #------------------------------------------------------------# 109 | 110 | 111 | #------------------------------------------------------------# 112 | # Deploy VPC Stack 113 | #------------------------------------------------------------# 114 | sam deploy \ 115 | --template-file templates/0-vpc.yml \ 116 | --stack-name ${APP_ID}-${ENV_ID}-vpc-stack \ 117 | --capabilities CAPABILITY_NAMED_IAM \ 118 | --region ${AWS_REGION} \ 119 | --parameter-overrides \ 120 | AppId=${APP_ID} \ 121 | EnvId=${ENV_ID} \ 122 | EnvName=${ENV_ID^} \ 123 | VpcCIDR=${VPC_CIDR} \ 124 | PublicSubnet1CIDR=${PUBLIC_SUBNET_1_CIDR} \ 125 | PublicSubnet2CIDR=${PUBLIC_SUBNET_2_CIDR} \ 126 | PrivateSubnet1CIDR=${PRIVATE_SUBNET_1_CIDR} \ 127 | PrivateSubnet2CIDR=${PRIVATE_SUBNET_2_CIDR} \ 128 | --no-fail-on-empty-changeset 129 | #------------------------------------------------------------# 130 | 131 | 132 | #------------------------------------------------------------# 133 | # Deploy Service Common Stack 134 | #------------------------------------------------------------# 135 | sam deploy \ 136 | --template-file templates/1-svc-base.yml \ 137 | --stack-name ${APP_ID}-${ENV_ID}-svc-base-stack \ 138 | --capabilities CAPABILITY_NAMED_IAM \ 139 | --region ${AWS_REGION} \ 140 | --parameter-overrides \ 141 | AppId=${APP_ID} \ 142 | EnvId=${ENV_ID} \ 143 | --no-fail-on-empty-changeset 144 | #------------------------------------------------------------# 145 | 146 | 147 | #------------------------------------------------------------# 148 | # Build / Push Container Images 149 | #------------------------------------------------------------# 150 | for SUB_APP_ID in "${SUB_APP_IDS[@]}" ; do 151 | # Build Container Images 152 | ECR_DOMAIN=${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com 153 | ECR_REPOSITORY_NAME=${APP_ID}-${ENV_ID}-${SUB_APP_ID} 154 | printf "\nBuilding image: [${ECR_DOMAIN}:${ECR_REPOSITORY_NAME}]..\n" 155 | docker build -t ${ECR_DOMAIN}/${ECR_REPOSITORY_NAME}:latest ${APP_DIR}/${SUB_APP_ID} 156 | docker images ${ECR_DOMAIN}/${ECR_REPOSITORY_NAME}:latest 157 | 158 | # Push Container Images 159 | IMAGE_TAG=$(docker images ${ECR_DOMAIN}/${ECR_REPOSITORY_NAME}:latest --format "{{.ID}}") 160 | IMAGE_TAGS["${SUB_APP_ID}"]=${IMAGE_TAG} 161 | printf "\nUploading image: [${ECR_DOMAIN}/${ECR_REPOSITORY_NAME}:${IMAGE_TAG}]..\n" 162 | NUM_OF_SAME_IMAGES_ON_ECR=$(aws ecr batch-get-image --repository-name ${ECR_REPOSITORY_NAME} --image-ids imageTag=${IMAGE_TAG} --query "length(images)") 163 | if [ "${NUM_OF_SAME_IMAGES_ON_ECR}" -eq 0 ]; then 164 | aws ecr get-login-password --region ${AWS_REGION} \ 165 | | docker login --username AWS --password-stdin ${ECR_DOMAIN} 166 | docker push ${ECR_DOMAIN}/${ECR_REPOSITORY_NAME}:latest 167 | docker tag ${ECR_DOMAIN}/${ECR_REPOSITORY_NAME}:latest ${ECR_DOMAIN}/${ECR_REPOSITORY_NAME}:${IMAGE_TAG} 168 | docker push ${ECR_DOMAIN}/${ECR_REPOSITORY_NAME}:${IMAGE_TAG} 169 | else 170 | printf "\nProcess skipped. The same image already exists on ECR. [${ECR_DOMAIN}/${ECR_REPOSITORY_NAME}:${IMAGE_TAG}]\n" 171 | fi 172 | done 173 | #------------------------------------------------------------# 174 | 175 | 176 | #------------------------------------------------------------# 177 | # Deploy Service Individual Stack 178 | #------------------------------------------------------------# 179 | for SUB_APP_ID in "${SUB_APP_IDS[@]}" ; do 180 | sam deploy \ 181 | --template-file templates/2-svc-${SUB_APP_ID}.yml \ 182 | --stack-name ${APP_ID}-${ENV_ID}-svc-${SUB_APP_ID}-stack \ 183 | --capabilities CAPABILITY_NAMED_IAM \ 184 | --region ${AWS_REGION} \ 185 | --parameter-overrides \ 186 | AppId=${APP_ID} \ 187 | EnvId=${ENV_ID} \ 188 | ImageTag=${IMAGE_TAGS["${SUB_APP_ID}"]} \ 189 | --no-fail-on-empty-changeset 190 | done 191 | #------------------------------------------------------------# 192 | 193 | 194 | success 195 | -------------------------------------------------------------------------------- /templates/0-vpc.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Parameters: 4 | AppId: 5 | Description: An application ID that is prefixed to resource names 6 | Type: String 7 | 8 | EnvId: 9 | Description: An environment ID that is prefixed to resource names 10 | Type: String 11 | 12 | VpcCidr: 13 | Description: Please enter the IP range (CIDR notation) for this VPC 14 | Type: String 15 | Default: 10.192.0.0/16 16 | 17 | PublicSubnet1Cidr: 18 | Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone 19 | Type: String 20 | Default: 10.192.10.0/24 21 | 22 | PublicSubnet2Cidr: 23 | Description: Please enter the IP range (CIDR notation) for the public subnet in the second Availability Zone 24 | Type: String 25 | Default: 10.192.11.0/24 26 | 27 | PrivateSubnet1Cidr: 28 | Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone 29 | Type: String 30 | Default: 10.192.20.0/24 31 | 32 | PrivateSubnet2Cidr: 33 | Description: Please enter the IP range (CIDR notation) for the private subnet in the second Availability Zone 34 | Type: String 35 | Default: 10.192.21.0/24 36 | 37 | Resources: 38 | VPC: 39 | Type: AWS::EC2::VPC 40 | Properties: 41 | CidrBlock: !Ref VpcCidr 42 | EnableDnsSupport: true 43 | EnableDnsHostnames: true 44 | Tags: 45 | - Key: Name 46 | Value: !Ref EnvId 47 | 48 | InternetGateway: 49 | Type: AWS::EC2::InternetGateway 50 | Properties: 51 | Tags: 52 | - Key: Name 53 | Value: !Ref EnvId 54 | 55 | InternetGatewayAttachment: 56 | Type: AWS::EC2::VPCGatewayAttachment 57 | Properties: 58 | InternetGatewayId: !Ref InternetGateway 59 | VpcId: !Ref VPC 60 | 61 | PublicSubnet1: 62 | Type: AWS::EC2::Subnet 63 | Properties: 64 | VpcId: !Ref VPC 65 | AvailabilityZone: !Select [ 0, !GetAZs "" ] 66 | CidrBlock: !Ref PublicSubnet1Cidr 67 | MapPublicIpOnLaunch: true 68 | Tags: 69 | - Key: Name 70 | Value: !Sub "${AppId}-${EnvId}-public-subnet-1" 71 | 72 | PublicSubnet2: 73 | Type: AWS::EC2::Subnet 74 | Properties: 75 | VpcId: !Ref VPC 76 | AvailabilityZone: !Select [ 1, !GetAZs "" ] 77 | CidrBlock: !Ref PublicSubnet2Cidr 78 | MapPublicIpOnLaunch: true 79 | Tags: 80 | - Key: Name 81 | Value: !Sub "${AppId}-${EnvId}-public-subnet-2" 82 | 83 | PrivateSubnet1: 84 | Type: AWS::EC2::Subnet 85 | Properties: 86 | VpcId: !Ref VPC 87 | AvailabilityZone: !Select [ 0, !GetAZs "" ] 88 | CidrBlock: !Ref PrivateSubnet1Cidr 89 | MapPublicIpOnLaunch: false 90 | Tags: 91 | - Key: Name 92 | Value: !Sub "${AppId}-${EnvId}-private-subnet-1" 93 | 94 | PrivateSubnet2: 95 | Type: AWS::EC2::Subnet 96 | Properties: 97 | VpcId: !Ref VPC 98 | AvailabilityZone: !Select [ 1, !GetAZs "" ] 99 | CidrBlock: !Ref PrivateSubnet2Cidr 100 | MapPublicIpOnLaunch: false 101 | Tags: 102 | - Key: Name 103 | Value: !Sub "${AppId}-${EnvId}-private-subnet-2" 104 | 105 | NatGateway1EIP: 106 | Type: AWS::EC2::EIP 107 | DependsOn: InternetGatewayAttachment 108 | Properties: 109 | Domain: vpc 110 | 111 | NatGateway2EIP: 112 | Type: AWS::EC2::EIP 113 | DependsOn: InternetGatewayAttachment 114 | Properties: 115 | Domain: vpc 116 | 117 | NatGateway1: 118 | Type: AWS::EC2::NatGateway 119 | Properties: 120 | AllocationId: !GetAtt NatGateway1EIP.AllocationId 121 | SubnetId: !Ref PublicSubnet1 122 | 123 | NatGateway2: 124 | Type: AWS::EC2::NatGateway 125 | Properties: 126 | AllocationId: !GetAtt NatGateway2EIP.AllocationId 127 | SubnetId: !Ref PublicSubnet2 128 | 129 | PublicRouteTable: 130 | Type: AWS::EC2::RouteTable 131 | Properties: 132 | VpcId: !Ref VPC 133 | Tags: 134 | - Key: Name 135 | Value: !Sub "${AppId}-${EnvId}-public-routes" 136 | 137 | DefaultPublicRoute: 138 | Type: AWS::EC2::Route 139 | DependsOn: InternetGatewayAttachment 140 | Properties: 141 | RouteTableId: !Ref PublicRouteTable 142 | DestinationCidrBlock: 0.0.0.0/0 143 | GatewayId: !Ref InternetGateway 144 | 145 | PublicSubnet1RouteTableAssociation: 146 | Type: AWS::EC2::SubnetRouteTableAssociation 147 | Properties: 148 | RouteTableId: !Ref PublicRouteTable 149 | SubnetId: !Ref PublicSubnet1 150 | 151 | PublicSubnet2RouteTableAssociation: 152 | Type: AWS::EC2::SubnetRouteTableAssociation 153 | Properties: 154 | RouteTableId: !Ref PublicRouteTable 155 | SubnetId: !Ref PublicSubnet2 156 | 157 | PrivateRouteTable1: 158 | Type: AWS::EC2::RouteTable 159 | Properties: 160 | VpcId: !Ref VPC 161 | Tags: 162 | - Key: Name 163 | Value: !Sub "${AppId}-${EnvId}-private-routes-1" 164 | 165 | DefaultPrivateRoute1: 166 | Type: AWS::EC2::Route 167 | Properties: 168 | RouteTableId: !Ref PrivateRouteTable1 169 | DestinationCidrBlock: 0.0.0.0/0 170 | NatGatewayId: !Ref NatGateway1 171 | 172 | PrivateSubnet1RouteTableAssociation: 173 | Type: AWS::EC2::SubnetRouteTableAssociation 174 | Properties: 175 | RouteTableId: !Ref PrivateRouteTable1 176 | SubnetId: !Ref PrivateSubnet1 177 | 178 | PrivateRouteTable2: 179 | Type: AWS::EC2::RouteTable 180 | Properties: 181 | VpcId: !Ref VPC 182 | Tags: 183 | - Key: Name 184 | Value: !Sub "${AppId}-${EnvId}-private-routes-2" 185 | 186 | DefaultPrivateRoute2: 187 | Type: AWS::EC2::Route 188 | Properties: 189 | RouteTableId: !Ref PrivateRouteTable2 190 | DestinationCidrBlock: 0.0.0.0/0 191 | NatGatewayId: !Ref NatGateway2 192 | 193 | PrivateSubnet2RouteTableAssociation: 194 | Type: AWS::EC2::SubnetRouteTableAssociation 195 | Properties: 196 | RouteTableId: !Ref PrivateRouteTable2 197 | SubnetId: !Ref PrivateSubnet2 198 | 199 | NoIngressSecurityGroup: 200 | Type: AWS::EC2::SecurityGroup 201 | Properties: 202 | GroupName: "no-ingress-sg" 203 | GroupDescription: "Security group with no ingress rule" 204 | VpcId: !Ref VPC 205 | 206 | Outputs: 207 | VPC: 208 | Description: A reference to the created VPC 209 | Value: !Ref VPC 210 | Export: 211 | Name: !Sub "${AppId}-${EnvId}-vpc-id" 212 | 213 | PublicSubnets: 214 | Description: A list of the public subnets 215 | Value: !Join [ ",", [ !Ref PublicSubnet1, !Ref PublicSubnet2 ]] 216 | Export: 217 | Name: !Sub "${AppId}-${EnvId}-public-subnets" 218 | 219 | PrivateSubnets: 220 | Description: A list of the private subnets 221 | Value: !Join [ ",", [ !Ref PrivateSubnet1, !Ref PrivateSubnet2 ]] 222 | Export: 223 | Name: !Sub "${AppId}-${EnvId}-private-subnets" 224 | 225 | PublicSubnet1: 226 | Description: A reference to the public subnet in the 1st Availability Zone 227 | Value: !Ref PublicSubnet1 228 | Export: 229 | Name: !Sub "${AppId}-${EnvId}-public-subnet-1" 230 | 231 | PublicSubnet2: 232 | Description: A reference to the public subnet in the 2nd Availability Zone 233 | Value: !Ref PublicSubnet2 234 | Export: 235 | Name: !Sub "${AppId}-${EnvId}-public-subnet-2" 236 | 237 | PrivateSubnet1: 238 | Description: A reference to the private subnet in the 1st Availability Zone 239 | Value: !Ref PrivateSubnet1 240 | Export: 241 | Name: !Sub "${AppId}-${EnvId}-private-subnet-1" 242 | 243 | PrivateSubnet2: 244 | Description: A reference to the private subnet in the 2nd Availability Zone 245 | Value: !Ref PrivateSubnet2 246 | Export: 247 | Name: !Sub "${AppId}-${EnvId}-private-subnet-2" 248 | 249 | NoIngressSecurityGroup: 250 | Description: Security group with no ingress rule 251 | Value: !Ref NoIngressSecurityGroup 252 | Export: 253 | Name: !Sub "${AppId}-${EnvId}-no-ingress-security-group" 254 | -------------------------------------------------------------------------------- /templates/1-svc-base.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | 3 | Parameters: 4 | AppId: 5 | Description: An application ID that is prefixed to resource names 6 | Type: String 7 | 8 | EnvId: 9 | Description: An environment ID that is prefixed to resource names 10 | Type: String 11 | 12 | Resources: 13 | #----------------------------------------------------------# 14 | # Security Groups 15 | #----------------------------------------------------------# 16 | AlbSecurityGroup: 17 | Type: AWS::EC2::SecurityGroup 18 | Properties: 19 | GroupDescription: "Security Group for ALB" 20 | GroupName: !Sub "${AppId}-${EnvId}-commmon-alb-sg" 21 | VpcId: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-vpc-id"} 22 | SecurityGroupIngress: 23 | - IpProtocol: tcp 24 | FromPort: 80 25 | ToPort: 80 26 | CidrIp: 0.0.0.0/0 27 | 28 | FrontendLayerEcsSecurityGroup: 29 | Type: AWS::EC2::SecurityGroup 30 | Properties: 31 | GroupDescription: "Security Group for Frontend Layer ECS" 32 | GroupName: !Sub "${AppId}-${EnvId}-frontend-layer-ecs-sg" 33 | VpcId: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-vpc-id"} 34 | SecurityGroupIngress: 35 | - IpProtocol: tcp 36 | FromPort: 80 37 | ToPort: 80 38 | SourceSecurityGroupId: !GetAtt AlbSecurityGroup.GroupId 39 | 40 | BackendLayerEcsSecurityGroup: 41 | Type: AWS::EC2::SecurityGroup 42 | Properties: 43 | GroupDescription: "Security Group for Backend Layer ECS" 44 | GroupName: !Sub "${AppId}-${EnvId}-backend-layer-ecs-sg" 45 | VpcId: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-vpc-id"} 46 | SecurityGroupIngress: 47 | - IpProtocol: tcp 48 | FromPort: 80 49 | ToPort: 80 50 | SourceSecurityGroupId: !GetAtt FrontendLayerEcsSecurityGroup.GroupId 51 | #----------------------------------------------------------# 52 | 53 | 54 | #----------------------------------------------------------# 55 | # IAM Roles 56 | #----------------------------------------------------------# 57 | EcsTaskExecutionRole: 58 | Type: AWS::IAM::Role 59 | Properties: 60 | RoleName: !Sub "${AppId}-${EnvId}-ecs-task-execution-role" 61 | Path: / 62 | AssumeRolePolicyDocument: 63 | Version: 2012-10-17 64 | Statement: 65 | - Effect: Allow 66 | Principal: 67 | Service: 68 | - ecs-tasks.amazonaws.com 69 | Action: sts:AssumeRole 70 | ManagedPolicyArns: 71 | - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy 72 | 73 | EcsTaskRole: 74 | Type: AWS::IAM::Role 75 | Properties: 76 | RoleName: !Sub "${AppId}-${EnvId}-ecs-task-role" 77 | Path: / 78 | AssumeRolePolicyDocument: 79 | Version: 2012-10-17 80 | Statement: 81 | - Effect: Allow 82 | Principal: 83 | Service: 84 | - ecs-tasks.amazonaws.com 85 | Action: sts:AssumeRole 86 | ManagedPolicyArns: 87 | #------------------------------------------------------------# 88 | # Required role for X-Ray daemon container in ECS Task 89 | # - https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/security_iam_id-based-policy-examples.html 90 | #------------------------------------------------------------# 91 | - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess 92 | #------------------------------------------------------------# 93 | - arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess 94 | Policies: 95 | - PolicyName: EcsExecPolicy 96 | PolicyDocument: 97 | Version: "2012-10-17" 98 | Statement: 99 | - Effect: Allow 100 | Action: 101 | - ssmmessages:CreateControlChannel 102 | - ssmmessages:CreateDataChannel 103 | - ssmmessages:OpenControlChannel 104 | - ssmmessages:OpenDataChannel 105 | - logs:CreateLogStream 106 | - logs:DescribeLogStreams 107 | - logs:PutLogEvents 108 | Resource: "*" 109 | #----------------------------------------------------------# 110 | 111 | 112 | #----------------------------------------------------------# 113 | # ECR Repositories 114 | #----------------------------------------------------------# 115 | FrontendEcrRepository: 116 | Type: AWS::ECR::Repository 117 | Properties: 118 | RepositoryName: !Sub "${AppId}-${EnvId}-frontend" 119 | 120 | BffEcrRepository: 121 | Type: AWS::ECR::Repository 122 | Properties: 123 | RepositoryName: !Sub "${AppId}-${EnvId}-bff" 124 | 125 | Backend1EcrRepository: 126 | Type: AWS::ECR::Repository 127 | Properties: 128 | RepositoryName: !Sub "${AppId}-${EnvId}-backend1" 129 | 130 | Backend2EcrRepository: 131 | Type: AWS::ECR::Repository 132 | Properties: 133 | RepositoryName: !Sub "${AppId}-${EnvId}-backend2" 134 | #----------------------------------------------------------# 135 | 136 | 137 | #----------------------------------------------------------# 138 | # ALB 139 | #----------------------------------------------------------# 140 | Alb: 141 | Type: AWS::ElasticLoadBalancingV2::LoadBalancer 142 | Properties: 143 | Name: !Sub "${AppId}-${EnvId}" 144 | Scheme: internet-facing 145 | Subnets: 146 | - {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-public-subnet-1"} 147 | - {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-public-subnet-2"} 148 | SecurityGroups: 149 | - !GetAtt AlbSecurityGroup.GroupId 150 | 151 | AlbListener: 152 | Type: AWS::ElasticLoadBalancingV2::Listener 153 | Properties: 154 | DefaultActions: 155 | - Type: forward 156 | TargetGroupArn: !Ref FrontendAlbTargetGroup 157 | LoadBalancerArn: !Ref Alb 158 | Port: 80 159 | Protocol: HTTP 160 | 161 | AlbListenerRule: 162 | Type: AWS::ElasticLoadBalancingV2::ListenerRule 163 | Properties: 164 | Actions: 165 | - TargetGroupArn: !Ref BffAlbTargetGroup 166 | Type: forward 167 | Conditions: 168 | - Field: path-pattern 169 | PathPatternConfig: 170 | Values: 171 | - /api/* 172 | ListenerArn: !Ref AlbListener 173 | Priority: 1 174 | 175 | FrontendAlbTargetGroup: 176 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 177 | Properties: 178 | VpcId: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-vpc-id"} 179 | Name: !Sub "${AppId}-${EnvId}-frontend-alb-tg" 180 | Protocol: HTTP 181 | Port: 80 182 | TargetType: ip 183 | HealthCheckPath: /health 184 | HealthCheckPort: 80 185 | HealthCheckProtocol: HTTP 186 | HealthCheckIntervalSeconds: 10 187 | HealthCheckTimeoutSeconds: 5 188 | HealthyThresholdCount: 2 189 | UnhealthyThresholdCount: 2 190 | Matcher: 191 | HttpCode: 200 192 | 193 | BffAlbTargetGroup: 194 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 195 | Properties: 196 | VpcId: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-vpc-id"} 197 | Name: !Sub "${AppId}-${EnvId}-bff-alb-tg" 198 | Protocol: HTTP 199 | Port: 80 200 | TargetType: ip 201 | HealthCheckPath: /health 202 | HealthCheckPort: 80 203 | HealthCheckProtocol: HTTP 204 | HealthCheckIntervalSeconds: 10 205 | HealthCheckTimeoutSeconds: 5 206 | HealthyThresholdCount: 2 207 | UnhealthyThresholdCount: 2 208 | Matcher: 209 | HttpCode: 200 210 | #----------------------------------------------------------# 211 | 212 | 213 | #----------------------------------------------------------# 214 | # ECS 215 | #----------------------------------------------------------# 216 | EcsCluster: 217 | Type: AWS::ECS::Cluster 218 | Properties: 219 | ClusterName: !Sub "${AppId}-${EnvId}-ecs-cluster" 220 | Configuration: 221 | ExecuteCommandConfiguration: 222 | Logging: DEFAULT 223 | 224 | EcsLogGroup: 225 | Type: AWS::Logs::LogGroup 226 | Properties: 227 | LogGroupName: !Sub "/aws/ecs/${AppId}/${EnvId}" 228 | #----------------------------------------------------------# 229 | 230 | 231 | #----------------------------------------------------------# 232 | # Cloud Map 233 | #----------------------------------------------------------# 234 | CloudMapNamespace: 235 | Type: AWS::ServiceDiscovery::PrivateDnsNamespace 236 | Properties: 237 | Name: !Sub "ecs.${EnvId}.${AppId}.private" 238 | Vpc: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-vpc-id"} 239 | #----------------------------------------------------------# 240 | 241 | 242 | #----------------------------------------------------------# 243 | # DynamoDB 244 | #----------------------------------------------------------# 245 | DynamoDbTable: 246 | Type: AWS::DynamoDB::Table 247 | Properties: 248 | TableName: !Sub "${AppId}-${EnvId}-last-accessed" 249 | AttributeDefinitions: 250 | - AttributeName: SubAppId 251 | AttributeType: S 252 | KeySchema: 253 | - AttributeName: SubAppId 254 | KeyType: HASH 255 | BillingMode: PAY_PER_REQUEST 256 | #----------------------------------------------------------# 257 | 258 | 259 | #------------------------------------------------------------# 260 | # X-Ray Sampling rules to disable sampling healthcheck traffic 261 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-console-sampling.html#xray-console-sampling-options 262 | #------------------------------------------------------------# 263 | XRaySamplingRule: 264 | Type: AWS::XRay::SamplingRule 265 | Properties: 266 | SamplingRule: 267 | Version: 1 268 | RuleName: !Sub "${AppId}-${EnvId}-ignore-healthcheck" 269 | Priority: 1 270 | ReservoirSize: 0 271 | FixedRate: 0 272 | ServiceName: "*" 273 | ServiceType: "*" 274 | HTTPMethod: "GET" 275 | URLPath: "/health" 276 | ResourceARN: "*" 277 | Host: "*" 278 | #------------------------------------------------------------# 279 | 280 | Outputs: 281 | FrontendLayerEcsSecurityGroupId: 282 | Description: A reference to the ID of frontend layer ECS security group 283 | Value: !Ref FrontendLayerEcsSecurityGroup 284 | Export: 285 | Name: !Sub "${AppId}-${EnvId}-frontend-layer-ecs-sg-id" 286 | 287 | BackendLayerEcsSecurityGroupId: 288 | Description: A reference to the ID of backend layer ECS security group 289 | Value: !Ref BackendLayerEcsSecurityGroup 290 | Export: 291 | Name: !Sub "${AppId}-${EnvId}-backend-layer-ecs-sg-id" 292 | 293 | EcsTaskExecutionRoleArn: 294 | Description: A reference to the ARN of common ECS task execution role 295 | Value: !GetAtt EcsTaskExecutionRole.Arn 296 | Export: 297 | Name: !Sub "${AppId}-${EnvId}-ecs-task-execution-role-arn" 298 | 299 | EcsTaskRoleArn: 300 | Description: A reference to the ARN of common ECS task role 301 | Value: !GetAtt EcsTaskRole.Arn 302 | Export: 303 | Name: !Sub "${AppId}-${EnvId}-ecs-task-role-arn" 304 | 305 | FrontendEcrRepositoryUri: 306 | Description: A reference to the URI of frontend ECR repository 307 | Value: !GetAtt FrontendEcrRepository.RepositoryUri 308 | Export: 309 | Name: !Sub "${AppId}-${EnvId}-frontend-ecr-repository-uri" 310 | 311 | BffEcrRepositoryUri: 312 | Description: A reference to the URI of bff ECR repository 313 | Value: !GetAtt BffEcrRepository.RepositoryUri 314 | Export: 315 | Name: !Sub "${AppId}-${EnvId}-bff-ecr-repository-uri" 316 | 317 | Backend1EcrRepositoryUri: 318 | Description: A reference to the URI of backend1 ECR repository 319 | Value: !GetAtt Backend1EcrRepository.RepositoryUri 320 | Export: 321 | Name: !Sub "${AppId}-${EnvId}-backend1-ecr-repository-uri" 322 | 323 | Backend2EcrRepositoryUri: 324 | Description: A reference to the URI of backend2 ECR repository 325 | Value: !GetAtt Backend2EcrRepository.RepositoryUri 326 | Export: 327 | Name: !Sub "${AppId}-${EnvId}-backend2-ecr-repository-uri" 328 | 329 | AlbDnsName: 330 | Description: A reference to the DNS name of common ALB 331 | Value: !GetAtt Alb.DNSName 332 | Export: 333 | Name: !Sub "${AppId}-${EnvId}-alb-dns-name" 334 | 335 | FrontendAlbTargetGroupArn: 336 | Description: A reference to the ARN of frontend ALB target group 337 | Value: !Ref FrontendAlbTargetGroup 338 | Export: 339 | Name: !Sub "${AppId}-${EnvId}-frontend-alb-tg-arn" 340 | 341 | BffAlbTargetGroupArn: 342 | Description: A reference to the ARN of bff ALB target group 343 | Value: !Ref BffAlbTargetGroup 344 | Export: 345 | Name: !Sub "${AppId}-${EnvId}-bff-alb-tg-arn" 346 | 347 | EcsClusterId: 348 | Description: A reference to the ID of common ECS cluster 349 | Value: !Ref EcsCluster 350 | Export: 351 | Name: !Sub "${AppId}-${EnvId}-ecs-cluster-id" 352 | 353 | EcsLogGroupName: 354 | Description: A reference to the Name of common ECS log group 355 | Value: !Ref EcsLogGroup 356 | Export: 357 | Name: !Sub "${AppId}-${EnvId}-ecs-log-group-name" 358 | 359 | CloudMapNamespaceName: 360 | Description: A reference to the Name of service discovery DNS namespace 361 | Value: !Sub "ecs.${EnvId}.${AppId}.private" 362 | Export: 363 | Name: !Sub "${AppId}-${EnvId}-cloud-map-namespace-name" 364 | 365 | BffCloudMapServiceName: 366 | Description: A reference to the Name of bff service in service discovery DNS namespace 367 | Value: bff 368 | Export: 369 | Name: !Sub "${AppId}-${EnvId}-bff-cloud-map-service-name" 370 | 371 | Backend1CloudMapServiceName: 372 | Description: A reference to the Name of backend1 service in service discovery DNS namespace 373 | Value: backend1 374 | Export: 375 | Name: !Sub "${AppId}-${EnvId}-backend1-cloud-map-service-name" 376 | 377 | Backend2CloudMapServiceName: 378 | Description: A reference to the Name of backend2 service in service discovery DNS namespace 379 | Value: backend2 380 | Export: 381 | Name: !Sub "${AppId}-${EnvId}-backend2-cloud-map-service-name" 382 | 383 | DynamoDbTableName: 384 | Description: A reference to the name of common DynamoDB table 385 | Value: !Ref DynamoDbTable 386 | Export: 387 | Name: !Sub "${AppId}-${EnvId}-dynamodb-table-name" 388 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example for AWS X-Ray in Amazon ECS 2 | 3 | 4 | 5 | 6 | ## 1. Overview 7 | 8 | ### 1.1. Use case 9 | 10 | - This pattern describes implementation guides and example codes for the developers who are planning to implement distributed tracing system using AWS X-Ray, under distributed architecture (i.e. Microservices architecture) that consists of multiple containers based on Amazon ECS. 11 | 12 | 13 | ### 1.2. Assumed requirements 14 | 15 | - System adminstrators can monitor system performance and reliability across multiple containers based on Amazon ECS, and can identify the bottlenecks of them. 16 | 17 | 18 | ### 1.3. Limitations 19 | 20 | - This pattern contains only content about _ECS on Fargate_. The contents specific to _ECS on EC2_ are not mentioned here. 21 | 22 | - Some application codes are included as example codes, but not all the languages or frameworks supported in AWS X-Ray are described here, only for _Express framework for Node.js_, _Flask and Django framework for Python_. 23 | 24 | - For the sake of simplicity, it has not been properly designed except for the settings mentioned in this article, such as communication encryption, capacity tuning, redundancy, etc. 25 | 26 | 27 | ### 1.4. Architecture 28 | 29 | - The example codes assumes an architecture consisting of 4 containers based on ECS Fargate. 30 | 31 | 1. **Frontend (nginx, html)** 32 | 33 | Returns simple html file that sends requests to BFF and displays the result. 34 | 35 | 1. **BFF (Express, Node.js)** 36 | 37 | Requests to Backend #1, #2 sequentially and responds with the combined result in JSON format. 38 | 39 | 1. **Backend #1 (Flask, Python)** 40 | 41 | Responds current date information in JSON format, writing log (last accessed time) to DynamoDB. 42 | 43 | 1. **Backend #2 (Django, Python)** 44 | 45 | Responds current time information in JSON format, writing log (last accessed time) to DynamoDB. 46 | 47 |
48 |
49 |
50 | ### 1.5. Distributed tracing mechanism
51 |
52 | - Trace data is obtained and associated through **BFF, Backend #1, Backend #2** using AWS X-Ray SDK and AWS X-Ray Daemon on Amazon ECS tasks.
53 |
54 |
55 |
56 | _Note: In this case, trace data of **Frontend** was excluded from the collection, because it can be obtained from developer tools of common browser._
57 |
58 | - Trace data can be monitored on AWS X-Ray Console like below.
59 |
60 |
61 |
62 |
63 |
64 |
65 | ## 2. Prerequisites
66 |
67 | ### 2.1. Tools
68 |
69 | - The following tools need to be installed on your machine.
70 | - [AWS CLI version 2](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html)
71 | - [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)
72 | - [docker](https://docs.docker.com/get-docker/)
73 |
74 |
75 |
76 |
77 | ## 3. Usage
78 |
79 | ### 3.1. Installation
80 |
81 | 1. Download source codes from this repository to your machine.
82 |
83 | 1. Edit `envs/default.conf`. You can skip this step if you proceed with default settings.
84 |
85 | 1. Run `install.sh` with the following command. This might take 15 minutes or more.
86 |
87 | ```bash
88 | $ bash scripts/install.sh
89 | ```
90 |
91 | 1. After installation, get the frontend endpoint URL with the following command.
92 |
93 | ```bash
94 | $ bash scripts/get_url.sh
95 | ```
96 |
97 | 1. Access to the URL via browser, then current date and time are shown like below.
98 |
99 |
100 |
101 | 1. After trying to access the URL some times, access to [X-Ray console](https://ap-northeast-1.console.aws.amazon.com/xray/home) then you can see the trace map.
102 |
103 |
104 |
105 |
106 | ### 3.2. Update
107 |
108 | 1. Run `install.sh` again after modifications with the following command.
109 |
110 | ```bash
111 | $ bash scripts/install.sh
112 | ```
113 |
114 |
115 | ### 3.3. Uninstallation
116 |
117 | 1. Run `uninstall.sh` with the following command.
118 |
119 | ```bash
120 | $ bash scripts/uninstall.sh
121 | ```
122 |
123 |
124 |
125 |
126 | ## 4. Implementation guide
127 |
128 | - This pattern describes how to implement distributed tracing mechanism under the Amazon ECS container-based distributed system using AWS X-Ray.
129 |
130 | - To obtain trace data in AWS X-Ray from Amazon ECS containers, 2 types of configurations need to be configured properly.
131 |
132 | 1. Application codes
133 |
134 | 1. CloudFormation templates
135 |
136 |
137 |
138 | ### 4.1. Application codes
139 |
140 | #### 4.1.1. Add X-Ray SDK middlewares (for tracing incoming requests)
141 |
142 | - To enable tracing incoming requests in application codes, you can add X-Ray SDK middlewares for supported framework.
143 |
144 | - In the following sections, we share examples for Express(Node.js), Flask(Python) and Django(Python).
145 |
146 |
147 | ##### 4.1.1.1. Example for Express(Node.js): [app/bff/server.js](app/bff/server.js)
148 |
149 | ```javascript
150 | //------------------------------------------------------------
151 | // Add X-Ray SDK for Node.js with the middleware (Express)
152 | // - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-middleware.html#xray-sdk-nodejs-middleware-express
153 | //------------------------------------------------------------
154 | const AWSXRay = require('aws-xray-sdk');
155 | //------------------------------------------------------------
156 |
157 | const express = require('express');
158 |
159 | ...
160 |
161 | const app = express();
162 |
163 | //------------------------------------------------------------
164 | // Open segment for X-Ray with the middleware (Express)
165 | // - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-middleware.html#xray-sdk-nodejs-middleware-express
166 | //------------------------------------------------------------
167 | app.use(AWSXRay.express.openSegment('BFF'));
168 | //------------------------------------------------------------
169 |
170 | ...
171 |
172 | //------------------------------------------------------------
173 | // Close segment for X-Ray with the middleware (Express)
174 | // - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-middleware.html#xray-sdk-nodejs-middleware-express
175 | //------------------------------------------------------------
176 | app.use(AWSXRay.express.closeSegment());
177 | //------------------------------------------------------------
178 |
179 | ...
180 | ```
181 |
182 | References:
183 | - [Tracing incoming requests with Express](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-middleware.html#xray-sdk-nodejs-middleware-express)
184 |
185 |
186 |
187 | ##### 4.1.1.2. Example for Flask(Python): [app/backend1/app.py](app/backend1/app.py)
188 |
189 | ```python
190 | from flask import *
191 | from flask_cors import CORS
192 | import time, datetime, os, math, logging
193 | import boto3
194 |
195 | ...
196 |
197 | #------------------------------------------------------------#
198 | # Add X-Ray SDK for Python with the middleware (Flask)
199 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-middleware.html#xray-sdk-python-adding-middleware-flask
200 | #------------------------------------------------------------#
201 | from aws_xray_sdk.core import xray_recorder
202 | from aws_xray_sdk.ext.flask.middleware import XRayMiddleware
203 | #------------------------------------------------------------#
204 |
205 | ...
206 |
207 | app = Flask(__name__)
208 | CORS(app)
209 |
210 | #------------------------------------------------------------#
211 | # Setup X-Ray SDK and apply patch to Flask application
212 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-middleware.html#xray-sdk-python-adding-middleware-flask
213 | #------------------------------------------------------------#
214 | xray_recorder.configure(service='Backend #1')
215 | XRayMiddleware(app, xray_recorder)
216 | #------------------------------------------------------------#
217 |
218 | ...
219 | ```
220 |
221 | References:
222 | - [Adding the middleware to your application (flask)](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-middleware.html#xray-sdk-python-adding-middleware-flask)
223 |
224 |
225 | ##### 4.1.1.3. Example for Django(Python): [app/backend2/project/project/settings.py](app/backend2/project/project/settings.py)
226 |
227 | ```python
228 | ...
229 |
230 | #------------------------------------------------------------#
231 | # Add X-Ray SDK for Python with the middleware (Django)
232 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-middleware.html#xray-sdk-python-adding-middleware-django
233 | #------------------------------------------------------------#
234 | INSTALLED_APPS = [
235 | 'aws_xray_sdk.ext.django', # Added
236 | 'django.contrib.admin',
237 | 'django.contrib.auth',
238 | 'django.contrib.contenttypes',
239 | 'django.contrib.sessions',
240 | 'django.contrib.messages',
241 | 'django.contrib.staticfiles',
242 | ]
243 |
244 | MIDDLEWARE = [
245 | 'aws_xray_sdk.ext.django.middleware.XRayMiddleware', # Added
246 | 'django.middleware.security.SecurityMiddleware',
247 | 'django.contrib.sessions.middleware.SessionMiddleware',
248 | 'django.middleware.common.CommonMiddleware',
249 | 'django.middleware.csrf.CsrfViewMiddleware',
250 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
251 | 'django.contrib.messages.middleware.MessageMiddleware',
252 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
253 | ]
254 | #------------------------------------------------------------#
255 |
256 | ...
257 |
258 | #------------------------------------------------------------#
259 | # Recorder configuration for X-Ray
260 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-configuration.html#xray-sdk-python-middleware-configuration-django
261 | #------------------------------------------------------------#
262 | XRAY_RECORDER = {
263 | 'AWS_XRAY_TRACING_NAME': 'Backend #2',
264 | 'AWS_XRAY_CONTEXT_MISSING': 'LOG_ERROR',
265 | }
266 | #------------------------------------------------------------#
267 |
268 | ```
269 |
270 | References:
271 | - [Adding the middleware to your application (Django)](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-middleware.html#xray-sdk-python-adding-middleware-django)
272 | - [Recorder configuration with Django](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-configuration.html#xray-sdk-python-middleware-configuration-django)
273 |
274 |
275 |
276 |
277 | #### 4.1.2. Apply patches to libraries (for tracing downstream calls)
278 |
279 | - If you want to trace downstream calls from applications and associate them, you can apply patches to the supported libraries for each language.
280 | - In the following sections, we share examples for Node.js and Python.
281 |
282 |
283 | ##### 4.1.2.1. Example for Node.js: [app/bff/server.js](app/bff/server.js)
284 |
285 | ```javascript
286 | ...
287 |
288 | //------------------------------------------------------------
289 | // Apply patches to Node.js libraries for tracing downstream HTTP requests
290 | // - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-httpclients.html
291 | // - https://github.com/aws-samples/aws-xray-sdk-node-sample/blob/master/index.js
292 | //------------------------------------------------------------
293 | // HTTP Client
294 | AWSXRay.captureHTTPsGlobal(require('http'));
295 | AWSXRay.capturePromise();
296 | const axios = require('axios');
297 |
298 | // // AWS SDK (not used in this sample)
299 | // const AWS = AWSXRay.captureAWS(require('aws-sdk'));
300 | //------------------------------------------------------------
301 |
302 | ...
303 | ```
304 |
305 | References:
306 | - [Tracing calls to downstream HTTP web services using the X-Ray SDK for Node.js](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs-httpclients.html)
307 | - [X-Ray SDK for Node.js Sample App](https://github.com/aws-samples/aws-xray-sdk-node-sample/tree/56edb37a5fae46c14eb74793509de3ed6e2f5c5c)
308 |
309 |
310 |
311 | ##### 4.1.2.2. Example for Python: [app/backend1/app.py](app/backend1/app.py), [app/backend2/project/api/views.py](app/backend2/project/api/views.py)
312 |
313 | ```python
314 | ...
315 |
316 | #------------------------------------------------------------#
317 | # Apply patches to Python libraries for tracing downstream HTTP requests
318 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-patching.html
319 | #------------------------------------------------------------#
320 | from aws_xray_sdk.core import patch_all
321 | patch_all()
322 | #------------------------------------------------------------#
323 |
324 | ...
325 | ```
326 |
327 | References:
328 | - [Patching libraries to instrument downstream calls](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-patching.html)
329 |
330 |
331 |
332 |
333 | #### 4.1.3. Configure X-Ray segments manually
334 |
335 | - Without using X-Ray SDK with the middleware and patches for the libraries, you can manually configure X-Ray segments and subsegments.
336 |
337 | - It enables us to associate services and obtain trace data through the communication between them, but you need to take care header information to be set and passed.
338 |
339 |
340 |
341 |
342 | ##### 4.1.3.1. Example for Python in BFF
343 |
344 | ```python
345 | from flask import *
346 | import time, datetime, os, math, logging
347 | import boto3
348 |
349 | #------------------------------------------------------------#
350 | # Add X-Ray SDK for Python
351 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-middleware.html#xray-sdk-python-middleware-manual
352 | #------------------------------------------------------------#
353 | from aws_xray_sdk.core import xray_recorder
354 | #------------------------------------------------------------#
355 |
356 | app = Flask(__name__)
357 |
358 |
359 | @app.route('/health', methods=['GET'])
360 | def health_check():
361 | return jsonify({
362 | "status": "ok"
363 | })
364 |
365 |
366 | @app.route("/", methods=["GET"])
367 | def main():
368 |
369 | #--------------------------------------------------------#
370 | # Get trace_id and parent_id from headers
371 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-services-elb.html
372 | #--------------------------------------------------------#
373 | trace_id = request.headers.get('X_RAY_HEADER_TRACE')
374 | parent_id = request.headers.get('X_RAY_HEADER_PARENT')
375 | #--------------------------------------------------------#
376 |
377 | #--------------------------------------------------------#
378 | # Begin "segment"
379 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-middleware.html#xray-sdk-python-middleware-manual
380 | #--------------------------------------------------------#
381 | xray_recorder.begin_segment(name='Backend #1', parent_id=parent_id ,traceid=trace_id)
382 | #--------------------------------------------------------#
383 |
384 | #--------------------------------------------------------#
385 | # Create new headers using current segment information
386 | # - https://docs.aws.amazon.com/xray-sdk-for-python/latest/reference/basic.html
387 | #--------------------------------------------------------#
388 | current_segment = xray_recorder.current_segment()
389 | headers = {
390 | 'X_RAY_HEADER_TRACE': current_segment.trace_id,
391 | 'X_RAY_HEADER_PARENT': current_segment.id
392 | }
393 | #--------------------------------------------------------#
394 |
395 | #----------------------------------------------------#
396 | # Begin "subsegment" #1
397 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-subsegments.html
398 | #----------------------------------------------------#
399 | xray_recorder.begin_subsegment(name='Call Backend #1')
400 | #----------------------------------------------------#
401 |
402 | # Write to DynamoDB table
403 | dyname_db_table_name = os.environ.get('DYNAMO_DB_TABLE_NAME', '')
404 | region_name = os.environ.get('AWS_DEFAULT_REGION', '')
405 | try:
406 | dynamodb = boto3.resource('dynamodb', region_name=region_name)
407 | table = dynamodb.Table(dyname_db_table_name)
408 | table.put_item(Item={
409 | "SubAppId": "backend1",
410 | "LastAccessed": str(math.floor(time.time()))
411 | })
412 | except Exception as e:
413 | logger.exception(e)
414 |
415 | #----------------------------------------------------#
416 | # End "subsegment" #1
417 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-subsegments.html
418 | #----------------------------------------------------#
419 | xray_recorder.end_subsegment()
420 | #----------------------------------------------------#
421 |
422 | #----------------------------------------------------#
423 | # Begin "subsegment" #2
424 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-subsegments.html
425 | #----------------------------------------------------#
426 | xray_recorder.begin_subsegment(name='Call Backend #2')
427 | #----------------------------------------------------#
428 |
429 | #----------------------------------------------------#
430 | # End "subsegment" #2
431 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-subsegments.html
432 | #----------------------------------------------------#
433 | xray_recorder.end_subsegment()
434 | #----------------------------------------------------#
435 |
436 | #--------------------------------------------------------#
437 | # End "segment"
438 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-middleware.html#xray-sdk-python-middleware-manual
439 | #--------------------------------------------------------#
440 | xray_recorder.end_segment()
441 | #--------------------------------------------------------#
442 |
443 | # Return current date
444 | current_datetime = datetime.datetime.fromtimestamp(time.time()).astimezone(datetime.timezone(datetime.timedelta(hours=9)))
445 | return jsonify({
446 | "currentDate": current_datetime.strftime('%Y/%m/%d')
447 | })
448 |
449 |
450 | if __name__ == '__main__':
451 | app.run(host="0.0.0.0", port=80)
452 | ```
453 |
454 | References:
455 | - [Instrumenting Python code manually](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-middleware.html#xray-sdk-python-middleware-manual)
456 | - [Generating custom subsegments with the X-Ray SDK for Python](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-python-subsegments.html)
457 | - [aws-xray-sdk - Basic Usage](https://docs.aws.amazon.com/xray-sdk-for-python/latest/reference/basic.html)
458 | - [Elastic Load Balancing and AWS X-Ray](https://docs.aws.amazon.com/xray/latest/devguide/xray-services-elb.html)
459 |
460 |
461 |
462 |
463 | ### 4.2. CloudFormation templates
464 |
465 | #### 4.2.1. Add X-Ray daemon container settings to ECS task definition
466 |
467 | - X-Ray daemon container settings are needed in ECS task definition.
468 | - About X-Ray daemon container, please see section 1.5.
469 |
470 | ##### 4.2.1.1. Example for CFn template: [templates/2-svc-bff.yml](templates/2-svc-bff.yml)
471 |
472 | ```yaml
473 | BffEcsTaskDefinition:
474 | Type: AWS::ECS::TaskDefinition
475 | Properties:
476 | ContainerDefinitions:
477 | - Name: "bff-app"
478 | ...
479 | #------------------------------------------------------------#
480 | # X-Ray daemon container (side-car) definition for ECS Task
481 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-daemon-ecs.html
482 | # - https://gallery.ecr.aws/xray/aws-xray-daemon
483 | #------------------------------------------------------------#
484 | - Name: "bff-xray-daemon"
485 | Image: public.ecr.aws/xray/aws-xray-daemon
486 | PortMappings:
487 | - ContainerPort: 2000
488 | Protocol: udp
489 | LogConfiguration:
490 | LogDriver: awslogs
491 | Options:
492 | awslogs-create-group: True
493 | awslogs-group: {"Fn::ImportValue": !Sub "${AppId}-${EnvId}-ecs-log-group-name"}
494 | awslogs-region: !Ref AWS::Region
495 | awslogs-stream-prefix: bff
496 | #------------------------------------------------------------#
497 | ...
498 | TaskRoleArn: !GetAtt EcsTaskRole.Arn
499 |
500 | ```
501 |
502 | References:
503 | - [Running the X-Ray daemon on Amazon ECS](https://docs.aws.amazon.com/xray/latest/devguide/xray-daemon-ecs.html)
504 | - [Amazon ECR Public Gallery > xray > xray/aws-xray-daemon](https://gallery.ecr.aws/xray/aws-xray-daemon)
505 |
506 |
507 |
508 | #### 4.2.2. Add a policy to ECS task role
509 |
510 | - A policy is needed in ECS task role for writing data into X-Ray through the X-Ray daemon container.
511 |
512 | ##### 4.2.2.1. Example for CFn template: [templates/1-svc-base.yml](templates/1-svc-base.yml)
513 |
514 | ```yaml
515 | EcsTaskRole:
516 | Type: AWS::IAM::Role
517 | Properties:
518 | ...
519 | ManagedPolicyArns:
520 | #------------------------------------------------------------#
521 | # Required role for X-Ray daemon container in ECS Task
522 | # - https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/security_iam_id-based-policy-examples.html
523 | #------------------------------------------------------------#
524 | - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
525 | #------------------------------------------------------------#
526 | ...
527 | ```
528 |
529 | References:
530 | - [AWS X-Ray identity-based policy examples](https://docs.aws.amazon.com/xray/latest/devguide/security_iam_id-based-policy-examples.html)
531 |
532 |
533 |
534 | #### 4.2.3. Configure X-Ray Sampling Rule
535 |
536 | - In X-Ray, sampling rules can be adjusted mainly for the cost perspectives.
537 | - In this case we add a sampling rule to disable sampling healthcheck traffic in each service using CloudFormation.
538 |
539 |
540 | ##### 4.2.3.1. Example for CFn template: [templates/1-svc-base.yml](templates/1-svc-base.yml)
541 |
542 | ```yaml
543 | #------------------------------------------------------------#
544 | # X-Ray Sampling rules to disable sampling healthcheck traffic
545 | # - https://docs.aws.amazon.com/xray/latest/devguide/xray-console-sampling.html#xray-console-sampling-options
546 | #------------------------------------------------------------#
547 | XRaySamplingRule:
548 | Type: AWS::XRay::SamplingRule
549 | Properties:
550 | SamplingRule:
551 | Version: 1
552 | RuleName: !Sub "ignore-healthcheck"
553 | Priority: 1
554 | ReservoirSize: 0
555 | FixedRate: 0
556 | ServiceName: "*"
557 | ServiceType: "*"
558 | HTTPMethod: "GET"
559 | URLPath: "/health"
560 | ResourceARN: "*"
561 | Host: "*"
562 | #------------------------------------------------------------#
563 | ```
564 |
565 | References:
566 | - [Sampling rule options](https://docs.aws.amazon.com/xray/latest/devguide/xray-console-sampling.html#xray-console-sampling-options)
567 |
568 |
569 |
570 |
571 | ## 5. Security
572 |
573 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.
574 |
575 |
576 |
577 | ## 6. License
578 |
579 | This library is licensed under the MIT-0 License. See the LICENSE file.
580 |
581 |
--------------------------------------------------------------------------------