├── .gitignore ├── LICENSE ├── README.md ├── chapter10 ├── README.md ├── cli.txt ├── index.js ├── nodetodo.png └── package.json ├── chapter11 ├── multiaz-ebs.json ├── multiaz-elasticip.json ├── multiaz.json └── recovery.json ├── chapter12 ├── loadbalancer.json ├── policy.json └── url2png │ ├── README.md │ ├── config.json │ ├── index.js │ ├── package.json │ ├── url2png.png │ └── worker.js ├── chapter13 ├── README.md ├── build │ ├── server.zip │ └── worker.zip ├── bundle.sh ├── imagery.png ├── lib.js ├── server │ ├── lib.js │ ├── package.json │ ├── public │ │ ├── app.js │ │ └── index.html │ └── server.js ├── template.json └── worker │ ├── .ebextensions │ └── worker.config │ ├── lib.js │ ├── package.json │ └── worker.js ├── chapter14 ├── url2png-loadtest.json ├── url2png.json ├── wordpress-loadtest.json └── wordpress.json ├── chapter2 ├── cost.html └── template.json ├── chapter3 ├── a │ └── index.html └── b │ └── index.html ├── chapter4 ├── nodecc │ ├── README.md │ ├── index.js │ ├── lib │ │ ├── createServer.js │ │ ├── listAMIs.js │ │ ├── listServers.js │ │ ├── listSubnets.js │ │ ├── showServer.js │ │ └── terminateServer.js │ ├── nodecc.png │ └── package.json ├── server.json ├── server.ps1 └── server.sh ├── chapter5 ├── Dev.md ├── etherpad.zip ├── irc-cloudformation.json ├── irc-create-cloudformation-stack.sh ├── vpn-cloudformation.json ├── vpn-create-cloudformation-stack.sh └── vpn-setup.sh ├── chapter6 ├── firewall1.json ├── firewall2.json ├── firewall3.json ├── firewall4.json ├── firewall5.json ├── server.json ├── update.sh └── vpc.json ├── chapter7 ├── bucketpolicy.json ├── gallery │ ├── index.html │ ├── package.json │ └── server.js └── helloworld.html ├── chapter8 ├── ebs.json ├── instance_store.json ├── nfs-server-install.sh └── nfs.json └── chapter9 ├── template-multiaz.json ├── template-snapshot.json ├── template.json └── wordpress-import.sql /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | nodecc.log 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Andreas & Michael Wittig 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amazon Web Services in Action 2 | 3 | ![Cover of Amazon Web Services in Action](http://manning.com/wittig/wittig_cover150.jpg) 4 | 5 | Code of Amazon Web Services in Action http://bit.ly/amazon-web-services-in-action 6 | 7 | ## About the book 8 | 9 | Distributed systems are unpredictable, and it can be an enormous (and expensive!) challenge to manage around potentially-crippling obstacles like hardware failures, unanticipated changes in load, network issues, or the need to rapidly scale or reconfigure your infrastructure. Amazon Web Services is a platform for hosting distributed applications in a secure, flexible cloud environment. AWS provides a suite of services designed to help you focus on what your application does instead of the infrastructure required to run it. Whether you're serving up blog pages, analyzing fast data in real-time, building software as a service, or implementing a massive e-commerce site, AWS provides both a stable platform and services that will scale with your application. 10 | 11 | Amazon Web Services in Action introduces you to computing, storing and networking in the AWS cloud. You'll start with a broad overview of AWS and learn how to spin up servers manually and from the command line. Then, you'll explore infrastructure automation with the AWS CloudFormation service, where you can describe a blueprint of your infrastructure as code. As you progress through the book, you will learn how to isolate your systems using private networks to increase security and how to use the most valuable AWS managed services available on AWS. You'll also discover the benefits of stateless servers. In the end, you'll look at the AWS model for high availability, scaling, decoupling with queues and load balancers, and fault tolerance. 12 | 13 | ## About the authors 14 | 15 | Andreas and Michael Wittig work as software engineers and consultants focusing on AWS, web and mobile application development. They work with clients all around the globe. Together, they migrated the complete IT infrastructure of the first Bank in Germany to AWS. They have expertise in distributed system development and architecture, algorithmic trading and real-time analytics. Andreas and Michael are proponents of the DevOps model. They are both AWS Certified Solutions Architects – Professional Level. 16 | -------------------------------------------------------------------------------- /chapter10/README.md: -------------------------------------------------------------------------------- 1 | # Node TODO for AWS 2 | 3 | ![Node TODO for AWS](./nodetodo.png?raw=true "Node TODO for AWS") 4 | 5 | Install the dependencies ... 6 | 7 | npm install 8 | 9 | ... and run nodetodo 10 | 11 | node index.js --help 12 | 13 | ## usage 14 | 15 | ### user 16 | 17 | #### add 18 | 19 | node index.js user-add 20 | 21 | node index.js user-add michael michael@widdix.de 0123456789 22 | 23 | #### remove 24 | 25 | node index.js user-rm 26 | 27 | node index.js user-rm michael 28 | 29 | #### list 30 | 31 | node index.js user-ls 32 | 33 | #### show 34 | 35 | node index.js user 36 | 37 | node index.js user michael 38 | 39 | ### task 40 | 41 | #### add 42 | 43 | node index.js task-add [] [--dueat=] 44 | 45 | node index.js task-add michael "plan lunch" --dueat=20150522 46 | 47 | #### remove 48 | 49 | node index.js task-rm 50 | 51 | node index.js task-rm michael 1432187491647 52 | 53 | #### list 54 | 55 | node index.js task-ls [] [--overdue|--due|--withoutdue|--futuredue|--dueafter=|--duebefore=] [--limit=] [--next=] 56 | 57 | node index.js task-ls michael 58 | 59 | #### mark as done 60 | 61 | node index.js task-done 62 | 63 | node index.js task-done michael 1432187491647 64 | 65 | ## schema 66 | 67 | ### user 68 | 69 | aws dynamodb create-table --table-name todo-user --attribute-definitions AttributeName=uid,AttributeType=S --key-schema AttributeName=uid,KeyType=HASH --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 70 | 71 | #### key 72 | 73 | * HASH: uid 74 | 75 | #### values 76 | 77 | * uid: string 78 | * email: string 79 | * phone: string 80 | 81 | ### task 82 | 83 | aws dynamodb create-table --table-name todo-task --attribute-definitions AttributeName=uid,AttributeType=S AttributeName=tid,AttributeType=N --key-schema AttributeName=uid,KeyType=HASH AttributeName=tid,KeyType=RANGE --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 84 | 85 | aws dynamodb update-table --table-name todo-task --attribute-definitions AttributeName=uid,AttributeType=S AttributeName=tid,AttributeType=N AttributeName=category,AttributeType=S --global-secondary-index-updates '[{"Create": {"IndexName": "category-index", "KeySchema": [{"AttributeName": "category", "KeyType": "HASH"}, {"AttributeName": "tid", "KeyType": "RANGE"}], "Projection": {"ProjectionType": "ALL"}, "ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}}}]' 86 | 87 | #### key 88 | 89 | * HASH: uid 90 | * RANGE: tid 91 | 92 | #### values 93 | 94 | * uid: string 95 | * tid: number (time stamp) 96 | * category: string (optional) 97 | * description: string 98 | * due: number (yyyymmdd) 99 | * created: number (yyyymmdd) 100 | * completed: number (yyyymmdd) 101 | -------------------------------------------------------------------------------- /chapter10/cli.txt: -------------------------------------------------------------------------------- 1 | nodetodo 2 | 3 | Usage: 4 | nodetodo user-add 5 | nodetodo user-rm 6 | nodetodo user-ls [--limit=] [--next=] 7 | nodetodo user 8 | nodetodo task-add [] [--dueat=] 9 | nodetodo task-rm 10 | nodetodo task-ls [] [--overdue|--due|--withoutdue|--futuredue|--dueafter=|--duebefore=] [--limit=] [--next=] 11 | nodetodo task-la [--overdue|--due|--withoutdue|--futuredue|--dueafter=|--duebefore=] [--limit=] [--next=] 12 | nodetodo task-done 13 | nodetodo -h | --help 14 | nodetodo --version 15 | 16 | Options: 17 | -h --help Show this screen. 18 | --version Show version. 19 | --limit= Maximum number of results [default: 10]. 20 | -------------------------------------------------------------------------------- /chapter10/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var docopt = require('docopt'); 3 | var moment = require("moment"); 4 | var AWS = require('aws-sdk'); 5 | var db = new AWS.DynamoDB({ 6 | "region": "us-east-1" 7 | }); 8 | 9 | var cli = fs.readFileSync('./cli.txt', {"encoding": "utf8"}); 10 | var input = docopt.docopt(cli, {"version": "1.0", "argv": process.argv.splice(2)}); 11 | 12 | function getValue(attribute, type) { 13 | if (attribute === undefined) { 14 | return null; 15 | } 16 | return attribute[type]; 17 | } 18 | 19 | function mapTaskItem(item) { 20 | return { 21 | "tid": item.tid.N, 22 | "description": item.description.S, 23 | "created": item.created.N, 24 | "due": getValue(item.due, 'N'), 25 | "category": getValue(item.category, 'S'), 26 | "completed": getValue(item.completed, 'N') 27 | }; 28 | } 29 | 30 | function mapUserItem(item) { 31 | return { 32 | "uid": item.uid.S, 33 | "email": item.email.S, 34 | "phone": item.phone.S 35 | }; 36 | } 37 | 38 | if (input['user-add'] === true) { 39 | var params = { 40 | "Item": { 41 | "uid": { 42 | "S": input[''] 43 | }, 44 | "email": { 45 | "S": input[''] 46 | }, 47 | "phone": { 48 | "S": input[''] 49 | } 50 | }, 51 | "TableName": "todo-user", 52 | "ConditionExpression": "attribute_not_exists(uid)" 53 | }; 54 | db.putItem(params, function(err) { 55 | if (err) { 56 | console.error('error', err); 57 | } else { 58 | console.log('user added with uid ' + input['']); 59 | } 60 | }); 61 | } else if (input['user-rm'] === true) { 62 | var params = { 63 | "Key": { 64 | "uid": { 65 | "S": input[''] 66 | } 67 | }, 68 | "TableName": "todo-user" 69 | }; 70 | db.deleteItem(params, function(err) { 71 | if (err) { 72 | console.error('error', err); 73 | } else { 74 | console.log('user removed with uid ' + input['']); 75 | } 76 | }); 77 | } else if (input['user-ls'] === true) { 78 | var params = { 79 | "TableName": "todo-user", 80 | "Limit": input['--limit'] 81 | }; 82 | if (input['--next'] !== null) { 83 | params.ExclusiveStartKey = { 84 | "uid": { 85 | "S": input['--next'] 86 | } 87 | }; 88 | } 89 | db.scan(params, function(err, data) { 90 | if (err) { 91 | console.error('error', err); 92 | } else { 93 | console.log('users', data.Items.map(mapUserItem)); 94 | if (data.LastEvaluatedKey !== undefined) { 95 | console.log('more users available with --next=' + data.LastEvaluatedKey.uid.S); 96 | } 97 | } 98 | }); 99 | } else if (input['user'] === true) { 100 | var params = { 101 | "Key": { 102 | "uid": { 103 | "S": input[''] 104 | } 105 | }, 106 | "TableName": "todo-user" 107 | }; 108 | db.getItem(params, function(err, data) { 109 | if (err) { 110 | console.error('error', err); 111 | } else { 112 | if (data.Item) { 113 | console.log('user with uid ' + input[''], mapUserItem(data.Item)); 114 | } else { 115 | console.error('user with uid ' + input[''] + ' not found'); 116 | } 117 | } 118 | }); 119 | } else if (input['task-add'] === true) { 120 | var tid = Date.now(); 121 | var params = { 122 | "Item": { 123 | "uid": { 124 | "S": input[''] 125 | }, 126 | "tid": { 127 | "N": tid.toString() 128 | }, 129 | "description": { 130 | "S": input[''] 131 | }, 132 | "created": { 133 | "N": moment().format("YYYYMMDD") 134 | } 135 | }, 136 | "TableName": "todo-task", 137 | "ConditionExpression": "attribute_not_exists(uid) and attribute_not_exists(tid)" 138 | }; 139 | if (input['--dueat'] !== null) { 140 | params.Item.due = { 141 | "N": input['--dueat'] 142 | }; 143 | } 144 | if (input[''] !== null) { 145 | params.Item.category = { 146 | "S": input[''] 147 | }; 148 | } 149 | db.putItem(params, function(err) { 150 | if (err) { 151 | console.error('error', err); 152 | } else { 153 | console.log('task added with tid ' + tid); 154 | } 155 | }); 156 | } else if (input['task-rm'] === true) { 157 | var params = { 158 | "Key": { 159 | "uid": { 160 | "S": input[''] 161 | }, 162 | "tid": { 163 | "N": input[''] 164 | } 165 | }, 166 | "TableName": "todo-task" 167 | }; 168 | db.deleteItem(params, function(err) { 169 | if (err) { 170 | console.error('error', err); 171 | } else { 172 | console.log('task removed with tid ' + input['']); 173 | } 174 | }); 175 | } else if (input['task-ls'] === true) { 176 | var params = { 177 | "KeyConditionExpression": "uid = :uid", 178 | "ExpressionAttributeValues": { 179 | ":uid": { 180 | "S": input[''] 181 | } 182 | }, 183 | "TableName": "todo-task", 184 | "Limit": input['--limit'] 185 | }; 186 | if (input['--next'] !== null) { 187 | params.KeyConditionExpression += ' AND tid > :next'; 188 | params.ExpressionAttributeValues[':next'] = { 189 | "N": input['--next'] 190 | }; 191 | } 192 | if (input['--overdue'] === true) { 193 | params.FilterExpression = "due < :yyyymmdd"; 194 | params.ExpressionAttributeValues[':yyyymmdd'] = {"N": moment().format("YYYYMMDD")}; 195 | } else if (input['--due'] === true) { 196 | params.FilterExpression = "due = :yyyymmdd"; 197 | params.ExpressionAttributeValues[':yyyymmdd'] = {"N": moment().format("YYYYMMDD")}; 198 | } else if (input['--withoutdue'] === true) { 199 | params.FilterExpression = "attribute_not_exists(due)"; 200 | } else if (input['--futuredue'] === true) { 201 | params.FilterExpression = "due > :yyyymmdd"; 202 | params.ExpressionAttributeValues[':yyyymmdd'] = {"N": moment().format("YYYYMMDD")}; 203 | } else if (input['--dueafter'] !== null) { 204 | params.FilterExpression = "due > :yyyymmdd"; 205 | params.ExpressionAttributeValues[':yyyymmdd'] = {"N": input['--dueafter']}; 206 | } else if (input['--duebefore'] !== null) { 207 | params.FilterExpression = "due < :yyyymmdd"; 208 | params.ExpressionAttributeValues[':yyyymmdd'] = {"N": input['--duebefore']}; 209 | } 210 | if (input[''] !== null) { 211 | if (params.FilterExpression === undefined) { 212 | params.FilterExpression = ''; 213 | } else { 214 | params.FilterExpression += ' AND '; 215 | } 216 | params.FilterExpression += 'category = :category'; 217 | params.ExpressionAttributeValues[':category'] = { 218 | "S": input[''] 219 | }; 220 | } 221 | db.query(params, function(err, data) { 222 | if (err) { 223 | console.error('error', err); 224 | } else { 225 | console.log('tasks', data.Items.map(mapTaskItem)); 226 | if (data.LastEvaluatedKey !== undefined) { 227 | console.log('more tasks available with --next=' + data.LastEvaluatedKey.tid.N); 228 | } 229 | } 230 | }); 231 | } else if (input['task-la'] === true) { 232 | var params = { 233 | "KeyConditionExpression": "category = :category", 234 | "ExpressionAttributeValues": { 235 | ":category": { 236 | "S": input[''] 237 | } 238 | }, 239 | "TableName": "todo-task", 240 | "IndexName": "category-index", 241 | "Limit": input['--limit'] 242 | }; 243 | if (input['--next'] !== null) { 244 | params.KeyConditionExpression += ' AND tid > :next'; 245 | params.ExpressionAttributeValues[':next'] = { 246 | "N": input['--next'] 247 | }; 248 | } 249 | if (input['--overdue'] === true) { 250 | params.FilterExpression = "due < :yyyymmdd"; 251 | params.ExpressionAttributeValues[':yyyymmdd'] = {"N": moment().format("YYYYMMDD")}; 252 | } else if (input['--due'] === true) { 253 | params.FilterExpression = "due = :yyyymmdd"; 254 | params.ExpressionAttributeValues[':yyyymmdd'] = {"N": moment().format("YYYYMMDD")}; 255 | } else if (input['--withoutdue'] === true) { 256 | params.FilterExpression = "attribute_not_exists(due)"; 257 | } else if (input['--futuredue'] === true) { 258 | params.FilterExpression = "due > :yyyymmdd"; 259 | params.ExpressionAttributeValues[':yyyymmdd'] = {"N": moment().format("YYYYMMDD")}; 260 | } else if (input['--dueafter'] !== null) { 261 | params.FilterExpression = "due > :yyyymmdd"; 262 | params.ExpressionAttributeValues[':yyyymmdd'] = {"N": input['--dueafter']}; 263 | } else if (input['--duebefore'] !== null) { 264 | params.FilterExpression = "due < :yyyymmdd"; 265 | params.ExpressionAttributeValues[':yyyymmdd'] = {"N": input['--duebefore']}; 266 | } 267 | db.query(params, function(err, data) { 268 | if (err) { 269 | console.error('error', err); 270 | } else { 271 | console.log('tasks', data.Items.map(mapTaskItem)); 272 | if (data.LastEvaluatedKey !== undefined) { 273 | console.log('more tasks available with --next=' + data.LastEvaluatedKey.tid.N); 274 | } 275 | } 276 | }); 277 | } else if (input['task-done'] === true) { 278 | var params = { 279 | "Key": { 280 | "uid": { 281 | "S": input[''] 282 | }, 283 | "tid": { 284 | "N": input[''] 285 | } 286 | }, 287 | "UpdateExpression": "SET completed = :yyyymmdd", 288 | "ExpressionAttributeValues": { 289 | ":yyyymmdd": { 290 | "N": moment().format("YYYYMMDD") 291 | } 292 | }, 293 | "TableName": "todo-task" 294 | }; 295 | db.updateItem(params, function(err) { 296 | if (err) { 297 | console.error('error', err); 298 | } else { 299 | console.log('task completed with tid ' + input['']); 300 | } 301 | }); 302 | } 303 | -------------------------------------------------------------------------------- /chapter10/nodetodo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AWSinAction/code/9acfdeb3642a3b7e1af82c7bc78bdf10ac2bd9f3/chapter10/nodetodo.png -------------------------------------------------------------------------------- /chapter10/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "aws-sdk": "2.1.29", 4 | "docopt": "0.6.2", 5 | "moment": "2.10.3" 6 | }, 7 | "private": true 8 | } 9 | -------------------------------------------------------------------------------- /chapter11/multiaz-ebs.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 11 (Jenkins (CI server) running with Auto Scaling Group over multiple AZs)", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "JenkinsAdminPassword": { 11 | "Description": "Password for Jenkins admin user", 12 | "Type": "String", 13 | "AllowedPattern" : "[a-zA-Z0-9]*", 14 | "MinLength" : "8", 15 | "MaxLength" : "42" 16 | }, 17 | "AMISnapshot": { 18 | "Description": "AMI ID to start virtual server from.", 19 | "Type": "String", 20 | "AllowedPattern" : "[\u0020-\uD7FF\uE000-\uFFFD\uD800\uDC00-\uDBFF\uDFFF\r\n\t]*", 21 | "MinLength" : "1", 22 | "MaxLength" : "255" 23 | } 24 | }, 25 | "Resources": { 26 | "VPC": { 27 | "Type": "AWS::EC2::VPC", 28 | "Properties": { 29 | "EnableDnsSupport": "true", 30 | "EnableDnsHostnames": "true", 31 | "CidrBlock": "10.0.0.0/16", 32 | "Tags": [ 33 | { 34 | "Key": "Name", 35 | "Value": "jenkins-multiaz" 36 | } 37 | ] 38 | } 39 | }, 40 | "SubnetA": { 41 | "Type": "AWS::EC2::Subnet", 42 | "Properties": { 43 | "VpcId": { 44 | "Ref": "VPC" 45 | }, 46 | "AvailabilityZone": {"Fn::Select": ["0", {"Fn::GetAZs": ""}]}, 47 | "CidrBlock": "10.0.0.0/24", 48 | "Tags": [ 49 | { 50 | "Key": "Name", 51 | "Value": "jenkins-multiaz" 52 | } 53 | ] 54 | } 55 | }, 56 | "SubnetB": { 57 | "Type": "AWS::EC2::Subnet", 58 | "Properties": { 59 | "VpcId": { 60 | "Ref": "VPC" 61 | }, 62 | "AvailabilityZone": {"Fn::Select": ["1", {"Fn::GetAZs": ""}]}, 63 | "CidrBlock": "10.0.1.0/24", 64 | "Tags": [ 65 | { 66 | "Key": "Name", 67 | "Value": "jenkins-multiaz" 68 | } 69 | ] 70 | } 71 | }, 72 | "InternetGateway": { 73 | "Type": "AWS::EC2::InternetGateway", 74 | "Properties": { 75 | "Tags": [ 76 | { 77 | "Key": "Name", 78 | "Value": "jenkins-multiaz" 79 | } 80 | ] 81 | } 82 | }, 83 | "GatewayToInternet": { 84 | "Type": "AWS::EC2::VPCGatewayAttachment", 85 | "Properties": { 86 | "VpcId": { 87 | "Ref": "VPC" 88 | }, 89 | "InternetGatewayId": { 90 | "Ref": "InternetGateway" 91 | } 92 | } 93 | }, 94 | "RouteTable": { 95 | "Type": "AWS::EC2::RouteTable", 96 | "Properties": { 97 | "VpcId": { 98 | "Ref": "VPC" 99 | }, 100 | "Tags": [ 101 | { 102 | "Key": "Name", 103 | "Value": "jenkins-multiaz" 104 | } 105 | ] 106 | } 107 | }, 108 | "InternetRoute": { 109 | "Type": "AWS::EC2::Route", 110 | "Properties": { 111 | "RouteTableId": { 112 | "Ref": "RouteTable" 113 | }, 114 | "DestinationCidrBlock": "0.0.0.0/0", 115 | "GatewayId": { 116 | "Ref": "InternetGateway" 117 | } 118 | }, 119 | "DependsOn": "GatewayToInternet" 120 | }, 121 | "RouteTableAssociationA": { 122 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 123 | "Properties": { 124 | "SubnetId": { 125 | "Ref": "SubnetA" 126 | }, 127 | "RouteTableId": { 128 | "Ref": "RouteTable" 129 | } 130 | } 131 | }, 132 | "RouteTableAssociationB": { 133 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 134 | "Properties": { 135 | "SubnetId": { 136 | "Ref": "SubnetB" 137 | }, 138 | "RouteTableId": { 139 | "Ref": "RouteTable" 140 | } 141 | } 142 | }, 143 | "NetworkAcl": { 144 | "Type": "AWS::EC2::NetworkAcl", 145 | "Properties": { 146 | "VpcId": { 147 | "Ref": "VPC" 148 | }, 149 | "Tags": [ 150 | { 151 | "Key": "Name", 152 | "Value": "jenkins-multiaz" 153 | } 154 | ] 155 | } 156 | }, 157 | "NetworkAceSSH": { 158 | "Type": "AWS::EC2::NetworkAclEntry", 159 | "Properties": { 160 | "NetworkAclId": { 161 | "Ref": "NetworkAcl" 162 | }, 163 | "RuleNumber": "10", 164 | "Protocol": "6", 165 | "RuleAction": "allow", 166 | "Egress": "false", 167 | "CidrBlock": "0.0.0.0/0", 168 | "PortRange": { 169 | "From": "22", 170 | "To": "22" 171 | } 172 | } 173 | }, 174 | "NetworkAceJenkinsHTTP": { 175 | "Type": "AWS::EC2::NetworkAclEntry", 176 | "Properties": { 177 | "NetworkAclId": { 178 | "Ref": "NetworkAcl" 179 | }, 180 | "RuleNumber": "11", 181 | "Protocol": "6", 182 | "RuleAction": "allow", 183 | "Egress": "false", 184 | "CidrBlock": "0.0.0.0/0", 185 | "PortRange": { 186 | "From": "8080", 187 | "To": "8080" 188 | } 189 | } 190 | }, 191 | "NetworkAceNTP": { 192 | "Type": "AWS::EC2::NetworkAclEntry", 193 | "Properties": { 194 | "NetworkAclId": { 195 | "Ref": "NetworkAcl" 196 | }, 197 | "RuleNumber": "20", 198 | "Protocol": "17", 199 | "RuleAction": "allow", 200 | "Egress": "false", 201 | "CidrBlock": "0.0.0.0/0", 202 | "PortRange": { 203 | "From": "123", 204 | "To": "123" 205 | } 206 | } 207 | }, 208 | "NetworkAceICMP": { 209 | "Type": "AWS::EC2::NetworkAclEntry", 210 | "Properties": { 211 | "NetworkAclId": { 212 | "Ref": "NetworkAcl" 213 | }, 214 | "RuleNumber": "30", 215 | "Protocol": "1", 216 | "RuleAction": "allow", 217 | "Egress": "false", 218 | "CidrBlock": "0.0.0.0/0", 219 | "Icmp": { 220 | "Code": "-1", 221 | "Type": "-1" 222 | } 223 | } 224 | }, 225 | "NetworkAceHighPortsTCP": { 226 | "Type": "AWS::EC2::NetworkAclEntry", 227 | "Properties": { 228 | "NetworkAclId": { 229 | "Ref": "NetworkAcl" 230 | }, 231 | "RuleNumber": "40", 232 | "Protocol": "6", 233 | "RuleAction": "allow", 234 | "Egress": "false", 235 | "CidrBlock": "0.0.0.0/0", 236 | "PortRange": { 237 | "From": "1024", 238 | "To": "65535" 239 | } 240 | } 241 | }, 242 | "NetworkAceHighPortsUDP": { 243 | "Type": "AWS::EC2::NetworkAclEntry", 244 | "Properties": { 245 | "NetworkAclId": { 246 | "Ref": "NetworkAcl" 247 | }, 248 | "RuleNumber": "41", 249 | "Protocol": "17", 250 | "RuleAction": "allow", 251 | "Egress": "false", 252 | "CidrBlock": "0.0.0.0/0", 253 | "PortRange": { 254 | "From": "1024", 255 | "To": "65535" 256 | } 257 | } 258 | }, 259 | "NetworkAceEgress": { 260 | "Type": "AWS::EC2::NetworkAclEntry", 261 | "Properties": { 262 | "NetworkAclId": { 263 | "Ref": "NetworkAcl" 264 | }, 265 | "RuleNumber": "10", 266 | "Protocol": "-1", 267 | "RuleAction": "allow", 268 | "Egress": "true", 269 | "CidrBlock": "0.0.0.0/0", 270 | "PortRange": { 271 | "From": "0", 272 | "To": "65535" 273 | } 274 | } 275 | }, 276 | "NetworkAclAssociationA": { 277 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 278 | "Properties": { 279 | "SubnetId": { 280 | "Ref": "SubnetA" 281 | }, 282 | "NetworkAclId": { 283 | "Ref": "NetworkAcl" 284 | } 285 | } 286 | }, 287 | "NetworkAclAssociationB": { 288 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 289 | "Properties": { 290 | "SubnetId": { 291 | "Ref": "SubnetB" 292 | }, 293 | "NetworkAclId": { 294 | "Ref": "NetworkAcl" 295 | } 296 | } 297 | }, 298 | "SecurityGroupJenkins": { 299 | "Type": "AWS::EC2::SecurityGroup", 300 | "Properties": { 301 | "GroupDescription": "SecurityGroupforjenkins", 302 | "VpcId": { 303 | "Ref": "VPC" 304 | }, 305 | "Tags": [ 306 | { 307 | "Key": "Name", 308 | "Value": "jenkins-multiaz" 309 | } 310 | ], 311 | "SecurityGroupIngress": [ 312 | { 313 | "IpProtocol": "tcp", 314 | "FromPort": "22", 315 | "ToPort": "22", 316 | "CidrIp": "0.0.0.0/0" 317 | }, 318 | { 319 | "IpProtocol": "tcp", 320 | "FromPort": "8080", 321 | "ToPort": "8080", 322 | "CidrIp": "0.0.0.0/0" 323 | }, 324 | { 325 | "IpProtocol": "icmp", 326 | "FromPort": "-1", 327 | "ToPort": "-1", 328 | "CidrIp": "0.0.0.0/0" 329 | } 330 | ] 331 | } 332 | }, 333 | "LaunchConfiguration": { 334 | "Type": "AWS::AutoScaling::LaunchConfiguration", 335 | "Properties": { 336 | "InstanceMonitoring": false, 337 | "ImageId": {"Ref": "AMISnapshot"}, 338 | "KeyName": {"Ref": "KeyName"}, 339 | "SecurityGroups": [{"Ref": "SecurityGroupJenkins"}], 340 | "AssociatePublicIpAddress": true, 341 | "InstanceType": "t2.micro", 342 | "UserData": { 343 | "Fn::Base64": { 344 | "Fn::Join": [ 345 | "", 346 | [ 347 | "#!/bin/bash -ex\n", 348 | "wget http://pkg.jenkins-ci.org/redhat/jenkins-1.616-1.1.noarch.rpm\n", 349 | "rpm --install jenkins-1.616-1.1.noarch.rpm\n", 350 | "sed -i -e 's/JENKINS_ARGS=\\\"\\\"/JENKINS_ARGS=\\\"--argumentsRealm.passwd.admin=", {"Ref": "JenkinsAdminPassword"}, " --argumentsRealm.roles.admin=admin\\\"/g' /etc/sysconfig/jenkins\n", 351 | "echo \"1.0true\" > /var/lib/jenkins/config.xml\n", 352 | "service jenkins start\n" 353 | ] 354 | ] 355 | } 356 | } 357 | } 358 | }, 359 | "AutoScalingGroup": { 360 | "Type": "AWS::AutoScaling::AutoScalingGroup", 361 | "Properties": { 362 | "LaunchConfigurationName": {"Ref": "LaunchConfiguration"}, 363 | "Tags": [ 364 | { 365 | "Key": "Name", 366 | "Value": "jenkins-multiaz", 367 | "PropagateAtLaunch": true 368 | } 369 | ], 370 | "DesiredCapacity": 1, 371 | "MinSize": 1, 372 | "MaxSize": 1, 373 | "VPCZoneIdentifier": [{"Ref": "SubnetA"}, {"Ref": "SubnetB"}], 374 | "TerminationPolicies": ["Default"], 375 | "HealthCheckGracePeriod": 600, 376 | "HealthCheckType": "EC2" 377 | }, 378 | "DependsOn": "GatewayToInternet" 379 | } 380 | } 381 | } -------------------------------------------------------------------------------- /chapter11/multiaz-elasticip.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 11 (Jenkins (CI server) running with Auto Scaling Group over multiple AZs)", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "JenkinsAdminPassword": { 11 | "Description": "Password for Jenkins admin user", 12 | "Type": "String", 13 | "AllowedPattern" : "[a-zA-Z0-9]*", 14 | "MinLength" : "8", 15 | "MaxLength" : "42" 16 | } 17 | }, 18 | "Mappings": { 19 | "EC2RegionMap": { 20 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 21 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 22 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 23 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 24 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 25 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 26 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 27 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 28 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 29 | } 30 | }, 31 | "Resources": { 32 | "VPC": { 33 | "Type": "AWS::EC2::VPC", 34 | "Properties": { 35 | "EnableDnsSupport": "true", 36 | "EnableDnsHostnames": "true", 37 | "CidrBlock": "10.0.0.0/16", 38 | "Tags": [ 39 | { 40 | "Key": "Name", 41 | "Value": "jenkins-multiaz" 42 | } 43 | ] 44 | } 45 | }, 46 | "SubnetA": { 47 | "Type": "AWS::EC2::Subnet", 48 | "Properties": { 49 | "VpcId": { 50 | "Ref": "VPC" 51 | }, 52 | "AvailabilityZone": {"Fn::Select": ["0", {"Fn::GetAZs": ""}]}, 53 | "CidrBlock": "10.0.0.0/24", 54 | "Tags": [ 55 | { 56 | "Key": "Name", 57 | "Value": "jenkins-multiaz" 58 | } 59 | ] 60 | } 61 | }, 62 | "SubnetB": { 63 | "Type": "AWS::EC2::Subnet", 64 | "Properties": { 65 | "VpcId": { 66 | "Ref": "VPC" 67 | }, 68 | "AvailabilityZone": {"Fn::Select": ["1", {"Fn::GetAZs": ""}]}, 69 | "CidrBlock": "10.0.1.0/24", 70 | "Tags": [ 71 | { 72 | "Key": "Name", 73 | "Value": "jenkins-multiaz" 74 | } 75 | ] 76 | } 77 | }, 78 | "InternetGateway": { 79 | "Type": "AWS::EC2::InternetGateway", 80 | "Properties": { 81 | "Tags": [ 82 | { 83 | "Key": "Name", 84 | "Value": "jenkins-multiaz" 85 | } 86 | ] 87 | } 88 | }, 89 | "GatewayToInternet": { 90 | "Type": "AWS::EC2::VPCGatewayAttachment", 91 | "Properties": { 92 | "VpcId": { 93 | "Ref": "VPC" 94 | }, 95 | "InternetGatewayId": { 96 | "Ref": "InternetGateway" 97 | } 98 | } 99 | }, 100 | "RouteTable": { 101 | "Type": "AWS::EC2::RouteTable", 102 | "Properties": { 103 | "VpcId": { 104 | "Ref": "VPC" 105 | }, 106 | "Tags": [ 107 | { 108 | "Key": "Name", 109 | "Value": "jenkins-multiaz" 110 | } 111 | ] 112 | } 113 | }, 114 | "InternetRoute": { 115 | "Type": "AWS::EC2::Route", 116 | "Properties": { 117 | "RouteTableId": { 118 | "Ref": "RouteTable" 119 | }, 120 | "DestinationCidrBlock": "0.0.0.0/0", 121 | "GatewayId": { 122 | "Ref": "InternetGateway" 123 | } 124 | }, 125 | "DependsOn": "GatewayToInternet" 126 | }, 127 | "RouteTableAssociationA": { 128 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 129 | "Properties": { 130 | "SubnetId": { 131 | "Ref": "SubnetA" 132 | }, 133 | "RouteTableId": { 134 | "Ref": "RouteTable" 135 | } 136 | } 137 | }, 138 | "RouteTableAssociationB": { 139 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 140 | "Properties": { 141 | "SubnetId": { 142 | "Ref": "SubnetB" 143 | }, 144 | "RouteTableId": { 145 | "Ref": "RouteTable" 146 | } 147 | } 148 | }, 149 | "NetworkAcl": { 150 | "Type": "AWS::EC2::NetworkAcl", 151 | "Properties": { 152 | "VpcId": { 153 | "Ref": "VPC" 154 | }, 155 | "Tags": [ 156 | { 157 | "Key": "Name", 158 | "Value": "jenkins-multiaz" 159 | } 160 | ] 161 | } 162 | }, 163 | "NetworkAceSSH": { 164 | "Type": "AWS::EC2::NetworkAclEntry", 165 | "Properties": { 166 | "NetworkAclId": { 167 | "Ref": "NetworkAcl" 168 | }, 169 | "RuleNumber": "10", 170 | "Protocol": "6", 171 | "RuleAction": "allow", 172 | "Egress": "false", 173 | "CidrBlock": "0.0.0.0/0", 174 | "PortRange": { 175 | "From": "22", 176 | "To": "22" 177 | } 178 | } 179 | }, 180 | "NetworkAceJenkinsHTTP": { 181 | "Type": "AWS::EC2::NetworkAclEntry", 182 | "Properties": { 183 | "NetworkAclId": { 184 | "Ref": "NetworkAcl" 185 | }, 186 | "RuleNumber": "11", 187 | "Protocol": "6", 188 | "RuleAction": "allow", 189 | "Egress": "false", 190 | "CidrBlock": "0.0.0.0/0", 191 | "PortRange": { 192 | "From": "8080", 193 | "To": "8080" 194 | } 195 | } 196 | }, 197 | "NetworkAceNTP": { 198 | "Type": "AWS::EC2::NetworkAclEntry", 199 | "Properties": { 200 | "NetworkAclId": { 201 | "Ref": "NetworkAcl" 202 | }, 203 | "RuleNumber": "20", 204 | "Protocol": "17", 205 | "RuleAction": "allow", 206 | "Egress": "false", 207 | "CidrBlock": "0.0.0.0/0", 208 | "PortRange": { 209 | "From": "123", 210 | "To": "123" 211 | } 212 | } 213 | }, 214 | "NetworkAceICMP": { 215 | "Type": "AWS::EC2::NetworkAclEntry", 216 | "Properties": { 217 | "NetworkAclId": { 218 | "Ref": "NetworkAcl" 219 | }, 220 | "RuleNumber": "30", 221 | "Protocol": "1", 222 | "RuleAction": "allow", 223 | "Egress": "false", 224 | "CidrBlock": "0.0.0.0/0", 225 | "Icmp": { 226 | "Code": "-1", 227 | "Type": "-1" 228 | } 229 | } 230 | }, 231 | "NetworkAceHighPortsTCP": { 232 | "Type": "AWS::EC2::NetworkAclEntry", 233 | "Properties": { 234 | "NetworkAclId": { 235 | "Ref": "NetworkAcl" 236 | }, 237 | "RuleNumber": "40", 238 | "Protocol": "6", 239 | "RuleAction": "allow", 240 | "Egress": "false", 241 | "CidrBlock": "0.0.0.0/0", 242 | "PortRange": { 243 | "From": "1024", 244 | "To": "65535" 245 | } 246 | } 247 | }, 248 | "NetworkAceHighPortsUDP": { 249 | "Type": "AWS::EC2::NetworkAclEntry", 250 | "Properties": { 251 | "NetworkAclId": { 252 | "Ref": "NetworkAcl" 253 | }, 254 | "RuleNumber": "41", 255 | "Protocol": "17", 256 | "RuleAction": "allow", 257 | "Egress": "false", 258 | "CidrBlock": "0.0.0.0/0", 259 | "PortRange": { 260 | "From": "1024", 261 | "To": "65535" 262 | } 263 | } 264 | }, 265 | "NetworkAceEgress": { 266 | "Type": "AWS::EC2::NetworkAclEntry", 267 | "Properties": { 268 | "NetworkAclId": { 269 | "Ref": "NetworkAcl" 270 | }, 271 | "RuleNumber": "10", 272 | "Protocol": "-1", 273 | "RuleAction": "allow", 274 | "Egress": "true", 275 | "CidrBlock": "0.0.0.0/0", 276 | "PortRange": { 277 | "From": "0", 278 | "To": "65535" 279 | } 280 | } 281 | }, 282 | "NetworkAclAssociationA": { 283 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 284 | "Properties": { 285 | "SubnetId": { 286 | "Ref": "SubnetA" 287 | }, 288 | "NetworkAclId": { 289 | "Ref": "NetworkAcl" 290 | } 291 | } 292 | }, 293 | "NetworkAclAssociationB": { 294 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 295 | "Properties": { 296 | "SubnetId": { 297 | "Ref": "SubnetB" 298 | }, 299 | "NetworkAclId": { 300 | "Ref": "NetworkAcl" 301 | } 302 | } 303 | }, 304 | "SecurityGroupJenkins": { 305 | "Type": "AWS::EC2::SecurityGroup", 306 | "Properties": { 307 | "GroupDescription": "SecurityGroupforjenkins", 308 | "VpcId": { 309 | "Ref": "VPC" 310 | }, 311 | "Tags": [ 312 | { 313 | "Key": "Name", 314 | "Value": "jenkins-multiaz" 315 | } 316 | ], 317 | "SecurityGroupIngress": [ 318 | { 319 | "IpProtocol": "tcp", 320 | "FromPort": "22", 321 | "ToPort": "22", 322 | "CidrIp": "0.0.0.0/0" 323 | }, 324 | { 325 | "IpProtocol": "tcp", 326 | "FromPort": "8080", 327 | "ToPort": "8080", 328 | "CidrIp": "0.0.0.0/0" 329 | }, 330 | { 331 | "IpProtocol": "icmp", 332 | "FromPort": "-1", 333 | "ToPort": "-1", 334 | "CidrIp": "0.0.0.0/0" 335 | } 336 | ] 337 | } 338 | }, 339 | "IamRole": { 340 | "Type": "AWS::IAM::Role", 341 | "Properties": { 342 | "AssumeRolePolicyDocument": { 343 | "Version": "2012-10-17", 344 | "Statement": [ 345 | { 346 | "Effect": "Allow", 347 | "Principal": {"Service": ["ec2.amazonaws.com"] 348 | }, 349 | "Action": ["sts:AssumeRole"] 350 | } 351 | ] 352 | }, 353 | "Path": "/", 354 | "Policies": [ 355 | { 356 | "PolicyName": "root", 357 | "PolicyDocument": { 358 | "Version": "2012-10-17", 359 | "Statement": [ 360 | { 361 | "Action": ["ec2:AssociateAddress"], 362 | "Resource": ["*"], 363 | "Effect": "Allow" 364 | } 365 | ] 366 | } 367 | } 368 | ] 369 | } 370 | }, 371 | "IamInstanceProfile": { 372 | "Type": "AWS::IAM::InstanceProfile", 373 | "Properties": { 374 | "Path": "/", 375 | "Roles": [{"Ref": "IamRole"}] 376 | } 377 | }, 378 | "ElasticIP": { 379 | "Type": "AWS::EC2::EIP", 380 | "Properties": { 381 | "Domain": "vpc" 382 | }, 383 | "DependsOn": "GatewayToInternet" 384 | }, 385 | "LaunchConfiguration": { 386 | "Type": "AWS::AutoScaling::LaunchConfiguration", 387 | "Properties": { 388 | "InstanceMonitoring": false, 389 | "IamInstanceProfile": {"Ref": "IamInstanceProfile"}, 390 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 391 | "KeyName": {"Ref": "KeyName"}, 392 | "SecurityGroups": [{"Ref": "SecurityGroupJenkins"}], 393 | "AssociatePublicIpAddress": true, 394 | "InstanceType": "t2.micro", 395 | "UserData": { 396 | "Fn::Base64": { 397 | "Fn::Join": [ 398 | "", 399 | [ 400 | "#!/bin/bash -ex\n", 401 | "aws configure set default.region ", {"Ref": "AWS::Region"},"\n", 402 | "INSTANCE_ID=`curl -s http://169.254.169.254/latest/meta-data/instance-id`\n", 403 | "aws ec2 associate-address --instance-id $INSTANCE_ID --allocation-id ", {"Fn::GetAtt": ["ElasticIP", "AllocationId"]}, "\n", 404 | "wget http://pkg.jenkins-ci.org/redhat/jenkins-1.616-1.1.noarch.rpm\n", 405 | "rpm --install jenkins-1.616-1.1.noarch.rpm\n", 406 | "sed -i -e 's/JENKINS_ARGS=\\\"\\\"/JENKINS_ARGS=\\\"--argumentsRealm.passwd.admin=", {"Ref": "JenkinsAdminPassword"}, " --argumentsRealm.roles.admin=admin\\\"/g' /etc/sysconfig/jenkins\n", 407 | "echo \"1.0true\" > /var/lib/jenkins/config.xml\n", 408 | "service jenkins start\n" 409 | ] 410 | ] 411 | } 412 | } 413 | } 414 | }, 415 | "AutoScalingGroup": { 416 | "Type": "AWS::AutoScaling::AutoScalingGroup", 417 | "Properties": { 418 | "LaunchConfigurationName": {"Ref": "LaunchConfiguration"}, 419 | "Tags": [ 420 | { 421 | "Key": "Name", 422 | "Value": "jenkins-elasticip", 423 | "PropagateAtLaunch": true 424 | } 425 | ], 426 | "DesiredCapacity": 1, 427 | "MinSize": 1, 428 | "MaxSize": 1, 429 | "VPCZoneIdentifier": [{"Ref": "SubnetA"}, {"Ref": "SubnetB"}], 430 | "HealthCheckGracePeriod": 600, 431 | "HealthCheckType": "EC2" 432 | }, 433 | "DependsOn": "GatewayToInternet" 434 | } 435 | }, 436 | "Outputs": { 437 | "JenkinsURL": { 438 | "Description": "URL to access web interface of Jenkins server.", 439 | "Value": {"Fn::Join": ["", ["http://", {"Ref": "ElasticIP"}, ":8080"]]} 440 | }, 441 | "User": { 442 | "Description": "Administrator user for Jenkins.", 443 | "Value": "admin" 444 | }, 445 | "Password": { 446 | "Description": "Password for Jenkins administrator user.", 447 | "Value": {"Ref": "JenkinsAdminPassword"} 448 | } 449 | } 450 | } -------------------------------------------------------------------------------- /chapter11/multiaz.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 11 (Jenkins (CI server) running with Auto Scaling Group over multiple AZs)", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "JenkinsAdminPassword": { 11 | "Description": "Password for Jenkins admin user", 12 | "Type": "String", 13 | "AllowedPattern" : "[a-zA-Z0-9]*", 14 | "MinLength" : "8", 15 | "MaxLength" : "42" 16 | } 17 | }, 18 | "Mappings": { 19 | "EC2RegionMap": { 20 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 21 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 22 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 23 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 24 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 25 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 26 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 27 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 28 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 29 | } 30 | }, 31 | "Resources": { 32 | "VPC": { 33 | "Type": "AWS::EC2::VPC", 34 | "Properties": { 35 | "EnableDnsSupport": "true", 36 | "EnableDnsHostnames": "true", 37 | "CidrBlock": "10.0.0.0/16", 38 | "Tags": [ 39 | { 40 | "Key": "Name", 41 | "Value": "jenkins-multiaz" 42 | } 43 | ] 44 | } 45 | }, 46 | "SubnetA": { 47 | "Type": "AWS::EC2::Subnet", 48 | "Properties": { 49 | "VpcId": { 50 | "Ref": "VPC" 51 | }, 52 | "AvailabilityZone": {"Fn::Select": ["0", {"Fn::GetAZs": ""}]}, 53 | "CidrBlock": "10.0.0.0/24", 54 | "Tags": [ 55 | { 56 | "Key": "Name", 57 | "Value": "jenkins-multiaz" 58 | } 59 | ] 60 | } 61 | }, 62 | "SubnetB": { 63 | "Type": "AWS::EC2::Subnet", 64 | "Properties": { 65 | "VpcId": { 66 | "Ref": "VPC" 67 | }, 68 | "AvailabilityZone": {"Fn::Select": ["1", {"Fn::GetAZs": ""}]}, 69 | "CidrBlock": "10.0.1.0/24", 70 | "Tags": [ 71 | { 72 | "Key": "Name", 73 | "Value": "jenkins-multiaz" 74 | } 75 | ] 76 | } 77 | }, 78 | "InternetGateway": { 79 | "Type": "AWS::EC2::InternetGateway", 80 | "Properties": { 81 | "Tags": [ 82 | { 83 | "Key": "Name", 84 | "Value": "jenkins-multiaz" 85 | } 86 | ] 87 | } 88 | }, 89 | "GatewayToInternet": { 90 | "Type": "AWS::EC2::VPCGatewayAttachment", 91 | "Properties": { 92 | "VpcId": { 93 | "Ref": "VPC" 94 | }, 95 | "InternetGatewayId": { 96 | "Ref": "InternetGateway" 97 | } 98 | } 99 | }, 100 | "RouteTable": { 101 | "Type": "AWS::EC2::RouteTable", 102 | "Properties": { 103 | "VpcId": { 104 | "Ref": "VPC" 105 | }, 106 | "Tags": [ 107 | { 108 | "Key": "Name", 109 | "Value": "jenkins-multiaz" 110 | } 111 | ] 112 | } 113 | }, 114 | "InternetRoute": { 115 | "Type": "AWS::EC2::Route", 116 | "Properties": { 117 | "RouteTableId": { 118 | "Ref": "RouteTable" 119 | }, 120 | "DestinationCidrBlock": "0.0.0.0/0", 121 | "GatewayId": { 122 | "Ref": "InternetGateway" 123 | } 124 | }, 125 | "DependsOn": "GatewayToInternet" 126 | }, 127 | "RouteTableAssociationA": { 128 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 129 | "Properties": { 130 | "SubnetId": { 131 | "Ref": "SubnetA" 132 | }, 133 | "RouteTableId": { 134 | "Ref": "RouteTable" 135 | } 136 | } 137 | }, 138 | "RouteTableAssociationB": { 139 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 140 | "Properties": { 141 | "SubnetId": { 142 | "Ref": "SubnetB" 143 | }, 144 | "RouteTableId": { 145 | "Ref": "RouteTable" 146 | } 147 | } 148 | }, 149 | "NetworkAcl": { 150 | "Type": "AWS::EC2::NetworkAcl", 151 | "Properties": { 152 | "VpcId": { 153 | "Ref": "VPC" 154 | }, 155 | "Tags": [ 156 | { 157 | "Key": "Name", 158 | "Value": "jenkins-multiaz" 159 | } 160 | ] 161 | } 162 | }, 163 | "NetworkAceSSH": { 164 | "Type": "AWS::EC2::NetworkAclEntry", 165 | "Properties": { 166 | "NetworkAclId": { 167 | "Ref": "NetworkAcl" 168 | }, 169 | "RuleNumber": "10", 170 | "Protocol": "6", 171 | "RuleAction": "allow", 172 | "Egress": "false", 173 | "CidrBlock": "0.0.0.0/0", 174 | "PortRange": { 175 | "From": "22", 176 | "To": "22" 177 | } 178 | } 179 | }, 180 | "NetworkAceJenkinsHTTP": { 181 | "Type": "AWS::EC2::NetworkAclEntry", 182 | "Properties": { 183 | "NetworkAclId": { 184 | "Ref": "NetworkAcl" 185 | }, 186 | "RuleNumber": "11", 187 | "Protocol": "6", 188 | "RuleAction": "allow", 189 | "Egress": "false", 190 | "CidrBlock": "0.0.0.0/0", 191 | "PortRange": { 192 | "From": "8080", 193 | "To": "8080" 194 | } 195 | } 196 | }, 197 | "NetworkAceNTP": { 198 | "Type": "AWS::EC2::NetworkAclEntry", 199 | "Properties": { 200 | "NetworkAclId": { 201 | "Ref": "NetworkAcl" 202 | }, 203 | "RuleNumber": "20", 204 | "Protocol": "17", 205 | "RuleAction": "allow", 206 | "Egress": "false", 207 | "CidrBlock": "0.0.0.0/0", 208 | "PortRange": { 209 | "From": "123", 210 | "To": "123" 211 | } 212 | } 213 | }, 214 | "NetworkAceICMP": { 215 | "Type": "AWS::EC2::NetworkAclEntry", 216 | "Properties": { 217 | "NetworkAclId": { 218 | "Ref": "NetworkAcl" 219 | }, 220 | "RuleNumber": "30", 221 | "Protocol": "1", 222 | "RuleAction": "allow", 223 | "Egress": "false", 224 | "CidrBlock": "0.0.0.0/0", 225 | "Icmp": { 226 | "Code": "-1", 227 | "Type": "-1" 228 | } 229 | } 230 | }, 231 | "NetworkAceHighPortsTCP": { 232 | "Type": "AWS::EC2::NetworkAclEntry", 233 | "Properties": { 234 | "NetworkAclId": { 235 | "Ref": "NetworkAcl" 236 | }, 237 | "RuleNumber": "40", 238 | "Protocol": "6", 239 | "RuleAction": "allow", 240 | "Egress": "false", 241 | "CidrBlock": "0.0.0.0/0", 242 | "PortRange": { 243 | "From": "1024", 244 | "To": "65535" 245 | } 246 | } 247 | }, 248 | "NetworkAceHighPortsUDP": { 249 | "Type": "AWS::EC2::NetworkAclEntry", 250 | "Properties": { 251 | "NetworkAclId": { 252 | "Ref": "NetworkAcl" 253 | }, 254 | "RuleNumber": "41", 255 | "Protocol": "17", 256 | "RuleAction": "allow", 257 | "Egress": "false", 258 | "CidrBlock": "0.0.0.0/0", 259 | "PortRange": { 260 | "From": "1024", 261 | "To": "65535" 262 | } 263 | } 264 | }, 265 | "NetworkAceEgress": { 266 | "Type": "AWS::EC2::NetworkAclEntry", 267 | "Properties": { 268 | "NetworkAclId": { 269 | "Ref": "NetworkAcl" 270 | }, 271 | "RuleNumber": "10", 272 | "Protocol": "-1", 273 | "RuleAction": "allow", 274 | "Egress": "true", 275 | "CidrBlock": "0.0.0.0/0", 276 | "PortRange": { 277 | "From": "0", 278 | "To": "65535" 279 | } 280 | } 281 | }, 282 | "NetworkAclAssociationA": { 283 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 284 | "Properties": { 285 | "SubnetId": { 286 | "Ref": "SubnetA" 287 | }, 288 | "NetworkAclId": { 289 | "Ref": "NetworkAcl" 290 | } 291 | } 292 | }, 293 | "NetworkAclAssociationB": { 294 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 295 | "Properties": { 296 | "SubnetId": { 297 | "Ref": "SubnetB" 298 | }, 299 | "NetworkAclId": { 300 | "Ref": "NetworkAcl" 301 | } 302 | } 303 | }, 304 | "SecurityGroupJenkins": { 305 | "Type": "AWS::EC2::SecurityGroup", 306 | "Properties": { 307 | "GroupDescription": "SecurityGroupforjenkins", 308 | "VpcId": { 309 | "Ref": "VPC" 310 | }, 311 | "Tags": [ 312 | { 313 | "Key": "Name", 314 | "Value": "jenkins-multiaz" 315 | } 316 | ], 317 | "SecurityGroupIngress": [ 318 | { 319 | "IpProtocol": "tcp", 320 | "FromPort": "22", 321 | "ToPort": "22", 322 | "CidrIp": "0.0.0.0/0" 323 | }, 324 | { 325 | "IpProtocol": "tcp", 326 | "FromPort": "8080", 327 | "ToPort": "8080", 328 | "CidrIp": "0.0.0.0/0" 329 | }, 330 | { 331 | "IpProtocol": "icmp", 332 | "FromPort": "-1", 333 | "ToPort": "-1", 334 | "CidrIp": "0.0.0.0/0" 335 | } 336 | ] 337 | } 338 | }, 339 | "LaunchConfiguration": { 340 | "Type": "AWS::AutoScaling::LaunchConfiguration", 341 | "Properties": { 342 | "InstanceMonitoring": false, 343 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 344 | "KeyName": {"Ref": "KeyName"}, 345 | "SecurityGroups": [{"Ref": "SecurityGroupJenkins"}], 346 | "AssociatePublicIpAddress": true, 347 | "InstanceType": "t2.micro", 348 | "UserData": { 349 | "Fn::Base64": { 350 | "Fn::Join": [ 351 | "", 352 | [ 353 | "#!/bin/bash -ex\n", 354 | "wget http://pkg.jenkins-ci.org/redhat/jenkins-1.616-1.1.noarch.rpm\n", 355 | "rpm --install jenkins-1.616-1.1.noarch.rpm\n", 356 | "sed -i -e 's/JENKINS_ARGS=\\\"\\\"/JENKINS_ARGS=\\\"--argumentsRealm.passwd.admin=", {"Ref": "JenkinsAdminPassword"}, " --argumentsRealm.roles.admin=admin\\\"/g' /etc/sysconfig/jenkins\n", 357 | "echo \"1.0true\" > /var/lib/jenkins/config.xml\n", 358 | "service jenkins start\n" 359 | ] 360 | ] 361 | } 362 | } 363 | } 364 | }, 365 | "AutoScalingGroup": { 366 | "Type": "AWS::AutoScaling::AutoScalingGroup", 367 | "Properties": { 368 | "LaunchConfigurationName": {"Ref": "LaunchConfiguration"}, 369 | "Tags": [ 370 | { 371 | "Key": "Name", 372 | "Value": "jenkins-multiaz", 373 | "PropagateAtLaunch": true 374 | } 375 | ], 376 | "DesiredCapacity": 1, 377 | "MinSize": 1, 378 | "MaxSize": 1, 379 | "VPCZoneIdentifier": [{"Ref": "SubnetA"}, {"Ref": "SubnetB"}], 380 | "HealthCheckGracePeriod": 600, 381 | "HealthCheckType": "EC2" 382 | }, 383 | "DependsOn": "GatewayToInternet" 384 | } 385 | } 386 | } -------------------------------------------------------------------------------- /chapter11/recovery.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 11 (Jenkins (CI server) running on EC2 with AWS CloudWatch recovery)", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "JenkinsAdminPassword": { 11 | "Description": "Password for Jenkins admin user", 12 | "Type": "String", 13 | "AllowedPattern" : "[a-zA-Z0-9]*", 14 | "MinLength" : "8", 15 | "MaxLength" : "42" 16 | } 17 | }, 18 | "Mappings": { 19 | "EC2RegionMap": { 20 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 21 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 22 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 23 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 24 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 25 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 26 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 27 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 28 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 29 | } 30 | }, 31 | "Resources": { 32 | "VPC": { 33 | "Type": "AWS::EC2::VPC", 34 | "Properties": { 35 | "EnableDnsSupport": "true", 36 | "EnableDnsHostnames": "true", 37 | "CidrBlock": "10.0.0.0/16", 38 | "Tags": [ 39 | { 40 | "Key": "Name", 41 | "Value": "jenkins-recovery" 42 | } 43 | ] 44 | } 45 | }, 46 | "Subnet": { 47 | "Type": "AWS::EC2::Subnet", 48 | "Properties": { 49 | "VpcId": { 50 | "Ref": "VPC" 51 | }, 52 | "AvailabilityZone": {"Fn::Select": ["0", {"Fn::GetAZs": ""}]}, 53 | "CidrBlock": "10.0.0.0/24", 54 | "Tags": [ 55 | { 56 | "Key": "Name", 57 | "Value": "jenkins-recovery" 58 | } 59 | ] 60 | } 61 | }, 62 | "InternetGateway": { 63 | "Type": "AWS::EC2::InternetGateway", 64 | "Properties": { 65 | "Tags": [ 66 | { 67 | "Key": "Name", 68 | "Value": "jenkins-recovery" 69 | } 70 | ] 71 | } 72 | }, 73 | "GatewayToInternet": { 74 | "Type": "AWS::EC2::VPCGatewayAttachment", 75 | "Properties": { 76 | "VpcId": { 77 | "Ref": "VPC" 78 | }, 79 | "InternetGatewayId": { 80 | "Ref": "InternetGateway" 81 | } 82 | } 83 | }, 84 | "RouteTable": { 85 | "Type": "AWS::EC2::RouteTable", 86 | "Properties": { 87 | "VpcId": { 88 | "Ref": "VPC" 89 | }, 90 | "Tags": [ 91 | { 92 | "Key": "Name", 93 | "Value": "jenkins-recovery" 94 | } 95 | ] 96 | } 97 | }, 98 | "InternetRoute": { 99 | "Type": "AWS::EC2::Route", 100 | "Properties": { 101 | "RouteTableId": { 102 | "Ref": "RouteTable" 103 | }, 104 | "DestinationCidrBlock": "0.0.0.0/0", 105 | "GatewayId": { 106 | "Ref": "InternetGateway" 107 | } 108 | }, 109 | "DependsOn": "GatewayToInternet" 110 | }, 111 | "RouteTableAssociation": { 112 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 113 | "Properties": { 114 | "SubnetId": { 115 | "Ref": "Subnet" 116 | }, 117 | "RouteTableId": { 118 | "Ref": "RouteTable" 119 | } 120 | } 121 | }, 122 | "NetworkAcl": { 123 | "Type": "AWS::EC2::NetworkAcl", 124 | "Properties": { 125 | "VpcId": { 126 | "Ref": "VPC" 127 | }, 128 | "Tags": [ 129 | { 130 | "Key": "Name", 131 | "Value": "jenkins-recovery" 132 | } 133 | ] 134 | } 135 | }, 136 | "NetworkAceSSH": { 137 | "Type": "AWS::EC2::NetworkAclEntry", 138 | "Properties": { 139 | "NetworkAclId": { 140 | "Ref": "NetworkAcl" 141 | }, 142 | "RuleNumber": "10", 143 | "Protocol": "6", 144 | "RuleAction": "allow", 145 | "Egress": "false", 146 | "CidrBlock": "0.0.0.0/0", 147 | "PortRange": { 148 | "From": "22", 149 | "To": "22" 150 | } 151 | } 152 | }, 153 | "NetworkAceJenkinsHTTP": { 154 | "Type": "AWS::EC2::NetworkAclEntry", 155 | "Properties": { 156 | "NetworkAclId": { 157 | "Ref": "NetworkAcl" 158 | }, 159 | "RuleNumber": "11", 160 | "Protocol": "6", 161 | "RuleAction": "allow", 162 | "Egress": "false", 163 | "CidrBlock": "0.0.0.0/0", 164 | "PortRange": { 165 | "From": "8080", 166 | "To": "8080" 167 | } 168 | } 169 | }, 170 | "NetworkAceNTP": { 171 | "Type": "AWS::EC2::NetworkAclEntry", 172 | "Properties": { 173 | "NetworkAclId": { 174 | "Ref": "NetworkAcl" 175 | }, 176 | "RuleNumber": "20", 177 | "Protocol": "17", 178 | "RuleAction": "allow", 179 | "Egress": "false", 180 | "CidrBlock": "0.0.0.0/0", 181 | "PortRange": { 182 | "From": "123", 183 | "To": "123" 184 | } 185 | } 186 | }, 187 | "NetworkAceICMP": { 188 | "Type": "AWS::EC2::NetworkAclEntry", 189 | "Properties": { 190 | "NetworkAclId": { 191 | "Ref": "NetworkAcl" 192 | }, 193 | "RuleNumber": "30", 194 | "Protocol": "1", 195 | "RuleAction": "allow", 196 | "Egress": "false", 197 | "CidrBlock": "0.0.0.0/0", 198 | "Icmp": { 199 | "Code": "-1", 200 | "Type": "-1" 201 | } 202 | } 203 | }, 204 | "NetworkAceHighPortsTCP": { 205 | "Type": "AWS::EC2::NetworkAclEntry", 206 | "Properties": { 207 | "NetworkAclId": { 208 | "Ref": "NetworkAcl" 209 | }, 210 | "RuleNumber": "40", 211 | "Protocol": "6", 212 | "RuleAction": "allow", 213 | "Egress": "false", 214 | "CidrBlock": "0.0.0.0/0", 215 | "PortRange": { 216 | "From": "1024", 217 | "To": "65535" 218 | } 219 | } 220 | }, 221 | "NetworkAceHighPortsUDP": { 222 | "Type": "AWS::EC2::NetworkAclEntry", 223 | "Properties": { 224 | "NetworkAclId": { 225 | "Ref": "NetworkAcl" 226 | }, 227 | "RuleNumber": "41", 228 | "Protocol": "17", 229 | "RuleAction": "allow", 230 | "Egress": "false", 231 | "CidrBlock": "0.0.0.0/0", 232 | "PortRange": { 233 | "From": "1024", 234 | "To": "65535" 235 | } 236 | } 237 | }, 238 | "NetworkAceEgress": { 239 | "Type": "AWS::EC2::NetworkAclEntry", 240 | "Properties": { 241 | "NetworkAclId": { 242 | "Ref": "NetworkAcl" 243 | }, 244 | "RuleNumber": "10", 245 | "Protocol": "-1", 246 | "RuleAction": "allow", 247 | "Egress": "true", 248 | "CidrBlock": "0.0.0.0/0", 249 | "PortRange": { 250 | "From": "0", 251 | "To": "65535" 252 | } 253 | } 254 | }, 255 | "NetworkAclAssociation": { 256 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 257 | "Properties": { 258 | "SubnetId": { 259 | "Ref": "Subnet" 260 | }, 261 | "NetworkAclId": { 262 | "Ref": "NetworkAcl" 263 | } 264 | } 265 | }, 266 | "SecurityGroup": { 267 | "Type": "AWS::EC2::SecurityGroup", 268 | "Properties": { 269 | "GroupDescription": "SecurityGroupforjenkins", 270 | "VpcId": { 271 | "Ref": "VPC" 272 | }, 273 | "Tags": [ 274 | { 275 | "Key": "Name", 276 | "Value": "jenkins-recovery" 277 | } 278 | ], 279 | "SecurityGroupIngress": [ 280 | { 281 | "IpProtocol": "tcp", 282 | "FromPort": "22", 283 | "ToPort": "22", 284 | "CidrIp": "0.0.0.0/0" 285 | }, 286 | { 287 | "IpProtocol": "tcp", 288 | "FromPort": "8080", 289 | "ToPort": "8080", 290 | "CidrIp": "0.0.0.0/0" 291 | }, 292 | { 293 | "IpProtocol": "icmp", 294 | "FromPort": "-1", 295 | "ToPort": "-1", 296 | "CidrIp": "0.0.0.0/0" 297 | } 298 | ] 299 | } 300 | }, 301 | "ElasticIP": { 302 | "Type": "AWS::EC2::EIP", 303 | "Properties": { 304 | "InstanceId": {"Ref": "Server"}, 305 | "Domain": "vpc" 306 | }, 307 | "DependsOn": "GatewayToInternet" 308 | }, 309 | "Server": { 310 | "Type": "AWS::EC2::Instance", 311 | "Properties": { 312 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 313 | "InstanceType": "t2.micro", 314 | "KeyName": {"Ref": "KeyName"}, 315 | "SecurityGroupIds": [{"Ref": "SecurityGroup"}], 316 | "SubnetId": {"Ref": "Subnet"}, 317 | "UserData": {"Fn::Base64": {"Fn::Join": ["", [ 318 | "#!/bin/bash -ex\n", 319 | "wget http://pkg.jenkins-ci.org/redhat/jenkins-1.616-1.1.noarch.rpm\n", 320 | "rpm --install jenkins-1.616-1.1.noarch.rpm\n", 321 | "sed -i -e 's/JENKINS_ARGS=\\\"\\\"/JENKINS_ARGS=\\\"--argumentsRealm.passwd.admin=", {"Ref": "JenkinsAdminPassword"}, " --argumentsRealm.roles.admin=admin\\\"/g' /etc/sysconfig/jenkins\n", 322 | "echo \"1.0true\" > /var/lib/jenkins/config.xml\n", 323 | "service jenkins start\n" 324 | ]]}}, 325 | "Tags": [ 326 | { 327 | "Key": "Name", 328 | "Value": "jenkins-recovery" 329 | } 330 | ] 331 | }, 332 | "DependsOn": "GatewayToInternet" 333 | }, 334 | "RecoveryAlarm": { 335 | "Type": "AWS::CloudWatch::Alarm", 336 | "Properties": { 337 | "AlarmDescription": "Recover server when underlying hardware fails.", 338 | "Namespace": "AWS/EC2" , 339 | "MetricName": "StatusCheckFailed_System", 340 | "Statistic": "Minimum", 341 | "Period": "60", 342 | "EvaluationPeriods": "5", 343 | "ComparisonOperator": "GreaterThanThreshold", 344 | "Threshold": "0", 345 | "AlarmActions": [{"Fn::Join": ["", ["arn:aws:automate:", { "Ref": "AWS::Region"}, ":ec2:recover"]]}], 346 | "Dimensions": [{"Name": "InstanceId", "Value": {"Ref": "Server"}}] 347 | } 348 | } 349 | }, 350 | "Outputs": { 351 | "JenkinsURL": { 352 | "Description": "URL to access web interface of Jenkins server.", 353 | "Value": {"Fn::Join": ["", ["http://", {"Ref": "ElasticIP"}, ":8080"]]} 354 | }, 355 | "User": { 356 | "Description": "Administrator user for Jenkins.", 357 | "Value": "admin" 358 | }, 359 | "Password": { 360 | "Description": "Password for Jenkins administrator user.", 361 | "Value": {"Ref": "JenkinsAdminPassword"} 362 | } 363 | } 364 | } -------------------------------------------------------------------------------- /chapter12/loadbalancer.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 12 (Load Balancer)", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "NumberOfServers": { 11 | "Description": "Number of servers", 12 | "Type": "Number", 13 | "Default": "2", 14 | "MinValue": "2", 15 | "MaxValue": "4" 16 | } 17 | }, 18 | "Mappings": { 19 | "EC2RegionMap": { 20 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 21 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 22 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 23 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 24 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 25 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 26 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 27 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 28 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 29 | } 30 | }, 31 | "Resources": { 32 | "VPC": { 33 | "Type": "AWS::EC2::VPC", 34 | "Properties": { 35 | "CidrBlock": "172.31.0.0/16", 36 | "EnableDnsHostnames": "true" 37 | } 38 | }, 39 | "InternetGateway": { 40 | "Type": "AWS::EC2::InternetGateway", 41 | "Properties": { 42 | } 43 | }, 44 | "VPCGatewayAttachment": { 45 | "Type": "AWS::EC2::VPCGatewayAttachment", 46 | "Properties": { 47 | "VpcId": {"Ref": "VPC"}, 48 | "InternetGatewayId": {"Ref": "InternetGateway"} 49 | } 50 | }, 51 | "Subnet": { 52 | "Type": "AWS::EC2::Subnet", 53 | "Properties": { 54 | "AvailabilityZone": {"Fn::Select": ["0", {"Fn::GetAZs": ""}]}, 55 | "CidrBlock": "172.31.38.0/24", 56 | "VpcId": {"Ref": "VPC"} 57 | } 58 | }, 59 | "RouteTable": { 60 | "Type": "AWS::EC2::RouteTable", 61 | "Properties": { 62 | "VpcId": {"Ref": "VPC"} 63 | } 64 | }, 65 | "RouteTableAssociation": { 66 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 67 | "Properties": { 68 | "SubnetId": {"Ref": "Subnet"}, 69 | "RouteTableId": {"Ref": "RouteTable"} 70 | } 71 | }, 72 | "RoutePublicNATToInternet": { 73 | "Type": "AWS::EC2::Route", 74 | "Properties": { 75 | "RouteTableId": {"Ref": "RouteTable"}, 76 | "DestinationCidrBlock": "0.0.0.0/0", 77 | "GatewayId": {"Ref": "InternetGateway"} 78 | }, 79 | "DependsOn": "VPCGatewayAttachment" 80 | }, 81 | "NetworkAcl": { 82 | "Type": "AWS::EC2::NetworkAcl", 83 | "Properties": { 84 | "VpcId": {"Ref": "VPC"} 85 | } 86 | }, 87 | "SubnetNetworkAclAssociation": { 88 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 89 | "Properties": { 90 | "SubnetId": {"Ref": "Subnet"}, 91 | "NetworkAclId": {"Ref": "NetworkAcl"} 92 | } 93 | }, 94 | "NetworkAclEntryIngress": { 95 | "Type": "AWS::EC2::NetworkAclEntry", 96 | "Properties": { 97 | "NetworkAclId": {"Ref": "NetworkAcl"}, 98 | "RuleNumber": "100", 99 | "Protocol": "-1", 100 | "RuleAction": "allow", 101 | "Egress": "false", 102 | "CidrBlock": "0.0.0.0/0" 103 | } 104 | }, 105 | "NetworkAclEntryEgress": { 106 | "Type": "AWS::EC2::NetworkAclEntry", 107 | "Properties": { 108 | "NetworkAclId": {"Ref": "NetworkAcl"}, 109 | "RuleNumber": "100", 110 | "Protocol": "-1", 111 | "RuleAction": "allow", 112 | "Egress": "true", 113 | "CidrBlock": "0.0.0.0/0" 114 | } 115 | }, 116 | "LoadBalancerSecurityGroup": { 117 | "Type": "AWS::EC2::SecurityGroup", 118 | "Properties": { 119 | "GroupDescription": "elb-sg", 120 | "VpcId": {"Ref": "VPC"}, 121 | "SecurityGroupIngress": [{ 122 | "CidrIp": "0.0.0.0/0", 123 | "FromPort": 80, 124 | "IpProtocol": "tcp", 125 | "ToPort": 80 126 | }] 127 | } 128 | }, 129 | "LoadBalancer": { 130 | "Type": "AWS::ElasticLoadBalancing::LoadBalancer", 131 | "Properties": { 132 | "Subnets": [{"Ref": "Subnet"}], 133 | "LoadBalancerName": "elb", 134 | "Listeners": [{ 135 | "InstancePort": "80", 136 | "InstanceProtocol": "HTTP", 137 | "LoadBalancerPort": "80", 138 | "Protocol": "HTTP" 139 | }], 140 | "HealthCheck": { 141 | "HealthyThreshold": "3", 142 | "Interval": "10", 143 | "Target": "HTTP:80/index.html", 144 | "Timeout": "5", 145 | "UnhealthyThreshold": "2" 146 | }, 147 | "SecurityGroups": [{"Ref": "LoadBalancerSecurityGroup"}], 148 | "Scheme": "internet-facing" 149 | }, 150 | "DependsOn": "VPCGatewayAttachment" 151 | }, 152 | "WebServerSecurityGroup": { 153 | "Type": "AWS::EC2::SecurityGroup", 154 | "Properties": { 155 | "GroupDescription": "awsinaction-sg", 156 | "VpcId": {"Ref": "VPC"}, 157 | "SecurityGroupIngress": [{ 158 | "CidrIp": "0.0.0.0/0", 159 | "FromPort": 22, 160 | "IpProtocol": "tcp", 161 | "ToPort": 22 162 | }, { 163 | "FromPort": 80, 164 | "IpProtocol": "tcp", 165 | "SourceSecurityGroupId": {"Ref": "LoadBalancerSecurityGroup"}, 166 | "ToPort": 80 167 | }] 168 | } 169 | }, 170 | "LaunchConfiguration": { 171 | "Type": "AWS::AutoScaling::LaunchConfiguration", 172 | "Metadata": { 173 | "AWS::CloudFormation::Init": { 174 | "config": { 175 | "packages": { 176 | "yum": { 177 | "httpd": [] 178 | } 179 | }, 180 | "files": { 181 | "/tmp/config": { 182 | "content": {"Fn::Join": ["", [ 183 | "#!/bin/bash -ex\n", 184 | "PRIVATE_IP=`curl -s http://169.254.169.254/latest/meta-data/local-ipv4`\n", 185 | "echo \"$PRIVATE_IP

$PRIVATE_IP

\" > index.html\n" 186 | ]]}, 187 | "mode": "000500", 188 | "owner": "root", 189 | "group": "root" 190 | } 191 | }, 192 | "commands": { 193 | "01_config": { 194 | "command": "/tmp/config", 195 | "cwd": "/var/www/html" 196 | } 197 | }, 198 | "services": { 199 | "sysvinit": { 200 | "httpd": { 201 | "enabled": "true", 202 | "ensureRunning": "true" 203 | } 204 | } 205 | } 206 | } 207 | } 208 | }, 209 | "Properties": { 210 | "EbsOptimized": false, 211 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 212 | "InstanceType": "t2.micro", 213 | "SecurityGroups": [{"Ref": "WebServerSecurityGroup"}], 214 | "KeyName": {"Ref": "KeyName"}, 215 | "AssociatePublicIpAddress": true, 216 | "UserData": {"Fn::Base64": {"Fn::Join": ["", [ 217 | "#!/bin/bash -ex\n", 218 | "yum update -y aws-cfn-bootstrap\n", 219 | "/opt/aws/bin/cfn-init -v --stack ", {"Ref": "AWS::StackName"}, " --resource LaunchConfiguration --region ", {"Ref": "AWS::Region"}, "\n", 220 | "/opt/aws/bin/cfn-signal -e $? --stack ", {"Ref": "AWS::StackName"}, " --resource AutoScalingGroup --region ", {"Ref": "AWS::Region"}, "\n" 221 | ]]}} 222 | } 223 | }, 224 | "AutoScalingGroup": { 225 | "Type": "AWS::AutoScaling::AutoScalingGroup", 226 | "Properties": { 227 | "LoadBalancerNames": [{"Ref": "LoadBalancer"}], 228 | "LaunchConfigurationName": {"Ref": "LaunchConfiguration"}, 229 | "MinSize": {"Ref": "NumberOfServers"}, 230 | "MaxSize": {"Ref": "NumberOfServers"}, 231 | "DesiredCapacity": {"Ref": "NumberOfServers"}, 232 | "VPCZoneIdentifier": [{"Ref": "Subnet"}] 233 | }, 234 | "CreationPolicy": { 235 | "ResourceSignal": { 236 | "Timeout": "PT10M" 237 | } 238 | }, 239 | "DependsOn": "VPCGatewayAttachment" 240 | } 241 | }, 242 | "Outputs": { 243 | "URL": { 244 | "Value": {"Fn::Join": ["", ["http://", {"Fn::GetAtt": ["LoadBalancer", "DNSName"]}]]}, 245 | "Description": "Load Balancer URL" 246 | } 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /chapter12/policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Id": "Policy1429136655940", 3 | "Version": "2012-10-17", 4 | "Statement": [{ 5 | "Sid": "Stmt1429136633762", 6 | "Action": ["s3:PutObject"], 7 | "Effect": "Allow", 8 | "Resource": "arn:aws:s3:::elb-logging-bucket-$YourName/*", 9 | "Principal": { 10 | "AWS": [ 11 | "127311923021", "027434742980", "797873946194", 12 | "156460612806", "054676820928", "582318560864", 13 | "114774131450", "783225319266", "507241528517" 14 | ] 15 | } 16 | }] 17 | } 18 | -------------------------------------------------------------------------------- /chapter12/url2png/README.md: -------------------------------------------------------------------------------- 1 | # URL2PNG 2 | 3 | ![URL2PNG](./url2png.png?raw=true "URL2PNG") 4 | 5 | Install the dependencies ... 6 | 7 | $ npm install 8 | 9 | ... and create a S3 bucket 10 | 11 | $ aws s3 mb s3://url2png 12 | 13 | ... and activate web hosting for bucket 14 | 15 | $ aws s3 website s3://url2png --index-document index.html --error-document error.html 16 | 17 | ... and create a SQS message queue with the help of the AWS CLI 18 | 19 | $ aws sqs create-queue --queue-name url2png 20 | { 21 | "QueueUrl": "https://queue.amazonaws.com/878533158213/url2png" 22 | } 23 | 24 | ... edit config.json and set QueueUrl and Bucket 25 | 26 | ... and run the URL2PNG worker 27 | 28 | $ node worker.js 29 | 30 | ... open another terminal and start a URL2PNG process 31 | 32 | $ node index.js "http://aws.amazon.com/" 33 | PNG will be soon available at http://aws-in-action-url2png.s3-website-us-east-1.amazonaws.com/6dbe4a05-82b3-4cbd-bd2b-65bbc8a51539.png 34 | 35 | ... wait and open the image 36 | -------------------------------------------------------------------------------- /chapter12/url2png/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "QueueUrl": "https://queue.amazonaws.com/123456789/url2png", 3 | "Bucket": "url2png-YourName" 4 | } 5 | -------------------------------------------------------------------------------- /chapter12/url2png/index.js: -------------------------------------------------------------------------------- 1 | var AWS = require('aws-sdk'); 2 | var uuid = require('node-uuid'); 3 | var config = require('./config.json'); 4 | var sqs = new AWS.SQS({ 5 | "region": "us-east-1" 6 | }); 7 | 8 | if (process.argv.length !== 3) { 9 | console.log('URL missing'); 10 | process.exit(1); 11 | } 12 | 13 | var id = uuid.v4(); 14 | var body = { 15 | "id": id, 16 | "url": process.argv[2] 17 | }; 18 | 19 | sqs.sendMessage({ 20 | "MessageBody": JSON.stringify(body), 21 | "QueueUrl": config.QueueUrl 22 | }, function(err) { 23 | if (err) { 24 | console.log('error', err); 25 | } else { 26 | console.log('PNG will be soon available at http://' + config.Bucket + '.s3-website-us-east-1.amazonaws.com/' + id + '.png'); 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /chapter12/url2png/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "aws-sdk": "2.1.18", 4 | "node-uuid": "1.4.3", 5 | "webshot": "0.16.0" 6 | }, 7 | "private": true 8 | } 9 | -------------------------------------------------------------------------------- /chapter12/url2png/url2png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AWSinAction/code/9acfdeb3642a3b7e1af82c7bc78bdf10ac2bd9f3/chapter12/url2png/url2png.png -------------------------------------------------------------------------------- /chapter12/url2png/worker.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var AWS = require('aws-sdk'); 3 | var webshot = require('webshot'); 4 | var config = require('./config.json'); 5 | var sqs = new AWS.SQS({ 6 | "region": "us-east-1" 7 | }); 8 | var s3 = new AWS.S3({ 9 | "region": "us-east-1" 10 | }); 11 | 12 | function acknowledge(message, cb) { 13 | var params = { 14 | "QueueUrl": config.QueueUrl, 15 | "ReceiptHandle": message.ReceiptHandle 16 | }; 17 | sqs.deleteMessage(params, cb); 18 | } 19 | 20 | function process(message, cb) { 21 | var body = JSON.parse(message.Body); 22 | var file = body.id + '.png'; 23 | webshot(body.url, file, function(err) { 24 | if (err) { 25 | cb(err); 26 | } else { 27 | fs.readFile(file, function(err, buf) { 28 | if (err) { 29 | cb(err); 30 | } else { 31 | var params = { 32 | "Bucket": config.Bucket, 33 | "Key": file, 34 | "ACL": "public-read", 35 | "ContentType": "image/png", 36 | "Body": buf 37 | }; 38 | s3.putObject(params, function(err) { 39 | if (err) { 40 | cb(err); 41 | } else { 42 | fs.unlink(file, cb); 43 | } 44 | }); 45 | } 46 | }); 47 | } 48 | }); 49 | } 50 | 51 | function receive(cb) { 52 | var params = { 53 | "QueueUrl": config.QueueUrl, 54 | "MaxNumberOfMessages": 1, 55 | "VisibilityTimeout": 120, 56 | "WaitTimeSeconds": 10 57 | }; 58 | sqs.receiveMessage(params, function(err, data) { 59 | if (err) { 60 | cb(err); 61 | } else { 62 | if (data.Messages === undefined) { 63 | cb(null, null); 64 | } else { 65 | cb(null, data.Messages[0]); 66 | } 67 | } 68 | }); 69 | } 70 | 71 | function run() { 72 | receive(function(err, message) { 73 | if (err) { 74 | throw err; 75 | } else { 76 | if (message === null) { 77 | console.log('nothing to do'); 78 | setTimeout(run, 1000); 79 | } else { 80 | console.log('process'); 81 | process(message, function(err) { 82 | if (err) { 83 | throw err; 84 | } else { 85 | acknowledge(message, function(err) { 86 | if (err) { 87 | throw err; 88 | } else { 89 | console.log('done'); 90 | setTimeout(run, 1000); 91 | } 92 | }); 93 | } 94 | }); 95 | } 96 | } 97 | }); 98 | } 99 | 100 | run(); 101 | -------------------------------------------------------------------------------- /chapter13/README.md: -------------------------------------------------------------------------------- 1 | # Imagery 2 | 3 | ![Imagery](./imagery.png?raw=true "Imagery") 4 | 5 | To try Imagery app create a CloudFormation stack based on the template https://s3.amazonaws.com/awsinaction/chapter13/template.json 6 | -------------------------------------------------------------------------------- /chapter13/build/server.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AWSinAction/code/9acfdeb3642a3b7e1af82c7bc78bdf10ac2bd9f3/chapter13/build/server.zip -------------------------------------------------------------------------------- /chapter13/build/worker.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AWSinAction/code/9acfdeb3642a3b7e1af82c7bc78bdf10ac2bd9f3/chapter13/build/worker.zip -------------------------------------------------------------------------------- /chapter13/bundle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | rm -rf build/ 4 | mkdir -p build/ 5 | 6 | cd worker/ 7 | zip -r ../build/worker.zip lib.js package.json worker.js .ebextensions/ 8 | cd .. 9 | 10 | cd server/ 11 | zip -r ../build/server.zip lib.js package.json server.js public/ 12 | cd .. 13 | -------------------------------------------------------------------------------- /chapter13/imagery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AWSinAction/code/9acfdeb3642a3b7e1af82c7bc78bdf10ac2bd9f3/chapter13/imagery.png -------------------------------------------------------------------------------- /chapter13/lib.js: -------------------------------------------------------------------------------- 1 | function getOptionalAttribute(item, attr, type) { 2 | return (item[attr] !== undefined) ? item[attr][type] : undefined; 3 | } 4 | 5 | exports.mapImage = function(item) { 6 | return { 7 | "id": item.id.S, 8 | "version": parseInt(item.version.N, 10), 9 | "state": item.state.S, 10 | "rawS3Key": getOptionalAttribute(item, 'rawS3Key', 'S'), 11 | "processedS3Key": getOptionalAttribute(item, 'processedS3Key', 'S'), 12 | "processedImage": (item.processedS3Key !== undefined) ? ("https://s3.amazonaws.com/" + process.env.ImageBucket + "/" + item.processedS3Key.S) : undefined 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /chapter13/server/lib.js: -------------------------------------------------------------------------------- 1 | ../lib.js -------------------------------------------------------------------------------- /chapter13/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "aws-sdk": "2.1.38", 4 | "express": "4.13.1", 5 | "body-parser": "1.13.2", 6 | "multiparty": "4.1.1", 7 | "node-uuid": "1.4.3", 8 | "assert-plus": "0.1.5" 9 | }, 10 | "private": true 11 | } 12 | -------------------------------------------------------------------------------- /chapter13/server/public/app.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | function show(id) { 4 | $('#new').hide(); 5 | $('#upload').hide(); 6 | $('#view').hide(); 7 | $('#' + id).show(); 8 | } 9 | function updateImage(image) { 10 | document.title = 'Imagery | #' + image.id; 11 | $('#upload form').attr("action", "/image/" + image.id + "/upload"); 12 | $('#upload blockquote').html("state " + image.state); 13 | $('#view img').attr("src", image.processedImage); 14 | $('#view blockquote').html("state " + image.state); 15 | } 16 | 17 | var hash = window.location.hash.substr(1).split("="); 18 | if (window.location.hash.length > 0) { 19 | if (hash[0] === 'upload' && hash.length === 2) { 20 | $.get('/image/' + hash[1], function(data) { 21 | updateImage(data); 22 | show('upload'); 23 | }) 24 | .fail(function() { 25 | alert('error'); 26 | }); 27 | } else if (hash[0] === 'view' && hash.length === 2) { 28 | $.get('/image/' + hash[1], function(data) { 29 | updateImage(data); 30 | show('view'); 31 | }) 32 | .fail(function() { 33 | alert('error'); 34 | }); 35 | } else { 36 | show('new'); 37 | } 38 | } else { 39 | show('new'); 40 | } 41 | 42 | $('#new a').click(function() { 43 | $.post('/image', function(data) { 44 | updateImage(data); 45 | show('upload'); 46 | window.location.hash = '#upload='+ data.id; 47 | }) 48 | .fail(function() { 49 | alert('error'); 50 | }); 51 | return false; 52 | }); 53 | $('#view a.refresh').click(function() { 54 | $.get('/image/' + hash[1], function(data) { 55 | updateImage(data); 56 | }) 57 | .fail(function() { 58 | alert('error'); 59 | }); 60 | return false; 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /chapter13/server/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Imagery | AWS in Action: chapter 13 5 | 6 | 7 | 8 | 9 |
10 |

New image

11 |

Create a new image

12 |
13 |
14 |

Upload

15 |

16 |
17 | 18 | 19 |
20 |
21 |
22 |

View

23 |

24 |

25 |

Refresh - New image

26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /chapter13/server/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var bodyParser = require('body-parser'); 3 | var AWS = require('aws-sdk'); 4 | var uuid = require('node-uuid'); 5 | var multiparty = require('multiparty'); 6 | 7 | var lib = require('./lib.js'); 8 | 9 | var db = new AWS.DynamoDB({ 10 | "region": "us-east-1" 11 | }); 12 | var sqs = new AWS.SQS({ 13 | "region": "us-east-1" 14 | }); 15 | var s3 = new AWS.S3({ 16 | "region": "us-east-1" 17 | }); 18 | 19 | var app = express(); 20 | app.use(bodyParser.json()); 21 | app.use(express.static('public')); 22 | 23 | function getImage(id, cb) { 24 | db.getItem({ 25 | "Key": { 26 | "id": { 27 | "S": id 28 | } 29 | }, 30 | "TableName": "imagery-image" 31 | }, function(err, data) { 32 | if (err) { 33 | cb(err); 34 | } else { 35 | if (data.Item) { 36 | cb(null, lib.mapImage(data.Item)); 37 | } else { 38 | cb(new Error("image not found")); 39 | } 40 | } 41 | }); 42 | } 43 | 44 | function uploadImage(image, part, response) { 45 | var rawS3Key = 'upload/' + image.id + '-' + Date.now(); 46 | s3.putObject({ 47 | "Bucket": process.env.ImageBucket, 48 | "Key": rawS3Key, 49 | "Body": part, 50 | "ContentLength": part.byteCount 51 | }, function(err, data) { 52 | if (err) { 53 | throw err; 54 | } else { 55 | db.updateItem({ 56 | "Key": { 57 | "id": { 58 | "S": image.id 59 | } 60 | }, 61 | "UpdateExpression": "SET #s=:newState, version=:newVersion, rawS3Key=:rawS3Key", 62 | "ConditionExpression": "attribute_exists(id) AND version=:oldVersion AND #s IN (:stateCreated, :stateUploaded)", 63 | "ExpressionAttributeNames": { 64 | "#s": "state" 65 | }, 66 | "ExpressionAttributeValues": { 67 | ":newState": { 68 | "S": "uploaded" 69 | }, 70 | ":oldVersion": { 71 | "N": image.version.toString() 72 | }, 73 | ":newVersion": { 74 | "N": (image.version + 1).toString() 75 | }, 76 | ":rawS3Key": { 77 | "S": rawS3Key 78 | }, 79 | ":stateCreated": { 80 | "S": "created" 81 | }, 82 | ":stateUploaded": { 83 | "S": "uploaded" 84 | } 85 | }, 86 | "ReturnValues": "ALL_NEW", 87 | "TableName": "imagery-image" 88 | }, function(err, data) { 89 | if (err) { 90 | throw err; 91 | } else { 92 | sqs.sendMessage({ 93 | "MessageBody": JSON.stringify({"imageId": image.id, "desiredState": "processed"}), 94 | "QueueUrl": process.env.ImageQueue, 95 | }, function(err) { 96 | if (err) { 97 | throw err; 98 | } else { 99 | //response.json(lib.mapImage(data.Attributes)); 100 | response.redirect('/#view=' + image.id); 101 | response.end(); 102 | } 103 | }); 104 | } 105 | }); 106 | } 107 | }); 108 | } 109 | 110 | app.post('/image', function(request, response) { 111 | var id = uuid.v4(); 112 | db.putItem({ 113 | "Item": { 114 | "id": { 115 | "S": id 116 | }, 117 | "version": { 118 | "N": "0" 119 | }, 120 | "created": { 121 | "N": Date.now().toString() 122 | }, 123 | "state": { 124 | "S": "created" 125 | } 126 | }, 127 | "TableName": "imagery-image", 128 | "ConditionExpression": "attribute_not_exists(id)" 129 | }, function(err, data) { 130 | if (err) { 131 | throw err; 132 | } else { 133 | response.json({"id": id, "state": "created"}); 134 | } 135 | }); 136 | }); 137 | 138 | app.get('/image/:id', function(request, response) { 139 | getImage(request.params.id, function(err, image) { 140 | if (err) { 141 | throw err; 142 | } else { 143 | response.json(image); 144 | } 145 | }); 146 | }); 147 | 148 | app.post('/image/:id/upload', function(request, response) { 149 | getImage(request.params.id, function(err, image) { 150 | if (err) { 151 | throw err; 152 | } else { 153 | var form = new multiparty.Form(); 154 | form.on('part', function(part) { 155 | uploadImage(image, part, response); 156 | }); 157 | form.parse(request); 158 | } 159 | }); 160 | }); 161 | 162 | app.listen(process.env.PORT || 8080, function() { 163 | console.log("Server started. Open http://localhost:" + (process.env.PORT || 8080) + " with browser."); 164 | }); 165 | -------------------------------------------------------------------------------- /chapter13/worker/.ebextensions/worker.config: -------------------------------------------------------------------------------- 1 | packages: 2 | yum: 3 | cairo-devel: [] 4 | libjpeg-turbo-devel: [] 5 | giflib-devel: [] 6 | -------------------------------------------------------------------------------- /chapter13/worker/lib.js: -------------------------------------------------------------------------------- 1 | ../lib.js -------------------------------------------------------------------------------- /chapter13/worker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "aws-sdk": "2.1.38", 4 | "express": "4.13.1", 5 | "body-parser": "1.13.2", 6 | "node-uuid": "1.4.3", 7 | "assert-plus": "0.1.5", 8 | "caman": "4.1.2" 9 | }, 10 | "private": true 11 | } 12 | -------------------------------------------------------------------------------- /chapter13/worker/worker.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var bodyParser = require('body-parser'); 3 | var AWS = require('aws-sdk'); 4 | var assert = require('assert-plus'); 5 | var Caman = require('caman').Caman; 6 | var fs = require('fs'); 7 | 8 | var lib = require('./lib.js'); 9 | 10 | var db = new AWS.DynamoDB({ 11 | "region": "us-east-1" 12 | }); 13 | var s3 = new AWS.S3({ 14 | "region": "us-east-1" 15 | }); 16 | 17 | var app = express(); 18 | app.use(bodyParser.json()); 19 | 20 | function getImage(id, cb) { 21 | db.getItem({ 22 | "Key": { 23 | "id": { 24 | "S": id 25 | } 26 | }, 27 | "TableName": "imagery-image" 28 | }, function(err, data) { 29 | if (err) { 30 | cb(err); 31 | } else { 32 | if (data.Item) { 33 | cb(null, lib.mapImage(data.Item)); 34 | } else { 35 | cb(new Error("image not found")); 36 | } 37 | } 38 | }); 39 | } 40 | 41 | app.get('/', function(request, response) { 42 | response.json({}); 43 | }); 44 | 45 | app.post('/sqs', function(request, response) { 46 | assert.string(request.body.imageId, "imageId"); 47 | assert.string(request.body.desiredState, "desiredState"); 48 | getImage(request.body.imageId, function(err, image) { 49 | if (err) { 50 | throw err; 51 | } else { 52 | if (typeof states[request.body.desiredState] === 'function') { 53 | states[request.body.desiredState](image, request, response); 54 | } else { 55 | throw new Error("unsupported desiredState"); 56 | } 57 | } 58 | }); 59 | }); 60 | 61 | var states = { 62 | "processed": processed 63 | }; 64 | 65 | function processImage(image, cb) { 66 | var processedS3Key = 'processed/' + image.id + '-' + Date.now() + '.png'; 67 | var rawFile = './tmp_raw_' + image.id; 68 | var processedFile = './tmp_processed_' + image.id; 69 | s3.getObject({ 70 | "Bucket": process.env.ImageBucket, 71 | "Key": image.rawS3Key 72 | }, function(err, data) { 73 | if (err) { 74 | cb(err); 75 | } else { 76 | fs.writeFile(rawFile, data.Body, {"encoding": null}, function(err) { 77 | if (err) { 78 | cb(err); 79 | } else { 80 | Caman(rawFile, function () { 81 | this.brightness(10); 82 | this.contrast(30); 83 | this.sepia(60); 84 | this.saturation(-30); 85 | this.render(function() { 86 | this.save(processedFile); 87 | fs.unlink(rawFile, function() { 88 | fs.readFile(processedFile, {"encoding": null}, function(err, buf) { 89 | if (err) { 90 | cb(err); 91 | } else { 92 | s3.putObject({ 93 | "Bucket": process.env.ImageBucket, 94 | "Key": processedS3Key, 95 | "ACL": "public-read", 96 | "Body": buf, 97 | "ContentType": "image/png" 98 | }, function(err) { 99 | console.log("s3.putObject", err); // TODO debug only 100 | if (err) { 101 | cb(err); 102 | } else { 103 | fs.unlink(processedFile, function() { 104 | cb(null, processedS3Key); 105 | }); 106 | } 107 | }); 108 | } 109 | }); 110 | }); 111 | }); 112 | }); 113 | } 114 | }); 115 | } 116 | }); 117 | } 118 | 119 | function processed(image, request, response) { 120 | processImage(image, function(err, processedS3Key) { 121 | if (err) { 122 | throw err; 123 | } else { 124 | db.updateItem({ 125 | "Key": { 126 | "id": { 127 | "S": image.id 128 | } 129 | }, 130 | "UpdateExpression": "SET #s=:newState, version=:newVersion, processedS3Key=:processedS3Key", 131 | "ConditionExpression": "attribute_exists(id) AND version=:oldVersion AND #s IN (:stateUploaded, :stateProcessed)", 132 | "ExpressionAttributeNames": { 133 | "#s": "state" 134 | }, 135 | "ExpressionAttributeValues": { 136 | ":newState": { 137 | "S": "processed" 138 | }, 139 | ":oldVersion": { 140 | "N": image.version.toString() 141 | }, 142 | ":newVersion": { 143 | "N": (image.version + 1).toString() 144 | }, 145 | ":processedS3Key": { 146 | "S": processedS3Key 147 | }, 148 | ":stateUploaded": { 149 | "S": "uploaded" 150 | }, 151 | ":stateProcessed": { 152 | "S": "processed" 153 | } 154 | }, 155 | "ReturnValues": "ALL_NEW", 156 | "TableName": "imagery-image" 157 | }, function(err, data) { 158 | if (err) { 159 | throw err; 160 | } else { 161 | response.json(lib.mapImage(data.Attributes)); 162 | } 163 | }); 164 | } 165 | }); 166 | } 167 | 168 | app.listen(process.env.PORT || 8080, function() { 169 | console.log("Worker started on port " + (process.env.PORT || 8080)); 170 | }); 171 | -------------------------------------------------------------------------------- /chapter14/url2png-loadtest.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 14 (URL2PNG load test)", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "ApplicationID": { 11 | "Description": "A unique identifier for your application.", 12 | "Type": "String", 13 | "AllowedPattern": "[A-Za-z0-9\\-]+", 14 | "ConstraintDescription": "Only letters, digits or dash allowed." 15 | } 16 | }, 17 | "Mappings": { 18 | "EC2RegionMap": { 19 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 20 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 21 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 22 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 23 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 24 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 25 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 26 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 27 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 28 | } 29 | }, 30 | "Resources": { 31 | "VPC": { 32 | "Type": "AWS::EC2::VPC", 33 | "Properties": { 34 | "CidrBlock": "172.31.0.0/16", 35 | "EnableDnsHostnames": "true" 36 | } 37 | }, 38 | "InternetGateway": { 39 | "Type": "AWS::EC2::InternetGateway", 40 | "Properties": { 41 | } 42 | }, 43 | "VPCGatewayAttachment": { 44 | "Type": "AWS::EC2::VPCGatewayAttachment", 45 | "Properties": { 46 | "VpcId": {"Ref": "VPC"}, 47 | "InternetGatewayId": {"Ref": "InternetGateway"} 48 | } 49 | }, 50 | "SubnetA": { 51 | "Type": "AWS::EC2::Subnet", 52 | "Properties": { 53 | "AvailabilityZone": {"Fn::Select": ["0", {"Fn::GetAZs": ""}]}, 54 | "CidrBlock": "172.31.38.0/24", 55 | "VpcId": {"Ref": "VPC"} 56 | } 57 | }, 58 | "SubnetB": { 59 | "Type": "AWS::EC2::Subnet", 60 | "Properties": { 61 | "AvailabilityZone": {"Fn::Select": ["1", {"Fn::GetAZs": ""}]}, 62 | "CidrBlock": "172.31.37.0/24", 63 | "VpcId": {"Ref": "VPC"} 64 | } 65 | }, 66 | "RouteTable": { 67 | "Type": "AWS::EC2::RouteTable", 68 | "Properties": { 69 | "VpcId": {"Ref": "VPC"} 70 | } 71 | }, 72 | "RouteTableAssociationA": { 73 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 74 | "Properties": { 75 | "SubnetId": {"Ref": "SubnetA"}, 76 | "RouteTableId": {"Ref": "RouteTable"} 77 | } 78 | }, 79 | "RouteTableAssociationB": { 80 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 81 | "Properties": { 82 | "SubnetId": {"Ref": "SubnetB"}, 83 | "RouteTableId": {"Ref": "RouteTable"} 84 | } 85 | }, 86 | "RoutePublicNATToInternet": { 87 | "Type": "AWS::EC2::Route", 88 | "Properties": { 89 | "RouteTableId": {"Ref": "RouteTable"}, 90 | "DestinationCidrBlock": "0.0.0.0/0", 91 | "GatewayId": {"Ref": "InternetGateway"} 92 | }, 93 | "DependsOn": "VPCGatewayAttachment" 94 | }, 95 | "NetworkAcl": { 96 | "Type": "AWS::EC2::NetworkAcl", 97 | "Properties": { 98 | "VpcId": {"Ref": "VPC"} 99 | } 100 | }, 101 | "SubnetNetworkAclAssociationA": { 102 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 103 | "Properties": { 104 | "SubnetId": {"Ref": "SubnetA"}, 105 | "NetworkAclId": {"Ref": "NetworkAcl"} 106 | } 107 | }, 108 | "SubnetNetworkAclAssociationB": { 109 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 110 | "Properties": { 111 | "SubnetId": {"Ref": "SubnetB"}, 112 | "NetworkAclId": {"Ref": "NetworkAcl"} 113 | } 114 | }, 115 | "NetworkAclEntryIngress": { 116 | "Type": "AWS::EC2::NetworkAclEntry", 117 | "Properties": { 118 | "NetworkAclId": {"Ref": "NetworkAcl"}, 119 | "RuleNumber": "100", 120 | "Protocol": "-1", 121 | "RuleAction": "allow", 122 | "Egress": "false", 123 | "CidrBlock": "0.0.0.0/0" 124 | } 125 | }, 126 | "NetworkAclEntryEgress": { 127 | "Type": "AWS::EC2::NetworkAclEntry", 128 | "Properties": { 129 | "NetworkAclId": {"Ref": "NetworkAcl"}, 130 | "RuleNumber": "100", 131 | "Protocol": "-1", 132 | "RuleAction": "allow", 133 | "Egress": "true", 134 | "CidrBlock": "0.0.0.0/0" 135 | } 136 | }, 137 | "SecurityGroup": { 138 | "Type": "AWS::EC2::SecurityGroup", 139 | "Properties": { 140 | "GroupDescription": "awsinaction-sg", 141 | "VpcId": {"Ref": "VPC"}, 142 | "SecurityGroupIngress": [{ 143 | "CidrIp": "0.0.0.0/0", 144 | "FromPort": 22, 145 | "IpProtocol": "tcp", 146 | "ToPort": 22 147 | }] 148 | } 149 | }, 150 | "IamRole": { 151 | "Type": "AWS::IAM::Role", 152 | "Properties": { 153 | "AssumeRolePolicyDocument": { 154 | "Version": "2012-10-17", 155 | "Statement": [ 156 | { 157 | "Effect": "Allow", 158 | "Principal": { 159 | "Service": ["ec2.amazonaws.com"] 160 | }, 161 | "Action": ["sts:AssumeRole"] 162 | } 163 | ] 164 | }, 165 | "Path": "/", 166 | "Policies": [ 167 | { 168 | "PolicyName": "root", 169 | "PolicyDocument": { 170 | "Version": "2012-10-17", 171 | "Statement": [ 172 | { 173 | "Effect" : "Allow", 174 | "Action" : ["s3:*"], 175 | "Resource" : [ 176 | {"Fn::Join": ["", ["arn:aws:s3:::", {"Ref": "ApplicationID"} ,"/*"]]} 177 | ] 178 | }, 179 | { 180 | "Effect" : "Allow", 181 | "Action" : ["sqs:*"], 182 | "Resource" : [ 183 | {"Fn::Join": ["", ["arn:aws:sqs:", {"Ref": "AWS::Region"}, ":*:", {"Fn::GetAtt": ["SQSQueue", "QueueName"]}]]} 184 | ] 185 | } 186 | ] 187 | } 188 | } 189 | ] 190 | } 191 | }, 192 | "IamInstanceProfile": { 193 | "Type": "AWS::IAM::InstanceProfile", 194 | "Properties": { 195 | "Path": "/", 196 | "Roles": [{"Ref": "IamRole"}] 197 | } 198 | }, 199 | "S3Bucket": { 200 | "Type": "AWS::S3::Bucket", 201 | "Properties": { 202 | "BucketName": {"Ref": "ApplicationID"}, 203 | "WebsiteConfiguration": { 204 | "IndexDocument": "index.html" 205 | } 206 | } 207 | }, 208 | "SQSQueue" : { 209 | "Type" : "AWS::SQS::Queue", 210 | "Properties" : { 211 | "QueueName": "url2png" 212 | } 213 | }, 214 | "LaunchConfiguration": { 215 | "Type": "AWS::AutoScaling::LaunchConfiguration", 216 | "Properties": { 217 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 218 | "InstanceType": "t2.micro", 219 | "SecurityGroups": [{"Ref": "SecurityGroup"}], 220 | "KeyName": {"Ref": "KeyName"}, 221 | "AssociatePublicIpAddress": true, 222 | "IamInstanceProfile": {"Ref": "IamInstanceProfile"}, 223 | "UserData": {"Fn::Base64": {"Fn::Join": ["", [ 224 | "#!/bin/bash -ex\n", 225 | "yum update -y aws-cfn-bootstrap\n", 226 | "curl -sL https://rpm.nodesource.com/setup | bash -\n", 227 | "yum install -y nodejs\n", 228 | "wget https://github.com/AWSinAction/code/archive/master.zip\n", 229 | "unzip master.zip\n", 230 | "cd code-master/chapter12/url2png/\n", 231 | "npm install\n", 232 | "echo \" {\\\"QueueUrl\\\": \\\"", {"Ref": "SQSQueue"},"\\\", \\\"Bucket\\\": \\\"", {"Ref": "ApplicationID"}, "\\\"} \" > config.json \n", 233 | "node worker.js\n" 234 | ]]}} 235 | } 236 | }, 237 | "AutoScalingGroup": { 238 | "Type": "AWS::AutoScaling::AutoScalingGroup", 239 | "Properties": { 240 | "LaunchConfigurationName": {"Ref": "LaunchConfiguration"}, 241 | "MinSize": "1", 242 | "MaxSize": "2", 243 | "DesiredCapacity": "1", 244 | "Cooldown": "60", 245 | "HealthCheckGracePeriod": "120", 246 | "HealthCheckType": "EC2", 247 | "VPCZoneIdentifier": [{"Ref": "SubnetA"}, {"Ref": "SubnetB"}], 248 | "Tags": [{ 249 | "PropagateAtLaunch": true, 250 | "Value": "url2png-consumer", 251 | "Key": "Name" 252 | }] 253 | }, 254 | "DependsOn": "VPCGatewayAttachment" 255 | }, 256 | "ScalingUpPolicy": { 257 | "Type": "AWS::AutoScaling::ScalingPolicy", 258 | "Properties": { 259 | "AdjustmentType": "ChangeInCapacity", 260 | "AutoScalingGroupName": {"Ref": "AutoScalingGroup"}, 261 | "Cooldown": "60", 262 | "ScalingAdjustment": "1" 263 | } 264 | }, 265 | "HighQueueAlarm": { 266 | "Type": "AWS::CloudWatch::Alarm", 267 | "Properties": { 268 | "EvaluationPeriods": "1", 269 | "Statistic": "Sum", 270 | "Threshold": "5", 271 | "AlarmDescription": "Alarm if queue length is higher than 5.", 272 | "Period": "300", 273 | "AlarmActions": [{"Ref": "ScalingUpPolicy"}], 274 | "Namespace": "AWS/SQS", 275 | "Dimensions": [{ 276 | "Name": "QueueName", 277 | "Value" : {"Fn::GetAtt": ["SQSQueue", "QueueName"]} 278 | }], 279 | "ComparisonOperator": "GreaterThanThreshold", 280 | "MetricName": "ApproximateNumberOfMessagesVisible" 281 | } 282 | }, 283 | "ScalingDownPolicy": { 284 | "Type": "AWS::AutoScaling::ScalingPolicy", 285 | "Properties": { 286 | "AdjustmentType": "ChangeInCapacity", 287 | "AutoScalingGroupName": {"Ref": "AutoScalingGroup"}, 288 | "Cooldown": "60", 289 | "ScalingAdjustment": "-1" 290 | } 291 | }, 292 | "LowQueueAlarm": { 293 | "Type": "AWS::CloudWatch::Alarm", 294 | "Properties": { 295 | "EvaluationPeriods": "1", 296 | "Statistic": "Sum", 297 | "Threshold": "5", 298 | "AlarmDescription": "Alarm if queue length is lower than 5.", 299 | "Period": "300", 300 | "AlarmActions": [{"Ref": "ScalingDownPolicy"}], 301 | "Namespace": "AWS/SQS", 302 | "Dimensions": [{ 303 | "Name": "QueueName", 304 | "Value" : { "Fn::GetAtt" : ["SQSQueue", "QueueName"] } 305 | }], 306 | "ComparisonOperator": "LessThanThreshold", 307 | "MetricName": "ApproximateNumberOfMessagesVisible" 308 | } 309 | }, 310 | "LoadTestServer": { 311 | "Type": "AWS::EC2::Instance", 312 | "Properties": { 313 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 314 | "InstanceType": "t2.micro", 315 | "KeyName": {"Ref": "KeyName"}, 316 | "IamInstanceProfile": {"Ref": "IamInstanceProfile"}, 317 | "UserData": {"Fn::Base64": {"Fn::Join": ["", [ 318 | "#!/bin/bash -ex\n", 319 | "for i in `seq 1 25`;\n", 320 | "do\n", 321 | "aws sqs send-message --queue-url ", {"Ref": "SQSQueue"} ," --message-body \"{\\\"id\\\": \\\"$i.www.google.com\\\", \\\"url\\\": \\\"http://www.google.com\\\"}\" --region ", {"Ref": "AWS::Region"}, "\n", 322 | "aws sqs send-message --queue-url ", {"Ref": "SQSQueue"} ," --message-body \"{\\\"id\\\": \\\"$i.www.youtube.com\\\", \\\"url\\\": \\\"http://www.youtube.com\\\"}\" --region ", {"Ref": "AWS::Region"}, "\n", 323 | "aws sqs send-message --queue-url ", {"Ref": "SQSQueue"} ," --message-body \"{\\\"id\\\": \\\"$i.www.amazon.com\\\", \\\"url\\\": \\\"http://www.amazon.com\\\"}\" --region ", {"Ref": "AWS::Region"}, "\n", 324 | "aws sqs send-message --queue-url ", {"Ref": "SQSQueue"} ," --message-body \"{\\\"id\\\": \\\"$i.www.twitter.com\\\", \\\"url\\\": \\\"http://www.twitter.com\\\"}\" --region ", {"Ref": "AWS::Region"}, "\n", 325 | "aws sqs send-message --queue-url ", {"Ref": "SQSQueue"} ," --message-body \"{\\\"id\\\": \\\"$i.www.linkedin.com\\\", \\\"url\\\": \\\"http://www.linkedin.com\\\"}\" --region ", {"Ref": "AWS::Region"}, "\n", 326 | "aws sqs send-message --queue-url ", {"Ref": "SQSQueue"} ," --message-body \"{\\\"id\\\": \\\"$i.www.ebay.com\\\", \\\"url\\\": \\\"http://www.ebay.com\\\"}\" --region ", {"Ref": "AWS::Region"}, "\n", 327 | "aws sqs send-message --queue-url ", {"Ref": "SQSQueue"} ," --message-body \"{\\\"id\\\": \\\"$i.www.instagram.com\\\", \\\"url\\\": \\\"http://www.instagram.com\\\"}\" --region ", {"Ref": "AWS::Region"}, "\n", 328 | "aws sqs send-message --queue-url ", {"Ref": "SQSQueue"} ," --message-body \"{\\\"id\\\": \\\"$i.www.reddit.com\\\", \\\"url\\\": \\\"http://www.reddit.com\\\"}\" --region ", {"Ref": "AWS::Region"}, "\n", 329 | "aws sqs send-message --queue-url ", {"Ref": "SQSQueue"} ," --message-body \"{\\\"id\\\": \\\"$i.www.pinterest.com\\\", \\\"url\\\": \\\"http://www.pinterest.com\\\"}\" --region ", {"Ref": "AWS::Region"}, "\n", 330 | "aws sqs send-message --queue-url ", {"Ref": "SQSQueue"} ," --message-body \"{\\\"id\\\": \\\"$i.www.wordpress.com\\\", \\\"url\\\": \\\"http://www.wordpress.com\\\"}\" --region ", {"Ref": "AWS::Region"}, "\n", 331 | "done\n" 332 | ]]}}, 333 | "Tags": [ 334 | { 335 | "Key": "Name", 336 | "Value": "url2png-loadtest" 337 | } 338 | ], 339 | "NetworkInterfaces": [{ 340 | "AssociatePublicIpAddress": "true", 341 | "DeviceIndex": "0", 342 | "GroupSet": [{"Ref": "SecurityGroup"}], 343 | "SubnetId": {"Ref": "SubnetA"} 344 | }] 345 | }, 346 | "DependsOn": "VPCGatewayAttachment" 347 | } 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /chapter14/url2png.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 14 (URL2PNG)", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "ApplicationID": { 11 | "Description": "A unique identifier for your application.", 12 | "Type": "String", 13 | "AllowedPattern": "[A-Za-z0-9\\-]+", 14 | "ConstraintDescription": "Only letters, digits or dash allowed." 15 | } 16 | }, 17 | "Mappings": { 18 | "EC2RegionMap": { 19 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 20 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 21 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 22 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 23 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 24 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 25 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 26 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 27 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 28 | } 29 | }, 30 | "Resources": { 31 | "VPC": { 32 | "Type": "AWS::EC2::VPC", 33 | "Properties": { 34 | "CidrBlock": "172.31.0.0/16", 35 | "EnableDnsHostnames": "true" 36 | } 37 | }, 38 | "InternetGateway": { 39 | "Type": "AWS::EC2::InternetGateway", 40 | "Properties": { 41 | } 42 | }, 43 | "VPCGatewayAttachment": { 44 | "Type": "AWS::EC2::VPCGatewayAttachment", 45 | "Properties": { 46 | "VpcId": {"Ref": "VPC"}, 47 | "InternetGatewayId": {"Ref": "InternetGateway"} 48 | } 49 | }, 50 | "SubnetA": { 51 | "Type": "AWS::EC2::Subnet", 52 | "Properties": { 53 | "AvailabilityZone": {"Fn::Select": ["0", {"Fn::GetAZs": ""}]}, 54 | "CidrBlock": "172.31.38.0/24", 55 | "VpcId": {"Ref": "VPC"} 56 | } 57 | }, 58 | "SubnetB": { 59 | "Type": "AWS::EC2::Subnet", 60 | "Properties": { 61 | "AvailabilityZone": {"Fn::Select": ["1", {"Fn::GetAZs": ""}]}, 62 | "CidrBlock": "172.31.37.0/24", 63 | "VpcId": {"Ref": "VPC"} 64 | } 65 | }, 66 | "RouteTable": { 67 | "Type": "AWS::EC2::RouteTable", 68 | "Properties": { 69 | "VpcId": {"Ref": "VPC"} 70 | } 71 | }, 72 | "RouteTableAssociationA": { 73 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 74 | "Properties": { 75 | "SubnetId": {"Ref": "SubnetA"}, 76 | "RouteTableId": {"Ref": "RouteTable"} 77 | } 78 | }, 79 | "RouteTableAssociationB": { 80 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 81 | "Properties": { 82 | "SubnetId": {"Ref": "SubnetB"}, 83 | "RouteTableId": {"Ref": "RouteTable"} 84 | } 85 | }, 86 | "RoutePublicNATToInternet": { 87 | "Type": "AWS::EC2::Route", 88 | "Properties": { 89 | "RouteTableId": {"Ref": "RouteTable"}, 90 | "DestinationCidrBlock": "0.0.0.0/0", 91 | "GatewayId": {"Ref": "InternetGateway"} 92 | }, 93 | "DependsOn": "VPCGatewayAttachment" 94 | }, 95 | "NetworkAcl": { 96 | "Type": "AWS::EC2::NetworkAcl", 97 | "Properties": { 98 | "VpcId": {"Ref": "VPC"} 99 | } 100 | }, 101 | "SubnetNetworkAclAssociationA": { 102 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 103 | "Properties": { 104 | "SubnetId": {"Ref": "SubnetA"}, 105 | "NetworkAclId": {"Ref": "NetworkAcl"} 106 | } 107 | }, 108 | "SubnetNetworkAclAssociationB": { 109 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 110 | "Properties": { 111 | "SubnetId": {"Ref": "SubnetB"}, 112 | "NetworkAclId": {"Ref": "NetworkAcl"} 113 | } 114 | }, 115 | "NetworkAclEntryIngress": { 116 | "Type": "AWS::EC2::NetworkAclEntry", 117 | "Properties": { 118 | "NetworkAclId": {"Ref": "NetworkAcl"}, 119 | "RuleNumber": "100", 120 | "Protocol": "-1", 121 | "RuleAction": "allow", 122 | "Egress": "false", 123 | "CidrBlock": "0.0.0.0/0" 124 | } 125 | }, 126 | "NetworkAclEntryEgress": { 127 | "Type": "AWS::EC2::NetworkAclEntry", 128 | "Properties": { 129 | "NetworkAclId": {"Ref": "NetworkAcl"}, 130 | "RuleNumber": "100", 131 | "Protocol": "-1", 132 | "RuleAction": "allow", 133 | "Egress": "true", 134 | "CidrBlock": "0.0.0.0/0" 135 | } 136 | }, 137 | "SecurityGroup": { 138 | "Type": "AWS::EC2::SecurityGroup", 139 | "Properties": { 140 | "GroupDescription": "awsinaction-sg", 141 | "VpcId": {"Ref": "VPC"}, 142 | "SecurityGroupIngress": [{ 143 | "CidrIp": "0.0.0.0/0", 144 | "FromPort": 22, 145 | "IpProtocol": "tcp", 146 | "ToPort": 22 147 | }] 148 | } 149 | }, 150 | "IamRole": { 151 | "Type": "AWS::IAM::Role", 152 | "Properties": { 153 | "AssumeRolePolicyDocument": { 154 | "Version": "2012-10-17", 155 | "Statement": [ 156 | { 157 | "Effect": "Allow", 158 | "Principal": { 159 | "Service": ["ec2.amazonaws.com"] 160 | }, 161 | "Action": ["sts:AssumeRole"] 162 | } 163 | ] 164 | }, 165 | "Path": "/", 166 | "Policies": [ 167 | { 168 | "PolicyName": "root", 169 | "PolicyDocument": { 170 | "Version": "2012-10-17", 171 | "Statement": [ 172 | { 173 | "Effect" : "Allow", 174 | "Action" : ["s3:*"], 175 | "Resource" : [ 176 | {"Fn::Join": ["", ["arn:aws:s3:::", {"Ref": "ApplicationID"} ,"/*"]]} 177 | ] 178 | }, 179 | { 180 | "Effect" : "Allow", 181 | "Action" : ["sqs:*"], 182 | "Resource" : [ 183 | {"Fn::Join": ["", ["arn:aws:sqs:", {"Ref": "AWS::Region"}, ":*:", {"Fn::GetAtt": ["SQSQueue", "QueueName"]}]]} 184 | ] 185 | } 186 | ] 187 | } 188 | } 189 | ] 190 | } 191 | }, 192 | "IamInstanceProfile": { 193 | "Type": "AWS::IAM::InstanceProfile", 194 | "Properties": { 195 | "Path": "/", 196 | "Roles": [{"Ref": "IamRole"}] 197 | } 198 | }, 199 | "S3Bucket": { 200 | "Type": "AWS::S3::Bucket", 201 | "Properties": { 202 | "BucketName": {"Ref": "ApplicationID"}, 203 | "WebsiteConfiguration": { 204 | "IndexDocument": "index.html" 205 | } 206 | } 207 | }, 208 | "SQSQueue" : { 209 | "Type" : "AWS::SQS::Queue", 210 | "Properties" : { 211 | "QueueName": "url2png" 212 | } 213 | }, 214 | "LaunchConfiguration": { 215 | "Type": "AWS::AutoScaling::LaunchConfiguration", 216 | "Properties": { 217 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 218 | "InstanceType": "t2.micro", 219 | "SecurityGroups": [{"Ref": "SecurityGroup"}], 220 | "KeyName": {"Ref": "KeyName"}, 221 | "AssociatePublicIpAddress": true, 222 | "IamInstanceProfile": {"Ref": "IamInstanceProfile"}, 223 | "UserData": {"Fn::Base64": {"Fn::Join": ["", [ 224 | "#!/bin/bash -ex\n", 225 | "yum update -y aws-cfn-bootstrap\n", 226 | "curl -sL https://rpm.nodesource.com/setup | bash -\n", 227 | "yum install -y nodejs\n", 228 | "wget https://github.com/AWSinAction/code/archive/master.zip\n", 229 | "unzip master.zip\n", 230 | "cd code-master/chapter12/url2png/\n", 231 | "npm install\n", 232 | "echo \" {\\\"QueueUrl\\\": \\\"", {"Ref": "SQSQueue"},"\\\", \\\"Bucket\\\": \\\"", {"Ref": "ApplicationID"}, "\\\"} \" > config.json \n", 233 | "node worker.js\n" 234 | ]]}} 235 | } 236 | }, 237 | "AutoScalingGroup": { 238 | "Type": "AWS::AutoScaling::AutoScalingGroup", 239 | "Properties": { 240 | "LaunchConfigurationName": {"Ref": "LaunchConfiguration"}, 241 | "MinSize": "1", 242 | "MaxSize": "2", 243 | "DesiredCapacity": "1", 244 | "Cooldown": "60", 245 | "HealthCheckGracePeriod": "120", 246 | "HealthCheckType": "EC2", 247 | "VPCZoneIdentifier": [{"Ref": "SubnetA"}, {"Ref": "SubnetB"}], 248 | "Tags": [{ 249 | "PropagateAtLaunch": true, 250 | "Value": "url2png-consumer", 251 | "Key": "Name" 252 | }] 253 | }, 254 | "DependsOn": "VPCGatewayAttachment" 255 | }, 256 | "ScalingUpPolicy": { 257 | "Type": "AWS::AutoScaling::ScalingPolicy", 258 | "Properties": { 259 | "AdjustmentType": "ChangeInCapacity", 260 | "AutoScalingGroupName": {"Ref": "AutoScalingGroup"}, 261 | "Cooldown": "60", 262 | "ScalingAdjustment": "1" 263 | } 264 | }, 265 | "HighQueueAlarm": { 266 | "Type": "AWS::CloudWatch::Alarm", 267 | "Properties": { 268 | "EvaluationPeriods": "1", 269 | "Statistic": "Sum", 270 | "Threshold": "5", 271 | "AlarmDescription": "Alarm if queue length is higher than 5.", 272 | "Period": "300", 273 | "AlarmActions": [{"Ref": "ScalingUpPolicy"}], 274 | "Namespace": "AWS/SQS", 275 | "Dimensions": [{ 276 | "Name": "QueueName", 277 | "Value" : {"Fn::GetAtt": ["SQSQueue", "QueueName"]} 278 | }], 279 | "ComparisonOperator": "GreaterThanThreshold", 280 | "MetricName": "ApproximateNumberOfMessagesVisible" 281 | } 282 | }, 283 | "ScalingDownPolicy": { 284 | "Type": "AWS::AutoScaling::ScalingPolicy", 285 | "Properties": { 286 | "AdjustmentType": "ChangeInCapacity", 287 | "AutoScalingGroupName": {"Ref": "AutoScalingGroup"}, 288 | "Cooldown": "60", 289 | "ScalingAdjustment": "-1" 290 | } 291 | }, 292 | "LowQueueAlarm": { 293 | "Type": "AWS::CloudWatch::Alarm", 294 | "Properties": { 295 | "EvaluationPeriods": "1", 296 | "Statistic": "Sum", 297 | "Threshold": "5", 298 | "AlarmDescription": "Alarm if queue length is lower than 5.", 299 | "Period": "300", 300 | "AlarmActions": [{"Ref": "ScalingDownPolicy"}], 301 | "Namespace": "AWS/SQS", 302 | "Dimensions": [{ 303 | "Name": "QueueName", 304 | "Value" : { "Fn::GetAtt" : ["SQSQueue", "QueueName"] } 305 | }], 306 | "ComparisonOperator": "LessThanThreshold", 307 | "MetricName": "ApproximateNumberOfMessagesVisible" 308 | } 309 | } 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /chapter2/cost.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AWS in Action: chapter 2 7 | 8 | 9 | click here 10 | 11 | 12 | -------------------------------------------------------------------------------- /chapter2/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 2", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | } 10 | }, 11 | "Mappings": { 12 | "EC2RegionMap": { 13 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 14 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 15 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 16 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 17 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 18 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 19 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 20 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 21 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 22 | } 23 | }, 24 | "Resources": { 25 | "VPC": { 26 | "Type": "AWS::EC2::VPC", 27 | "Properties": { 28 | "CidrBlock": "172.31.0.0/16", 29 | "EnableDnsHostnames": "true" 30 | } 31 | }, 32 | "InternetGateway": { 33 | "Type": "AWS::EC2::InternetGateway", 34 | "Properties": { 35 | } 36 | }, 37 | "VPCGatewayAttachment": { 38 | "Type": "AWS::EC2::VPCGatewayAttachment", 39 | "Properties": { 40 | "VpcId": {"Ref": "VPC"}, 41 | "InternetGatewayId": {"Ref": "InternetGateway"} 42 | } 43 | }, 44 | "SubnetA": { 45 | "Type": "AWS::EC2::Subnet", 46 | "Properties": { 47 | "AvailabilityZone": {"Fn::Select": ["0", {"Fn::GetAZs": ""}]}, 48 | "CidrBlock": "172.31.38.0/24", 49 | "VpcId": {"Ref": "VPC"} 50 | } 51 | }, 52 | "SubnetB": { 53 | "Type": "AWS::EC2::Subnet", 54 | "Properties": { 55 | "AvailabilityZone": {"Fn::Select": ["1", {"Fn::GetAZs": ""}]}, 56 | "CidrBlock": "172.31.37.0/24", 57 | "VpcId": {"Ref": "VPC"} 58 | } 59 | }, 60 | "RouteTable": { 61 | "Type": "AWS::EC2::RouteTable", 62 | "Properties": { 63 | "VpcId": {"Ref": "VPC"} 64 | } 65 | }, 66 | "RouteTableAssociationA": { 67 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 68 | "Properties": { 69 | "SubnetId": {"Ref": "SubnetA"}, 70 | "RouteTableId": {"Ref": "RouteTable"} 71 | } 72 | }, 73 | "RouteTableAssociationB": { 74 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 75 | "Properties": { 76 | "SubnetId": {"Ref": "SubnetB"}, 77 | "RouteTableId": {"Ref": "RouteTable"} 78 | } 79 | }, 80 | "RoutePublicNATToInternet": { 81 | "Type": "AWS::EC2::Route", 82 | "Properties": { 83 | "RouteTableId": {"Ref": "RouteTable"}, 84 | "DestinationCidrBlock": "0.0.0.0/0", 85 | "GatewayId": {"Ref": "InternetGateway"} 86 | }, 87 | "DependsOn": "VPCGatewayAttachment" 88 | }, 89 | "NetworkAcl": { 90 | "Type": "AWS::EC2::NetworkAcl", 91 | "Properties": { 92 | "VpcId": {"Ref": "VPC"} 93 | } 94 | }, 95 | "SubnetNetworkAclAssociationA": { 96 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 97 | "Properties": { 98 | "SubnetId": {"Ref": "SubnetA"}, 99 | "NetworkAclId": {"Ref": "NetworkAcl"} 100 | } 101 | }, 102 | "SubnetNetworkAclAssociationB": { 103 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 104 | "Properties": { 105 | "SubnetId": {"Ref": "SubnetB"}, 106 | "NetworkAclId": {"Ref": "NetworkAcl"} 107 | } 108 | }, 109 | "NetworkAclEntryIngress": { 110 | "Type": "AWS::EC2::NetworkAclEntry", 111 | "Properties": { 112 | "NetworkAclId": {"Ref": "NetworkAcl"}, 113 | "RuleNumber": "100", 114 | "Protocol": "-1", 115 | "RuleAction": "allow", 116 | "Egress": "false", 117 | "CidrBlock": "0.0.0.0/0" 118 | } 119 | }, 120 | "NetworkAclEntryEgress": { 121 | "Type": "AWS::EC2::NetworkAclEntry", 122 | "Properties": { 123 | "NetworkAclId": {"Ref": "NetworkAcl"}, 124 | "RuleNumber": "100", 125 | "Protocol": "-1", 126 | "RuleAction": "allow", 127 | "Egress": "true", 128 | "CidrBlock": "0.0.0.0/0" 129 | } 130 | }, 131 | "LoadBalancer": { 132 | "Type": "AWS::ElasticLoadBalancing::LoadBalancer", 133 | "Properties": { 134 | "Subnets": [{"Ref": "SubnetA"}, {"Ref": "SubnetB"}], 135 | "LoadBalancerName": "awsinaction-elb", 136 | "Listeners": [{ 137 | "InstancePort": "80", 138 | "InstanceProtocol": "HTTP", 139 | "LoadBalancerPort": "80", 140 | "Protocol": "HTTP" 141 | }], 142 | "HealthCheck": { 143 | "HealthyThreshold": "2", 144 | "Interval": "5", 145 | "Target": "TCP:80", 146 | "Timeout": "3", 147 | "UnhealthyThreshold": "2" 148 | }, 149 | "SecurityGroups": [{"Ref": "LoadBalancerSecurityGroup"}], 150 | "Scheme": "internet-facing" 151 | }, 152 | "DependsOn": "VPCGatewayAttachment" 153 | }, 154 | "LoadBalancerSecurityGroup": { 155 | "Type": "AWS::EC2::SecurityGroup", 156 | "Properties": { 157 | "GroupDescription": "awsinaction-elb-sg", 158 | "VpcId": {"Ref": "VPC"}, 159 | "SecurityGroupIngress": [{ 160 | "CidrIp": "0.0.0.0/0", 161 | "FromPort": 80, 162 | "IpProtocol": "tcp", 163 | "ToPort": 80 164 | }] 165 | } 166 | }, 167 | "WebServerSecurityGroup": { 168 | "Type": "AWS::EC2::SecurityGroup", 169 | "Properties": { 170 | "GroupDescription": "awsinaction-sg", 171 | "VpcId": {"Ref": "VPC"}, 172 | "SecurityGroupIngress": [{ 173 | "CidrIp": "0.0.0.0/0", 174 | "FromPort": 22, 175 | "IpProtocol": "tcp", 176 | "ToPort": 22 177 | }, { 178 | "FromPort": 80, 179 | "IpProtocol": "tcp", 180 | "SourceSecurityGroupId": {"Ref": "LoadBalancerSecurityGroup"}, 181 | "ToPort": 80 182 | }] 183 | } 184 | }, 185 | "DatabaseSecurityGroup": { 186 | "Type": "AWS::EC2::SecurityGroup", 187 | "Properties": { 188 | "GroupDescription": "awsinaction-db-sg", 189 | "VpcId": {"Ref": "VPC"}, 190 | "SecurityGroupIngress": [{ 191 | "IpProtocol": "tcp", 192 | "FromPort": "3306", 193 | "ToPort": "3306", 194 | "SourceSecurityGroupId": {"Ref": "WebServerSecurityGroup"} 195 | }] 196 | } 197 | }, 198 | "Database": { 199 | "Type": "AWS::RDS::DBInstance", 200 | "DeletionPolicy": "Delete", 201 | "Properties": { 202 | "AllocatedStorage": "5", 203 | "BackupRetentionPeriod": "0", 204 | "DBInstanceClass": "db.t2.micro", 205 | "DBInstanceIdentifier": "awsinaction-db", 206 | "DBName": "wordpress", 207 | "Engine": "MySQL", 208 | "MasterUsername": "wordpress", 209 | "MasterUserPassword": "wordpress", 210 | "VPCSecurityGroups": [{"Fn::GetAtt": ["DatabaseSecurityGroup", "GroupId"]}], 211 | "DBSubnetGroupName": {"Ref": "DBSubnetGroup"} 212 | }, 213 | "DependsOn": "VPCGatewayAttachment" 214 | }, 215 | "DBSubnetGroup" : { 216 | "Type" : "AWS::RDS::DBSubnetGroup", 217 | "Properties" : { 218 | "DBSubnetGroupDescription" : "DB subnet group", 219 | "SubnetIds": [{"Ref": "SubnetA"}, {"Ref": "SubnetB"}] 220 | } 221 | }, 222 | "LaunchConfiguration": { 223 | "Type": "AWS::AutoScaling::LaunchConfiguration", 224 | "Metadata": { 225 | "AWS::CloudFormation::Init": { 226 | "config": { 227 | "packages": { 228 | "yum": { 229 | "php": [], 230 | "php-mysql": [], 231 | "mysql": [], 232 | "httpd": [] 233 | } 234 | }, 235 | "sources": { 236 | "/var/www/html": "https://wordpress.org/wordpress-4.2.4.tar.gz" 237 | }, 238 | "files": { 239 | "/tmp/config": { 240 | "content": {"Fn::Join": ["", [ 241 | "#!/bin/bash -ex\n", 242 | "cp /var/www/html/wordpress/wp-config-sample.php /var/www/html/wordpress/wp-config.php\n", 243 | "sed -i \"s/'database_name_here'/'wordpress'/g\" wp-config.php\n", 244 | "sed -i \"s/'username_here'/'wordpress'/g\" wp-config.php\n", 245 | "sed -i \"s/'password_here'/'wordpress'/g\" wp-config.php\n", 246 | "sed -i \"s/'localhost'/'", {"Fn::GetAtt": ["Database", "Endpoint.Address"]}, "'/g\" wp-config.php\n", 247 | "chmod -R 777 wp-content/ \n" 248 | ]]}, 249 | "mode": "000500", 250 | "owner": "root", 251 | "group": "root" 252 | } 253 | }, 254 | "commands": { 255 | "01_config": { 256 | "command": "/tmp/config", 257 | "cwd": "/var/www/html/wordpress" 258 | } 259 | }, 260 | "services": { 261 | "sysvinit": { 262 | "httpd": { 263 | "enabled": "true", 264 | "ensureRunning": "true" 265 | } 266 | } 267 | } 268 | } 269 | } 270 | }, 271 | "Properties": { 272 | "EbsOptimized": false, 273 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 274 | "InstanceType": "t2.micro", 275 | "SecurityGroups": [{"Ref": "WebServerSecurityGroup"}], 276 | "KeyName": {"Ref": "KeyName"}, 277 | "AssociatePublicIpAddress": true, 278 | "UserData": {"Fn::Base64": {"Fn::Join": ["", [ 279 | "#!/bin/bash -ex\n", 280 | "yum update -y aws-cfn-bootstrap\n", 281 | "/opt/aws/bin/cfn-init -v --stack ", {"Ref": "AWS::StackName"}, " --resource LaunchConfiguration --region ", {"Ref": "AWS::Region"}, "\n", 282 | "/opt/aws/bin/cfn-signal -e $? --stack ", {"Ref": "AWS::StackName"}, " --resource AutoScalingGroup --region ", {"Ref": "AWS::Region"}, "\n" 283 | ]]}} 284 | } 285 | }, 286 | "AutoScalingGroup": { 287 | "Type": "AWS::AutoScaling::AutoScalingGroup", 288 | "Properties": { 289 | "LoadBalancerNames": [{"Ref": "LoadBalancer"}], 290 | "LaunchConfigurationName": {"Ref": "LaunchConfiguration"}, 291 | "MinSize": "2", 292 | "MaxSize": "2", 293 | "DesiredCapacity": "2", 294 | "VPCZoneIdentifier": [{"Ref": "SubnetA"}, {"Ref": "SubnetB"}] 295 | }, 296 | "CreationPolicy": { 297 | "ResourceSignal": { 298 | "Timeout": "PT10M" 299 | } 300 | }, 301 | "DependsOn": "VPCGatewayAttachment" 302 | } 303 | }, 304 | "Outputs": { 305 | "URL": { 306 | "Value": {"Fn::Join": ["", ["http://", {"Fn::GetAtt": ["LoadBalancer", "DNSName"]}, "/wordpress"]]}, 307 | "Description": "Wordpress URL" 308 | } 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /chapter3/a/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | A 4 | 5 | 6 |

Hello A!

7 | 8 | 9 | -------------------------------------------------------------------------------- /chapter3/b/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | B 4 | 5 | 6 |

Hello B!

7 | 8 | 9 | -------------------------------------------------------------------------------- /chapter4/nodecc/README.md: -------------------------------------------------------------------------------- 1 | # Node Control Center for AWS 2 | 3 | ![Node Control Center for AWS](./nodecc.png?raw=true "Node Control Center for AWS") 4 | 5 | Install the dependencies ... 6 | 7 | npm install 8 | 9 | ... and run nodecc 10 | 11 | node index.js 12 | -------------------------------------------------------------------------------- /chapter4/nodecc/index.js: -------------------------------------------------------------------------------- 1 | var blessed = require('blessed'); 2 | 3 | var screen = blessed.screen({ 4 | autoPadding: true, 5 | smartCSR: true, 6 | log: "./nodecc.log" 7 | }); 8 | 9 | screen.title = 'Node Control Center for AWS'; 10 | 11 | var content = blessed.box({ 12 | parent: screen, 13 | width: '70%', 14 | height: '90%', 15 | top: '10%', 16 | left: '30%', 17 | border: { 18 | type: 'none', 19 | fg: '#ffffff' 20 | }, 21 | fg: 'white', 22 | bg: 'blue', 23 | content: '{bold}Node Control Center for AWS{/bold}\n\nPlease select one of the actions from the left and press return.\n\nYou can always go back with the left arrow key.\n\nYou can terminate the application by pressing ESC or q.', 24 | tags: true 25 | }); 26 | 27 | var progress = blessed.progressbar({ 28 | parent: screen, 29 | width: '70%', 30 | height: '10%', 31 | top: '0%', 32 | left: '30%', 33 | orientation: 'horizontal', 34 | border: { 35 | type: 'line', 36 | fg: '#ffffff' 37 | }, 38 | fg: 'white', 39 | bg: 'blue', 40 | barFg: 'green', 41 | barBg: 'green', 42 | filled: 0 43 | }); 44 | 45 | var list = blessed.list({ 46 | parent: screen, 47 | width: '30%', 48 | height: '100%', 49 | top: '0%', 50 | left: '0%', 51 | border: { 52 | type: 'line', 53 | fg: '#ffffff' 54 | }, 55 | fg: 'white', 56 | bg: 'blue', 57 | selectedBg: 'green', 58 | mouse: true, 59 | keys: true, 60 | vi: true, 61 | label: 'actions', 62 | items: ['list servers', 'create server', 'terminate server'] 63 | }); 64 | list.on('select', function(ev, i) { 65 | content.border.type = 'line'; 66 | content.focus(); 67 | list.border.type = 'none'; 68 | open(i); 69 | screen.render(); 70 | }); 71 | list.focus(); 72 | 73 | function open(i) { 74 | screen.log('open(' + i + ')'); 75 | if (i === 0) { 76 | loading(); 77 | require('./lib/listServers.js')(function(err, instanceIds) { 78 | loaded(); 79 | if (err) { 80 | log('error', 'listServers cb err: ' + err); 81 | } else { 82 | var instanceList = blessed.list({ 83 | fg: 'white', 84 | bg: 'blue', 85 | selectedBg: 'green', 86 | mouse: true, 87 | keys: true, 88 | vi: true, 89 | items: instanceIds 90 | }); 91 | content.append(instanceList); 92 | instanceList.focus(); 93 | instanceList.on('select', function(ev, i) { 94 | loading(); 95 | require('./lib/showServer.js')(instanceIds[i], function(err, instance) { 96 | loaded(); 97 | if (err) { 98 | log('error', 'showServer cb err: ' + err); 99 | } else { 100 | var serverContent = blessed.box({ 101 | fg: 'white', 102 | bg: 'blue', 103 | content: 104 | 'InstanceId: ' + instance.InstanceId + '\n' + 105 | 'InstanceType: ' + instance.InstanceType + '\n' + 106 | 'LaunchTime: ' + instance.LaunchTime + '\n' + 107 | 'ImageId: ' + instance.ImageId + '\n' + 108 | 'PublicDnsName: ' + instance.PublicDnsName 109 | }); 110 | content.append(serverContent); 111 | } 112 | screen.render(); 113 | }); 114 | }); 115 | screen.render(); 116 | } 117 | screen.render(); 118 | }); 119 | } else if (i === 1) { 120 | loading(); 121 | require('./lib/listAMIs.js')(function(err, result) { 122 | loaded(); 123 | if (err) { 124 | log('error', 'listAMIs cb err: ' + err); 125 | } else { 126 | var amiList = blessed.list({ 127 | fg: 'white', 128 | bg: 'blue', 129 | selectedBg: 'green', 130 | mouse: true, 131 | keys: true, 132 | vi: true, 133 | items: result.descriptions 134 | }); 135 | content.append(amiList); 136 | amiList.focus(); 137 | amiList.on('select', function(ev, i) { 138 | var amiId = result.amiIds[i]; 139 | loading(); 140 | require('./lib/listSubnets.js')(function(err, subnetIds) { 141 | loaded(); 142 | if (err) { 143 | log('error', 'listSubnets cb err: ' + err); 144 | } else { 145 | var subnetList = blessed.list({ 146 | fg: 'white', 147 | bg: 'blue', 148 | selectedBg: 'green', 149 | mouse: true, 150 | keys: true, 151 | vi: true, 152 | items: subnetIds 153 | }); 154 | content.append(subnetList); 155 | subnetList.focus(); 156 | subnetList.on('select', function(ev, i) { 157 | loading(); 158 | require('./lib/createServer.js')(amiId, subnetIds[i], function(err) { 159 | loaded(); 160 | if (err) { 161 | log('error', 'createServer cb err: ' + err); 162 | } else { 163 | var serverContent = blessed.box({ 164 | fg: 'white', 165 | bg: 'blue', 166 | content: 'starting ...' 167 | }); 168 | content.append(serverContent); 169 | } 170 | screen.render(); 171 | }); 172 | }); 173 | screen.render(); 174 | } 175 | screen.render(); 176 | }); 177 | }); 178 | screen.render(); 179 | } 180 | screen.render(); 181 | }); 182 | } else if (i === 2) { 183 | loading(); 184 | require('./lib/listServers.js')(function(err, instanceIds) { 185 | loaded(); 186 | if (err) { 187 | log('error', 'listServers cb err: ' + err); 188 | } else { 189 | var instanceList = blessed.list({ 190 | fg: 'white', 191 | bg: 'blue', 192 | selectedBg: 'green', 193 | mouse: true, 194 | keys: true, 195 | vi: true, 196 | items: instanceIds 197 | }); 198 | content.append(instanceList); 199 | instanceList.focus(); 200 | instanceList.on('select', function(ev, i) { 201 | loading(); 202 | require('./lib/terminateServer.js')(instanceIds[i], function(err) { 203 | loaded(); 204 | if (err) { 205 | log('error', 'terminateServer cb err: ' + err); 206 | } else { 207 | var serverContent = blessed.box({ 208 | fg: 'white', 209 | bg: 'blue', 210 | content: 'terminating ...' 211 | }); 212 | content.append(serverContent); 213 | } 214 | screen.render(); 215 | }); 216 | }); 217 | screen.render(); 218 | } 219 | screen.render(); 220 | }); 221 | } else { 222 | log('error', 'not supported'); 223 | screen.render(); 224 | } 225 | } 226 | 227 | screen.key('left', function(ch, key) { 228 | content.border.type = 'none'; 229 | content.children.slice().forEach(function(child) { 230 | content.remove(child); 231 | }); 232 | list.border.type = 'line'; 233 | list.focus(); 234 | screen.render(); 235 | }); 236 | 237 | screen.key(['escape', 'q', 'C-c'], function(ch, key) { 238 | return process.exit(0); 239 | }); 240 | 241 | var loadingInterval; 242 | 243 | function loading() { 244 | progress.reset(); 245 | clearInterval(loadingInterval); 246 | loadingInterval = setInterval(function() { 247 | if (progress.filled < 75) { 248 | progress.progress(progress.filled + 5); 249 | } 250 | screen.render(); 251 | }, 200); 252 | } 253 | 254 | function loaded() { 255 | clearInterval(loadingInterval); 256 | progress.progress(100); 257 | screen.render(); 258 | } 259 | 260 | function log(level, message) { 261 | screen.log('[' + level + ']: ' + message); 262 | } 263 | 264 | screen.render(); 265 | -------------------------------------------------------------------------------- /chapter4/nodecc/lib/createServer.js: -------------------------------------------------------------------------------- 1 | var AWS = require('aws-sdk'); 2 | var ec2 = new AWS.EC2({ 3 | "region": "us-east-1" 4 | }); 5 | 6 | module.exports = function(amiId, subnetId, cb) { 7 | ec2.runInstances({ 8 | "ImageId": amiId, 9 | "MinCount": 1, 10 | "MaxCount": 1, 11 | "KeyName": "mykey", 12 | "InstanceType": "t2.micro", 13 | "SubnetId": subnetId 14 | }, function(err) { 15 | if (err) { 16 | cb(err); 17 | } else { 18 | cb(null); 19 | } 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /chapter4/nodecc/lib/listAMIs.js: -------------------------------------------------------------------------------- 1 | var jmespath = require('jmespath'); 2 | var AWS = require('aws-sdk'); 3 | var ec2 = new AWS.EC2({ 4 | "region": "us-east-1" 5 | }); 6 | 7 | module.exports = function(cb) { 8 | ec2.describeImages({ 9 | "Filters": [{ 10 | "Name": "description", 11 | "Values": ["Amazon Linux AMI 2015.03.? x86_64 HVM GP2"] 12 | }] 13 | }, function(err, data) { 14 | if (err) { 15 | cb(err); 16 | } else { 17 | var amiIds = jmespath.search(data, 'Images[*].ImageId'); 18 | var descriptions = jmespath.search(data, 'Images[*].Description'); 19 | cb(null, {"amiIds": amiIds, "descriptions": descriptions}); 20 | } 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /chapter4/nodecc/lib/listServers.js: -------------------------------------------------------------------------------- 1 | var jmespath = require('jmespath'); 2 | var AWS = require('aws-sdk'); 3 | var ec2 = new AWS.EC2({ 4 | "region": "us-east-1" 5 | }); 6 | 7 | module.exports = function(cb) { 8 | ec2.describeInstances({ 9 | "Filters": [{ 10 | "Name": "instance-state-name", 11 | "Values": ["pending", "running"] 12 | }], 13 | "MaxResults": 10 14 | }, function(err, data) { 15 | if (err) { 16 | cb(err); 17 | } else { 18 | var instanceIds = jmespath.search(data, 'Reservations[].Instances[].InstanceId'); 19 | cb(null, instanceIds); 20 | } 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /chapter4/nodecc/lib/listSubnets.js: -------------------------------------------------------------------------------- 1 | var jmespath = require('jmespath'); 2 | var AWS = require('aws-sdk'); 3 | var ec2 = new AWS.EC2({ 4 | "region": "us-east-1" 5 | }); 6 | 7 | module.exports = function(cb) { 8 | ec2.describeVpcs({ 9 | "Filters": [{ 10 | "Name": "isDefault", 11 | "Values": ["true"] 12 | }] 13 | }, function(err, data) { 14 | if (err) { 15 | cb(err); 16 | } else { 17 | var vpcId = data.Vpcs[0].VpcId; 18 | ec2.describeSubnets({ 19 | "Filters": [{ 20 | "Name": "vpc-id", 21 | "Values": [vpcId] 22 | }] 23 | }, function(err, data) { 24 | if (err) { 25 | cb(err); 26 | } else { 27 | var subnetIds = jmespath.search(data, 'Subnets[*].SubnetId'); 28 | cb(null, subnetIds); 29 | } 30 | }); 31 | } 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /chapter4/nodecc/lib/showServer.js: -------------------------------------------------------------------------------- 1 | var AWS = require('aws-sdk'); 2 | var ec2 = new AWS.EC2({ 3 | "region": "us-east-1" 4 | }); 5 | 6 | module.exports = function(instanceId, cb) { 7 | ec2.describeInstances({ 8 | "InstanceIds": [instanceId] 9 | }, function(err, data) { 10 | if (err) { 11 | cb(err); 12 | } else { 13 | cb(null, data.Reservations[0].Instances[0]); 14 | } 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /chapter4/nodecc/lib/terminateServer.js: -------------------------------------------------------------------------------- 1 | var AWS = require('aws-sdk'); 2 | var ec2 = new AWS.EC2({ 3 | "region": "us-east-1" 4 | }); 5 | 6 | module.exports = function(instanceId, cb) { 7 | ec2.terminateInstances({ 8 | "InstanceIds": [instanceId] 9 | }, function(err) { 10 | if (err) { 11 | cb(err); 12 | } else { 13 | cb(null); 14 | } 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /chapter4/nodecc/nodecc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AWSinAction/code/9acfdeb3642a3b7e1af82c7bc78bdf10ac2bd9f3/chapter4/nodecc/nodecc.png -------------------------------------------------------------------------------- /chapter4/nodecc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "aws-sdk": "2.1.18", 4 | "blessed": "0.0.51", 5 | "jmespath": "0.10.0" 6 | }, 7 | "private": true 8 | } 9 | -------------------------------------------------------------------------------- /chapter4/server.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 4", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "VPC": { 11 | "Description": "Just select the one and only default VPC", 12 | "Type": "AWS::EC2::VPC::Id" 13 | }, 14 | "Subnet": { 15 | "Description": "Just select one of the available subnets", 16 | "Type": "AWS::EC2::Subnet::Id" 17 | }, 18 | "InstanceType": { 19 | "Description": "Select one of the possible instance types", 20 | "Type": "String", 21 | "Default": "t2.micro", 22 | "AllowedValues": ["t2.micro", "t2.small", "t2.medium"] 23 | } 24 | }, 25 | "Mappings": { 26 | "EC2RegionMap": { 27 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 28 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 29 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 30 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 31 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 32 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 33 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 34 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 35 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 36 | } 37 | }, 38 | "Resources": { 39 | "SecurityGroup": { 40 | "Type": "AWS::EC2::SecurityGroup", 41 | "Properties": { 42 | "GroupDescription": "My security group", 43 | "VpcId": {"Ref": "VPC"}, 44 | "SecurityGroupIngress": [{ 45 | "CidrIp": "0.0.0.0/0", 46 | "FromPort": 22, 47 | "IpProtocol": "tcp", 48 | "ToPort": 22 49 | }] 50 | } 51 | }, 52 | "Server": { 53 | "Type": "AWS::EC2::Instance", 54 | "Properties": { 55 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 56 | "InstanceType": {"Ref": "InstanceType"}, 57 | "KeyName": {"Ref": "KeyName"}, 58 | "SecurityGroupIds": [{"Ref": "SecurityGroup"}], 59 | "SubnetId": {"Ref": "Subnet"} 60 | } 61 | } 62 | }, 63 | "Outputs": { 64 | "PublicName": { 65 | "Value": {"Fn::GetAtt": ["Server", "PublicDnsName"]}, 66 | "Description": "Public name (connect via SSH as user ec2-user)" 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /chapter4/server.ps1: -------------------------------------------------------------------------------- 1 | # To start PowerShell scripts first start PowerShell as Administrator 2 | # to allow unsigned scripts to be executed. To do so enter: 3 | # Set-ExecutionPolicy Unrestricted 4 | # Close the PowerShell window (you don't need Administrator privileges to run the scripts) 5 | # 6 | # You also need to install the AWS Command Line Interface from http://aws.amazon.com/cli/ 7 | # 8 | # Right click on the *.ps1 file and select Run with PowerShell 9 | $ErrorActionPreference = "Stop" 10 | 11 | $AMIID=aws ec2 describe-images --filters "Name=description, Values=Amazon Linux AMI 2015.03.? x86_64 HVM GP2" --query "Images[0].ImageId" --output text 12 | $VPCID=aws ec2 describe-vpcs --filter "Name=isDefault, Values=true" --query "Vpcs[0].VpcId" --output text 13 | $SUBNETID=aws ec2 describe-subnets --filters "Name=vpc-id, Values=$VPCID" --query "Subnets[0].SubnetId" --output text 14 | $SGID=aws ec2 create-security-group --group-name mysecuritygroup --description "My security group" --vpc-id $VPCID --output text 15 | aws ec2 authorize-security-group-ingress --group-id $SGID --protocol tcp --port 22 --cidr 0.0.0.0/0 16 | $INSTANCEID=aws ec2 run-instances --image-id $AMIID --key-name mykey --instance-type t2.micro --security-group-ids $SGID --subnet-id $SUBNETID --query "Instances[0].InstanceId" --output text 17 | Write-Host "waiting for $INSTANCEID ..." 18 | aws ec2 wait instance-running --instance-ids $INSTANCEID 19 | $PUBLICNAME=aws ec2 describe-instances --instance-ids $INSTANCEID --query "Reservations[0].Instances[0].PublicDnsName" --output text 20 | Write-Host "$INSTANCEID is accepting SSH connections under $PUBLICNAME" 21 | Write-Host "connect to $PUBLICNAME via SSH as user ec2-user" 22 | Write-Host "Press [Enter] key to terminate $INSTANCEID ..." 23 | Read-Host 24 | aws ec2 terminate-instances --instance-ids $INSTANCEID 25 | Write-Host "terminating $INSTANCEID ..." 26 | aws ec2 wait instance-terminated --instance-ids $INSTANCEID 27 | aws ec2 delete-security-group --group-id $SGID 28 | Write-Host "done." 29 | -------------------------------------------------------------------------------- /chapter4/server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # You need to install the AWS Command Line Interface from http://aws.amazon.com/cli/ 3 | AMIID=$(aws ec2 describe-images --filters "Name=description, Values=Amazon Linux AMI 2015.03.? x86_64 HVM GP2" --query "Images[0].ImageId" --output text) 4 | VPCID=$(aws ec2 describe-vpcs --filter "Name=isDefault, Values=true" --query "Vpcs[0].VpcId" --output text) 5 | SUBNETID=$(aws ec2 describe-subnets --filters "Name=vpc-id, Values=$VPCID" --query "Subnets[0].SubnetId" --output text) 6 | SGID=$(aws ec2 create-security-group --group-name mysecuritygroup --description "My security group" --vpc-id $VPCID --output text) 7 | aws ec2 authorize-security-group-ingress --group-id $SGID --protocol tcp --port 22 --cidr 0.0.0.0/0 8 | INSTANCEID=$(aws ec2 run-instances --image-id $AMIID --key-name mykey --instance-type t2.micro --security-group-ids $SGID --subnet-id $SUBNETID --query "Instances[0].InstanceId" --output text) 9 | echo "waiting for $INSTANCEID ..." 10 | aws ec2 wait instance-running --instance-ids $INSTANCEID 11 | PUBLICNAME=$(aws ec2 describe-instances --instance-ids $INSTANCEID --query "Reservations[0].Instances[0].PublicDnsName" --output text) 12 | echo "$INSTANCEID is accepting SSH connections under $PUBLICNAME" 13 | echo "ssh -i mykey.pem ec2-user@$PUBLICNAME" 14 | read -p "Press [Enter] key to terminate $INSTANCEID ..." 15 | aws ec2 terminate-instances --instance-ids $INSTANCEID 16 | echo "terminating $INSTANCEID ..." 17 | aws ec2 wait instance-terminated --instance-ids $INSTANCEID 18 | aws ec2 delete-security-group --group-id $SGID 19 | echo "done." 20 | -------------------------------------------------------------------------------- /chapter5/Dev.md: -------------------------------------------------------------------------------- 1 | # How to create a new etherpad.zip 2 | 3 | 1. On Amazon Linux (see version here http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/concepts.platforms.html#concepts.platforms.nodejs) 4 | 1. Download latest version for Linux/Max from http://etherpad.org/#download 5 | 1. cd into the unzipped directory 6 | 1. Create `.ebextensions/custom.config` with the following content: 7 | ``` 8 | option_settings: 9 | aws:elasticbeanstalk:container:nodejs: 10 | NodeCommand: "bin/run.sh" 11 | ``` 12 | 1. Copy `settings.json.template` to `settings.json` and change port to 8081 13 | 1. Run `zip -r etherpad.zip ./` 14 | -------------------------------------------------------------------------------- /chapter5/etherpad.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AWSinAction/code/9acfdeb3642a3b7e1af82c7bc78bdf10ac2bd9f3/chapter5/etherpad.zip -------------------------------------------------------------------------------- /chapter5/irc-cloudformation.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 5 (firewall for IRC server)", 4 | "Parameters": { 5 | "VPC": { 6 | "Description": "Just select the one and only default VPC.", 7 | "Type": "AWS::EC2::VPC::Id" 8 | } 9 | }, 10 | "Resources": { 11 | "SecurityGroup": { 12 | "Type": "AWS::EC2::SecurityGroup", 13 | "Properties": { 14 | "GroupDescription": "Enables access to IRC server", 15 | "VpcId": {"Ref": "VPC"}, 16 | "SecurityGroupIngress": [ 17 | { 18 | "IpProtocol": "tcp", 19 | "FromPort": "6667", 20 | "ToPort": "6667", 21 | "CidrIp": "0.0.0.0/0" 22 | } 23 | ] 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /chapter5/irc-create-cloudformation-stack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | vpc=$(aws ec2 describe-vpcs --filter "Name=isDefault, Values=true" --query "Vpcs[0].VpcId" --output text) 4 | aws cloudformation create-stack --stack-name irc --template-url https://s3.amazonaws.com/awsinaction/chapter5/irc-cloudformation.json --parameters ParameterKey=VPC,ParameterValue=$vpc 5 | 6 | while [[ `aws cloudformation describe-stacks --stack-name irc --query Stacks[0].StackStatus` != *"COMPLETE"* ]] 7 | do 8 | sleep 10 9 | done 10 | -------------------------------------------------------------------------------- /chapter5/vpn-cloudformation.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 5 (OpenSwan acting as VPN IPSec endpoint)", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "key for SSH access", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "ConstraintDescription": "Must be the name of an existing key pair." 9 | }, 10 | "VPC": { 11 | "Description": "Just select the one and only default VPC.", 12 | "Type": "AWS::EC2::VPC::Id" 13 | }, 14 | "Subnet": { 15 | "Description": "Just select one of the available subnets.", 16 | "Type": "AWS::EC2::Subnet::Id" 17 | }, 18 | "IPSecSharedSecret": { 19 | "Description": "The shared secret key for IPSec.", 20 | "Type": "String" 21 | }, 22 | "VPNUser": { 23 | "Description": "The VPN user.", 24 | "Type": "String" 25 | }, 26 | "VPNPassword": { 27 | "Description": "The VPN password.", 28 | "Type": "String" 29 | } 30 | }, 31 | "Mappings": { 32 | "EC2RegionMap": { 33 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 34 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 35 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 36 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 37 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 38 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 39 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 40 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 41 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 42 | } 43 | }, 44 | "Resources": { 45 | "EC2Instance": { 46 | "Type": "AWS::EC2::Instance", 47 | "Properties": { 48 | "InstanceType": "t2.micro", 49 | "SecurityGroupIds": [{"Ref": "InstanceSecurityGroup"}], 50 | "KeyName": {"Ref": "KeyName"}, 51 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 52 | "SubnetId": {"Ref": "Subnet"}, 53 | "UserData": {"Fn::Base64": {"Fn::Join": ["", [ 54 | "#!/bin/bash -ex\n", 55 | "export IPSEC_PSK=", {"Ref": "IPSecSharedSecret"}, "\n", 56 | "export VPN_USER=", {"Ref": "VPNUser"}, "\n", 57 | "export VPN_PASSWORD=", {"Ref": "VPNPassword"}, "\n", 58 | "export STACK_NAME=", {"Ref": "AWS::StackName"}, "\n", 59 | "export REGION=", {"Ref": "AWS::Region"}, "\n", 60 | "curl -s https://raw.githubusercontent.com/AWSinAction/code/master/chapter5/vpn-setup.sh | bash -ex\n" 61 | ]]}} 62 | } 63 | }, 64 | "InstanceSecurityGroup": { 65 | "Type": "AWS::EC2::SecurityGroup", 66 | "Properties": { 67 | "GroupDescription": "Enable access to VPN server", 68 | "VpcId": {"Ref": "VPC"}, 69 | "SecurityGroupIngress": [ 70 | { 71 | "IpProtocol": "tcp", 72 | "FromPort": "22", 73 | "ToPort": "22", 74 | "CidrIp": "0.0.0.0/0" 75 | }, 76 | { 77 | "IpProtocol": "udp", 78 | "FromPort": "500", 79 | "ToPort": "500", 80 | "CidrIp": "0.0.0.0/0" 81 | }, 82 | { 83 | "IpProtocol": "udp", 84 | "FromPort": "1701", 85 | "ToPort": "1701", 86 | "CidrIp": "0.0.0.0/0" 87 | }, 88 | { 89 | "IpProtocol": "udp", 90 | "FromPort": "4500", 91 | "ToPort": "4500", 92 | "CidrIp": "0.0.0.0/0" 93 | } 94 | ] 95 | } 96 | } 97 | }, 98 | "Outputs": { 99 | "ServerIP": { 100 | "Description": "Public IP address of the vpn server", 101 | "Value": {"Fn::GetAtt": ["EC2Instance", "PublicIp"]} 102 | }, 103 | "IPSecSharedSecret": { 104 | "Description": "The shared key for the VPN connection (IPSec)", 105 | "Value": {"Ref": "IPSecSharedSecret"} 106 | }, 107 | "VPNUser": { 108 | "Description": "The username for the vpn connection", 109 | "Value": {"Ref": "VPNUser"} 110 | }, 111 | "VPNPassword": { 112 | "Description": "The password for the vpn connection", 113 | "Value": {"Ref": "VPNPassword"} 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /chapter5/vpn-create-cloudformation-stack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | vpc=$(aws ec2 describe-vpcs --filter "Name=isDefault, Values=true" --query "Vpcs[0].VpcId" --output text) 4 | subnet=$(aws ec2 describe-subnets --filters Name=vpc-id,Values=$vpc --query Subnets[0].SubnetId --output text) 5 | sharedsecret=$(openssl rand -base64 30) 6 | user=vpn 7 | password=$(openssl rand -base64 30) 8 | 9 | aws cloudformation create-stack --stack-name vpn --template-url https://s3.amazonaws.com/awsinaction/chapter5/vpn-cloudformation.json --parameters ParameterKey=KeyName,ParameterValue=mykey ParameterKey=VPC,ParameterValue=$vpc ParameterKey=Subnet,ParameterValue=$subnet ParameterKey=IPSecSharedSecret,ParameterValue=$sharedsecret ParameterKey=VPNUser,ParameterValue=$user ParameterKey=VPNPassword,ParameterValue=$password 10 | 11 | while [[ `aws cloudformation describe-stacks --stack-name vpn --query Stacks[0].StackStatus` != *"COMPLETE"* ]] 12 | do 13 | sleep 10 14 | done 15 | aws cloudformation describe-stacks --stack-name vpn --query Stacks[0].Outputs 16 | -------------------------------------------------------------------------------- /chapter5/vpn-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | #param IPSEC_PSK the shared secret 4 | #param VPN_USER the vpn username 5 | #param VPN_PASSWORD the vpn password 6 | #param STACK_NAME 7 | #param REGION 8 | 9 | PRIVATE_IP=`curl -s http://169.254.169.254/latest/meta-data/local-ipv4` 10 | PUBLIC_IP=`curl -s http://169.254.169.254/latest/meta-data/public-ipv4` 11 | 12 | yum-config-manager --enable epel && yum clean all 13 | yum install -y openswan xl2tpd 14 | 15 | cat > /etc/ipsec.conf < /etc/ipsec.secrets < /etc/xl2tpd/xl2tpd.conf < /etc/ppp/chap-secrets < /etc/ppp/options.xl2tpd < /proc/sys/net/ipv4/ip_forward 88 | iptables-save > /etc/iptables.rules 89 | 90 | mkdir -p /etc/network/if-pre-up.d 91 | cat > /etc/network/if-pre-up.d/iptablesload < /proc/sys/net/ipv4/ip_forward 95 | exit 0 96 | EOF 97 | 98 | service ipsec start && service xl2tpd start 99 | chkconfig ipsec on && chkconfig xl2tpd on 100 | 101 | /opt/aws/bin/cfn-signal --stack $STACK_NAME --resource EC2Instance --region $REGION -------------------------------------------------------------------------------- /chapter6/firewall1.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 6 (firewall 1)", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "VPC": { 11 | "Description": "Just select the one and only default VPC", 12 | "Type": "AWS::EC2::VPC::Id" 13 | }, 14 | "Subnet": { 15 | "Description": "Just select one of the available subnets", 16 | "Type": "AWS::EC2::Subnet::Id" 17 | } 18 | }, 19 | "Mappings": { 20 | "EC2RegionMap": { 21 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 22 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 23 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 24 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 25 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 26 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 27 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 28 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 29 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 30 | } 31 | }, 32 | "Resources": { 33 | "SecurityGroup": { 34 | "Type": "AWS::EC2::SecurityGroup", 35 | "Properties": { 36 | "GroupDescription": "My security group", 37 | "VpcId": {"Ref": "VPC"} 38 | } 39 | }, 40 | "Server": { 41 | "Type": "AWS::EC2::Instance", 42 | "Properties": { 43 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 44 | "InstanceType": "t2.micro", 45 | "KeyName": {"Ref": "KeyName"}, 46 | "SecurityGroupIds": [{"Ref": "SecurityGroup"}], 47 | "SubnetId": {"Ref": "Subnet"} 48 | } 49 | } 50 | }, 51 | "Outputs": { 52 | "PublicName": { 53 | "Value": {"Fn::GetAtt": ["Server", "PublicDnsName"]}, 54 | "Description": "Public name (connect via SSH as user ec2-user)" 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /chapter6/firewall2.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 6 (firewall 2)", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "VPC": { 11 | "Description": "Just select the one and only default VPC", 12 | "Type": "AWS::EC2::VPC::Id" 13 | }, 14 | "Subnet": { 15 | "Description": "Just select one of the available subnets", 16 | "Type": "AWS::EC2::Subnet::Id" 17 | } 18 | }, 19 | "Mappings": { 20 | "EC2RegionMap": { 21 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 22 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 23 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 24 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 25 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 26 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 27 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 28 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 29 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 30 | } 31 | }, 32 | "Resources": { 33 | "SecurityGroup": { 34 | "Type": "AWS::EC2::SecurityGroup", 35 | "Properties": { 36 | "GroupDescription": "My security group", 37 | "VpcId": {"Ref": "VPC"} 38 | } 39 | }, 40 | "AllowInboundICMP": { 41 | "Type": "AWS::EC2::SecurityGroupIngress", 42 | "Properties": { 43 | "GroupId": {"Ref": "SecurityGroup"}, 44 | "IpProtocol": "icmp", 45 | "FromPort": "-1", 46 | "ToPort": "-1", 47 | "CidrIp": "0.0.0.0/0" 48 | } 49 | }, 50 | "Server": { 51 | "Type": "AWS::EC2::Instance", 52 | "Properties": { 53 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 54 | "InstanceType": "t2.micro", 55 | "KeyName": {"Ref": "KeyName"}, 56 | "SecurityGroupIds": [{"Ref": "SecurityGroup"}], 57 | "SubnetId": {"Ref": "Subnet"} 58 | } 59 | } 60 | }, 61 | "Outputs": { 62 | "PublicName": { 63 | "Value": {"Fn::GetAtt": ["Server", "PublicDnsName"]}, 64 | "Description": "Public name (connect via SSH as user ec2-user)" 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /chapter6/firewall3.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 6 (firewall 3)", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "VPC": { 11 | "Description": "Just select the one and only default VPC", 12 | "Type": "AWS::EC2::VPC::Id" 13 | }, 14 | "Subnet": { 15 | "Description": "Just select one of the available subnets", 16 | "Type": "AWS::EC2::Subnet::Id" 17 | } 18 | }, 19 | "Mappings": { 20 | "EC2RegionMap": { 21 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 22 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 23 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 24 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 25 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 26 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 27 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 28 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 29 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 30 | } 31 | }, 32 | "Resources": { 33 | "SecurityGroup": { 34 | "Type": "AWS::EC2::SecurityGroup", 35 | "Properties": { 36 | "GroupDescription": "My security group", 37 | "VpcId": {"Ref": "VPC"} 38 | } 39 | }, 40 | "AllowInboundICMP": { 41 | "Type": "AWS::EC2::SecurityGroupIngress", 42 | "Properties": { 43 | "GroupId": {"Ref": "SecurityGroup"}, 44 | "IpProtocol": "icmp", 45 | "FromPort": "-1", 46 | "ToPort": "-1", 47 | "CidrIp": "0.0.0.0/0" 48 | } 49 | }, 50 | "AllowInboundSSH": { 51 | "Type": "AWS::EC2::SecurityGroupIngress", 52 | "Properties": { 53 | "GroupId": {"Ref": "SecurityGroup"}, 54 | "IpProtocol": "tcp", 55 | "FromPort": "22", 56 | "ToPort": "22", 57 | "CidrIp": "0.0.0.0/0" 58 | } 59 | }, 60 | "Server": { 61 | "Type": "AWS::EC2::Instance", 62 | "Properties": { 63 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 64 | "InstanceType": "t2.micro", 65 | "KeyName": {"Ref": "KeyName"}, 66 | "SecurityGroupIds": [{"Ref": "SecurityGroup"}], 67 | "SubnetId": {"Ref": "Subnet"} 68 | } 69 | } 70 | }, 71 | "Outputs": { 72 | "PublicName": { 73 | "Value": {"Fn::GetAtt": ["Server", "PublicDnsName"]}, 74 | "Description": "Public name (connect via SSH as user ec2-user)" 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /chapter6/firewall4.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 6 (firewall 4)", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "VPC": { 11 | "Description": "Just select the one and only default VPC", 12 | "Type": "AWS::EC2::VPC::Id" 13 | }, 14 | "Subnet": { 15 | "Description": "Just select one of the available subnets", 16 | "Type": "AWS::EC2::Subnet::Id" 17 | }, 18 | "IpForSSH": { 19 | "Description": "Your public IP address to allow SSH access", 20 | "Type": "String" 21 | } 22 | }, 23 | "Mappings": { 24 | "EC2RegionMap": { 25 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 26 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 27 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 28 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 29 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 30 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 31 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 32 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 33 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 34 | } 35 | }, 36 | "Resources": { 37 | "SecurityGroup": { 38 | "Type": "AWS::EC2::SecurityGroup", 39 | "Properties": { 40 | "GroupDescription": "My security group", 41 | "VpcId": {"Ref": "VPC"} 42 | } 43 | }, 44 | "AllowInboundICMP": { 45 | "Type": "AWS::EC2::SecurityGroupIngress", 46 | "Properties": { 47 | "GroupId": {"Ref": "SecurityGroup"}, 48 | "IpProtocol": "icmp", 49 | "FromPort": "-1", 50 | "ToPort": "-1", 51 | "CidrIp": "0.0.0.0/0" 52 | } 53 | }, 54 | "AllowInboundSSH": { 55 | "Type": "AWS::EC2::SecurityGroupIngress", 56 | "Properties": { 57 | "GroupId": {"Ref": "SecurityGroup"}, 58 | "IpProtocol": "tcp", 59 | "FromPort": "22", 60 | "ToPort": "22", 61 | "CidrIp": {"Fn::Join": ["", [{"Ref": "IpForSSH"}, "/32"]]} 62 | } 63 | }, 64 | "Server": { 65 | "Type": "AWS::EC2::Instance", 66 | "Properties": { 67 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 68 | "InstanceType": "t2.micro", 69 | "KeyName": {"Ref": "KeyName"}, 70 | "SecurityGroupIds": [{"Ref": "SecurityGroup"}], 71 | "SubnetId": {"Ref": "Subnet"} 72 | } 73 | } 74 | }, 75 | "Outputs": { 76 | "PublicName": { 77 | "Value": {"Fn::GetAtt": ["Server", "PublicDnsName"]}, 78 | "Description": "Public name (connect via SSH as user ec2-user)" 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /chapter6/firewall5.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 6 (firewall 5)", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "VPC": { 11 | "Description": "Just select the one and only default VPC", 12 | "Type": "AWS::EC2::VPC::Id" 13 | }, 14 | "Subnet": { 15 | "Description": "Just select one of the available subnets", 16 | "Type": "AWS::EC2::Subnet::Id" 17 | }, 18 | "IpForSSH": { 19 | "Description": "Your public IP address to allow SSH access", 20 | "Type": "String" 21 | } 22 | }, 23 | "Mappings": { 24 | "EC2RegionMap": { 25 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 26 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 27 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 28 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 29 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 30 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 31 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 32 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 33 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 34 | } 35 | }, 36 | "Resources": { 37 | "SecurityGroup": { 38 | "Type": "AWS::EC2::SecurityGroup", 39 | "Properties": { 40 | "GroupDescription": "My security group", 41 | "VpcId": {"Ref": "VPC"} 42 | } 43 | }, 44 | "AllowInboundICMP": { 45 | "Type": "AWS::EC2::SecurityGroupIngress", 46 | "Properties": { 47 | "GroupId": {"Ref": "SecurityGroup"}, 48 | "IpProtocol": "icmp", 49 | "FromPort": "-1", 50 | "ToPort": "-1", 51 | "CidrIp": "0.0.0.0/0" 52 | } 53 | }, 54 | "AllowInboundSSH": { 55 | "Type": "AWS::EC2::SecurityGroupIngress", 56 | "Properties": { 57 | "GroupId": {"Ref": "SecurityGroup"}, 58 | "IpProtocol": "tcp", 59 | "FromPort": "22", 60 | "ToPort": "22", 61 | "CidrIp": {"Fn::Join": ["", [{"Ref": "IpForSSH"}, "/32"]]} 62 | } 63 | }, 64 | "SecurityGroupPrivate": { 65 | "Type": "AWS::EC2::SecurityGroup", 66 | "Properties": { 67 | "GroupDescription": "My security group", 68 | "VpcId": {"Ref": "VPC"} 69 | } 70 | }, 71 | "AllowPrivateInboundSSH": { 72 | "Type": "AWS::EC2::SecurityGroupIngress", 73 | "Properties": { 74 | "GroupId": {"Ref": "SecurityGroupPrivate"}, 75 | "IpProtocol": "tcp", 76 | "FromPort": "22", 77 | "ToPort": "22", 78 | "SourceSecurityGroupId": {"Ref": "SecurityGroup"} 79 | } 80 | }, 81 | "BastionHost": { 82 | "Type": "AWS::EC2::Instance", 83 | "Properties": { 84 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 85 | "InstanceType": "t2.micro", 86 | "KeyName": {"Ref": "KeyName"}, 87 | "SecurityGroupIds": [{"Ref": "SecurityGroup"}], 88 | "SubnetId": {"Ref": "Subnet"} 89 | } 90 | }, 91 | "Server1": { 92 | "Type": "AWS::EC2::Instance", 93 | "Properties": { 94 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 95 | "InstanceType": "t2.micro", 96 | "KeyName": {"Ref": "KeyName"}, 97 | "SecurityGroupIds": [{"Ref": "SecurityGroupPrivate"}], 98 | "SubnetId": {"Ref": "Subnet"} 99 | } 100 | }, 101 | "Server2": { 102 | "Type": "AWS::EC2::Instance", 103 | "Properties": { 104 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 105 | "InstanceType": "t2.micro", 106 | "KeyName": {"Ref": "KeyName"}, 107 | "SecurityGroupIds": [{"Ref": "SecurityGroupPrivate"}], 108 | "SubnetId": {"Ref": "Subnet"} 109 | } 110 | } 111 | }, 112 | "Outputs": { 113 | "BastionHostPublicName": { 114 | "Value": {"Fn::GetAtt": ["BastionHost", "PublicDnsName"]}, 115 | "Description": "Bastion host public name (connect via SSH as user ec2-user)" 116 | }, 117 | "Server1PublicName": { 118 | "Value": {"Fn::GetAtt": ["Server1", "PublicDnsName"]}, 119 | "Description": "Server1 public name" 120 | }, 121 | "Server2PublicName": { 122 | "Value": {"Fn::GetAtt": ["Server2", "PublicDnsName"]}, 123 | "Description": "Server2 public name" 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /chapter6/server.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 6 (IAM role)", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "VPC": { 11 | "Description": "Just select the one and only default VPC", 12 | "Type": "AWS::EC2::VPC::Id" 13 | }, 14 | "Subnet": { 15 | "Description": "Just select one of the available subnets", 16 | "Type": "AWS::EC2::Subnet::Id" 17 | }, 18 | "Lifetime": { 19 | "Description": "Lifetime in minutes (2-59)", 20 | "Type": "Number", 21 | "Default": "2", 22 | "MinValue": "2", 23 | "MaxValue": "59" 24 | } 25 | }, 26 | "Mappings": { 27 | "EC2RegionMap": { 28 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 29 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 30 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 31 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 32 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 33 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 34 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 35 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 36 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 37 | } 38 | }, 39 | "Resources": { 40 | "SecurityGroup": { 41 | "Type": "AWS::EC2::SecurityGroup", 42 | "Properties": { 43 | "GroupDescription": "My security group", 44 | "VpcId": {"Ref": "VPC"}, 45 | "SecurityGroupIngress": [{ 46 | "CidrIp": "0.0.0.0/0", 47 | "FromPort": 22, 48 | "IpProtocol": "tcp", 49 | "ToPort": 22 50 | }] 51 | } 52 | }, 53 | "InstanceProfile": { 54 | "Type": "AWS::IAM::InstanceProfile", 55 | "Properties": { 56 | "Path": "/", 57 | "Roles": [{"Ref": "Role"}] 58 | } 59 | }, 60 | "Role": { 61 | "Type": "AWS::IAM::Role", 62 | "Properties": { 63 | "AssumeRolePolicyDocument": { 64 | "Version": "2012-10-17", 65 | "Statement": [{ 66 | "Effect": "Allow", 67 | "Principal": { 68 | "Service": ["ec2.amazonaws.com"] 69 | }, 70 | "Action": ["sts:AssumeRole"] 71 | }] 72 | }, 73 | "Path": "/", 74 | "Policies": [{ 75 | "PolicyName": "ec2", 76 | "PolicyDocument": { 77 | "Version": "2012-10-17", 78 | "Statement": [{ 79 | "Sid": "Stmt1425388787000", 80 | "Effect": "Allow", 81 | "Action": ["ec2:StopInstances"], 82 | "Resource": ["*"], 83 | "Condition": { 84 | "StringEquals": {"ec2:ResourceTag/aws:cloudformation:stack-id": {"Ref": "AWS::StackId"}} 85 | } 86 | }] 87 | } 88 | }] 89 | } 90 | }, 91 | "Server": { 92 | "Type": "AWS::EC2::Instance", 93 | "Properties": { 94 | "IamInstanceProfile": {"Ref": "InstanceProfile"}, 95 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 96 | "InstanceType": "t2.micro", 97 | "KeyName": {"Ref": "KeyName"}, 98 | "SecurityGroupIds": [{"Ref": "SecurityGroup"}], 99 | "SubnetId": {"Ref": "Subnet"}, 100 | "UserData": {"Fn::Base64": {"Fn::Join": ["", [ 101 | "#!/bin/bash -ex\n", 102 | "INSTANCEID=`curl -s http://169.254.169.254/latest/meta-data/instance-id`\n", 103 | "echo \"aws --region ", {"Ref": "AWS::Region"}, " ec2 stop-instances --instance-ids $INSTANCEID\" | at now + ", {"Ref": "Lifetime"} ," minutes\n" 104 | ]]}} 105 | } 106 | } 107 | }, 108 | "Outputs": { 109 | "PublicName": { 110 | "Value": {"Fn::GetAtt": ["Server", "PublicDnsName"]}, 111 | "Description": "Public name (connect via SSH as user ec2-user)" 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /chapter6/update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | PUBLICNAMES=$(aws ec2 describe-instances --filters "Name=instance-state-name,Values=running" --query "Reservations[].Instances[].PublicDnsName" --output text) 4 | 5 | for PUBLICNAME in $PUBLICNAMES; do 6 | ssh -t -o StrictHostKeyChecking=no ec2-user@$PUBLICNAME "sudo yum -y --security update" 7 | done 8 | -------------------------------------------------------------------------------- /chapter7/bucketpolicy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version":"2012-10-17", 3 | "Statement":[ 4 | { 5 | "Sid":"AddPerm", 6 | "Effect":"Allow", 7 | "Principal": "*", 8 | "Action":["s3:GetObject"], 9 | "Resource":["arn:aws:s3:::$BucketName/*"] 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /chapter7/gallery/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Simple S3 Gallery 4 | 5 | 6 |

Simple S3 Gallery

7 |

Upload

8 |
9 |

10 |

11 |
12 |

Images

13 | {{#Objects}} 14 |

15 | {{/Objects}} 16 | 17 | -------------------------------------------------------------------------------- /chapter7/gallery/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "aws-sdk": "2.1.23", 4 | "mu2-updated": "0.5.21", 5 | "uuid": "2.0.1", 6 | "multiparty": "4.1.1", 7 | "express": "4.12.3" 8 | }, 9 | "private": true 10 | } 11 | -------------------------------------------------------------------------------- /chapter7/gallery/server.js: -------------------------------------------------------------------------------- 1 | var express = require("express"); 2 | var AWS = require("aws-sdk"); 3 | var mu = require("mu2-updated"); 4 | var uuid = require("uuid"); 5 | var multiparty = require("multiparty"); 6 | 7 | var app = express(); 8 | var s3 = new AWS.S3({ 9 | "region": "us-east-1" 10 | }); 11 | 12 | var bucket = process.argv[2]; 13 | if (!bucket || bucket.length < 1) { 14 | console.error("Missing S3 bucket. Start with node server.js BUCKETNAME instead."); 15 | process.exit(1); 16 | } 17 | 18 | function listImages(response) { 19 | var params = { 20 | Bucket: bucket 21 | }; 22 | s3.listObjects(params, function(err, data) { 23 | if (err) { 24 | console.error(err); 25 | response.status(500); 26 | response.send("Internal server error."); 27 | } else { 28 | var stream = mu.compileAndRender( 29 | "index.html", 30 | { 31 | Objects: data.Contents, 32 | Bucket: bucket 33 | } 34 | ); 35 | stream.pipe(response); 36 | } 37 | }); 38 | } 39 | 40 | function uploadImage(image, response) { 41 | var params = { 42 | Body: image, 43 | Bucket: bucket, 44 | Key: uuid.v4(), 45 | ACL: "public-read", 46 | ContentLength: image.byteCount, 47 | ContentType: image.headers["content-type"] 48 | }; 49 | s3.putObject(params, function(err, data) { 50 | if (err) { 51 | console.error(err); 52 | response.status(500); 53 | response.send("Internal server error."); 54 | } else { 55 | response.redirect("/"); 56 | } 57 | }); 58 | } 59 | 60 | app.get('/', function (request, response) { 61 | listImages(response); 62 | }); 63 | 64 | app.post('/upload', function (request, response) { 65 | var form = new multiparty.Form(); 66 | form.on("part", function(part) { 67 | uploadImage(part, response); 68 | }); 69 | form.parse(request); 70 | }); 71 | 72 | app.listen(8080); 73 | 74 | console.log("Server started. Open http://localhost:8080 with browser."); 75 | -------------------------------------------------------------------------------- /chapter7/helloworld.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello World! 4 | 5 | 6 | Hello World! 7 | 8 | -------------------------------------------------------------------------------- /chapter8/ebs.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 8 (EBS)", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "VPC": { 11 | "Description": "Just select the one and only default VPC", 12 | "Type": "AWS::EC2::VPC::Id" 13 | }, 14 | "Subnet": { 15 | "Description": "Just select one of the available subnets", 16 | "Type": "AWS::EC2::Subnet::Id" 17 | }, 18 | "AttachVolume": { 19 | "Description": "Should the volume be attached?", 20 | "Type": "String", 21 | "Default": "yes", 22 | "AllowedValues": ["yes", "no"] 23 | } 24 | }, 25 | "Mappings": { 26 | "EC2RegionMap": { 27 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 28 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 29 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 30 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 31 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 32 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 33 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 34 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 35 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 36 | } 37 | }, 38 | "Conditions": { 39 | "Attached": {"Fn::Equals": [{"Ref": "AttachVolume"}, "yes"]} 40 | }, 41 | "Resources": { 42 | "SecurityGroup": { 43 | "Type": "AWS::EC2::SecurityGroup", 44 | "Properties": { 45 | "GroupDescription": "My security group", 46 | "VpcId": {"Ref": "VPC"}, 47 | "SecurityGroupIngress": [{ 48 | "CidrIp": "0.0.0.0/0", 49 | "FromPort": 22, 50 | "IpProtocol": "tcp", 51 | "ToPort": 22 52 | }] 53 | } 54 | }, 55 | "IamRole": { 56 | "Type": "AWS::IAM::Role", 57 | "Properties": { 58 | "AssumeRolePolicyDocument": { 59 | "Version": "2012-10-17", 60 | "Statement": [ 61 | { 62 | "Effect": "Allow", 63 | "Principal": { 64 | "Service": ["ec2.amazonaws.com"] 65 | }, 66 | "Action": ["sts:AssumeRole"] 67 | } 68 | ] 69 | }, 70 | "Path": "/", 71 | "Policies": [ 72 | { 73 | "PolicyName": "ec2", 74 | "PolicyDocument": { 75 | "Version": "2012-10-17", 76 | "Statement": [{ 77 | "Effect" : "Allow", 78 | "Action" : ["ec2:DescribeVolumes", "ec2:CreateSnapshot", "ec2:DescribeSnapshots", "ec2:DeleteSnapshot"], 79 | "Resource": "*" 80 | }] 81 | } 82 | } 83 | ] 84 | } 85 | }, 86 | "IamInstanceProfile": { 87 | "Type": "AWS::IAM::InstanceProfile", 88 | "Properties": { 89 | "Path": "/", 90 | "Roles": [{"Ref": "IamRole"}] 91 | } 92 | }, 93 | "Server": { 94 | "Type": "AWS::EC2::Instance", 95 | "Properties": { 96 | "IamInstanceProfile": {"Ref": "IamInstanceProfile"}, 97 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 98 | "InstanceType": "t2.micro", 99 | "KeyName": {"Ref": "KeyName"}, 100 | "SecurityGroupIds": [{"Ref": "SecurityGroup"}], 101 | "SubnetId": {"Ref": "Subnet"} 102 | } 103 | }, 104 | "Volume": { 105 | "Type": "AWS::EC2::Volume", 106 | "Properties": { 107 | "AvailabilityZone": {"Fn::GetAtt": ["Server", "AvailabilityZone"]}, 108 | "Size": "5", 109 | "VolumeType": "gp2" 110 | } 111 | }, 112 | "VolumeAttachment": { 113 | "Type": "AWS::EC2::VolumeAttachment", 114 | "Condition": "Attached", 115 | "Properties": { 116 | "Device": "/dev/xvdf", 117 | "InstanceId": {"Ref": "Server"}, 118 | "VolumeId": {"Ref": "Volume"} 119 | } 120 | } 121 | }, 122 | "Outputs": { 123 | "PublicName": { 124 | "Value": {"Fn::GetAtt": ["Server", "PublicDnsName"]}, 125 | "Description": "Public name (connect via SSH as user ec2-user)" 126 | }, 127 | "VolumeId": { 128 | "Value": {"Ref": "Volume"}, 129 | "Description": "Volume id" 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /chapter8/instance_store.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 8 (Instance Store)", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "VPC": { 11 | "Description": "Just select the one and only default VPC", 12 | "Type": "AWS::EC2::VPC::Id" 13 | }, 14 | "Subnet": { 15 | "Description": "Just select one of the available subnets", 16 | "Type": "AWS::EC2::Subnet::Id" 17 | } 18 | }, 19 | "Mappings": { 20 | "EC2RegionMap": { 21 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 22 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 23 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 24 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 25 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 26 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 27 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 28 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 29 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 30 | } 31 | }, 32 | "Resources": { 33 | "SecurityGroup": { 34 | "Type": "AWS::EC2::SecurityGroup", 35 | "Properties": { 36 | "GroupDescription": "My security group", 37 | "VpcId": {"Ref": "VPC"}, 38 | "SecurityGroupIngress": [{ 39 | "CidrIp": "0.0.0.0/0", 40 | "FromPort": 22, 41 | "IpProtocol": "tcp", 42 | "ToPort": 22 43 | }] 44 | } 45 | }, 46 | "Server": { 47 | "Type": "AWS::EC2::Instance", 48 | "Properties": { 49 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 50 | "InstanceType": "m3.medium", 51 | "KeyName": {"Ref": "KeyName"}, 52 | "SecurityGroupIds": [{"Ref": "SecurityGroup"}], 53 | "SubnetId": {"Ref": "Subnet"}, 54 | "BlockDeviceMappings": [{ 55 | "DeviceName": "/dev/xvda", 56 | "Ebs": { 57 | "VolumeSize": "8", 58 | "VolumeType": "gp2" 59 | } 60 | }, { 61 | "DeviceName": "/dev/xvdb", 62 | "VirtualName": "ephemeral0" 63 | }] 64 | } 65 | } 66 | }, 67 | "Outputs": { 68 | "PublicName": { 69 | "Value": {"Fn::GetAtt": ["Server", "PublicDnsName"]}, 70 | "Description": "Public name (connect via SSH as user ec2-user)" 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /chapter8/nfs-server-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | # install and configure NFS 4 | yum -y install nfs-utils nfs-utils-lib 5 | service rpcbind start 6 | service nfs start 7 | chmod 777 /media/ephemeral0 8 | echo "/media/ephemeral0 *(rw,async)" >> /etc/exports 9 | exportfs -a 10 | 11 | # wait until EBS volume is attached 12 | while ! [ "$(fdisk -l | grep '/dev/xvdf' | wc -l)" -ge "1" ]; do sleep 10; done 13 | 14 | # format EBS volume if needed 15 | if [[ "$(file -s /dev/xvdf)" != *"ext4"* ]] 16 | then 17 | mkfs -t ext4 /dev/xvdf 18 | fi 19 | 20 | # mount EBS volume 21 | mkdir /mnt/backup 22 | echo "/dev/xvdf /mnt/backup ext4 defaults,nofail 0 2" >> /etc/fstab 23 | mount -a 24 | 25 | INSTANCEID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id) 26 | VOLUMEID=$(aws --region $REGION ec2 describe-volumes --filters "Name=attachment.instance-id,Values=$INSTANCEID" --query "Volumes[0].VolumeId" --output text) 27 | 28 | # backup cron 29 | cat > /etc/cron.d/backup << EOF 30 | SHELL=/bin/bash 31 | PATH=/sbin:/bin:/usr/sbin:/usr/bin:/opt/aws/bin 32 | MAILTO=root 33 | HOME=/ 34 | 0,15,30,45 * * * * rsync -av --delete --exclude /media/ephemeral0/ /mnt/backup/ ; fsfreeze -f /mnt/backup/ ; aws --region $REGION ec2 create-snapshot --volume-id $VOLUMEID --description "NFS backup"; fsfreeze -u /mnt/backup/ 35 | EOF 36 | -------------------------------------------------------------------------------- /chapter8/nfs.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 8 (NFS)", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "VPC": { 11 | "Description": "Just select the one and only default VPC", 12 | "Type": "AWS::EC2::VPC::Id" 13 | }, 14 | "Subnet": { 15 | "Description": "Just select one of the available subnets", 16 | "Type": "AWS::EC2::Subnet::Id" 17 | } 18 | }, 19 | "Mappings": { 20 | "EC2RegionMap": { 21 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 22 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 23 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 24 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 25 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 26 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 27 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 28 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 29 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 30 | } 31 | }, 32 | "Resources": { 33 | "SecurityGroupClient": { 34 | "Type": "AWS::EC2::SecurityGroup", 35 | "Properties": { 36 | "GroupDescription": "My client security group", 37 | "VpcId": {"Ref": "VPC"} 38 | } 39 | }, 40 | "SecurityGroupServer": { 41 | "Type": "AWS::EC2::SecurityGroup", 42 | "Properties": { 43 | "GroupDescription": "My server security group", 44 | "VpcId": {"Ref": "VPC"}, 45 | "SecurityGroupIngress": [{ 46 | "SourceSecurityGroupId": {"Ref": "SecurityGroupClient"}, 47 | "FromPort": 111, 48 | "IpProtocol": "tcp", 49 | "ToPort": 111 50 | }, { 51 | "SourceSecurityGroupId": {"Ref": "SecurityGroupClient"}, 52 | "FromPort": 111, 53 | "IpProtocol": "udp", 54 | "ToPort": 111 55 | }, { 56 | "SourceSecurityGroupId": {"Ref": "SecurityGroupClient"}, 57 | "FromPort": 2049, 58 | "IpProtocol": "tcp", 59 | "ToPort": 2049 60 | }, { 61 | "SourceSecurityGroupId": {"Ref": "SecurityGroupClient"}, 62 | "FromPort": 2049, 63 | "IpProtocol": "udp", 64 | "ToPort": 2049 65 | }] 66 | } 67 | }, 68 | "SecurityGroupCommon": { 69 | "Type": "AWS::EC2::SecurityGroup", 70 | "Properties": { 71 | "GroupDescription": "My security group", 72 | "VpcId": {"Ref": "VPC"}, 73 | "SecurityGroupIngress": [{ 74 | "CidrIp": "0.0.0.0/0", 75 | "FromPort": 22, 76 | "IpProtocol": "tcp", 77 | "ToPort": 22 78 | }] 79 | } 80 | }, 81 | "InstanceProfile": { 82 | "Type": "AWS::IAM::InstanceProfile", 83 | "Properties": { 84 | "Path": "/", 85 | "Roles": [{"Ref": "Role"}] 86 | } 87 | }, 88 | "Role": { 89 | "Type": "AWS::IAM::Role", 90 | "Properties": { 91 | "AssumeRolePolicyDocument": { 92 | "Version": "2012-10-17", 93 | "Statement": [{ 94 | "Effect": "Allow", 95 | "Principal": { 96 | "Service": ["ec2.amazonaws.com"] 97 | }, 98 | "Action": ["sts:AssumeRole"] 99 | }] 100 | }, 101 | "Path": "/", 102 | "Policies": [{ 103 | "PolicyName": "ec2", 104 | "PolicyDocument": { 105 | "Version": "2012-10-17", 106 | "Statement": [{ 107 | "Sid": "Stmt1425388787000", 108 | "Effect": "Allow", 109 | "Action": ["ec2:DescribeVolumes", "ec2:CreateSnapshot"], 110 | "Resource": ["*"] 111 | }] 112 | } 113 | }] 114 | } 115 | }, 116 | "Server": { 117 | "Type": "AWS::EC2::Instance", 118 | "Properties": { 119 | "IamInstanceProfile": {"Ref": "InstanceProfile"}, 120 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 121 | "InstanceType": "m3.medium", 122 | "KeyName": {"Ref": "KeyName"}, 123 | "SecurityGroupIds": [{"Ref": "SecurityGroupCommon"}, {"Ref": "SecurityGroupServer"}], 124 | "SubnetId": {"Ref": "Subnet"}, 125 | "BlockDeviceMappings": [{ 126 | "DeviceName": "/dev/xvda", 127 | "Ebs": { 128 | "VolumeSize": "8", 129 | "VolumeType": "gp2" 130 | } 131 | }, { 132 | "DeviceName": "/dev/xvdb", 133 | "VirtualName": "ephemeral0" 134 | }], 135 | "UserData": {"Fn::Base64": {"Fn::Join": ["", [ 136 | "#!/bin/bash -ex\n", 137 | "export REGION=\"", {"Ref": "AWS::Region"}, "\"\n", 138 | "curl -s https://s3.amazonaws.com/awsinaction/chapter8/nfs-server-install.sh | bash -ex\n", 139 | "/opt/aws/bin/cfn-signal --success true '", {"Ref": "WaitConditionHandle"}, "'\n" 140 | ]]}} 141 | } 142 | }, 143 | "Volume": { 144 | "Type": "AWS::EC2::Volume", 145 | "Properties": { 146 | "AvailabilityZone": {"Fn::GetAtt": ["Server", "AvailabilityZone"]}, 147 | "Size": "5", 148 | "VolumeType": "gp2" 149 | } 150 | }, 151 | "VolumeAttachment": { 152 | "Type": "AWS::EC2::VolumeAttachment", 153 | "Properties": { 154 | "Device": "/dev/xvdf", 155 | "InstanceId": {"Ref": "Server"}, 156 | "VolumeId": {"Ref": "Volume"} 157 | } 158 | }, 159 | "WaitConditionHandle": { 160 | "Type": "AWS::CloudFormation::WaitConditionHandle", 161 | "Properties": { 162 | } 163 | }, 164 | "WaitCondition": { 165 | "Type": "AWS::CloudFormation::WaitCondition", 166 | "Properties": { 167 | "Count": "1", 168 | "Handle": {"Ref": "WaitConditionHandle"}, 169 | "Timeout": "600" 170 | } 171 | }, 172 | "Client1": { 173 | "Type": "AWS::EC2::Instance", 174 | "Properties": { 175 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 176 | "InstanceType": "t2.micro", 177 | "KeyName": {"Ref": "KeyName"}, 178 | "SecurityGroupIds": [{"Ref": "SecurityGroupCommon"}, {"Ref": "SecurityGroupClient"}], 179 | "SubnetId": {"Ref": "Subnet"}, 180 | "UserData": {"Fn::Base64": {"Fn::Join": ["", [ 181 | "#!/bin/bash -ex\n", 182 | "yum -y install nfs-utils nfs-utils-lib\n", 183 | "mkdir /mnt/nfs\n", 184 | "echo \"", {"Fn::GetAtt": ["Server", "PublicDnsName"]}, ":/media/ephemeral0 /mnt/nfs nfs rw 0 0\" >> /etc/fstab\n", 185 | "mount -a\n", 186 | "/opt/aws/bin/cfn-signal -e $? --stack ", {"Ref": "AWS::StackName"}, " --resource Client1 --region ", {"Ref": "AWS::Region"}, "\n" 187 | ]]}} 188 | }, 189 | "CreationPolicy": { 190 | "ResourceSignal": { 191 | "Timeout": "PT10M" 192 | } 193 | }, 194 | "DependsOn": "WaitCondition" 195 | }, 196 | "Client2": { 197 | "Type": "AWS::EC2::Instance", 198 | "Properties": { 199 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 200 | "InstanceType": "t2.micro", 201 | "KeyName": {"Ref": "KeyName"}, 202 | "SecurityGroupIds": [{"Ref": "SecurityGroupCommon"}, {"Ref": "SecurityGroupClient"}], 203 | "SubnetId": {"Ref": "Subnet"}, 204 | "UserData": {"Fn::Base64": {"Fn::Join": ["", [ 205 | "#!/bin/bash -ex\n", 206 | "yum -y install nfs-utils nfs-utils-lib\n", 207 | "mkdir /mnt/nfs\n", 208 | "echo \"", {"Fn::GetAtt": ["Server", "PublicDnsName"]}, ":/media/ephemeral0 /mnt/nfs nfs rw 0 0\" >> /etc/fstab\n", 209 | "mount -a\n", 210 | "/opt/aws/bin/cfn-signal -e $? --stack ", {"Ref": "AWS::StackName"}, " --resource Client2 --region ", {"Ref": "AWS::Region"}, "\n" 211 | ]]}} 212 | }, 213 | "CreationPolicy": { 214 | "ResourceSignal": { 215 | "Timeout": "PT10M" 216 | } 217 | }, 218 | "DependsOn": "WaitCondition" 219 | } 220 | }, 221 | "Outputs": { 222 | "ServerPublicName": { 223 | "Value": {"Fn::GetAtt": ["Server", "PublicDnsName"]}, 224 | "Description": "Public name (connect via SSH as user ec2-user)" 225 | }, 226 | "VolumeId": { 227 | "Value": {"Ref": "Volume"}, 228 | "Description": "Volume id" 229 | }, 230 | "Client1PublicName": { 231 | "Value": {"Fn::GetAtt": ["Client1", "PublicDnsName"]}, 232 | "Description": "Public name (connect via SSH as user ec2-user)" 233 | }, 234 | "Client2PublicName": { 235 | "Value": {"Fn::GetAtt": ["Client2", "PublicDnsName"]}, 236 | "Description": "Public name (connect via SSH as user ec2-user)" 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /chapter9/template-multiaz.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 9", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "BlogTitle": { 11 | "Description": "The title of the blog.", 12 | "Type": "String", 13 | "Default": "Amazon Web Services in Action - Example" 14 | }, 15 | "AdminUsername": { 16 | "Description": "A username for admin.", 17 | "Type": "String", 18 | "Default": "admin" 19 | }, 20 | "AdminPassword": { 21 | "Description": "A password for admin", 22 | "Type": "String", 23 | "NoEcho": "true" 24 | }, 25 | "AdminEMail": { 26 | "Description": "The email address of the administrator.", 27 | "Type": "String" 28 | } 29 | }, 30 | "Mappings": { 31 | "EC2RegionMap": { 32 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 33 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 34 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 35 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 36 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 37 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 38 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 39 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 40 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 41 | } 42 | }, 43 | "Resources": { 44 | "VPC": { 45 | "Type": "AWS::EC2::VPC", 46 | "Properties": { 47 | "CidrBlock": "172.31.0.0/16", 48 | "EnableDnsHostnames": "true" 49 | } 50 | }, 51 | "InternetGateway": { 52 | "Type": "AWS::EC2::InternetGateway", 53 | "Properties": { 54 | } 55 | }, 56 | "VPCGatewayAttachment": { 57 | "Type": "AWS::EC2::VPCGatewayAttachment", 58 | "Properties": { 59 | "VpcId": {"Ref": "VPC"}, 60 | "InternetGatewayId": {"Ref": "InternetGateway"} 61 | } 62 | }, 63 | "SubnetA": { 64 | "Type": "AWS::EC2::Subnet", 65 | "Properties": { 66 | "AvailabilityZone": {"Fn::Select": ["0", {"Fn::GetAZs": ""}]}, 67 | "CidrBlock": "172.31.38.0/24", 68 | "VpcId": {"Ref": "VPC"} 69 | } 70 | }, 71 | "SubnetB": { 72 | "Type": "AWS::EC2::Subnet", 73 | "Properties": { 74 | "AvailabilityZone": {"Fn::Select": ["1", {"Fn::GetAZs": ""}]}, 75 | "CidrBlock": "172.31.37.0/24", 76 | "VpcId": {"Ref": "VPC"} 77 | } 78 | }, 79 | "RouteTable": { 80 | "Type": "AWS::EC2::RouteTable", 81 | "Properties": { 82 | "VpcId": {"Ref": "VPC"} 83 | } 84 | }, 85 | "RouteTableAssociationA": { 86 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 87 | "Properties": { 88 | "SubnetId": {"Ref": "SubnetA"}, 89 | "RouteTableId": {"Ref": "RouteTable"} 90 | } 91 | }, 92 | "RouteTableAssociationB": { 93 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 94 | "Properties": { 95 | "SubnetId": {"Ref": "SubnetB"}, 96 | "RouteTableId": {"Ref": "RouteTable"} 97 | } 98 | }, 99 | "RoutePublicNATToInternet": { 100 | "Type": "AWS::EC2::Route", 101 | "Properties": { 102 | "RouteTableId": {"Ref": "RouteTable"}, 103 | "DestinationCidrBlock": "0.0.0.0/0", 104 | "GatewayId": {"Ref": "InternetGateway"} 105 | }, 106 | "DependsOn": "VPCGatewayAttachment" 107 | }, 108 | "NetworkAcl": { 109 | "Type": "AWS::EC2::NetworkAcl", 110 | "Properties": { 111 | "VpcId": {"Ref": "VPC"} 112 | } 113 | }, 114 | "SubnetNetworkAclAssociationA": { 115 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 116 | "Properties": { 117 | "SubnetId": {"Ref": "SubnetA"}, 118 | "NetworkAclId": {"Ref": "NetworkAcl"} 119 | } 120 | }, 121 | "SubnetNetworkAclAssociationB": { 122 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 123 | "Properties": { 124 | "SubnetId": {"Ref": "SubnetB"}, 125 | "NetworkAclId": {"Ref": "NetworkAcl"} 126 | } 127 | }, 128 | "NetworkAclEntryIngress": { 129 | "Type": "AWS::EC2::NetworkAclEntry", 130 | "Properties": { 131 | "NetworkAclId": {"Ref": "NetworkAcl"}, 132 | "RuleNumber": "100", 133 | "Protocol": "-1", 134 | "RuleAction": "allow", 135 | "Egress": "false", 136 | "CidrBlock": "0.0.0.0/0" 137 | } 138 | }, 139 | "NetworkAclEntryEgress": { 140 | "Type": "AWS::EC2::NetworkAclEntry", 141 | "Properties": { 142 | "NetworkAclId": {"Ref": "NetworkAcl"}, 143 | "RuleNumber": "100", 144 | "Protocol": "-1", 145 | "RuleAction": "allow", 146 | "Egress": "true", 147 | "CidrBlock": "0.0.0.0/0" 148 | } 149 | }, 150 | "LoadBalancer": { 151 | "Type": "AWS::ElasticLoadBalancing::LoadBalancer", 152 | "Properties": { 153 | "Subnets": [{"Ref": "SubnetA"}, {"Ref": "SubnetB"}], 154 | "LoadBalancerName": "awsinaction-elb", 155 | "Listeners": [{ 156 | "InstancePort": "80", 157 | "InstanceProtocol": "HTTP", 158 | "LoadBalancerPort": "80", 159 | "Protocol": "HTTP" 160 | }], 161 | "HealthCheck": { 162 | "HealthyThreshold": "2", 163 | "Interval": "5", 164 | "Target": "TCP:80", 165 | "Timeout": "3", 166 | "UnhealthyThreshold": "2" 167 | }, 168 | "SecurityGroups": [{"Ref": "LoadBalancerSecurityGroup"}], 169 | "Scheme": "internet-facing" 170 | }, 171 | "DependsOn": "VPCGatewayAttachment" 172 | }, 173 | "LoadBalancerSecurityGroup": { 174 | "Type": "AWS::EC2::SecurityGroup", 175 | "Properties": { 176 | "GroupDescription": "awsinaction-elb-sg", 177 | "VpcId": {"Ref": "VPC"}, 178 | "SecurityGroupIngress": [{ 179 | "CidrIp": "0.0.0.0/0", 180 | "FromPort": 80, 181 | "IpProtocol": "tcp", 182 | "ToPort": 80 183 | }] 184 | } 185 | }, 186 | "WebServerSecurityGroup": { 187 | "Type": "AWS::EC2::SecurityGroup", 188 | "Properties": { 189 | "GroupDescription": "awsinaction-sg", 190 | "VpcId": {"Ref": "VPC"}, 191 | "SecurityGroupIngress": [{ 192 | "CidrIp": "0.0.0.0/0", 193 | "FromPort": 22, 194 | "IpProtocol": "tcp", 195 | "ToPort": 22 196 | }, { 197 | "FromPort": 80, 198 | "IpProtocol": "tcp", 199 | "SourceSecurityGroupId": {"Ref": "LoadBalancerSecurityGroup"}, 200 | "ToPort": 80 201 | }] 202 | } 203 | }, 204 | "DatabaseSecurityGroup": { 205 | "Type": "AWS::EC2::SecurityGroup", 206 | "Properties": { 207 | "GroupDescription": "awsinaction-db-sg", 208 | "VpcId": {"Ref": "VPC"}, 209 | "SecurityGroupIngress": [{ 210 | "IpProtocol": "tcp", 211 | "FromPort": "3306", 212 | "ToPort": "3306", 213 | "SourceSecurityGroupId": {"Ref": "WebServerSecurityGroup"} 214 | }] 215 | } 216 | }, 217 | "Database": { 218 | "Type": "AWS::RDS::DBInstance", 219 | "DeletionPolicy": "Delete", 220 | "Properties": { 221 | "AllocatedStorage": "5", 222 | "DBInstanceClass": "db.t2.micro", 223 | "DBInstanceIdentifier": "awsinaction-db", 224 | "DBName": "wordpress", 225 | "Engine": "MySQL", 226 | "MasterUsername": "wordpress", 227 | "MasterUserPassword": "wordpress", 228 | "VPCSecurityGroups": [{"Fn::GetAtt": ["DatabaseSecurityGroup", "GroupId"]}], 229 | "DBSubnetGroupName": {"Ref": "DBSubnetGroup"}, 230 | "MultiAZ": true 231 | }, 232 | "DependsOn": "VPCGatewayAttachment" 233 | }, 234 | "DBSubnetGroup" : { 235 | "Type" : "AWS::RDS::DBSubnetGroup", 236 | "Properties" : { 237 | "DBSubnetGroupDescription" : "DB subnet group", 238 | "SubnetIds": [{"Ref": "SubnetA"}, {"Ref": "SubnetB"}] 239 | } 240 | }, 241 | "LaunchConfiguration": { 242 | "Type": "AWS::AutoScaling::LaunchConfiguration", 243 | "Metadata": { 244 | "AWS::CloudFormation::Init": { 245 | "config": { 246 | "packages": { 247 | "yum": { 248 | "php": [], 249 | "php-mysql": [], 250 | "mysql": [], 251 | "httpd": [] 252 | } 253 | }, 254 | "sources": { 255 | "/var/www/html": "https://wordpress.org/wordpress-4.2.4.tar.gz" 256 | }, 257 | "files": { 258 | "/tmp/config": { 259 | "content": {"Fn::Join": ["", [ 260 | "#!/bin/bash -ex\n", 261 | "cp /var/www/html/wordpress/wp-config-sample.php /var/www/html/wordpress/wp-config.php\n", 262 | "sed -i \"s/'database_name_here'/'wordpress'/g\" wp-config.php\n", 263 | "sed -i \"s/'username_here'/'wordpress'/g\" wp-config.php\n", 264 | "sed -i \"s/'password_here'/'wordpress'/g\" wp-config.php\n", 265 | "sed -i \"s/'localhost'/'", {"Fn::GetAtt": ["Database", "Endpoint.Address"]}, "'/g\" wp-config.php\n", 266 | "chmod -R 777 wp-content/ \n", 267 | "curl -O https://raw.githubusercontent.com/AWSinAction/builds/gh-pages/phar/wp-cli.phar \n", 268 | "php wp-cli.phar core install --url=\"", {"Fn::GetAtt": ["LoadBalancer", "DNSName"]}, "/wordpress\" --title=\"", {"Ref": "BlogTitle"}, "\" --admin_user=\"", {"Ref": "AdminUsername"}, "\" --admin_password=\"", {"Ref": "AdminPassword"}, "\" --admin_email=\"", {"Ref": "AdminEMail"}, "\" \n" ]]}, 269 | "mode": "000500", 270 | "owner": "root", 271 | "group": "root" 272 | } 273 | }, 274 | "commands": { 275 | "01_config": { 276 | "command": "/tmp/config", 277 | "cwd": "/var/www/html/wordpress" 278 | } 279 | }, 280 | "services": { 281 | "sysvinit": { 282 | "httpd": { 283 | "enabled": "true", 284 | "ensureRunning": "true" 285 | } 286 | } 287 | } 288 | } 289 | } 290 | }, 291 | "Properties": { 292 | "EbsOptimized": false, 293 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 294 | "InstanceType": "t2.micro", 295 | "SecurityGroups": [{"Ref": "WebServerSecurityGroup"}], 296 | "KeyName": {"Ref": "KeyName"}, 297 | "AssociatePublicIpAddress": true, 298 | "UserData": {"Fn::Base64": {"Fn::Join": ["", [ 299 | "#!/bin/bash -ex\n", 300 | "yum update -y aws-cfn-bootstrap\n", 301 | "/opt/aws/bin/cfn-init -v --stack ", {"Ref": "AWS::StackName"}, " --resource LaunchConfiguration --region ", {"Ref": "AWS::Region"}, "\n", 302 | "/opt/aws/bin/cfn-signal -e $? --stack ", {"Ref": "AWS::StackName"}, " --resource AutoScalingGroup --region ", {"Ref": "AWS::Region"}, "\n" 303 | ]]}} 304 | } 305 | }, 306 | "AutoScalingGroup": { 307 | "Type": "AWS::AutoScaling::AutoScalingGroup", 308 | "Properties": { 309 | "LoadBalancerNames": [{"Ref": "LoadBalancer"}], 310 | "LaunchConfigurationName": {"Ref": "LaunchConfiguration"}, 311 | "MinSize": "2", 312 | "MaxSize": "2", 313 | "DesiredCapacity": "2", 314 | "VPCZoneIdentifier": [{"Ref": "SubnetA"}, {"Ref": "SubnetB"}] 315 | }, 316 | "CreationPolicy": { 317 | "ResourceSignal": { 318 | "Timeout": "PT10M" 319 | } 320 | }, 321 | "DependsOn": "VPCGatewayAttachment" 322 | } 323 | }, 324 | "Outputs": { 325 | "URL": { 326 | "Value": {"Fn::Join": ["", ["http://", {"Fn::GetAtt": ["LoadBalancer", "DNSName"]}, "/wordpress"]]}, 327 | "Description": "Wordpress URL" 328 | } 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /chapter9/template-snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 9", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "BlogTitle": { 11 | "Description": "The title of the blog.", 12 | "Type": "String", 13 | "Default": "Amazon Web Services in Action - Example" 14 | }, 15 | "AdminUsername": { 16 | "Description": "A username for admin.", 17 | "Type": "String", 18 | "Default": "admin" 19 | }, 20 | "AdminPassword": { 21 | "Description": "A password for admin", 22 | "Type": "String", 23 | "NoEcho": "true" 24 | }, 25 | "AdminEMail": { 26 | "Description": "The email address of the administrator.", 27 | "Type": "String" 28 | } 29 | }, 30 | "Mappings": { 31 | "EC2RegionMap": { 32 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 33 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 34 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 35 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 36 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 37 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 38 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 39 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 40 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 41 | } 42 | }, 43 | "Resources": { 44 | "VPC": { 45 | "Type": "AWS::EC2::VPC", 46 | "Properties": { 47 | "CidrBlock": "172.31.0.0/16", 48 | "EnableDnsHostnames": "true" 49 | } 50 | }, 51 | "InternetGateway": { 52 | "Type": "AWS::EC2::InternetGateway", 53 | "Properties": { 54 | } 55 | }, 56 | "VPCGatewayAttachment": { 57 | "Type": "AWS::EC2::VPCGatewayAttachment", 58 | "Properties": { 59 | "VpcId": {"Ref": "VPC"}, 60 | "InternetGatewayId": {"Ref": "InternetGateway"} 61 | } 62 | }, 63 | "SubnetA": { 64 | "Type": "AWS::EC2::Subnet", 65 | "Properties": { 66 | "AvailabilityZone": {"Fn::Select": ["0", {"Fn::GetAZs": ""}]}, 67 | "CidrBlock": "172.31.38.0/24", 68 | "VpcId": {"Ref": "VPC"} 69 | } 70 | }, 71 | "SubnetB": { 72 | "Type": "AWS::EC2::Subnet", 73 | "Properties": { 74 | "AvailabilityZone": {"Fn::Select": ["1", {"Fn::GetAZs": ""}]}, 75 | "CidrBlock": "172.31.37.0/24", 76 | "VpcId": {"Ref": "VPC"} 77 | } 78 | }, 79 | "RouteTable": { 80 | "Type": "AWS::EC2::RouteTable", 81 | "Properties": { 82 | "VpcId": {"Ref": "VPC"} 83 | } 84 | }, 85 | "RouteTableAssociationA": { 86 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 87 | "Properties": { 88 | "SubnetId": {"Ref": "SubnetA"}, 89 | "RouteTableId": {"Ref": "RouteTable"} 90 | } 91 | }, 92 | "RouteTableAssociationB": { 93 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 94 | "Properties": { 95 | "SubnetId": {"Ref": "SubnetB"}, 96 | "RouteTableId": {"Ref": "RouteTable"} 97 | } 98 | }, 99 | "RoutePublicNATToInternet": { 100 | "Type": "AWS::EC2::Route", 101 | "Properties": { 102 | "RouteTableId": {"Ref": "RouteTable"}, 103 | "DestinationCidrBlock": "0.0.0.0/0", 104 | "GatewayId": {"Ref": "InternetGateway"} 105 | }, 106 | "DependsOn": "VPCGatewayAttachment" 107 | }, 108 | "NetworkAcl": { 109 | "Type": "AWS::EC2::NetworkAcl", 110 | "Properties": { 111 | "VpcId": {"Ref": "VPC"} 112 | } 113 | }, 114 | "SubnetNetworkAclAssociationA": { 115 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 116 | "Properties": { 117 | "SubnetId": {"Ref": "SubnetA"}, 118 | "NetworkAclId": {"Ref": "NetworkAcl"} 119 | } 120 | }, 121 | "SubnetNetworkAclAssociationB": { 122 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 123 | "Properties": { 124 | "SubnetId": {"Ref": "SubnetB"}, 125 | "NetworkAclId": {"Ref": "NetworkAcl"} 126 | } 127 | }, 128 | "NetworkAclEntryIngress": { 129 | "Type": "AWS::EC2::NetworkAclEntry", 130 | "Properties": { 131 | "NetworkAclId": {"Ref": "NetworkAcl"}, 132 | "RuleNumber": "100", 133 | "Protocol": "-1", 134 | "RuleAction": "allow", 135 | "Egress": "false", 136 | "CidrBlock": "0.0.0.0/0" 137 | } 138 | }, 139 | "NetworkAclEntryEgress": { 140 | "Type": "AWS::EC2::NetworkAclEntry", 141 | "Properties": { 142 | "NetworkAclId": {"Ref": "NetworkAcl"}, 143 | "RuleNumber": "100", 144 | "Protocol": "-1", 145 | "RuleAction": "allow", 146 | "Egress": "true", 147 | "CidrBlock": "0.0.0.0/0" 148 | } 149 | }, 150 | "LoadBalancer": { 151 | "Type": "AWS::ElasticLoadBalancing::LoadBalancer", 152 | "Properties": { 153 | "Subnets": [{"Ref": "SubnetA"}, {"Ref": "SubnetB"}], 154 | "LoadBalancerName": "awsinaction-elb", 155 | "Listeners": [{ 156 | "InstancePort": "80", 157 | "InstanceProtocol": "HTTP", 158 | "LoadBalancerPort": "80", 159 | "Protocol": "HTTP" 160 | }], 161 | "HealthCheck": { 162 | "HealthyThreshold": "2", 163 | "Interval": "5", 164 | "Target": "TCP:80", 165 | "Timeout": "3", 166 | "UnhealthyThreshold": "2" 167 | }, 168 | "SecurityGroups": [{"Ref": "LoadBalancerSecurityGroup"}], 169 | "Scheme": "internet-facing" 170 | }, 171 | "DependsOn": "VPCGatewayAttachment" 172 | }, 173 | "LoadBalancerSecurityGroup": { 174 | "Type": "AWS::EC2::SecurityGroup", 175 | "Properties": { 176 | "GroupDescription": "awsinaction-elb-sg", 177 | "VpcId": {"Ref": "VPC"}, 178 | "SecurityGroupIngress": [{ 179 | "CidrIp": "0.0.0.0/0", 180 | "FromPort": 80, 181 | "IpProtocol": "tcp", 182 | "ToPort": 80 183 | }] 184 | } 185 | }, 186 | "WebServerSecurityGroup": { 187 | "Type": "AWS::EC2::SecurityGroup", 188 | "Properties": { 189 | "GroupDescription": "awsinaction-sg", 190 | "VpcId": {"Ref": "VPC"}, 191 | "SecurityGroupIngress": [{ 192 | "CidrIp": "0.0.0.0/0", 193 | "FromPort": 22, 194 | "IpProtocol": "tcp", 195 | "ToPort": 22 196 | }, { 197 | "FromPort": 80, 198 | "IpProtocol": "tcp", 199 | "SourceSecurityGroupId": {"Ref": "LoadBalancerSecurityGroup"}, 200 | "ToPort": 80 201 | }] 202 | } 203 | }, 204 | "DatabaseSecurityGroup": { 205 | "Type": "AWS::EC2::SecurityGroup", 206 | "Properties": { 207 | "GroupDescription": "awsinaction-db-sg", 208 | "VpcId": {"Ref": "VPC"}, 209 | "SecurityGroupIngress": [{ 210 | "IpProtocol": "tcp", 211 | "FromPort": "3306", 212 | "ToPort": "3306", 213 | "SourceSecurityGroupId": {"Ref": "WebServerSecurityGroup"} 214 | }] 215 | } 216 | }, 217 | "Database": { 218 | "Type": "AWS::RDS::DBInstance", 219 | "DeletionPolicy": "Delete", 220 | "Properties": { 221 | "AllocatedStorage": "5", 222 | "DBInstanceClass": "db.t2.micro", 223 | "DBInstanceIdentifier": "awsinaction-db", 224 | "DBName": "wordpress", 225 | "Engine": "MySQL", 226 | "MasterUsername": "wordpress", 227 | "MasterUserPassword": "wordpress", 228 | "VPCSecurityGroups": [{"Fn::GetAtt": ["DatabaseSecurityGroup", "GroupId"]}], 229 | "DBSubnetGroupName": {"Ref": "DBSubnetGroup"}, 230 | "PreferredMaintenanceWindow": "Sun:06:00-Sun:07:00", 231 | "BackupRetentionPeriod": 3, 232 | "PreferredBackupWindow": "05:00-06:00" 233 | }, 234 | "DependsOn": "VPCGatewayAttachment" 235 | }, 236 | "DBSubnetGroup" : { 237 | "Type" : "AWS::RDS::DBSubnetGroup", 238 | "Properties" : { 239 | "DBSubnetGroupDescription" : "DB subnet group", 240 | "SubnetIds": [{"Ref": "SubnetA"}, {"Ref": "SubnetB"}] 241 | } 242 | }, 243 | "LaunchConfiguration": { 244 | "Type": "AWS::AutoScaling::LaunchConfiguration", 245 | "Metadata": { 246 | "AWS::CloudFormation::Init": { 247 | "config": { 248 | "packages": { 249 | "yum": { 250 | "php": [], 251 | "php-mysql": [], 252 | "mysql": [], 253 | "httpd": [] 254 | } 255 | }, 256 | "sources": { 257 | "/var/www/html": "https://wordpress.org/wordpress-4.2.4.tar.gz" 258 | }, 259 | "files": { 260 | "/tmp/config": { 261 | "content": {"Fn::Join": ["", [ 262 | "#!/bin/bash -ex\n", 263 | "cp /var/www/html/wordpress/wp-config-sample.php /var/www/html/wordpress/wp-config.php\n", 264 | "sed -i \"s/'database_name_here'/'wordpress'/g\" wp-config.php\n", 265 | "sed -i \"s/'username_here'/'wordpress'/g\" wp-config.php\n", 266 | "sed -i \"s/'password_here'/'wordpress'/g\" wp-config.php\n", 267 | "sed -i \"s/'localhost'/'", {"Fn::GetAtt": ["Database", "Endpoint.Address"]}, "'/g\" wp-config.php\n", 268 | "chmod -R 777 wp-content/ \n", 269 | "curl -O https://raw.githubusercontent.com/AWSinAction/builds/gh-pages/phar/wp-cli.phar \n", 270 | "php wp-cli.phar core install --url=\"", {"Fn::GetAtt": ["LoadBalancer", "DNSName"]}, "/wordpress\" --title=\"", {"Ref": "BlogTitle"}, "\" --admin_user=\"", {"Ref": "AdminUsername"}, "\" --admin_password=\"", {"Ref": "AdminPassword"}, "\" --admin_email=\"", {"Ref": "AdminEMail"}, "\" \n" ]]}, 271 | "mode": "000500", 272 | "owner": "root", 273 | "group": "root" 274 | } 275 | }, 276 | "commands": { 277 | "01_config": { 278 | "command": "/tmp/config", 279 | "cwd": "/var/www/html/wordpress" 280 | } 281 | }, 282 | "services": { 283 | "sysvinit": { 284 | "httpd": { 285 | "enabled": "true", 286 | "ensureRunning": "true" 287 | } 288 | } 289 | } 290 | } 291 | } 292 | }, 293 | "Properties": { 294 | "EbsOptimized": false, 295 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 296 | "InstanceType": "t2.micro", 297 | "SecurityGroups": [{"Ref": "WebServerSecurityGroup"}], 298 | "KeyName": {"Ref": "KeyName"}, 299 | "AssociatePublicIpAddress": true, 300 | "UserData": {"Fn::Base64": {"Fn::Join": ["", [ 301 | "#!/bin/bash -ex\n", 302 | "yum update -y aws-cfn-bootstrap\n", 303 | "/opt/aws/bin/cfn-init -v --stack ", {"Ref": "AWS::StackName"}, " --resource LaunchConfiguration --region ", {"Ref": "AWS::Region"}, "\n", 304 | "/opt/aws/bin/cfn-signal -e $? --stack ", {"Ref": "AWS::StackName"}, " --resource AutoScalingGroup --region ", {"Ref": "AWS::Region"}, "\n" 305 | ]]}} 306 | } 307 | }, 308 | "AutoScalingGroup": { 309 | "Type": "AWS::AutoScaling::AutoScalingGroup", 310 | "Properties": { 311 | "LoadBalancerNames": [{"Ref": "LoadBalancer"}], 312 | "LaunchConfigurationName": {"Ref": "LaunchConfiguration"}, 313 | "MinSize": "2", 314 | "MaxSize": "2", 315 | "DesiredCapacity": "2", 316 | "VPCZoneIdentifier": [{"Ref": "SubnetA"}, {"Ref": "SubnetB"}] 317 | }, 318 | "CreationPolicy": { 319 | "ResourceSignal": { 320 | "Timeout": "PT10M" 321 | } 322 | }, 323 | "DependsOn": "VPCGatewayAttachment" 324 | } 325 | }, 326 | "Outputs": { 327 | "URL": { 328 | "Value": {"Fn::Join": ["", ["http://", {"Fn::GetAtt": ["LoadBalancer", "DNSName"]}, "/wordpress"]]}, 329 | "Description": "Wordpress URL" 330 | } 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /chapter9/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "AWS in Action: chapter 9", 4 | "Parameters": { 5 | "KeyName": { 6 | "Description": "Key Pair name", 7 | "Type": "AWS::EC2::KeyPair::KeyName", 8 | "Default": "mykey" 9 | }, 10 | "BlogTitle": { 11 | "Description": "The title of the blog.", 12 | "Type": "String", 13 | "Default": "Amazon Web Services in Action - Example" 14 | }, 15 | "AdminUsername": { 16 | "Description": "A username for admin.", 17 | "Type": "String", 18 | "Default": "admin" 19 | }, 20 | "AdminPassword": { 21 | "Description": "A password for admin", 22 | "Type": "String", 23 | "NoEcho": "true" 24 | }, 25 | "AdminEMail": { 26 | "Description": "The email address of the administrator.", 27 | "Type": "String" 28 | } 29 | }, 30 | "Mappings": { 31 | "EC2RegionMap": { 32 | "ap-northeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-cbf90ecb"}, 33 | "ap-southeast-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-68d8e93a"}, 34 | "ap-southeast-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-fd9cecc7"}, 35 | "eu-central-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a8221fb5"}, 36 | "eu-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-a10897d6"}, 37 | "sa-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-b52890a8"}, 38 | "us-east-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-1ecae776"}, 39 | "us-west-1": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-d114f295"}, 40 | "us-west-2": {"AmazonLinuxAMIHVMEBSBacked64bit": "ami-e7527ed7"} 41 | } 42 | }, 43 | "Resources": { 44 | "VPC": { 45 | "Type": "AWS::EC2::VPC", 46 | "Properties": { 47 | "CidrBlock": "172.31.0.0/16", 48 | "EnableDnsHostnames": "true" 49 | } 50 | }, 51 | "InternetGateway": { 52 | "Type": "AWS::EC2::InternetGateway", 53 | "Properties": { 54 | } 55 | }, 56 | "VPCGatewayAttachment": { 57 | "Type": "AWS::EC2::VPCGatewayAttachment", 58 | "Properties": { 59 | "VpcId": {"Ref": "VPC"}, 60 | "InternetGatewayId": {"Ref": "InternetGateway"} 61 | } 62 | }, 63 | "SubnetA": { 64 | "Type": "AWS::EC2::Subnet", 65 | "Properties": { 66 | "AvailabilityZone": {"Fn::Select": ["0", {"Fn::GetAZs": ""}]}, 67 | "CidrBlock": "172.31.38.0/24", 68 | "VpcId": {"Ref": "VPC"} 69 | } 70 | }, 71 | "SubnetB": { 72 | "Type": "AWS::EC2::Subnet", 73 | "Properties": { 74 | "AvailabilityZone": {"Fn::Select": ["1", {"Fn::GetAZs": ""}]}, 75 | "CidrBlock": "172.31.37.0/24", 76 | "VpcId": {"Ref": "VPC"} 77 | } 78 | }, 79 | "RouteTable": { 80 | "Type": "AWS::EC2::RouteTable", 81 | "Properties": { 82 | "VpcId": {"Ref": "VPC"} 83 | } 84 | }, 85 | "RouteTableAssociationA": { 86 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 87 | "Properties": { 88 | "SubnetId": {"Ref": "SubnetA"}, 89 | "RouteTableId": {"Ref": "RouteTable"} 90 | } 91 | }, 92 | "RouteTableAssociationB": { 93 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 94 | "Properties": { 95 | "SubnetId": {"Ref": "SubnetB"}, 96 | "RouteTableId": {"Ref": "RouteTable"} 97 | } 98 | }, 99 | "RoutePublicNATToInternet": { 100 | "Type": "AWS::EC2::Route", 101 | "Properties": { 102 | "RouteTableId": {"Ref": "RouteTable"}, 103 | "DestinationCidrBlock": "0.0.0.0/0", 104 | "GatewayId": {"Ref": "InternetGateway"} 105 | }, 106 | "DependsOn": "VPCGatewayAttachment" 107 | }, 108 | "NetworkAcl": { 109 | "Type": "AWS::EC2::NetworkAcl", 110 | "Properties": { 111 | "VpcId": {"Ref": "VPC"} 112 | } 113 | }, 114 | "SubnetNetworkAclAssociationA": { 115 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 116 | "Properties": { 117 | "SubnetId": {"Ref": "SubnetA"}, 118 | "NetworkAclId": {"Ref": "NetworkAcl"} 119 | } 120 | }, 121 | "SubnetNetworkAclAssociationB": { 122 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 123 | "Properties": { 124 | "SubnetId": {"Ref": "SubnetB"}, 125 | "NetworkAclId": {"Ref": "NetworkAcl"} 126 | } 127 | }, 128 | "NetworkAclEntryIngress": { 129 | "Type": "AWS::EC2::NetworkAclEntry", 130 | "Properties": { 131 | "NetworkAclId": {"Ref": "NetworkAcl"}, 132 | "RuleNumber": "100", 133 | "Protocol": "-1", 134 | "RuleAction": "allow", 135 | "Egress": "false", 136 | "CidrBlock": "0.0.0.0/0" 137 | } 138 | }, 139 | "NetworkAclEntryEgress": { 140 | "Type": "AWS::EC2::NetworkAclEntry", 141 | "Properties": { 142 | "NetworkAclId": {"Ref": "NetworkAcl"}, 143 | "RuleNumber": "100", 144 | "Protocol": "-1", 145 | "RuleAction": "allow", 146 | "Egress": "true", 147 | "CidrBlock": "0.0.0.0/0" 148 | } 149 | }, 150 | "LoadBalancer": { 151 | "Type": "AWS::ElasticLoadBalancing::LoadBalancer", 152 | "Properties": { 153 | "Subnets": [{"Ref": "SubnetA"}, {"Ref": "SubnetB"}], 154 | "LoadBalancerName": "awsinaction-elb", 155 | "Listeners": [{ 156 | "InstancePort": "80", 157 | "InstanceProtocol": "HTTP", 158 | "LoadBalancerPort": "80", 159 | "Protocol": "HTTP" 160 | }], 161 | "HealthCheck": { 162 | "HealthyThreshold": "2", 163 | "Interval": "5", 164 | "Target": "TCP:80", 165 | "Timeout": "3", 166 | "UnhealthyThreshold": "2" 167 | }, 168 | "SecurityGroups": [{"Ref": "LoadBalancerSecurityGroup"}], 169 | "Scheme": "internet-facing" 170 | }, 171 | "DependsOn": "VPCGatewayAttachment" 172 | }, 173 | "LoadBalancerSecurityGroup": { 174 | "Type": "AWS::EC2::SecurityGroup", 175 | "Properties": { 176 | "GroupDescription": "awsinaction-elb-sg", 177 | "VpcId": {"Ref": "VPC"}, 178 | "SecurityGroupIngress": [{ 179 | "CidrIp": "0.0.0.0/0", 180 | "FromPort": 80, 181 | "IpProtocol": "tcp", 182 | "ToPort": 80 183 | }] 184 | } 185 | }, 186 | "WebServerSecurityGroup": { 187 | "Type": "AWS::EC2::SecurityGroup", 188 | "Properties": { 189 | "GroupDescription": "awsinaction-sg", 190 | "VpcId": {"Ref": "VPC"}, 191 | "SecurityGroupIngress": [{ 192 | "CidrIp": "0.0.0.0/0", 193 | "FromPort": 22, 194 | "IpProtocol": "tcp", 195 | "ToPort": 22 196 | }, { 197 | "FromPort": 80, 198 | "IpProtocol": "tcp", 199 | "SourceSecurityGroupId": {"Ref": "LoadBalancerSecurityGroup"}, 200 | "ToPort": 80 201 | }] 202 | } 203 | }, 204 | "DatabaseSecurityGroup": { 205 | "Type": "AWS::EC2::SecurityGroup", 206 | "Properties": { 207 | "GroupDescription": "awsinaction-db-sg", 208 | "VpcId": {"Ref": "VPC"}, 209 | "SecurityGroupIngress": [{ 210 | "IpProtocol": "tcp", 211 | "FromPort": "3306", 212 | "ToPort": "3306", 213 | "SourceSecurityGroupId": {"Ref": "WebServerSecurityGroup"} 214 | }] 215 | } 216 | }, 217 | "Database": { 218 | "Type": "AWS::RDS::DBInstance", 219 | "DeletionPolicy": "Delete", 220 | "Properties": { 221 | "AllocatedStorage": "5", 222 | "DBInstanceClass": "db.t2.micro", 223 | "DBInstanceIdentifier": "awsinaction-db", 224 | "DBName": "wordpress", 225 | "Engine": "MySQL", 226 | "MasterUsername": "wordpress", 227 | "MasterUserPassword": "wordpress", 228 | "VPCSecurityGroups": [{"Fn::GetAtt": ["DatabaseSecurityGroup", "GroupId"]}], 229 | "DBSubnetGroupName": {"Ref": "DBSubnetGroup"} 230 | }, 231 | "DependsOn": "VPCGatewayAttachment" 232 | }, 233 | "DBSubnetGroup" : { 234 | "Type" : "AWS::RDS::DBSubnetGroup", 235 | "Properties" : { 236 | "DBSubnetGroupDescription" : "DB subnet group", 237 | "SubnetIds": [{"Ref": "SubnetA"}, {"Ref": "SubnetB"}] 238 | } 239 | }, 240 | "LaunchConfiguration": { 241 | "Type": "AWS::AutoScaling::LaunchConfiguration", 242 | "Metadata": { 243 | "AWS::CloudFormation::Init": { 244 | "config": { 245 | "packages": { 246 | "yum": { 247 | "php": [], 248 | "php-mysql": [], 249 | "mysql": [], 250 | "httpd": [] 251 | } 252 | }, 253 | "sources": { 254 | "/var/www/html": "https://wordpress.org/wordpress-4.2.4.tar.gz" 255 | }, 256 | "files": { 257 | "/tmp/config": { 258 | "content": {"Fn::Join": ["", [ 259 | "#!/bin/bash -ex\n", 260 | "cp /var/www/html/wordpress/wp-config-sample.php /var/www/html/wordpress/wp-config.php\n", 261 | "sed -i \"s/'database_name_here'/'wordpress'/g\" wp-config.php\n", 262 | "sed -i \"s/'username_here'/'wordpress'/g\" wp-config.php\n", 263 | "sed -i \"s/'password_here'/'wordpress'/g\" wp-config.php\n", 264 | "sed -i \"s/'localhost'/'", {"Fn::GetAtt": ["Database", "Endpoint.Address"]}, "'/g\" wp-config.php\n", 265 | "chmod -R 777 wp-content/ \n", 266 | "curl -O https://raw.githubusercontent.com/AWSinAction/builds/gh-pages/phar/wp-cli.phar \n", 267 | "php wp-cli.phar core install --url=\"", {"Fn::GetAtt": ["LoadBalancer", "DNSName"]}, "/wordpress\" --title=\"", {"Ref": "BlogTitle"}, "\" --admin_user=\"", {"Ref": "AdminUsername"}, "\" --admin_password=\"", {"Ref": "AdminPassword"}, "\" --admin_email=\"", {"Ref": "AdminEMail"}, "\" \n" ]]}, 268 | "mode": "000500", 269 | "owner": "root", 270 | "group": "root" 271 | } 272 | }, 273 | "commands": { 274 | "01_config": { 275 | "command": "/tmp/config", 276 | "cwd": "/var/www/html/wordpress" 277 | } 278 | }, 279 | "services": { 280 | "sysvinit": { 281 | "httpd": { 282 | "enabled": "true", 283 | "ensureRunning": "true" 284 | } 285 | } 286 | } 287 | } 288 | } 289 | }, 290 | "Properties": { 291 | "EbsOptimized": false, 292 | "ImageId": {"Fn::FindInMap": ["EC2RegionMap", {"Ref": "AWS::Region"}, "AmazonLinuxAMIHVMEBSBacked64bit"]}, 293 | "InstanceType": "t2.micro", 294 | "SecurityGroups": [{"Ref": "WebServerSecurityGroup"}], 295 | "KeyName": {"Ref": "KeyName"}, 296 | "AssociatePublicIpAddress": true, 297 | "UserData": {"Fn::Base64": {"Fn::Join": ["", [ 298 | "#!/bin/bash -ex\n", 299 | "yum update -y aws-cfn-bootstrap\n", 300 | "/opt/aws/bin/cfn-init -v --stack ", {"Ref": "AWS::StackName"}, " --resource LaunchConfiguration --region ", {"Ref": "AWS::Region"}, "\n", 301 | "/opt/aws/bin/cfn-signal -e $? --stack ", {"Ref": "AWS::StackName"}, " --resource AutoScalingGroup --region ", {"Ref": "AWS::Region"}, "\n" 302 | ]]}} 303 | } 304 | }, 305 | "AutoScalingGroup": { 306 | "Type": "AWS::AutoScaling::AutoScalingGroup", 307 | "Properties": { 308 | "LoadBalancerNames": [{"Ref": "LoadBalancer"}], 309 | "LaunchConfigurationName": {"Ref": "LaunchConfiguration"}, 310 | "MinSize": "2", 311 | "MaxSize": "2", 312 | "DesiredCapacity": "2", 313 | "VPCZoneIdentifier": [{"Ref": "SubnetA"}, {"Ref": "SubnetB"}] 314 | }, 315 | "CreationPolicy": { 316 | "ResourceSignal": { 317 | "Timeout": "PT10M" 318 | } 319 | }, 320 | "DependsOn": "VPCGatewayAttachment" 321 | } 322 | }, 323 | "Outputs": { 324 | "URL": { 325 | "Value": {"Fn::Join": ["", ["http://", {"Fn::GetAtt": ["LoadBalancer", "DNSName"]}, "/wordpress"]]}, 326 | "Description": "Wordpress URL" 327 | } 328 | } 329 | } 330 | --------------------------------------------------------------------------------