├── .gitignore ├── LICENSE.txt ├── README.md ├── pom.xml └── src ├── main ├── java │ └── demo │ │ ├── Application.java │ │ └── ServletInitializer.java └── resources │ ├── application.properties │ └── aws │ └── create_environment.template └── test └── java └── demo └── ApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | demo.iml 3 | target/* -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Future Processing Sp. z o.o. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Boot CloudFormation demo 2 | This project demonstrates how to deploy Spring Boot app (generated on http://start.spring.io/) in AWS using: 3 | 4 | * Elastic Beanstalk 5 | * VPC 6 | * CloudFormation 7 | 8 | ### Running on AWS 9 | 10 | 1. Run `mvn clean package` and upload generated war to S3 bucket (for example using aws-cli) 11 | 2. Go to CloudFormation and create a new stack using `resources/aws/create_environment.template`. 12 | 3. Provide stack input parameters: keypair names (must be created manually before) and location of war file (bucket and key). 13 | 4. Wait for stack to be ready. 14 | 5. Check `ApplicationUrl` stack output, this is an URL where the app is deployed. 15 | 16 | ### About template 17 | 18 | Running mentioned template creates: 19 | 20 | * VPC in 3 availability zones, one public and private subnet per AZ. 21 | * HA NAT that allows outbound network traffic from private subnets (based on AWS [article](https://aws.amazon.com/articles/6079781443936876)). 22 | * Bastion host. It servers as a proxy for ssh connections (instances in private subnets cannot be reached directly from outside of the VPC). 23 | * Demo bucket. 24 | * Backend instance profile allowing backend instances all operations on demo bucket. 25 | * Backend beanstalk environment inside VPC with initial version (.war file passed as input parameter). 26 | 27 | License 28 | ======= 29 | 30 | Copyright 2015 Future Processing Sp. z o.o. 31 | 32 | Licensed under The MIT License (MIT), see LICENSE.txt for details. -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.test 7 | demo 8 | 0.0.1-SNAPSHOT 9 | war 10 | 11 | spring-boot-cloudformation-demo 12 | Demo project for Spring Boot deployment using cloudformation 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.1.6.RELEASE 18 | 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-data-mongodb 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-data-rest 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-web 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-tomcat 37 | provided 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-test 42 | test 43 | 44 | 45 | 46 | 47 | UTF-8 48 | demo.Application 49 | 1.7 50 | 51 | 52 | 53 | 54 | 55 | org.springframework.boot 56 | spring-boot-maven-plugin 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/main/java/demo/Application.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.context.annotation.ComponentScan; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | @ComponentScan 10 | @EnableAutoConfiguration 11 | public class Application { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(Application.class, args); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/demo/ServletInitializer.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import org.springframework.boot.builder.SpringApplicationBuilder; 4 | import org.springframework.boot.context.web.SpringBootServletInitializer; 5 | 6 | public class ServletInitializer extends SpringBootServletInitializer { 7 | 8 | @Override 9 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 10 | return application.sources(Application.class); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FutureProcessing/spring-boot-cloudformation-demo/e6cff3bcf31750d56f19d0e647ed70203bd0c125/src/main/resources/application.properties -------------------------------------------------------------------------------- /src/main/resources/aws/create_environment.template: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "Spring Boot Cloudformation demo stack.", 4 | "Parameters": { 5 | "NatKeyName": { 6 | "Description": "Name of an existing EC2 KeyPair to enable SSH access to the NAT instances", 7 | "Type": "String", 8 | "Default": "nat" 9 | }, 10 | "BastionHostKeyName": { 11 | "Description": "Keypair for Bastion Host instances", 12 | "Type": "String", 13 | "Default": "bastion" 14 | }, 15 | "BackendKeyName": { 16 | "Description": "Keypair for backend instances", 17 | "Type": "String", 18 | "Default": "backend" 19 | }, 20 | "EnvName": { 21 | "Description": "Name of the environment to create", 22 | "Type": "String", 23 | "Default": "testing" 24 | }, 25 | "SourceBundleS3Bucket": { 26 | "Description": "Bucket with source bundle", 27 | "Type": "String" 28 | }, 29 | "SourceBundleS3Key": { 30 | "Description": "Source bundle S3 key", 31 | "Type": "String" 32 | } 33 | }, 34 | "Mappings": { 35 | "AWSNATAMI": { 36 | "us-east-1": { 37 | "AMI": "ami-54cf5c3d" 38 | }, 39 | "us-west-2": { 40 | "AMI": "ami-8e27adbe" 41 | }, 42 | "us-west-1": { 43 | "AMI": "ami-b63210f3" 44 | }, 45 | "eu-west-1": { 46 | "AMI": "ami-3c5f5748" 47 | }, 48 | "ap-southeast-1": { 49 | "AMI": "ami-ba7538e8" 50 | }, 51 | "ap-southeast-2": { 52 | "AMI": "ami-b6df4e8c" 53 | }, 54 | "ap-northeast-1": { 55 | "AMI": "ami-5d7dfa5c" 56 | }, 57 | "sa-east-1": { 58 | "AMI": "ami-89c81394" 59 | } 60 | } 61 | }, 62 | "Resources": { 63 | "NATRole": { 64 | "Type": "AWS::IAM::Role", 65 | "Properties": { 66 | "AssumeRolePolicyDocument": { 67 | "Statement": [ 68 | { 69 | "Effect": "Allow", 70 | "Principal": { 71 | "Service": [ 72 | "ec2.amazonaws.com" 73 | ] 74 | }, 75 | "Action": [ 76 | "sts:AssumeRole" 77 | ] 78 | } 79 | ] 80 | }, 81 | "Path": "/", 82 | "Policies": [ 83 | { 84 | "PolicyName": "NAT_Takeover", 85 | "PolicyDocument": { 86 | "Statement": [ 87 | { 88 | "Effect": "Allow", 89 | "Action": [ 90 | "ec2:DescribeInstances", 91 | "ec2:DescribeRouteTables", 92 | "ec2:CreateRoute", 93 | "ec2:ReplaceRoute", 94 | "ec2:StartInstances", 95 | "ec2:StopInstances" 96 | ], 97 | "Resource": "*" 98 | } 99 | ] 100 | } 101 | } 102 | ] 103 | } 104 | }, 105 | "NATRoleProfile": { 106 | "Type": "AWS::IAM::InstanceProfile", 107 | "Properties": { 108 | "Path": "/", 109 | "Roles": [ 110 | { 111 | "Ref": "NATRole" 112 | } 113 | ] 114 | } 115 | }, 116 | "VPC": { 117 | "Type": "AWS::EC2::VPC", 118 | "Properties": { 119 | "CidrBlock": "10.0.0.0/16", 120 | "Tags": [ 121 | { 122 | "Key": "Application", 123 | "Value": { 124 | "Ref": "AWS::StackName" 125 | } 126 | } 127 | ] 128 | } 129 | }, 130 | "PubSubnet1": { 131 | "Type": "AWS::EC2::Subnet", 132 | "Properties": { 133 | "VpcId": { 134 | "Ref": "VPC" 135 | }, 136 | "AvailabilityZone": "eu-west-1a", 137 | "CidrBlock": "10.0.0.0/24", 138 | "Tags": [ 139 | { 140 | "Key": "Application", 141 | "Value": { 142 | "Ref": "AWS::StackName" 143 | } 144 | }, 145 | { 146 | "Key": "Network", 147 | "Value": "Public" 148 | } 149 | ] 150 | } 151 | }, 152 | "PriSubnet1": { 153 | "Type": "AWS::EC2::Subnet", 154 | "Properties": { 155 | "VpcId": { 156 | "Ref": "VPC" 157 | }, 158 | "AvailabilityZone": "eu-west-1a", 159 | "CidrBlock": "10.0.3.0/24", 160 | "Tags": [ 161 | { 162 | "Key": "Application", 163 | "Value": { 164 | "Ref": "AWS::StackName" 165 | } 166 | }, 167 | { 168 | "Key": "Network", 169 | "Value": "Private" 170 | } 171 | ] 172 | } 173 | }, 174 | "PubSubnet2": { 175 | "Type": "AWS::EC2::Subnet", 176 | "Properties": { 177 | "VpcId": { 178 | "Ref": "VPC" 179 | }, 180 | "AvailabilityZone": "eu-west-1b", 181 | "CidrBlock": "10.0.1.0/24", 182 | "Tags": [ 183 | { 184 | "Key": "Application", 185 | "Value": { 186 | "Ref": "AWS::StackName" 187 | } 188 | }, 189 | { 190 | "Key": "Network", 191 | "Value": "Public" 192 | } 193 | ] 194 | } 195 | }, 196 | "PriSubnet2": { 197 | "Type": "AWS::EC2::Subnet", 198 | "Properties": { 199 | "VpcId": { 200 | "Ref": "VPC" 201 | }, 202 | "AvailabilityZone": "eu-west-1b", 203 | "CidrBlock": "10.0.4.0/24", 204 | "Tags": [ 205 | { 206 | "Key": "Application", 207 | "Value": { 208 | "Ref": "AWS::StackName" 209 | } 210 | }, 211 | { 212 | "Key": "Network", 213 | "Value": "Private" 214 | } 215 | ] 216 | } 217 | }, 218 | "PubSubnet3": { 219 | "Type": "AWS::EC2::Subnet", 220 | "Properties": { 221 | "VpcId": { 222 | "Ref": "VPC" 223 | }, 224 | "AvailabilityZone": "eu-west-1c", 225 | "CidrBlock": "10.0.2.0/24", 226 | "Tags": [ 227 | { 228 | "Key": "Application", 229 | "Value": { 230 | "Ref": "AWS::StackName" 231 | } 232 | }, 233 | { 234 | "Key": "Network", 235 | "Value": "Public" 236 | } 237 | ] 238 | } 239 | }, 240 | "PriSubnet3": { 241 | "Type": "AWS::EC2::Subnet", 242 | "Properties": { 243 | "VpcId": { 244 | "Ref": "VPC" 245 | }, 246 | "AvailabilityZone": "eu-west-1c", 247 | "CidrBlock": "10.0.5.0/24", 248 | "Tags": [ 249 | { 250 | "Key": "Application", 251 | "Value": { 252 | "Ref": "AWS::StackName" 253 | } 254 | }, 255 | { 256 | "Key": "Network", 257 | "Value": "Private" 258 | } 259 | ] 260 | } 261 | }, 262 | "InternetGateway": { 263 | "Type": "AWS::EC2::InternetGateway", 264 | "Properties": { 265 | "Tags": [ 266 | { 267 | "Key": "Application", 268 | "Value": { 269 | "Ref": "AWS::StackName" 270 | } 271 | }, 272 | { 273 | "Key": "Network", 274 | "Value": "Public" 275 | } 276 | ] 277 | } 278 | }, 279 | "GatewayToInternet": { 280 | "Type": "AWS::EC2::VPCGatewayAttachment", 281 | "Properties": { 282 | "VpcId": { 283 | "Ref": "VPC" 284 | }, 285 | "InternetGatewayId": { 286 | "Ref": "InternetGateway" 287 | } 288 | } 289 | }, 290 | "PublicRouteTable": { 291 | "Type": "AWS::EC2::RouteTable", 292 | "Properties": { 293 | "VpcId": { 294 | "Ref": "VPC" 295 | }, 296 | "Tags": [ 297 | { 298 | "Key": "Application", 299 | "Value": { 300 | "Ref": "AWS::StackName" 301 | } 302 | }, 303 | { 304 | "Key": "Network", 305 | "Value": "Public" 306 | } 307 | ] 308 | } 309 | }, 310 | "PrivateRouteTable1": { 311 | "Type": "AWS::EC2::RouteTable", 312 | "Properties": { 313 | "VpcId": { 314 | "Ref": "VPC" 315 | }, 316 | "Tags": [ 317 | { 318 | "Key": "Application", 319 | "Value": { 320 | "Ref": "AWS::StackName" 321 | } 322 | }, 323 | { 324 | "Key": "Network", 325 | "Value": "Private" 326 | } 327 | ] 328 | } 329 | }, 330 | "PrivateRouteTable2": { 331 | "Type": "AWS::EC2::RouteTable", 332 | "Properties": { 333 | "VpcId": { 334 | "Ref": "VPC" 335 | }, 336 | "Tags": [ 337 | { 338 | "Key": "Application", 339 | "Value": { 340 | "Ref": "AWS::StackName" 341 | } 342 | }, 343 | { 344 | "Key": "Network", 345 | "Value": "Private" 346 | } 347 | ] 348 | } 349 | }, 350 | "PrivateRouteTable3": { 351 | "Type": "AWS::EC2::RouteTable", 352 | "Properties": { 353 | "VpcId": { 354 | "Ref": "VPC" 355 | }, 356 | "Tags": [ 357 | { 358 | "Key": "Application", 359 | "Value": { 360 | "Ref": "AWS::StackName" 361 | } 362 | }, 363 | { 364 | "Key": "Network", 365 | "Value": "Private" 366 | } 367 | ] 368 | } 369 | }, 370 | "PublicRoute": { 371 | "Type": "AWS::EC2::Route", 372 | "Properties": { 373 | "RouteTableId": { 374 | "Ref": "PublicRouteTable" 375 | }, 376 | "DestinationCidrBlock": "0.0.0.0/0", 377 | "GatewayId": { 378 | "Ref": "InternetGateway" 379 | } 380 | } 381 | }, 382 | "PrivateRoute1": { 383 | "Type": "AWS::EC2::Route", 384 | "Properties": { 385 | "RouteTableId": { 386 | "Ref": "PrivateRouteTable1" 387 | }, 388 | "DestinationCidrBlock": "0.0.0.0/0", 389 | "InstanceId": { 390 | "Ref": "NAT1Instance" 391 | } 392 | } 393 | }, 394 | "PrivateRoute2": { 395 | "Type": "AWS::EC2::Route", 396 | "Properties": { 397 | "RouteTableId": { 398 | "Ref": "PrivateRouteTable2" 399 | }, 400 | "DestinationCidrBlock": "0.0.0.0/0", 401 | "InstanceId": { 402 | "Ref": "NAT2Instance" 403 | } 404 | } 405 | }, 406 | "PrivateRoute3": { 407 | "Type": "AWS::EC2::Route", 408 | "Properties": { 409 | "RouteTableId": { 410 | "Ref": "PrivateRouteTable3" 411 | }, 412 | "DestinationCidrBlock": "0.0.0.0/0", 413 | "InstanceId": { 414 | "Ref": "NAT2Instance" 415 | } 416 | } 417 | }, 418 | "PubSubnet1RTAssoc": { 419 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 420 | "Properties": { 421 | "SubnetId": { 422 | "Ref": "PubSubnet1" 423 | }, 424 | "RouteTableId": { 425 | "Ref": "PublicRouteTable" 426 | } 427 | } 428 | }, 429 | "PubSubnet2RTAssoc": { 430 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 431 | "Properties": { 432 | "SubnetId": { 433 | "Ref": "PubSubnet2" 434 | }, 435 | "RouteTableId": { 436 | "Ref": "PublicRouteTable" 437 | } 438 | } 439 | }, 440 | "PubSubnet3RTAssoc": { 441 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 442 | "Properties": { 443 | "SubnetId": { 444 | "Ref": "PubSubnet3" 445 | }, 446 | "RouteTableId": { 447 | "Ref": "PublicRouteTable" 448 | } 449 | } 450 | }, 451 | "PriSubnet1RTAssoc": { 452 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 453 | "Properties": { 454 | "SubnetId": { 455 | "Ref": "PriSubnet1" 456 | }, 457 | "RouteTableId": { 458 | "Ref": "PrivateRouteTable1" 459 | } 460 | } 461 | }, 462 | "PriSubnet2RTAssoc": { 463 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 464 | "Properties": { 465 | "SubnetId": { 466 | "Ref": "PriSubnet2" 467 | }, 468 | "RouteTableId": { 469 | "Ref": "PrivateRouteTable2" 470 | } 471 | } 472 | }, 473 | "PriSubnet3RTAssoc": { 474 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 475 | "Properties": { 476 | "SubnetId": { 477 | "Ref": "PriSubnet3" 478 | }, 479 | "RouteTableId": { 480 | "Ref": "PrivateRouteTable3" 481 | } 482 | } 483 | }, 484 | "NAT1EIP": { 485 | "Type": "AWS::EC2::EIP", 486 | "Properties": { 487 | "Domain": "vpc", 488 | "InstanceId": { 489 | "Ref": "NAT1Instance" 490 | } 491 | } 492 | }, 493 | "NAT2EIP": { 494 | "Type": "AWS::EC2::EIP", 495 | "Properties": { 496 | "Domain": "vpc", 497 | "InstanceId": { 498 | "Ref": "NAT2Instance" 499 | } 500 | } 501 | }, 502 | "NAT1Instance": { 503 | "Type": "AWS::EC2::Instance", 504 | "Metadata": { 505 | "Comment1": "Create NAT #1" 506 | }, 507 | "Properties": { 508 | "InstanceType": "m1.small", 509 | "KeyName": { 510 | "Ref": "NatKeyName" 511 | }, 512 | "IamInstanceProfile": { 513 | "Ref": "NATRoleProfile" 514 | }, 515 | "SubnetId": { 516 | "Ref": "PubSubnet1" 517 | }, 518 | "SourceDestCheck": "false", 519 | "ImageId": { 520 | "Fn::FindInMap": [ 521 | "AWSNATAMI", 522 | { 523 | "Ref": "AWS::Region" 524 | }, 525 | "AMI" 526 | ] 527 | }, 528 | "SecurityGroupIds": [ 529 | { 530 | "Ref": "NATSecurityGroup" 531 | } 532 | ], 533 | "Tags": [ 534 | { 535 | "Key": "Name", 536 | "Value": "NAT #1" 537 | } 538 | ], 539 | "UserData": { 540 | "Fn::Base64": { 541 | "Fn::Join": [ 542 | "", 543 | [ 544 | "#!/bin/bash -v\n", 545 | "yum update -y aws*\n", 546 | ". /etc/profile.d/aws-apitools-common.sh\n", 547 | "# Configure iptables\n", 548 | "/sbin/iptables -t nat -A POSTROUTING -o eth0 -s 0.0.0.0/0 -j MASQUERADE\n", 549 | "/sbin/iptables-save > /etc/sysconfig/iptables\n", 550 | "# Configure ip forwarding and redirects\n", 551 | "echo 1 > /proc/sys/net/ipv4/ip_forward && echo 0 > /proc/sys/net/ipv4/conf/eth0/send_redirects\n", 552 | "mkdir -p /etc/sysctl.d/\n", 553 | "cat < /etc/sysctl.d/nat.conf\n", 554 | "net.ipv4.ip_forward = 1\n", 555 | "net.ipv4.conf.eth0.send_redirects = 0\n", 556 | "EOF\n", 557 | "# Download nat_monitor.sh and configure\n", 558 | "cd /root\n", 559 | "wget http://media.amazonwebservices.com/articles/nat_monitor_files/nat_monitor.sh\n", 560 | "NAT_ID=\n", 561 | "# CloudFormation should have updated the PrivateRouteTable2 by now (due to yum update), however loop to make sure\n", 562 | "while [ \"$NAT_ID\" == \"\" ]; do\n", 563 | " sleep 60\n", 564 | " NAT_ID=`/opt/aws/bin/ec2-describe-route-tables ", 565 | { 566 | "Ref": "PrivateRouteTable2" 567 | }, 568 | " -U https://ec2.", 569 | { 570 | "Ref": "AWS::Region" 571 | }, 572 | ".amazonaws.com | grep 0.0.0.0/0 | awk '{print $2;}'`\n", 573 | " #echo `date` \"-- NAT_ID=$NAT_ID\" >> /tmp/test.log\n", 574 | "done\n", 575 | "# Update NAT_ID, NAT_RT_ID, and My_RT_ID\n", 576 | "sed \"s/NAT_ID=/NAT_ID=$NAT_ID/g\" /root/nat_monitor.sh > /root/nat_monitor.tmp\n", 577 | "sed \"s/NAT_RT_ID=/NAT_RT_ID=", 578 | { 579 | "Ref": "PrivateRouteTable2" 580 | }, 581 | "/g\" /root/nat_monitor.tmp > /root/nat_monitor.sh\n", 582 | "sed \"s/My_RT_ID=/My_RT_ID=", 583 | { 584 | "Ref": "PrivateRouteTable1" 585 | }, 586 | "/g\" /root/nat_monitor.sh > /root/nat_monitor.tmp\n", 587 | "sed \"s/EC2_URL=/EC2_URL=https:\\/\\/ec2.", 588 | { 589 | "Ref": "AWS::Region" 590 | }, 591 | ".amazonaws.com", 592 | "/g\" /root/nat_monitor.tmp > /root/nat_monitor.sh\n", 593 | "sed \"s/Num_Pings=3/Num_Pings=3", 594 | "/g\" /root/nat_monitor.sh > /root/nat_monitor.tmp\n", 595 | "sed \"s/Ping_Timeout=1/Ping_Timeout=1", 596 | "/g\" /root/nat_monitor.tmp > /root/nat_monitor.sh\n", 597 | "sed \"s/Wait_Between_Pings=2/Wait_Between_Pings=2", 598 | "/g\" /root/nat_monitor.sh > /root/nat_monitor.tmp\n", 599 | "sed \"s/Wait_for_Instance_Stop=60/Wait_for_Instance_Stop=60", 600 | "/g\" /root/nat_monitor.tmp > /root/nat_monitor.sh\n", 601 | "sed \"s/Wait_for_Instance_Start=300/Wait_for_Instance_Start=300", 602 | "/g\" /root/nat_monitor.sh > /root/nat_monitor.tmp\n", 603 | "mv /root/nat_monitor.tmp /root/nat_monitor.sh\n", 604 | "chmod a+x /root/nat_monitor.sh\n", 605 | "echo '@reboot /root/nat_monitor.sh > /tmp/nat_monitor.log' | crontab\n", 606 | "/root/nat_monitor.sh > /tmp/nat_monitor.log &\n" 607 | ] 608 | ] 609 | } 610 | } 611 | } 612 | }, 613 | "NAT2Instance": { 614 | "Type": "AWS::EC2::Instance", 615 | "Metadata": { 616 | "Comment1": "Create NAT #2" 617 | }, 618 | "Properties": { 619 | "InstanceType": "m1.small", 620 | "KeyName": { 621 | "Ref": "NatKeyName" 622 | }, 623 | "IamInstanceProfile": { 624 | "Ref": "NATRoleProfile" 625 | }, 626 | "SubnetId": { 627 | "Ref": "PubSubnet2" 628 | }, 629 | "SourceDestCheck": "false", 630 | "ImageId": { 631 | "Fn::FindInMap": [ 632 | "AWSNATAMI", 633 | { 634 | "Ref": "AWS::Region" 635 | }, 636 | "AMI" 637 | ] 638 | }, 639 | "SecurityGroupIds": [ 640 | { 641 | "Ref": "NATSecurityGroup" 642 | } 643 | ], 644 | "Tags": [ 645 | { 646 | "Key": "Name", 647 | "Value": "NAT #2" 648 | } 649 | ], 650 | "UserData": { 651 | "Fn::Base64": { 652 | "Fn::Join": [ 653 | "", 654 | [ 655 | "#!/bin/bash -v\n", 656 | "yum update -y aws*\n", 657 | "# Configure iptables\n", 658 | "/sbin/iptables -t nat -A POSTROUTING -o eth0 -s 0.0.0.0/0 -j MASQUERADE\n", 659 | "/sbin/iptables-save > /etc/sysconfig/iptables\n", 660 | "# Configure ip forwarding and redirects\n", 661 | "echo 1 > /proc/sys/net/ipv4/ip_forward && echo 0 > /proc/sys/net/ipv4/conf/eth0/send_redirects\n", 662 | "mkdir -p /etc/sysctl.d/\n", 663 | "cat < /etc/sysctl.d/nat.conf\n", 664 | "net.ipv4.ip_forward = 1\n", 665 | "net.ipv4.conf.eth0.send_redirects = 0\n", 666 | "EOF\n", 667 | "# Download nat_monitor.sh and configure\n", 668 | "cd /root\n", 669 | "wget http://media.amazonwebservices.com/articles/nat_monitor_files/nat_monitor.sh\n", 670 | "# Update NAT_ID, NAT_RT_ID, and My_RT_ID\n", 671 | "sed \"s/NAT_ID=/NAT_ID=", 672 | { 673 | "Ref": "NAT1Instance" 674 | }, 675 | "/g\" /root/nat_monitor.sh > /root/nat_monitor.tmp\n", 676 | "sed \"s/NAT_RT_ID=/NAT_RT_ID=", 677 | { 678 | "Ref": "PrivateRouteTable1" 679 | }, 680 | "/g\" /root/nat_monitor.tmp > /root/nat_monitor.sh\n", 681 | "sed \"s/My_RT_ID=/My_RT_ID=", 682 | { 683 | "Ref": "PrivateRouteTable2" 684 | }, 685 | "/g\" /root/nat_monitor.sh > /root/nat_monitor.tmp\n", 686 | "sed \"s/EC2_URL=/EC2_URL=https:\\/\\/ec2.", 687 | { 688 | "Ref": "AWS::Region" 689 | }, 690 | ".amazonaws.com", 691 | "/g\" /root/nat_monitor.tmp > /root/nat_monitor.sh\n", 692 | "sed \"s/Num_Pings=3/Num_Pings=3", 693 | "/g\" /root/nat_monitor.sh > /root/nat_monitor.tmp\n", 694 | "sed \"s/Ping_Timeout=1/Ping_Timeout=1", 695 | "/g\" /root/nat_monitor.tmp > /root/nat_monitor.sh\n", 696 | "sed \"s/Wait_Between_Pings=2/Wait_Between_Pings=2", 697 | "/g\" /root/nat_monitor.sh > /root/nat_monitor.tmp\n", 698 | "sed \"s/Wait_for_Instance_Stop=60/Wait_for_Instance_Stop=60", 699 | "/g\" /root/nat_monitor.tmp > /root/nat_monitor.sh\n", 700 | "sed \"s/Wait_for_Instance_Start=300/Wait_for_Instance_Start=300", 701 | "/g\" /root/nat_monitor.sh > /root/nat_monitor.tmp\n", 702 | "mv /root/nat_monitor.tmp /root/nat_monitor.sh\n", 703 | "chmod a+x /root/nat_monitor.sh\n", 704 | "echo '@reboot /root/nat_monitor.sh > /tmp/nat_monitor.log' | crontab\n", 705 | "/root/nat_monitor.sh >> /tmp/nat_monitor.log &\n" 706 | ] 707 | ] 708 | } 709 | } 710 | } 711 | }, 712 | "NATSecurityGroup": { 713 | "Type": "AWS::EC2::SecurityGroup", 714 | "Properties": { 715 | "GroupDescription": "Rules for allowing access to HA Nodes", 716 | "VpcId": { 717 | "Ref": "VPC" 718 | }, 719 | "SecurityGroupIngress": [ 720 | { 721 | "IpProtocol": "tcp", 722 | "FromPort": "22", 723 | "ToPort": "22", 724 | "CidrIp": "0.0.0.0/0" 725 | }, 726 | { 727 | "IpProtocol": "-1", 728 | "FromPort": "0", 729 | "ToPort": "65535", 730 | "CidrIp": "10.0.0.0/16" 731 | } 732 | ], 733 | "SecurityGroupEgress": [ 734 | { 735 | "IpProtocol": "-1", 736 | "FromPort": "0", 737 | "ToPort": "65535", 738 | "CidrIp": "0.0.0.0/0" 739 | } 740 | ] 741 | } 742 | }, 743 | "NATAllowICMP": { 744 | "Type": "AWS::EC2::SecurityGroupIngress", 745 | "Properties": { 746 | "GroupId": { 747 | "Ref": "NATSecurityGroup" 748 | }, 749 | "IpProtocol": "icmp", 750 | "FromPort": "-1", 751 | "ToPort": "-1", 752 | "SourceSecurityGroupId": { 753 | "Ref": "NATSecurityGroup" 754 | } 755 | } 756 | }, 757 | "BastionHost": { 758 | "Type": "AWS::EC2::Instance", 759 | "Properties": { 760 | "DisableApiTermination": "FALSE", 761 | "ImageId": "ami-892fe1fe", 762 | "InstanceType": "t2.micro", 763 | "KeyName": { 764 | "Ref": "BastionHostKeyName" 765 | }, 766 | "Monitoring": "false", 767 | "Tags": [ 768 | { 769 | "Key": "Name", 770 | "Value": "Bastion host" 771 | } 772 | ], 773 | "NetworkInterfaces": [ 774 | { 775 | "DeleteOnTermination": "true", 776 | "Description": "Primary network interface", 777 | "DeviceIndex": 0, 778 | "SubnetId": { 779 | "Ref": "PubSubnet1" 780 | }, 781 | "PrivateIpAddresses": [ 782 | { 783 | "PrivateIpAddress": "10.0.0.99", 784 | "Primary": "true" 785 | } 786 | ], 787 | "GroupSet": [ 788 | { 789 | "Ref": "BastionSG" 790 | } 791 | ] 792 | } 793 | ] 794 | } 795 | }, 796 | "BastionHostEIP": { 797 | "Type": "AWS::EC2::EIP", 798 | "Properties": { 799 | "Domain": "vpc", 800 | "InstanceId": { 801 | "Ref": "BastionHost" 802 | } 803 | } 804 | }, 805 | "BastionSG": { 806 | "Type": "AWS::EC2::SecurityGroup", 807 | "Properties": { 808 | "GroupDescription": "Security group for bastion host, can be used to allow ssh connections from particular IP addresses.", 809 | "VpcId": { 810 | "Ref": "VPC" 811 | }, 812 | "SecurityGroupIngress": [ 813 | { 814 | "IpProtocol": "tcp", 815 | "FromPort": "22", 816 | "ToPort": "22", 817 | "CidrIp": "0.0.0.0/0" 818 | } 819 | ], 820 | "SecurityGroupEgress": [ 821 | { 822 | "IpProtocol": "-1", 823 | "CidrIp": "0.0.0.0/0" 824 | } 825 | ], 826 | "Tags": [ 827 | { 828 | "Key": "Name", 829 | "Value": "Bastion server security group" 830 | } 831 | ] 832 | } 833 | }, 834 | "DemoBucket": { 835 | "Type": "AWS::S3::Bucket", 836 | "Properties": { 837 | "BucketName": { 838 | "Fn::Join": [ 839 | "", 840 | [ 841 | "demo-bucket-", 842 | { 843 | "Ref": "EnvName" 844 | } 845 | ] 846 | ] 847 | } 848 | } 849 | }, 850 | "BackendSG": { 851 | "Type": "AWS::EC2::SecurityGroup", 852 | "Properties": { 853 | "GroupDescription": "Backend security group", 854 | "VpcId": { 855 | "Ref": "VPC" 856 | }, 857 | "SecurityGroupEgress": [ 858 | { 859 | "IpProtocol": "-1", 860 | "CidrIp": "0.0.0.0/0" 861 | } 862 | ], 863 | "Tags": [ 864 | { 865 | "Key": "Name", 866 | "Value": "Backend security group" 867 | } 868 | ] 869 | } 870 | }, 871 | "BackendSGIngress1": { 872 | "Type": "AWS::EC2::SecurityGroupIngress", 873 | "Properties": { 874 | "GroupId": { 875 | "Ref": "BackendSG" 876 | }, 877 | "IpProtocol": "tcp", 878 | "FromPort": "22", 879 | "ToPort": "22", 880 | "SourceSecurityGroupId": { 881 | "Ref": "BastionSG" 882 | } 883 | } 884 | }, 885 | "BackendIAMRole": { 886 | "Type": "AWS::IAM::Role", 887 | "Properties": { 888 | "Policies": [ 889 | { 890 | "PolicyName": "root", 891 | "PolicyDocument": { 892 | "Version": "2012-10-17", 893 | "Statement": [ 894 | { 895 | "Effect": "Allow", 896 | "Action": [ 897 | "s3:*" 898 | ], 899 | "Resource": { 900 | "Fn::Join": [ 901 | "", 902 | [ 903 | "arn:aws:s3:::", 904 | { 905 | "Ref": "DemoBucket" 906 | }, 907 | "/*" 908 | ] 909 | ] 910 | } 911 | } 912 | ] 913 | } 914 | } 915 | ], 916 | "AssumeRolePolicyDocument": { 917 | "Version": "2012-10-17", 918 | "Statement": [ 919 | { 920 | "Effect": "Allow", 921 | "Principal": { 922 | "Service": [ 923 | "ec2.amazonaws.com" 924 | ] 925 | }, 926 | "Action": [ 927 | "sts:AssumeRole" 928 | ] 929 | } 930 | ] 931 | }, 932 | "Path": "/" 933 | } 934 | }, 935 | "BackendInstanceProfile": { 936 | "Type": "AWS::IAM::InstanceProfile", 937 | "Properties": { 938 | "Path": "/", 939 | "Roles": [ 940 | { 941 | "Ref": "BackendIAMRole" 942 | } 943 | ] 944 | } 945 | }, 946 | "BackendBeanstalkApp": { 947 | "Type": "AWS::ElasticBeanstalk::Application", 948 | "Properties": { 949 | "ApplicationName": "spring-boot-demo", 950 | "Description": "Backend Beanstalk demo application" 951 | } 952 | }, 953 | "BackendBeanstalkConfigTemplate": { 954 | "Type": "AWS::ElasticBeanstalk::ConfigurationTemplate", 955 | "Properties": { 956 | "ApplicationName": { 957 | "Ref": "BackendBeanstalkApp" 958 | }, 959 | "Description": "Backend app configuration template.", 960 | "SolutionStackName": "64bit Amazon Linux 2014.03 v1.0.3 running Tomcat 7 Java 7", 961 | "OptionSettings": [ 962 | { 963 | "Namespace": "aws:autoscaling:launchconfiguration", 964 | "OptionName": "IamInstanceProfile", 965 | "Value": { 966 | "Ref": "BackendInstanceProfile" 967 | } 968 | }, 969 | { 970 | "Namespace": "aws:autoscaling:launchconfiguration", 971 | "OptionName": "ImageId", 972 | "Value": "ami-2918e35e" 973 | }, 974 | { 975 | "Namespace": "aws:elasticbeanstalk:container:tomcat:jvmoptions", 976 | "OptionName": "Xmx", 977 | "Value": "1024m" 978 | }, 979 | { 980 | "Namespace": "aws:elasticbeanstalk:application:environment", 981 | "OptionName": "spring.profiles.active", 982 | "Value": { 983 | "Ref": "EnvName" 984 | } 985 | }, 986 | { 987 | "Namespace": "aws:elasticbeanstalk:application:environment", 988 | "OptionName": "aws.s3.demoBucket", 989 | "Value": { 990 | "Ref": "DemoBucket" 991 | } 992 | }, 993 | { 994 | "Namespace": "aws:autoscaling:launchconfiguration", 995 | "OptionName": "EC2KeyName", 996 | "Value": { 997 | "Ref": "BackendKeyName" 998 | } 999 | }, 1000 | { 1001 | "Namespace": "aws:autoscaling:launchconfiguration", 1002 | "OptionName": "InstanceType", 1003 | "Value": "m1.small" 1004 | }, 1005 | { 1006 | "Namespace": "aws:autoscaling:launchconfiguration", 1007 | "OptionName": "SecurityGroups", 1008 | "Value": { 1009 | "Ref": "BackendSG" 1010 | } 1011 | }, 1012 | { 1013 | "Namespace": "aws:ec2:vpc", 1014 | "OptionName": "VPCId", 1015 | "Value": { 1016 | "Ref": "VPC" 1017 | } 1018 | }, 1019 | { 1020 | "Namespace": "aws:ec2:vpc", 1021 | "OptionName": "Subnets", 1022 | "Value": { 1023 | "Fn::Join": [ 1024 | "", 1025 | [ 1026 | { 1027 | "Ref": "PriSubnet1" 1028 | }, 1029 | ", ", 1030 | { 1031 | "Ref": "PriSubnet2" 1032 | }, 1033 | ", ", 1034 | { 1035 | "Ref": "PriSubnet3" 1036 | } 1037 | ] 1038 | ] 1039 | } 1040 | }, 1041 | { 1042 | "Namespace": "aws:ec2:vpc", 1043 | "OptionName": "ELBSubnets", 1044 | "Value": { 1045 | "Fn::Join": [ 1046 | "", 1047 | [ 1048 | { 1049 | "Ref": "PubSubnet1" 1050 | }, 1051 | ", ", 1052 | { 1053 | "Ref": "PubSubnet2" 1054 | }, 1055 | ", ", 1056 | { 1057 | "Ref": "PubSubnet3" 1058 | } 1059 | ] 1060 | ] 1061 | } 1062 | }, 1063 | { 1064 | "Namespace": "aws:elb:loadbalancer", 1065 | "OptionName": "LoadBalancerHTTPPort", 1066 | "Value": "80" 1067 | } 1068 | ] 1069 | } 1070 | }, 1071 | "BackendEnvironment": { 1072 | "Type": "AWS::ElasticBeanstalk::Environment", 1073 | "Properties": { 1074 | "ApplicationName": { 1075 | "Ref": "BackendBeanstalkApp" 1076 | }, 1077 | "Description": "Demo environment", 1078 | "EnvironmentName": { 1079 | "Fn::Join": [ 1080 | "", 1081 | [ 1082 | "demo-", 1083 | { 1084 | "Ref": "EnvName" 1085 | } 1086 | ] 1087 | ] 1088 | }, 1089 | "TemplateName": { 1090 | "Ref": "BackendBeanstalkConfigTemplate" 1091 | }, 1092 | "VersionLabel": { 1093 | "Ref": "BackendInitialVersion" 1094 | } 1095 | } 1096 | }, 1097 | "BackendInitialVersion": { 1098 | "Type": "AWS::ElasticBeanstalk::ApplicationVersion", 1099 | "Properties": { 1100 | "ApplicationName": { 1101 | "Ref": "BackendBeanstalkApp" 1102 | }, 1103 | "Description": "Initial version", 1104 | "SourceBundle": { 1105 | "S3Bucket": { 1106 | "Ref": "SourceBundleS3Bucket" 1107 | }, 1108 | "S3Key": { 1109 | "Ref": "SourceBundleS3Key" 1110 | } 1111 | } 1112 | } 1113 | } 1114 | }, 1115 | "Outputs": { 1116 | "BastionHostPublicIp": { 1117 | "Description": "Public IP address of the bastion host", 1118 | "Value": { 1119 | "Ref": "BastionHostEIP" 1120 | } 1121 | }, 1122 | "ApplicationUrl": { 1123 | "Description": "URL of the app", 1124 | "Value": { 1125 | "Fn::GetAtt": ["BackendEnvironment", "EndpointURL"] 1126 | } 1127 | } 1128 | } 1129 | } -------------------------------------------------------------------------------- /src/test/java/demo/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.test.context.web.WebAppConfiguration; 6 | import org.springframework.boot.test.SpringApplicationConfiguration; 7 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 8 | 9 | @RunWith(SpringJUnit4ClassRunner.class) 10 | @SpringApplicationConfiguration(classes = Application.class) 11 | @WebAppConfiguration 12 | public class ApplicationTests { 13 | 14 | @Test 15 | public void contextLoads() { 16 | } 17 | 18 | } 19 | --------------------------------------------------------------------------------