├── LICENSE ├── README.md ├── aerospike-existing-vpc.json ├── aerospike-new-vpc.json └── custom_namespace.conf /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Note : This repo is no longer maintained, please refer to [aws-quickstart/quickstart-aerospike](https://github.com/aws-quickstart/quickstart-aerospike). 2 | 3 | 4 | aws-cloudformation 5 | ================== 6 | 7 | AWS CloudFormation scripts used for [Amazon's Marketplace](https://aws.amazon.com/marketplace/pp/B00LW9382A/) 8 | 9 | Download a copy of this repo using either the Download link or use git clone. 10 | 11 | **Note** 12 | Starting with Aerospike 3.8.1, the Community Edition is configured to transmit anonymous usage statistics. 13 | We ask your help in making Aerospike better by leaving this feature enabled. You can learn about our goals, 14 | how we use the data, and how to disable the feature [here](http://www.aerospike.com/aerospike-telemetry/). 15 | 16 | 17 | ## Usage 18 | Go to AWS Cloud formation console at https://console.aws.amazon.com/cloudformation/home 19 | 20 | Change the region as per your requirement. 21 | 22 | **Uploading the template** 23 | 24 | 1. Choose "Create New Stack". 25 | 26 | 2. Upload one of the aerospike-\*-vpc.json template found in this repo. 27 | * aerospike-new-vpc.json is for creating a new VPC. This is the all-in-one self contained template. 28 | * aerospike-existing-json is for utilizing and deploying into an existing VPC. This is quicker due to not having to deploy a VPC. 29 | 30 | 3. Click Next 31 | 32 | **Template Parameters** 33 | 34 | 1. Give a name to your stack 35 | 36 | 2. Choose the Aerospike version you'd like to deploy. 37 | 38 | 3. Select if you'd like to publish base statistics to Cloudwatch. 39 | * Statistics are: Cluster Integrity, Free Memory, Free Disk and Number of Objects 40 | 41 | 4. Enter the size of an EBS volume you'd like to use. EBS volumes are always type gp2 and attached under /dev/sdg. Enter 0 to not use EBS volumes. An ephemeral volume is always available (if the instance type has them) under /dev/sdf 42 | 43 | 5. Choose an instance type from the ones available at 44 | http://aws.amazon.com/ec2/instance-types/ 45 | For more info on which instance to use, refer to Aerospike [AWS Capacity Planning](http://www.aerospike.com/docs/deploy_guides/aws/plan/). 46 | 47 | 6. Choose a valid existing keypair. If you don't have a keypair in AWS already, [create one first](http://docs.aws.amazon.com/gettingstarted/latest/wah/getting-started-create-key-pair.html) 48 | 49 | 7. *(Optional, but suggested)* Enter the URL where CloudFormation can download your customized namespace settings. This will be appended to the end of the aerospike.conf file as-is. If this option is defined, the default namespaces will be removed. 50 | * The simplest method is to upload a file to S3, then making the file public. The direct link is available via the properties tab of the S3 object. 51 | * Your custom namespace settings should take advantage of the ephemeral storage at /dev/sdf and your provisioned EBS volume at /dev/sdg. 52 | * Custom namespace file is everything under the namespace section of aerospike.conf file, including the namespace { } declaration. 53 | * See the included custom\_namespace.conf file as an example 54 | 55 | 8. Enter number of instances as required. 56 | 57 | 9. Enter the CIDR block from which you permit SSH access. You can use many online sites like [whatismyip](http://whatismyip.org/) to find out your IP. For single IP addresses appending /32 is required. Only 1 entry is permitted. If you'd like to give access to everyone/anyone, use 0.0.0.0/0 58 | 59 | 10. Choose if you'd like dedicated tenancy. There will be additional costs with this option. 60 | 61 | 11. Click Next 62 | 63 | **Options** (Optional) 64 | * Enter additional tags as desired. 65 | 66 | **Advanced** (Optional) 67 | * Enter advanced configurations as desired. 68 | * Click Next 69 | 70 | **Review** 71 | * Check "I acknowledge that this template might cause AWS CloudFormation to create IAM resources." This is required for cluster discovery. See Architecture for details. 72 | * Review and click Create. 73 | 74 | Go to your EC2 console and login to the instances using the IPs listed against the instances. 75 | 76 | Fire off some load using the [java benchmark client](http://www.aerospike.com/docs/client/java/benchmarks.html) included in the instances and watch the load with [AMC](http://www.aerospike.com/docs/amc/) 77 | 78 | ## System Access 79 | 80 | SSH access is enabled on the instances under the **ec2-user** user using the key-pair you've selected during stack creation. You are prompted to enter the IP (in CIDR format) from where you permit SSH access also during stack creation. 81 | 82 | ## Cost 83 | * EC2 instances 84 | * GP2 EBS volume per instance 85 | * Cloudwatch metrics per instance 86 | * 4 metrics x 5 minute polling ~= 35000 API requests/mo = $0.35 + $2 for 4 metrics ~= $2.35 87 | * SQS queue 88 | * 1 message on creation, 1 message per instance per scale-in. Fits into free tier. Would require a constant >2.5 scale-in events per second to exceed free tier. 89 | * Dedicated tenancy. See [Dedicated instance pricing](https://aws.amazon.com/ec2/purchasing-options/dedicated-instances/) on AWS. 90 | 91 | 92 | ## Architecture 93 | Cloudformation will create all the VPCs, Subnets, Security Groups, Autoscaling, etc... as separate entities just for the Aerospike cluster. 94 | 95 | Upon instance startup, instances will run a userdata script that will query AWS for instances based on the unique StackID tag CloudFormation generates. This functionality requires the ec2-describe instance policy and utilizes IAM roles for this. 96 | 97 | This script will then parse out the private IP addresses and modify the clustering section of aerospike configs with said IPs. 98 | 99 | This cluster is resiliant to any node being added/dropped. Additional nodes added with autoscaling will be able to automatically join the cluster. **Nodes leaving the cluster must be triggered by autoscaling to guarantee data consistency**. On scale-in, an SQS message will be sent with information on which node is being terminated. Each node polls SQS for its own message. Once the node finds an SQS message for itself, it first checks for data migrations. If no migrations are occuring, it will stop ASD and continues the autoscaling termination process. If there are data migrations occuring, it will interrupt the scale-in, leaving the instance running and cluster untouched, and wait for the next poll. **Only 1 node may scale-in at a time to ensure no data loss**. (Technically it's replication factor - 1) 100 | 101 | By default ping, Aerospike port 3000 and AMC port 8081 are open globally (0.0.0.0/0). You may want to lock this down to just your own IP range. 102 | 103 | ## Pricing 104 | The Aerospike AMI is a free subscription. You will be prompted to subscribe to the AMI before this CF template can be used. Pricing is dependant on the instance type used. Please see EC2 pricing [here](https://aws.amazon.com/ec2/pricing/). Cost will increase if launching more than 1 instance. 105 | 106 | # Clients 107 | An AMI pre-loaded with most clients is available for quick development uptake. 108 | 109 | Owner: 262212597706 110 | 111 | | Region | AMI | 112 | |----------------|--------------| 113 | | us-east-1 | ami-6cf30216 | 114 | | us-east-2 | ami-e74b6682 | 115 | | us-west-1 | ami-f092a390 | 116 | | us-west-2 | ami-f0669888 | 117 | | ca-central-1 | ami-3e75cc5a | 118 | | eu-central-1 | ami-d24afabd | 119 | | eu-west-1 | ami-ced911b7 | 120 | | eu-west-2 | ami-abbba8cf | 121 | | ap-northeast-1 | ami-1166ac77 | 122 | | ap-northeast-2 | ami-51ad773f | 123 | | ap-south-1 | ami-632e6f0c | 124 | | ap-southeast-1 | ami-2d6c1f4e | 125 | | ap-southeast-2 | ami-410aeb23 | 126 | | sa-east-1 | ami-1d4e3271 | 127 | -------------------------------------------------------------------------------- /aerospike-existing-vpc.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion" : "2010-09-09", 3 | "Description" : "Template to create an Aerospike cluster", 4 | "Parameters" : { 5 | "KeyPair" : { 6 | "Description" : "Name of the KeyPair that would be used to ssh into the instances", 7 | "Type" : "AWS::EC2::KeyPair::KeyName", 8 | "ConstraintDescription" : "Please specify the name of the keypair that you use to login" 9 | }, 10 | "VPC" : { 11 | "Description" : "The VPC to deploy into", 12 | "Type" : "AWS::EC2::VPC::Id" 13 | }, 14 | "VPCSubnet" : { 15 | "Description" : "Choose a subnet from the VPC selected above.", 16 | "Type" : "AWS::EC2::Subnet::Id" 17 | }, 18 | "Tenancy" : { 19 | "Description" : "The tenancy of your instance", 20 | "Type" : "String", 21 | "Default" : "default", 22 | "AllowedValues" : [ "default", "dedicated"] 23 | }, 24 | "NumberOfInstances" : { 25 | "Description" : "Number of instances in the cluster", 26 | "Type" : "Number", 27 | "Default" : "4", 28 | "MinValue" : "1", 29 | "MaxValue" : "15" 30 | }, 31 | "Cloudwatch" : { 32 | "Description" : "Add basic Aerospike metrics to Cloudwatch. Will incur Cloudwatch expenses ~ $24/mo/instance", 33 | "Type" : "String", 34 | "Default" : "no", 35 | "AllowedValues": [ "yes" , "no" ] 36 | }, 37 | "PermitSSH" : { 38 | "Description" : "CIDR block that's permitted to SSH to the Aerospike Cluster", 39 | "Type" : "String", 40 | "AllowedPattern" : "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/([0-9]|[1-2][0-9]|3[0-2]))$", 41 | "ConstraintDescription" : "Must be in CIDR notation. To specify one specifc IPv4 address, append /32. eg: 192.168.1.100/32" 42 | }, 43 | "InstanceType" : { 44 | "Description" : "Type of EC2 instance to launch.", 45 | "Type" : "String", 46 | "Default" : "t2.large", 47 | "AllowedValues" : [ "t2.micro", "t2.small", "t2.medium", "t2.large", 48 | "t3.micro", "t3.small", "t3.medium", "t3.large","t3.xlarge","t3.2xlarge", 49 | "m3.medium", "m3.large", "m3.xlarge", "m3.2xlarge", 50 | "m4.large", "m4.xlarge", "m4.2xlarge", "m4.4xlarge", "m4.10xlarge","m4.16xlarge", 51 | "m5.large", "m5.xlarge", "m5.2xlarge", "m5.4xlarge", "m5.12xlarge","m5.24xlarge", 52 | "c3.large", "c3.xlarge", "c3.2xlarge", "c3.4xlarge", "c3.8xlarge", 53 | "c4.large", "c4.xlarge", "c4.2xlarge", "c4.4xlarge", "c4.8xlarge", 54 | "c5.large", "c5.xlarge", "c5.2xlarge", "c5.4xlarge", "c5.9xlarge","c5.18xlarge", 55 | "r3.large", "r3.xlarge", "r3.2xlarge", "r3.4xlarge", "r3.8xlarge", 56 | "r4.large", "r4.xlarge", "r4.2xlarge", "r4.4xlarge", "r4.8xlarge","r4.16xlarge", 57 | "r5.large", "r5.xlarge", "r5.2xlarge", "r5.4xlarge", "r5.12xlarge","r5.24xlarge", 58 | "r5d.large", "r5d.xlarge", "r5d.2xlarge", "r5d.4xlarge", "r5d.12xlarge","r5d.24xlarge", 59 | "i2.xlarge", "i2.2xlarge", "i2.4xlarge", "i2.8xlarge", 60 | "i3.large", "i3.xlarge", "i3.2xlarge", "i3.4xlarge", "i3.8xlarge", "i3.16xlarge" 61 | ] 62 | }, 63 | "EBS" : { 64 | "Description" : "Size of EBS SSD volume in GB. The volume will attach under /dev/sdg. Limit of 16000. Enter 0 to not use EBS.", 65 | "Type" : "Number", 66 | "Default" : "50", 67 | "MinValue" : "0", 68 | "MaxValue" : "16000" 69 | }, 70 | "NamespaceFile" : { 71 | "Description" : "(Optional) Location of your namespace definition. Must be publically downloadable. Will append file directly to end of aerospike.conf", 72 | "Type" : "String" 73 | } 74 | }, 75 | "Mappings": { 76 | "RegionMap": { 77 | "us-east-1": { 78 | "name": "ami-0895d4c9d4bfdf18c" 79 | }, 80 | "us-east-2": { 81 | "name": "ami-0bcf02858b1d7b1c8" 82 | }, 83 | "us-west-1": { 84 | "name": "ami-04e7e941819d5b42e" 85 | }, 86 | "us-west-2": { 87 | "name": "ami-0215478c6825946ef" 88 | }, 89 | "ca-central-1": { 90 | "name": "ami-0fc779f68c06da678" 91 | }, 92 | "eu-west-1": { 93 | "name": "ami-044eab4bf7d36d03f" 94 | }, 95 | "eu-west-2": { 96 | "name": "ami-08fe2d186cf1d08bd" 97 | }, 98 | "eu-west-3": { 99 | "name": "ami-02abd0a6ee61ffba6" 100 | }, 101 | "eu-central-1": { 102 | "name": "ami-0a1b535c47aaff563" 103 | }, 104 | "ap-southeast-1": { 105 | "name": "ami-0fe59bd476d9008e7" 106 | }, 107 | "ap-southeast-2": { 108 | "name": "ami-006b2cde509ab4366" 109 | }, 110 | "ap-south-1": { 111 | "name": "ami-0629ad978b645ffb0" 112 | }, 113 | "ap-northeast-1": { 114 | "name": "ami-0f1725c179df38b12" 115 | }, 116 | "ap-northeast-2": { 117 | "name": "ami-0a293d9795a62ef08" 118 | }, 119 | "sa-east-1": { 120 | "name": "ami-051e8e91e5ee25f53" 121 | } 122 | } 123 | }, 124 | "Conditions" : { 125 | "NotUsingEBS" : { "Fn::Equals" : [ { "Ref" : "EBS" }, 0 ] } 126 | }, 127 | 128 | "Resources" : { 129 | "ClusterRole" : { 130 | "Type": "AWS::IAM::Role", 131 | "Properties": { 132 | "AssumeRolePolicyDocument": { 133 | "Version" : "2012-10-17", 134 | "Statement": [ { 135 | "Effect": "Allow", 136 | "Principal": { 137 | "Service": [ "ec2.amazonaws.com","autoscaling.amazonaws.com" ] 138 | }, 139 | "Action": [ "sts:AssumeRole" ] 140 | } ] 141 | }, 142 | "Path": "/", 143 | "Policies": [ { 144 | "PolicyName": "AerospikeClusterPolicy", 145 | "PolicyDocument": { 146 | "Version" : "2012-10-17", 147 | "Statement": [ { 148 | "Effect": "Allow", 149 | "Action": [ 150 | "ec2:DescribeInstances", 151 | "ec2:DescribeVpcAttribute" 152 | ], 153 | "Resource": "*" 154 | } ] 155 | } 156 | },{ 157 | "PolicyName": "AerospikeCloudWatchPolicy", 158 | "PolicyDocument" :{ 159 | "Version" : "2012-10-17", 160 | "Statement": [ { 161 | "Effect": "Allow", 162 | "Action": "cloudwatch:PutMetricData", 163 | "Resource": "*" 164 | } ] 165 | } 166 | },{ 167 | "PolicyName": "AerospikeSQSPolicy", 168 | "PolicyDocument" :{ 169 | "Version" : "2012-10-17", 170 | "Statement": [ { 171 | "Effect": "Allow", 172 | "Action": "sqs:*", 173 | "Resource": { "Fn::GetAtt" : ["MigrationSQS", "Arn"]} 174 | } ] 175 | } 176 | },{ 177 | "PolicyName": "AerospikeAutoScalingPolicy", 178 | "PolicyDocument" :{ 179 | "Version" : "2012-10-17", 180 | "Statement" : [ { 181 | "Effect": "Allow", 182 | "Action": "autoscaling:*", 183 | "Resource": "*" 184 | } ] 185 | } 186 | }] 187 | } 188 | }, 189 | "ClusterInstanceProfile": { 190 | "Type": "AWS::IAM::InstanceProfile", 191 | "Properties": { 192 | "Path": "/", 193 | "Roles": [ { 194 | "Ref": "ClusterRole" 195 | } ] 196 | } 197 | }, 198 | "MigrationSQS" : { 199 | "Type": "AWS::SQS::Queue", 200 | "Properties": { 201 | "ReceiveMessageWaitTimeSeconds": 10 202 | } 203 | }, 204 | "MigrationHook": { 205 | "Type": "AWS::AutoScaling::LifecycleHook", 206 | "DependsOn" : "MigrationSQS", 207 | "Properties": { 208 | "AutoScalingGroupName": { "Ref": "ClusterGroup" }, 209 | "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING", 210 | "NotificationTargetARN": { "Fn::GetAtt": [ "MigrationSQS", "Arn" ] }, 211 | "RoleARN": { "Fn::GetAtt": [ "ClusterRole", "Arn" ] } 212 | } 213 | }, 214 | "ClusterGroup" : { 215 | "Type" : "AWS::AutoScaling::AutoScalingGroup", 216 | "Properties" : { 217 | "LaunchConfigurationName" : { "Ref" : "LaunchConfig" }, 218 | "DesiredCapacity" : { "Ref" : "NumberOfInstances"}, 219 | "MinSize" : "1", 220 | "MaxSize" : "15", 221 | "VPCZoneIdentifier" : [{ "Ref" : "VPCSubnet" }], 222 | "Tags" : [ {"Key" : "StackID", "Value" : { "Ref" : "AWS::StackId"}, "PropagateAtLaunch" : "true" }, 223 | {"Key" : "Name" , "Value" : { "Ref" : "AWS::StackName"}, "PropagateAtLaunch" : "true" } ] 224 | } 225 | }, 226 | "LaunchConfig" : { 227 | "Type" : "AWS::AutoScaling::LaunchConfiguration", 228 | "Metadata" : { 229 | "AWS::CloudFormation::Init" : { 230 | "config" : { 231 | "files" : { 232 | "/tmp/aerospike_cluster" : { 233 | "content" : { "Fn::Join" : ["", [ 234 | "#!/bin/bash\n", 235 | "echo ClusterInstancesScriptStart > /var/log/awsuserdatascript\n", 236 | " PUBLICIP=$(curl http://169.254.169.254/latest/meta-data/public-ipv4)\n", 237 | " CONF=/etc/aerospike/aerospike.conf\n", 238 | " REGION=",{ "Ref" : "AWS::Region"},"\n", 239 | " sed -i \"/port 3000/a \\\t\taccess-address $PUBLICIP virtual\" $CONF\n", 240 | " ###Point to all instances using the mesh-address config option\n", 241 | " HOSTNAMES=$(aws ec2 describe-vpc-attribute --vpc-id=",{ "Ref":"VPC"}," --region=$REGION --attribute=enableDnsHostnames --output=text | grep ENABLEDNSHOSTNAMES | awk '{print $2}')\n", 242 | " sleep 60 # wait for AWS to provision \n", 243 | " if [[ \"$HOSTNAMES\" == \"True\" ]]; then\n", 244 | " PRIVATEIP=$(aws ec2 describe-instances --filter Name=tag-key,Values=StackID Name=tag-value,Values=", { "Ref" : "AWS::StackId" }," --output=text --region=$REGION | grep PRIVATEIPADDRESSES | awk '{print $4}') \n", 245 | " else PRIVATEIP=$(aws ec2 describe-instances --filter Name=tag-key,Values=StackID Name=tag-value,Values=", { "Ref" : "AWS::StackId" }," --output=text --region=$REGION | grep PRIVATEIPADDRESSES | awk '{print $3}') \n", 246 | " fi\n", 247 | " echo $PRIVATEIP >> /var/log/awsuserdatascript\n", 248 | " sed -i '/.*mesh-seed-address-port/d' $CONF\n", 249 | " for i in $PRIVATEIP; do ", 250 | " sed -i \"/interval/i \\\t\tmesh-seed-address-port $i 3002\" $CONF; done\n", 251 | " CODE=$(curl -Is ",{ "Ref" :"NamespaceFile" }," | head -n 1 | cut -d$' ' -f2)\n", 252 | " if [ \"$CODE\" != \"200\" ]; then echo 'Namespace File not found' >> /var/log/awsuserdatascript\n", 253 | " else sed -i '/namespace test/,$d' $CONF\n", 254 | " curl -s ",{ "Ref" :"NamespaceFile" }," >> $CONF; fi\n", 255 | " /etc/init.d/aerospike start\n", 256 | " /etc/init.d/amc start\n", 257 | "echo OtherInstancesScriptFinish >> /var/log/awsuserdatascript\n", 258 | "(crontab -l 2>/dev/null; echo '*/5 * * * * /opt/aerospike/poll_sqs') | crontab -\n", 259 | " if [[ \"",{ "Ref" : "Cloudwatch" },"\" == \"yes\" ]]; then\n", 260 | " (crontab -l 2>/dev/null; echo '*/5 * * * * /opt/aerospike/cloudwatch') | crontab -; fi\n" 261 | ] ] }, 262 | "mode" : "000744", 263 | "owner" : "root", 264 | "group" : "root" 265 | }, 266 | "/opt/aerospike/cloudwatch" : { 267 | "content" : { "Fn::Join" : ["", [ 268 | "#!/bin/bash\n", 269 | "METRICS=$(asinfo -v stats -l)\n", 270 | "NAMESPACE=aerospike\n", 271 | "REGION=",{ "Ref" : "AWS::Region" },"\n", 272 | "INSTANCE=$(curl 169.254.169.254/latest/meta-data/instance-id)\n", 273 | "CLUSTER=",{ "Ref" : "AWS::StackId" },"\n", 274 | "for L in $METRICS; do\n", 275 | " LINE=(${L//=/ })\n", 276 | " case ${LINE[0]} in\n", 277 | " cluster_integrity)\n", 278 | " if [[ ${LINE[1]} != \"true\" ]]; then\n", 279 | " INTEGRITY_ERROR=1\n", 280 | " else\n", 281 | " INTEGRITY_ERROR=0\n", 282 | " fi\n", 283 | " ;;\n", 284 | " total-bytes-memory)\n", 285 | " TBM=${LINE[1]}\n", 286 | " ;;\n", 287 | " used-bytes-memory)\n", 288 | " UBM=${LINE[1]}\n", 289 | " ;;\n", 290 | " total-bytes-disk)\n", 291 | " TBD=${LINE[1]}\n", 292 | " ;;\n", 293 | " used-bytes-disk)\n", 294 | " UBD=${LINE[1]}\n", 295 | " ;;\n", 296 | " objects)\n", 297 | " OBJECTS=${LINE[1]}\n", 298 | " ;;\n", 299 | " *)\n", 300 | " continue\n", 301 | " ;;\n", 302 | " esac\n", 303 | "done\n", 304 | "FM=$(expr $TBM - $UBM)\n", 305 | "FD=$(expr $TBD - $UBD)\n", 306 | "# Submit metrics\n", 307 | "aws cloudwatch --region $REGION put-metric-data --dimensions Cluster=$CLUSTER,Instance=$INSTANCE --namespace $NAMESPACE --value $INTEGRITY_ERROR --metric-name 'Cluster Integrity'\n", 308 | "aws cloudwatch --region $REGION put-metric-data --dimensions Cluster=$CLUSTER,Instance=$INSTANCE --namespace $NAMESPACE --value $FM --metric-name 'Free Memory' --unit 'Bytes'\n", 309 | "aws cloudwatch --region $REGION put-metric-data --dimensions Cluster=$CLUSTER,Instance=$INSTANCE --namespace $NAMESPACE --value $FD --metric-name 'Free Disk' --unit 'Bytes'\n", 310 | "aws cloudwatch --region $REGION put-metric-data --dimensions Cluster=$CLUSTER,Instance=$INSTANCE --namespace $NAMESPACE --value $OBJECTS --metric-name 'Number of Objects' --unit 'Count'\n" 311 | ] ] }, 312 | "mode" : "000744", 313 | "owner" : "root", 314 | "group" : "root" 315 | }, 316 | "/opt/aerospike/poll_sqs" : { 317 | "content" : { "Fn::Join" : [ "" , [ 318 | "#!/bin/bash\n", 319 | "# This script will prevent autoscaling from terminating\n", 320 | "# this instance until ASD migrations are completed\n", 321 | "set -e\n", 322 | "MYIP=$(curl 169.254.169.254/latest/meta-data/local-ipv4)\n", 323 | "MYNODE=$(curl 169.254.169.254/latest/meta-data/instance-id)\n", 324 | "REGION='",{ "Ref" : "AWS::Region"},"' # CFT\n", 325 | "QUEUE='",{ "Ref" : "MigrationSQS"},"' # CFT\n", 326 | "HOSTNAMES=$(aws ec2 describe-vpc-attribute --vpc-id=",{ "Ref":"VPC"}," --region=$REGION --attribute=enableDnsHostnames --output=text | grep ENABLEDNSHOSTNAMES | awk '{print $2}')\n", 327 | "if [[ \"$HOSTNAMES\" == \"True\" ]]; then\n", 328 | " CLUSTER=$(aws ec2 describe-instances --filter Name=tag-key,Values=StackID Name=tag-value,Values=",{ "Ref":"AWS::StackId"}," --output=text --region=$REGION | grep PRIVATEIPADDRESSES | awk '{print $4}')\n", 329 | "else CLUSTER=$(aws ec2 describe-instances --filter Name=tag-key,Values=StackID Name=tag-value,Values=",{ "Ref":"AWS::StackId"}," --output=text --region=$REGION | grep PRIVATEIPADDRESSES | awk '{print $3}')\n", 330 | "fi\n", 331 | "# Find SQS message with termination message\n", 332 | "FOUND=false\n", 333 | "MESSAGE=$(aws sqs receive-message --region $REGION --queue-url $QUEUE --wait-time-seconds 10 --visibility-timeout 2 )\n", 334 | "BODY=$(echo $MESSAGE | jq '.Messages[0] .Body')\n", 335 | "RECEIPT=$(echo $MESSAGE | jq --raw-output '.Messages[0] .ReceiptHandle')\n", 336 | "LIFECYCLE=$(eval echo $BODY | jq --raw-output '.LifecycleTransition')\n", 337 | "INSTANCE=$(eval echo $BODY | jq --raw-output '.EC2InstanceId')\n", 338 | "if [[ \"$LIFECYCLE\" == \"autoscaling:EC2_INSTANCE_TERMINATING\" ]] && [[ \"$INSTANCE\" == \"$MYNODE\" ]]; then\n", 339 | " TOKEN=$(eval echo $BODY | jq --raw-output '.LifecycleActionToken')\n", 340 | " HOOK=$(eval echo $BODY | jq --raw-output '.LifecycleHookName')\n", 341 | " ASG=$(eval echo $BODY | jq --raw-output '.AutoScalingGroupName')\n", 342 | " FOUND=true\n", 343 | " aws sqs delete-message --region $REGION --queue-url $QUEUE --receipt-handle $RECEIPT\n", 344 | "fi\n", 345 | "# If not not found, exit\n", 346 | "if [[ $FOUND == false ]]; then\n", 347 | " exit 0\n", 348 | "fi\n", 349 | "# stop aerospike\n", 350 | "/etc/init.d/aerospike stop\n", 351 | "# give time for cluster to react\n", 352 | "sleep 10\n", 353 | "# Find first node that's not myself\n", 354 | "for I in $CLUSTER; do\n", 355 | " if [[ $I == $MYIP ]]; then\n", 356 | " continue;\n", 357 | " fi\n", 358 | " NODE=$I\n", 359 | " break\n", 360 | "done\n", 361 | "# Grab migration info\n", 362 | "MIGRATIONS=$(asadm -h $NODE -e 'show statistics namespace' | grep migrate-[rt]x-partitions-remaining | awk '{print !$1}')\n", 363 | "DONE=true\n", 364 | "# check every node's migration status\n", 365 | "for STAT in $MIGRATIONS; do\n", 366 | " if [[ \"$STAT\" != '0' ]]; then\n", 367 | " $DONE=false\n", 368 | " break;\n", 369 | " fi\n", 370 | "done\n", 371 | "# if migrations not done, pause ASG actions. Otherwise, continue autoscaling termination.\n", 372 | "if [[ $DONE == false ]]; then\n", 373 | " aws autoscaling record-lifecycle-action-heartbeat --region $REGION --lifecycle-action-token $TOKEN --auto-scaling-group-name $ASG --lifecycle-hook-name $HOOK\n", 374 | " else\n", 375 | " aws autoscaling complete-lifecycle-action --region $REGION --lifecycle-action-token $TOKEN --lifecycle-hook-name $HOOK --auto-scaling-group-name $ASG --lifecycle-action-result CONTINUE\n", 376 | "fi\n" 377 | ] ] }, 378 | "mode" : "000744", 379 | "owner" : "root", 380 | "group" : "root" 381 | } 382 | }, 383 | "commands" : { 384 | "01_form_asd_cluster" : { 385 | "command" : "/tmp/aerospike_cluster", 386 | "cwd" : "/tmp" 387 | } 388 | } 389 | } 390 | } 391 | }, 392 | "Properties" : { 393 | "InstanceType" : { "Ref" : "InstanceType"}, 394 | "KeyName" : { "Ref" : "KeyPair" }, 395 | "BlockDeviceMappings" : { "Fn::If" : [ "NotUsingEBS", 396 | { "Ref" : "AWS::NoValue" }, 397 | [ { 398 | "DeviceName" : "/dev/sdg", 399 | "Ebs" : { "VolumeSize" : {"Ref" : "EBS" }, 400 | "VolumeType" : "gp2" } 401 | } ] 402 | ] }, 403 | "IamInstanceProfile" : { "Ref" : "ClusterInstanceProfile" }, 404 | "ImageId" : { "Fn::FindInMap" : [ "RegionMap", {"Ref" : "AWS::Region"} , "name"] }, 405 | "AssociatePublicIpAddress" : "true", 406 | "PlacementTenancy" : { "Ref" : "Tenancy" }, 407 | "SecurityGroups" : [ { "Fn::GetAtt" : [ "InstanceSecurityGroup", "GroupId" ] } ], 408 | "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [ 409 | "#!/bin/bash -xe\n", 410 | "yum update -y aws-cfn-bootstrap\n", 411 | "yum install -y jq\n", 412 | "/opt/aws/bin/cfn-init -v ", 413 | " --stack ", { "Ref" : "AWS::StackName" }, 414 | " --resource LaunchConfig ", 415 | " --region ", { "Ref" : "AWS::Region" }, "\n", 416 | 417 | "/opt/aws/bin/cfn-signal -e $? ", 418 | " --stack ", { "Ref" : "AWS::StackName" }, 419 | " --resource ClusterGroup ", 420 | " --region ", { "Ref" : "AWS::Region" }, "\n" 421 | ] ] } 422 | } 423 | } 424 | }, 425 | "InstanceSecurityGroup" : { 426 | "Type" : "AWS::EC2::SecurityGroup", 427 | "Properties" : { 428 | "GroupDescription" : "Enable ports to access Aerospike", 429 | "VpcId" : { "Ref" : "VPC" }, 430 | "SecurityGroupIngress" : [ { 431 | "IpProtocol" : "tcp", 432 | "FromPort" : "3000", 433 | "ToPort" : "3000", 434 | "CidrIp" : "0.0.0.0/0" 435 | }, 436 | { 437 | "IpProtocol" : "tcp", 438 | "FromPort" : "8081", 439 | "ToPort" : "8081", 440 | "CidrIp" : "0.0.0.0/0" 441 | }, 442 | { 443 | "IpProtocol" : "tcp", 444 | "FromPort" : "22", 445 | "ToPort" : "22", 446 | "CidrIp" : { "Ref" : "PermitSSH" } 447 | 448 | }, 449 | { 450 | "IpProtocol" : "icmp", 451 | "FromPort" : "-1", 452 | "ToPort" : "-1", 453 | "CidrIp" : "0.0.0.0/0" 454 | } ], 455 | "Tags" : [ {"Key" : "StackID", "Value" : { "Ref" : "AWS::StackId"} } ] 456 | } 457 | }, 458 | "InstanceSecurityGroupIngress" : { 459 | "Type" : "AWS::EC2::SecurityGroupIngress", 460 | "Properties" : { 461 | "GroupId" : { "Fn::GetAtt" : [ "InstanceSecurityGroup", "GroupId"] }, 462 | "IpProtocol" : "tcp", 463 | "FromPort" : "3001", 464 | "ToPort" : "3004", 465 | "SourceSecurityGroupId" : { "Fn::GetAtt" : ["InstanceSecurityGroup", "GroupId"] } 466 | }, 467 | "DependsOn" : "InstanceSecurityGroup" 468 | } 469 | }, 470 | "Outputs" : { 471 | "AutoscalingID" : { 472 | "Description" : "The Autoscaling Group ID that is used to deploy your cluster", 473 | "Value" : { "Ref" : "ClusterGroup" } 474 | } 475 | } 476 | } 477 | -------------------------------------------------------------------------------- /aerospike-new-vpc.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion" : "2010-09-09", 3 | "Description" : "Template to create an Aerospike cluster", 4 | "Parameters" : { 5 | "KeyPair" : { 6 | "Description" : "Name of the KeyPair that would be used to ssh into the instances", 7 | "Type" : "AWS::EC2::KeyPair::KeyName", 8 | "ConstraintDescription" : "Please specify the name of the keypair that you use to login" 9 | }, 10 | "Tenancy" : { 11 | "Description" : "The tenancy of your instance", 12 | "Type" : "String", 13 | "Default" : "default", 14 | "AllowedValues" : [ "default", "dedicated"] 15 | }, 16 | "NumberOfInstances" : { 17 | "Description" : "Number of instances in the cluster", 18 | "Type" : "Number", 19 | "Default" : "4", 20 | "MinValue" : "1", 21 | "MaxValue" : "15" 22 | }, 23 | "Cloudwatch" : { 24 | "Description" : "Add basic Aerospike metrics to Cloudwatch. Will incur Cloudwatch expenses ~ $24/mo/instance", 25 | "Type" : "String", 26 | "Default" : "no", 27 | "AllowedValues": [ "yes" , "no" ] 28 | }, 29 | "PermitSSH" : { 30 | "Description" : "CIDR block that's permitted to SSH to the Aerospike Cluster", 31 | "Type" : "String", 32 | "AllowedPattern" : "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/([0-9]|[1-2][0-9]|3[0-2]))$", 33 | "ConstraintDescription" : "Must be in CIDR notation. To specify one specifc IPv4 address, append /32. eg: 192.168.1.100/32" 34 | }, 35 | "InstanceType" : { 36 | "Description" : "Type of EC2 instance to launch.", 37 | "Type" : "String", 38 | "Default" : "t2.large", 39 | "AllowedValues" : [ "t2.micro", "t2.small", "t2.medium", "t2.large","t2.xlarge","t2.2xlarge", 40 | "t3.micro", "t3.small", "t3.medium", "t3.large","t3.xlarge","t3.2xlarge", 41 | "m3.medium", "m3.large", "m3.xlarge", "m3.2xlarge", 42 | "m4.large", "m4.xlarge", "m4.2xlarge", "m4.4xlarge", "m4.10xlarge","m4.16xlarge", 43 | "m5.large", "m5.xlarge", "m5.2xlarge", "m5.4xlarge", "m5.12xlarge","m5.24xlarge", 44 | "c3.large", "c3.xlarge", "c3.2xlarge", "c3.4xlarge", "c3.8xlarge", 45 | "c4.large", "c4.xlarge", "c4.2xlarge", "c4.4xlarge", "c4.8xlarge", 46 | "c5.large", "c5.xlarge", "c5.2xlarge", "c5.4xlarge", "c5.9xlarge","c5.18xlarge", 47 | "r3.large", "r3.xlarge", "r3.2xlarge", "r3.4xlarge", "r3.8xlarge", 48 | "r4.large", "r4.xlarge", "r4.2xlarge", "r4.4xlarge", "r4.8xlarge","r4.16xlarge", 49 | "r5.large", "r5.xlarge", "r5.2xlarge", "r5.4xlarge", "r5.12xlarge","r5.24xlarge", 50 | "r5d.large", "r5d.xlarge", "r5d.2xlarge", "r5d.4xlarge", "r5d.12xlarge","r5d.24xlarge", 51 | "i2.xlarge", "i2.2xlarge", "i2.4xlarge", "i2.8xlarge", 52 | "i3.large", "i3.xlarge", "i3.2xlarge", "i3.4xlarge", "i3.8xlarge", "i3.16xlarge" 53 | ] 54 | }, 55 | "EBS" : { 56 | "Description" : "Size of EBS SSD volume in GB. The volume will attach under /dev/sdg. Limit of 16000. Enter 0 to not use EBS.", 57 | "Type" : "Number", 58 | "Default" : "50", 59 | "MinValue" : "0", 60 | "MaxValue" : "16000" 61 | }, 62 | "NamespaceFile" : { 63 | "Description" : "(Optional) Location of your namespace definition. Must be publically downloadable. Will append file directly to end of aerospike.conf", 64 | "Type" : "String" 65 | } 66 | }, 67 | "Mappings": { 68 | "RegionMap": { 69 | "us-east-1": { 70 | "name": "ami-0895d4c9d4bfdf18c" 71 | }, 72 | "us-east-2": { 73 | "name": "ami-0bcf02858b1d7b1c8" 74 | }, 75 | "us-west-1": { 76 | "name": "ami-04e7e941819d5b42e" 77 | }, 78 | "us-west-2": { 79 | "name": "ami-0215478c6825946ef" 80 | }, 81 | "ca-central-1": { 82 | "name": "ami-0fc779f68c06da678" 83 | }, 84 | "eu-west-1": { 85 | "name": "ami-044eab4bf7d36d03f" 86 | }, 87 | "eu-west-2": { 88 | "name": "ami-08fe2d186cf1d08bd" 89 | }, 90 | "eu-west-3": { 91 | "name": "ami-02abd0a6ee61ffba6" 92 | }, 93 | "eu-central-1": { 94 | "name": "ami-0a1b535c47aaff563" 95 | }, 96 | "ap-southeast-1": { 97 | "name": "ami-0fe59bd476d9008e7" 98 | }, 99 | "ap-southeast-2": { 100 | "name": "ami-006b2cde509ab4366" 101 | }, 102 | "ap-south-1": { 103 | "name": "ami-0629ad978b645ffb0" 104 | }, 105 | "ap-northeast-1": { 106 | "name": "ami-0f1725c179df38b12" 107 | }, 108 | "ap-northeast-2": { 109 | "name": "ami-0a293d9795a62ef08" 110 | }, 111 | "sa-east-1": { 112 | "name": "ami-051e8e91e5ee25f53" 113 | } 114 | } 115 | }, 116 | "Conditions" : { 117 | "NotUsingEBS" : { "Fn::Equals" : [ { "Ref" : "EBS" }, 0 ] } 118 | }, 119 | 120 | "Resources" : { 121 | "ClusterRole" : { 122 | "Type": "AWS::IAM::Role", 123 | "Properties": { 124 | "AssumeRolePolicyDocument": { 125 | "Version" : "2012-10-17", 126 | "Statement": [ { 127 | "Effect": "Allow", 128 | "Principal": { 129 | "Service": [ "ec2.amazonaws.com","autoscaling.amazonaws.com" ] 130 | }, 131 | "Action": [ "sts:AssumeRole" ] 132 | } ] 133 | }, 134 | "Path": "/", 135 | "Policies": [ { 136 | "PolicyName": "AerospikeClusterPolicy", 137 | "PolicyDocument": { 138 | "Version" : "2012-10-17", 139 | "Statement": [ { 140 | "Effect": "Allow", 141 | "Action": "ec2:DescribeInstances", 142 | "Resource": "*" 143 | } ] 144 | } 145 | },{ 146 | "PolicyName": "AerospikeCloudWatchPolicy", 147 | "PolicyDocument" :{ 148 | "Version" : "2012-10-17", 149 | "Statement": [ { 150 | "Effect": "Allow", 151 | "Action": "cloudwatch:PutMetricData", 152 | "Resource": "*" 153 | } ] 154 | } 155 | },{ 156 | "PolicyName": "AerospikeSQSPolicy", 157 | "PolicyDocument" :{ 158 | "Version" : "2012-10-17", 159 | "Statement": [ { 160 | "Effect": "Allow", 161 | "Action": "sqs:*", 162 | "Resource": { "Fn::GetAtt" : ["MigrationSQS", "Arn"]} 163 | } ] 164 | } 165 | },{ 166 | "PolicyName": "AerospikeAutoScalingPolicy", 167 | "PolicyDocument" :{ 168 | "Version" : "2012-10-17", 169 | "Statement" : [ { 170 | "Effect": "Allow", 171 | "Action": "autoscaling:*", 172 | "Resource": "*" 173 | } ] 174 | } 175 | }] 176 | } 177 | }, 178 | "ClusterInstanceProfile": { 179 | "Type": "AWS::IAM::InstanceProfile", 180 | "Properties": { 181 | "Path": "/", 182 | "Roles": [ { 183 | "Ref": "ClusterRole" 184 | } ] 185 | } 186 | }, 187 | "MigrationSQS" : { 188 | "Type": "AWS::SQS::Queue", 189 | "Properties": { 190 | "ReceiveMessageWaitTimeSeconds": 10 191 | } 192 | }, 193 | "MigrationHook": { 194 | "Type": "AWS::AutoScaling::LifecycleHook", 195 | "DependsOn" : "MigrationSQS", 196 | "Properties": { 197 | "AutoScalingGroupName": { "Ref": "ClusterGroup" }, 198 | "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING", 199 | "NotificationTargetARN": { "Fn::GetAtt": [ "MigrationSQS", "Arn" ] }, 200 | "RoleARN": { "Fn::GetAtt": [ "ClusterRole", "Arn" ] } 201 | } 202 | }, 203 | "ClusterGroup" : { 204 | "Type" : "AWS::AutoScaling::AutoScalingGroup", 205 | "Properties" : { 206 | "LaunchConfigurationName" : { "Ref" : "LaunchConfig" }, 207 | "DesiredCapacity" : { "Ref" : "NumberOfInstances"}, 208 | "MinSize" : "1", 209 | "MaxSize" : "15", 210 | "AvailabilityZones" : [ { "Fn::Select" : [ "0", { "Fn::GetAZs" : { "Ref" : "AWS::Region" } } ] } ], 211 | "VPCZoneIdentifier" : [{ "Ref" : "PublicSubnet" }], 212 | "Tags" : [ {"Key" : "StackID", "Value" : { "Ref" : "AWS::StackId"}, "PropagateAtLaunch" : "true" }, 213 | {"Key" : "Name" , "Value" : { "Ref" : "AWS::StackName"}, "PropagateAtLaunch" : "true" } ] 214 | } 215 | }, 216 | "LaunchConfig" : { 217 | "Type" : "AWS::AutoScaling::LaunchConfiguration", 218 | "Metadata" : { 219 | "AWS::CloudFormation::Init" : { 220 | "config" : { 221 | "files" : { 222 | "/tmp/aerospike_cluster" : { 223 | "content" : { "Fn::Join" : ["", [ 224 | "#!/bin/bash\n", 225 | "echo ClusterInstancesScriptStart > /var/log/awsuserdatascript\n", 226 | " PUBLICIP=$(curl http://169.254.169.254/latest/meta-data/public-ipv4)\n", 227 | " CONF=/etc/aerospike/aerospike.conf\n", 228 | " sed -i \"/port 3000/a \\\t\taccess-address $PUBLICIP virtual\" $CONF\n", 229 | " ###Point to all instances using the mesh-address config option\n", 230 | " sleep 60 # wait for AWS to provision \n", 231 | " PRIVATEIP=$(aws ec2 describe-instances --filter Name=tag-key,Values=StackID Name=tag-value,Values=", { "Ref" : "AWS::StackId" }," --output=text --region=",{ "Ref" : "AWS::Region" }," | grep PRIVATEIPADDRESSES | awk '{print $4}') \n", 232 | " echo $PRIVATEIP >> /var/log/awsuserdatascript\n", 233 | " sed -i '/.*mesh-seed-address-port/d' $CONF\n", 234 | " for i in $PRIVATEIP; do ", 235 | " sed -i \"/interval/i \\\t\tmesh-seed-address-port $i 3002\" $CONF; done\n", 236 | " CODE=$(curl -Is ",{ "Ref" :"NamespaceFile" }," | head -n 1 | cut -d$' ' -f2)\n", 237 | " if [ \"$CODE\" != \"200\" ]; then echo 'Namespace File not found' >> /var/log/awsuserdatascript\n", 238 | " else sed -i '/namespace test/,$d' $CONF\n", 239 | " curl -s ",{ "Ref" :"NamespaceFile" }," >> $CONF; fi\n", 240 | " /etc/init.d/aerospike start\n", 241 | " /etc/init.d/amc start\n", 242 | "echo OtherInstancesScriptFinish >> /var/log/awsuserdatascript\n", 243 | "(crontab -l 2>/dev/null; echo '*/5 * * * * /opt/aerospike/poll_sqs') | crontab -\n", 244 | " if [[ \"",{ "Ref" : "Cloudwatch" },"\" == \"yes\" ]]; then\n", 245 | " (crontab -l 2>/dev/null; echo '*/5 * * * * /opt/aerospike/cloudwatch') | crontab -; fi\n" 246 | ] ] }, 247 | "mode" : "000744", 248 | "owner" : "root", 249 | "group" : "root" 250 | }, 251 | "/opt/aerospike/cloudwatch" : { 252 | "content" : { "Fn::Join" : ["", [ 253 | "#!/bin/bash\n", 254 | "METRICS=$(asinfo -v stats -l)\n", 255 | "NAMESPACE=aerospike\n", 256 | "REGION=",{ "Ref" : "AWS::Region" },"\n", 257 | "INSTANCE=$(curl 169.254.169.254/latest/meta-data/instance-id)\n", 258 | "CLUSTER=",{ "Ref" : "AWS::StackId" },"\n", 259 | "for L in $METRICS; do\n", 260 | " LINE=(${L//=/ })\n", 261 | " case ${LINE[0]} in\n", 262 | " cluster_integrity)\n", 263 | " if [[ ${LINE[1]} != \"true\" ]]; then\n", 264 | " INTEGRITY_ERROR=1\n", 265 | " else\n", 266 | " INTEGRITY_ERROR=0\n", 267 | " fi\n", 268 | " ;;\n", 269 | " total-bytes-memory)\n", 270 | " TBM=${LINE[1]}\n", 271 | " ;;\n", 272 | " used-bytes-memory)\n", 273 | " UBM=${LINE[1]}\n", 274 | " ;;\n", 275 | " total-bytes-disk)\n", 276 | " TBD=${LINE[1]}\n", 277 | " ;;\n", 278 | " used-bytes-disk)\n", 279 | " UBD=${LINE[1]}\n", 280 | " ;;\n", 281 | " objects)\n", 282 | " OBJECTS=${LINE[1]}\n", 283 | " ;;\n", 284 | " *)\n", 285 | " continue\n", 286 | " ;;\n", 287 | " esac\n", 288 | "done\n", 289 | "FM=$(expr $TBM - $UBM)\n", 290 | "FD=$(expr $TBD - $UBD)\n", 291 | "# Submit metrics\n", 292 | "aws cloudwatch --region $REGION put-metric-data --dimensions Cluster=$CLUSTER,Instance=$INSTANCE --namespace $NAMESPACE --value $INTEGRITY_ERROR --metric-name 'Cluster Integrity'\n", 293 | "aws cloudwatch --region $REGION put-metric-data --dimensions Cluster=$CLUSTER,Instance=$INSTANCE --namespace $NAMESPACE --value $FM --metric-name 'Free Memory' --unit 'Bytes'\n", 294 | "aws cloudwatch --region $REGION put-metric-data --dimensions Cluster=$CLUSTER,Instance=$INSTANCE --namespace $NAMESPACE --value $FD --metric-name 'Free Disk' --unit 'Bytes'\n", 295 | "aws cloudwatch --region $REGION put-metric-data --dimensions Cluster=$CLUSTER,Instance=$INSTANCE --namespace $NAMESPACE --value $OBJECTS --metric-name 'Number of Objects' --unit 'Count'\n" 296 | ] ] }, 297 | "mode" : "000744", 298 | "owner" : "root", 299 | "group" : "root" 300 | }, 301 | "/opt/aerospike/poll_sqs" : { 302 | "content" : { "Fn::Join" : [ "" , [ 303 | "#!/bin/bash\n", 304 | "# This script will prevent autoscaling from terminating\n", 305 | "# this instance until ASD migrations are completed\n", 306 | "set -e\n", 307 | "MYIP=$(curl 169.254.169.254/latest/meta-data/local-ipv4)\n", 308 | "MYNODE=$(curl 169.254.169.254/latest/meta-data/instance-id)\n", 309 | "REGION='",{ "Ref" : "AWS::Region"},"' # CFT\n", 310 | "QUEUE='",{ "Ref" : "MigrationSQS"},"' # CFT\n", 311 | "CLUSTER=$(aws ec2 describe-instances --filter Name=tag-key,Values=StackID Name=tag-value,Values=",{ "Ref":"AWS::StackId"}," --output=text --region=$REGION | grep PRIVATEIPADDRESSES | awk '{print $4}')\n", 312 | "# Find SQS message with termination message\n", 313 | "FOUND=false\n", 314 | "MESSAGE=$(aws sqs receive-message --region $REGION --queue-url $QUEUE --wait-time-seconds 10 --visibility-timeout 2 )\n", 315 | "BODY=$(echo $MESSAGE | jq '.Messages[0] .Body')\n", 316 | "RECEIPT=$(echo $MESSAGE | jq --raw-output '.Messages[0] .ReceiptHandle')\n", 317 | "LIFECYCLE=$(eval echo $BODY | jq --raw-output '.LifecycleTransition')\n", 318 | "INSTANCE=$(eval echo $BODY | jq --raw-output '.EC2InstanceId')\n", 319 | "if [[ \"$LIFECYCLE\" == \"autoscaling:EC2_INSTANCE_TERMINATING\" ]] && [[ \"$INSTANCE\" == \"$MYNODE\" ]]; then\n", 320 | " TOKEN=$(eval echo $BODY | jq --raw-output '.LifecycleActionToken')\n", 321 | " HOOK=$(eval echo $BODY | jq --raw-output '.LifecycleHookName')\n", 322 | " ASG=$(eval echo $BODY | jq --raw-output '.AutoScalingGroupName')\n", 323 | " FOUND=true\n", 324 | " aws sqs delete-message --region $REGION --queue-url $QUEUE --receipt-handle $RECEIPT\n", 325 | "fi\n", 326 | "# If not not found, exit\n", 327 | "if [[ $FOUND == false ]]; then\n", 328 | " exit 0\n", 329 | "fi\n", 330 | "# stop aerospike\n", 331 | "/etc/init.d/aerospike stop\n", 332 | "# give time for cluster to react\n", 333 | "sleep 10\n", 334 | "# Find first node that's not myself\n", 335 | "for I in $CLUSTER; do\n", 336 | " if [[ $I == $MYIP ]]; then\n", 337 | " continue;\n", 338 | " fi\n", 339 | " NODE=$I\n", 340 | " break\n", 341 | "done\n", 342 | "# Grab migration info\n", 343 | "MIGRATIONS=$(asadm -h $NODE -e 'show statistics namespace' | grep migrate-[rt]x-partitions-remaining | awk '{print !$1}')\n", 344 | "DONE=true\n", 345 | "# check every node's migration status\n", 346 | "for STAT in $MIGRATIONS; do\n", 347 | " if [[ \"$STAT\" != '0' ]]; then\n", 348 | " $DONE=false\n", 349 | " break;\n", 350 | " fi\n", 351 | "done\n", 352 | "# if migrations not done, pause ASG actions. Otherwise, continue autoscaling termination.\n", 353 | "if [[ $DONE == false ]]; then\n", 354 | " aws autoscaling record-lifecycle-action-heartbeat --region $REGION --lifecycle-action-token $TOKEN --auto-scaling-group-name $ASG --lifecycle-hook-name $HOOK\n", 355 | " else\n", 356 | " aws autoscaling complete-lifecycle-action --region $REGION --lifecycle-action-token $TOKEN --lifecycle-hook-name $HOOK --auto-scaling-group-name $ASG --lifecycle-action-result CONTINUE\n", 357 | "fi\n" 358 | ] ] }, 359 | "mode" : "000744", 360 | "owner" : "root", 361 | "group" : "root" 362 | } 363 | }, 364 | "commands" : { 365 | "01_form_asd_cluster" : { 366 | "command" : "/tmp/aerospike_cluster", 367 | "cwd" : "/tmp" 368 | } 369 | } 370 | } 371 | } 372 | }, 373 | "Properties" : { 374 | "InstanceType" : { "Ref" : "InstanceType"}, 375 | "KeyName" : { "Ref" : "KeyPair" }, 376 | "BlockDeviceMappings" : { "Fn::If" : [ "NotUsingEBS", 377 | { "Ref" : "AWS::NoValue" }, 378 | [ { 379 | "DeviceName" : "/dev/sdg", 380 | "Ebs" : { "VolumeSize" : {"Ref" : "EBS" }, 381 | "VolumeType" : "gp2" } 382 | } ] 383 | ] }, 384 | "IamInstanceProfile" : { "Ref" : "ClusterInstanceProfile" }, 385 | "ImageId" : { "Fn::FindInMap" : [ "RegionMap", {"Ref" : "AWS::Region"} , "name"] }, 386 | "AssociatePublicIpAddress" : "true", 387 | "SecurityGroups" : [ { "Fn::GetAtt" : [ "InstanceSecurityGroup", "GroupId" ] } ], 388 | "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [ 389 | "#!/bin/bash -xe\n", 390 | "yum update -y aws-cfn-bootstrap\n", 391 | "yum install -y jq\n", 392 | "/opt/aws/bin/cfn-init -v ", 393 | " --stack ", { "Ref" : "AWS::StackName" }, 394 | " --resource LaunchConfig ", 395 | " --region ", { "Ref" : "AWS::Region" }, "\n", 396 | 397 | "/opt/aws/bin/cfn-signal -e $? ", 398 | " --stack ", { "Ref" : "AWS::StackName" }, 399 | " --resource ClusterGroup ", 400 | " --region ", { "Ref" : "AWS::Region" }, "\n" 401 | ] ] } 402 | } 403 | } 404 | }, 405 | "VPC" : { 406 | "Type" : "AWS::EC2::VPC", 407 | "Properties" : { 408 | "CidrBlock" : "10.0.0.0/16", 409 | "EnableDnsSupport" : "true", 410 | "EnableDnsHostnames" : "true", 411 | "InstanceTenancy": { "Ref" : "Tenancy" }, 412 | "Tags" : [ {"Key" : "StackID", "Value" : { "Ref" : "AWS::StackId"} } ] 413 | } 414 | }, 415 | "PublicSubnet" : { 416 | "Type" : "AWS::EC2::Subnet", 417 | "Properties" : { 418 | "VpcId" : { "Ref" : "VPC" }, 419 | "CidrBlock" : "10.0.0.0/24", 420 | "AvailabilityZone" : { "Fn::Select" : [ "0", { "Fn::GetAZs" : { "Ref" : "AWS::Region" } } ] }, 421 | "Tags" : [ {"Key" : "StackID", "Value" : { "Ref" : "AWS::StackId"} } ] 422 | } 423 | }, 424 | "InternetGateway" : { 425 | "Type" : "AWS::EC2::InternetGateway", 426 | "Properties" : { 427 | "Tags" : [ {"Key" : "StackID", "Value" : { "Ref" : "AWS::StackId"} } ] 428 | } 429 | }, 430 | "GatewayToInternet" : { 431 | "Type" : "AWS::EC2::VPCGatewayAttachment", 432 | "Properties" : { 433 | "VpcId" : { "Ref" : "VPC" }, 434 | "InternetGatewayId" : { "Ref" : "InternetGateway" } 435 | } 436 | }, 437 | "PublicRouteTable" : { 438 | "Type" : "AWS::EC2::RouteTable", 439 | "Properties" : { 440 | "VpcId" : { "Ref" : "VPC" }, 441 | "Tags" : [ {"Key" : "StackID", "Value" : { "Ref" : "AWS::StackId"} } ] 442 | } 443 | }, 444 | "PublicRoute" : { 445 | "Type" : "AWS::EC2::Route", 446 | "DependsOn" : "GatewayToInternet", 447 | "Properties" : { 448 | "RouteTableId" : { "Ref" : "PublicRouteTable" }, 449 | "DestinationCidrBlock" : "0.0.0.0/0", 450 | "GatewayId" : { "Ref" : "InternetGateway" } 451 | } 452 | }, 453 | "PublicSubnetRouteTableAssociation" : { 454 | "Type" : "AWS::EC2::SubnetRouteTableAssociation", 455 | "Properties" : { 456 | "SubnetId" : { "Ref" : "PublicSubnet" }, 457 | "RouteTableId" : { "Ref" : "PublicRouteTable" } 458 | } 459 | }, 460 | "InstanceSecurityGroup" : { 461 | "Type" : "AWS::EC2::SecurityGroup", 462 | "Properties" : { 463 | "GroupDescription" : "Enable ports needed by Aerospike", 464 | "VpcId" : { "Ref" : "VPC" }, 465 | "SecurityGroupIngress" : [ { 466 | "IpProtocol" : "tcp", 467 | "FromPort" : "3000", 468 | "ToPort" : "3000", 469 | "CidrIp" : "0.0.0.0/0" 470 | }, 471 | { 472 | "IpProtocol" : "tcp", 473 | "FromPort" : "22", 474 | "ToPort" : "22", 475 | "CidrIp" : { "Ref" : "PermitSSH" } 476 | }, 477 | { 478 | "IpProtocol" : "tcp", 479 | "FromPort" : "8081", 480 | "ToPort" : "8081", 481 | "CidrIp" : "0.0.0.0/0" 482 | }, 483 | { 484 | "IpProtocol" : "icmp", 485 | "FromPort" : "-1", 486 | "ToPort" : "-1", 487 | "CidrIp" : "0.0.0.0/0" 488 | } ], 489 | "Tags" : [ {"Key" : "StackID", "Value" : { "Ref" : "AWS::StackId"} } ] 490 | } 491 | }, 492 | "InstanceSecurityGroupIngress" : { 493 | "Type" : "AWS::EC2::SecurityGroupIngress", 494 | "Properties" : { 495 | "GroupId" : { "Fn::GetAtt" : [ "InstanceSecurityGroup", "GroupId"] }, 496 | "IpProtocol" : "tcp", 497 | "FromPort" : "3001", 498 | "ToPort" : "3004", 499 | "SourceSecurityGroupId" : { "Fn::GetAtt" : ["InstanceSecurityGroup", "GroupId"] } 500 | }, 501 | "DependsOn" : "InstanceSecurityGroup" 502 | } 503 | }, 504 | "Outputs" : { 505 | "AutoscalingID" : { 506 | "Description" : "The Autoscaling Group ID that is used to deploy your cluster", 507 | "Value" : { "Ref" : "ClusterGroup" } 508 | } 509 | } 510 | } 511 | -------------------------------------------------------------------------------- /custom_namespace.conf: -------------------------------------------------------------------------------- 1 | # Namespace configuration for ExampleNS 2 | namespace ExampleNS { 3 | 4 | memory-size 4G # Must be less than instance memory 5 | replication-factor 2 6 | # high-water-memory-pct 60 7 | # high-water-disk-pct 50 8 | # stop-writes-pct 90 9 | # default-ttl 0 10 | 11 | storage-engine device { 12 | #data-in-memory false 13 | device /dev/sdf /dev/sdg # /dev/sdf = ephemeral. t2, m4 and c4 instances do not support ephemeral 14 | # /dev/sdg = EBS. Must have provisioned an EBS volume. 15 | # The second entry is used as a shadow device. It is always recommended 16 | # to use ephemeral as the primary storage device and EBS as a shadow 17 | # device. See the following article for more details: 18 | # www.aerospike.com/docs/deploy_guides/aws/recommendations 19 | write-block-size 1M 20 | } 21 | } 22 | --------------------------------------------------------------------------------