├── .github
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── ci.yml
├── .gitignore
├── .yamllint
├── LICENSE
├── README.md
├── chapter02
├── cost.html
└── template.yaml
├── chapter03
├── a
│ └── index.html
└── b
│ └── index.html
├── chapter04
├── nodecc
│ ├── .eslintrc.yml
│ ├── .gitignore
│ ├── .nvmrc
│ ├── README.md
│ ├── index.js
│ ├── lib
│ │ ├── createVM.js
│ │ ├── listAMIs.js
│ │ ├── listSubnets.js
│ │ ├── listVMs.js
│ │ ├── showVM.js
│ │ └── terminateVM.js
│ ├── nodecc.png
│ ├── package-lock.json
│ └── package.json
├── virtualmachine.ps1
├── virtualmachine.sh
└── virtualmachine.yaml
├── chapter05
├── Dev.md
├── etherpad.zip
├── irc-cloudformation.yaml
├── irc-create-cloudformation-stack.sh
├── vpn-cloudformation.yaml
├── vpn-create-cloudformation-stack.sh
└── vpn-setup.sh
├── chapter06
├── ec2-iamrole.yaml
├── ec2-yum-update.yaml
├── firewall1.yaml
├── firewall2.yaml
├── firewall3.yaml
├── firewall4.yaml
├── firewall5.yaml
├── update.sh
└── vpc.yaml
├── chapter07
├── lambda_function.py
└── template.yaml
├── chapter08
├── bucketpolicy.json
├── gallery
│ ├── index.html
│ ├── package.json
│ └── server.js
└── helloworld.html
├── chapter09
├── ebs.yaml
└── instancestore.yaml
├── chapter10
├── template-with-backup.yaml
└── template.yaml
├── chapter11
├── cleanup.sh
├── template-multiaz.yaml
├── template-snapshot.yaml
├── template.yaml
└── wordpress-import.sql
├── chapter12
├── minimal.yaml
└── template.yaml
├── chapter13
├── .eslintrc.yml
├── .nvmrc
├── README.md
├── cli.txt
├── index.js
├── nodetodo.png
├── package-lock.json
├── package.json
└── tables.yaml
├── chapter14
├── multiaz-efs-eip.yaml
├── multiaz-efs.yaml
├── multiaz.yaml
└── recovery.yaml
├── chapter15
├── loadbalancer.yaml
└── url2png
│ ├── .eslintrc.yml
│ ├── .nvmrc
│ ├── README.md
│ ├── config.json
│ ├── index.js
│ ├── package-lock.json
│ ├── package.json
│ ├── url2png.png
│ └── worker.js
├── chapter16
├── 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.yaml
└── worker
│ ├── .ebextensions
│ └── worker.config
│ ├── lib.js
│ ├── package.json
│ └── worker.js
└── chapter17
├── url2png-loadtest.yaml
├── url2png.yaml
├── wordpress-loadtest.yaml
├── wordpress-schedule.yaml
└── wordpress.yaml
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | Please run `yamllint folder/template.yaml` and `aws cloudformation validate-template --template-body file://folder/template.yaml` before
2 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: 'ubuntu-latest'
8 |
9 | steps:
10 |
11 | - uses: 'actions/checkout@v2'
12 |
13 | - uses: 'actions/setup-python@v2'
14 | with:
15 | python-version: '3.8'
16 |
17 | - name: yamlllint
18 | run: |
19 | pip install yamllint==1.26.3
20 | find . -type f \( -name '*.yml' -o -name '*.yaml' \) -a ! -name '.*' | while read file; do set -ex && yamllint "$file"; done;
21 |
22 | - name: cfn-lint
23 | run: |
24 | pip install cfn-lint==0.54.1
25 | cfn-lint -i W2501 W4002 -t **/*.yaml
26 |
27 | - name: license
28 | run: |
29 | sudo apt-get install -y shellcheck
30 | find . -type f -name '*.sh' | while read file; do set -ex && shellcheck -s bash "$file"; done
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .DS_Store
3 | dump.rdb
4 |
--------------------------------------------------------------------------------
/.yamllint:
--------------------------------------------------------------------------------
1 | ---
2 | extends: default
3 | rules:
4 | indentation:
5 | indent-sequences: false
6 | line-length:
7 | max: 999
8 | comments:
9 | min-spaces-from-content: 1
10 | ignore: |
11 | node_modules/
12 |
--------------------------------------------------------------------------------
/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, Second Edition
2 |
3 | 
4 |
5 | Code of [Amazon Web Services in Action, Second Edition](http://bit.ly/amazon-web-services-in-action-2nd-edition)
6 |
7 | ## About the book
8 |
9 | Amazon Web Services in Action, Second Edition is a comprehensive introduction to computing, storing, and networking in the AWS cloud. You'll find clear, relevant coverage of all the essential AWS services you to know, emphasizing best practices for security, high availability and scalability. The practical, hands-on examples include different approaches to deploying applications on AWS, how to secure your infrastructure by isolating networks, and controlling traffic and managing access to AWS resources. You'll also learn to integrate AWS services into your own applications using SDKs and gain handy ideas on how to design applications for high availability, fault tolerance, and scalability.
10 |
11 | Fully updated to include the latest revisions and updates to AWS; this new edition also offers three new chapters covering the latest additions to the AWS platform: serverless infrastructure automation with AWS Lambda, sharing data volumes between machines with EFS, and caching data in memory with ElastiCache!
12 |
13 | ## Code examples by chapter
14 |
15 | * 02 [A simple example: WordPress on AWS in five minutes](./chapter02)
16 | * 03 [Using virtual machines: Amazon EC2](./chapter03)
17 | * 04 [Programming your infrastructure: the command-line interface, SDKs, and AWS CloudFormation](./chapter04)
18 | * 05 [Deploying apps onto virtual machines: AWS CloudFormation, AWS Elastic Beanstalk, and AWS OpsWorks](./chapter05)
19 | * 06 [Securing your system: AWS IAM, security groups, and Amazon VPC](./chapter06)
20 | * 07 [Automating operational tasks with AWS Lambda](./chapter07)
21 | * 08 [Storing your objects: Amazon S3 and Amazon Glacier](./chapter08)
22 | * 09 [Storing your data on hard drives: Amazon EBS and instance store](./chapter09)
23 | * 10 [Sharing data volumes between machines: Amazon EFS](./chapter10)
24 | * 11 [Using a relational database service: Amazon RDS](./chapter11)
25 | * 12 [Caching data in memory: Amazon ElastiCache](./chapter12)
26 | * 13 [Programming for the NoSQL database service: Amazon DynamoDB](./chapter13)
27 | * 14 [Making your infrastructure highly available: availability zones, auto-scaling, and Amazon CloudWatch](./chapter14)
28 | * 15 [Decoupling your infrastructure: Elastic Load Balancing and Amazon Simple Queue Service](./chapter15)
29 | * 16 [Designing for fault tolerance](./chapter16)
30 | * 17 [Scaling up and down: auto-scaling and Amazon CloudWatch](./chapter17)
31 |
32 | ## About the authors
33 |
34 | Andreas Wittig and Michael Wittig are software engineers and consultants focused on AWS and web development. They migrated the first bank in Germany to AWS along with other heavily regulated businesses with legacy applications.
35 |
--------------------------------------------------------------------------------
/chapter02/cost.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | AWS in Action: chapter 2
7 |
8 |
9 | click here
10 |
11 |
12 |
--------------------------------------------------------------------------------
/chapter02/template.yaml:
--------------------------------------------------------------------------------
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 | Mappings:
10 | RegionMap:
11 | 'ap-south-1':
12 | AMI: 'ami-2ed19c41'
13 | 'eu-west-3':
14 | AMI: 'ami-c8a017b5'
15 | 'eu-west-2':
16 | AMI: 'ami-e3051987'
17 | 'eu-west-1':
18 | AMI: 'ami-760aaa0f'
19 | 'ap-northeast-2':
20 | AMI: 'ami-fc862292'
21 | 'ap-northeast-1':
22 | AMI: 'ami-2803ac4e'
23 | 'sa-east-1':
24 | AMI: 'ami-1678037a'
25 | 'ca-central-1':
26 | AMI: 'ami-ef3b838b'
27 | 'ap-southeast-1':
28 | AMI: 'ami-dd7935be'
29 | 'ap-southeast-2':
30 | AMI: 'ami-1a668878'
31 | 'eu-central-1':
32 | AMI: 'ami-e28d098d'
33 | 'us-east-1':
34 | AMI: 'ami-6057e21a'
35 | 'us-east-2':
36 | AMI: 'ami-aa1b34cf'
37 | 'us-west-1':
38 | AMI: 'ami-1a033c7a'
39 | 'us-west-2':
40 | AMI: 'ami-32d8124a'
41 | Resources:
42 | VPC:
43 | Type: 'AWS::EC2::VPC'
44 | Properties:
45 | CidrBlock: '172.31.0.0/16'
46 | EnableDnsHostnames: true
47 | InternetGateway:
48 | Type: 'AWS::EC2::InternetGateway'
49 | Properties: {}
50 | VPCGatewayAttachment:
51 | Type: 'AWS::EC2::VPCGatewayAttachment'
52 | Properties:
53 | VpcId: !Ref VPC
54 | InternetGatewayId: !Ref InternetGateway
55 | SubnetA:
56 | Type: 'AWS::EC2::Subnet'
57 | Properties:
58 | AvailabilityZone: !Select [0, !GetAZs '']
59 | CidrBlock: '172.31.38.0/24'
60 | VpcId: !Ref VPC
61 | SubnetB:
62 | Type: 'AWS::EC2::Subnet'
63 | Properties:
64 | AvailabilityZone: !Select [1, !GetAZs '']
65 | CidrBlock: '172.31.37.0/24'
66 | VpcId: !Ref VPC
67 | RouteTable:
68 | Type: 'AWS::EC2::RouteTable'
69 | Properties:
70 | VpcId: !Ref VPC
71 | RouteTableAssociationA:
72 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
73 | Properties:
74 | SubnetId: !Ref SubnetA
75 | RouteTableId: !Ref RouteTable
76 | RouteTableAssociationB:
77 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
78 | Properties:
79 | SubnetId: !Ref SubnetB
80 | RouteTableId: !Ref RouteTable
81 | RoutePublicNATToInternet:
82 | Type: 'AWS::EC2::Route'
83 | Properties:
84 | RouteTableId: !Ref RouteTable
85 | DestinationCidrBlock: '0.0.0.0/0'
86 | GatewayId: !Ref InternetGateway
87 | DependsOn: VPCGatewayAttachment
88 | NetworkAcl:
89 | Type: 'AWS::EC2::NetworkAcl'
90 | Properties:
91 | VpcId: !Ref VPC
92 | SubnetNetworkAclAssociationA:
93 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
94 | Properties:
95 | SubnetId: !Ref SubnetA
96 | NetworkAclId: !Ref NetworkAcl
97 | SubnetNetworkAclAssociationB:
98 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
99 | Properties:
100 | SubnetId: !Ref SubnetB
101 | NetworkAclId: !Ref NetworkAcl
102 | NetworkAclEntryIngress:
103 | Type: 'AWS::EC2::NetworkAclEntry'
104 | Properties:
105 | NetworkAclId: !Ref NetworkAcl
106 | RuleNumber: 100
107 | Protocol: -1
108 | RuleAction: allow
109 | Egress: false
110 | CidrBlock: '0.0.0.0/0'
111 | NetworkAclEntryEgress:
112 | Type: 'AWS::EC2::NetworkAclEntry'
113 | Properties:
114 | NetworkAclId: !Ref NetworkAcl
115 | RuleNumber: 100
116 | Protocol: -1
117 | RuleAction: allow
118 | Egress: true
119 | CidrBlock: '0.0.0.0/0'
120 | LoadBalancer:
121 | Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
122 | Properties:
123 | Subnets:
124 | - Ref: SubnetA
125 | - Ref: SubnetB
126 | SecurityGroups:
127 | - !Ref LoadBalancerSecurityGroup
128 | Scheme: 'internet-facing'
129 | DependsOn: VPCGatewayAttachment
130 | LoadBalancerListener:
131 | Type: 'AWS::ElasticLoadBalancingV2::Listener'
132 | Properties:
133 | DefaultActions:
134 | - Type: forward
135 | TargetGroupArn: !Ref LoadBalancerTargetGroup
136 | LoadBalancerArn: !Ref LoadBalancer
137 | Port: 80
138 | Protocol: HTTP
139 | LoadBalancerTargetGroup:
140 | Type: 'AWS::ElasticLoadBalancingV2::TargetGroup'
141 | Properties:
142 | HealthCheckIntervalSeconds: 5
143 | HealthCheckPath: '/'
144 | HealthCheckPort: '80'
145 | HealthCheckProtocol: HTTP
146 | HealthCheckTimeoutSeconds: 3
147 | HealthyThresholdCount: 2
148 | UnhealthyThresholdCount: 2
149 | Matcher:
150 | HttpCode: '200,302'
151 | Port: 80
152 | Protocol: HTTP
153 | VpcId: !Ref VPC
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 | WebServerSecurityGroup:
165 | Type: 'AWS::EC2::SecurityGroup'
166 | Properties:
167 | GroupDescription: 'awsinaction-sg'
168 | VpcId: !Ref VPC
169 | SecurityGroupIngress:
170 | - CidrIp: '0.0.0.0/0'
171 | FromPort: 22
172 | IpProtocol: tcp
173 | ToPort: 22
174 | - FromPort: 80
175 | IpProtocol: tcp
176 | SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup
177 | ToPort: 80
178 | DatabaseSecurityGroup:
179 | Type: 'AWS::EC2::SecurityGroup'
180 | Properties:
181 | GroupDescription: 'awsinaction-db-sg'
182 | VpcId: !Ref VPC
183 | SecurityGroupIngress:
184 | - IpProtocol: tcp
185 | FromPort: 3306
186 | ToPort: 3306
187 | SourceSecurityGroupId: !Ref WebServerSecurityGroup
188 | Database:
189 | Type: 'AWS::RDS::DBInstance'
190 | DeletionPolicy: Delete # For AWS::RDS::DBInstance resources that don't specify the DBClusterIdentifier property, the default policy is Snapshot which can cause unwanted costs. However, for production setups, we highly recommend to stay with the default to avoid data loss.
191 | Properties:
192 | AllocatedStorage: '5'
193 | BackupRetentionPeriod: 0
194 | DBInstanceClass: 'db.t2.micro'
195 | DBName: wordpress
196 | Engine: MySQL
197 | MasterUsername: wordpress
198 | MasterUserPassword: wordpress
199 | VPCSecurityGroups:
200 | - !Sub ${DatabaseSecurityGroup.GroupId}
201 | DBSubnetGroupName: !Ref DBSubnetGroup
202 | DependsOn: VPCGatewayAttachment
203 | DBSubnetGroup:
204 | Type: 'AWS::RDS::DBSubnetGroup'
205 | Properties:
206 | DBSubnetGroupDescription: DB subnet group
207 | SubnetIds:
208 | - Ref: SubnetA
209 | - Ref: SubnetB
210 | EFSFileSystem:
211 | Type: 'AWS::EFS::FileSystem'
212 | Properties:
213 | FileSystemTags:
214 | - Key: Name
215 | Value: 'wordpress-efs'
216 | EFSMountTargetA:
217 | Type: 'AWS::EFS::MountTarget'
218 | Properties:
219 | FileSystemId: !Ref EFSFileSystem
220 | SubnetId: !Ref SubnetA
221 | SecurityGroups:
222 | - !Ref EFSSecurityGroup
223 | EFSMountTargetB:
224 | Type: 'AWS::EFS::MountTarget'
225 | Properties:
226 | FileSystemId: !Ref EFSFileSystem
227 | SubnetId: !Ref SubnetB
228 | SecurityGroups:
229 | - !Ref EFSSecurityGroup
230 | EFSSecurityGroup:
231 | Type: 'AWS::EC2::SecurityGroup'
232 | Properties:
233 | GroupDescription: 'Allowing access to EFS'
234 | VpcId: !Ref VPC
235 | SecurityGroupIngress:
236 | - IpProtocol: tcp
237 | FromPort: 2049
238 | ToPort: 2049
239 | SourceSecurityGroupId: !Ref WebServerSecurityGroup
240 | LaunchConfiguration:
241 | Type: 'AWS::AutoScaling::LaunchConfiguration'
242 | Metadata:
243 | 'AWS::CloudFormation::Init':
244 | configSets:
245 | default: [config]
246 | config:
247 | packages:
248 | yum:
249 | php70: []
250 | php70-opcache: []
251 | php70-mysqlnd: []
252 | mysql56: []
253 | httpd24: []
254 | files:
255 | '/etc/httpd/conf.d/wordpress.conf':
256 | content: |
257 |
258 | Options Indexes FollowSymLinks
259 | AllowOverride All
260 | Require all granted
261 |
262 | mode: 000500
263 | owner: root
264 | group: root
265 | '/root/wordpress.sh':
266 | content: !Sub |
267 | #!/bin/bash -ex
268 | sed -i 's/;opcache.revalidate_freq=2/opcache.revalidate_freq=300/g' /etc/php-7.0.d/10-opcache.ini
269 | # ensure than only one machine installs wp
270 | if mkdir /var/www/lock; then
271 | cd /var/www/html
272 | wget -q -T 60 https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
273 | if ! php wp-cli.phar core is-installed --allow-root; then
274 | php wp-cli.phar core download --allow-root --version=4.8
275 | php wp-cli.phar core config --dbname='wordpress' --dbuser='wordpress' --dbpass='wordpress' --dbhost='${Database.Endpoint.Address}' --allow-root
276 | fi
277 | chown -R apache:apache /var/www/html
278 | chmod u+wrx /var/www/html/wp-content/*
279 | rm wp-cli.phar
280 | fi
281 | mode: 000500
282 | owner: root
283 | group: root
284 | commands:
285 | 01_wordpress:
286 | command: '/root/wordpress.sh'
287 | cwd: '/var/www/html'
288 | services:
289 | sysvinit:
290 | httpd:
291 | enabled: true
292 | ensureRunning: true
293 | Properties:
294 | AssociatePublicIpAddress: true
295 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
296 | InstanceMonitoring: false
297 | InstanceType: 't2.micro'
298 | SecurityGroups:
299 | - !Ref WebServerSecurityGroup
300 | KeyName: !Ref KeyName
301 | UserData:
302 | 'Fn::Base64': !Sub |
303 | #!/bin/bash -x
304 | bash -ex << "TRY"
305 | while ! nc -z ${EFSFileSystem}.efs.${AWS::Region}.amazonaws.com 2049; do sleep 1; done
306 | mkdir /var/www
307 | mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 "${EFSFileSystem}.efs.${AWS::Region}.amazonaws.com:/" /var/www/
308 | /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchConfiguration --region ${AWS::Region}
309 | TRY
310 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource AutoScalingGroup --region ${AWS::Region}
311 | AutoScalingGroup:
312 | Type: 'AWS::AutoScaling::AutoScalingGroup'
313 | DependsOn:
314 | - EFSMountTargetA
315 | - EFSMountTargetB
316 | Properties:
317 | TargetGroupARNs:
318 | - !Ref LoadBalancerTargetGroup
319 | LaunchConfigurationName: !Ref LaunchConfiguration
320 | MinSize: '2'
321 | MaxSize: '4'
322 | Cooldown: '60'
323 | HealthCheckGracePeriod: 300
324 | HealthCheckType: ELB
325 | VPCZoneIdentifier:
326 | - !Ref SubnetA
327 | - !Ref SubnetB
328 | Tags:
329 | - PropagateAtLaunch: true
330 | Value: wordpress
331 | Key: Name
332 | CreationPolicy:
333 | ResourceSignal:
334 | Timeout: PT10M
335 | UpdatePolicy:
336 | AutoScalingRollingUpdate:
337 | PauseTime: PT10M
338 | WaitOnResourceSignals: true
339 | Outputs:
340 | URL:
341 | Value: !Sub 'http://${LoadBalancer.DNSName}'
342 | Description: 'Wordpress URL'
343 |
--------------------------------------------------------------------------------
/chapter03/a/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | A
4 |
5 |
6 | Hello A!
7 |
8 |
9 |
--------------------------------------------------------------------------------
/chapter03/b/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | B
4 |
5 |
6 | Hello B!
7 |
8 |
9 |
--------------------------------------------------------------------------------
/chapter04/nodecc/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | ---
2 | env:
3 | es6: true
4 | node: true
5 | extends: 'eslint:recommended'
6 | rules:
7 | no-console:
8 | - 'off'
9 | indent:
10 | - error
11 | - 2
12 | linebreak-style:
13 | - error
14 | - unix
15 | quotes:
16 | - error
17 | - single
18 | semi:
19 | - error
20 | - always
21 |
--------------------------------------------------------------------------------
/chapter04/nodecc/.gitignore:
--------------------------------------------------------------------------------
1 | nodecc.log
2 |
--------------------------------------------------------------------------------
/chapter04/nodecc/.nvmrc:
--------------------------------------------------------------------------------
1 | 8
2 |
--------------------------------------------------------------------------------
/chapter04/nodecc/README.md:
--------------------------------------------------------------------------------
1 | # Node Control Center for AWS
2 |
3 | 
4 |
5 | Install the dependencies ...
6 |
7 | ```
8 | npm install
9 | ```
10 |
11 | ... and run nodecc
12 |
13 | ```
14 | node index.js
15 | ```
16 |
--------------------------------------------------------------------------------
/chapter04/nodecc/index.js:
--------------------------------------------------------------------------------
1 | const blessed = require('blessed');
2 |
3 | const screen = blessed.screen({
4 | autoPadding: true,
5 | smartCSR: true,
6 | log: './nodecc.log'
7 | });
8 | screen.title = 'Node Control Center for AWS';
9 |
10 | const content = blessed.box({
11 | parent: screen,
12 | width: '70%',
13 | height: '90%',
14 | top: '10%',
15 | left: '30%',
16 | border: {
17 | type: 'none',
18 | fg: '#ffffff'
19 | },
20 | fg: 'white',
21 | bg: 'blue',
22 | 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.',
23 | tags: true
24 | });
25 |
26 | const progress = blessed.progressbar({
27 | parent: screen,
28 | width: '70%',
29 | height: '10%',
30 | top: '0%',
31 | left: '30%',
32 | orientation: 'horizontal',
33 | border: {
34 | type: 'line',
35 | fg: '#ffffff'
36 | },
37 | fg: 'white',
38 | bg: 'blue',
39 | barFg: 'green',
40 | barBg: 'green',
41 | filled: 0
42 | });
43 |
44 | const list = blessed.list({
45 | parent: screen,
46 | width: '30%',
47 | height: '100%',
48 | top: '0%',
49 | left: '0%',
50 | border: {
51 | type: 'line',
52 | fg: '#ffffff'
53 | },
54 | fg: 'white',
55 | bg: 'blue',
56 | selectedBg: 'green',
57 | mouse: true,
58 | keys: true,
59 | vi: true,
60 | label: 'actions',
61 | items: ['list virtual machines', 'create virtual machine', 'terminate virtual machine']
62 | });
63 | list.on('select', (ev, i) => {
64 | content.border.type = 'line';
65 | content.focus();
66 | list.border.type = 'none';
67 | open(i);
68 | screen.render();
69 | });
70 | list.focus();
71 |
72 | const open = (i) => {
73 | screen.log('open(' + i + ')');
74 | if (i === 0) {
75 | loading();
76 | require('./lib/listVMs.js')((err, instanceIds) => {
77 | loaded();
78 | if (err) {
79 | log('error', 'listVMs cb err: ' + err);
80 | } else {
81 | const instanceList = blessed.list({
82 | fg: 'white',
83 | bg: 'blue',
84 | selectedBg: 'green',
85 | mouse: true,
86 | keys: true,
87 | vi: true,
88 | items: instanceIds
89 | });
90 | content.append(instanceList);
91 | instanceList.focus();
92 | instanceList.on('select', (ev, i) => {
93 | loading();
94 | require('./lib/showVM.js')(instanceIds[i], (err, instance) => {
95 | loaded();
96 | if (err) {
97 | log('error', 'showVM cb err: ' + err);
98 | } else {
99 | const vmContent = blessed.box({
100 | fg: 'white',
101 | bg: 'blue',
102 | content:
103 | 'InstanceId: ' + instance.InstanceId + '\n' +
104 | 'InstanceType: ' + instance.InstanceType + '\n' +
105 | 'LaunchTime: ' + instance.LaunchTime + '\n' +
106 | 'ImageId: ' + instance.ImageId + '\n' +
107 | 'PublicDnsName: ' + instance.PublicDnsName
108 | });
109 | content.append(vmContent);
110 | }
111 | screen.render();
112 | });
113 | });
114 | screen.render();
115 | }
116 | screen.render();
117 | });
118 | } else if (i === 1) {
119 | loading();
120 | require('./lib/listAMIs.js')((err, result) => {
121 | loaded();
122 | if (err) {
123 | log('error', 'listAMIs cb err: ' + err);
124 | } else {
125 | const amiList = blessed.list({
126 | fg: 'white',
127 | bg: 'blue',
128 | selectedBg: 'green',
129 | mouse: true,
130 | keys: true,
131 | vi: true,
132 | items: result.descriptions
133 | });
134 | content.append(amiList);
135 | amiList.focus();
136 | amiList.on('select', (ev, i) => {
137 | const amiId = result.amiIds[i];
138 | loading();
139 | require('./lib/listSubnets.js')((err, subnetIds) => {
140 | loaded();
141 | if (err) {
142 | log('error', 'listSubnets cb err: ' + err);
143 | } else {
144 | const subnetList = blessed.list({
145 | fg: 'white',
146 | bg: 'blue',
147 | selectedBg: 'green',
148 | mouse: true,
149 | keys: true,
150 | vi: true,
151 | items: subnetIds
152 | });
153 | content.append(subnetList);
154 | subnetList.focus();
155 | subnetList.on('select', (ev, i) => {
156 | loading();
157 | require('./lib/createVM.js')(amiId, subnetIds[i], (err) => {
158 | loaded();
159 | if (err) {
160 | log('error', 'createVM cb err: ' + err);
161 | } else {
162 | const vmContent = blessed.box({
163 | fg: 'white',
164 | bg: 'blue',
165 | content: 'starting ...'
166 | });
167 | content.append(vmContent);
168 | }
169 | screen.render();
170 | });
171 | });
172 | screen.render();
173 | }
174 | screen.render();
175 | });
176 | });
177 | screen.render();
178 | }
179 | screen.render();
180 | });
181 | } else if (i === 2) {
182 | loading();
183 | require('./lib/listVMs.js')((err, instanceIds) => {
184 | loaded();
185 | if (err) {
186 | log('error', 'listVMs cb err: ' + err);
187 | } else {
188 | const instanceList = blessed.list({
189 | fg: 'white',
190 | bg: 'blue',
191 | selectedBg: 'green',
192 | mouse: true,
193 | keys: true,
194 | vi: true,
195 | items: instanceIds
196 | });
197 | content.append(instanceList);
198 | instanceList.focus();
199 | instanceList.on('select', (ev, i) => {
200 | loading();
201 | require('./lib/terminateVM.js')(instanceIds[i], (err) => {
202 | loaded();
203 | if (err) {
204 | log('error', 'terminateVM cb err: ' + err);
205 | } else {
206 | const vmContent = blessed.box({
207 | fg: 'white',
208 | bg: 'blue',
209 | content: 'terminating ...'
210 | });
211 | content.append(vmContent);
212 | }
213 | screen.render();
214 | });
215 | });
216 | screen.render();
217 | }
218 | screen.render();
219 | });
220 | } else {
221 | log('error', 'not supported');
222 | screen.render();
223 | }
224 | };
225 |
226 | screen.key('left', () => {
227 | content.border.type = 'none';
228 | content.children.slice().forEach((child) => {
229 | content.remove(child);
230 | });
231 | list.border.type = 'line';
232 | list.focus();
233 | screen.render();
234 | });
235 |
236 | screen.key(['escape', 'q', 'C-c'], () => {
237 | return process.exit(0);
238 | });
239 |
240 | let loadingInterval;
241 |
242 | const loading = () => {
243 | progress.reset();
244 | clearInterval(loadingInterval);
245 | loadingInterval = setInterval(() => {
246 | if (progress.filled < 75) {
247 | progress.progress(progress.filled + 5);
248 | }
249 | screen.render();
250 | }, 200);
251 | };
252 |
253 | const loaded = () => {
254 | clearInterval(loadingInterval);
255 | progress.progress(100);
256 | screen.render();
257 | };
258 |
259 | const log = (level, message) => {
260 | screen.log('[' + level + ']: ' + message);
261 | };
262 |
263 | screen.render();
264 |
--------------------------------------------------------------------------------
/chapter04/nodecc/lib/createVM.js:
--------------------------------------------------------------------------------
1 | const AWS = require('aws-sdk');
2 | const ec2 = new AWS.EC2({
3 | region: 'us-east-1'
4 | });
5 |
6 | module.exports = (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 | }, (err) => {
15 | if (err) {
16 | cb(err);
17 | } else {
18 | cb(null);
19 | }
20 | });
21 | };
22 |
--------------------------------------------------------------------------------
/chapter04/nodecc/lib/listAMIs.js:
--------------------------------------------------------------------------------
1 | const jmespath = require('jmespath');
2 | const AWS = require('aws-sdk');
3 | const ec2 = new AWS.EC2({
4 | region: 'us-east-1'
5 | });
6 |
7 | module.exports = (cb) => {
8 | ec2.describeImages({
9 | Filters: [{
10 | Name: 'name',
11 | Values: ['amzn-ami-hvm-2017.09.1.*-x86_64-gp2']
12 | }]
13 | }, (err, data) => {
14 | if (err) {
15 | cb(err);
16 | } else {
17 | const amiIds = jmespath.search(data, 'Images[*].ImageId');
18 | const descriptions = jmespath.search(data, 'Images[*].Description');
19 | cb(null, {amiIds: amiIds, descriptions: descriptions});
20 | }
21 | });
22 | };
23 |
--------------------------------------------------------------------------------
/chapter04/nodecc/lib/listSubnets.js:
--------------------------------------------------------------------------------
1 | const jmespath = require('jmespath');
2 | const AWS = require('aws-sdk');
3 | const ec2 = new AWS.EC2({
4 | region: 'us-east-1'
5 | });
6 |
7 | module.exports = (cb) => {
8 | ec2.describeVpcs({
9 | Filters: [{
10 | Name: 'isDefault',
11 | Values: ['true']
12 | }]
13 | }, (err, data) => {
14 | if (err) {
15 | cb(err);
16 | } else {
17 | const vpcId = data.Vpcs[0].VpcId;
18 | ec2.describeSubnets({
19 | Filters: [{
20 | Name: 'vpc-id',
21 | Values: [vpcId]
22 | }]
23 | }, (err, data) => {
24 | if (err) {
25 | cb(err);
26 | } else {
27 | const subnetIds = jmespath.search(data, 'Subnets[*].SubnetId');
28 | cb(null, subnetIds);
29 | }
30 | });
31 | }
32 | });
33 | };
34 |
--------------------------------------------------------------------------------
/chapter04/nodecc/lib/listVMs.js:
--------------------------------------------------------------------------------
1 | const jmespath = require('jmespath');
2 | const AWS = require('aws-sdk');
3 | const ec2 = new AWS.EC2({
4 | region: 'us-east-1'
5 | });
6 |
7 | module.exports = (cb) => {
8 | ec2.describeInstances({
9 | Filters: [{
10 | Name: 'instance-state-name',
11 | Values: ['pending', 'running']
12 | }],
13 | MaxResults: 10
14 | }, (err, data) => {
15 | if (err) {
16 | cb(err);
17 | } else {
18 | const instanceIds = jmespath.search(data, 'Reservations[].Instances[].InstanceId');
19 | cb(null, instanceIds);
20 | }
21 | });
22 | };
23 |
--------------------------------------------------------------------------------
/chapter04/nodecc/lib/showVM.js:
--------------------------------------------------------------------------------
1 | const AWS = require('aws-sdk');
2 | const ec2 = new AWS.EC2({
3 | region: 'us-east-1'
4 | });
5 |
6 | module.exports = (instanceId, cb) => {
7 | ec2.describeInstances({
8 | InstanceIds: [instanceId]
9 | }, (err, data) => {
10 | if (err) {
11 | cb(err);
12 | } else {
13 | cb(null, data.Reservations[0].Instances[0]);
14 | }
15 | });
16 | };
17 |
--------------------------------------------------------------------------------
/chapter04/nodecc/lib/terminateVM.js:
--------------------------------------------------------------------------------
1 | const AWS = require('aws-sdk');
2 | const ec2 = new AWS.EC2({
3 | region: 'us-east-1'
4 | });
5 |
6 | module.exports = (instanceId, cb) => {
7 | ec2.terminateInstances({
8 | InstanceIds: [instanceId]
9 | }, (err) => {
10 | if (err) {
11 | cb(err);
12 | } else {
13 | cb(null);
14 | }
15 | });
16 | };
17 |
--------------------------------------------------------------------------------
/chapter04/nodecc/nodecc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AWSinAction/code2/04250e5824f06fbfd6e174d87701ae53ce7b5289/chapter04/nodecc/nodecc.png
--------------------------------------------------------------------------------
/chapter04/nodecc/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "aws-sdk": "2.369.0",
4 | "blessed": "0.1.81",
5 | "jmespath": "0.15.0"
6 | },
7 | "devDependencies": {
8 | "eslint": "5.9.0"
9 | },
10 | "scripts": {
11 | "test": "eslint ."
12 | },
13 | "private": true
14 | }
15 |
--------------------------------------------------------------------------------
/chapter04/virtualmachine.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=name,Values=amzn-ami-hvm-2017.09.1.*-x86_64-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 |
--------------------------------------------------------------------------------
/chapter04/virtualmachine.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=name,Values=amzn-ami-hvm-2017.09.1.*-x86_64-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 -r -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 |
--------------------------------------------------------------------------------
/chapter04/virtualmachine.yaml:
--------------------------------------------------------------------------------
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 | VPC:
10 | Description: 'Just select the one and only default VPC'
11 | Type: 'AWS::EC2::VPC::Id'
12 | Subnet:
13 | Description: 'Just select one of the available subnets'
14 | Type: 'AWS::EC2::Subnet::Id'
15 | InstanceType:
16 | Description: Select one of the possible instance types'
17 | Type: String
18 | Default: 't2.micro'
19 | AllowedValues: ['t2.micro', 't2.small', 't2.medium']
20 | Mappings:
21 | RegionMap:
22 | 'ap-south-1':
23 | AMI: 'ami-2ed19c41'
24 | 'eu-west-3':
25 | AMI: 'ami-c8a017b5'
26 | 'eu-west-2':
27 | AMI: 'ami-e3051987'
28 | 'eu-west-1':
29 | AMI: 'ami-760aaa0f'
30 | 'ap-northeast-2':
31 | AMI: 'ami-fc862292'
32 | 'ap-northeast-1':
33 | AMI: 'ami-2803ac4e'
34 | 'sa-east-1':
35 | AMI: 'ami-1678037a'
36 | 'ca-central-1':
37 | AMI: 'ami-ef3b838b'
38 | 'ap-southeast-1':
39 | AMI: 'ami-dd7935be'
40 | 'ap-southeast-2':
41 | AMI: 'ami-1a668878'
42 | 'eu-central-1':
43 | AMI: 'ami-e28d098d'
44 | 'us-east-1':
45 | AMI: 'ami-6057e21a'
46 | 'us-east-2':
47 | AMI: 'ami-aa1b34cf'
48 | 'us-west-1':
49 | AMI: 'ami-1a033c7a'
50 | 'us-west-2':
51 | AMI: 'ami-32d8124a'
52 | Resources:
53 | SecurityGroup:
54 | Type: 'AWS::EC2::SecurityGroup'
55 | Properties:
56 | GroupDescription: 'My security group'
57 | VpcId: !Ref VPC
58 | SecurityGroupIngress:
59 | - CidrIp: '0.0.0.0/0'
60 | FromPort: 22
61 | IpProtocol: tcp
62 | ToPort: 22
63 | VM:
64 | Type: 'AWS::EC2::Instance'
65 | Properties:
66 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
67 | InstanceType: !Ref InstanceType
68 | KeyName: !Ref KeyName
69 | SecurityGroupIds: [!Ref SecurityGroup]
70 | SubnetId: !Ref Subnet
71 | Outputs:
72 | PublicName:
73 | Value: !GetAtt 'VM.PublicDnsName'
74 | Description: 'Public name (connect via SSH as user ec2-user)'
75 |
--------------------------------------------------------------------------------
/chapter05/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 (make sure NodeVersion is available in latest EB environment):
7 | ```
8 | option_settings:
9 | aws:elasticbeanstalk:container:nodejs:
10 | NodeCommand: "bin/run.sh"
11 | NodeVersion: "8.12.0"
12 | ```
13 | 1. Create `src/.npmrc` with the following content:
14 | ```
15 | unsafe-perm = true
16 | ```
17 | 1. Copy `settings.json.template` to `settings.json` and change port to 8081
18 | 1. Run `zip -r etherpad.zip ./`
19 |
--------------------------------------------------------------------------------
/chapter05/etherpad.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AWSinAction/code2/04250e5824f06fbfd6e174d87701ae53ce7b5289/chapter05/etherpad.zip
--------------------------------------------------------------------------------
/chapter05/irc-cloudformation.yaml:
--------------------------------------------------------------------------------
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 | Resources:
9 | SecurityGroup:
10 | Type: 'AWS::EC2::SecurityGroup'
11 | Properties:
12 | GroupDescription: 'Enables access to IRC server'
13 | VpcId: !Ref VPC
14 | SecurityGroupIngress:
15 | - IpProtocol: tcp
16 | FromPort: 6667
17 | ToPort: 6667
18 | CidrIp: '0.0.0.0/0'
19 |
--------------------------------------------------------------------------------
/chapter05/irc-create-cloudformation-stack.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -ex
2 |
3 | VpcId="$(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-code2/chapter05/irc-cloudformation.yaml --parameters "ParameterKey=VPC,ParameterValue=$VpcId"
5 |
6 | aws cloudformation wait stack-create-complete --stack-name irc
7 |
--------------------------------------------------------------------------------
/chapter05/vpn-cloudformation.yaml:
--------------------------------------------------------------------------------
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 pair name for SSH access'
7 | Type: 'AWS::EC2::KeyPair::KeyName'
8 | VPC:
9 | Description: 'Just select the one and only default VPC.'
10 | Type: 'AWS::EC2::VPC::Id'
11 | Subnet:
12 | Description: 'Just select one of the available subnets.'
13 | Type: 'AWS::EC2::Subnet::Id'
14 | IPSecSharedSecret:
15 | Description: 'The shared secret key for IPSec.'
16 | Type: String
17 | VPNUser:
18 | Description: 'The VPN user.'
19 | Type: String
20 | VPNPassword:
21 | Description: 'The VPN password.'
22 | Type: String
23 | Mappings:
24 | RegionMap:
25 | 'ap-south-1':
26 | AMI: 'ami-2ed19c41'
27 | 'eu-west-3':
28 | AMI: 'ami-c8a017b5'
29 | 'eu-west-2':
30 | AMI: 'ami-e3051987'
31 | 'eu-west-1':
32 | AMI: 'ami-760aaa0f'
33 | 'ap-northeast-2':
34 | AMI: 'ami-fc862292'
35 | 'ap-northeast-1':
36 | AMI: 'ami-2803ac4e'
37 | 'sa-east-1':
38 | AMI: 'ami-1678037a'
39 | 'ca-central-1':
40 | AMI: 'ami-ef3b838b'
41 | 'ap-southeast-1':
42 | AMI: 'ami-dd7935be'
43 | 'ap-southeast-2':
44 | AMI: 'ami-1a668878'
45 | 'eu-central-1':
46 | AMI: 'ami-e28d098d'
47 | 'us-east-1':
48 | AMI: 'ami-6057e21a'
49 | 'us-east-2':
50 | AMI: 'ami-aa1b34cf'
51 | 'us-west-1':
52 | AMI: 'ami-1a033c7a'
53 | 'us-west-2':
54 | AMI: 'ami-32d8124a'
55 | Resources:
56 | EC2Instance:
57 | Type: 'AWS::EC2::Instance'
58 | Properties:
59 | InstanceType: 't2.micro'
60 | SecurityGroupIds:
61 | - !Ref InstanceSecurityGroup
62 | KeyName: !Ref KeyName
63 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
64 | SubnetId: !Ref Subnet
65 | UserData:
66 | 'Fn::Base64': !Sub |
67 | #!/bin/bash -x
68 | export IPSEC_PSK="${IPSecSharedSecret}"
69 | export VPN_USER="${VPNUser}"
70 | export VPN_PASSWORD="${VPNPassword}"
71 | curl -s https://raw.githubusercontent.com/AWSinAction/code2/master/chapter05/vpn-setup.sh | bash -ex
72 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource EC2Instance --region ${AWS::Region}
73 | CreationPolicy:
74 | ResourceSignal:
75 | Timeout: PT10M
76 | InstanceSecurityGroup:
77 | Type: 'AWS::EC2::SecurityGroup'
78 | Properties:
79 | GroupDescription: 'Enable access to VPN server'
80 | VpcId: !Ref VPC
81 | SecurityGroupIngress:
82 | - IpProtocol: tcp
83 | FromPort: 22
84 | ToPort: 22
85 | CidrIp: '0.0.0.0/0'
86 | - IpProtocol: udp
87 | FromPort: 500
88 | ToPort: 500
89 | CidrIp: '0.0.0.0/0'
90 | - IpProtocol: udp
91 | FromPort: 1701
92 | ToPort: 1701
93 | CidrIp: '0.0.0.0/0'
94 | - IpProtocol: udp
95 | FromPort: 4500
96 | ToPort: 4500
97 | CidrIp: '0.0.0.0/0'
98 | Outputs:
99 | ServerIP:
100 | Description: 'Public IP address of the vpn server'
101 | Value: !GetAtt 'EC2Instance.PublicIp'
102 | IPSecSharedSecret:
103 | Description: 'The shared key for the VPN connection (IPSec)'
104 | Value: !Ref IPSecSharedSecret
105 | VPNUser:
106 | Description: 'The username for the vpn connection'
107 | Value: !Ref VPNUser
108 | VPNPassword:
109 | Description: 'The password for the vpn connection'
110 | Value: !Ref VPNPassword
111 |
--------------------------------------------------------------------------------
/chapter05/vpn-create-cloudformation-stack.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -ex
2 |
3 | VpcId="$(aws ec2 describe-vpcs --filter "Name=isDefault, Values=true" --query "Vpcs[0].VpcId" --output text)"
4 | SubnetId="$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VpcId" --query "Subnets[0].SubnetId" --output text)"
5 | SharedSecret="$(openssl rand -base64 30)"
6 | Password="$(openssl rand -base64 30)"
7 |
8 | aws cloudformation create-stack --stack-name vpn --template-url https://s3.amazonaws.com/awsinaction-code2/chapter05/vpn-cloudformation.yaml --parameters ParameterKey=KeyName,ParameterValue=mykey "ParameterKey=VPC,ParameterValue=$VpcId" "ParameterKey=Subnet,ParameterValue=$SubnetId" "ParameterKey=IPSecSharedSecret,ParameterValue=$SharedSecret" ParameterKey=VPNUser,ParameterValue=vpn "ParameterKey=VPNPassword,ParameterValue=$Password"
9 |
10 | aws cloudformation wait stack-create-complete --stack-name vpn
11 |
12 | aws cloudformation describe-stacks --stack-name vpn --query "Stacks[0].Outputs"
13 |
--------------------------------------------------------------------------------
/chapter05/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 |
7 | PRIVATE_IP="$(curl -s http://169.254.169.254/latest/meta-data/local-ipv4)"
8 | PUBLIC_IP="$(curl -s http://169.254.169.254/latest/meta-data/public-ipv4)"
9 |
10 | yum-config-manager --enable epel
11 | yum clean all
12 | yum install -y openswan xl2tpd
13 |
14 | 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
87 | iptables-save > /etc/iptables.rules
88 |
89 | mkdir -p /etc/network/if-pre-up.d
90 | cat > /etc/network/if-pre-up.d/iptablesload < /proc/sys/net/ipv4/ip_forward
94 | exit 0
95 | EOF
96 |
97 | service ipsec start
98 | service xl2tpd start
99 |
100 | chkconfig ipsec on
101 | chkconfig xl2tpd on
102 |
--------------------------------------------------------------------------------
/chapter06/ec2-iamrole.yaml:
--------------------------------------------------------------------------------
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 | VPC:
10 | Description: 'Just select the one and only default VPC'
11 | Type: 'AWS::EC2::VPC::Id'
12 | Subnet:
13 | Description: 'Just select one of the available subnets'
14 | Type: 'AWS::EC2::Subnet::Id'
15 | Lifetime:
16 | Description: 'Lifetime in minutes (2-59)'
17 | Type: Number
18 | Default: '2'
19 | MinValue: '2'
20 | MaxValue: '59'
21 | Mappings:
22 | RegionMap:
23 | 'ap-south-1':
24 | AMI: 'ami-2ed19c41'
25 | 'eu-west-3':
26 | AMI: 'ami-c8a017b5'
27 | 'eu-west-2':
28 | AMI: 'ami-e3051987'
29 | 'eu-west-1':
30 | AMI: 'ami-760aaa0f'
31 | 'ap-northeast-2':
32 | AMI: 'ami-fc862292'
33 | 'ap-northeast-1':
34 | AMI: 'ami-2803ac4e'
35 | 'sa-east-1':
36 | AMI: 'ami-1678037a'
37 | 'ca-central-1':
38 | AMI: 'ami-ef3b838b'
39 | 'ap-southeast-1':
40 | AMI: 'ami-dd7935be'
41 | 'ap-southeast-2':
42 | AMI: 'ami-1a668878'
43 | 'eu-central-1':
44 | AMI: 'ami-e28d098d'
45 | 'us-east-1':
46 | AMI: 'ami-6057e21a'
47 | 'us-east-2':
48 | AMI: 'ami-aa1b34cf'
49 | 'us-west-1':
50 | AMI: 'ami-1a033c7a'
51 | 'us-west-2':
52 | AMI: 'ami-32d8124a'
53 | Resources:
54 | SecurityGroup:
55 | Type: 'AWS::EC2::SecurityGroup'
56 | Properties:
57 | GroupDescription: 'My security group'
58 | VpcId: !Ref VPC
59 | SecurityGroupIngress:
60 | - CidrIp: '0.0.0.0/0'
61 | FromPort: 22
62 | IpProtocol: tcp
63 | ToPort: 22
64 | Tags:
65 | - Key: Name
66 | Value: 'AWS in Action: chapter 6 (IAM role)'
67 | InstanceProfile:
68 | Type: 'AWS::IAM::InstanceProfile'
69 | Properties:
70 | Roles:
71 | - !Ref Role
72 | Role:
73 | Type: 'AWS::IAM::Role'
74 | Properties:
75 | AssumeRolePolicyDocument:
76 | Version: '2012-10-17'
77 | Statement:
78 | - Effect: Allow
79 | Principal:
80 | Service:
81 | - 'ec2.amazonaws.com'
82 | Action:
83 | - 'sts:AssumeRole'
84 | Policies:
85 | - PolicyName: ec2
86 | PolicyDocument:
87 | Version: '2012-10-17'
88 | Statement:
89 | - Sid: Stmt1425388787000
90 | Effect: Allow
91 | Action:
92 | - 'ec2:StopInstances'
93 | Resource:
94 | - '*'
95 | Condition:
96 | StringEquals:
97 | 'ec2:ResourceTag/aws:cloudformation:stack-id': !Ref 'AWS::StackId'
98 | Instance:
99 | Type: 'AWS::EC2::Instance'
100 | Properties:
101 | IamInstanceProfile: !Ref InstanceProfile
102 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
103 | InstanceType: 't2.micro'
104 | KeyName: !Ref KeyName
105 | SecurityGroupIds:
106 | - !Ref SecurityGroup
107 | SubnetId: !Ref Subnet
108 | UserData:
109 | 'Fn::Base64': !Sub |
110 | #!/bin/bash -x
111 | INSTANCEID="$(curl -s http://169.254.169.254/latest/meta-data/instance-id)"
112 | echo "aws ec2 stop-instances --instance-ids $INSTANCEID --region ${AWS::Region}" | at now + ${Lifetime} minutes
113 | Tags:
114 | - Key: Name
115 | Value: 'AWS in Action: chapter 6 (IAM role)'
116 | Outputs:
117 | InstancePublicName:
118 | Value: !Sub ${Instance.PublicDnsName}
119 | Description: Public name (connect via SSH as user ec2-user)
120 |
--------------------------------------------------------------------------------
/chapter06/ec2-yum-update.yaml:
--------------------------------------------------------------------------------
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 | VPC:
10 | Description: 'Just select the one and only default VPC'
11 | Type: 'AWS::EC2::VPC::Id'
12 | Subnet:
13 | Description: 'Just select one of the available subnets'
14 | Type: 'AWS::EC2::Subnet::Id'
15 | Mappings:
16 | RegionMap:
17 | 'ap-south-1':
18 | AMI: 'ami-2ed19c41'
19 | 'eu-west-3':
20 | AMI: 'ami-c8a017b5'
21 | 'eu-west-2':
22 | AMI: 'ami-e3051987'
23 | 'eu-west-1':
24 | AMI: 'ami-760aaa0f'
25 | 'ap-northeast-2':
26 | AMI: 'ami-fc862292'
27 | 'ap-northeast-1':
28 | AMI: 'ami-2803ac4e'
29 | 'sa-east-1':
30 | AMI: 'ami-1678037a'
31 | 'ca-central-1':
32 | AMI: 'ami-ef3b838b'
33 | 'ap-southeast-1':
34 | AMI: 'ami-dd7935be'
35 | 'ap-southeast-2':
36 | AMI: 'ami-1a668878'
37 | 'eu-central-1':
38 | AMI: 'ami-e28d098d'
39 | 'us-east-1':
40 | AMI: 'ami-6057e21a'
41 | 'us-east-2':
42 | AMI: 'ami-aa1b34cf'
43 | 'us-west-1':
44 | AMI: 'ami-1a033c7a'
45 | 'us-west-2':
46 | AMI: 'ami-32d8124a'
47 | Resources:
48 | SecurityGroup:
49 | Type: 'AWS::EC2::SecurityGroup'
50 | Properties:
51 | GroupDescription: 'My security group'
52 | VpcId: !Ref VPC
53 | SecurityGroupIngress:
54 | - CidrIp: '0.0.0.0/0'
55 | FromPort: 22
56 | IpProtocol: tcp
57 | ToPort: 22
58 | Tags:
59 | - Key: Name
60 | Value: 'AWS in Action: chapter 6 (IAM role)'
61 | Instance:
62 | Type: 'AWS::EC2::Instance'
63 | Properties:
64 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
65 | InstanceType: 't2.micro'
66 | KeyName: !Ref KeyName
67 | SecurityGroupIds:
68 | - !Ref SecurityGroup
69 | SubnetId: !Ref Subnet
70 | Tags:
71 | - Key: Name
72 | Value: 'AWS in Action: chapter 6 (yum update)'
73 | UserData:
74 | 'Fn::Base64': |
75 | #!/bin/bash -x
76 | yum -y update
77 | Outputs:
78 | InstancePublicName:
79 | Value: !Sub ${Instance.PublicDnsName}
80 | Description: Public name (connect via SSH as user ec2-user)
81 |
--------------------------------------------------------------------------------
/chapter06/firewall1.yaml:
--------------------------------------------------------------------------------
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 | VPC:
10 | Description: 'Just select the one and only default VPC'
11 | Type: 'AWS::EC2::VPC::Id'
12 | Subnet:
13 | Description: 'Just select one of the available subnets'
14 | Type: 'AWS::EC2::Subnet::Id'
15 | Mappings:
16 | RegionMap:
17 | 'ap-south-1':
18 | AMI: 'ami-2ed19c41'
19 | 'eu-west-3':
20 | AMI: 'ami-c8a017b5'
21 | 'eu-west-2':
22 | AMI: 'ami-e3051987'
23 | 'eu-west-1':
24 | AMI: 'ami-760aaa0f'
25 | 'ap-northeast-2':
26 | AMI: 'ami-fc862292'
27 | 'ap-northeast-1':
28 | AMI: 'ami-2803ac4e'
29 | 'sa-east-1':
30 | AMI: 'ami-1678037a'
31 | 'ca-central-1':
32 | AMI: 'ami-ef3b838b'
33 | 'ap-southeast-1':
34 | AMI: 'ami-dd7935be'
35 | 'ap-southeast-2':
36 | AMI: 'ami-1a668878'
37 | 'eu-central-1':
38 | AMI: 'ami-e28d098d'
39 | 'us-east-1':
40 | AMI: 'ami-6057e21a'
41 | 'us-east-2':
42 | AMI: 'ami-aa1b34cf'
43 | 'us-west-1':
44 | AMI: 'ami-1a033c7a'
45 | 'us-west-2':
46 | AMI: 'ami-32d8124a'
47 | Resources:
48 | SecurityGroup:
49 | Type: 'AWS::EC2::SecurityGroup'
50 | Properties:
51 | GroupDescription: 'Learn how to protect your EC2 Instance.'
52 | VpcId: !Ref VPC
53 | Tags:
54 | - Key: Name
55 | Value: 'AWS in Action: chapter 6 (firewall)'
56 | Instance:
57 | Type: 'AWS::EC2::Instance'
58 | Properties:
59 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
60 | InstanceType: 't2.micro'
61 | KeyName: !Ref KeyName
62 | SecurityGroupIds:
63 | - !Ref SecurityGroup
64 | SubnetId: !Ref Subnet
65 | Tags:
66 | - Key: Name
67 | Value: 'AWS in Action: chapter 6 (firewall)'
68 | Outputs:
69 | PublicName:
70 | Value: !Sub ${Instance.PublicDnsName}
71 | Description: 'Public name of EC2 Instance (connect via SSH as user ec2-user)'
72 |
--------------------------------------------------------------------------------
/chapter06/firewall2.yaml:
--------------------------------------------------------------------------------
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 | VPC:
10 | Description: 'Just select the one and only default VPC'
11 | Type: 'AWS::EC2::VPC::Id'
12 | Subnet:
13 | Description: 'Just select one of the available subnets'
14 | Type: 'AWS::EC2::Subnet::Id'
15 | Mappings:
16 | RegionMap:
17 | 'ap-south-1':
18 | AMI: 'ami-2ed19c41'
19 | 'eu-west-3':
20 | AMI: 'ami-c8a017b5'
21 | 'eu-west-2':
22 | AMI: 'ami-e3051987'
23 | 'eu-west-1':
24 | AMI: 'ami-760aaa0f'
25 | 'ap-northeast-2':
26 | AMI: 'ami-fc862292'
27 | 'ap-northeast-1':
28 | AMI: 'ami-2803ac4e'
29 | 'sa-east-1':
30 | AMI: 'ami-1678037a'
31 | 'ca-central-1':
32 | AMI: 'ami-ef3b838b'
33 | 'ap-southeast-1':
34 | AMI: 'ami-dd7935be'
35 | 'ap-southeast-2':
36 | AMI: 'ami-1a668878'
37 | 'eu-central-1':
38 | AMI: 'ami-e28d098d'
39 | 'us-east-1':
40 | AMI: 'ami-6057e21a'
41 | 'us-east-2':
42 | AMI: 'ami-aa1b34cf'
43 | 'us-west-1':
44 | AMI: 'ami-1a033c7a'
45 | 'us-west-2':
46 | AMI: 'ami-32d8124a'
47 | Resources:
48 | SecurityGroup:
49 | Type: 'AWS::EC2::SecurityGroup'
50 | Properties:
51 | GroupDescription: 'Learn how to protect your EC2 Instance.'
52 | VpcId: !Ref VPC
53 | Tags:
54 | - Key: Name
55 | Value: 'AWS in Action: chapter 6 (firewall)'
56 | # allowing inbound ICMP traffic
57 | SecurityGroupIngress:
58 | - IpProtocol: icmp
59 | FromPort: -1
60 | ToPort: -1
61 | CidrIp: '0.0.0.0/0'
62 | Instance:
63 | Type: 'AWS::EC2::Instance'
64 | Properties:
65 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
66 | InstanceType: 't2.micro'
67 | KeyName: !Ref KeyName
68 | SecurityGroupIds:
69 | - !Ref SecurityGroup
70 | SubnetId: !Ref Subnet
71 | Tags:
72 | - Key: Name
73 | Value: 'AWS in Action: chapter 6 (firewall)'
74 | Outputs:
75 | PublicName:
76 | Value: !Sub ${Instance.PublicDnsName}
77 | Description: 'Public name of EC2 Instance (connect via SSH as user ec2-user)'
78 |
--------------------------------------------------------------------------------
/chapter06/firewall3.yaml:
--------------------------------------------------------------------------------
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 | VPC:
10 | Description: 'Just select the one and only default VPC'
11 | Type: 'AWS::EC2::VPC::Id'
12 | Subnet:
13 | Description: 'Just select one of the available subnets'
14 | Type: 'AWS::EC2::Subnet::Id'
15 | Mappings:
16 | RegionMap:
17 | 'ap-south-1':
18 | AMI: 'ami-2ed19c41'
19 | 'eu-west-3':
20 | AMI: 'ami-c8a017b5'
21 | 'eu-west-2':
22 | AMI: 'ami-e3051987'
23 | 'eu-west-1':
24 | AMI: 'ami-760aaa0f'
25 | 'ap-northeast-2':
26 | AMI: 'ami-fc862292'
27 | 'ap-northeast-1':
28 | AMI: 'ami-2803ac4e'
29 | 'sa-east-1':
30 | AMI: 'ami-1678037a'
31 | 'ca-central-1':
32 | AMI: 'ami-ef3b838b'
33 | 'ap-southeast-1':
34 | AMI: 'ami-dd7935be'
35 | 'ap-southeast-2':
36 | AMI: 'ami-1a668878'
37 | 'eu-central-1':
38 | AMI: 'ami-e28d098d'
39 | 'us-east-1':
40 | AMI: 'ami-6057e21a'
41 | 'us-east-2':
42 | AMI: 'ami-aa1b34cf'
43 | 'us-west-1':
44 | AMI: 'ami-1a033c7a'
45 | 'us-west-2':
46 | AMI: 'ami-32d8124a'
47 | Resources:
48 | SecurityGroup:
49 | Type: 'AWS::EC2::SecurityGroup'
50 | Properties:
51 | GroupDescription: 'Learn how to protect your EC2 Instance.'
52 | VpcId: !Ref VPC
53 | Tags:
54 | - Key: Name
55 | Value: 'AWS in Action: chapter 6 (firewall)'
56 | # allowing inbound ICMP traffic
57 | SecurityGroupIngress:
58 | - IpProtocol: icmp
59 | FromPort: -1
60 | ToPort: -1
61 | CidrIp: '0.0.0.0/0'
62 | # allowing inbound SSH traffic
63 | - IpProtocol: tcp
64 | FromPort: 22
65 | ToPort: 22
66 | CidrIp: '0.0.0.0/0'
67 | Instance:
68 | Type: 'AWS::EC2::Instance'
69 | Properties:
70 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
71 | InstanceType: 't2.micro'
72 | KeyName: !Ref KeyName
73 | SecurityGroupIds:
74 | - !Ref SecurityGroup
75 | SubnetId: !Ref Subnet
76 | Tags:
77 | - Key: Name
78 | Value: 'AWS in Action: chapter 6 (firewall)'
79 | Outputs:
80 | PublicName:
81 | Value: !Sub ${Instance.PublicDnsName}
82 | Description: 'Public name of EC2 Instance (connect via SSH as user ec2-user)'
83 |
--------------------------------------------------------------------------------
/chapter06/firewall4.yaml:
--------------------------------------------------------------------------------
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 | VPC:
10 | Description: 'Just select the one and only default VPC'
11 | Type: 'AWS::EC2::VPC::Id'
12 | Subnet:
13 | Description: 'Just select one of the available subnets'
14 | Type: 'AWS::EC2::Subnet::Id'
15 | IpForSSH:
16 | Description: 'Your public IP address to allow SSH access'
17 | Type: String
18 | AllowedPattern: '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'
19 | ConstraintDescription: 'Enter a valid IPv4 address'
20 | Mappings:
21 | RegionMap:
22 | 'ap-south-1':
23 | AMI: 'ami-2ed19c41'
24 | 'eu-west-3':
25 | AMI: 'ami-c8a017b5'
26 | 'eu-west-2':
27 | AMI: 'ami-e3051987'
28 | 'eu-west-1':
29 | AMI: 'ami-760aaa0f'
30 | 'ap-northeast-2':
31 | AMI: 'ami-fc862292'
32 | 'ap-northeast-1':
33 | AMI: 'ami-2803ac4e'
34 | 'sa-east-1':
35 | AMI: 'ami-1678037a'
36 | 'ca-central-1':
37 | AMI: 'ami-ef3b838b'
38 | 'ap-southeast-1':
39 | AMI: 'ami-dd7935be'
40 | 'ap-southeast-2':
41 | AMI: 'ami-1a668878'
42 | 'eu-central-1':
43 | AMI: 'ami-e28d098d'
44 | 'us-east-1':
45 | AMI: 'ami-6057e21a'
46 | 'us-east-2':
47 | AMI: 'ami-aa1b34cf'
48 | 'us-west-1':
49 | AMI: 'ami-1a033c7a'
50 | 'us-west-2':
51 | AMI: 'ami-32d8124a'
52 | Resources:
53 | SecurityGroup:
54 | Type: 'AWS::EC2::SecurityGroup'
55 | Properties:
56 | GroupDescription: 'Learn how to protect your EC2 Instance.'
57 | VpcId: !Ref VPC
58 | Tags:
59 | - Key: Name
60 | Value: 'AWS in Action: chapter 6 (firewall)'
61 | # allowing inbound ICMP traffic
62 | SecurityGroupIngress:
63 | - IpProtocol: icmp
64 | FromPort: -1
65 | ToPort: -1
66 | CidrIp: '0.0.0.0/0'
67 | # allowing inbound SSH traffic
68 | - IpProtocol: tcp
69 | FromPort: 22
70 | ToPort: 22
71 | CidrIp: !Sub '${IpForSSH}/32'
72 | Instance:
73 | Type: 'AWS::EC2::Instance'
74 | Properties:
75 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
76 | InstanceType: 't2.micro'
77 | KeyName: !Ref KeyName
78 | SecurityGroupIds:
79 | - !Ref SecurityGroup
80 | SubnetId: !Ref Subnet
81 | Tags:
82 | - Key: Name
83 | Value: 'AWS in Action: chapter 6 (firewall)'
84 | Outputs:
85 | PublicName:
86 | Value: !Sub ${Instance.PublicDnsName}
87 | Description: 'Public name of EC2 Instance (connect via SSH as user ec2-user)'
88 |
--------------------------------------------------------------------------------
/chapter06/firewall5.yaml:
--------------------------------------------------------------------------------
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 | VPC:
10 | Description: 'Just select the one and only default VPC'
11 | Type: 'AWS::EC2::VPC::Id'
12 | Subnet:
13 | Description: 'Just select one of the available subnets'
14 | Type: 'AWS::EC2::Subnet::Id'
15 | IpForSSH:
16 | Description: 'Your public IP address to allow SSH access'
17 | Type: String
18 | AllowedPattern: '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'
19 | ConstraintDescription: 'Enter a valid IPv4 address'
20 | Mappings:
21 | RegionMap:
22 | 'ap-south-1':
23 | AMI: 'ami-2ed19c41'
24 | 'eu-west-3':
25 | AMI: 'ami-c8a017b5'
26 | 'eu-west-2':
27 | AMI: 'ami-e3051987'
28 | 'eu-west-1':
29 | AMI: 'ami-760aaa0f'
30 | 'ap-northeast-2':
31 | AMI: 'ami-fc862292'
32 | 'ap-northeast-1':
33 | AMI: 'ami-2803ac4e'
34 | 'sa-east-1':
35 | AMI: 'ami-1678037a'
36 | 'ca-central-1':
37 | AMI: 'ami-ef3b838b'
38 | 'ap-southeast-1':
39 | AMI: 'ami-dd7935be'
40 | 'ap-southeast-2':
41 | AMI: 'ami-1a668878'
42 | 'eu-central-1':
43 | AMI: 'ami-e28d098d'
44 | 'us-east-1':
45 | AMI: 'ami-6057e21a'
46 | 'us-east-2':
47 | AMI: 'ami-aa1b34cf'
48 | 'us-west-1':
49 | AMI: 'ami-1a033c7a'
50 | 'us-west-2':
51 | AMI: 'ami-32d8124a'
52 | Resources:
53 | SecurityGroupBastionHost:
54 | Type: 'AWS::EC2::SecurityGroup'
55 | Properties:
56 | GroupDescription: 'Allowing incoming SSH and ICPM from anywhere.'
57 | VpcId: !Ref VPC
58 | SecurityGroupIngress:
59 | - IpProtocol: icmp
60 | FromPort: -1
61 | ToPort: -1
62 | CidrIp: '0.0.0.0/0'
63 | - IpProtocol: tcp
64 | FromPort: 22
65 | ToPort: 22
66 | CidrIp: !Sub '${IpForSSH}/32'
67 | Tags:
68 | - Key: Name
69 | Value: 'Bastion Host'
70 | SecurityGroupInstance:
71 | Type: 'AWS::EC2::SecurityGroup'
72 | Properties:
73 | GroupDescription: 'Allowing incoming SSH from the Bastion Host.'
74 | VpcId: !Ref VPC
75 | SecurityGroupIngress:
76 | - IpProtocol: tcp
77 | FromPort: 22
78 | ToPort: 22
79 | SourceSecurityGroupId: !Ref SecurityGroupBastionHost
80 | Tags:
81 | - Key: Name
82 | Value: 'Instance'
83 | BastionHost:
84 | Type: 'AWS::EC2::Instance'
85 | Properties:
86 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
87 | InstanceType: 't2.micro'
88 | KeyName: !Ref KeyName
89 | SecurityGroupIds:
90 | - !Ref SecurityGroupBastionHost
91 | SubnetId: !Ref Subnet
92 | Tags:
93 | - Key: Name
94 | Value: 'Bastion Host'
95 | Instance1:
96 | Type: 'AWS::EC2::Instance'
97 | Properties:
98 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
99 | InstanceType: 't2.micro'
100 | KeyName: !Ref KeyName
101 | SecurityGroupIds:
102 | - !Ref SecurityGroupInstance
103 | SubnetId: !Ref Subnet
104 | Tags:
105 | - Key: Name
106 | Value: 'Instance 1'
107 | Instance2:
108 | Type: 'AWS::EC2::Instance'
109 | Properties:
110 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
111 | InstanceType: 't2.micro'
112 | KeyName: !Ref KeyName
113 | SecurityGroupIds:
114 | - !Ref SecurityGroupInstance
115 | SubnetId: !Ref Subnet
116 | Tags:
117 | - Key: Name
118 | Value: 'Instance 2'
119 | Outputs:
120 | BastionHostPublicName:
121 | Value: !Sub ${BastionHost.PublicDnsName}
122 | Description: 'Bastion host public name (connect via SSH as user ec2-user)'
123 | Instance1PublicName:
124 | Value: !Sub ${Instance1.PublicDnsName}
125 | Description: 'Instance1 public name'
126 | Instance2PublicName:
127 | Value: !Sub ${Instance2.PublicDnsName}
128 | Description: 'Instance2 public name'
129 |
--------------------------------------------------------------------------------
/chapter06/update.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -ex
2 |
3 | PUBLICIPADDRESSESS="$(aws ec2 describe-instances --filters "Name=instance-state-name,Values=running" --query "Reservations[].Instances[].PublicIpAddress" --output text)"
4 |
5 | for PUBLICIPADDRESS in $PUBLICIPADDRESSESS; do
6 | ssh -t "ec2-user@$PUBLICIPADDRESS" "sudo yum -y --security update"
7 | done
8 |
--------------------------------------------------------------------------------
/chapter07/lambda_function.py:
--------------------------------------------------------------------------------
1 | import boto3
2 | ec2 = boto3.client('ec2')
3 |
4 | def lambda_handler(event, context):
5 | print(event)
6 | if "/" in event['detail']['userIdentity']['arn']:
7 | userName = event['detail']['userIdentity']['arn'].split('/')[1]
8 | else:
9 | userName = event['detail']['userIdentity']['arn']
10 | instanceId = event['detail']['responseElements']['instancesSet']['items'][0]['instanceId']
11 | print("Adding owner tag " + userName + " to instance " + instanceId + ".")
12 | ec2.create_tags(Resources=[instanceId,],Tags=[{'Key': 'Owner', 'Value': userName},])
13 | return
14 |
--------------------------------------------------------------------------------
/chapter07/template.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: '2010-09-09'
3 | Transform: AWS::Serverless-2016-10-31
4 | Description: Adding an owner tag to EC2 instances automatically
5 | Parameters:
6 | CreateCloudTrail:
7 | Description: 'Create CloudTrail (set to false if CloudTrail is already enabled in your account).'
8 | Type: String
9 | Default: 'true'
10 | AllowedValues: ['true', 'false']
11 | Conditions:
12 | HasCreateCloudTrail: !Equals [!Ref CreateCloudTrail, 'true']
13 | Resources:
14 | TrailBucket:
15 | Condition: HasCreateCloudTrail
16 | DeletionPolicy: Retain
17 | UpdateReplacePolicy: Retain
18 | Type: 'AWS::S3::Bucket'
19 | Properties:
20 | BucketName: !Sub '${AWS::StackName}-${AWS::AccountId}'
21 | TrailBucketPolicy:
22 | Condition: HasCreateCloudTrail
23 | Type: 'AWS::S3::BucketPolicy'
24 | Properties:
25 | Bucket: !Ref TrailBucket
26 | PolicyDocument:
27 | Version: '2012-10-17'
28 | Statement:
29 | - Sid: AWSCloudTrailAclCheck
30 | Effect: Allow
31 | Principal:
32 | Service: 'cloudtrail.amazonaws.com'
33 | Action: 's3:GetBucketAcl'
34 | Resource: !Sub 'arn:aws:s3:::${TrailBucket}'
35 | - Sid: AWSCloudTrailWrite
36 | Effect: Allow
37 | Principal:
38 | Service: 'cloudtrail.amazonaws.com'
39 | Action: 's3:PutObject'
40 | Resource: !Sub 'arn:aws:s3:::${TrailBucket}/AWSLogs/${AWS::AccountId}/*'
41 | Condition:
42 | StringEquals:
43 | 's3:x-amz-acl': 'bucket-owner-full-control'
44 | Trail:
45 | Condition: HasCreateCloudTrail
46 | DependsOn: TrailBucketPolicy
47 | Type: 'AWS::CloudTrail::Trail'
48 | Properties:
49 | IsLogging: true
50 | IsMultiRegionTrail: false
51 | S3BucketName: !Ref TrailBucket
52 | EC2OwnerTagFunction:
53 | Type: AWS::Serverless::Function
54 | Properties:
55 | Handler: lambda_function.lambda_handler
56 | Runtime: python3.6
57 | CodeUri: '.'
58 | Policies:
59 | - Version: '2012-10-17'
60 | Statement:
61 | - Effect: Allow
62 | Action: 'ec2:CreateTags'
63 | Resource: '*'
64 | Events:
65 | CloudTrail:
66 | Type: CloudWatchEvent
67 | Properties:
68 | Pattern:
69 | detail-type:
70 | - 'AWS API Call via CloudTrail'
71 | source:
72 | - 'aws.ec2'
73 | detail:
74 | eventName:
75 | - 'RunInstances'
76 |
--------------------------------------------------------------------------------
/chapter08/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 | }
--------------------------------------------------------------------------------
/chapter08/gallery/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Simple S3 Gallery
4 |
5 |
6 | Simple S3 Gallery
7 | Upload
8 |
12 | Images
13 | {{#Objects}}
14 | 
15 | {{/Objects}}
16 |
17 |
--------------------------------------------------------------------------------
/chapter08/gallery/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "aws-sdk": "2.116.0",
4 | "mu2-updated": "0.5.21",
5 | "uuid": "3.1.0",
6 | "multiparty": "4.1.3",
7 | "express": "4.15.4"
8 | },
9 | "private": true
10 | }
11 |
--------------------------------------------------------------------------------
/chapter08/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 |
--------------------------------------------------------------------------------
/chapter08/helloworld.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hello World!
4 |
5 |
6 | Hello World!
7 |
8 |
--------------------------------------------------------------------------------
/chapter09/ebs.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: '2010-09-09'
3 | Description: 'AWS in Action: chapter 9 (EBS)'
4 | Parameters:
5 | KeyName:
6 | Description: Key Pair name
7 | Type: 'AWS::EC2::KeyPair::KeyName'
8 | Default: mykey
9 | VPC:
10 | Description: 'Just select the one and only default VPC'
11 | Type: 'AWS::EC2::VPC::Id'
12 | Subnet:
13 | Description: 'Just select one of the available subnets'
14 | Type: 'AWS::EC2::Subnet::Id'
15 | AttachVolume:
16 | Description: 'Should the volume be attached?'
17 | Type: String
18 | Default: 'yes'
19 | AllowedValues:
20 | - 'yes'
21 | - 'no'
22 | Mappings:
23 | RegionMap:
24 | 'ap-south-1':
25 | AMI: 'ami-2ed19c41'
26 | 'eu-west-3':
27 | AMI: 'ami-c8a017b5'
28 | 'eu-west-2':
29 | AMI: 'ami-e3051987'
30 | 'eu-west-1':
31 | AMI: 'ami-760aaa0f'
32 | 'ap-northeast-2':
33 | AMI: 'ami-fc862292'
34 | 'ap-northeast-1':
35 | AMI: 'ami-2803ac4e'
36 | 'sa-east-1':
37 | AMI: 'ami-1678037a'
38 | 'ca-central-1':
39 | AMI: 'ami-ef3b838b'
40 | 'ap-southeast-1':
41 | AMI: 'ami-dd7935be'
42 | 'ap-southeast-2':
43 | AMI: 'ami-1a668878'
44 | 'eu-central-1':
45 | AMI: 'ami-e28d098d'
46 | 'us-east-1':
47 | AMI: 'ami-6057e21a'
48 | 'us-east-2':
49 | AMI: 'ami-aa1b34cf'
50 | 'us-west-1':
51 | AMI: 'ami-1a033c7a'
52 | 'us-west-2':
53 | AMI: 'ami-32d8124a'
54 | Conditions:
55 | Attached: !Equals [!Ref AttachVolume, 'yes']
56 | Resources:
57 | SecurityGroup:
58 | Type: 'AWS::EC2::SecurityGroup'
59 | Properties:
60 | GroupDescription: 'Allow incoming SSH from anywhere'
61 | VpcId: !Ref VPC
62 | SecurityGroupIngress:
63 | - CidrIp: '0.0.0.0/0'
64 | FromPort: 22
65 | ToPort: 22
66 | IpProtocol: tcp
67 | Tags:
68 | - Key: Name
69 | Value: 'AWS in Action: chapter 9 (EBS)'
70 | IamRole:
71 | Type: 'AWS::IAM::Role'
72 | Properties:
73 | AssumeRolePolicyDocument:
74 | Version: '2012-10-17'
75 | Statement:
76 | - Effect: Allow
77 | Principal:
78 | Service: 'ec2.amazonaws.com'
79 | Action: 'sts:AssumeRole'
80 | Policies:
81 | - PolicyName: ec2
82 | PolicyDocument:
83 | Version: '2012-10-17'
84 | Statement:
85 | - Effect: Allow
86 | Action:
87 | - 'ec2:DescribeVolumes'
88 | - 'ec2:CreateSnapshot'
89 | - 'ec2:DescribeSnapshots'
90 | - 'ec2:DeleteSnapshot'
91 | Resource: '*'
92 | IamInstanceProfile:
93 | Type: AWS::IAM::InstanceProfile
94 | Properties:
95 | Roles:
96 | - !Ref IamRole
97 | EC2Instance:
98 | Type: 'AWS::EC2::Instance'
99 | Properties:
100 | IamInstanceProfile: !Ref IamInstanceProfile
101 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
102 | InstanceType: 't2.micro'
103 | KeyName: !Ref KeyName
104 | SecurityGroupIds:
105 | - !Ref SecurityGroup
106 | SubnetId: !Ref Subnet
107 | Tags:
108 | - Key: Name
109 | Value: 'AWS in Action: chapter 9 (EBS)'
110 | Volume:
111 | Type: 'AWS::EC2::Volume'
112 | Properties:
113 | AvailabilityZone: !Sub ${EC2Instance.AvailabilityZone}
114 | Size: 5
115 | VolumeType: gp2
116 | Tags:
117 | - Key: Name
118 | Value: 'AWS in Action: chapter 9 (EBS)'
119 | VolumeAttachment:
120 | Type: 'AWS::EC2::VolumeAttachment'
121 | Condition: Attached
122 | Properties:
123 | Device: '/dev/xvdf'
124 | InstanceId: !Ref EC2Instance
125 | VolumeId: !Ref Volume
126 | Outputs:
127 | PublicName:
128 | Value: !Sub ${EC2Instance.PublicDnsName}
129 | Description: 'Public name (connect via SSH as user ec2-user)'
130 | VolumeId:
131 | Value: !Ref Volume
132 | Description: 'ID of the EBS volume'
133 |
--------------------------------------------------------------------------------
/chapter09/instancestore.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: '2010-09-09'
3 | Description: 'AWS in Action: chapter 9 (Instance Store)'
4 | Parameters:
5 | KeyName:
6 | Description: 'Key Pair name'
7 | Type: 'AWS::EC2::KeyPair::KeyName'
8 | Default: mykey
9 | VPC:
10 | Description: 'Just select the one and only default VPC'
11 | Type: 'AWS::EC2::VPC::Id'
12 | Subnet:
13 | Description: 'Just select one of the available subnets'
14 | Type: 'AWS::EC2::Subnet::Id'
15 | Mappings:
16 | RegionMap:
17 | 'ap-south-1':
18 | AMI: 'ami-2ed19c41'
19 | 'eu-west-3':
20 | AMI: 'ami-c8a017b5'
21 | 'eu-west-2':
22 | AMI: 'ami-e3051987'
23 | 'eu-west-1':
24 | AMI: 'ami-760aaa0f'
25 | 'ap-northeast-2':
26 | AMI: 'ami-fc862292'
27 | 'ap-northeast-1':
28 | AMI: 'ami-2803ac4e'
29 | 'sa-east-1':
30 | AMI: 'ami-1678037a'
31 | 'ca-central-1':
32 | AMI: 'ami-ef3b838b'
33 | 'ap-southeast-1':
34 | AMI: 'ami-dd7935be'
35 | 'ap-southeast-2':
36 | AMI: 'ami-1a668878'
37 | 'eu-central-1':
38 | AMI: 'ami-e28d098d'
39 | 'us-east-1':
40 | AMI: 'ami-6057e21a'
41 | 'us-east-2':
42 | AMI: 'ami-aa1b34cf'
43 | 'us-west-1':
44 | AMI: 'ami-1a033c7a'
45 | 'us-west-2':
46 | AMI: 'ami-32d8124a'
47 | Resources:
48 | SecurityGroup:
49 | Type: 'AWS::EC2::SecurityGroup'
50 | Properties:
51 | GroupDescription: 'Allow incoming SSH from anywhere'
52 | VpcId: !Ref VPC
53 | SecurityGroupIngress:
54 | - CidrIp: '0.0.0.0/0'
55 | FromPort: 22
56 | ToPort: 22
57 | IpProtocol: tcp
58 | Tags:
59 | - Key: Name
60 | Value: 'AWS in Action: chapter 9 (Instance Store)'
61 | EC2Instance:
62 | Type: AWS::EC2::Instance
63 | Properties:
64 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
65 | InstanceType: 'm3.medium'
66 | KeyName: !Ref KeyName
67 | SecurityGroupIds:
68 | - !Ref SecurityGroup
69 | SubnetId: !Ref Subnet
70 | BlockDeviceMappings:
71 | - DeviceName: '/dev/xvda'
72 | Ebs:
73 | VolumeSize: 8
74 | VolumeType: gp2
75 | - DeviceName: '/dev/xvdb'
76 | VirtualName: ephemeral0
77 | Tags:
78 | - Key: Name
79 | Value: 'AWS in Action: chapter 9 (Instance Store)'
80 | Outputs:
81 | PublicName:
82 | Value: !Sub ${EC2Instance.PublicDnsName}
83 | Description: 'Public name (connect via SSH as user ec2-user)'
84 |
--------------------------------------------------------------------------------
/chapter10/template-with-backup.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: '2010-09-09'
3 | Description: 'AWS in Action: chapter 10 (with backup)'
4 | Parameters:
5 | KeyName:
6 | Description: Key Pair name
7 | Type: 'AWS::EC2::KeyPair::KeyName'
8 | Default: mykey
9 | EBSBackupVolumeSize:
10 | Description: 'The size of the EBS backup volume, in gibibytes (GiBs).'
11 | Type: Number
12 | Default: 5
13 | Mappings:
14 | RegionMap:
15 | 'ap-south-1':
16 | AMI: 'ami-2ed19c41'
17 | 'eu-west-3':
18 | AMI: 'ami-c8a017b5'
19 | 'eu-west-2':
20 | AMI: 'ami-e3051987'
21 | 'eu-west-1':
22 | AMI: 'ami-760aaa0f'
23 | 'ap-northeast-2':
24 | AMI: 'ami-fc862292'
25 | 'ap-northeast-1':
26 | AMI: 'ami-2803ac4e'
27 | 'sa-east-1':
28 | AMI: 'ami-1678037a'
29 | 'ca-central-1':
30 | AMI: 'ami-ef3b838b'
31 | 'ap-southeast-1':
32 | AMI: 'ami-dd7935be'
33 | 'ap-southeast-2':
34 | AMI: 'ami-1a668878'
35 | 'eu-central-1':
36 | AMI: 'ami-e28d098d'
37 | 'us-east-1':
38 | AMI: 'ami-6057e21a'
39 | 'us-east-2':
40 | AMI: 'ami-aa1b34cf'
41 | 'us-west-1':
42 | AMI: 'ami-1a033c7a'
43 | 'us-west-2':
44 | AMI: 'ami-32d8124a'
45 | Resources:
46 | ##########################################################################
47 | # #
48 | # VPC with two public subnets #
49 | # #
50 | ##########################################################################
51 | VPC:
52 | Type: 'AWS::EC2::VPC'
53 | Properties:
54 | CidrBlock: '172.31.0.0/16'
55 | EnableDnsHostnames: true
56 | InternetGateway:
57 | Type: 'AWS::EC2::InternetGateway'
58 | Properties: {}
59 | VPCGatewayAttachment:
60 | Type: 'AWS::EC2::VPCGatewayAttachment'
61 | Properties:
62 | VpcId: !Ref VPC
63 | InternetGatewayId: !Ref InternetGateway
64 | SubnetA:
65 | Type: 'AWS::EC2::Subnet'
66 | Properties:
67 | AvailabilityZone: !Select [0, !GetAZs '']
68 | CidrBlock: '172.31.38.0/24'
69 | VpcId: !Ref VPC
70 | SubnetB:
71 | Type: 'AWS::EC2::Subnet'
72 | Properties:
73 | AvailabilityZone: !Select [1, !GetAZs '']
74 | CidrBlock: '172.31.37.0/24'
75 | VpcId: !Ref VPC
76 | RouteTable:
77 | Type: 'AWS::EC2::RouteTable'
78 | Properties:
79 | VpcId: !Ref VPC
80 | SubnetRouteTableAssociationA:
81 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
82 | Properties:
83 | SubnetId: !Ref SubnetA
84 | RouteTableId: !Ref RouteTable
85 | SubnetRouteTableAssociationB:
86 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
87 | Properties:
88 | SubnetId: !Ref SubnetB
89 | RouteTableId: !Ref RouteTable
90 | RouteToInternet:
91 | Type: 'AWS::EC2::Route'
92 | Properties:
93 | RouteTableId: !Ref RouteTable
94 | DestinationCidrBlock: '0.0.0.0/0'
95 | GatewayId: !Ref InternetGateway
96 | DependsOn: VPCGatewayAttachment
97 | NetworkAcl:
98 | Type: AWS::EC2::NetworkAcl
99 | Properties:
100 | VpcId: !Ref VPC
101 | SubnetNetworkAclAssociationA:
102 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
103 | Properties:
104 | SubnetId: !Ref SubnetA
105 | NetworkAclId: !Ref NetworkAcl
106 | SubnetNetworkAclAssociationB:
107 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
108 | Properties:
109 | SubnetId: !Ref SubnetB
110 | NetworkAclId: !Ref NetworkAcl
111 | NetworkAclEntryIngress:
112 | Type: 'AWS::EC2::NetworkAclEntry'
113 | Properties:
114 | NetworkAclId: !Ref NetworkAcl
115 | RuleNumber: 100
116 | Protocol: -1
117 | RuleAction: allow
118 | Egress: false
119 | CidrBlock: '0.0.0.0/0'
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 | # EFS related resources #
132 | # #
133 | ##########################################################################
134 | FileSystem:
135 | Type: 'AWS::EFS::FileSystem'
136 | Properties: {}
137 | EFSClientSecurityGroup:
138 | Type: 'AWS::EC2::SecurityGroup'
139 | Properties:
140 | GroupDescription: 'EFS client'
141 | VpcId: !Ref VPC
142 | MountTargetSecurityGroup:
143 | Type: 'AWS::EC2::SecurityGroup'
144 | Properties:
145 | GroupDescription: 'EFS Mount target'
146 | SecurityGroupIngress:
147 | - FromPort: 2049
148 | IpProtocol: tcp
149 | SourceSecurityGroupId: !Ref EFSClientSecurityGroup
150 | ToPort: 2049
151 | VpcId: !Ref VPC
152 | MountTargetA:
153 | Type: 'AWS::EFS::MountTarget'
154 | Properties:
155 | FileSystemId: !Ref FileSystem
156 | SecurityGroups:
157 | - !Ref MountTargetSecurityGroup
158 | SubnetId: !Ref SubnetA
159 | MountTargetB:
160 | Type: 'AWS::EFS::MountTarget'
161 | Properties:
162 | FileSystemId: !Ref FileSystem
163 | SecurityGroups:
164 | - !Ref MountTargetSecurityGroup
165 | SubnetId: !Ref SubnetB
166 | ##########################################################################
167 | # #
168 | # EC2 instances for testing #
169 | # #
170 | ##########################################################################
171 | EC2SecurityGroup:
172 | Type: 'AWS::EC2::SecurityGroup'
173 | Properties:
174 | GroupDescription: 'EC2 instance'
175 | SecurityGroupIngress:
176 | - CidrIp: '0.0.0.0/0'
177 | FromPort: 22
178 | IpProtocol: tcp
179 | ToPort: 22
180 | VpcId: !Ref VPC
181 | InstanceProfile:
182 | Type: 'AWS::IAM::InstanceProfile'
183 | Properties:
184 | Roles:
185 | - !Ref Role
186 | Role:
187 | Type: 'AWS::IAM::Role'
188 | Properties:
189 | AssumeRolePolicyDocument:
190 | Version: '2012-10-17'
191 | Statement:
192 | - Effect: Allow
193 | Principal:
194 | Service: 'ec2.amazonaws.com'
195 | Action: 'sts:AssumeRole'
196 | Policies:
197 | - PolicyName: ec2
198 | PolicyDocument:
199 | Version: '2012-10-17'
200 | Statement:
201 | - Effect: Allow
202 | Action: 'ec2:CreateSnapshot'
203 | Resource: '*'
204 | EC2InstanceA:
205 | Type: 'AWS::EC2::Instance'
206 | Properties:
207 | IamInstanceProfile: !Ref InstanceProfile
208 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
209 | InstanceType: 't2.micro'
210 | KeyName: !Ref KeyName
211 | NetworkInterfaces:
212 | - AssociatePublicIpAddress: true
213 | DeleteOnTermination: true
214 | DeviceIndex: '0'
215 | GroupSet:
216 | - !Ref EC2SecurityGroup
217 | - !Ref EFSClientSecurityGroup
218 | SubnetId: !Ref SubnetA
219 | UserData:
220 | 'Fn::Base64': !Sub |
221 | #!/bin/bash -x
222 | bash -ex << "TRY"
223 | # wait until EFS file system is available
224 | while ! nc -z ${FileSystem}.efs.${AWS::Region}.amazonaws.com 2049; do sleep 10; done
225 | sleep 10
226 |
227 | # copy existing /home to /oldhome
228 | mkdir /oldhome
229 | cp -a /home/. /oldhome
230 |
231 | # mount EFS file system
232 | echo "${FileSystem}.efs.${AWS::Region}.amazonaws.com:/ /home nfs4 nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,_netdev 0 0" >> /etc/fstab
233 | mount -a
234 |
235 | # copy /oldhome to new /home
236 | cp -a /oldhome/. /home
237 | TRY
238 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource EC2InstanceA --region ${AWS::Region}
239 |
240 | # wait until EBS volume is attached
241 | while ! [ "`fdisk -l | grep '/dev/xvdf' | wc -l`" -ge "1" ]; do sleep 10; done
242 |
243 | # format EBS volume if needed
244 | if [[ "`file -s /dev/xvdf`" != *"ext4"* ]]; then mkfs -t ext4 /dev/xvdf; fi
245 |
246 | # mount EBS volume
247 | mkdir /mnt/backup
248 | echo "/dev/xvdf /mnt/backup ext4 defaults,nofail 0 2" >> /etc/fstab
249 | mount -a
250 |
251 | # install backup cron
252 | cat > /etc/cron.d/backup << EOF
253 | SHELL=/bin/bash
254 | PATH=/sbin:/bin:/usr/sbin:/usr/bin:/opt/aws/bin
255 | MAILTO=root
256 | HOME=/
257 | */15 * * * * root rsync -av --delete /home/ /mnt/backup/ ; fsfreeze -f /mnt/backup/ ; aws --region ${AWS::Region} ec2 create-snapshot --volume-id ${EBSBackupVolumeA} --description "EFS backup" ; fsfreeze -u /mnt/backup/
258 | EOF
259 | CreationPolicy:
260 | ResourceSignal:
261 | Timeout: PT10M
262 | DependsOn:
263 | - VPCGatewayAttachment
264 | - MountTargetA
265 | EC2InstanceB:
266 | Type: 'AWS::EC2::Instance'
267 | Properties:
268 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
269 | InstanceType: 't2.micro'
270 | KeyName: !Ref KeyName
271 | NetworkInterfaces:
272 | - AssociatePublicIpAddress: true
273 | DeleteOnTermination: true
274 | DeviceIndex: '0'
275 | GroupSet:
276 | - !Ref EC2SecurityGroup
277 | - !Ref EFSClientSecurityGroup
278 | SubnetId: !Ref SubnetB
279 | UserData:
280 | 'Fn::Base64': !Sub |
281 | #!/bin/bash -x
282 | bash -ex << "TRY"
283 | # wait until EFS file system is available
284 | while ! nc -z ${FileSystem}.efs.${AWS::Region}.amazonaws.com 2049; do sleep 10; done
285 | sleep 10
286 |
287 | # mount EFS file system
288 | echo "${FileSystem}.efs.${AWS::Region}.amazonaws.com:/ /home nfs4 nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,_netdev 0 0" >> /etc/fstab
289 | mount -a
290 | TRY
291 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource EC2InstanceB --region ${AWS::Region}
292 | CreationPolicy:
293 | ResourceSignal:
294 | Timeout: PT10M
295 | DependsOn:
296 | - VPCGatewayAttachment
297 | - MountTargetB
298 | EBSBackupVolumeA:
299 | Type: 'AWS::EC2::Volume'
300 | Properties:
301 | AvailabilityZone: !Select [0, !GetAZs '']
302 | Size: !Ref EBSBackupVolumeSize
303 | VolumeType: gp2
304 | EBSBackupVolumeAttachmentA:
305 | Type: 'AWS::EC2::VolumeAttachment'
306 | Properties:
307 | Device: '/dev/xvdf'
308 | InstanceId: !Ref EC2InstanceA
309 | VolumeId: !Ref EBSBackupVolumeA
310 | Outputs:
311 | EC2InstanceAIPAddress:
312 | Value: !GetAtt 'EC2InstanceA.PublicIp'
313 | Description: 'EC2 Instance (AZ A) public IP address (connect via SSH as user ec2-user)'
314 | EC2InstanceBIPAddress:
315 | Value: !GetAtt 'EC2InstanceB.PublicIp'
316 | Description: 'EC2 Instance (AZ B) public IP address (connect via SSH as user ec2-user)'
317 |
--------------------------------------------------------------------------------
/chapter10/template.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: '2010-09-09'
3 | Description: 'AWS in Action: chapter 10'
4 | Parameters:
5 | KeyName:
6 | Description: Key Pair name
7 | Type: 'AWS::EC2::KeyPair::KeyName'
8 | Default: mykey
9 | Mappings:
10 | RegionMap:
11 | 'ap-south-1':
12 | AMI: 'ami-2ed19c41'
13 | 'eu-west-3':
14 | AMI: 'ami-c8a017b5'
15 | 'eu-west-2':
16 | AMI: 'ami-e3051987'
17 | 'eu-west-1':
18 | AMI: 'ami-760aaa0f'
19 | 'ap-northeast-2':
20 | AMI: 'ami-fc862292'
21 | 'ap-northeast-1':
22 | AMI: 'ami-2803ac4e'
23 | 'sa-east-1':
24 | AMI: 'ami-1678037a'
25 | 'ca-central-1':
26 | AMI: 'ami-ef3b838b'
27 | 'ap-southeast-1':
28 | AMI: 'ami-dd7935be'
29 | 'ap-southeast-2':
30 | AMI: 'ami-1a668878'
31 | 'eu-central-1':
32 | AMI: 'ami-e28d098d'
33 | 'us-east-1':
34 | AMI: 'ami-6057e21a'
35 | 'us-east-2':
36 | AMI: 'ami-aa1b34cf'
37 | 'us-west-1':
38 | AMI: 'ami-1a033c7a'
39 | 'us-west-2':
40 | AMI: 'ami-32d8124a'
41 | Resources:
42 | ##########################################################################
43 | # #
44 | # VPC with two public subnets #
45 | # #
46 | ##########################################################################
47 | VPC:
48 | Type: 'AWS::EC2::VPC'
49 | Properties:
50 | CidrBlock: '172.31.0.0/16'
51 | EnableDnsHostnames: true
52 | InternetGateway:
53 | Type: 'AWS::EC2::InternetGateway'
54 | Properties: {}
55 | VPCGatewayAttachment:
56 | Type: 'AWS::EC2::VPCGatewayAttachment'
57 | Properties:
58 | VpcId: !Ref VPC
59 | InternetGatewayId: !Ref InternetGateway
60 | SubnetA:
61 | Type: 'AWS::EC2::Subnet'
62 | Properties:
63 | AvailabilityZone: !Select [0, !GetAZs '']
64 | CidrBlock: '172.31.38.0/24'
65 | VpcId: !Ref VPC
66 | SubnetB:
67 | Type: 'AWS::EC2::Subnet'
68 | Properties:
69 | AvailabilityZone: !Select [1, !GetAZs '']
70 | CidrBlock: '172.31.37.0/24'
71 | VpcId: !Ref VPC
72 | RouteTable:
73 | Type: 'AWS::EC2::RouteTable'
74 | Properties:
75 | VpcId: !Ref VPC
76 | SubnetRouteTableAssociationA:
77 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
78 | Properties:
79 | SubnetId: !Ref SubnetA
80 | RouteTableId: !Ref RouteTable
81 | SubnetRouteTableAssociationB:
82 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
83 | Properties:
84 | SubnetId: !Ref SubnetB
85 | RouteTableId: !Ref RouteTable
86 | RouteToInternet:
87 | Type: 'AWS::EC2::Route'
88 | Properties:
89 | RouteTableId: !Ref RouteTable
90 | DestinationCidrBlock: '0.0.0.0/0'
91 | GatewayId: !Ref InternetGateway
92 | DependsOn: VPCGatewayAttachment
93 | NetworkAcl:
94 | Type: AWS::EC2::NetworkAcl
95 | Properties:
96 | VpcId: !Ref VPC
97 | SubnetNetworkAclAssociationA:
98 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
99 | Properties:
100 | SubnetId: !Ref SubnetA
101 | NetworkAclId: !Ref NetworkAcl
102 | SubnetNetworkAclAssociationB:
103 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
104 | Properties:
105 | SubnetId: !Ref SubnetB
106 | NetworkAclId: !Ref NetworkAcl
107 | NetworkAclEntryIngress:
108 | Type: 'AWS::EC2::NetworkAclEntry'
109 | Properties:
110 | NetworkAclId: !Ref NetworkAcl
111 | RuleNumber: 100
112 | Protocol: -1
113 | RuleAction: allow
114 | Egress: false
115 | CidrBlock: '0.0.0.0/0'
116 | NetworkAclEntryEgress:
117 | Type: 'AWS::EC2::NetworkAclEntry'
118 | Properties:
119 | NetworkAclId: !Ref NetworkAcl
120 | RuleNumber: 100
121 | Protocol: -1
122 | RuleAction: allow
123 | Egress: true
124 | CidrBlock: 0.0.0.0/0
125 | ##########################################################################
126 | # #
127 | # EFS related resources #
128 | # #
129 | ##########################################################################
130 | FileSystem:
131 | Type: 'AWS::EFS::FileSystem'
132 | Properties: {}
133 | EFSClientSecurityGroup:
134 | Type: 'AWS::EC2::SecurityGroup'
135 | Properties:
136 | GroupDescription: 'EFS Mount target client'
137 | VpcId: !Ref VPC
138 | MountTargetSecurityGroup:
139 | Type: 'AWS::EC2::SecurityGroup'
140 | Properties:
141 | GroupDescription: 'EFS Mount target'
142 | SecurityGroupIngress:
143 | - FromPort: 2049
144 | IpProtocol: tcp
145 | SourceSecurityGroupId: !Ref EFSClientSecurityGroup
146 | ToPort: 2049
147 | VpcId: !Ref VPC
148 | MountTargetA:
149 | Type: 'AWS::EFS::MountTarget'
150 | Properties:
151 | FileSystemId: !Ref FileSystem
152 | SecurityGroups:
153 | - !Ref MountTargetSecurityGroup
154 | SubnetId: !Ref SubnetA
155 | MountTargetB:
156 | Type: 'AWS::EFS::MountTarget'
157 | Properties:
158 | FileSystemId: !Ref FileSystem
159 | SecurityGroups:
160 | - !Ref MountTargetSecurityGroup
161 | SubnetId: !Ref SubnetB
162 | ##########################################################################
163 | # #
164 | # EC2 instances for testing #
165 | # #
166 | ##########################################################################
167 | EC2SecurityGroup:
168 | Type: 'AWS::EC2::SecurityGroup'
169 | Properties:
170 | GroupDescription: 'EC2 instance'
171 | SecurityGroupIngress:
172 | - CidrIp: '0.0.0.0/0'
173 | FromPort: 22
174 | IpProtocol: tcp
175 | ToPort: 22
176 | VpcId: !Ref VPC
177 | EC2InstanceA:
178 | Type: 'AWS::EC2::Instance'
179 | Properties:
180 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
181 | InstanceType: 't2.micro'
182 | KeyName: !Ref KeyName
183 | NetworkInterfaces:
184 | - AssociatePublicIpAddress: true
185 | DeleteOnTermination: true
186 | DeviceIndex: '0'
187 | GroupSet:
188 | - !Ref EC2SecurityGroup
189 | - !Ref EFSClientSecurityGroup
190 | SubnetId: !Ref SubnetA
191 | UserData:
192 | 'Fn::Base64': !Sub |
193 | #!/bin/bash -x
194 | bash -ex << "TRY"
195 | # wait until EFS file system is available
196 | while ! nc -z ${FileSystem}.efs.${AWS::Region}.amazonaws.com 2049; do sleep 10; done
197 | sleep 10
198 |
199 | # copy existing /home to /oldhome
200 | mkdir /oldhome
201 | cp -a /home/. /oldhome
202 |
203 | # mount EFS file system
204 | echo "${FileSystem}.efs.${AWS::Region}.amazonaws.com:/ /home nfs4 nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,_netdev 0 0" >> /etc/fstab
205 | mount -a
206 |
207 | # copy /oldhome to new /home
208 | cp -a /oldhome/. /home
209 | TRY
210 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource EC2InstanceA --region ${AWS::Region}
211 | CreationPolicy:
212 | ResourceSignal:
213 | Timeout: PT10M
214 | DependsOn:
215 | - VPCGatewayAttachment
216 | - MountTargetA
217 | EC2InstanceB:
218 | Type: 'AWS::EC2::Instance'
219 | Properties:
220 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
221 | InstanceType: 't2.micro'
222 | KeyName: !Ref KeyName
223 | NetworkInterfaces:
224 | - AssociatePublicIpAddress: true
225 | DeleteOnTermination: true
226 | DeviceIndex: '0'
227 | GroupSet:
228 | - !Ref EC2SecurityGroup
229 | - !Ref EFSClientSecurityGroup
230 | SubnetId: !Ref SubnetB
231 | UserData:
232 | 'Fn::Base64': !Sub |
233 | #!/bin/bash -x
234 | bash -ex << "TRY"
235 | # wait until EFS file system is available
236 | while ! nc -z ${FileSystem}.efs.${AWS::Region}.amazonaws.com 2049; do sleep 10; done
237 | sleep 10
238 |
239 | # mount EFS file system
240 | echo "${FileSystem}.efs.${AWS::Region}.amazonaws.com:/ /home nfs4 nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,_netdev 0 0" >> /etc/fstab
241 | mount -a
242 | TRY
243 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource EC2InstanceB --region ${AWS::Region}
244 | CreationPolicy:
245 | ResourceSignal:
246 | Timeout: PT10M
247 | DependsOn:
248 | - VPCGatewayAttachment
249 | - MountTargetB
250 | Outputs:
251 | EC2InstanceAIPAddress:
252 | Value: !GetAtt 'EC2InstanceA.PublicIp'
253 | Description: 'EC2 Instance (AZ A) public IP address (connect via SSH as user ec2-user)'
254 | EC2InstanceBIPAddress:
255 | Value: !GetAtt 'EC2InstanceB.PublicIp'
256 | Description: 'EC2 Instance (AZ B) public IP address (connect via SSH as user ec2-user)'
257 |
--------------------------------------------------------------------------------
/chapter11/cleanup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -ex
2 |
3 | aws rds delete-db-instance --db-instance-identifier awsinaction-db-restore --skip-final-snapshot
4 | aws rds delete-db-instance --db-instance-identifier awsinaction-db-restore-time --skip-final-snapshot
5 | aws rds delete-db-snapshot --db-snapshot-identifier wordpress-manual-snapshot
6 | aws rds delete-db-snapshot --db-snapshot-identifier wordpress-copy-snapshot
7 | aws --region eu-west-1 rds delete-db-snapshot --db-snapshot-identifier wordpress-manual-snapshot
8 |
--------------------------------------------------------------------------------
/chapter12/minimal.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: '2010-09-09'
3 | Description: 'AWS in Action: chapter 12 (minimal)'
4 | Parameters:
5 | VPC:
6 | Description: 'Just select the one and only default VPC'
7 | Type: 'AWS::EC2::VPC::Id'
8 | SubnetA:
9 | Description: 'Just select the first of the available subnets'
10 | Type: 'AWS::EC2::Subnet::Id'
11 | SubnetB:
12 | Description: 'Just select the second of the available subnets'
13 | Type: 'AWS::EC2::Subnet::Id'
14 | KeyName:
15 | Description: 'Key Pair name'
16 | Type: 'AWS::EC2::KeyPair::KeyName'
17 | Default: mykey
18 | Mappings:
19 | RegionMap:
20 | 'ap-south-1':
21 | AMI: 'ami-2ed19c41'
22 | 'eu-west-3':
23 | AMI: 'ami-c8a017b5'
24 | 'eu-west-2':
25 | AMI: 'ami-e3051987'
26 | 'eu-west-1':
27 | AMI: 'ami-760aaa0f'
28 | 'ap-northeast-2':
29 | AMI: 'ami-fc862292'
30 | 'ap-northeast-1':
31 | AMI: 'ami-2803ac4e'
32 | 'sa-east-1':
33 | AMI: 'ami-1678037a'
34 | 'ca-central-1':
35 | AMI: 'ami-ef3b838b'
36 | 'ap-southeast-1':
37 | AMI: 'ami-dd7935be'
38 | 'ap-southeast-2':
39 | AMI: 'ami-1a668878'
40 | 'eu-central-1':
41 | AMI: 'ami-e28d098d'
42 | 'us-east-1':
43 | AMI: 'ami-6057e21a'
44 | 'us-east-2':
45 | AMI: 'ami-aa1b34cf'
46 | 'us-west-1':
47 | AMI: 'ami-1a033c7a'
48 | 'us-west-2':
49 | AMI: 'ami-32d8124a'
50 | Resources:
51 | CacheSecurityGroup:
52 | Type: 'AWS::EC2::SecurityGroup'
53 | Properties:
54 | GroupDescription: cache
55 | VpcId: !Ref VPC
56 | SecurityGroupIngress:
57 | - IpProtocol: tcp
58 | FromPort: 6379
59 | ToPort: 6379
60 | CidrIp: '0.0.0.0/0'
61 | CacheSubnetGroup:
62 | Type: 'AWS::ElastiCache::SubnetGroup'
63 | Properties:
64 | Description: cache
65 | SubnetIds:
66 | - Ref: SubnetA
67 | - Ref: SubnetB
68 | Cache:
69 | Type: 'AWS::ElastiCache::CacheCluster'
70 | Properties:
71 | CacheNodeType: 'cache.t2.micro'
72 | CacheSubnetGroupName: !Ref CacheSubnetGroup
73 | Engine: redis
74 | NumCacheNodes: 1
75 | VpcSecurityGroupIds:
76 | - !Ref CacheSecurityGroup
77 | VMSecurityGroup:
78 | Type: 'AWS::EC2::SecurityGroup'
79 | Properties:
80 | GroupDescription: 'vm'
81 | SecurityGroupIngress:
82 | - IpProtocol: tcp
83 | FromPort: 22
84 | ToPort: 22
85 | CidrIp: '0.0.0.0/0'
86 | VpcId: !Ref VPC
87 | VMInstance:
88 | Type: 'AWS::EC2::Instance'
89 | Properties:
90 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
91 | InstanceType: 't2.micro'
92 | KeyName: !Ref KeyName
93 | NetworkInterfaces:
94 | - AssociatePublicIpAddress: true
95 | DeleteOnTermination: true
96 | DeviceIndex: '0'
97 | GroupSet:
98 | - !Ref VMSecurityGroup
99 | SubnetId: !Ref SubnetA
100 | Outputs:
101 | VMInstanceIPAddress:
102 | Value: !GetAtt 'VMInstance.PublicIp'
103 | Description: 'EC2 Instance public IP address (connect via SSH as user ec2-user)'
104 | CacheAddress:
105 | Value: !GetAtt 'Cache.RedisEndpoint.Address'
106 | Description: 'Redis DNS name (resolves to a private IP address)'
107 |
--------------------------------------------------------------------------------
/chapter13/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | ---
2 | env:
3 | es6: true
4 | node: true
5 | extends: 'eslint:recommended'
6 | rules:
7 | no-console:
8 | - 'off'
9 | indent:
10 | - error
11 | - 2
12 | linebreak-style:
13 | - error
14 | - unix
15 | quotes:
16 | - error
17 | - single
18 | semi:
19 | - error
20 | - always
21 |
--------------------------------------------------------------------------------
/chapter13/.nvmrc:
--------------------------------------------------------------------------------
1 | 8
2 |
--------------------------------------------------------------------------------
/chapter13/README.md:
--------------------------------------------------------------------------------
1 | # Node TODO for AWS
2 |
3 | 
4 |
5 | Install the dependencies ...
6 |
7 | ```
8 | npm install
9 | ```
10 |
11 | ... and run nodetodo
12 |
13 | ```
14 | node index.js --help
15 | ```
16 |
17 | ## usage
18 |
19 | ### user
20 |
21 | #### add
22 |
23 | ```
24 | node index.js user-add
25 |
26 | node index.js user-add michael michael@widdix.de 0123456789
27 | ```
28 |
29 | #### remove
30 |
31 | ```
32 | node index.js user-rm
33 |
34 | node index.js user-rm michael
35 | ```
36 |
37 | #### list
38 |
39 | ```
40 | node index.js user-ls
41 | ```
42 |
43 | #### show
44 |
45 | ```
46 | node index.js user
47 |
48 | node index.js user michael
49 | ```
50 |
51 | ### task
52 |
53 | #### add
54 |
55 | ```
56 | node index.js task-add [] [--dueat=]
57 |
58 | node index.js task-add michael "plan lunch" --dueat=20150522
59 | ```
60 |
61 | #### remove
62 |
63 | ```
64 | node index.js task-rm
65 |
66 | node index.js task-rm michael 1432187491647
67 | ```
68 |
69 | #### list
70 |
71 | ```
72 | node index.js task-ls [] [--overdue|--due|--withoutdue|--futuredue|--dueafter=|--duebefore=] [--limit=] [--next=]
73 |
74 | node index.js task-ls michael
75 | ```
76 |
77 | #### mark as done
78 |
79 | ```
80 | node index.js task-done
81 |
82 | node index.js task-done michael 1432187491647
83 | ```
84 |
85 | ## schema
86 |
87 | You can use the `tables.yaml` CloudFormation template to create the DynamoDB tables, or the CLI:
88 |
89 | ### user
90 |
91 | ```
92 | 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
93 | ```
94 |
95 | #### primary key
96 |
97 | * HASH: uid
98 |
99 | #### attributes
100 |
101 | * uid: string
102 | * email: string
103 | * phone: string
104 |
105 | ### task
106 |
107 | ```
108 | 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
109 |
110 | 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}}}]'
111 | ```
112 |
113 | #### primary key
114 |
115 | * HASH: uid
116 | * RANGE: tid
117 |
118 | #### attributes
119 |
120 | * uid: string
121 | * tid: number (time stamp)
122 | * category: string (optional)
123 | * description: string
124 | * due: number (yyyymmdd)
125 | * created: number (yyyymmdd)
126 | * completed: number (yyyymmdd)
127 |
128 | ## demo
129 |
130 | ```
131 | $ node index.js user-add michael michael@widdix.de +4971537507824
132 | $ node index.js task-add michael "book flight to AWS re:Invent"
133 | $ node index.js task-add michael "revise chapter 10"
134 | $ node index.js task-ls michael
135 | $ node index.js task-done michael
136 | ```
137 |
--------------------------------------------------------------------------------
/chapter13/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 |
--------------------------------------------------------------------------------
/chapter13/index.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const docopt = require('docopt');
3 | const moment = require('moment');
4 | const AWS = require('aws-sdk');
5 | const db = new AWS.DynamoDB({
6 | region: 'us-east-1'
7 | });
8 |
9 | const cli = fs.readFileSync('./cli.txt', {encoding: 'utf8'});
10 | const input = docopt.docopt(cli, {
11 | version: '1.0',
12 | argv: process.argv.splice(2)
13 | });
14 |
15 | const getValue = (attribute, type) => {
16 | if (attribute === undefined) {
17 | return null;
18 | }
19 | return attribute[type];
20 | };
21 |
22 | const mapTaskItem = (item) => {
23 | return {
24 | tid: item.tid.N,
25 | description: item.description.S,
26 | created: item.created.N,
27 | due: getValue(item.due, 'N'),
28 | category: getValue(item.category, 'S'),
29 | completed: getValue(item.completed, 'N')
30 | };
31 | };
32 |
33 | const mapUserItem = (item) => {
34 | return {
35 | uid: item.uid.S,
36 | email: item.email.S,
37 | phone: item.phone.S
38 | };
39 | };
40 |
41 | if (input['user-add'] === true) {
42 | const params = {
43 | Item: {
44 | uid: {S: input['']},
45 | email: {S: input['']},
46 | phone: {S: input['']}
47 | },
48 | TableName: 'todo-user',
49 | ConditionExpression: 'attribute_not_exists(uid)'
50 | };
51 | db.putItem(params, (err) => {
52 | if (err) {
53 | console.error('error', err);
54 | } else {
55 | console.log('user added with uid ' + input['']);
56 | }
57 | });
58 | } else if (input['user-rm'] === true) {
59 | const params = {
60 | Key: {
61 | uid: {S: input['']}
62 | },
63 | TableName: 'todo-user'
64 | };
65 | db.deleteItem(params, (err) => {
66 | if (err) {
67 | console.error('error', err);
68 | } else {
69 | console.log('user removed with uid ' + input['']);
70 | }
71 | });
72 | } else if (input['user-ls'] === true) {
73 | const params = {
74 | TableName: 'todo-user',
75 | Limit: input['--limit']
76 | };
77 | if (input['--next'] !== null) {
78 | params.ExclusiveStartKey = {
79 | uid: {S: input['--next']}
80 | };
81 | }
82 | db.scan(params, (err, data) => {
83 | if (err) {
84 | console.error('error', err);
85 | } else {
86 | console.log('users', data.Items.map(mapUserItem));
87 | if (data.LastEvaluatedKey !== undefined) {
88 | console.log('more users available with --next=' + data.LastEvaluatedKey.uid.S);
89 | }
90 | }
91 | });
92 | } else if (input['user'] === true) {
93 | const params = {
94 | Key: {
95 | uid: {S: input['']}
96 | },
97 | TableName: 'todo-user'
98 | };
99 | db.getItem(params, (err, data) => {
100 | if (err) {
101 | console.error('error', err);
102 | } else {
103 | if (data.Item) {
104 | console.log('user with uid ' + input[''], mapUserItem(data.Item));
105 | } else {
106 | console.error('user with uid ' + input[''] + ' not found');
107 | }
108 | }
109 | });
110 | } else if (input['task-add'] === true) {
111 | const tid = Date.now();
112 | const params = {
113 | Item: {
114 | uid: {S: input['']},
115 | tid: {N: tid.toString()},
116 | description: {S: input['']},
117 | created: {N: moment(tid).format('YYYYMMDD')}
118 | },
119 | TableName: 'todo-task',
120 | ConditionExpression: 'attribute_not_exists(uid) and attribute_not_exists(tid)'
121 | };
122 | if (input['--dueat'] !== null) {
123 | params.Item.due = {N: input['--dueat']};
124 | }
125 | if (input[''] !== null) {
126 | params.Item.category = {S: input['']};
127 | }
128 | db.putItem(params, (err) => {
129 | if (err) {
130 | console.error('error', err);
131 | } else {
132 | console.log('task added with tid ' + tid);
133 | }
134 | });
135 | } else if (input['task-rm'] === true) {
136 | const params = {
137 | Key: {
138 | uid: {S: input['']},
139 | tid: {N: input['']}
140 | },
141 | TableName: 'todo-task'
142 | };
143 | db.deleteItem(params, (err) => {
144 | if (err) {
145 | console.error('error', err);
146 | } else {
147 | console.log('task removed with tid ' + input['']);
148 | }
149 | });
150 | } else if (input['task-ls'] === true) {
151 | const yyyymmdd = moment().format('YYYYMMDD');
152 | const params = {
153 | KeyConditionExpression: 'uid = :uid',
154 | ExpressionAttributeValues: {
155 | ':uid': {S: input['']}
156 | },
157 | TableName: 'todo-task',
158 | Limit: input['--limit']
159 | };
160 | if (input['--next'] !== null) {
161 | params.KeyConditionExpression += ' AND tid > :next';
162 | params.ExpressionAttributeValues[':next'] = {N: input['--next']};
163 | }
164 | if (input['--overdue'] === true) {
165 | params.FilterExpression = 'due < :yyyymmdd';
166 | params.ExpressionAttributeValues[':yyyymmdd'] = {N: yyyymmdd};
167 | } else if (input['--due'] === true) {
168 | params.FilterExpression = 'due = :yyyymmdd';
169 | params.ExpressionAttributeValues[':yyyymmdd'] = {N: yyyymmdd};
170 | } else if (input['--withoutdue'] === true) {
171 | params.FilterExpression = 'attribute_not_exists(due)';
172 | } else if (input['--futuredue'] === true) {
173 | params.FilterExpression = 'due > :yyyymmdd';
174 | params.ExpressionAttributeValues[':yyyymmdd'] = {N: yyyymmdd};
175 | } else if (input['--dueafter'] !== null) {
176 | params.FilterExpression = 'due > :yyyymmdd';
177 | params.ExpressionAttributeValues[':yyyymmdd'] = {N: input['--dueafter']};
178 | } else if (input['--duebefore'] !== null) {
179 | params.FilterExpression = 'due < :yyyymmdd';
180 | params.ExpressionAttributeValues[':yyyymmdd'] = {N: input['--duebefore']};
181 | }
182 | if (input[''] !== null) {
183 | if (params.FilterExpression === undefined) {
184 | params.FilterExpression = '';
185 | } else {
186 | params.FilterExpression += ' AND ';
187 | }
188 | params.FilterExpression += 'category = :category';
189 | params.ExpressionAttributeValues[':category'] = {S: input['']};
190 | }
191 | db.query(params, (err, data) => {
192 | if (err) {
193 | console.error('error', err);
194 | } else {
195 | console.log('tasks', data.Items.map(mapTaskItem));
196 | if (data.LastEvaluatedKey !== undefined) {
197 | console.log('more tasks available with --next=' + data.LastEvaluatedKey.tid.N);
198 | }
199 | }
200 | });
201 | } else if (input['task-la'] === true) {
202 | const yyyymmdd = moment().format('YYYYMMDD');
203 | const params = {
204 | KeyConditionExpression: 'category = :category',
205 | ExpressionAttributeValues: {
206 | ':category': {S: input['']}
207 | },
208 | TableName: 'todo-task',
209 | IndexName: 'category-index',
210 | Limit: input['--limit']
211 | };
212 | if (input['--next'] !== null) {
213 | params.KeyConditionExpression += ' AND tid > :next';
214 | params.ExpressionAttributeValues[':next'] = {N: input['--next']};
215 | }
216 | if (input['--overdue'] === true) {
217 | params.FilterExpression = 'due < :yyyymmdd';
218 | params.ExpressionAttributeValues[':yyyymmdd'] = {N: yyyymmdd};
219 | } else if (input['--due'] === true) {
220 | params.FilterExpression = 'due = :yyyymmdd';
221 | params.ExpressionAttributeValues[':yyyymmdd'] = {N: yyyymmdd};
222 | } else if (input['--withoutdue'] === true) {
223 | params.FilterExpression = 'attribute_not_exists(due)';
224 | } else if (input['--futuredue'] === true) {
225 | params.FilterExpression = 'due > :yyyymmdd';
226 | params.ExpressionAttributeValues[':yyyymmdd'] = {N: yyyymmdd};
227 | } else if (input['--dueafter'] !== null) {
228 | params.FilterExpression = 'due > :yyyymmdd';
229 | params.ExpressionAttributeValues[':yyyymmdd'] = {N: input['--dueafter']};
230 | } else if (input['--duebefore'] !== null) {
231 | params.FilterExpression = 'due < :yyyymmdd';
232 | params.ExpressionAttributeValues[':yyyymmdd'] = {N: input['--duebefore']};
233 | }
234 | db.query(params, (err, data) => {
235 | if (err) {
236 | console.error('error', err);
237 | } else {
238 | console.log('tasks', data.Items.map(mapTaskItem));
239 | if (data.LastEvaluatedKey !== undefined) {
240 | console.log('more tasks available with --next=' + data.LastEvaluatedKey.tid.N);
241 | }
242 | }
243 | });
244 | } else if (input['task-done'] === true) {
245 | const yyyymmdd = moment().format('YYYYMMDD');
246 | const params = {
247 | Key: {
248 | uid: {S: input['']},
249 | tid: {N: input['']}
250 | },
251 | UpdateExpression: 'SET completed = :yyyymmdd',
252 | ExpressionAttributeValues: {
253 | ':yyyymmdd': {N: yyyymmdd}
254 | },
255 | TableName: 'todo-task'
256 | };
257 | db.updateItem(params, (err) => {
258 | if (err) {
259 | console.error('error', err);
260 | } else {
261 | console.log('task completed with tid ' + input['']);
262 | }
263 | });
264 | }
265 |
--------------------------------------------------------------------------------
/chapter13/nodetodo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AWSinAction/code2/04250e5824f06fbfd6e174d87701ae53ce7b5289/chapter13/nodetodo.png
--------------------------------------------------------------------------------
/chapter13/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "aws-sdk": "2.369.0",
4 | "docopt": "0.6.2",
5 | "moment": "2.22.2"
6 | },
7 | "devDependencies": {
8 | "eslint": "5.9.0"
9 | },
10 | "scripts": {
11 | "test": "eslint ."
12 | },
13 | "private": true
14 | }
15 |
--------------------------------------------------------------------------------
/chapter13/tables.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: '2010-09-09'
3 | Description: 'AWS in Action: chapter 13 (DynamoDB tables with load balancing)'
4 | Resources:
5 | RoleScaling:
6 | Type: 'AWS::IAM::Role'
7 | Properties:
8 | AssumeRolePolicyDocument:
9 | Version: 2012-10-17
10 | Statement:
11 | - Effect: Allow
12 | Principal:
13 | Service: 'application-autoscaling.amazonaws.com'
14 | Action: 'sts:AssumeRole'
15 | Policies:
16 | - PolicyName: scaling
17 | PolicyDocument:
18 | Version: '2012-10-17'
19 | Statement:
20 | - Effect: Allow
21 | Action:
22 | - 'dynamodb:DescribeTable'
23 | - 'dynamodb:UpdateTable'
24 | - 'cloudwatch:PutMetricAlarm'
25 | - 'cloudwatch:DescribeAlarms'
26 | - 'cloudwatch:DeleteAlarms'
27 | Resource: '*'
28 | TableUser:
29 | Type: 'AWS::DynamoDB::Table'
30 | Properties:
31 | TableName: 'todo-user'
32 | AttributeDefinitions:
33 | - AttributeName: uid
34 | AttributeType: S
35 | KeySchema:
36 | - AttributeName: uid
37 | KeyType: HASH
38 | ProvisionedThroughput:
39 | ReadCapacityUnits: 5
40 | WriteCapacityUnits: 5
41 | TableUserWriteScalableTarget:
42 | Type: 'AWS::ApplicationAutoScaling::ScalableTarget'
43 | Properties:
44 | MaxCapacity: 20
45 | MinCapacity: 5
46 | ResourceId: !Sub 'table/${TableUser}'
47 | RoleARN: !GetAtt 'RoleScaling.Arn'
48 | ScalableDimension: 'dynamodb:table:WriteCapacityUnits'
49 | ServiceNamespace: dynamodb
50 | TableUserReadScalableTarget:
51 | Type: 'AWS::ApplicationAutoScaling::ScalableTarget'
52 | Properties:
53 | MaxCapacity: 20
54 | MinCapacity: 5
55 | ResourceId: !Sub 'table/${TableUser}'
56 | RoleARN: !GetAtt 'RoleScaling.Arn'
57 | ScalableDimension: 'dynamodb:table:ReadCapacityUnits'
58 | ServiceNamespace: dynamodb
59 | TableUserWriteScalingPolicy:
60 | Type: 'AWS::ApplicationAutoScaling::ScalingPolicy'
61 | Properties:
62 | PolicyName: TableUserWriteScalingPolicy
63 | PolicyType: TargetTrackingScaling
64 | ScalingTargetId: !Ref TableUserWriteScalableTarget
65 | TargetTrackingScalingPolicyConfiguration:
66 | TargetValue: 50.0
67 | ScaleInCooldown: 600
68 | ScaleOutCooldown: 60
69 | PredefinedMetricSpecification:
70 | PredefinedMetricType: DynamoDBWriteCapacityUtilization
71 | TableUserReadScalingPolicy:
72 | Type: 'AWS::ApplicationAutoScaling::ScalingPolicy'
73 | Properties:
74 | PolicyName: TableUserReadScalingPolicy
75 | PolicyType: TargetTrackingScaling
76 | ScalingTargetId: !Ref TableUserReadScalableTarget
77 | TargetTrackingScalingPolicyConfiguration:
78 | TargetValue: 50.0
79 | ScaleInCooldown: 600
80 | ScaleOutCooldown: 60
81 | PredefinedMetricSpecification:
82 | PredefinedMetricType: DynamoDBReadCapacityUtilization
83 | TableTask:
84 | Type: 'AWS::DynamoDB::Table'
85 | Properties:
86 | TableName: 'todo-task'
87 | AttributeDefinitions:
88 | - AttributeName: uid
89 | AttributeType: S
90 | - AttributeName: tid
91 | AttributeType: N
92 | - AttributeName: category
93 | AttributeType: S
94 | KeySchema:
95 | - AttributeName: uid
96 | KeyType: HASH
97 | - AttributeName: tid
98 | KeyType: RANGE
99 | ProvisionedThroughput:
100 | ReadCapacityUnits: 5
101 | WriteCapacityUnits: 5
102 | GlobalSecondaryIndexes:
103 | - IndexName: 'category-index'
104 | KeySchema:
105 | - AttributeName: 'category'
106 | KeyType: HASH
107 | - AttributeName: 'tid'
108 | KeyType: RANGE
109 | Projection:
110 | ProjectionType: ALL
111 | ProvisionedThroughput:
112 | ReadCapacityUnits: 5
113 | WriteCapacityUnits: 5
114 | TableTaskWriteScalableTarget:
115 | Type: 'AWS::ApplicationAutoScaling::ScalableTarget'
116 | Properties:
117 | MaxCapacity: 20
118 | MinCapacity: 5
119 | ResourceId: !Sub 'table/${TableTask}'
120 | RoleARN: !GetAtt 'RoleScaling.Arn'
121 | ScalableDimension: 'dynamodb:table:WriteCapacityUnits'
122 | ServiceNamespace: dynamodb
123 | TableTaskReadScalableTarget:
124 | Type: 'AWS::ApplicationAutoScaling::ScalableTarget'
125 | Properties:
126 | MaxCapacity: 20
127 | MinCapacity: 5
128 | ResourceId: !Sub 'table/${TableTask}'
129 | RoleARN: !GetAtt 'RoleScaling.Arn'
130 | ScalableDimension: 'dynamodb:table:ReadCapacityUnits'
131 | ServiceNamespace: dynamodb
132 | TableTaskWriteScalingPolicy:
133 | Type: 'AWS::ApplicationAutoScaling::ScalingPolicy'
134 | Properties:
135 | PolicyName: TableTaskWriteScalingPolicy
136 | PolicyType: TargetTrackingScaling
137 | ScalingTargetId: !Ref TableTaskWriteScalableTarget
138 | TargetTrackingScalingPolicyConfiguration:
139 | TargetValue: 50.0
140 | ScaleInCooldown: 600
141 | ScaleOutCooldown: 60
142 | PredefinedMetricSpecification:
143 | PredefinedMetricType: DynamoDBWriteCapacityUtilization
144 | TableTaskReadScalingPolicy:
145 | Type: 'AWS::ApplicationAutoScaling::ScalingPolicy'
146 | Properties:
147 | PolicyName: TableTaskReadScalingPolicy
148 | PolicyType: TargetTrackingScaling
149 | ScalingTargetId: !Ref TableTaskReadScalableTarget
150 | TargetTrackingScalingPolicyConfiguration:
151 | TargetValue: 50.0
152 | ScaleInCooldown: 600
153 | ScaleOutCooldown: 60
154 | PredefinedMetricSpecification:
155 | PredefinedMetricType: DynamoDBReadCapacityUtilization
156 | IndexCategoryWriteScalableTarget:
157 | Type: 'AWS::ApplicationAutoScaling::ScalableTarget'
158 | Properties:
159 | MaxCapacity: 20
160 | MinCapacity: 5
161 | ResourceId: !Sub 'table/${TableTask}/index/category-index'
162 | RoleARN: !GetAtt 'RoleScaling.Arn'
163 | ScalableDimension: 'dynamodb:index:WriteCapacityUnits'
164 | ServiceNamespace: dynamodb
165 | IndexCategoryReadScalableTarget:
166 | Type: 'AWS::ApplicationAutoScaling::ScalableTarget'
167 | Properties:
168 | MaxCapacity: 20
169 | MinCapacity: 5
170 | ResourceId: !Sub 'table/${TableTask}/index/category-index'
171 | RoleARN: !GetAtt 'RoleScaling.Arn'
172 | ScalableDimension: 'dynamodb:index:ReadCapacityUnits'
173 | ServiceNamespace: dynamodb
174 | IndexCategoryWriteScalingPolicy:
175 | Type: 'AWS::ApplicationAutoScaling::ScalingPolicy'
176 | Properties:
177 | PolicyName: IndexCategoryWriteScalingPolicy
178 | PolicyType: TargetTrackingScaling
179 | ScalingTargetId: !Ref IndexCategoryWriteScalableTarget
180 | TargetTrackingScalingPolicyConfiguration:
181 | TargetValue: 50.0
182 | ScaleInCooldown: 600
183 | ScaleOutCooldown: 60
184 | PredefinedMetricSpecification:
185 | PredefinedMetricType: DynamoDBWriteCapacityUtilization
186 | IndexCategoryReadScalingPolicy:
187 | Type: 'AWS::ApplicationAutoScaling::ScalingPolicy'
188 | Properties:
189 | PolicyName: IndexCategoryReadScalingPolicy
190 | PolicyType: TargetTrackingScaling
191 | ScalingTargetId: !Ref IndexCategoryReadScalableTarget
192 | TargetTrackingScalingPolicyConfiguration:
193 | TargetValue: 50.0
194 | ScaleInCooldown: 600
195 | ScaleOutCooldown: 60
196 | PredefinedMetricSpecification:
197 | PredefinedMetricType: DynamoDBReadCapacityUtilization
198 |
--------------------------------------------------------------------------------
/chapter14/multiaz-efs-eip.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: '2010-09-09'
3 | Description: 'AWS in Action: chapter 14 (Jenkins running in Auto Scaling Group over multiple AZs, with EFS, with EIP)'
4 | Parameters:
5 | KeyName:
6 | Description: 'Key Pair name'
7 | Type: 'AWS::EC2::KeyPair::KeyName'
8 | Default: mykey
9 | JenkinsAdminPassword:
10 | Description: 'Password for Jenkins admin user'
11 | Type: String
12 | AllowedPattern: '[a-zA-Z0-9]*'
13 | MinLength: 8
14 | MaxLength: 42
15 | Mappings:
16 | RegionMap:
17 | 'ap-south-1':
18 | AMI: 'ami-2ed19c41'
19 | 'eu-west-3':
20 | AMI: 'ami-c8a017b5'
21 | 'eu-west-2':
22 | AMI: 'ami-e3051987'
23 | 'eu-west-1':
24 | AMI: 'ami-760aaa0f'
25 | 'ap-northeast-2':
26 | AMI: 'ami-fc862292'
27 | 'ap-northeast-1':
28 | AMI: 'ami-2803ac4e'
29 | 'sa-east-1':
30 | AMI: 'ami-1678037a'
31 | 'ca-central-1':
32 | AMI: 'ami-ef3b838b'
33 | 'ap-southeast-1':
34 | AMI: 'ami-dd7935be'
35 | 'ap-southeast-2':
36 | AMI: 'ami-1a668878'
37 | 'eu-central-1':
38 | AMI: 'ami-e28d098d'
39 | 'us-east-1':
40 | AMI: 'ami-6057e21a'
41 | 'us-east-2':
42 | AMI: 'ami-aa1b34cf'
43 | 'us-west-1':
44 | AMI: 'ami-1a033c7a'
45 | 'us-west-2':
46 | AMI: 'ami-32d8124a'
47 | Resources:
48 | ##########################################################################
49 | # #
50 | # VPC with two public subnets #
51 | # #
52 | ##########################################################################
53 | VPC:
54 | Type: 'AWS::EC2::VPC'
55 | Properties:
56 | CidrBlock: '172.31.0.0/16'
57 | EnableDnsHostnames: true
58 | InternetGateway:
59 | Type: 'AWS::EC2::InternetGateway'
60 | Properties: {}
61 | VPCGatewayAttachment:
62 | Type: 'AWS::EC2::VPCGatewayAttachment'
63 | Properties:
64 | VpcId: !Ref VPC
65 | InternetGatewayId: !Ref InternetGateway
66 | SubnetA:
67 | Type: 'AWS::EC2::Subnet'
68 | Properties:
69 | AvailabilityZone: !Select [0, !GetAZs '']
70 | CidrBlock: '172.31.38.0/24'
71 | VpcId: !Ref VPC
72 | SubnetB:
73 | Type: 'AWS::EC2::Subnet'
74 | Properties:
75 | AvailabilityZone: !Select [1, !GetAZs '']
76 | CidrBlock: '172.31.37.0/24'
77 | VpcId: !Ref VPC
78 | RouteTable:
79 | Type: 'AWS::EC2::RouteTable'
80 | Properties:
81 | VpcId: !Ref VPC
82 | RouteTableAssociationA:
83 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
84 | Properties:
85 | SubnetId: !Ref SubnetA
86 | RouteTableId: !Ref RouteTable
87 | RouteTableAssociationB:
88 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
89 | Properties:
90 | SubnetId: !Ref SubnetB
91 | RouteTableId: !Ref RouteTable
92 | RoutePublicNATToInternet:
93 | Type: 'AWS::EC2::Route'
94 | Properties:
95 | RouteTableId: !Ref RouteTable
96 | DestinationCidrBlock: '0.0.0.0/0'
97 | GatewayId: !Ref InternetGateway
98 | DependsOn: VPCGatewayAttachment
99 | NetworkAcl:
100 | Type: 'AWS::EC2::NetworkAcl'
101 | Properties:
102 | VpcId: !Ref VPC
103 | SubnetNetworkAclAssociationA:
104 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
105 | Properties:
106 | SubnetId: !Ref SubnetA
107 | NetworkAclId: !Ref NetworkAcl
108 | SubnetNetworkAclAssociationB:
109 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
110 | Properties:
111 | SubnetId: !Ref SubnetB
112 | NetworkAclId: !Ref NetworkAcl
113 | NetworkAclEntryIngress:
114 | Type: 'AWS::EC2::NetworkAclEntry'
115 | Properties:
116 | NetworkAclId: !Ref NetworkAcl
117 | RuleNumber: 100
118 | Protocol: -1
119 | RuleAction: allow
120 | Egress: false
121 | CidrBlock: '0.0.0.0/0'
122 | NetworkAclEntryEgress:
123 | Type: 'AWS::EC2::NetworkAclEntry'
124 | Properties:
125 | NetworkAclId: !Ref NetworkAcl
126 | RuleNumber: 100
127 | Protocol: -1
128 | RuleAction: allow
129 | Egress: true
130 | CidrBlock: '0.0.0.0/0'
131 | ##########################################################################
132 | # #
133 | # EFS related resources #
134 | # #
135 | ##########################################################################
136 | FileSystem:
137 | Type: 'AWS::EFS::FileSystem'
138 | Properties: {}
139 | MountTargetSecurityGroup:
140 | Type: 'AWS::EC2::SecurityGroup'
141 | Properties:
142 | GroupDescription: 'EFS Mount target'
143 | SecurityGroupIngress:
144 | - FromPort: 2049
145 | IpProtocol: tcp
146 | SourceSecurityGroupId: !Ref SecurityGroup
147 | ToPort: 2049
148 | VpcId: !Ref VPC
149 | MountTargetA:
150 | Type: 'AWS::EFS::MountTarget'
151 | Properties:
152 | FileSystemId: !Ref FileSystem
153 | SecurityGroups:
154 | - !Ref MountTargetSecurityGroup
155 | SubnetId: !Ref SubnetA
156 | MountTargetB:
157 | Type: 'AWS::EFS::MountTarget'
158 | Properties:
159 | FileSystemId: !Ref FileSystem
160 | SecurityGroups:
161 | - !Ref MountTargetSecurityGroup
162 | SubnetId: !Ref SubnetB
163 | ##########################################################################
164 | # #
165 | # fixed public IP address (EIP) #
166 | # #
167 | ##########################################################################
168 | ElasticIP:
169 | Type: 'AWS::EC2::EIP'
170 | Properties:
171 | Domain: vpc
172 | DependsOn: VPCGatewayAttachment
173 | ##########################################################################
174 | # #
175 | # Auto Scaling Group running Jenkins #
176 | # #
177 | ##########################################################################
178 | IamRole:
179 | Type: 'AWS::IAM::Role'
180 | Properties:
181 | AssumeRolePolicyDocument:
182 | Version: '2012-10-17'
183 | Statement:
184 | - Effect: Allow
185 | Principal:
186 | Service: 'ec2.amazonaws.com'
187 | Action: 'sts:AssumeRole'
188 | Policies:
189 | - PolicyName: root
190 | PolicyDocument:
191 | Version: '2012-10-17'
192 | Statement:
193 | - Action: 'ec2:AssociateAddress'
194 | Resource: '*'
195 | Effect: Allow
196 | IamInstanceProfile:
197 | Type: 'AWS::IAM::InstanceProfile'
198 | Properties:
199 | Roles:
200 | - !Ref IamRole
201 | SecurityGroup:
202 | Type: 'AWS::EC2::SecurityGroup'
203 | Properties:
204 | GroupDescription: 'SecurityGroupforjenkins'
205 | VpcId: !Ref VPC
206 | Tags:
207 | - Key: Name
208 | Value: 'jenkins-multiaz-efs-eip'
209 | SecurityGroupIngress:
210 | - IpProtocol: tcp
211 | FromPort: 22
212 | ToPort: 22
213 | CidrIp: '0.0.0.0/0'
214 | - IpProtocol: tcp
215 | FromPort: 8080
216 | ToPort: 8080
217 | CidrIp: '0.0.0.0/0'
218 | - IpProtocol: icmp
219 | FromPort: -1
220 | ToPort: -1
221 | CidrIp: '0.0.0.0/0'
222 | LaunchConfiguration:
223 | Type: 'AWS::AutoScaling::LaunchConfiguration'
224 | Properties:
225 | AssociatePublicIpAddress: true
226 | IamInstanceProfile: !Ref IamInstanceProfile
227 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
228 | InstanceMonitoring: false
229 | InstanceType: 't2.micro'
230 | KeyName: !Ref KeyName
231 | SecurityGroups:
232 | - !Ref SecurityGroup
233 | UserData:
234 | 'Fn::Base64': !Sub |
235 | #!/bin/bash -x
236 | bash -ex << "TRY"
237 | # attach EIP
238 | INSTANCE_ID="$(curl -s http://169.254.169.254/latest/meta-data/instance-id)"
239 | aws --region ${AWS::Region} ec2 associate-address --instance-id $INSTANCE_ID --allocation-id ${ElasticIP.AllocationId}
240 |
241 | # install Jenkins
242 | wget -q -T 60 https://archives.jenkins-ci.org/redhat/jenkins-1.616-1.1.noarch.rpm
243 | rpm --install jenkins-1.616-1.1.noarch.rpm
244 |
245 | # wait until EFS file system is available
246 | while ! nc -z ${FileSystem}.efs.${AWS::Region}.amazonaws.com 2049; do sleep 10; done
247 | sleep 10
248 |
249 | # mount EFS file system
250 | echo "${FileSystem}.efs.${AWS::Region}.amazonaws.com:/ /var/lib/jenkins nfs4 nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,_netdev 0 0" >> /etc/fstab
251 | mount -a
252 | chown jenkins:jenkins /var/lib/jenkins/
253 |
254 | # configure Jenkins
255 | sed -i -e 's/JENKINS_ARGS=""/JENKINS_ARGS="--argumentsRealm.passwd.admin=${JenkinsAdminPassword} --argumentsRealm.roles.admin=admin"/g' /etc/sysconfig/jenkins
256 | if [ ! -f /var/lib/jenkins/config.xml ]; then
257 | echo '1.0true' > /var/lib/jenkins/config.xml
258 | chown jenkins:jenkins /var/lib/jenkins/config.xml
259 | fi
260 |
261 | # start jenkins
262 | service jenkins start
263 | TRY
264 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource AutoScalingGroup --region ${AWS::Region}
265 | AutoScalingGroup:
266 | Type: 'AWS::AutoScaling::AutoScalingGroup'
267 | Properties:
268 | LaunchConfigurationName: !Ref LaunchConfiguration
269 | Tags:
270 | - Key: Name
271 | Value: 'jenkins-multiaz-efs-eip'
272 | PropagateAtLaunch: true
273 | MinSize: '1'
274 | MaxSize: '1'
275 | VPCZoneIdentifier:
276 | - !Ref SubnetA
277 | - !Ref SubnetB
278 | HealthCheckGracePeriod: 600
279 | HealthCheckType: EC2
280 | CreationPolicy:
281 | ResourceSignal:
282 | Timeout: PT10M
283 | DependsOn: VPCGatewayAttachment
284 | Outputs:
285 | JenkinsURL:
286 | Description: 'URL to access web interface of Jenkins server.'
287 | Value: !Sub 'http://${ElasticIP}:8080'
288 | User:
289 | Description: 'Administrator user for Jenkins.'
290 | Value: admin
291 | Password:
292 | Description: 'Password for Jenkins administrator user.'
293 | Value: !Ref JenkinsAdminPassword
294 |
--------------------------------------------------------------------------------
/chapter14/multiaz-efs.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: '2010-09-09'
3 | Description: 'AWS in Action: chapter 14 (Jenkins running in Auto Scaling Group over multiple AZs, with EFS)'
4 | Parameters:
5 | KeyName:
6 | Description: 'Key Pair name'
7 | Type: 'AWS::EC2::KeyPair::KeyName'
8 | Default: mykey
9 | JenkinsAdminPassword:
10 | Description: 'Password for Jenkins admin user'
11 | Type: String
12 | AllowedPattern: '[a-zA-Z0-9]*'
13 | MinLength: 8
14 | MaxLength: 42
15 | Mappings:
16 | RegionMap:
17 | 'ap-south-1':
18 | AMI: 'ami-2ed19c41'
19 | 'eu-west-3':
20 | AMI: 'ami-c8a017b5'
21 | 'eu-west-2':
22 | AMI: 'ami-e3051987'
23 | 'eu-west-1':
24 | AMI: 'ami-760aaa0f'
25 | 'ap-northeast-2':
26 | AMI: 'ami-fc862292'
27 | 'ap-northeast-1':
28 | AMI: 'ami-2803ac4e'
29 | 'sa-east-1':
30 | AMI: 'ami-1678037a'
31 | 'ca-central-1':
32 | AMI: 'ami-ef3b838b'
33 | 'ap-southeast-1':
34 | AMI: 'ami-dd7935be'
35 | 'ap-southeast-2':
36 | AMI: 'ami-1a668878'
37 | 'eu-central-1':
38 | AMI: 'ami-e28d098d'
39 | 'us-east-1':
40 | AMI: 'ami-6057e21a'
41 | 'us-east-2':
42 | AMI: 'ami-aa1b34cf'
43 | 'us-west-1':
44 | AMI: 'ami-1a033c7a'
45 | 'us-west-2':
46 | AMI: 'ami-32d8124a'
47 | Resources:
48 | ##########################################################################
49 | # #
50 | # VPC with two public subnets #
51 | # #
52 | ##########################################################################
53 | VPC:
54 | Type: 'AWS::EC2::VPC'
55 | Properties:
56 | CidrBlock: '172.31.0.0/16'
57 | EnableDnsHostnames: true
58 | InternetGateway:
59 | Type: 'AWS::EC2::InternetGateway'
60 | Properties: {}
61 | VPCGatewayAttachment:
62 | Type: 'AWS::EC2::VPCGatewayAttachment'
63 | Properties:
64 | VpcId: !Ref VPC
65 | InternetGatewayId: !Ref InternetGateway
66 | SubnetA:
67 | Type: 'AWS::EC2::Subnet'
68 | Properties:
69 | AvailabilityZone: !Select [0, !GetAZs '']
70 | CidrBlock: '172.31.38.0/24'
71 | VpcId: !Ref VPC
72 | SubnetB:
73 | Type: 'AWS::EC2::Subnet'
74 | Properties:
75 | AvailabilityZone: !Select [1, !GetAZs '']
76 | CidrBlock: '172.31.37.0/24'
77 | VpcId: !Ref VPC
78 | RouteTable:
79 | Type: 'AWS::EC2::RouteTable'
80 | Properties:
81 | VpcId: !Ref VPC
82 | RouteTableAssociationA:
83 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
84 | Properties:
85 | SubnetId: !Ref SubnetA
86 | RouteTableId: !Ref RouteTable
87 | RouteTableAssociationB:
88 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
89 | Properties:
90 | SubnetId: !Ref SubnetB
91 | RouteTableId: !Ref RouteTable
92 | RoutePublicNATToInternet:
93 | Type: 'AWS::EC2::Route'
94 | Properties:
95 | RouteTableId: !Ref RouteTable
96 | DestinationCidrBlock: '0.0.0.0/0'
97 | GatewayId: !Ref InternetGateway
98 | DependsOn: VPCGatewayAttachment
99 | NetworkAcl:
100 | Type: 'AWS::EC2::NetworkAcl'
101 | Properties:
102 | VpcId: !Ref VPC
103 | SubnetNetworkAclAssociationA:
104 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
105 | Properties:
106 | SubnetId: !Ref SubnetA
107 | NetworkAclId: !Ref NetworkAcl
108 | SubnetNetworkAclAssociationB:
109 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
110 | Properties:
111 | SubnetId: !Ref SubnetB
112 | NetworkAclId: !Ref NetworkAcl
113 | NetworkAclEntryIngress:
114 | Type: 'AWS::EC2::NetworkAclEntry'
115 | Properties:
116 | NetworkAclId: !Ref NetworkAcl
117 | RuleNumber: 100
118 | Protocol: -1
119 | RuleAction: allow
120 | Egress: false
121 | CidrBlock: '0.0.0.0/0'
122 | NetworkAclEntryEgress:
123 | Type: 'AWS::EC2::NetworkAclEntry'
124 | Properties:
125 | NetworkAclId: !Ref NetworkAcl
126 | RuleNumber: 100
127 | Protocol: -1
128 | RuleAction: allow
129 | Egress: true
130 | CidrBlock: '0.0.0.0/0'
131 | ##########################################################################
132 | # #
133 | # EFS related resources #
134 | # #
135 | ##########################################################################
136 | FileSystem:
137 | Type: 'AWS::EFS::FileSystem'
138 | Properties: {}
139 | MountTargetSecurityGroup:
140 | Type: 'AWS::EC2::SecurityGroup'
141 | Properties:
142 | GroupDescription: 'EFS Mount target'
143 | SecurityGroupIngress:
144 | - FromPort: 2049
145 | IpProtocol: tcp
146 | SourceSecurityGroupId: !Ref SecurityGroup
147 | ToPort: 2049
148 | VpcId: !Ref VPC
149 | MountTargetA:
150 | Type: 'AWS::EFS::MountTarget'
151 | Properties:
152 | FileSystemId: !Ref FileSystem
153 | SecurityGroups:
154 | - !Ref MountTargetSecurityGroup
155 | SubnetId: !Ref SubnetA
156 | MountTargetB:
157 | Type: 'AWS::EFS::MountTarget'
158 | Properties:
159 | FileSystemId: !Ref FileSystem
160 | SecurityGroups:
161 | - !Ref MountTargetSecurityGroup
162 | SubnetId: !Ref SubnetB
163 | ##########################################################################
164 | # #
165 | # Auto Scaling Group running Jenkins #
166 | # #
167 | ##########################################################################
168 | SecurityGroup:
169 | Type: 'AWS::EC2::SecurityGroup'
170 | Properties:
171 | GroupDescription: 'SecurityGroupforjenkins'
172 | VpcId: !Ref VPC
173 | Tags:
174 | - Key: Name
175 | Value: 'jenkins-multiaz-efs'
176 | SecurityGroupIngress:
177 | - IpProtocol: tcp
178 | FromPort: 22
179 | ToPort: 22
180 | CidrIp: '0.0.0.0/0'
181 | - IpProtocol: tcp
182 | FromPort: 8080
183 | ToPort: 8080
184 | CidrIp: '0.0.0.0/0'
185 | - IpProtocol: icmp
186 | FromPort: -1
187 | ToPort: -1
188 | CidrIp: '0.0.0.0/0'
189 | LaunchConfiguration:
190 | Type: 'AWS::AutoScaling::LaunchConfiguration'
191 | Properties:
192 | AssociatePublicIpAddress: true
193 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
194 | InstanceMonitoring: false
195 | InstanceType: 't2.micro'
196 | KeyName: !Ref KeyName
197 | SecurityGroups:
198 | - !Ref SecurityGroup
199 | UserData:
200 | 'Fn::Base64': !Sub |
201 | #!/bin/bash -x
202 | bash -ex << "TRY"
203 | # install Jenkins
204 | wget -q -T 60 https://archives.jenkins-ci.org/redhat/jenkins-1.616-1.1.noarch.rpm
205 | rpm --install jenkins-1.616-1.1.noarch.rpm
206 |
207 | # wait until EFS file system is available
208 | while ! nc -z ${FileSystem}.efs.${AWS::Region}.amazonaws.com 2049; do sleep 10; done
209 | sleep 10
210 |
211 | # mount EFS file system
212 | echo "${FileSystem}.efs.${AWS::Region}.amazonaws.com:/ /var/lib/jenkins nfs4 nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,_netdev 0 0" >> /etc/fstab
213 | mount -a
214 | chown jenkins:jenkins /var/lib/jenkins/
215 |
216 | # configure Jenkins
217 | sed -i -e 's/JENKINS_ARGS=""/JENKINS_ARGS="--argumentsRealm.passwd.admin=${JenkinsAdminPassword} --argumentsRealm.roles.admin=admin"/g' /etc/sysconfig/jenkins
218 | if [ ! -f /var/lib/jenkins/config.xml ]; then
219 | echo '1.0true' > /var/lib/jenkins/config.xml
220 | chown jenkins:jenkins /var/lib/jenkins/config.xml
221 | fi
222 |
223 | # start jenkins
224 | service jenkins start
225 | TRY
226 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource AutoScalingGroup --region ${AWS::Region}
227 | AutoScalingGroup:
228 | Type: 'AWS::AutoScaling::AutoScalingGroup'
229 | Properties:
230 | LaunchConfigurationName: !Ref LaunchConfiguration
231 | Tags:
232 | - Key: Name
233 | Value: 'jenkins-multiaz-efs'
234 | PropagateAtLaunch: true
235 | MinSize: '1'
236 | MaxSize: '1'
237 | VPCZoneIdentifier:
238 | - !Ref SubnetA
239 | - !Ref SubnetB
240 | HealthCheckGracePeriod: 600
241 | HealthCheckType: EC2
242 | CreationPolicy:
243 | ResourceSignal:
244 | Timeout: PT10M
245 | DependsOn: VPCGatewayAttachment
246 | Outputs:
247 | User:
248 | Description: 'Administrator user for Jenkins.'
249 | Value: admin
250 | Password:
251 | Description: 'Password for Jenkins administrator user.'
252 | Value: !Ref JenkinsAdminPassword
253 |
--------------------------------------------------------------------------------
/chapter14/multiaz.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: '2010-09-09'
3 | Description: 'AWS in Action: chapter 14 (Jenkins running in 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 | JenkinsAdminPassword:
10 | Description: 'Password for Jenkins admin user'
11 | Type: String
12 | AllowedPattern: '[a-zA-Z0-9]*'
13 | MinLength: 8
14 | MaxLength: 42
15 | Mappings:
16 | RegionMap:
17 | 'ap-south-1':
18 | AMI: 'ami-2ed19c41'
19 | 'eu-west-3':
20 | AMI: 'ami-c8a017b5'
21 | 'eu-west-2':
22 | AMI: 'ami-e3051987'
23 | 'eu-west-1':
24 | AMI: 'ami-760aaa0f'
25 | 'ap-northeast-2':
26 | AMI: 'ami-fc862292'
27 | 'ap-northeast-1':
28 | AMI: 'ami-2803ac4e'
29 | 'sa-east-1':
30 | AMI: 'ami-1678037a'
31 | 'ca-central-1':
32 | AMI: 'ami-ef3b838b'
33 | 'ap-southeast-1':
34 | AMI: 'ami-dd7935be'
35 | 'ap-southeast-2':
36 | AMI: 'ami-1a668878'
37 | 'eu-central-1':
38 | AMI: 'ami-e28d098d'
39 | 'us-east-1':
40 | AMI: 'ami-6057e21a'
41 | 'us-east-2':
42 | AMI: 'ami-aa1b34cf'
43 | 'us-west-1':
44 | AMI: 'ami-1a033c7a'
45 | 'us-west-2':
46 | AMI: 'ami-32d8124a'
47 | Resources:
48 | ##########################################################################
49 | # #
50 | # VPC with two public subnets #
51 | # #
52 | ##########################################################################
53 | VPC:
54 | Type: 'AWS::EC2::VPC'
55 | Properties:
56 | CidrBlock: '172.31.0.0/16'
57 | EnableDnsHostnames: true
58 | InternetGateway:
59 | Type: 'AWS::EC2::InternetGateway'
60 | Properties: {}
61 | VPCGatewayAttachment:
62 | Type: 'AWS::EC2::VPCGatewayAttachment'
63 | Properties:
64 | VpcId: !Ref VPC
65 | InternetGatewayId: !Ref InternetGateway
66 | SubnetA:
67 | Type: 'AWS::EC2::Subnet'
68 | Properties:
69 | AvailabilityZone: !Select [0, !GetAZs '']
70 | CidrBlock: '172.31.38.0/24'
71 | VpcId: !Ref VPC
72 | SubnetB:
73 | Type: 'AWS::EC2::Subnet'
74 | Properties:
75 | AvailabilityZone: !Select [1, !GetAZs '']
76 | CidrBlock: '172.31.37.0/24'
77 | VpcId: !Ref VPC
78 | RouteTable:
79 | Type: 'AWS::EC2::RouteTable'
80 | Properties:
81 | VpcId: !Ref VPC
82 | RouteTableAssociationA:
83 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
84 | Properties:
85 | SubnetId: !Ref SubnetA
86 | RouteTableId: !Ref RouteTable
87 | RouteTableAssociationB:
88 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
89 | Properties:
90 | SubnetId: !Ref SubnetB
91 | RouteTableId: !Ref RouteTable
92 | RoutePublicNATToInternet:
93 | Type: 'AWS::EC2::Route'
94 | Properties:
95 | RouteTableId: !Ref RouteTable
96 | DestinationCidrBlock: '0.0.0.0/0'
97 | GatewayId: !Ref InternetGateway
98 | DependsOn: VPCGatewayAttachment
99 | NetworkAcl:
100 | Type: 'AWS::EC2::NetworkAcl'
101 | Properties:
102 | VpcId: !Ref VPC
103 | SubnetNetworkAclAssociationA:
104 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
105 | Properties:
106 | SubnetId: !Ref SubnetA
107 | NetworkAclId: !Ref NetworkAcl
108 | SubnetNetworkAclAssociationB:
109 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
110 | Properties:
111 | SubnetId: !Ref SubnetB
112 | NetworkAclId: !Ref NetworkAcl
113 | NetworkAclEntryIngress:
114 | Type: 'AWS::EC2::NetworkAclEntry'
115 | Properties:
116 | NetworkAclId: !Ref NetworkAcl
117 | RuleNumber: 100
118 | Protocol: -1
119 | RuleAction: allow
120 | Egress: false
121 | CidrBlock: '0.0.0.0/0'
122 | NetworkAclEntryEgress:
123 | Type: 'AWS::EC2::NetworkAclEntry'
124 | Properties:
125 | NetworkAclId: !Ref NetworkAcl
126 | RuleNumber: 100
127 | Protocol: -1
128 | RuleAction: allow
129 | Egress: true
130 | CidrBlock: '0.0.0.0/0'
131 | ##########################################################################
132 | # #
133 | # Auto Scaling Group running Jenkins #
134 | # #
135 | ##########################################################################
136 | SecurityGroup:
137 | Type: 'AWS::EC2::SecurityGroup'
138 | Properties:
139 | GroupDescription: 'SecurityGroupforjenkins'
140 | VpcId: !Ref VPC
141 | Tags:
142 | - Key: Name
143 | Value: 'jenkins-multiaz'
144 | SecurityGroupIngress:
145 | - IpProtocol: tcp
146 | FromPort: 22
147 | ToPort: 22
148 | CidrIp: '0.0.0.0/0'
149 | - IpProtocol: tcp
150 | FromPort: 8080
151 | ToPort: 8080
152 | CidrIp: '0.0.0.0/0'
153 | - IpProtocol: icmp
154 | FromPort: -1
155 | ToPort: -1
156 | CidrIp: '0.0.0.0/0'
157 | LaunchConfiguration:
158 | Type: 'AWS::AutoScaling::LaunchConfiguration'
159 | Properties:
160 | AssociatePublicIpAddress: true
161 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
162 | InstanceMonitoring: false
163 | InstanceType: 't2.micro'
164 | KeyName: !Ref KeyName
165 | SecurityGroups:
166 | - !Ref SecurityGroup
167 | UserData:
168 | 'Fn::Base64': !Sub |
169 | #!/bin/bash -x
170 | bash -ex << "TRY"
171 | # install Jenkins
172 | wget -q -T 60 https://archives.jenkins-ci.org/redhat/jenkins-1.616-1.1.noarch.rpm
173 | rpm --install jenkins-1.616-1.1.noarch.rpm
174 |
175 | # configure Jenkins
176 | sed -i -e 's/JENKINS_ARGS=""/JENKINS_ARGS="--argumentsRealm.passwd.admin=${JenkinsAdminPassword} --argumentsRealm.roles.admin=admin"/g' /etc/sysconfig/jenkins
177 | if [ ! -f /var/lib/jenkins/config.xml ]; then
178 | echo '1.0true' > /var/lib/jenkins/config.xml
179 | chown jenkins:jenkins /var/lib/jenkins/config.xml
180 | fi
181 |
182 | # start jenkins
183 | service jenkins start
184 | TRY
185 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource AutoScalingGroup --region ${AWS::Region}
186 | AutoScalingGroup:
187 | Type: 'AWS::AutoScaling::AutoScalingGroup'
188 | Properties:
189 | LaunchConfigurationName: !Ref LaunchConfiguration
190 | Tags:
191 | - Key: Name
192 | Value: 'jenkins-multiaz'
193 | PropagateAtLaunch: true
194 | MinSize: '1'
195 | MaxSize: '1'
196 | VPCZoneIdentifier:
197 | - !Ref SubnetA
198 | - !Ref SubnetB
199 | HealthCheckGracePeriod: 600
200 | HealthCheckType: EC2
201 | CreationPolicy:
202 | ResourceSignal:
203 | Timeout: PT10M
204 | DependsOn: VPCGatewayAttachment
205 | Outputs:
206 | User:
207 | Description: 'Administrator user for Jenkins.'
208 | Value: admin
209 | Password:
210 | Description: 'Password for Jenkins administrator user.'
211 | Value: !Ref JenkinsAdminPassword
212 |
--------------------------------------------------------------------------------
/chapter14/recovery.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: '2010-09-09'
3 | Description: 'AWS in Action: chapter 14 (Jenkins running on single EC2 instance with AWS CloudWatch recovery)'
4 | Parameters:
5 | KeyName:
6 | Description: 'Key Pair name'
7 | Type: 'AWS::EC2::KeyPair::KeyName'
8 | Default: mykey
9 | JenkinsAdminPassword:
10 | Description: 'Password for Jenkins admin user'
11 | Type: String
12 | AllowedPattern: '[a-zA-Z0-9]*'
13 | MinLength: 8
14 | MaxLength: 42
15 | Mappings:
16 | RegionMap:
17 | 'ap-south-1':
18 | AMI: 'ami-2ed19c41'
19 | 'eu-west-3':
20 | AMI: 'ami-c8a017b5'
21 | 'eu-west-2':
22 | AMI: 'ami-e3051987'
23 | 'eu-west-1':
24 | AMI: 'ami-760aaa0f'
25 | 'ap-northeast-2':
26 | AMI: 'ami-fc862292'
27 | 'ap-northeast-1':
28 | AMI: 'ami-2803ac4e'
29 | 'sa-east-1':
30 | AMI: 'ami-1678037a'
31 | 'ca-central-1':
32 | AMI: 'ami-ef3b838b'
33 | 'ap-southeast-1':
34 | AMI: 'ami-dd7935be'
35 | 'ap-southeast-2':
36 | AMI: 'ami-1a668878'
37 | 'eu-central-1':
38 | AMI: 'ami-e28d098d'
39 | 'us-east-1':
40 | AMI: 'ami-6057e21a'
41 | 'us-east-2':
42 | AMI: 'ami-aa1b34cf'
43 | 'us-west-1':
44 | AMI: 'ami-1a033c7a'
45 | 'us-west-2':
46 | AMI: 'ami-32d8124a'
47 | Resources:
48 | ##########################################################################
49 | # #
50 | # VPC with one public subnet #
51 | # #
52 | ##########################################################################
53 | VPC:
54 | Type: 'AWS::EC2::VPC'
55 | Properties:
56 | CidrBlock: '172.31.0.0/16'
57 | EnableDnsHostnames: true
58 | InternetGateway:
59 | Type: 'AWS::EC2::InternetGateway'
60 | Properties: {}
61 | VPCGatewayAttachment:
62 | Type: 'AWS::EC2::VPCGatewayAttachment'
63 | Properties:
64 | VpcId: !Ref VPC
65 | InternetGatewayId: !Ref InternetGateway
66 | Subnet:
67 | Type: 'AWS::EC2::Subnet'
68 | Properties:
69 | AvailabilityZone: !Select [0, !GetAZs '']
70 | CidrBlock: '172.31.38.0/24'
71 | VpcId: !Ref VPC
72 | RouteTable:
73 | Type: 'AWS::EC2::RouteTable'
74 | Properties:
75 | VpcId: !Ref VPC
76 | RouteTableAssociation:
77 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
78 | Properties:
79 | SubnetId: !Ref Subnet
80 | RouteTableId: !Ref RouteTable
81 | RoutePublicNATToInternet:
82 | Type: 'AWS::EC2::Route'
83 | Properties:
84 | RouteTableId: !Ref RouteTable
85 | DestinationCidrBlock: '0.0.0.0/0'
86 | GatewayId: !Ref InternetGateway
87 | DependsOn: VPCGatewayAttachment
88 | NetworkAcl:
89 | Type: 'AWS::EC2::NetworkAcl'
90 | Properties:
91 | VpcId: !Ref VPC
92 | SubnetNetworkAclAssociation:
93 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
94 | Properties:
95 | SubnetId: !Ref Subnet
96 | NetworkAclId: !Ref NetworkAcl
97 | NetworkAclEntryIngress:
98 | Type: 'AWS::EC2::NetworkAclEntry'
99 | Properties:
100 | NetworkAclId: !Ref NetworkAcl
101 | RuleNumber: 100
102 | Protocol: -1
103 | RuleAction: allow
104 | Egress: false
105 | CidrBlock: '0.0.0.0/0'
106 | NetworkAclEntryEgress:
107 | Type: 'AWS::EC2::NetworkAclEntry'
108 | Properties:
109 | NetworkAclId: !Ref NetworkAcl
110 | RuleNumber: 100
111 | Protocol: -1
112 | RuleAction: allow
113 | Egress: true
114 | CidrBlock: '0.0.0.0/0'
115 | SecurityGroup:
116 | Type: 'AWS::EC2::SecurityGroup'
117 | Properties:
118 | GroupDescription: 'SecurityGroupforjenkins'
119 | VpcId: !Ref VPC
120 | Tags:
121 | - Key: Name
122 | Value: 'jenkins-multiaz'
123 | SecurityGroupIngress:
124 | - IpProtocol: tcp
125 | FromPort: 22
126 | ToPort: 22
127 | CidrIp: '0.0.0.0/0'
128 | - IpProtocol: tcp
129 | FromPort: 8080
130 | ToPort: 8080
131 | CidrIp: '0.0.0.0/0'
132 | - IpProtocol: icmp
133 | FromPort: -1
134 | ToPort: -1
135 | CidrIp: '0.0.0.0/0'
136 | ##########################################################################
137 | # #
138 | # fixed public IP address (EIP) #
139 | # #
140 | ##########################################################################
141 | ElasticIP:
142 | Type: 'AWS::EC2::EIP'
143 | Properties:
144 | InstanceId: !Ref VM
145 | Domain: vpc
146 | DependsOn: VPCGatewayAttachment
147 | ##########################################################################
148 | # #
149 | # EC2 instance running Jenkins #
150 | # #
151 | ##########################################################################
152 | VM:
153 | Type: 'AWS::EC2::Instance'
154 | Properties:
155 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
156 | InstanceType: 't2.micro'
157 | KeyName: !Ref KeyName
158 | NetworkInterfaces:
159 | - AssociatePublicIpAddress: true
160 | DeleteOnTermination: true
161 | DeviceIndex: '0'
162 | GroupSet:
163 | - !Ref SecurityGroup
164 | SubnetId: !Ref Subnet
165 | UserData:
166 | 'Fn::Base64': !Sub |
167 | #!/bin/bash -x
168 | bash -ex << "TRY"
169 | # install Jenkins
170 | wget -q -T 60 https://archives.jenkins-ci.org/redhat/jenkins-1.616-1.1.noarch.rpm
171 | rpm --install jenkins-1.616-1.1.noarch.rpm
172 |
173 | # configure Jenkins
174 | sed -i -e 's/JENKINS_ARGS=""/JENKINS_ARGS="--argumentsRealm.passwd.admin=${JenkinsAdminPassword} --argumentsRealm.roles.admin=admin"/g' /etc/sysconfig/jenkins
175 | if [ ! -f /var/lib/jenkins/config.xml ]; then
176 | echo '1.0true' > /var/lib/jenkins/config.xml
177 | chown jenkins:jenkins /var/lib/jenkins/config.xml
178 | fi
179 |
180 | # start jenkins
181 | service jenkins start
182 | TRY
183 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource VM --region ${AWS::Region}
184 | Tags:
185 | - Key: Name
186 | Value: 'jenkins-recovery'
187 | CreationPolicy:
188 | ResourceSignal:
189 | Timeout: PT10M
190 | DependsOn: VPCGatewayAttachment
191 | RecoveryAlarm:
192 | Type: 'AWS::CloudWatch::Alarm'
193 | Properties:
194 | AlarmDescription: 'Recover EC2 instance when underlying hardware fails.'
195 | Namespace: 'AWS/EC2'
196 | MetricName: 'StatusCheckFailed_System'
197 | Statistic: Maximum
198 | Period: 60
199 | EvaluationPeriods: 5
200 | ComparisonOperator: GreaterThanThreshold
201 | Threshold: 0
202 | AlarmActions:
203 | - !Sub 'arn:aws:automate:${AWS::Region}:ec2:recover'
204 | Dimensions:
205 | - Name: InstanceId
206 | Value: !Ref VM
207 | Outputs:
208 | JenkinsURL:
209 | Description: 'URL to access web interface of Jenkins server.'
210 | Value: !Sub 'http://${ElasticIP}:8080'
211 | User:
212 | Description: 'Administrator user for Jenkins.'
213 | Value: admin
214 | Password:
215 | Description: 'Password for Jenkins administrator user.'
216 | Value: !Ref JenkinsAdminPassword
217 |
--------------------------------------------------------------------------------
/chapter15/loadbalancer.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: '2010-09-09'
3 | Description: 'AWS in Action: chapter 15 (Load Balancer)'
4 | Parameters:
5 | KeyName:
6 | Description: 'Key Pair name'
7 | Type: 'AWS::EC2::KeyPair::KeyName'
8 | Default: mykey
9 | NumberOfVirtualMachines:
10 | Description: 'Number of virtual machines'
11 | Type: Number
12 | Default: 2
13 | MinValue: 2
14 | MaxValue: 4
15 | Mappings:
16 | RegionMap:
17 | 'ap-south-1':
18 | AMI: 'ami-2ed19c41'
19 | 'eu-west-3':
20 | AMI: 'ami-c8a017b5'
21 | 'eu-west-2':
22 | AMI: 'ami-e3051987'
23 | 'eu-west-1':
24 | AMI: 'ami-760aaa0f'
25 | 'ap-northeast-2':
26 | AMI: 'ami-fc862292'
27 | 'ap-northeast-1':
28 | AMI: 'ami-2803ac4e'
29 | 'sa-east-1':
30 | AMI: 'ami-1678037a'
31 | 'ca-central-1':
32 | AMI: 'ami-ef3b838b'
33 | 'ap-southeast-1':
34 | AMI: 'ami-dd7935be'
35 | 'ap-southeast-2':
36 | AMI: 'ami-1a668878'
37 | 'eu-central-1':
38 | AMI: 'ami-e28d098d'
39 | 'us-east-1':
40 | AMI: 'ami-6057e21a'
41 | 'us-east-2':
42 | AMI: 'ami-aa1b34cf'
43 | 'us-west-1':
44 | AMI: 'ami-1a033c7a'
45 | 'us-west-2':
46 | AMI: 'ami-32d8124a'
47 | Resources:
48 | VPC:
49 | Type: 'AWS::EC2::VPC'
50 | Properties:
51 | CidrBlock: '172.31.0.0/16'
52 | EnableDnsHostnames: true
53 | InternetGateway:
54 | Type: 'AWS::EC2::InternetGateway'
55 | Properties: {}
56 | VPCGatewayAttachment:
57 | Type: 'AWS::EC2::VPCGatewayAttachment'
58 | Properties:
59 | VpcId: !Ref VPC
60 | InternetGatewayId: !Ref InternetGateway
61 | SubnetA:
62 | Type: 'AWS::EC2::Subnet'
63 | Properties:
64 | AvailabilityZone: !Select [0, !GetAZs '']
65 | CidrBlock: '172.31.38.0/24'
66 | VpcId: !Ref VPC
67 | SubnetB:
68 | Type: 'AWS::EC2::Subnet'
69 | Properties:
70 | AvailabilityZone: !Select [1, !GetAZs '']
71 | CidrBlock: '172.31.37.0/24'
72 | VpcId: !Ref VPC
73 | RouteTable:
74 | Type: 'AWS::EC2::RouteTable'
75 | Properties:
76 | VpcId: !Ref VPC
77 | RouteTableAssociationA:
78 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
79 | Properties:
80 | SubnetId: !Ref SubnetA
81 | RouteTableId: !Ref RouteTable
82 | RouteTableAssociationB:
83 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
84 | Properties:
85 | SubnetId: !Ref SubnetB
86 | RouteTableId: !Ref RouteTable
87 | RoutePublicNATToInternet:
88 | Type: 'AWS::EC2::Route'
89 | Properties:
90 | RouteTableId: !Ref RouteTable
91 | DestinationCidrBlock: '0.0.0.0/0'
92 | GatewayId: !Ref InternetGateway
93 | DependsOn: VPCGatewayAttachment
94 | NetworkAcl:
95 | Type: 'AWS::EC2::NetworkAcl'
96 | Properties:
97 | VpcId: !Ref VPC
98 | SubnetNetworkAclAssociationA:
99 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
100 | Properties:
101 | SubnetId: !Ref SubnetA
102 | NetworkAclId: !Ref NetworkAcl
103 | SubnetNetworkAclAssociationB:
104 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
105 | Properties:
106 | SubnetId: !Ref SubnetB
107 | NetworkAclId: !Ref NetworkAcl
108 | NetworkAclEntryIngress:
109 | Type: 'AWS::EC2::NetworkAclEntry'
110 | Properties:
111 | NetworkAclId: !Ref NetworkAcl
112 | RuleNumber: 100
113 | Protocol: -1
114 | RuleAction: allow
115 | Egress: false
116 | CidrBlock: '0.0.0.0/0'
117 | NetworkAclEntryEgress:
118 | Type: 'AWS::EC2::NetworkAclEntry'
119 | Properties:
120 | NetworkAclId: !Ref NetworkAcl
121 | RuleNumber: 100
122 | Protocol: -1
123 | RuleAction: allow
124 | Egress: true
125 | CidrBlock: '0.0.0.0/0'
126 | LoadBalancerSecurityGroup:
127 | Type: 'AWS::EC2::SecurityGroup'
128 | Properties:
129 | GroupDescription: 'alb-sg'
130 | VpcId: !Ref VPC
131 | SecurityGroupIngress:
132 | - CidrIp: '0.0.0.0/0'
133 | FromPort: 80
134 | IpProtocol: tcp
135 | ToPort: 80
136 | LoadBalancer:
137 | Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
138 | Properties:
139 | Scheme: 'internet-facing'
140 | SecurityGroups:
141 | - !Ref LoadBalancerSecurityGroup
142 | Subnets:
143 | - !Ref SubnetA
144 | - !Ref SubnetB
145 | Type: application
146 | DependsOn: 'VPCGatewayAttachment'
147 | Listener:
148 | Type: 'AWS::ElasticLoadBalancingV2::Listener'
149 | Properties:
150 | DefaultActions:
151 | - TargetGroupArn: !Ref TargetGroup
152 | Type: forward
153 | LoadBalancerArn: !Ref LoadBalancer
154 | Port: 80
155 | Protocol: HTTP
156 | TargetGroup:
157 | Type: 'AWS::ElasticLoadBalancingV2::TargetGroup'
158 | Properties:
159 | HealthCheckIntervalSeconds: 10
160 | HealthCheckPath: '/index.html'
161 | HealthCheckProtocol: HTTP
162 | HealthCheckTimeoutSeconds: 5
163 | HealthyThresholdCount: 3
164 | UnhealthyThresholdCount: 2
165 | Matcher:
166 | HttpCode: '200-299'
167 | Port: 80
168 | Protocol: HTTP
169 | VpcId: !Ref VPC
170 | WebServerSecurityGroup:
171 | Type: 'AWS::EC2::SecurityGroup'
172 | Properties:
173 | GroupDescription: 'awsinaction-sg'
174 | VpcId: !Ref VPC
175 | SecurityGroupIngress:
176 | - CidrIp: '0.0.0.0/0'
177 | FromPort: 22
178 | IpProtocol: tcp
179 | ToPort: 22
180 | - SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup
181 | FromPort: 80
182 | IpProtocol: tcp
183 | ToPort: 80
184 | LaunchConfiguration:
185 | Type: 'AWS::AutoScaling::LaunchConfiguration'
186 | Metadata:
187 | 'AWS::CloudFormation::Init':
188 | config:
189 | packages:
190 | yum:
191 | httpd: []
192 | files:
193 | '/tmp/config':
194 | content: |
195 | #!/bin/bash -ex
196 | PRIVATE_IP=`curl -s http://169.254.169.254/latest/meta-data/local-ipv4`
197 | echo "$PRIVATE_IP$PRIVATE_IP
" > index.html
198 | mode: '000500'
199 | owner: root
200 | group: root
201 | commands:
202 | '01_config':
203 | command: '/tmp/config'
204 | cwd: '/var/www/html'
205 | services:
206 | sysvinit:
207 | httpd:
208 | enabled: true
209 | ensureRunning: true
210 | Properties:
211 | AssociatePublicIpAddress: true
212 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
213 | InstanceMonitoring: false
214 | InstanceType: 't2.micro'
215 | SecurityGroups:
216 | - !Ref WebServerSecurityGroup
217 | KeyName: !Ref KeyName
218 | UserData:
219 | 'Fn::Base64': !Sub |
220 | #!/bin/bash -x
221 | /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchConfiguration --region ${AWS::Region}
222 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource AutoScalingGroup --region ${AWS::Region}
223 | AutoScalingGroup:
224 | Type: 'AWS::AutoScaling::AutoScalingGroup'
225 | Properties:
226 | LaunchConfigurationName: !Ref LaunchConfiguration
227 | MinSize: !Ref NumberOfVirtualMachines
228 | MaxSize: !Ref NumberOfVirtualMachines
229 | TargetGroupARNs:
230 | - !Ref TargetGroup
231 | VPCZoneIdentifier:
232 | - !Ref SubnetA
233 | - !Ref SubnetB
234 | CreationPolicy:
235 | ResourceSignal:
236 | Timeout: 'PT10M'
237 | DependsOn: 'VPCGatewayAttachment'
238 | Outputs:
239 | URL:
240 | Value: !Sub 'http://${LoadBalancer.DNSName}'
241 | Description: 'Load Balancer URL'
242 |
--------------------------------------------------------------------------------
/chapter15/url2png/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | ---
2 | env:
3 | es6: true
4 | node: true
5 | extends: 'eslint:recommended'
6 | rules:
7 | no-console:
8 | - 'off'
9 | indent:
10 | - error
11 | - 2
12 | linebreak-style:
13 | - error
14 | - unix
15 | quotes:
16 | - error
17 | - single
18 | semi:
19 | - error
20 | - always
21 |
--------------------------------------------------------------------------------
/chapter15/url2png/.nvmrc:
--------------------------------------------------------------------------------
1 | 8
2 |
--------------------------------------------------------------------------------
/chapter15/url2png/README.md:
--------------------------------------------------------------------------------
1 | # URL2PNG
2 |
3 | 
4 |
5 | Install the dependencies ...
6 |
7 | ```
8 | $ npm install
9 | ```
10 |
11 | ... and create a S3 bucket
12 |
13 | ```
14 | $ aws s3 mb s3://url2png
15 | ```
16 |
17 | ... and activate web hosting for bucket
18 |
19 | ```
20 | $ aws s3 website s3://url2png --index-document index.html --error-document error.html
21 | ```
22 |
23 | ... and create a SQS message queue with the help of the AWS CLI
24 |
25 | ```
26 | $ aws sqs create-queue --queue-name url2png
27 | {
28 | "QueueUrl": "https://queue.amazonaws.com/878533158213/url2png"
29 | }
30 | ```
31 |
32 | ... edit config.json and set QueueUrl and Bucket
33 |
34 | ... and run the URL2PNG worker
35 |
36 | ```
37 | $ node worker.js
38 | ```
39 |
40 | ... open another terminal and start a URL2PNG process
41 |
42 | ```
43 | $ node index.js "http://aws.amazon.com/"
44 | PNG will be soon available at http://aws-in-action-url2png.s3-website-us-east-1.amazonaws.com/6dbe4a05-82b3-4cbd-bd2b-65bbc8a51539.png
45 | ```
46 |
47 | ... wait and open the image
48 |
--------------------------------------------------------------------------------
/chapter15/url2png/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "QueueUrl": "https://queue.amazonaws.com/123456789/url2png",
3 | "Bucket": "url2png-$yourname"
4 | }
5 |
--------------------------------------------------------------------------------
/chapter15/url2png/index.js:
--------------------------------------------------------------------------------
1 | const AWS = require('aws-sdk');
2 | const uuid = require('uuid/v4');
3 | const config = require('./config.json');
4 | const 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 | const id = uuid();
14 | const body = {
15 | id: id,
16 | url: process.argv[2]
17 | };
18 |
19 | sqs.sendMessage({
20 | MessageBody: JSON.stringify(body),
21 | QueueUrl: config.QueueUrl
22 | }, (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 |
--------------------------------------------------------------------------------
/chapter15/url2png/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "aws-sdk": "2.369.0",
4 | "uuid": "3.3.2",
5 | "node-webshot": "1.0.3"
6 | },
7 | "devDependencies": {
8 | "eslint": "5.9.0"
9 | },
10 | "scripts": {
11 | "test": "eslint ."
12 | },
13 | "private": true
14 | }
15 |
--------------------------------------------------------------------------------
/chapter15/url2png/url2png.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AWSinAction/code2/04250e5824f06fbfd6e174d87701ae53ce7b5289/chapter15/url2png/url2png.png
--------------------------------------------------------------------------------
/chapter15/url2png/worker.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const AWS = require('aws-sdk');
3 | const webshot = require('node-webshot');
4 | const config = require('./config.json');
5 | const sqs = new AWS.SQS({
6 | region: 'us-east-1'
7 | });
8 | const s3 = new AWS.S3({
9 | region: 'us-east-1'
10 | });
11 |
12 | const acknowledge = (message, cb) => {
13 | const params = {
14 | QueueUrl: config.QueueUrl,
15 | ReceiptHandle: message.ReceiptHandle
16 | };
17 | sqs.deleteMessage(params, cb);
18 | };
19 |
20 | const process = (message, cb) => {
21 | const body = JSON.parse(message.Body);
22 | const file = body.id + '.png';
23 | webshot(body.url, file, (err) => {
24 | if (err) {
25 | cb(err);
26 | } else {
27 | fs.readFile(file, (err, buf) => {
28 | if (err) {
29 | cb(err);
30 | } else {
31 | const params = {
32 | Bucket: config.Bucket,
33 | Key: file,
34 | ACL: 'public-read',
35 | ContentType: 'image/png',
36 | Body: buf
37 | };
38 | s3.putObject(params, (err) => {
39 | if (err) {
40 | cb(err);
41 | } else {
42 | fs.unlink(file, cb);
43 | }
44 | });
45 | }
46 | });
47 | }
48 | });
49 | };
50 |
51 | const receive = (cb) => {
52 | const params = {
53 | QueueUrl: config.QueueUrl,
54 | MaxNumberOfMessages: 1,
55 | VisibilityTimeout: 120,
56 | WaitTimeSeconds: 10
57 | };
58 | sqs.receiveMessage(params, (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 | const run = () => {
72 | receive((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, (err) => {
82 | if (err) {
83 | throw err;
84 | } else {
85 | acknowledge(message, (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 |
--------------------------------------------------------------------------------
/chapter16/README.md:
--------------------------------------------------------------------------------
1 | # Imagery
2 |
3 | 
4 |
5 | To try Imagery app create a CloudFormation stack based on the template https://s3.amazonaws.com/awsinaction-code2/chapter16/template.yaml
6 |
--------------------------------------------------------------------------------
/chapter16/build/server.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AWSinAction/code2/04250e5824f06fbfd6e174d87701ae53ce7b5289/chapter16/build/server.zip
--------------------------------------------------------------------------------
/chapter16/build/worker.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AWSinAction/code2/04250e5824f06fbfd6e174d87701ae53ce7b5289/chapter16/build/worker.zip
--------------------------------------------------------------------------------
/chapter16/bundle.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -ex
2 |
3 | rm -rf build/
4 | mkdir -p build/
5 |
6 | (
7 | cd worker/
8 | zip -r ../build/worker.zip lib.js package.json worker.js .ebextensions/
9 | )
10 |
11 | (
12 | cd server/
13 | zip -r ../build/server.zip lib.js package.json server.js public/
14 | )
15 |
--------------------------------------------------------------------------------
/chapter16/imagery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AWSinAction/code2/04250e5824f06fbfd6e174d87701ae53ce7b5289/chapter16/imagery.png
--------------------------------------------------------------------------------
/chapter16/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 |
--------------------------------------------------------------------------------
/chapter16/server/lib.js:
--------------------------------------------------------------------------------
1 | ../lib.js
--------------------------------------------------------------------------------
/chapter16/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "aws-sdk": "2.169.0",
4 | "express": "4.16.2",
5 | "body-parser": "1.18.2",
6 | "multiparty": "4.1.3",
7 | "uuid": "3.1.0",
8 | "assert-plus": "1.0.0"
9 | },
10 | "private": true
11 | }
12 |
--------------------------------------------------------------------------------
/chapter16/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 |
--------------------------------------------------------------------------------
/chapter16/server/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Imagery | AWS in Action: chapter 16
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
Upload
16 |
17 |
24 |
25 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/chapter16/server/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var bodyParser = require('body-parser');
3 | var AWS = require('aws-sdk');
4 | var uuidv4 = require('uuid/v4');
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.redirect('/#view=' + image.id);
100 | response.end();
101 | }
102 | });
103 | }
104 | });
105 | }
106 | });
107 | }
108 |
109 | app.post('/image', function(request, response) {
110 | var id = uuidv4();
111 | db.putItem({
112 | 'Item': {
113 | 'id': {
114 | 'S': id
115 | },
116 | 'version': {
117 | 'N': '0'
118 | },
119 | 'created': {
120 | 'N': Date.now().toString()
121 | },
122 | 'state': {
123 | 'S': 'created'
124 | }
125 | },
126 | 'TableName': 'imagery-image',
127 | 'ConditionExpression': 'attribute_not_exists(id)'
128 | }, function(err, data) {
129 | if (err) {
130 | throw err;
131 | } else {
132 | response.json({'id': id, 'state': 'created'});
133 | }
134 | });
135 | });
136 |
137 | app.get('/image/:id', function(request, response) {
138 | getImage(request.params.id, function(err, image) {
139 | if (err) {
140 | throw err;
141 | } else {
142 | response.json(image);
143 | }
144 | });
145 | });
146 |
147 | app.post('/image/:id/upload', function(request, response) {
148 | getImage(request.params.id, function(err, image) {
149 | if (err) {
150 | throw err;
151 | } else {
152 | var form = new multiparty.Form();
153 | form.on('part', function(part) {
154 | uploadImage(image, part, response);
155 | });
156 | form.parse(request);
157 | }
158 | });
159 | });
160 |
161 | app.listen(process.env.PORT || 8080, function() {
162 | console.log('Server started. Open http://localhost:' + (process.env.PORT || 8080) + ' with browser.');
163 | });
164 |
--------------------------------------------------------------------------------
/chapter16/template.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: '2010-09-09'
3 | Description: 'AWS in Action: chapter 16'
4 | Parameters:
5 | KeyName:
6 | Description: 'Key Pair name'
7 | Type: 'AWS::EC2::KeyPair::KeyName'
8 | Default: mykey
9 | Resources:
10 | Bucket:
11 | Type: 'AWS::S3::Bucket'
12 | Properties:
13 | BucketName: !Sub 'imagery-${AWS::AccountId}'
14 | WebsiteConfiguration:
15 | ErrorDocument: error.html
16 | IndexDocument: index.html
17 | Table:
18 | Type: 'AWS::DynamoDB::Table'
19 | Properties:
20 | AttributeDefinitions:
21 | - AttributeName: id
22 | AttributeType: S
23 | KeySchema:
24 | - AttributeName: id
25 | KeyType: HASH
26 | ProvisionedThroughput:
27 | ReadCapacityUnits: 1
28 | WriteCapacityUnits: 1
29 | TableName: 'imagery-image'
30 | SQSDLQueue:
31 | Type: 'AWS::SQS::Queue'
32 | Properties:
33 | QueueName: 'message-dlq'
34 | SQSQueue:
35 | Type: 'AWS::SQS::Queue'
36 | Properties:
37 | QueueName: message
38 | RedrivePolicy:
39 | deadLetterTargetArn: !Sub '${SQSDLQueue.Arn}'
40 | maxReceiveCount: 10
41 | WorkerInstanceProfile:
42 | Type: 'AWS::IAM::InstanceProfile'
43 | Properties:
44 | Roles:
45 | - !Ref WorkerRole
46 | WorkerRole:
47 | Type: 'AWS::IAM::Role'
48 | Properties:
49 | AssumeRolePolicyDocument:
50 | Version: '2012-10-17'
51 | Statement:
52 | - Effect: Allow
53 | Principal:
54 | Service:
55 | - 'ec2.amazonaws.com'
56 | Action:
57 | - 'sts:AssumeRole'
58 | Policies:
59 | - PolicyName: sqs
60 | PolicyDocument:
61 | Version: '2012-10-17'
62 | Statement:
63 | - Effect: Allow
64 | Action:
65 | - 'sqs:ChangeMessageVisibility'
66 | - 'sqs:DeleteMessage'
67 | - 'sqs:ReceiveMessage'
68 | Resource: !Sub '${SQSQueue.Arn}'
69 | - PolicyName: cloudwatch
70 | PolicyDocument:
71 | Version: '2012-10-17'
72 | Statement:
73 | - Effect: Allow
74 | Action:
75 | - 'cloudwatch:PutMetricData'
76 | Resource: '*'
77 | - PolicyName: 's3-elasticbeanstalk'
78 | PolicyDocument:
79 | Version: '2012-10-17'
80 | Statement:
81 | - Effect: Allow
82 | Action:
83 | - 's3:Get*'
84 | - 's3:List*'
85 | - 's3:PutObject'
86 | Resource:
87 | - !Sub 'arn:aws:s3:::elasticbeanstalk-*-${AWS::AccountId}/*'
88 | - !Sub 'arn:aws:s3:::elasticbeanstalk-*-${AWS::AccountId}-*/*'
89 | - PolicyName: 's3-image'
90 | PolicyDocument:
91 | Version: '2012-10-17'
92 | Statement:
93 | - Effect: Allow
94 | Action:
95 | - 's3:GetObject'
96 | - 's3:PutObject'
97 | - 's3:PutObjectAcl'
98 | Resource: !Sub 'arn:aws:s3:::${Bucket}/*'
99 | - PolicyName: dynamodb
100 | PolicyDocument:
101 | Version: '2012-10-17'
102 | Statement:
103 | - Effect: Allow
104 | Action:
105 | - 'dynamodb:GetItem'
106 | - 'dynamodb:UpdateItem'
107 | Resource: !Sub 'arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${Table}'
108 | EBWorkerConfigurationTemplate:
109 | Type: 'AWS::ElasticBeanstalk::ConfigurationTemplate'
110 | Properties:
111 | ApplicationName: !Ref EBWorkerApplication
112 | Description: 'Imagery worker: AWS in Action: chapter 16'
113 | SolutionStackName: '64bit Amazon Linux 2018.03 v4.17.9 running Node.js'
114 | OptionSettings:
115 | - Namespace: 'aws:autoscaling:launchconfiguration'
116 | OptionName: 'EC2KeyName'
117 | Value: !Ref KeyName
118 | - Namespace: 'aws:autoscaling:launchconfiguration'
119 | OptionName: 'IamInstanceProfile'
120 | Value: !Ref WorkerInstanceProfile
121 | - Namespace: 'aws:elasticbeanstalk:sqsd'
122 | OptionName: 'WorkerQueueURL'
123 | Value: !Ref SQSQueue
124 | - Namespace: 'aws:elasticbeanstalk:sqsd'
125 | OptionName: 'HttpPath'
126 | Value: '/sqs'
127 | - Namespace: 'aws:elasticbeanstalk:container:nodejs'
128 | OptionName: 'NodeCommand'
129 | Value: 'node worker.js'
130 | - Namespace: 'aws:elasticbeanstalk:application:environment'
131 | OptionName: 'ImageQueue'
132 | Value: !Ref SQSQueue
133 | - Namespace: 'aws:elasticbeanstalk:application:environment'
134 | OptionName: 'ImageBucket'
135 | Value: !Ref Bucket
136 | EBWorkerEnvironment:
137 | Type: 'AWS::ElasticBeanstalk::Environment'
138 | Properties:
139 | ApplicationName: !Ref EBWorkerApplication
140 | Description: 'Imagery worker: AWS in Action: chapter 16'
141 | TemplateName: !Ref EBWorkerConfigurationTemplate
142 | VersionLabel: !Ref EBWorkerApplicationVersion
143 | Tier:
144 | Type: 'SQS/HTTP'
145 | Name: 'Worker'
146 | Version: '1.0'
147 | EBWorkerApplication:
148 | Type: 'AWS::ElasticBeanstalk::Application'
149 | Properties:
150 | ApplicationName: 'imagery-worker'
151 | Description: 'Imagery worker: AWS in Action: chapter 16'
152 | EBWorkerApplicationVersion:
153 | Type: 'AWS::ElasticBeanstalk::ApplicationVersion'
154 | Properties:
155 | ApplicationName: !Ref EBWorkerApplication
156 | Description: 'Imagery worker: AWS in Action: chapter 16'
157 | SourceBundle:
158 | S3Bucket: 'awsinaction-code2'
159 | S3Key: 'chapter16/build/worker.zip'
160 | ServerInstanceProfile:
161 | Type: 'AWS::IAM::InstanceProfile'
162 | Properties:
163 | Roles:
164 | - !Ref ServerRole
165 | ServerRole:
166 | Type: 'AWS::IAM::Role'
167 | Properties:
168 | AssumeRolePolicyDocument:
169 | Version: '2012-10-17'
170 | Statement:
171 | - Effect: Allow
172 | Principal:
173 | Service:
174 | - 'ec2.amazonaws.com'
175 | Action:
176 | - 'sts:AssumeRole'
177 | Policies:
178 | - PolicyName: sqs
179 | PolicyDocument:
180 | Version: '2012-10-17'
181 | Statement:
182 | - Effect: Allow
183 | Action: 'sqs:SendMessage'
184 | Resource: !Sub '${SQSQueue.Arn}'
185 | - PolicyName: cloudwatch
186 | PolicyDocument:
187 | Version: '2012-10-17'
188 | Statement:
189 | - Effect: Allow
190 | Action: 'cloudwatch:PutMetricData'
191 | Resource: '*'
192 | - PolicyName: 's3-elasticbeanstalk'
193 | PolicyDocument:
194 | Version: '2012-10-17'
195 | Statement:
196 | - Effect: Allow
197 | Action:
198 | - 's3:Get*'
199 | - 's3:List*'
200 | - 's3:PutObject'
201 | Resource:
202 | - !Sub 'arn:aws:s3:::elasticbeanstalk-*-${AWS::AccountId}/*'
203 | - !Sub 'arn:aws:s3:::elasticbeanstalk-*-${AWS::AccountId}-*/*'
204 | - PolicyName: 's3-image'
205 | PolicyDocument:
206 | Version: '2012-10-17'
207 | Statement:
208 | - Effect: Allow
209 | Action: 's3:PutObject'
210 | Resource: !Sub 'arn:aws:s3:::${Bucket}/*'
211 | - PolicyName: dynamodb
212 | PolicyDocument:
213 | Version: '2012-10-17'
214 | Statement:
215 | - Effect: Allow
216 | Action:
217 | - 'dynamodb:GetItem'
218 | - 'dynamodb:PutItem'
219 | - 'dynamodb:UpdateItem'
220 | Resource: !Sub 'arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${Table}'
221 | EBServerConfigurationTemplate:
222 | Type: 'AWS::ElasticBeanstalk::ConfigurationTemplate'
223 | Properties:
224 | ApplicationName: !Ref EBServerApplication
225 | Description: 'Imagery server: AWS in Action: chapter 16'
226 | SolutionStackName: '64bit Amazon Linux 2018.03 v4.17.9 running Node.js'
227 | OptionSettings:
228 | - Namespace: 'aws:autoscaling:asg'
229 | OptionName: 'MinSize'
230 | Value: '2'
231 | - Namespace: 'aws:autoscaling:launchconfiguration'
232 | OptionName: 'EC2KeyName'
233 | Value: !Ref KeyName
234 | - Namespace: 'aws:autoscaling:launchconfiguration'
235 | OptionName: 'IamInstanceProfile'
236 | Value: !Ref ServerInstanceProfile
237 | - Namespace: 'aws:elasticbeanstalk:container:nodejs'
238 | OptionName: 'NodeCommand'
239 | Value: 'node server.js'
240 | - Namespace: 'aws:elasticbeanstalk:application:environment'
241 | OptionName: 'ImageQueue'
242 | Value: !Ref SQSQueue
243 | - Namespace: 'aws:elasticbeanstalk:application:environment'
244 | OptionName: 'ImageBucket'
245 | Value: !Ref Bucket
246 | - Namespace: 'aws:elasticbeanstalk:container:nodejs:staticfiles'
247 | OptionName: '/public'
248 | Value: '/public'
249 | EBServerEnvironment:
250 | Type: 'AWS::ElasticBeanstalk::Environment'
251 | Properties:
252 | ApplicationName: !Ref EBServerApplication
253 | Description: 'Imagery server: AWS in Action: chapter 16'
254 | TemplateName: !Ref EBServerConfigurationTemplate
255 | VersionLabel: !Ref EBServerApplicationVersion
256 | EBServerApplication:
257 | Type: 'AWS::ElasticBeanstalk::Application'
258 | Properties:
259 | ApplicationName: 'imagery-server'
260 | Description: 'Imagery server: AWS in Action: chapter 16'
261 | EBServerApplicationVersion:
262 | Type: 'AWS::ElasticBeanstalk::ApplicationVersion'
263 | Properties:
264 | ApplicationName: !Ref EBServerApplication
265 | Description: 'Imagery server: AWS in Action: chapter 16'
266 | SourceBundle:
267 | S3Bucket: 'awsinaction-code2'
268 | S3Key: 'chapter16/build/server.zip'
269 | Outputs:
270 | EndpointURL:
271 | Value: !Sub 'http://${EBServerEnvironment.EndpointURL}'
272 | Description: Load Balancer URL
273 |
--------------------------------------------------------------------------------
/chapter16/worker/.ebextensions/worker.config:
--------------------------------------------------------------------------------
1 | packages:
2 | yum:
3 | cairo-devel: []
4 | libjpeg-turbo-devel: []
5 | giflib-devel: []
6 |
--------------------------------------------------------------------------------
/chapter16/worker/lib.js:
--------------------------------------------------------------------------------
1 | ../lib.js
--------------------------------------------------------------------------------
/chapter16/worker/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "aws-sdk": "2.169.0",
4 | "express": "4.16.2",
5 | "body-parser": "1.18.2",
6 | "assert-plus": "1.0.0",
7 | "jimp": "0.6.0"
8 | },
9 | "private": true
10 | }
11 |
--------------------------------------------------------------------------------
/chapter16/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 Jimp = require('jimp');
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 | Jimp.read(rawFile, (err, lenna) => {
81 | if (err) {
82 | throw err;
83 | } else {
84 | lenna.sepia().write(processedFile);
85 | fs.unlink(rawFile, function() {
86 | fs.readFile(processedFile, {'encoding': null}, function(err, buf) {
87 | if (err) {
88 | cb(err);
89 | } else {
90 | s3.putObject({
91 | 'Bucket': process.env.ImageBucket,
92 | 'Key': processedS3Key,
93 | 'ACL': 'public-read',
94 | 'Body': buf,
95 | 'ContentType': 'image/png'
96 | }, function(err) {
97 | if (err) {
98 | cb(err);
99 | } else {
100 | fs.unlink(processedFile, function() {
101 | cb(null, processedS3Key);
102 | });
103 | }
104 | });
105 | }
106 | });
107 | });
108 | }
109 | });
110 | }
111 | });
112 | }
113 | });
114 | }
115 |
116 | function processed(image, request, response) {
117 | processImage(image, function(err, processedS3Key) {
118 | if (err) {
119 | throw err;
120 | } else {
121 | db.updateItem({
122 | 'Key': {
123 | 'id': {
124 | 'S': image.id
125 | }
126 | },
127 | 'UpdateExpression': 'SET #s=:newState, version=:newVersion, processedS3Key=:processedS3Key',
128 | 'ConditionExpression': 'attribute_exists(id) AND version=:oldVersion AND #s IN (:stateUploaded, :stateProcessed)',
129 | 'ExpressionAttributeNames': {
130 | '#s': 'state'
131 | },
132 | 'ExpressionAttributeValues': {
133 | ':newState': {
134 | 'S': 'processed'
135 | },
136 | ':oldVersion': {
137 | 'N': image.version.toString()
138 | },
139 | ':newVersion': {
140 | 'N': (image.version + 1).toString()
141 | },
142 | ':processedS3Key': {
143 | 'S': processedS3Key
144 | },
145 | ':stateUploaded': {
146 | 'S': 'uploaded'
147 | },
148 | ':stateProcessed': {
149 | 'S': 'processed'
150 | }
151 | },
152 | 'ReturnValues': 'ALL_NEW',
153 | 'TableName': 'imagery-image'
154 | }, function(err, data) {
155 | if (err) {
156 | throw err;
157 | } else {
158 | response.json(lib.mapImage(data.Attributes));
159 | }
160 | });
161 | }
162 | });
163 | }
164 |
165 | app.listen(process.env.PORT || 8080, function() {
166 | console.log('Worker started on port ' + (process.env.PORT || 8080));
167 | });
168 |
--------------------------------------------------------------------------------
/chapter17/url2png-loadtest.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: '2010-09-09'
3 | Description: 'AWS in Action: chapter 17 (URL2PNG)'
4 | Parameters:
5 | KeyName:
6 | Description: 'Key Pair name'
7 | Type: 'AWS::EC2::KeyPair::KeyName'
8 | Default: mykey
9 | ApplicationID:
10 | Description: 'A unique identifier for your application.'
11 | Type: String
12 | AllowedPattern: '[A-Za-z0-9\\-]+'
13 | ConstraintDescription: 'Only letters, digits or dash allowed.'
14 | Mappings:
15 | RegionMap:
16 | 'ap-south-1':
17 | AMI: 'ami-2ed19c41'
18 | 'eu-west-3':
19 | AMI: 'ami-c8a017b5'
20 | 'eu-west-2':
21 | AMI: 'ami-e3051987'
22 | 'eu-west-1':
23 | AMI: 'ami-760aaa0f'
24 | 'ap-northeast-2':
25 | AMI: 'ami-fc862292'
26 | 'ap-northeast-1':
27 | AMI: 'ami-2803ac4e'
28 | 'sa-east-1':
29 | AMI: 'ami-1678037a'
30 | 'ca-central-1':
31 | AMI: 'ami-ef3b838b'
32 | 'ap-southeast-1':
33 | AMI: 'ami-dd7935be'
34 | 'ap-southeast-2':
35 | AMI: 'ami-1a668878'
36 | 'eu-central-1':
37 | AMI: 'ami-e28d098d'
38 | 'us-east-1':
39 | AMI: 'ami-6057e21a'
40 | 'us-east-2':
41 | AMI: 'ami-aa1b34cf'
42 | 'us-west-1':
43 | AMI: 'ami-1a033c7a'
44 | 'us-west-2':
45 | AMI: 'ami-32d8124a'
46 | Resources:
47 | VPC:
48 | Type: 'AWS::EC2::VPC'
49 | Properties:
50 | CidrBlock: '172.31.0.0/16'
51 | EnableDnsHostnames: true
52 | InternetGateway:
53 | Type: 'AWS::EC2::InternetGateway'
54 | Properties: {}
55 | VPCGatewayAttachment:
56 | Type: 'AWS::EC2::VPCGatewayAttachment'
57 | Properties:
58 | VpcId: !Ref VPC
59 | InternetGatewayId: !Ref InternetGateway
60 | SubnetA:
61 | Type: 'AWS::EC2::Subnet'
62 | Properties:
63 | AvailabilityZone: !Select [0, !GetAZs '']
64 | CidrBlock: '172.31.38.0/24'
65 | VpcId: !Ref VPC
66 | SubnetB:
67 | Type: 'AWS::EC2::Subnet'
68 | Properties:
69 | AvailabilityZone: !Select [1, !GetAZs '']
70 | CidrBlock: '172.31.37.0/24'
71 | VpcId: !Ref VPC
72 | RouteTable:
73 | Type: 'AWS::EC2::RouteTable'
74 | Properties:
75 | VpcId: !Ref VPC
76 | RouteTableAssociationA:
77 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
78 | Properties:
79 | SubnetId: !Ref SubnetA
80 | RouteTableId: !Ref RouteTable
81 | RouteTableAssociationB:
82 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
83 | Properties:
84 | SubnetId: !Ref SubnetB
85 | RouteTableId: !Ref RouteTable
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 | DependsOn: VPCGatewayAttachment
93 | NetworkAcl:
94 | Type: 'AWS::EC2::NetworkAcl'
95 | Properties:
96 | VpcId: !Ref VPC
97 | SubnetNetworkAclAssociationA:
98 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
99 | Properties:
100 | SubnetId: !Ref SubnetA
101 | NetworkAclId: !Ref NetworkAcl
102 | SubnetNetworkAclAssociationB:
103 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
104 | Properties:
105 | SubnetId: !Ref SubnetB
106 | NetworkAclId: !Ref NetworkAcl
107 | NetworkAclEntryIngress:
108 | Type: 'AWS::EC2::NetworkAclEntry'
109 | Properties:
110 | NetworkAclId: !Ref NetworkAcl
111 | RuleNumber: 100
112 | Protocol: -1
113 | RuleAction: allow
114 | Egress: false
115 | CidrBlock: '0.0.0.0/0'
116 | NetworkAclEntryEgress:
117 | Type: 'AWS::EC2::NetworkAclEntry'
118 | Properties:
119 | NetworkAclId: !Ref NetworkAcl
120 | RuleNumber: 100
121 | Protocol: -1
122 | RuleAction: allow
123 | Egress: true
124 | CidrBlock: '0.0.0.0/0'
125 | SecurityGroup:
126 | Type: 'AWS::EC2::SecurityGroup'
127 | Properties:
128 | GroupDescription: 'url2png'
129 | VpcId: !Ref VPC
130 | SecurityGroupIngress:
131 | - CidrIp: '0.0.0.0/0'
132 | FromPort: 22
133 | IpProtocol: tcp
134 | ToPort: 22
135 | IamRole:
136 | Type: 'AWS::IAM::Role'
137 | Properties:
138 | AssumeRolePolicyDocument:
139 | Version: '2012-10-17'
140 | Statement:
141 | - Effect: Allow
142 | Principal:
143 | Service:
144 | - 'ec2.amazonaws.com'
145 | Action: 'sts:AssumeRole'
146 | Policies:
147 | - PolicyName: url2png
148 | PolicyDocument:
149 | Version: '2012-10-17'
150 | Statement:
151 | - Effect: Allow
152 | Action: 's3:*'
153 | Resource: !Sub 'arn:aws:s3:::${ApplicationID}/*'
154 | - Effect: Allow
155 | Action: 'sqs:*'
156 | Resource: !Sub 'arn:aws:sqs:${AWS::Region}:${AWS::AccountId}:${SQSQueue.QueueName}'
157 | IamInstanceProfile:
158 | Type: 'AWS::IAM::InstanceProfile'
159 | Properties:
160 | Roles:
161 | - !Ref IamRole
162 | S3Bucket:
163 | Type: 'AWS::S3::Bucket'
164 | Properties:
165 | BucketName: !Ref ApplicationID
166 | WebsiteConfiguration:
167 | IndexDocument: 'index.html'
168 | SQSQueue:
169 | Type: 'AWS::SQS::Queue'
170 | Properties:
171 | QueueName: url2png
172 | LaunchConfiguration:
173 | Type: 'AWS::AutoScaling::LaunchConfiguration'
174 | Properties:
175 | AssociatePublicIpAddress: true
176 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
177 | InstanceMonitoring: false
178 | InstanceType: t2.micro
179 | SecurityGroups:
180 | - !Ref SecurityGroup
181 | KeyName: !Ref KeyName
182 | IamInstanceProfile: !Ref IamInstanceProfile
183 | UserData:
184 | 'Fn::Base64': !Sub |
185 | #!/bin/bash -ex
186 | curl -sL https://rpm.nodesource.com/setup_8.x | bash -
187 | yum install -y nodejs
188 | wget -q -T 60 https://github.com/AWSinAction/code2/archive/master.zip
189 | unzip master.zip
190 | cd code2-master/chapter15/url2png/
191 | npm install
192 | echo "{\"QueueUrl\": \"${SQSQueue}\", \"Bucket\": \"${ApplicationID}\"}" > config.json
193 | node worker.js
194 | AutoScalingGroup:
195 | Type: 'AWS::AutoScaling::AutoScalingGroup'
196 | Properties:
197 | LaunchConfigurationName: !Ref LaunchConfiguration
198 | MinSize: '1'
199 | MaxSize: '2'
200 | HealthCheckGracePeriod: 120
201 | HealthCheckType: EC2
202 | VPCZoneIdentifier:
203 | - !Ref SubnetA
204 | - !Ref SubnetB
205 | Tags:
206 | - PropagateAtLaunch: true
207 | Value: 'url2png-consumer'
208 | Key: Name
209 | DependsOn: VPCGatewayAttachment
210 | ScalingUpPolicy:
211 | Type: 'AWS::AutoScaling::ScalingPolicy'
212 | Properties:
213 | AdjustmentType: 'ChangeInCapacity'
214 | AutoScalingGroupName: !Ref AutoScalingGroup
215 | PolicyType: 'StepScaling'
216 | MetricAggregationType: 'Average'
217 | EstimatedInstanceWarmup: 60
218 | StepAdjustments:
219 | - MetricIntervalLowerBound: 0
220 | ScalingAdjustment: 1
221 | HighQueueAlarm:
222 | Type: 'AWS::CloudWatch::Alarm'
223 | Properties:
224 | EvaluationPeriods: 1
225 | Statistic: Sum
226 | Threshold: 5
227 | AlarmDescription: 'Alarm if queue length is higher than 5.'
228 | Period: 300
229 | AlarmActions:
230 | - !Ref ScalingUpPolicy
231 | Namespace: 'AWS/SQS'
232 | Dimensions:
233 | - Name: QueueName
234 | Value: !Sub '${SQSQueue.QueueName}'
235 | ComparisonOperator: GreaterThanThreshold
236 | MetricName: ApproximateNumberOfMessagesVisible
237 | TreatMissingData: notBreaching
238 | ScalingDownPolicy:
239 | Type: 'AWS::AutoScaling::ScalingPolicy'
240 | Properties:
241 | AdjustmentType: 'ChangeInCapacity'
242 | AutoScalingGroupName: !Ref AutoScalingGroup
243 | PolicyType: 'StepScaling'
244 | MetricAggregationType: 'Average'
245 | EstimatedInstanceWarmup: 60
246 | StepAdjustments:
247 | - MetricIntervalUpperBound: 0
248 | ScalingAdjustment: -1
249 | LowQueueAlarm:
250 | Type: 'AWS::CloudWatch::Alarm'
251 | Properties:
252 | EvaluationPeriods: 1
253 | Statistic: Sum
254 | Threshold: 5
255 | AlarmDescription: 'Alarm if queue length is lower than 5.'
256 | Period: 300
257 | AlarmActions:
258 | - !Ref ScalingDownPolicy
259 | Namespace: 'AWS/SQS'
260 | Dimensions:
261 | - Name: QueueName
262 | Value: !Sub '${SQSQueue.QueueName}'
263 | ComparisonOperator: LessThanThreshold
264 | MetricName: ApproximateNumberOfMessagesVisible
265 | TreatMissingData: notBreaching
266 | LoadTestServer:
267 | Type: 'AWS::EC2::Instance'
268 | DependsOn: VPCGatewayAttachment
269 | Properties:
270 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
271 | InstanceType: t2.micro
272 | KeyName: !Ref KeyName
273 | IamInstanceProfile: !Ref IamInstanceProfile
274 | UserData:
275 | 'Fn::Base64': !Sub |
276 | #!/bin/bash -ex
277 | for i in `seq 1 25`;
278 | do
279 | aws sqs send-message --queue-url ${SQSQueue} --message-body "{\"id\": \"$i.www.google.com\", \"url\": \"http://www.google.com\"}" --region ${AWS::Region}
280 | aws sqs send-message --queue-url ${SQSQueue} --message-body "{\"id\": \"$i.www.youtube.com\", \"url\": \"http://www.youtube.com\"}" --region ${AWS::Region}
281 | aws sqs send-message --queue-url ${SQSQueue} --message-body "{\"id\": \"$i.www.amazon.com\", \"url\": \"http://www.amazon.com\"}" --region ${AWS::Region}
282 | aws sqs send-message --queue-url ${SQSQueue} --message-body "{\"id\": \"$i.www.twitter.com\", \"url\": \"http://www.twitter.com\"}" --region ${AWS::Region}
283 | aws sqs send-message --queue-url ${SQSQueue} --message-body "{\"id\": \"$i.www.linkedin.com\", \"url\": \"http://www.linkedin.com\"}" --region ${AWS::Region}
284 | aws sqs send-message --queue-url ${SQSQueue} --message-body "{\"id\": \"$i.www.ebay.com\", \"url\": \"http://www.ebay.com\"}" --region ${AWS::Region}
285 | aws sqs send-message --queue-url ${SQSQueue} --message-body "{\"id\": \"$i.www.instagram.com\", \"url\": \"http://www.instagram.com\"}" --region ${AWS::Region}
286 | aws sqs send-message --queue-url ${SQSQueue} --message-body "{\"id\": \"$i.www.reddit.com\", \"url\": \"http://www.reddit.com\"}" --region ${AWS::Region}
287 | aws sqs send-message --queue-url ${SQSQueue} --message-body "{\"id\": \"$i.www.pinterest.com\", \"url\": \"http://www.pinterest.com\"}" --region ${AWS::Region}
288 | aws sqs send-message --queue-url ${SQSQueue} --message-body "{\"id\": \"$i.www.wordpress.com\", \"url\": \"http://www.wordpress.com\"}" --region ${AWS::Region}
289 | done
290 | Tags:
291 | - Key: Name
292 | Value: 'url2png-loadtest'
293 | NetworkInterfaces:
294 | - AssociatePublicIpAddress: true
295 | DeviceIndex: '0'
296 | GroupSet:
297 | - !Ref SecurityGroup
298 | SubnetId: !Ref SubnetA
299 |
--------------------------------------------------------------------------------
/chapter17/url2png.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: '2010-09-09'
3 | Description: 'AWS in Action: chapter 17 (URL2PNG)'
4 | Parameters:
5 | KeyName:
6 | Description: 'Key Pair name'
7 | Type: 'AWS::EC2::KeyPair::KeyName'
8 | Default: mykey
9 | ApplicationID:
10 | Description: 'A unique identifier for your application.'
11 | Type: String
12 | AllowedPattern: '[A-Za-z0-9\\-]+'
13 | ConstraintDescription: 'Only letters, digits or dash allowed.'
14 | Mappings:
15 | RegionMap:
16 | 'ap-south-1':
17 | AMI: 'ami-2ed19c41'
18 | 'eu-west-3':
19 | AMI: 'ami-c8a017b5'
20 | 'eu-west-2':
21 | AMI: 'ami-e3051987'
22 | 'eu-west-1':
23 | AMI: 'ami-760aaa0f'
24 | 'ap-northeast-2':
25 | AMI: 'ami-fc862292'
26 | 'ap-northeast-1':
27 | AMI: 'ami-2803ac4e'
28 | 'sa-east-1':
29 | AMI: 'ami-1678037a'
30 | 'ca-central-1':
31 | AMI: 'ami-ef3b838b'
32 | 'ap-southeast-1':
33 | AMI: 'ami-dd7935be'
34 | 'ap-southeast-2':
35 | AMI: 'ami-1a668878'
36 | 'eu-central-1':
37 | AMI: 'ami-e28d098d'
38 | 'us-east-1':
39 | AMI: 'ami-6057e21a'
40 | 'us-east-2':
41 | AMI: 'ami-aa1b34cf'
42 | 'us-west-1':
43 | AMI: 'ami-1a033c7a'
44 | 'us-west-2':
45 | AMI: 'ami-32d8124a'
46 | Resources:
47 | VPC:
48 | Type: 'AWS::EC2::VPC'
49 | Properties:
50 | CidrBlock: '172.31.0.0/16'
51 | EnableDnsHostnames: true
52 | InternetGateway:
53 | Type: 'AWS::EC2::InternetGateway'
54 | Properties: {}
55 | VPCGatewayAttachment:
56 | Type: 'AWS::EC2::VPCGatewayAttachment'
57 | Properties:
58 | VpcId: !Ref VPC
59 | InternetGatewayId: !Ref InternetGateway
60 | SubnetA:
61 | Type: 'AWS::EC2::Subnet'
62 | Properties:
63 | AvailabilityZone: !Select [0, !GetAZs '']
64 | CidrBlock: '172.31.38.0/24'
65 | VpcId: !Ref VPC
66 | SubnetB:
67 | Type: 'AWS::EC2::Subnet'
68 | Properties:
69 | AvailabilityZone: !Select [1, !GetAZs '']
70 | CidrBlock: '172.31.37.0/24'
71 | VpcId: !Ref VPC
72 | RouteTable:
73 | Type: 'AWS::EC2::RouteTable'
74 | Properties:
75 | VpcId: !Ref VPC
76 | RouteTableAssociationA:
77 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
78 | Properties:
79 | SubnetId: !Ref SubnetA
80 | RouteTableId: !Ref RouteTable
81 | RouteTableAssociationB:
82 | Type: 'AWS::EC2::SubnetRouteTableAssociation'
83 | Properties:
84 | SubnetId: !Ref SubnetB
85 | RouteTableId: !Ref RouteTable
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 | DependsOn: VPCGatewayAttachment
93 | NetworkAcl:
94 | Type: 'AWS::EC2::NetworkAcl'
95 | Properties:
96 | VpcId: !Ref VPC
97 | SubnetNetworkAclAssociationA:
98 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
99 | Properties:
100 | SubnetId: !Ref SubnetA
101 | NetworkAclId: !Ref NetworkAcl
102 | SubnetNetworkAclAssociationB:
103 | Type: 'AWS::EC2::SubnetNetworkAclAssociation'
104 | Properties:
105 | SubnetId: !Ref SubnetB
106 | NetworkAclId: !Ref NetworkAcl
107 | NetworkAclEntryIngress:
108 | Type: 'AWS::EC2::NetworkAclEntry'
109 | Properties:
110 | NetworkAclId: !Ref NetworkAcl
111 | RuleNumber: 100
112 | Protocol: -1
113 | RuleAction: allow
114 | Egress: false
115 | CidrBlock: '0.0.0.0/0'
116 | NetworkAclEntryEgress:
117 | Type: 'AWS::EC2::NetworkAclEntry'
118 | Properties:
119 | NetworkAclId: !Ref NetworkAcl
120 | RuleNumber: 100
121 | Protocol: -1
122 | RuleAction: allow
123 | Egress: true
124 | CidrBlock: '0.0.0.0/0'
125 | SecurityGroup:
126 | Type: 'AWS::EC2::SecurityGroup'
127 | Properties:
128 | GroupDescription: 'url2png'
129 | VpcId: !Ref VPC
130 | SecurityGroupIngress:
131 | - CidrIp: '0.0.0.0/0'
132 | FromPort: 22
133 | IpProtocol: tcp
134 | ToPort: 22
135 | IamRole:
136 | Type: 'AWS::IAM::Role'
137 | Properties:
138 | AssumeRolePolicyDocument:
139 | Version: '2012-10-17'
140 | Statement:
141 | - Effect: Allow
142 | Principal:
143 | Service:
144 | - 'ec2.amazonaws.com'
145 | Action: 'sts:AssumeRole'
146 | Policies:
147 | - PolicyName: url2png
148 | PolicyDocument:
149 | Version: '2012-10-17'
150 | Statement:
151 | - Effect: Allow
152 | Action: 's3:*'
153 | Resource: !Sub 'arn:aws:s3:::${ApplicationID}/*'
154 | - Effect: Allow
155 | Action: 'sqs:*'
156 | Resource: !Sub 'arn:aws:sqs:${AWS::Region}:${AWS::AccountId}:${SQSQueue.QueueName}'
157 | IamInstanceProfile:
158 | Type: 'AWS::IAM::InstanceProfile'
159 | Properties:
160 | Roles:
161 | - !Ref IamRole
162 | S3Bucket:
163 | Type: 'AWS::S3::Bucket'
164 | Properties:
165 | BucketName: !Ref ApplicationID
166 | WebsiteConfiguration:
167 | IndexDocument: 'index.html'
168 | SQSQueue:
169 | Type: 'AWS::SQS::Queue'
170 | Properties:
171 | QueueName: url2png
172 | LaunchConfiguration:
173 | Type: 'AWS::AutoScaling::LaunchConfiguration'
174 | Properties:
175 | AssociatePublicIpAddress: true
176 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
177 | InstanceMonitoring: false
178 | InstanceType: t2.micro
179 | SecurityGroups:
180 | - !Ref SecurityGroup
181 | KeyName: !Ref KeyName
182 | IamInstanceProfile: !Ref IamInstanceProfile
183 | UserData:
184 | 'Fn::Base64': !Sub |
185 | #!/bin/bash -ex
186 | curl -sL https://rpm.nodesource.com/setup_8.x | bash -
187 | yum install -y nodejs
188 | wget -q -T 60 https://github.com/AWSinAction/code2/archive/master.zip
189 | unzip master.zip
190 | cd code2-master/chapter15/url2png/
191 | npm install
192 | echo "{\"QueueUrl\": \"${SQSQueue}\", \"Bucket\": \"${ApplicationID}\"}" > config.json
193 | node worker.js
194 | AutoScalingGroup:
195 | Type: 'AWS::AutoScaling::AutoScalingGroup'
196 | Properties:
197 | LaunchConfigurationName: !Ref LaunchConfiguration
198 | MinSize: '1'
199 | MaxSize: '2'
200 | HealthCheckGracePeriod: 120
201 | HealthCheckType: EC2
202 | VPCZoneIdentifier:
203 | - !Ref SubnetA
204 | - !Ref SubnetB
205 | Tags:
206 | - PropagateAtLaunch: true
207 | Value: 'url2png-consumer'
208 | Key: Name
209 | DependsOn: VPCGatewayAttachment
210 | ScalingUpPolicy:
211 | Type: 'AWS::AutoScaling::ScalingPolicy'
212 | Properties:
213 | AdjustmentType: 'ChangeInCapacity'
214 | AutoScalingGroupName: !Ref AutoScalingGroup
215 | PolicyType: 'StepScaling'
216 | MetricAggregationType: 'Average'
217 | EstimatedInstanceWarmup: 60
218 | StepAdjustments:
219 | - MetricIntervalLowerBound: 0
220 | ScalingAdjustment: 1
221 | HighQueueAlarm:
222 | Type: 'AWS::CloudWatch::Alarm'
223 | Properties:
224 | EvaluationPeriods: 1
225 | Statistic: Sum
226 | Threshold: 5
227 | AlarmDescription: 'Alarm if queue length is higher than 5.'
228 | Period: 300
229 | AlarmActions:
230 | - !Ref ScalingUpPolicy
231 | Namespace: 'AWS/SQS'
232 | Dimensions:
233 | - Name: QueueName
234 | Value: !Sub '${SQSQueue.QueueName}'
235 | ComparisonOperator: GreaterThanThreshold
236 | MetricName: ApproximateNumberOfMessagesVisible
237 | TreatMissingData: notBreaching
238 | ScalingDownPolicy:
239 | Type: 'AWS::AutoScaling::ScalingPolicy'
240 | Properties:
241 | AdjustmentType: 'ChangeInCapacity'
242 | AutoScalingGroupName: !Ref AutoScalingGroup
243 | PolicyType: 'StepScaling'
244 | MetricAggregationType: 'Average'
245 | EstimatedInstanceWarmup: 60
246 | StepAdjustments:
247 | - MetricIntervalUpperBound: 0
248 | ScalingAdjustment: -1
249 | LowQueueAlarm:
250 | Type: 'AWS::CloudWatch::Alarm'
251 | Properties:
252 | EvaluationPeriods: 1
253 | Statistic: Sum
254 | Threshold: 5
255 | AlarmDescription: 'Alarm if queue length is lower than 5.'
256 | Period: 300
257 | AlarmActions:
258 | - !Ref ScalingDownPolicy
259 | Namespace: 'AWS/SQS'
260 | Dimensions:
261 | - Name: QueueName
262 | Value: !Sub '${SQSQueue.QueueName}'
263 | ComparisonOperator: LessThanThreshold
264 | MetricName: ApproximateNumberOfMessagesVisible
265 | TreatMissingData: notBreaching
266 |
--------------------------------------------------------------------------------