├── docker ├── ssh-keyname.pem ├── custominit.sh ├── Dockerfile ├── custom-entrypoint.sh └── reconfigrs.sh ├── generate-keyfile.sh ├── LICENSE ├── README.md └── mongodb-aws-repliace-template.yaml /docker/ssh-keyname.pem: -------------------------------------------------------------------------------- 1 | should replace with your ssh-key content -------------------------------------------------------------------------------- /generate-keyfile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | openssl rand -base64 741 > docker/keyfile.pem -------------------------------------------------------------------------------- /docker/custominit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "RSINDEX"; 3 | if [ "$RS_INDEX" == "0" ]; then 4 | echo "rs index catched"; 5 | sleep 90 6 | echo "rs start initialize"; 7 | mongo "mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@$DB_HOST/admin?authSource=admin" --eval "cf={_id:\"rs01\", version:1, members:[{_id:0, priority:3},{_id:1, priority:2},{_id:2, priority:1}]}; cf.members[0].host=\"$DB_NAME-01.$SERVICE_DISCOVERY_NAME:27017\"; cf.members[1].host=\"$DB_NAME-02.$SERVICE_DISCOVERY_NAME:27017\"; cf.members[2].host=\"$DB_NAME-03.$SERVICE_DISCOVERY_NAME:27017\"; rs.initiate(cf);" 8 | fi 9 | 10 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mongo:5.0.16 2 | RUN apt-get update && apt-get install -y curl && apt-get install -y openssh-client && apt install -y iproute2 3 | COPY ssh-keyname.pem /sshdb-key.pem 4 | RUN chmod 400 /sshdb-key.pem 5 | COPY keyfile.pem /keyfile.pem 6 | COPY custominit.sh /docker-entrypoint-initdb.d/ 7 | RUN chmod a+x /docker-entrypoint-initdb.d/custominit.sh 8 | COPY custom-entrypoint.sh /usr/local/bin/ 9 | COPY reconfigrs.sh /usr/local/bin/ 10 | RUN chown mongodb:mongodb /keyfile.pem 11 | RUN chmod 400 /keyfile.pem 12 | RUN chmod a+x /usr/local/bin/custom-entrypoint.sh 13 | RUN chmod a+x /usr/local/bin/reconfigrs.sh 14 | ENTRYPOINT [ "custom-entrypoint.sh" ] 15 | EXPOSE 27017 16 | CMD ["mongod"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Cong Thang 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 | -------------------------------------------------------------------------------- /docker/custom-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo $VOLUME_ID 3 | HOST_IP=`cat /hostmetadata/hostlocalip | xargs` 4 | echo $HOST_IP 5 | echo "running reconfig" 6 | mongo "mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@$DB_HOST/admin?authSource=admin" --eval "cf=rs.conf();cf.members[$RS_INDEX].priority=0;rs.reconfig(cf,{force:true})" 7 | echo 'set priority to 0, sleep 60s..' 8 | sleep 30 9 | echo "running shutdown server" 10 | mongo "mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@$DB_HOST/admin?authSource=admin" --eval "db.shutdownServer({force:true})" 11 | # sleep 15 12 | # ssh -i /sshdb-key.pem -oStrictHostKeyChecking=no ec2-user@$HOST_IP "AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY aws ec2 detach-volume --force --volume-id $VOLUME_ID" 13 | # sleep 15 14 | currentInstanceType=`cat /hostmetadata/instanceType | xargs` 15 | if [ "$currentInstanceType" = "$INSTANCE_TYPE" ]; 16 | then 17 | echo "Using same instance type $currentInstanceType $INSTANCE_TYPE"; 18 | else 19 | echo "Using new instance type $currentInstanceType $INSTANCE_TYPE, Replacing first"; 20 | ssh -i /sshdb-key.pem -oStrictHostKeyChecking=no ec2-user@$HOST_IP "sudo shutdown now"; 21 | exit 1 22 | fi 23 | 24 | ssh -i /sshdb-key.pem -oStrictHostKeyChecking=no ec2-user@$HOST_IP "AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY aws ec2 attach-volume --volume-id $VOLUME_ID --instance-id \`ec2-metadata --instance-id | cut -d: -f2 | xargs \` --region $AWS_DEFAULT_REGION --device /dev/sdf || true && sleep 5 && sudo mount /dev/sdf /data/db"; 25 | # sleep 15 26 | # check if volume is mouted correctly 27 | InsatnceID=`ssh -i /sshdb-key.pem -oStrictHostKeyChecking=no ec2-user@$HOST_IP "ec2-metadata --instance-id | cut -d: -f2 | xargs"`; 28 | AttachedInstanceID=`ssh -i /sshdb-key.pem -oStrictHostKeyChecking=no ec2-user@$HOST_IP "AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY aws ec2 describe-volumes --volume-ids $VOLUME_ID --output text --query 'Volumes[0].Attachments[0].InstanceId'"` 29 | echo $InsatnceID $AttachedInstanceID 30 | if [ "$AttachedInstanceID" = "$InsatnceID" ]; 31 | then 32 | source reconfigrs.sh & 33 | source docker-entrypoint.sh "$@"; 34 | else 35 | echo "Volume not attached exit" 36 | exit 1 37 | fi -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MongoDB Automatic Scale on AWS 2 | AWS Cloudformation template for MongoDB with replica sets and Automatic Scale, support running on spot instance and ARM architecture. 3 | 4 | # Code of conduct 5 | ## Replica Resources: 6 | This deployment is creating A MongoDB cluster including 3 replicas. Each replica is set of resource: 7 | - 01 EC2 Autoscale Group (1 instance). 8 | - 01 Service on Elastic Container Services. 9 | - 01 EBS volume. 10 | - 01 Service Discovery Endpoint. 11 | ## Working flow 12 | - Everytime create or updating, scaling, the order is replica 03 > replica 02 > replica 01. 13 | - Priority of becoming Primary is Replica 01: 3, replica 02: 2, replica 01: 1. Replica 01 always try to to become primary at the end of updating. 14 | - Each replica is self repairing, when it fail or unhealthy, the replacement is initiated and bring it back to healthy. This is managed by ECS service deployment and EC2 autoscaling group. Even if you using Spot Instance (reduce up to 90% of cost), the Spot interuption is handled by autoscale group. 15 | 16 | ## Auto Scaling 17 | - A set of 2 alarms Low cpu and High cpu usage is added to Autoscale Group of Replica 01. When alarm fired, it is creating a SNS signal on SNS topic, SNS then trigger the Lambda scaling script to update cloudformation stack to update new instance type to higher if scale up or lower if scale down. The instance is replacing from replica 03 > 02 > 01, the scaling is ideally with zero downtime. 18 | 19 | # The Benefit 20 | - 1 click deploy a high availability MongoDB Cluster. 21 | - Control over your database data with your MongoDB instead of using other providers. 22 | - Reduce upto 90% of cost in comparison with using a managed mongodb from other providers. 23 | - Easy monitoring with ECS metrics and CloudWatch. 24 | - Keep everything safe under your private network on AWS Infrastructure. 25 | - Eliminate data transfer cost out of AWS Services. 26 | - Ability to use ARM architecture with higher Performance/price. 27 | - Ability to use Spot Instance (reduce up to 90% instance cost). 28 | - Ensuring high availability and auto scale at high demand spike. 29 | 30 | # Preparing for deployment 31 | 1. Create 3 EBS volume that will use for each mongo replica. The volume must be already formated (sudo mkfs -t xfs); 32 | 2. Create AWS ECS Cluster or use an existing cluster name; 33 | 3. Create a security group allow connecting between replica on port 27017 and to your application; 34 | 4. Create a Service Discovery private Endpoint on AWS cloudmap with your VPC or reuse an existing priate endpoint; 35 | 5. Create a Lamda IAM role with cloudwatch putlog permission; 36 | 6. Create an ssh key pair name or resue an existing ssh key name, download this keypair and copy to the ./docker folder; 37 | 7. Create or reuse an aws cli account (keyId and secret). This account must have permissions: 38 | - Attach/describe EBS volume to instance; 39 | - Refresh instance on EC2 autoscale group; 40 | - Update Cloudformation stack; 41 | 8. Create MongoDB keyfile for replica communication tls. 42 | 43 | ` 44 | chmod a+x 400 ./generate-keyfile.sh && ./generate-keyfile.sh 45 | ` 46 | 47 | 9. Build docker file and push to your docker repository. 48 | # Results 49 | Mongo Connection URI with replica will be: 50 | 51 | ` 52 | mongodb://mongouser:mongopass@DBNAME-01.service-discovery-name,DBNAME-02.service-discovery-name,DBNAME-03.service-discovery-name/?authSource=admin 53 | ` 54 | # Backup and restore 55 | Its recommended to use AWS backup plan to backup the First replica (replica 01) volume. Restore a backup just by put new volumeid to new deployment. 56 | 57 | -------------------------------------------------------------------------------- /docker/reconfigrs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "RECONFIG"; 3 | echo "RSINDEX":$RS_INDEX; 4 | sleep 30 5 | counter=0; 6 | currentInstanceType=`cat /hostmetadata/instanceType | xargs` 7 | lastInstanceType=`cat /data/db/lastInstanceType | xargs || echo noinstance` 8 | until [ $counter -gt 30 ] 9 | do 10 | echo "Checking ready $counter"; 11 | mongoready=`mongo "mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@$DB_HOST/admin?authSource=admin" --eval "db.runCommand(\"ping\").ok";`; 12 | readyText='UUID'; 13 | if [[ "$mongoready" == *"$readyText"* ]]; then 14 | echo "Mongo is ready! Configuring replica"; 15 | counter=100 16 | if [[ "$RS_INDEX" == "0" ]]; then 17 | echo "rs index catched"; 18 | if [[ "$NUMBER_REPLICA_INSTANCE" = "3" ]]; then 19 | mongo "mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@$DB_HOST/admin?authSource=admin" --eval "cf={_id:\"rs01\", version:1, members:[{_id:0, priority:3},{_id:1, priority:2},{_id:2, priority:1}]}; cf.members[0].host=\"$DB_NAME-01.$SERVICE_DISCOVERY_NAME:27017\"; cf.members[1].host=\"$DB_NAME-02.$SERVICE_DISCOVERY_NAME:27017\"; cf.members[2].host=\"$DB_NAME-03.$SERVICE_DISCOVERY_NAME:27017\"; rs.initiate(cf);"; 20 | sleep 5 21 | mongo "mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@$DB_HOST/admin?authSource=admin" --eval "cf=rs.conf(); cf.members[0].priority=3;cf.members[0].host=\"$DB_NAME-01.$SERVICE_DISCOVERY_NAME\"; cf.members[1].priority=2;cf.members[1].host=\"$DB_NAME-02.$SERVICE_DISCOVERY_NAME\"; cf.members[2].priority=1;cf.members[2].host=\"$DB_NAME-03.$SERVICE_DISCOVERY_NAME\";rs.reconfig(cf,{force:true});"; 22 | fi 23 | if [[ "$NUMBER_REPLICA_INSTANCE" = "2" ]]; then 24 | mongo "mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@$DB_HOST/admin?authSource=admin" --eval "cf={_id:\"rs01\", version:1, members:[{_id:0, priority:3},{_id:1, priority:2},{_id:2, priority:1}]}; cf.members[0].host=\"$DB_NAME-01.$SERVICE_DISCOVERY_NAME:27017\"; cf.members[1].host=\"$DB_NAME-02.$SERVICE_DISCOVERY_NAME:27017\"; rs.initiate(cf);"; 25 | sleep 5 26 | mongo "mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@$DB_HOST/admin?authSource=admin" --eval "cf=rs.conf(); cf.members=[{_id:0, host:\"$DB_NAME-01.$SERVICE_DISCOVERY_NAME\", priority:3},{_id:1, host: \"$DB_NAME-02.$SERVICE_DISCOVERY_NAME\" priority:2}]; rs.reconfig(cf,{force:true});"; 27 | fi 28 | if [[ "$NUMBER_REPLICA_INSTANCE" = "1" ]]; then 29 | mongo "mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@$DB_HOST/admin?authSource=admin" --eval "cf={_id:\"rs01\", version:1, members:[{_id:0, priority:3}]}; cf.members[0].host=\"$DB_NAME-01.$SERVICE_DISCOVERY_NAME:27017\"; rs.initiate(cf);"; 30 | sleep 5 31 | mongo "mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@$DB_HOST/admin?authSource=admin" --eval "cf=rs.conf(); cf.members=[{_id:0, host:\"$DB_NAME-01.$SERVICE_DISCOVERY_NAME\", priority:3}]; rs.reconfig(cf,{force:true});"; 32 | fi 33 | fi 34 | if [[ "$RS_INDEX" == "1" ]]; then 35 | if [[ "$currentInstanceType" != "$lastInstanceType" ]]; then 36 | echo $currentInstanceType > /data/db/lastInstanceType; 37 | mongo "mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@$DB_HOST/admin?authSource=admin" --eval "cf=rs.conf();cf.members[$RS_INDEX].priority=5;rs.reconfig(cf,{force:true})"; 38 | # sleep 30m 39 | # mongo "mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@$DB_HOST/admin?authSource=admin" --eval "cf=rs.conf();cf.members[$RS_INDEX].priority=2;rs.reconfig(cf,{force:true})"; 40 | else 41 | mongo "mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@$DB_HOST/admin?authSource=admin" --eval "cf=rs.conf();cf.members[$RS_INDEX].priority=$RS_PRIORITY;rs.reconfig(cf,{force:true})"; 42 | fi 43 | else 44 | mongo "mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@$DB_HOST/admin?authSource=admin" --eval "cf=rs.conf();cf.members[$RS_INDEX].priority=$RS_PRIORITY;rs.reconfig(cf,{force:true})"; 45 | fi 46 | fi 47 | ((counter++)); 48 | sleep 10 49 | done 50 | 51 | -------------------------------------------------------------------------------- /mongodb-aws-repliace-template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Parameters: 3 | DbName: 4 | Type: String 5 | Description: "Enter name of db eg: mongodb-replica-ebs-arm-01" 6 | DeployMode: 7 | Type: String 8 | Description: Select Number of Instance 9 | AllowedValues: 10 | - CREATE 11 | - RESTORE 12 | Default: RESTORE 13 | EnableAutoScale: 14 | Type: String 15 | Description: Select enable or disable autoscale 16 | AllowedValues: 17 | - "YES" 18 | - "NO" 19 | Default: "YES" 20 | EnableScaleIn: 21 | Type: String 22 | Description: Select enable or disable scale down 23 | AllowedValues: 24 | - "YES" 25 | - "NO" 26 | Default: "YES" 27 | LambdaExecutionRoleArn: 28 | Type: String 29 | Description: "Enter Lambda role that can create cloudwatch logs" 30 | Default: arn:aws:iam::YOURACCOUNTID:role/ROLENAME 31 | TemplateURL: 32 | Type: String 33 | Description: "Enter this Template URL you uploaded to your s3" 34 | Default: https://yourbucket.s3.ap-southeast-1.amazonaws.com/mongodb-aws-repliace-template.yaml 35 | VolumeId01: 36 | Type: String 37 | Description: "Enter Restore VolumeID 01" 38 | VolumeId02: 39 | Type: String 40 | Description: "Enter Restore VolumeID 02" 41 | VolumeId03: 42 | Type: String 43 | Description: "Enter Restore VolumeID 03" 44 | VolumeSize: 45 | Type: String 46 | Description: "Enter Size GiB" 47 | Default: 10 48 | CapabilityProvider01: 49 | Type: String 50 | Description: Select Capability Provider for node 01 51 | AllowedValues: 52 | - ONDEMAND 53 | - SPOT 54 | Default: SPOT 55 | CapabilityProvider02: 56 | Type: String 57 | Description: Select Capability Provider for node 02 58 | AllowedValues: 59 | - ONDEMAND 60 | - SPOT 61 | Default: SPOT 62 | CapabilityProvider03: 63 | Type: String 64 | Description: Select Capability Provider for node 03 65 | AllowedValues: 66 | - ONDEMAND 67 | - SPOT 68 | Default: SPOT 69 | MongoInitDbRootUsername: 70 | Type: String 71 | Description: "Enter MongoDb username to connect to" 72 | Default: mongouser 73 | MongoInitDbRootPassword: 74 | Type: String 75 | Description: "Enter your Mongodb master password" 76 | Default: mongopassword 77 | ContainerImageMongoUri: 78 | Type: String 79 | Description: "Enter custom Mongo Image uri build source" 80 | Default: your-mongo-build-uri 81 | AWSAccessKeyId: 82 | Type: String 83 | Description: "Enter your AWS_ACCESS_KEY_ID" 84 | Default: AWS_ACCESS_KEY_ID 85 | AWSSecretAccessId: 86 | Type: String 87 | Description: "Enter your AWS_SECRET_ACCESS_KEY" 88 | Default: AWS_SECRET_ACCESS_KEY 89 | AWSDefaultRegion: 90 | Type: String 91 | Description: "Enter AWS Default region" 92 | Default: ap-southeast-1 93 | ECSRoleArn: 94 | Type: String 95 | Description: Enter ECS Task excution Role Arn 96 | Default: "arn:aws:iam::YOURAWSID:role/ecsTaskExecutionRole" 97 | NumberOfReplicaInstance: 98 | Type: String 99 | Description: Select Number of Instance 100 | AllowedValues: 101 | - 0 102 | - 1 103 | - 3 104 | Default: 3 105 | InstanceType01: 106 | Type: String 107 | Description: Select instance type 108 | AllowedValues: 109 | - c6g.medium 110 | - c6g.large 111 | - c6gd.large 112 | - t3a.medium 113 | - c6gn.xlarge 114 | - m6a.large 115 | - m6g.medium 116 | - m6gd.medium 117 | - m7g.medium 118 | - m7gd.medium 119 | - m6g.large 120 | - m6gd.large 121 | - m7g.large 122 | - m7gd.large 123 | - m6g.xlarge 124 | - m6gd.xlarge 125 | - m7g.xlarge 126 | - m7gd.xlarge 127 | Default: m7g.medium 128 | InstanceType02: 129 | Type: String 130 | Description: Select instance type 131 | AllowedValues: 132 | - c6g.medium 133 | - c6g.large 134 | - c6gd.large 135 | - t3a.medium 136 | - c6gn.xlarge 137 | - m6a.large 138 | - m6g.medium 139 | - m6gd.medium 140 | - m7g.medium 141 | - m7gd.medium 142 | - m6g.large 143 | - m6gd.large 144 | - m7g.large 145 | - m7gd.large 146 | - m6g.xlarge 147 | - m6gd.xlarge 148 | - m7g.xlarge 149 | - m7gd.xlarge 150 | Default: m6g.medium 151 | InstanceType03: 152 | Type: String 153 | Description: Select instance type 154 | AllowedValues: 155 | - c6g.medium 156 | - c6g.large 157 | - c6gd.large 158 | - t3a.medium 159 | - c6gn.xlarge 160 | - m6a.large 161 | - m6g.medium 162 | - m6gd.medium 163 | - m7g.medium 164 | - m7gd.medium 165 | - m6g.large 166 | - m6gd.large 167 | - m7g.large 168 | - m7gd.large 169 | - m6g.xlarge 170 | - m6gd.xlarge 171 | - m7g.xlarge 172 | - m7gd.xlarge 173 | Default: m6gd.medium 174 | ClusterName: 175 | Type: String 176 | Description: Enter ECS Cluster Name 177 | SecurityGroupId: 178 | Type: String 179 | Description: Enter SecurityGroupId 180 | Default: sg-xxxxxxxx 181 | ECSIAMRoleArn: 182 | Type: String 183 | Description: Enter ECS instance role 184 | Default: arn:aws:iam::YOURACCOUNTID:instance-profile/ecsInstanceRole 185 | SecurityGroupService: 186 | Type: CommaDelimitedList 187 | Description: Enter service security group ids 188 | Default: "sg-xxxxxxxx" 189 | Subnets: 190 | Type: CommaDelimitedList 191 | Description: Enter Subnets ids 192 | Default: "subnet-xxxx" 193 | SubnetId: 194 | Type: String 195 | Description: Enter Subnets ids 196 | Default: subnet-xxxx 197 | SSHKeyName: 198 | Type: String 199 | Description: Enter SSH keypair name 200 | Default: ssh-keyname 201 | ServiceDiscoveryNameSpaceId: 202 | Type: String 203 | Description: Enter Service Name Space Id 204 | Default: ns-xxxxx 205 | ServiceDiscoveryNameSpace: 206 | Type: String 207 | Description: Enter Service Name Space 208 | Default: your-service-name-space 209 | AvailabilityZone: 210 | Type: String 211 | Description: Enter Availability zone 212 | Default: ap-southeast-1c 213 | ECSOptimizedAMIId: 214 | Type: String 215 | Description: Enter ECS optimized AMI ID 216 | Default: ami-02749f7a96386bacf 217 | ECSOptimizedAMIArm64Id: 218 | Type: String 219 | Description: Enter ECS optimized AMI ID 220 | Default: ami-0e94e3e3603496b1f 221 | Architecture: 222 | Type: String 223 | Description: Select Instance Platform architecture type 224 | AllowedValues: 225 | - x86 226 | - arm 227 | Default: arm 228 | Conditions: 229 | CreateReplica03: !Equals 230 | - !Ref NumberOfReplicaInstance 231 | - 3 232 | CreateReplica02: !Or 233 | - !Condition CreateReplica03 234 | - !Equals 235 | - !Ref NumberOfReplicaInstance 236 | - 2 237 | CreateReplica01: !Or 238 | - !Condition CreateReplica02 239 | - !Equals 240 | - !Ref NumberOfReplicaInstance 241 | - 1 242 | CreateNewMode: !Equals 243 | - !Ref DeployMode 244 | - CREATE 245 | RestoreMode: !Equals 246 | - !Ref DeployMode 247 | - RESTORE 248 | ConditionUseArm: !Equals 249 | - !Ref Architecture 250 | - "arm" 251 | UseOndemand01: !Equals 252 | - !Ref CapabilityProvider01 253 | - "ONDEMAND" 254 | UseOndemand02: !Equals 255 | - !Ref CapabilityProvider02 256 | - "ONDEMAND" 257 | UseOndemand03: !Equals 258 | - !Ref CapabilityProvider03 259 | - "ONDEMAND" 260 | Mappings: 261 | InstanceTypeMap: 262 | c6g.medium: 263 | Memory: 1900 264 | c6g.large: 265 | Memory: 3900 266 | c6gd.large: 267 | Memory: 3900 268 | t3a.medium: 269 | Memory: 1900 270 | c6gn.xlarge: 271 | Memory: 7600 272 | m6a.large: 273 | Memory: 7600 274 | m6g.medium: 275 | Memory: 3700 276 | m6gd.medium: 277 | Memory: 3700 278 | m7g.medium: 279 | Memory: 3700 280 | m7gd.medium: 281 | Memory: 3700 282 | m6g.large: 283 | Memory: 7600 284 | m6gd.large: 285 | Memory: 7600 286 | m7g.large: 287 | Memory: 7600 288 | m7gd.large: 289 | Memory: 7600 290 | m6g.xlarge: 291 | Memory: 15000 292 | m6gd.xlarge: 293 | Memory: 15000 294 | m7g.xlarge: 295 | Memory: 15000 296 | m7gd.xlarge: 297 | Memory: 15000 298 | Resources: 299 | Volume01: 300 | Type: AWS::EC2::Volume 301 | DeletionPolicy: Retain 302 | Properties: 303 | AvailabilityZone: !Ref AvailabilityZone 304 | Size: !Ref VolumeSize 305 | Tags: 306 | - Key: Service 307 | Value: !Sub Mongodb-${DbName} 308 | VolumeType: gp3 309 | Volume02: 310 | Type: AWS::EC2::Volume 311 | DeletionPolicy: Retain 312 | Properties: 313 | AvailabilityZone: !Ref AvailabilityZone 314 | Size: !Ref VolumeSize 315 | Tags: 316 | - Key: Service 317 | Value: !Sub Mongodb-${DbName} 318 | VolumeType: gp3 319 | Volume03: 320 | Type: AWS::EC2::Volume 321 | DeletionPolicy: Retain 322 | Properties: 323 | AvailabilityZone: !Ref AvailabilityZone 324 | Size: !Ref VolumeSize 325 | Tags: 326 | - Key: Service 327 | Value: !Sub Mongodb-${DbName} 328 | VolumeType: gp3 329 | LaunchTemplate01: 330 | Type: AWS::EC2::LaunchTemplate 331 | Properties: 332 | LaunchTemplateData: 333 | ImageId: !If 334 | - ConditionUseArm 335 | - !Ref ECSOptimizedAMIArm64Id 336 | - !Ref ECSOptimizedAMIId 337 | KeyName: !Ref SSHKeyName 338 | IamInstanceProfile: 339 | Arn: !Ref ECSIAMRoleArn 340 | NetworkInterfaces: 341 | - SubnetId: !Ref SubnetId 342 | DeviceIndex: 0 343 | Groups: 344 | - !Ref SecurityGroupId 345 | TagSpecifications: 346 | - ResourceType: instance 347 | Tags: 348 | - Key: ServiceType 349 | Value: MongoDB 350 | - Key: Service 351 | Value: !Ref DbName 352 | UserData: !If 353 | - CreateNewMode 354 | - Fn::Base64: !Sub | 355 | #!/bin/bash 356 | sudo yum install -y unzip; 357 | curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"; 358 | unzip awscliv2.zip; 359 | sudo ./aws/install; 360 | echo ECS_CLUSTER=${ClusterName} >> /etc/ecs/ecs.config; 361 | echo ECS_IMAGE_PULL_BEHAVIOR=always >> /etc/ecs/ecs.config; 362 | echo ECS_INSTANCE_ATTRIBUTES={\"${DbName}-rindex\": \"01\"} >> /etc/ecs/ecs.config; 363 | AWS_ACCESS_KEY_ID=${AWSAccessKeyId} AWS_SECRET_ACCESS_KEY=${AWSSecretAccessId} aws ec2 attach-volume --volume-id ${Volume01} --instance-id `ec2-metadata --instance-id | cut -d: -f2 | xargs` --region ${AWSDefaultRegion} --device /dev/sdf || true 364 | sleep 5 || true 365 | sudo mount /dev/sdf /data/db || true 366 | mkdir /ecsdata 367 | echo `ec2-metadata --local-ipv4 | cut -d: -f2 | xargs` > /ecsdata/hostlocalip 368 | echo ${InstanceType01} > /ecsdata/instanceType 369 | - Fn::Base64: !Sub | 370 | #!/bin/bash 371 | sudo yum install -y unzip; 372 | curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"; 373 | unzip awscliv2.zip; 374 | sudo ./aws/install; 375 | echo ECS_CLUSTER=${ClusterName} >> /etc/ecs/ecs.config; 376 | echo ECS_IMAGE_PULL_BEHAVIOR=always >> /etc/ecs/ecs.config; 377 | echo ECS_INSTANCE_ATTRIBUTES={\"${DbName}-rindex\": \"01\"} >> /etc/ecs/ecs.config; 378 | AWS_ACCESS_KEY_ID=${AWSAccessKeyId} AWS_SECRET_ACCESS_KEY=${AWSSecretAccessId} aws ec2 attach-volume --volume-id ${VolumeId01} --instance-id `ec2-metadata --instance-id | cut -d: -f2 | xargs` --region ${AWSDefaultRegion} --device /dev/sdf || true 379 | sleep 5 || true 380 | sudo mount /dev/sdf /data/db || true 381 | mkdir /ecsdata 382 | echo `ec2-metadata --local-ipv4 | cut -d: -f2 | xargs` > /ecsdata/hostlocalip 383 | echo ${InstanceType01} > /ecsdata/instanceType 384 | LaunchTemplateName: !Sub ${DbName}-MongoDB-EBS-LaunchTemplate-01 385 | LaunchTemplate02: 386 | Type: AWS::EC2::LaunchTemplate 387 | Properties: 388 | LaunchTemplateData: 389 | ImageId: !If 390 | - ConditionUseArm 391 | - !Ref ECSOptimizedAMIArm64Id 392 | - !Ref ECSOptimizedAMIId 393 | KeyName: !Ref SSHKeyName 394 | IamInstanceProfile: 395 | Arn: !Ref ECSIAMRoleArn 396 | NetworkInterfaces: 397 | - SubnetId: !Ref SubnetId 398 | DeviceIndex: 0 399 | Groups: 400 | - !Ref SecurityGroupId 401 | TagSpecifications: 402 | - ResourceType: instance 403 | Tags: 404 | - Key: ServiceType 405 | Value: MongoDB 406 | - Key: Service 407 | Value: !Ref DbName 408 | UserData: !If 409 | - CreateNewMode 410 | - Fn::Base64: !Sub | 411 | #!/bin/bash 412 | sudo yum install -y unzip; 413 | curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"; 414 | unzip awscliv2.zip; 415 | sudo ./aws/install; 416 | echo ECS_CLUSTER=${ClusterName} >> /etc/ecs/ecs.config; 417 | echo ECS_IMAGE_PULL_BEHAVIOR=always >> /etc/ecs/ecs.config; 418 | echo ECS_INSTANCE_ATTRIBUTES={\"${DbName}-rindex\": \"02\"} >> /etc/ecs/ecs.config; 419 | AWS_ACCESS_KEY_ID=${AWSAccessKeyId} AWS_SECRET_ACCESS_KEY=${AWSSecretAccessId} aws ec2 attach-volume --volume-id ${Volume02} --instance-id `ec2-metadata --instance-id | cut -d: -f2 | xargs` --region ${AWSDefaultRegion} --device /dev/sdf || true 420 | sleep 5 || true 421 | sudo mount /dev/sdf /data/db || true 422 | mkdir /ecsdata 423 | echo `ec2-metadata --local-ipv4 | cut -d: -f2 | xargs` > /ecsdata/hostlocalip 424 | echo ${InstanceType02} > /ecsdata/instanceType 425 | - Fn::Base64: !Sub | 426 | #!/bin/bash 427 | sudo yum install -y unzip; 428 | curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"; 429 | unzip awscliv2.zip; 430 | sudo ./aws/install; 431 | echo ECS_CLUSTER=${ClusterName} >> /etc/ecs/ecs.config; 432 | echo ECS_IMAGE_PULL_BEHAVIOR=always >> /etc/ecs/ecs.config; 433 | echo ECS_INSTANCE_ATTRIBUTES={\"${DbName}-rindex\": \"02\"} >> /etc/ecs/ecs.config; 434 | AWS_ACCESS_KEY_ID=${AWSAccessKeyId} AWS_SECRET_ACCESS_KEY=${AWSSecretAccessId} aws ec2 attach-volume --volume-id ${VolumeId02} --instance-id `ec2-metadata --instance-id | cut -d: -f2 | xargs` --region ${AWSDefaultRegion} --device /dev/sdf || true 435 | sleep 5 || true 436 | sudo mount /dev/sdf /data/db || true 437 | mkdir /ecsdata 438 | echo `ec2-metadata --local-ipv4 | cut -d: -f2 | xargs` > /ecsdata/hostlocalip 439 | echo ${InstanceType02} > /ecsdata/instanceType 440 | LaunchTemplateName: !Sub ${DbName}-MongoDB-EBS-LaunchTemplate-02 441 | LaunchTemplate03: 442 | Type: AWS::EC2::LaunchTemplate 443 | Properties: 444 | LaunchTemplateData: 445 | ImageId: !If 446 | - ConditionUseArm 447 | - !Ref ECSOptimizedAMIArm64Id 448 | - !Ref ECSOptimizedAMIId 449 | KeyName: !Ref SSHKeyName 450 | IamInstanceProfile: 451 | Arn: !Ref ECSIAMRoleArn 452 | NetworkInterfaces: 453 | - SubnetId: !Ref SubnetId 454 | DeviceIndex: 0 455 | Groups: 456 | - !Ref SecurityGroupId 457 | TagSpecifications: 458 | - ResourceType: instance 459 | Tags: 460 | - Key: ServiceType 461 | Value: MongoDB 462 | - Key: Service 463 | Value: !Ref DbName 464 | UserData: !If 465 | - CreateNewMode 466 | - Fn::Base64: !Sub | 467 | #!/bin/bash 468 | sudo yum install -y unzip; 469 | curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"; 470 | unzip awscliv2.zip; 471 | sudo ./aws/install; 472 | echo ECS_CLUSTER=${ClusterName} >> /etc/ecs/ecs.config; 473 | echo ECS_IMAGE_PULL_BEHAVIOR=always >> /etc/ecs/ecs.config; 474 | echo ECS_INSTANCE_ATTRIBUTES={\"${DbName}-rindex\": \"03\"} >> /etc/ecs/ecs.config; 475 | AWS_ACCESS_KEY_ID=${AWSAccessKeyId} AWS_SECRET_ACCESS_KEY=${AWSSecretAccessId} aws ec2 attach-volume --volume-id ${Volume03} --instance-id `ec2-metadata --instance-id | cut -d: -f2 | xargs` --region ${AWSDefaultRegion} --device /dev/sdf || true 476 | sleep 5 || true 477 | sudo mount /dev/sdf /data/db || true 478 | mkdir /ecsdata 479 | echo `ec2-metadata --local-ipv4 | cut -d: -f2 | xargs` > /ecsdata/hostlocalip 480 | echo ${InstanceType03} > /ecsdata/instanceType 481 | - Fn::Base64: !Sub | 482 | #!/bin/bash 483 | sudo yum install -y unzip; 484 | curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"; 485 | unzip awscliv2.zip; 486 | sudo ./aws/install; 487 | echo ECS_CLUSTER=${ClusterName} >> /etc/ecs/ecs.config; 488 | echo ECS_IMAGE_PULL_BEHAVIOR=always >> /etc/ecs/ecs.config; 489 | echo ECS_INSTANCE_ATTRIBUTES={\"${DbName}-rindex\": \"03\"} >> /etc/ecs/ecs.config; 490 | AWS_ACCESS_KEY_ID=${AWSAccessKeyId} AWS_SECRET_ACCESS_KEY=${AWSSecretAccessId} aws ec2 attach-volume --volume-id ${VolumeId03} --instance-id `ec2-metadata --instance-id | cut -d: -f2 | xargs` --region ${AWSDefaultRegion} --device /dev/sdf || true 491 | sleep 5 || true 492 | sudo mount /dev/sdf /data/db || true 493 | mkdir /ecsdata 494 | echo `ec2-metadata --local-ipv4 | cut -d: -f2 | xargs` > /ecsdata/hostlocalip 495 | echo ${InstanceType03} > /ecsdata/instanceType 496 | LaunchTemplateName: !Sub ${DbName}-MongoDB-EBS-LaunchTemplate-03 497 | AutoScaleGroup01: 498 | Type: AWS::AutoScaling::AutoScalingGroup 499 | Properties: 500 | AutoScalingGroupName: !Sub ${DbName}-MongoDB-EBS-AutoScaleGroup-01 501 | AvailabilityZones: 502 | - !Ref AvailabilityZone 503 | CapacityRebalance: true 504 | DesiredCapacity: !If 505 | - CreateReplica01 506 | - 1 507 | - 0 508 | MinSize: !If 509 | - CreateReplica01 510 | - 1 511 | - 0 512 | MaxSize: !If 513 | - CreateReplica01 514 | - 2 515 | - 0 516 | MixedInstancesPolicy: 517 | InstancesDistribution: 518 | OnDemandAllocationStrategy: prioritized 519 | OnDemandBaseCapacity: !If 520 | - UseOndemand01 521 | - 1 522 | - 0 523 | OnDemandPercentageAboveBaseCapacity: 0 524 | SpotAllocationStrategy: price-capacity-optimized 525 | LaunchTemplate: 526 | LaunchTemplateSpecification: 527 | LaunchTemplateId: !Ref LaunchTemplate01 528 | Version: !GetAtt LaunchTemplate01.LatestVersionNumber 529 | Overrides: 530 | - InstanceType: !Ref InstanceType01 531 | DependsOn: 532 | - Service02 533 | AutoScaleGroup02: 534 | Type: AWS::AutoScaling::AutoScalingGroup 535 | Properties: 536 | AutoScalingGroupName: !Sub ${DbName}-MongoDB-EBS-AutoScaleGroup-02 537 | AvailabilityZones: 538 | - !Ref AvailabilityZone 539 | CapacityRebalance: true 540 | DesiredCapacity: !If 541 | - CreateReplica02 542 | - 1 543 | - 0 544 | MinSize: !If 545 | - CreateReplica02 546 | - 1 547 | - 0 548 | MaxSize: !If 549 | - CreateReplica02 550 | - 2 551 | - 0 552 | MixedInstancesPolicy: 553 | InstancesDistribution: 554 | OnDemandAllocationStrategy: prioritized 555 | OnDemandBaseCapacity: !If 556 | - UseOndemand02 557 | - 1 558 | - 0 559 | OnDemandPercentageAboveBaseCapacity: 0 560 | SpotAllocationStrategy: price-capacity-optimized 561 | LaunchTemplate: 562 | LaunchTemplateSpecification: 563 | LaunchTemplateId: !Ref LaunchTemplate02 564 | Version: !GetAtt LaunchTemplate02.LatestVersionNumber 565 | Overrides: 566 | - InstanceType: !Ref InstanceType02 567 | DependsOn: 568 | - Service03 569 | AutoScaleGroup03: 570 | Type: AWS::AutoScaling::AutoScalingGroup 571 | Properties: 572 | AutoScalingGroupName: !Sub ${DbName}-MongoDB-EBS-AutoScaleGroup-03 573 | AvailabilityZones: 574 | - !Ref AvailabilityZone 575 | CapacityRebalance: true 576 | DesiredCapacity: !If 577 | - CreateReplica03 578 | - 1 579 | - 0 580 | MinSize: !If 581 | - CreateReplica03 582 | - 1 583 | - 0 584 | MaxSize: !If 585 | - CreateReplica03 586 | - 2 587 | - 0 588 | MixedInstancesPolicy: 589 | InstancesDistribution: 590 | OnDemandAllocationStrategy: prioritized 591 | OnDemandBaseCapacity: !If 592 | - UseOndemand03 593 | - 1 594 | - 0 595 | OnDemandPercentageAboveBaseCapacity: 0 596 | SpotAllocationStrategy: price-capacity-optimized 597 | LaunchTemplate: 598 | LaunchTemplateSpecification: 599 | LaunchTemplateId: !Ref LaunchTemplate03 600 | Version: !GetAtt LaunchTemplate03.LatestVersionNumber 601 | Overrides: 602 | - InstanceType: !Ref InstanceType03 603 | TaskDefinition01: 604 | Type: "AWS::ECS::TaskDefinition" 605 | Properties: 606 | ExecutionRoleArn: !Ref ECSRoleArn 607 | ContainerDefinitions: 608 | - Image: !Sub ${ContainerImageMongoUri} 609 | Cpu: 0 610 | HealthCheck: 611 | Command: 612 | - CMD-SHELL 613 | - !Sub mongo "mongodb://${MongoInitDbRootUsername}:${MongoInitDbRootPassword}@127.0.0.1:27017/admin?authSource=admin" --eval 'db.runCommand("ping").ok' 614 | Interval: 120 615 | Retries: 10 616 | StartPeriod: 300 617 | Timeout: 5 618 | Ulimits: 619 | - HardLimit: 100000 620 | Name: nofile 621 | SoftLimit: 100000 622 | Command: 623 | - --replSet 624 | - rs01 625 | - --keyFile 626 | - /keyfile.pem 627 | Environment: 628 | - Name: VOLUME_ID 629 | Value: !If 630 | - CreateNewMode 631 | - !Ref Volume01 632 | - !Ref VolumeId01 633 | - Name: INSTANCE_TYPE 634 | Value: !Ref InstanceType01 635 | - Name: AWS_ACCESS_KEY_ID 636 | Value: !Ref AWSAccessKeyId 637 | - Name: AWS_SECRET_ACCESS_KEY 638 | Value: !Ref AWSSecretAccessId 639 | - Name: AWS_DEFAULT_REGION 640 | Value: !Ref AWSDefaultRegion 641 | - Name: MONGO_INITDB_ROOT_USERNAME 642 | Value: !Ref MongoInitDbRootUsername 643 | - Name: MONGO_INITDB_ROOT_PASSWORD 644 | Value: !Ref MongoInitDbRootPassword 645 | - Name: DB_NAME 646 | Value: !Ref DbName 647 | - Name: DB_HOST 648 | Value: !Sub "${DbName}-01.${ServiceDiscoveryNameSpace}" 649 | - Name: RS_INDEX 650 | Value: 0 651 | - Name: RS_PRIORITY 652 | Value: 3 653 | - Name: NUMBER_REPLICA_INSTANCE 654 | Value: !Ref NumberOfReplicaInstance 655 | - Name: SERVICE_DISCOVERY_NAME 656 | Value: !Ref ServiceDiscoveryNameSpace 657 | MountPoints: 658 | - ContainerPath: /data/db 659 | SourceVolume: dbpath 660 | - ContainerPath: /hostmetadata 661 | SourceVolume: hostmetadata 662 | VolumesFrom: [] 663 | Essential: true 664 | Name: !Sub "${DbName}-01" 665 | LogConfiguration: 666 | LogDriver: awslogs 667 | Options: 668 | awslogs-group: /ecs/mongodb 669 | awslogs-region : !Ref AWSDefaultRegion 670 | awslogs-stream-prefix: !Sub "${DbName}-01" 671 | PlacementConstraints: [] 672 | TaskRoleArn: !Ref ECSRoleArn 673 | Family: !Sub "${DbName}-01" 674 | RequiresCompatibilities: 675 | - EC2 676 | NetworkMode: awsvpc 677 | Memory: !FindInMap [InstanceTypeMap, !Ref InstanceType01, "Memory"] 678 | Volumes: 679 | - Name : dbpath 680 | Host: 681 | SourcePath: /data/db 682 | - Name : hostmetadata 683 | Host: 684 | SourcePath: /ecsdata 685 | TaskDefinition02: 686 | Type: "AWS::ECS::TaskDefinition" 687 | Properties: 688 | ExecutionRoleArn: !Ref ECSRoleArn 689 | ContainerDefinitions: 690 | - Image: !Sub ${ContainerImageMongoUri} 691 | Cpu: 0 692 | HealthCheck: 693 | Command: 694 | - CMD-SHELL 695 | - !Sub mongo "mongodb://${MongoInitDbRootUsername}:${MongoInitDbRootPassword}@127.0.0.1:27017/admin?authSource=admin" --eval 'db.runCommand("ping").ok' 696 | Interval: 120 697 | Retries: 10 698 | StartPeriod: 300 699 | Timeout: 5 700 | Ulimits: 701 | - HardLimit: 100000 702 | Name: nofile 703 | SoftLimit: 100000 704 | Command: 705 | - --replSet 706 | - rs01 707 | - --keyFile 708 | - /keyfile.pem 709 | Environment: 710 | - Name: VOLUME_ID 711 | Value: !If 712 | - CreateNewMode 713 | - !Ref Volume02 714 | - !Ref VolumeId02 715 | - Name: INSTANCE_TYPE 716 | Value: !Ref InstanceType02 717 | - Name: AWS_ACCESS_KEY_ID 718 | Value: !Ref AWSAccessKeyId 719 | - Name: AWS_SECRET_ACCESS_KEY 720 | Value: !Ref AWSSecretAccessId 721 | - Name: AWS_DEFAULT_REGION 722 | Value: !Ref AWSDefaultRegion 723 | - Name: MONGO_INITDB_ROOT_USERNAME 724 | Value: !Ref MongoInitDbRootUsername 725 | - Name: MONGO_INITDB_ROOT_PASSWORD 726 | Value: !Ref MongoInitDbRootPassword 727 | - Name: DB_NAME 728 | Value: !Ref DbName 729 | - Name: DB_HOST 730 | Value: !Sub "${DbName}-02.${ServiceDiscoveryNameSpace}" 731 | - Name: RS_INDEX 732 | Value: 1 733 | - Name: RS_PRIORITY 734 | Value: 2 735 | - Name: NUMBER_REPLICA_INSTANCE 736 | Value: !Ref NumberOfReplicaInstance 737 | - Name: SERVICE_DISCOVERY_NAME 738 | Value: !Ref ServiceDiscoveryNameSpace 739 | MountPoints: 740 | - ContainerPath: /data/db 741 | SourceVolume: dbpath 742 | - ContainerPath: /hostmetadata 743 | SourceVolume: hostmetadata 744 | VolumesFrom: [] 745 | Essential: true 746 | Name: !Sub "${DbName}-02" 747 | LogConfiguration: 748 | LogDriver: awslogs 749 | Options: 750 | awslogs-group: /ecs/mongodb 751 | awslogs-region : !Ref AWSDefaultRegion 752 | awslogs-stream-prefix: !Sub "${DbName}-02" 753 | PlacementConstraints: [] 754 | TaskRoleArn: !Ref ECSRoleArn 755 | Family: !Sub "${DbName}-02" 756 | RequiresCompatibilities: 757 | - EC2 758 | NetworkMode: awsvpc 759 | Memory: !FindInMap [InstanceTypeMap, !Ref InstanceType02, "Memory"] 760 | Volumes: 761 | - Name : dbpath 762 | Host: 763 | SourcePath: /data/db 764 | - Name : hostmetadata 765 | Host: 766 | SourcePath: /ecsdata 767 | TaskDefinition03: 768 | Type: "AWS::ECS::TaskDefinition" 769 | Properties: 770 | ExecutionRoleArn: !Ref ECSRoleArn 771 | ContainerDefinitions: 772 | - Image: !Sub ${ContainerImageMongoUri} 773 | Cpu: 0 774 | HealthCheck: 775 | Command: 776 | - CMD-SHELL 777 | - !Sub mongo "mongodb://${MongoInitDbRootUsername}:${MongoInitDbRootPassword}@127.0.0.1:27017/admin?authSource=admin" --eval 'db.runCommand("ping").ok' 778 | Interval: 120 779 | Retries: 10 780 | StartPeriod: 300 781 | Timeout: 5 782 | Ulimits: 783 | - HardLimit: 100000 784 | Name: nofile 785 | SoftLimit: 100000 786 | Command: 787 | - --replSet 788 | - rs01 789 | - --keyFile 790 | - /keyfile.pem 791 | Environment: 792 | - Name: VOLUME_ID 793 | Value: !If 794 | - CreateNewMode 795 | - !Ref Volume03 796 | - !Ref VolumeId03 797 | - Name: INSTANCE_TYPE 798 | Value: !Ref InstanceType03 799 | - Name: AWS_ACCESS_KEY_ID 800 | Value: !Ref AWSAccessKeyId 801 | - Name: AWS_SECRET_ACCESS_KEY 802 | Value: !Ref AWSSecretAccessId 803 | - Name: AWS_DEFAULT_REGION 804 | Value: !Ref AWSDefaultRegion 805 | - Name: MONGO_INITDB_ROOT_USERNAME 806 | Value: !Ref MongoInitDbRootUsername 807 | - Name: MONGO_INITDB_ROOT_PASSWORD 808 | Value: !Ref MongoInitDbRootPassword 809 | - Name: DB_NAME 810 | Value: !Ref DbName 811 | - Name: DB_HOST 812 | Value: !Sub "${DbName}-03.${ServiceDiscoveryNameSpace}" 813 | - Name: RS_INDEX 814 | Value: 2 815 | - Name: RS_PRIORITY 816 | Value: 1 817 | - Name: NUMBER_REPLICA_INSTANCE 818 | Value: !Ref NumberOfReplicaInstance 819 | - Name: SERVICE_DISCOVERY_NAME 820 | Value: !Ref ServiceDiscoveryNameSpace 821 | MountPoints: 822 | - ContainerPath: /data/db 823 | SourceVolume: dbpath 824 | - ContainerPath: /hostmetadata 825 | SourceVolume: hostmetadata 826 | VolumesFrom: [] 827 | Essential: true 828 | Name: !Sub "${DbName}-03" 829 | LogConfiguration: 830 | LogDriver: awslogs 831 | Options: 832 | awslogs-group: /ecs/mongodb 833 | awslogs-region : !Ref AWSDefaultRegion 834 | awslogs-stream-prefix: !Sub "${DbName}-03" 835 | PlacementConstraints: [] 836 | TaskRoleArn: !Ref ECSRoleArn 837 | Family: !Sub "${DbName}-03" 838 | RequiresCompatibilities: 839 | - EC2 840 | NetworkMode: awsvpc 841 | Memory: !FindInMap [InstanceTypeMap, !Ref InstanceType03, "Memory"] 842 | Volumes: 843 | - Name : dbpath 844 | Host: 845 | SourcePath: /data/db 846 | - Name : hostmetadata 847 | Host: 848 | SourcePath: /ecsdata 849 | ServiceDiscovery01: 850 | Type: AWS::ServiceDiscovery::Service 851 | Properties: 852 | Description: "Service discovery 01" 853 | DnsConfig: 854 | DnsRecords: 855 | - TTL: 60 856 | Type: A 857 | Name: !Sub ${DbName}-01 858 | NamespaceId: !Ref ServiceDiscoveryNameSpaceId 859 | DependsOn: 860 | - ServiceDiscovery02 861 | ServiceDiscovery02: 862 | Type: AWS::ServiceDiscovery::Service 863 | Properties: 864 | Description: "Service discovery 02" 865 | DnsConfig: 866 | DnsRecords: 867 | - TTL: 60 868 | Type: A 869 | Name: !Sub ${DbName}-02 870 | NamespaceId: !Ref ServiceDiscoveryNameSpaceId 871 | DependsOn: 872 | - ServiceDiscovery03 873 | ServiceDiscovery03: 874 | Type: AWS::ServiceDiscovery::Service 875 | Properties: 876 | Description: "Service discovery 03" 877 | DnsConfig: 878 | DnsRecords: 879 | - TTL: 60 880 | Type: A 881 | Name: !Sub ${DbName}-03 882 | NamespaceId: !Ref ServiceDiscoveryNameSpaceId 883 | Service01: 884 | Type: "AWS::ECS::Service" 885 | Properties: 886 | Cluster: !Ref ClusterName 887 | DeploymentController: 888 | Type: ECS 889 | DesiredCount: !If 890 | - CreateReplica01 891 | - 1 892 | - 0 893 | LaunchType: EC2 894 | NetworkConfiguration: 895 | AwsvpcConfiguration: 896 | SecurityGroups: !Ref SecurityGroupService 897 | Subnets: !Ref Subnets 898 | DeploymentConfiguration: 899 | MinimumHealthyPercent: 0 900 | ServiceName: !Sub ${DbName}-01 901 | TaskDefinition: !Ref TaskDefinition01 902 | PlacementConstraints: 903 | - Expression: !Sub attribute:${DbName}-rindex==01 904 | Type: memberOf 905 | ServiceRegistries: 906 | - RegistryArn: !GetAtt ServiceDiscovery01.Arn 907 | DependsOn: 908 | - Service02 909 | - RefreshInstance01 910 | Service02: 911 | Type: "AWS::ECS::Service" 912 | Properties: 913 | Cluster: !Ref ClusterName 914 | DeploymentController: 915 | Type: ECS 916 | DesiredCount: !If 917 | - CreateReplica02 918 | - 1 919 | - 0 920 | LaunchType: EC2 921 | NetworkConfiguration: 922 | AwsvpcConfiguration: 923 | SecurityGroups: !Ref SecurityGroupService 924 | Subnets: !Ref Subnets 925 | DeploymentConfiguration: 926 | MinimumHealthyPercent: 0 927 | ServiceName: !Sub ${DbName}-02 928 | TaskDefinition: !Ref TaskDefinition02 929 | PlacementConstraints: 930 | - Expression: !Sub attribute:${DbName}-rindex==02 931 | Type: memberOf 932 | ServiceRegistries: 933 | - RegistryArn: !GetAtt ServiceDiscovery02.Arn 934 | DependsOn: 935 | - Service03 936 | - RefreshInstance02 937 | Service03: 938 | Type: "AWS::ECS::Service" 939 | Properties: 940 | Cluster: !Ref ClusterName 941 | DeploymentController: 942 | Type: ECS 943 | DesiredCount: !If 944 | - CreateReplica03 945 | - 1 946 | - 0 947 | LaunchType: EC2 948 | NetworkConfiguration: 949 | AwsvpcConfiguration: 950 | SecurityGroups: !Ref SecurityGroupService 951 | Subnets: !Ref Subnets 952 | DeploymentConfiguration: 953 | MinimumHealthyPercent: 0 954 | ServiceName: !Sub ${DbName}-03 955 | TaskDefinition: !Ref TaskDefinition03 956 | PlacementConstraints: 957 | - Expression: !Sub attribute:${DbName}-rindex==03 958 | Type: memberOf 959 | ServiceRegistries: 960 | - RegistryArn: !GetAtt ServiceDiscovery03.Arn 961 | DependsOn: 962 | - AutoScaleGroup03 963 | - Volume03 964 | - RefreshInstance03 965 | CPUAlarmHigh: 966 | DependsOn: 967 | - AutoScaleGroup01 968 | Type: 'AWS::CloudWatch::Alarm' 969 | Properties: 970 | AlarmDescription: Scale-up if CPU is greater than 90% for 10 minutes 971 | MetricName: CPUUtilization 972 | Namespace: AWS/EC2 973 | Statistic: Average 974 | Period: '300' 975 | EvaluationPeriods: '3' 976 | Threshold: '40' 977 | AlarmActions: 978 | - !Ref SNSTopicHigh 979 | Dimensions: 980 | - Name: AutoScalingGroupName 981 | Value: !Ref AutoScaleGroup01 982 | ComparisonOperator: GreaterThanThreshold 983 | CPUAlarmLow: 984 | DependsOn: 985 | - AutoScaleGroup01 986 | Type: 'AWS::CloudWatch::Alarm' 987 | Properties: 988 | AlarmDescription: Scale-down if CPU is less than 70% for 10 minutes 989 | MetricName: CPUUtilization 990 | Namespace: AWS/EC2 991 | Statistic: Average 992 | Period: '3600' 993 | EvaluationPeriods: '12' 994 | Threshold: '20' 995 | AlarmActions: 996 | - !Ref SNSTopicLow 997 | Dimensions: 998 | - Name: AutoScalingGroupName 999 | Value: !Ref AutoScaleGroup01 1000 | ComparisonOperator: LessThanThreshold 1001 | SNSTopicHigh: 1002 | Type: AWS::SNS::Topic 1003 | Properties: 1004 | DisplayName: !Sub ${DbName}-SNS-High 1005 | TopicName: !Sub ${DbName}-SNS-High 1006 | Subscription: 1007 | - 1008 | Endpoint: !GetAtt LamdaScaleHandleFunction.Arn 1009 | Protocol: lambda 1010 | SNSTopicLow: 1011 | Type: AWS::SNS::Topic 1012 | Properties: 1013 | DisplayName: !Sub ${DbName}-SNS-Low 1014 | TopicName: !Sub ${DbName}-SNS-Low 1015 | Subscription: 1016 | - 1017 | Endpoint: !GetAtt LamdaScaleHandleFunction.Arn 1018 | Protocol: lambda 1019 | LamdaScaleHandleFunction: 1020 | Type: AWS::Lambda::Function 1021 | Properties: 1022 | FunctionName: !Sub ${DbName}-lamda-scale-signal 1023 | Description: scale mongodb 1024 | Runtime: nodejs16.x 1025 | Handler: index.handler 1026 | Code: 1027 | ZipFile: !Sub | 1028 | const AWS = require("aws-sdk"); 1029 | var cloudformation = new AWS.CloudFormation({ 1030 | apiVersion: "2010-05-15", 1031 | accessKeyId: "${AWSAccessKeyId}", 1032 | secretAccessKey: "${AWSSecretAccessId}", 1033 | region: "${AWSDefaultRegion}", 1034 | }); 1035 | 1036 | exports.handler = async (event) => { 1037 | let snsMessage = JSON.parse(event.Records[0].Sns.Message); 1038 | console.log(snsMessage); 1039 | if (snsMessage.NewStateValue == "ALARM") { 1040 | console.log('ALARM fired') 1041 | var params = { 1042 | StackName: "${DbName}" 1043 | }; 1044 | return await new Promise(r => { 1045 | cloudformation.describeStacks(params, function (err, data) { 1046 | if (err) { 1047 | console.log(err, err.stack); // an error occurred 1048 | r(); 1049 | } 1050 | else { 1051 | let parameters = data.Stacks[0].Parameters; 1052 | let EnableAutoScale = parameters.find(p => p.ParameterKey == "EnableAutoScale").ParameterValue; 1053 | let EnableScaleIn = parameters.find(p => p.ParameterKey == "EnableScaleIn").ParameterValue; 1054 | let InstanceType01 = parameters.find(p => p.ParameterKey == "InstanceType01").ParameterValue; 1055 | let InstanceType02 = parameters.find(p => p.ParameterKey == "InstanceType02").ParameterValue; 1056 | let InstanceType03 = parameters.find(p => p.ParameterKey == "InstanceType03").ParameterValue; 1057 | console.log(EnableAutoScale, EnableScaleIn, InstanceType01, snsMessage.NewStateValue, snsMessage.Trigger.ComparisonOperator) 1058 | let mediumInstance01 = "m7g.medium"; 1059 | let mediumInstance02 = "m6g.medium"; 1060 | let mediumInstance03 = "m6gd.medium"; 1061 | let largeInstance01 = "m6g.large"; 1062 | let largeInstance02 = "m7g.large"; 1063 | let largeInstance03 = "m7gd.large"; 1064 | let xlargeInstance01 = "m6g.xlarge"; 1065 | let xlargeInstance02 = "m7g.xlarge"; 1066 | let xlargeInstance03 = "m7gd.xlarge"; 1067 | let xxlargeInstance01 = "m6g.2xlarge"; 1068 | let xxlargeInstance02 = "m7g.2xlarge"; 1069 | let xxlargeInstance03 = "m7gd.2xlarge"; 1070 | if (EnableAutoScale == "YES") { 1071 | let newScaleInstanceType01; 1072 | let newScaleInstanceType02; 1073 | let newScaleInstanceType03; 1074 | let needUpdate = false; 1075 | if (snsMessage.Trigger.ComparisonOperator == "GreaterThanThreshold") { 1076 | if (InstanceType01.indexOf('medium') > 0) { 1077 | newScaleInstanceType01 = largeInstance01 1078 | newScaleInstanceType02 = largeInstance02 1079 | newScaleInstanceType03 = largeInstance03 1080 | } 1081 | if (InstanceType01.indexOf('.large') > 0) { 1082 | newScaleInstanceType01 = xlargeInstance01 1083 | newScaleInstanceType02 = xlargeInstance02 1084 | newScaleInstanceType03 = xlargeInstance03 1085 | } 1086 | if (InstanceType01.indexOf('.xlarge') > 0) { 1087 | newScaleInstanceType01 = xxlargeInstance01 1088 | newScaleInstanceType02 = xxlargeInstance02 1089 | newScaleInstanceType03 = xxlargeInstance03 1090 | } 1091 | needUpdate = true; 1092 | } 1093 | if ( 1094 | snsMessage.Trigger.ComparisonOperator == "LessThanThreshold" && 1095 | EnableScaleIn == "YES" 1096 | ) { 1097 | if (InstanceType01.indexOf('.large') > 0) { 1098 | newScaleInstanceType01 = mediumInstance01 1099 | newScaleInstanceType02 = mediumInstance02 1100 | newScaleInstanceType03 = mediumInstance03 1101 | } 1102 | if (InstanceType01.indexOf('.xlarge') > 0) { 1103 | newScaleInstanceType01 = largeInstance01 1104 | newScaleInstanceType02 = largeInstance02 1105 | newScaleInstanceType03 = largeInstance03 1106 | } 1107 | if (InstanceType01.indexOf('.2xlarge') > 0) { 1108 | newScaleInstanceType01 = xlargeInstance01 1109 | newScaleInstanceType02 = xlargeInstance02 1110 | newScaleInstanceType03 = xlargeInstance03 1111 | } 1112 | needUpdate = true; 1113 | 1114 | 1115 | } 1116 | if(needUpdate){ 1117 | let newParams = parameters.map(el=>{ 1118 | if(el.ParameterKey == "InstanceType01"){ 1119 | el.ParameterValue = newScaleInstanceType01 1120 | } else if(el.ParameterKey == "InstanceType02"){ 1121 | el.ParameterValue = newScaleInstanceType02 1122 | } else if(el.ParameterKey == "InstanceType03"){ 1123 | el.ParameterValue = newScaleInstanceType03 1124 | } else { 1125 | delete el.ParameterValue 1126 | el.UsePreviousValue = true; 1127 | } 1128 | return el; 1129 | }) 1130 | 1131 | var updateParams = { 1132 | StackName: "${DbName}", 1133 | Parameters: newParams, 1134 | TemplateURL: "${TemplateURL}" 1135 | 1136 | } 1137 | // console.log(params); 1138 | cloudformation.updateStack(updateParams, function (err, data) { 1139 | if (err) { 1140 | console.log(err, err.stack); 1141 | } // an error occurred 1142 | else { 1143 | console.log(data); 1144 | } // successful response 1145 | r() 1146 | }); 1147 | } 1148 | 1149 | } 1150 | 1151 | 1152 | } 1153 | 1154 | }) 1155 | }) 1156 | 1157 | 1158 | } 1159 | 1160 | } 1161 | 1162 | MemorySize: 512 1163 | Timeout: 60 1164 | Role: !Ref LambdaExecutionRoleArn 1165 | LamdaScaleHanldeAlias: 1166 | Type: AWS::Lambda::Alias 1167 | Properties: 1168 | FunctionName: !Ref LamdaScaleHandleFunction 1169 | FunctionVersion: '$LATEST' 1170 | Name: live 1171 | 1172 | LambdaInvokePermissionHigh: 1173 | Type: AWS::Lambda::Permission 1174 | Properties: 1175 | Action: lambda:InvokeFunction 1176 | Principal: sns.amazonaws.com 1177 | SourceArn: !Ref SNSTopicHigh 1178 | FunctionName: !Ref LamdaScaleHandleFunction 1179 | LambdaInvokePermissionLow: 1180 | Type: AWS::Lambda::Permission 1181 | Properties: 1182 | Action: lambda:InvokeFunction 1183 | Principal: sns.amazonaws.com 1184 | SourceArn: !Ref SNSTopicLow 1185 | FunctionName: !Ref LamdaScaleHandleFunction 1186 | LamdaRefreshInstanceFunction: 1187 | Type: AWS::Lambda::Function 1188 | Properties: 1189 | FunctionName: !Sub ${DbName}-Refresh-Instance 1190 | Description: refersh autoscale function 1191 | Runtime: nodejs16.x 1192 | Handler: index.handler 1193 | Code: 1194 | ZipFile: !Sub | 1195 | const AWS = require("aws-sdk"); 1196 | var autoscaling = new AWS.AutoScaling({ 1197 | apiVersion: "2011-01-01", 1198 | accessKeyId: "${AWSAccessKeyId}", 1199 | secretAccessKey: "${AWSSecretAccessId}", 1200 | region: "${AWSDefaultRegion}", 1201 | }); 1202 | var response = require('cfn-response'); 1203 | 1204 | exports.handler = function (event, context) { 1205 | console.log(event); 1206 | 1207 | var responseData = {}; 1208 | responseData["Id"] = event.ResourceProperties.AutoScaleGroup; 1209 | if (event.RequestType == "Delete") { 1210 | response.send(event, context, "SUCCESS", responseData); 1211 | return; 1212 | } 1213 | var responseStatus = "FAILED"; 1214 | let autoscaleGroupName = event.ResourceProperties.AutoScaleGroup 1215 | let params = { 1216 | AutoScalingGroupName: autoscaleGroupName 1217 | } 1218 | autoscaling.startInstanceRefresh(params, function(err, data) { 1219 | if (err) { 1220 | console.log(err, err.stack); // an error occurred 1221 | responseData = { Error: err.toString() }; 1222 | } else { 1223 | console.log(data); 1224 | responseStatus = "SUCCESS" 1225 | } 1226 | if (event.RequestType == "Create") { 1227 | responseStatus = "SUCCESS" 1228 | } 1229 | response.send(event, context, responseStatus, responseData); 1230 | }); 1231 | } 1232 | 1233 | MemorySize: 512 1234 | Timeout: 15 1235 | Role: !Ref LambdaExecutionRoleArn 1236 | RefreshInstance01: 1237 | Type: Custom::RefereshInstance01 1238 | DependsOn: 1239 | - AutoScaleGroup01 1240 | - LamdaRefreshInstanceFunction 1241 | Version: "1.0" 1242 | Properties: 1243 | ServiceToken: !GetAtt LamdaRefreshInstanceFunction.Arn 1244 | AutoScaleGroup: !Ref AutoScaleGroup01 1245 | InstanceType: !Ref InstanceType01 1246 | RefreshInstance02: 1247 | Type: Custom::RefereshInstance02 1248 | DependsOn: 1249 | - AutoScaleGroup02 1250 | - LamdaRefreshInstanceFunction 1251 | Version: "1.0" 1252 | Properties: 1253 | ServiceToken: !GetAtt LamdaRefreshInstanceFunction.Arn 1254 | AutoScaleGroup: !Ref AutoScaleGroup02 1255 | InstanceType: !Ref InstanceType02 1256 | RefreshInstance03: 1257 | Type: Custom::RefereshInstance03 1258 | DependsOn: 1259 | - AutoScaleGroup03 1260 | - LamdaRefreshInstanceFunction 1261 | Version: "1.0" 1262 | Properties: 1263 | ServiceToken: !GetAtt LamdaRefreshInstanceFunction.Arn 1264 | AutoScaleGroup: !Ref AutoScaleGroup03 1265 | InstanceType: !Ref InstanceType03 1266 | --------------------------------------------------------------------------------