├── .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 |
--------------------------------------------------------------------------------