├── cdk ├── cdk │ ├── __init__.py │ ├── baseplatform.py │ └── nodejsservice.py ├── tests │ ├── __init__.py │ └── unit │ │ ├── __init__.py │ │ └── test_nodejs_service.py ├── cdk.json ├── requirements.txt ├── .gitignore ├── README.md └── app.py ├── code_hash.txt ├── .dockerignore ├── .gitignore ├── mu.yml ├── kubernetes ├── service.yaml ├── helm │ └── ecsdemo-nodejs │ │ ├── templates │ │ ├── serviceaccount.yaml │ │ ├── service.yaml │ │ ├── tests │ │ │ └── test-connection.yaml │ │ ├── hpa.yaml │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── deployment.yaml │ │ └── ingress.yaml │ │ ├── .helmignore │ │ ├── Chart.yaml │ │ └── values.yaml └── deployment.yaml ├── docker-compose.yml ├── README.md ├── package.json ├── ecs-params.yml.template ├── ecs-params.yml ├── test └── test.js ├── Dockerfile ├── Dockerfile.cdk ├── LICENSE ├── server.js ├── startup.sh └── startup-cdk.sh /cdk/cdk/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cdk/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cdk/tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /code_hash.txt: -------------------------------------------------------------------------------- 1 | fe183d7 2 | -------------------------------------------------------------------------------- /cdk/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "python3 app.py" 3 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .git 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | cdk.out 3 | cdk.context.json 4 | -------------------------------------------------------------------------------- /cdk/requirements.txt: -------------------------------------------------------------------------------- 1 | aws-cdk-lib>=2.50.0,<2.59.0 2 | constructs>=10.0.0,<11.0.0 -------------------------------------------------------------------------------- /mu.yml: -------------------------------------------------------------------------------- 1 | --- 2 | service: 3 | desiredCount: 3 4 | maxSize: 6 5 | port: 3000 6 | discoveryTTL: 5 7 | -------------------------------------------------------------------------------- /kubernetes/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: ecsdemo-nodejs 5 | spec: 6 | selector: 7 | app: ecsdemo-nodejs 8 | ports: 9 | - protocol: TCP 10 | port: 80 11 | targetPort: 3000 12 | 13 | -------------------------------------------------------------------------------- /cdk/.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | package-lock.json 3 | .pytest_cache 4 | *.egg-info 5 | 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # Environments 12 | .env 13 | .venv 14 | env/ 15 | venv/ 16 | ENV/ 17 | env.bak/ 18 | venv.bak/ 19 | 20 | # CDK Context & Staging files 21 | .cdk.staging/ 22 | cdk.out/ -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | ecsdemo-nodejs: 4 | image: brentley/ecsdemo-nodejs 5 | ports: 6 | - "3000:3000" 7 | logging: 8 | driver: awslogs 9 | options: 10 | awslogs-group: ecsdemo-nodejs 11 | awslogs-region: ${AWS_REGION} 12 | awslogs-stream-prefix: ecsdemo-nodejs 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Nodejs module for the ECS workshop 2 | 3 | This repository is part of the ECS Workshop. 4 | 5 | This repository will deploy a nodejs application in a Fargate service to prepare your environment for the [ECS Workshop](https://ecsworkshop.com/). 6 | 7 | Instructions on how to use the code in this repository can be found here: [https://ecsworkshop.com/microservices/nodejs/](https://ecsworkshop.com/microservices/nodejs/) 8 | 9 | -------------------------------------------------------------------------------- /cdk/README.md: -------------------------------------------------------------------------------- 1 | ## NodeJS Application module for the ECS workshop 2 | 3 | This repository is part of the ECS Workshop. 4 | 5 | This repository will deploy an application in a Fargate service to prepare your environment for the [ECS Workshop](https://ecsworkshop.com/). 6 | 7 | Instructions on how to use the code in this repository can be found here: [https://ecsworkshop.com/microservices/nodejs/](https://ecsworkshop.com/microservices/nodejs/) -------------------------------------------------------------------------------- /kubernetes/helm/ecsdemo-nodejs/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "ecsdemo-nodejs.serviceAccountName" . }} 6 | labels: 7 | {{- include "ecsdemo-nodejs.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-nodejs", 3 | "version": "1.0.0", 4 | "license": "ISC", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "dependencies": { 10 | "express": "^4.16.2", 11 | "express-healthcheck": "^0.1.0", 12 | "internal-ip": "^3.0.1", 13 | "ip": "^1.1.5", 14 | "morgan": "^1.9.0" 15 | }, 16 | "devDependencies": { 17 | "mocha": "^4.1.0", 18 | "supertest": "^3.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /cdk/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | 5 | from aws_cdk import ( 6 | App, 7 | Environment, 8 | ) 9 | 10 | from cdk.nodejsservice import NodejsService 11 | 12 | account = os.getenv('AWS_ACCOUNT_ID') 13 | region = os.getenv('AWS_DEFAULT_REGION') 14 | stack_name = "ecsworkshop-nodejs" 15 | env = Environment(account=account, region=region) 16 | 17 | app = App() 18 | NodejsService(app, stack_name, env=env) 19 | 20 | app.synth() 21 | -------------------------------------------------------------------------------- /kubernetes/helm/ecsdemo-nodejs/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /kubernetes/helm/ecsdemo-nodejs/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "ecsdemo-nodejs.fullname" . }} 5 | labels: 6 | {{- include "ecsdemo-nodejs.labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.port }} 11 | targetPort: http 12 | protocol: TCP 13 | name: http 14 | selector: 15 | {{- include "ecsdemo-nodejs.selectorLabels" . | nindent 4 }} 16 | -------------------------------------------------------------------------------- /kubernetes/helm/ecsdemo-nodejs/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "ecsdemo-nodejs.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "ecsdemo-nodejs.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "ecsdemo-nodejs.fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /cdk/tests/unit/test_nodejs_service.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import aws_cdk as core 4 | import aws_cdk.assertions as assertions 5 | 6 | from cdk.nodejsservice import NodejsService 7 | 8 | account = os.getenv('AWS_ACCOUNT_ID') 9 | region = os.getenv('AWS_DEFAULT_REGION') 10 | env = core.Environment(account=account, region=region) 11 | 12 | 13 | def test_task_created(): 14 | app = core.App() 15 | stack = NodejsService(app, "baseplatform", env=env) 16 | template = assertions.Template.from_stack(stack) 17 | 18 | template.has_resource_properties("AWS::ECS::TaskDefinition", { 19 | "Cpu": "256" 20 | }) 21 | -------------------------------------------------------------------------------- /ecs-params.yml.template: -------------------------------------------------------------------------------- 1 | version: 1 2 | task_definition: 3 | task_execution_role: $ecsTaskExecutionRole 4 | ecs_network_mode: awsvpc 5 | task_size: 6 | mem_limit: 0.5GB 7 | cpu_limit: 256 8 | run_params: 9 | network_configuration: 10 | awsvpc_configuration: 11 | subnets: 12 | - "$subnet_1" 13 | - "$subnet_2" 14 | - "$subnet_3" 15 | security_groups: 16 | - "$security_group" 17 | assign_public_ip: DISABLED 18 | service_discovery: 19 | container_name: ecsdemo-nodejs 20 | service_discovery_service: 21 | name: ecsdemo-nodejs 22 | description: string 23 | -------------------------------------------------------------------------------- /ecs-params.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | task_definition: 3 | task_execution_role: ecsTaskExecutionRole 4 | ecs_network_mode: awsvpc 5 | task_size: 6 | mem_limit: 0.5GB 7 | cpu_limit: 256 8 | run_params: 9 | network_configuration: 10 | awsvpc_configuration: 11 | subnets: 12 | - "subnet-0412684667a7a5147" 13 | - "subnet-08467586f86110cea" 14 | - "subnet-0afe24995d24d9e0f" 15 | security_groups: 16 | - "sg-03eef5d8f0eefa149" 17 | assign_public_ip: DISABLED 18 | service_discovery: 19 | container_name: ecsdemo-nodejs 20 | service_discovery_service: 21 | name: ecsdemo-nodejs 22 | description: string 23 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var request = require('supertest'); 2 | describe('loading express', function () { 3 | var server; 4 | beforeEach(function () { 5 | server = require('../server'); 6 | }); 7 | after(function (done) { 8 | server.close(done); 9 | }); 10 | it('responds to /', function testSlash(done) { 11 | request(server) 12 | .get('/') 13 | .expect(200, done); 14 | }); 15 | it('responds to /health', function testSlash(done) { 16 | request(server) 17 | .get('/health') 18 | .expect(200, done); 19 | }); 20 | it('404 everything else', function testPath(done) { 21 | request(server) 22 | .get('/foo/bar') 23 | .expect(404, done); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /kubernetes/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: ecsdemo-nodejs 5 | labels: 6 | app: ecsdemo-nodejs 7 | namespace: default 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: ecsdemo-nodejs 13 | strategy: 14 | rollingUpdate: 15 | maxSurge: 25% 16 | maxUnavailable: 25% 17 | type: RollingUpdate 18 | template: 19 | metadata: 20 | labels: 21 | app: ecsdemo-nodejs 22 | spec: 23 | containers: 24 | - image: brentley/ecsdemo-nodejs:latest 25 | imagePullPolicy: Always 26 | name: ecsdemo-nodejs 27 | ports: 28 | - containerPort: 3000 29 | protocol: TCP 30 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # FROM ubuntu:18.04 2 | FROM public.ecr.aws/docker/library/ubuntu:18.04 3 | 4 | # set the default NODE_ENV to production 5 | # for dev/test build with: docker build --build-arg NODE=development . 6 | # and the testing npms will be included 7 | ARG NODE=production 8 | ENV NODE_ENV ${NODE} 9 | 10 | # copy package info early to install npms and delete npm command 11 | WORKDIR /usr/src/app 12 | COPY package*.json ./ 13 | RUN apt-get update &&\ 14 | apt install -y curl jq bash nodejs npm python3 python3-pip && \ 15 | pip3 install awscli netaddr && \ 16 | npm install &&\ 17 | apt-get purge -y npm && \ 18 | apt clean 19 | 20 | # copy the code 21 | COPY . . 22 | HEALTHCHECK --interval=10s --timeout=3s \ 23 | CMD curl -f -s http://localhost:3000/health/ || exit 1 24 | EXPOSE 3000 25 | ENTRYPOINT ["bash","/usr/src/app/startup.sh"] 26 | -------------------------------------------------------------------------------- /Dockerfile.cdk: -------------------------------------------------------------------------------- 1 | # FROM node:alpine 2 | FROM alpine:3.6 3 | 4 | # set the default NODE_ENV to production 5 | # for dev/test build with: docker build --build-arg NODE=development . 6 | # and the testing npms will be included 7 | ARG NODE=production 8 | ENV NODE_ENV ${NODE} 9 | 10 | # copy package info early to install npms and delete npm command 11 | WORKDIR /usr/src/app 12 | COPY package*.json ./ 13 | RUN apk -U add curl jq bash nodejs nodejs-npm python3 py3-pip && \ 14 | pip3 install awscli netaddr && \ 15 | npm install && apk del --purge nodejs-npm && \ 16 | rm -rvf /var/cache/* /root/.npm /tmp/* 17 | 18 | # copy the code 19 | COPY . . 20 | RUN mv /usr/src/app/startup-cdk.sh /usr/src/app/startup.sh 21 | HEALTHCHECK --interval=10s --timeout=3s \ 22 | CMD curl -f -s http://localhost:3000/health/ || exit 1 23 | EXPOSE 3000 24 | ENTRYPOINT ["bash","/usr/src/app/startup.sh"] 25 | -------------------------------------------------------------------------------- /kubernetes/helm/ecsdemo-nodejs/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2beta1 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ include "ecsdemo-nodejs.fullname" . }} 6 | labels: 7 | {{- include "ecsdemo-nodejs.labels" . | nindent 4 }} 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ include "ecsdemo-nodejs.fullname" . }} 13 | minReplicas: {{ .Values.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.autoscaling.maxReplicas }} 15 | metrics: 16 | {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} 17 | - type: Resource 18 | resource: 19 | name: cpu 20 | targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} 21 | {{- end }} 22 | {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} 23 | - type: Resource 24 | resource: 25 | name: memory 26 | targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} 27 | {{- end }} 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Brent Langston 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /kubernetes/helm/ecsdemo-nodejs/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: ecsdemo-nodejs 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "1.16.0" 25 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // use the express framework 2 | var express = require('express'); 3 | var app = express(); 4 | 5 | var fs = require('fs'); 6 | var code_hash = fs.readFileSync('code_hash.txt','utf8'); 7 | console.log (code_hash); 8 | console.log('The IPADDRESS is:', process.env.IP); 9 | console.log('The message is:', process.env.AZ); 10 | console.log('The hash is: %s', code_hash); 11 | 12 | var ipaddress = process.env.IP; 13 | var message = process.env.AZ; 14 | 15 | // morgan: generate apache style logs to the console 16 | var morgan = require('morgan') 17 | app.use(morgan('combined')); 18 | 19 | // express-healthcheck: respond on /health route for LB checks 20 | app.use('/health', require('express-healthcheck')()); 21 | 22 | // main route 23 | app.get('/', function (req, res) { 24 | res.set({ 25 | 'Content-Type': 'text/plain' 26 | }) 27 | res.send(`Node.js backend: Hello! from ${message} commit ${code_hash}`); 28 | // res.send(`Hello World! from ${ipaddress} in AZ-${az} which has been up for ` + process.uptime() + 'ms'); 29 | }); 30 | 31 | app.get('/nodejs', function (req, res) { 32 | res.set({ 33 | 'Content-Type': 'text/plain' 34 | }) 35 | res.send(`Node.js backend: Hello! from ${message} commit ${code_hash}`); 36 | // res.send(`Hello World! from ${ipaddress} in AZ-${az} which has been up for ` + process.uptime() + 'ms'); 37 | }); 38 | 39 | app.get('/nodejs/api', function (req, res) { 40 | res.send({ 41 | from: 'Node.js backend', 42 | message: message, 43 | commit: code_hash 44 | }); 45 | }); 46 | 47 | // health route - variable subst is more pythonic just as an example 48 | var server = app.listen(3000, function() { 49 | var port = server.address().port; 50 | console.log('Example app listening on port %s!', port); 51 | }); 52 | 53 | // export the server to make tests work 54 | module.exports = server; 55 | -------------------------------------------------------------------------------- /kubernetes/helm/ecsdemo-nodejs/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "ecsdemo-nodejs.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "ecsdemo-nodejs.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "ecsdemo-nodejs.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "ecsdemo-nodejs.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 20 | echo "Visit http://127.0.0.1:8080 to use your application" 21 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /cdk/cdk/baseplatform.py: -------------------------------------------------------------------------------- 1 | from aws_cdk import ( 2 | Stack, 3 | Fn, 4 | aws_ec2 as ec2, 5 | aws_servicediscovery as servicediscovery, 6 | aws_ecs as ecs, 7 | ) 8 | from constructs import Construct 9 | 10 | 11 | # Creating a construct that will populate 12 | # the required objects created in the platform repo 13 | # such as vpc, ecs cluster, and service discovery namespace 14 | 15 | class BasePlatform(Construct): 16 | 17 | def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: 18 | super().__init__(scope, construct_id, **kwargs) 19 | 20 | region = Stack.of(self).region 21 | 22 | # The base platform stack is where the VPC was created, 23 | # so all we need is the name to do a lookup and import 24 | # it into this stack for use 25 | vpc = ec2.Vpc.from_lookup( 26 | self, 27 | "VPC", 28 | vpc_name='ecsworkshop-base/BaseVPC', 29 | region=region 30 | ) 31 | 32 | sd_namespace = servicediscovery.PrivateDnsNamespace.from_private_dns_namespace_attributes( 33 | self, 34 | "SDNamespace", 35 | namespace_name=Fn.import_value('NSNAME'), 36 | namespace_arn=Fn.import_value('NSARN'), 37 | namespace_id=Fn.import_value('NSID') 38 | ) 39 | 40 | ecs_cluster = ecs.Cluster.from_cluster_attributes( 41 | self, 42 | "ECSCluster", 43 | cluster_name=Fn.import_value('ECSClusterName'), 44 | security_groups=[], 45 | vpc=vpc, 46 | default_cloud_map_namespace=sd_namespace 47 | ) 48 | 49 | services_sec_grp = ec2.SecurityGroup.from_security_group_id( 50 | self, 51 | "ServicesSecGrp", 52 | security_group_id=Fn.import_value('ServicesSecGrp') 53 | ) 54 | 55 | self.vpc = vpc 56 | self.sd_namespace = sd_namespace 57 | self.ecs_cluster = ecs_cluster 58 | self.services_sec_grp = services_sec_grp 59 | -------------------------------------------------------------------------------- /kubernetes/helm/ecsdemo-nodejs/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "ecsdemo-nodejs.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "ecsdemo-nodejs.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "ecsdemo-nodejs.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "ecsdemo-nodejs.labels" -}} 37 | helm.sh/chart: {{ include "ecsdemo-nodejs.chart" . }} 38 | {{ include "ecsdemo-nodejs.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "ecsdemo-nodejs.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "ecsdemo-nodejs.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "ecsdemo-nodejs.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "ecsdemo-nodejs.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /kubernetes/helm/ecsdemo-nodejs/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "ecsdemo-nodejs.fullname" . }} 5 | labels: 6 | {{- include "ecsdemo-nodejs.labels" . | nindent 4 }} 7 | spec: 8 | {{- if not .Values.autoscaling.enabled }} 9 | replicas: {{ .Values.replicaCount }} 10 | {{- end }} 11 | selector: 12 | matchLabels: 13 | {{- include "ecsdemo-nodejs.selectorLabels" . | nindent 6 }} 14 | template: 15 | metadata: 16 | {{- with .Values.podAnnotations }} 17 | annotations: 18 | {{- toYaml . | nindent 8 }} 19 | {{- end }} 20 | labels: 21 | {{- include "ecsdemo-nodejs.selectorLabels" . | nindent 8 }} 22 | spec: 23 | {{- with .Values.imagePullSecrets }} 24 | imagePullSecrets: 25 | {{- toYaml . | nindent 8 }} 26 | {{- end }} 27 | serviceAccountName: {{ include "ecsdemo-nodejs.serviceAccountName" . }} 28 | securityContext: 29 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 30 | containers: 31 | - name: {{ .Chart.Name }} 32 | securityContext: 33 | {{- toYaml .Values.securityContext | nindent 12 }} 34 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 35 | imagePullPolicy: {{ .Values.image.pullPolicy }} 36 | ports: 37 | - name: http 38 | containerPort: 3000 39 | protocol: TCP 40 | livenessProbe: 41 | httpGet: 42 | path: /nodejs 43 | port: http 44 | readinessProbe: 45 | httpGet: 46 | path: /nodejs 47 | port: http 48 | resources: 49 | {{- toYaml .Values.resources | nindent 12 }} 50 | {{- with .Values.nodeSelector }} 51 | nodeSelector: 52 | {{- toYaml . | nindent 8 }} 53 | {{- end }} 54 | {{- with .Values.affinity }} 55 | affinity: 56 | {{- toYaml . | nindent 8 }} 57 | {{- end }} 58 | {{- with .Values.tolerations }} 59 | tolerations: 60 | {{- toYaml . | nindent 8 }} 61 | {{- end }} 62 | -------------------------------------------------------------------------------- /kubernetes/helm/ecsdemo-nodejs/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for ecsdemo-nodejs. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: public.ecr.aws/aws-containers/ecsdemo-nodejs 9 | pullPolicy: IfNotPresent 10 | # Overrides the image tag whose default is the chart appVersion. 11 | tag: "latest" 12 | 13 | imagePullSecrets: [] 14 | nameOverride: "" 15 | fullnameOverride: "" 16 | 17 | serviceAccount: 18 | # Specifies whether a service account should be created 19 | create: true 20 | # Annotations to add to the service account 21 | annotations: {} 22 | # The name of the service account to use. 23 | # If not set and create is true, a name is generated using the fullname template 24 | name: "" 25 | 26 | podAnnotations: {} 27 | 28 | podSecurityContext: {} 29 | # fsGroup: 2000 30 | 31 | securityContext: {} 32 | # capabilities: 33 | # drop: 34 | # - ALL 35 | # readOnlyRootFilesystem: true 36 | # runAsNonRoot: true 37 | # runAsUser: 1000 38 | 39 | service: 40 | type: ClusterIP 41 | port: 80 42 | 43 | ingress: 44 | enabled: false 45 | className: "" 46 | annotations: {} 47 | # kubernetes.io/ingress.class: nginx 48 | # kubernetes.io/tls-acme: "true" 49 | hosts: 50 | - host: chart-example.local 51 | paths: 52 | - path: / 53 | pathType: ImplementationSpecific 54 | tls: [] 55 | # - secretName: chart-example-tls 56 | # hosts: 57 | # - chart-example.local 58 | 59 | resources: {} 60 | # We usually recommend not to specify default resources and to leave this as a conscious 61 | # choice for the user. This also increases chances charts run on environments with little 62 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 63 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 64 | # limits: 65 | # cpu: 100m 66 | # memory: 128Mi 67 | # requests: 68 | # cpu: 100m 69 | # memory: 128Mi 70 | 71 | autoscaling: 72 | enabled: false 73 | minReplicas: 1 74 | maxReplicas: 100 75 | targetCPUUtilizationPercentage: 80 76 | # targetMemoryUtilizationPercentage: 80 77 | 78 | nodeSelector: {} 79 | 80 | tolerations: [] 81 | 82 | affinity: {} 83 | -------------------------------------------------------------------------------- /startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | 5 | IP=$(ip route show |grep -o src.* |cut -f2 -d" ") 6 | # kubernetes sets routes differently -- so we will discover our IP differently 7 | if [[ ${IP} == "" ]]; then 8 | IP=$(hostname -i) 9 | fi 10 | SUBNET=$(echo ${IP} | cut -f1 -d.) 11 | NETWORK=$(echo ${IP} | cut -f3 -d.) 12 | 13 | case "${SUBNET}" in 14 | 10) 15 | orchestrator=ecs 16 | ;; 17 | 192) 18 | orchestrator=kubernetes 19 | ;; 20 | *) 21 | orchestrator=unknown 22 | ;; 23 | esac 24 | 25 | if [[ "${orchestrator}" == 'ecs' ]]; then 26 | case "${NETWORK}" in 27 | 100) 28 | zone=a 29 | color=Crimson 30 | ;; 31 | 101) 32 | zone=b 33 | color=CornflowerBlue 34 | ;; 35 | 102) 36 | zone=c 37 | color=LightGreen 38 | ;; 39 | *) 40 | zone=unknown 41 | color=Yellow 42 | ;; 43 | esac 44 | fi 45 | 46 | if [[ "${orchestrator}" == 'kubernetes' ]]; then 47 | if ((0<=${NETWORK} && ${NETWORK}<32)) 48 | then 49 | zone=a 50 | elif ((32<=${NETWORK} && ${NETWORK}<64)) 51 | then 52 | zone=b 53 | elif ((64<=${NETWORK} && ${NETWORK}<96)) 54 | then 55 | zone=c 56 | elif ((96<=${NETWORK} && ${NETWORK}<128)) 57 | then 58 | zone=a 59 | elif ((128<=${NETWORK} && ${NETWORK}<160)) 60 | then 61 | zone=b 62 | elif ((160<=${NETWORK})) 63 | then 64 | zone=c 65 | else 66 | zone=unknown 67 | fi 68 | fi 69 | 70 | if [[ ${orchestrator} == 'unknown' ]]; then 71 | zone=$(curl -m2 -s http://169.254.169.254/latest/dynamic/instance-identity/document | jq -r '.availabilityZone' | grep -o .$) 72 | fi 73 | 74 | # Am I on ec2 instances? 75 | if [[ ${zone} == "unknown" ]]; then 76 | zone=$(curl -m2 -s http://169.254.169.254/latest/dynamic/instance-identity/document | jq -r '.availabilityZone' | grep -o .$) 77 | fi 78 | 79 | # Still no luck? Perhaps we're running fargate! 80 | if [[ -z ${zone} ]]; then 81 | zone=$(curl -s ${ECS_CONTAINER_METADATA_URI_V4}/task | jq -r '.AvailabilityZone' | grep -o .$) 82 | fi 83 | 84 | export CODE_HASH="$(cat code_hash.txt)" 85 | export IP 86 | export AZ="${IP} in AZ-${zone}" 87 | 88 | # exec container command 89 | exec node server.js 90 | -------------------------------------------------------------------------------- /kubernetes/helm/ecsdemo-nodejs/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "ecsdemo-nodejs.fullname" . -}} 3 | {{- $svcPort := .Values.service.port -}} 4 | {{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} 5 | {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} 6 | {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} 7 | {{- end }} 8 | {{- end }} 9 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} 10 | apiVersion: networking.k8s.io/v1 11 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 12 | apiVersion: networking.k8s.io/v1beta1 13 | {{- else -}} 14 | apiVersion: extensions/v1beta1 15 | {{- end }} 16 | kind: Ingress 17 | metadata: 18 | name: {{ $fullName }} 19 | labels: 20 | {{- include "ecsdemo-nodejs.labels" . | nindent 4 }} 21 | {{- with .Values.ingress.annotations }} 22 | annotations: 23 | {{- toYaml . | nindent 4 }} 24 | {{- end }} 25 | spec: 26 | {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 27 | ingressClassName: {{ .Values.ingress.className }} 28 | {{- end }} 29 | {{- if .Values.ingress.tls }} 30 | tls: 31 | {{- range .Values.ingress.tls }} 32 | - hosts: 33 | {{- range .hosts }} 34 | - {{ . | quote }} 35 | {{- end }} 36 | secretName: {{ .secretName }} 37 | {{- end }} 38 | {{- end }} 39 | rules: 40 | {{- range .Values.ingress.hosts }} 41 | - host: {{ .host | quote }} 42 | http: 43 | paths: 44 | {{- range .paths }} 45 | - path: {{ .path }} 46 | {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} 47 | pathType: {{ .pathType }} 48 | {{- end }} 49 | backend: 50 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 51 | service: 52 | name: {{ $fullName }} 53 | port: 54 | number: {{ $svcPort }} 55 | {{- else }} 56 | serviceName: {{ $fullName }} 57 | servicePort: {{ $svcPort }} 58 | {{- end }} 59 | {{- end }} 60 | {{- end }} 61 | {{- end }} 62 | -------------------------------------------------------------------------------- /startup-cdk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | 5 | IP=$(ip route show |grep -o src.* |cut -f2 -d" ") 6 | # kubernetes sets routes differently -- so we will discover our IP differently 7 | if [[ ${IP} == "" ]]; then 8 | IP=$(hostname -i) 9 | fi 10 | SUBNET=$(echo ${IP} | cut -f1 -d.) 11 | NETWORK=$(echo ${IP} | cut -f3 -d.) 12 | 13 | case "${SUBNET}" in 14 | 10) 15 | orchestrator=ecs 16 | ;; 17 | 192) 18 | orchestrator=kubernetes 19 | ;; 20 | *) 21 | orchestrator=unknown 22 | ;; 23 | esac 24 | 25 | if [[ "${orchestrator}" == 'ecs' ]]; then 26 | case "${NETWORK}" in 27 | 100) 28 | zone=a 29 | color=Crimson 30 | ;; 31 | 101) 32 | zone=b 33 | color=CornflowerBlue 34 | ;; 35 | 102) 36 | zone=c 37 | color=LightGreen 38 | ;; 39 | *) 40 | zone=unknown 41 | color=Yellow 42 | ;; 43 | esac 44 | fi 45 | 46 | if [[ "${orchestrator}" == 'kubernetes' ]]; then 47 | if ((0<=${NETWORK} && ${NETWORK}<32)) 48 | then 49 | zone=a 50 | elif ((32<=${NETWORK} && ${NETWORK}<64)) 51 | then 52 | zone=b 53 | elif ((64<=${NETWORK} && ${NETWORK}<96)) 54 | then 55 | zone=c 56 | elif ((96<=${NETWORK} && ${NETWORK}<128)) 57 | then 58 | zone=a 59 | elif ((128<=${NETWORK} && ${NETWORK}<160)) 60 | then 61 | zone=b 62 | elif ((160<=${NETWORK})) 63 | then 64 | zone=c 65 | else 66 | zone=unknown 67 | fi 68 | fi 69 | 70 | if [[ ${orchestrator} == 'unknown' ]]; then 71 | zone=$(curl -m2 -s http://169.254.169.254/latest/dynamic/instance-identity/document | jq -r '.availabilityZone' | grep -o .$) 72 | fi 73 | 74 | # Am I on ec2 instances? 75 | if [[ ${zone} == "unknown" ]]; then 76 | zone=$(curl -m2 -s http://169.254.169.254/latest/dynamic/instance-identity/document | jq -r '.availabilityZone' | grep -o .$) 77 | fi 78 | 79 | # Still no luck? Perhaps we're running fargate! 80 | if [[ -z ${zone} ]]; then 81 | export AWS_DEFAULT_REGION=$REGION 82 | ip_addr=$(curl -m2 -s ${ECS_CONTAINER_METADATA_URI} | jq -r '.Networks[].IPv4Addresses[]') 83 | declare -a subnets=( $(aws ec2 describe-subnets | jq -r .Subnets[].CidrBlock| sed ':a;N;$!ba;s/\n/ /g') ) 84 | for sub in "${subnets[@]}"; do 85 | ip_match=$(echo -e "from netaddr import IPNetwork, IPAddress\nif IPAddress('$ip_addr') in IPNetwork('$sub'):\n print('true')" | python3) 86 | if [[ $ip_match == "true" ]];then 87 | zone=$(aws ec2 describe-subnets | jq -r --arg sub "$sub" '.Subnets[] | select(.CidrBlock==$sub) | .AvailabilityZone' | grep -o .$) 88 | fi 89 | done 90 | fi 91 | 92 | export CODE_HASH="$(cat code_hash.txt)" 93 | export IP 94 | export AZ="${IP} in AZ-${zone}" 95 | 96 | # exec container command 97 | exec node server.js 98 | -------------------------------------------------------------------------------- /cdk/cdk/nodejsservice.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from aws_cdk import ( 4 | Fn, 5 | CfnOutput, 6 | Duration, 7 | Stack, 8 | aws_appmesh, 9 | aws_ec2, 10 | aws_ecs, 11 | aws_iam, 12 | aws_logs, 13 | ) 14 | from constructs import Construct 15 | 16 | from cdk.baseplatform import BasePlatform 17 | 18 | 19 | class NodejsService(Stack): 20 | 21 | def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: 22 | super().__init__(scope, construct_id, **kwargs) 23 | 24 | base_platform = BasePlatform(self, self.stack_name) 25 | 26 | fargate_task_def = aws_ecs.TaskDefinition( 27 | self, 28 | "TaskDef", 29 | compatibility=aws_ecs.Compatibility.EC2_AND_FARGATE, 30 | cpu='256', 31 | memory_mib='512', 32 | # appmesh-proxy-uncomment 33 | # proxy_configuration=aws_ecs.AppMeshProxyConfiguration( 34 | # container_name="envoy", # App Mesh side card name that will proxy the requests 35 | # properties=aws_ecs.AppMeshProxyConfigurationProps( 36 | # app_ports=[3000], # nodejs application port 37 | # proxy_ingress_port=15000, # side card default config 38 | # proxy_egress_port=15001, # side card default config 39 | # # side card default config 40 | # egress_ignored_i_ps=["169.254.170.2", "169.254.169.254"], 41 | # ignored_uid=1337 # side card default config 42 | # ) 43 | # ) 44 | # appmesh-proxy-uncomment 45 | ) 46 | 47 | log_group = aws_logs.LogGroup( 48 | self, 49 | "ecsworkshopNodejs", 50 | retention=aws_logs.RetentionDays.ONE_WEEK 51 | ) 52 | 53 | container = fargate_task_def.add_container( 54 | "NodeServiceContainerDef", 55 | image=aws_ecs.ContainerImage.from_registry( 56 | "public.ecr.aws/aws-containers/ecsdemo-nodejs"), 57 | memory_reservation_mib=128, 58 | logging=aws_ecs.LogDriver.aws_logs( 59 | stream_prefix='/nodejs-container', 60 | log_group=log_group 61 | ), 62 | environment={ 63 | "REGION": os.getenv('AWS_DEFAULT_REGION') 64 | }, 65 | container_name="nodejs-app" 66 | ) 67 | 68 | container.add_port_mappings( 69 | aws_ecs.PortMapping( 70 | container_port=3000 71 | ) 72 | ) 73 | 74 | fargate_service = aws_ecs.FargateService( 75 | self, 76 | "NodejsFargateService", 77 | service_name='ecsdemo-nodejs', 78 | task_definition=fargate_task_def, 79 | cluster=base_platform.ecs_cluster, 80 | security_groups=[base_platform.services_sec_grp], 81 | desired_count=1, 82 | cloud_map_options=aws_ecs.CloudMapOptions( 83 | cloud_map_namespace=base_platform.sd_namespace, 84 | name='ecsdemo-nodejs' 85 | ) 86 | ) 87 | 88 | fargate_task_def.add_to_task_role_policy( 89 | aws_iam.PolicyStatement( 90 | actions=['ec2:DescribeSubnets'], 91 | resources=['*'] 92 | ) 93 | ) 94 | 95 | # Enable Service Autoscaling 96 | # autoscale = fargate_service.auto_scale_task_count( 97 | # min_capacity=3, 98 | # max_capacity=10 99 | # ) 100 | 101 | # autoscale.scale_on_cpu_utilization( 102 | # "CPUAutoscaling", 103 | # target_utilization_percent=50, 104 | # scale_in_cooldown=Duration.seconds(30), 105 | # scale_out_cooldown=Duration.seconds(30) 106 | # ) 107 | # self.autoscale = autoscale 108 | 109 | self.fargate_task_def = fargate_task_def 110 | self.log_group = log_group 111 | self.container = container 112 | self.fargate_service = fargate_service 113 | 114 | # App Mesh Implementation 115 | # self.appmesh() 116 | 117 | def appmesh(self): 118 | 119 | # Importing app mesh service 120 | mesh = aws_appmesh.Mesh.from_mesh_arn( 121 | self, 122 | "EcsWorkShop-AppMesh", 123 | mesh_arn=Fn.import_value("MeshArn") 124 | ) 125 | 126 | # Importing App Mesh virtual gateway 127 | mesh_vgw = aws_appmesh.VirtualGateway.from_virtual_gateway_attributes( 128 | self, 129 | "Mesh-VGW", 130 | mesh=mesh, 131 | virtual_gateway_name=Fn.import_value("MeshVGWName") 132 | ) 133 | 134 | # App Mesh virtual node configuration 135 | mesh_nodejs_vn = aws_appmesh.VirtualNode( 136 | self, 137 | "MeshNodeJsNode", 138 | mesh=mesh, 139 | virtual_node_name="nodejs", 140 | listeners=[aws_appmesh.VirtualNodeListener.http(port=3000)], 141 | service_discovery=aws_appmesh.ServiceDiscovery.cloud_map( 142 | self.fargate_service.cloud_map_service), 143 | access_log=aws_appmesh.AccessLog.from_file_path("/dev/stdout") 144 | ) 145 | 146 | # App Mesh envoy proxy container configuration 147 | envoy_container = self.fargate_task_def.add_container( 148 | "NodeJsServiceProxyContdef", 149 | image=aws_ecs.ContainerImage.from_registry( 150 | "public.ecr.aws/appmesh/aws-appmesh-envoy:v1.18.3.0-prod"), 151 | container_name="envoy", 152 | memory_reservation_mib=128, 153 | environment={ 154 | "REGION": os.getenv('AWS_DEFAULT_REGION'), 155 | "ENVOY_LOG_LEVEL": "trace", 156 | "ENABLE_ENVOY_STATS_TAGS": "1", 157 | # "ENABLE_ENVOY_XRAY_TRACING": "1", 158 | "APPMESH_RESOURCE_ARN": mesh_nodejs_vn.virtual_node_arn 159 | }, 160 | essential=True, 161 | logging=aws_ecs.LogDriver.aws_logs( 162 | stream_prefix='/mesh-envoy-container', 163 | log_group=self.log_group 164 | ), 165 | health_check=aws_ecs.HealthCheck( 166 | interval=Duration.seconds(5), 167 | timeout=Duration.seconds(10), 168 | retries=10, 169 | command=[ 170 | "CMD-SHELL", "curl -s http://localhost:9901/server_info | grep state | grep -q LIVE"], 171 | ), 172 | user="1337" 173 | ) 174 | 175 | envoy_container.add_ulimits(aws_ecs.Ulimit( 176 | hard_limit=15000, 177 | name=aws_ecs.UlimitName.NOFILE, 178 | soft_limit=15000 179 | ) 180 | ) 181 | 182 | # Primary container needs to depend on envoy before it can be reached out 183 | self.container.add_container_dependencies(aws_ecs.ContainerDependency( 184 | container=envoy_container, 185 | condition=aws_ecs.ContainerDependencyCondition.HEALTHY 186 | ) 187 | ) 188 | 189 | # Enable app mesh Xray observability 190 | # xray_container = self.fargate_task_def.add_container( 191 | # "NodeJsServiceXrayContdef", 192 | # image=aws_ecs.ContainerImage.from_registry( 193 | # "amazon/aws-xray-daemon"), 194 | # logging=aws_ecs.LogDriver.aws_logs( 195 | # stream_prefix='/xray-container', 196 | # log_group=self.log_group 197 | # ), 198 | # essential=True, 199 | # container_name="xray", 200 | # memory_reservation_mib=256, 201 | # user="1337" 202 | # ) 203 | 204 | # envoy_container.add_container_dependencies(aws_ecs.ContainerDependency( 205 | # container=xray_container, 206 | # condition=aws_ecs.ContainerDependencyCondition.START 207 | # ) 208 | # ) 209 | 210 | self.fargate_task_def.add_to_task_role_policy( 211 | aws_iam.PolicyStatement( 212 | actions=['ec2:DescribeSubnets'], 213 | resources=['*'] 214 | ) 215 | ) 216 | 217 | self.fargate_service.connections.allow_from_any_ipv4( 218 | port_range=aws_ec2.Port(protocol=aws_ec2.Protocol.TCP, 219 | string_representation="tcp_3000", 220 | from_port=3000, to_port=3000), 221 | description="Allow TCP connections on port 3000" 222 | ) 223 | 224 | # Adding policies to work with observability (xray and cloudwath) 225 | self.fargate_task_def.execution_role.add_managed_policy( 226 | aws_iam.ManagedPolicy.from_aws_managed_policy_name("AmazonEC2ContainerRegistryReadOnly")) 227 | self.fargate_task_def.execution_role.add_managed_policy( 228 | aws_iam.ManagedPolicy.from_aws_managed_policy_name("CloudWatchLogsFullAccess")) 229 | self.fargate_task_def.task_role.add_managed_policy( 230 | aws_iam.ManagedPolicy.from_aws_managed_policy_name("CloudWatchFullAccess")) 231 | # self.fargate_task_def.task_role.add_managed_policy(aws_iam.ManagedPolicy.from_aws_managed_policy_name("AWSXRayDaemonWriteAccess")) 232 | self.fargate_task_def.task_role.add_managed_policy( 233 | aws_iam.ManagedPolicy.from_aws_managed_policy_name("AWSAppMeshEnvoyAccess")) 234 | 235 | # Adding mesh virtual service 236 | self.mesh_nodejs_vs = aws_appmesh.VirtualService(self, "mesh-nodejs-vs", 237 | virtual_service_provider=aws_appmesh.VirtualServiceProvider.virtual_node( 238 | self.mesh_nodejs_vn), 239 | virtual_service_name="{}.{}".format( 240 | self.fargate_service.cloud_map_service.service_name, self.fargate_service.cloud_map_service.namespace.namespace_name) 241 | ) 242 | 243 | # Exporting CF (outputs) to make references from other cdk projects. 244 | CfnOutput(self, "MeshNodejsVSARN", 245 | value=self.mesh_nodejs_vs.virtual_service_arn, export_name="MeshNodejsVSARN") 246 | CfnOutput(self, "MeshNodeJsVSName", 247 | value=self.mesh_nodejs_vs.virtual_service_name, export_name="MeshNodeJsVSName") 248 | 249 | self.mesh = mesh 250 | self.mesh_vgw = mesh_vgw 251 | self.mesh_nodejs_vn = mesh_nodejs_vn 252 | self.envoy_container = envoy_container 253 | # self.xray_container = xray_container 254 | --------------------------------------------------------------------------------