├── .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 | ![Cover of Amazon Web Services in Action, Second Edition](https://images.manning.com/720/960/resize/book/2/2e8abe8-aa59-434f-bbbe-57e4cac9e09f/Wittig-Amazon-2ed-MEAP-HI.png) 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 | ![Node Control Center for AWS](./nodecc.png?raw=true "Node Control Center for AWS") 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 |
9 |

10 |

11 |
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 | ![Node TODO for AWS](./nodetodo.png?raw=true "Node TODO for AWS") 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 | ![URL2PNG](./url2png.png?raw=true "URL2PNG") 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 | ![Imagery](./imagery.png?raw=true "Imagery") 4 | 5 | To try Imagery app create a CloudFormation stack based on the template https://s3.amazonaws.com/awsinaction-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 |
11 |

New image

12 |

Create a new image

13 |
14 |
15 |

Upload

16 |

17 |
18 |
19 | 20 | 21 |
22 | 23 |
24 |
25 |
26 |

View

27 |

28 |

29 |

Refresh New image

30 |
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 | --------------------------------------------------------------------------------