Skip to content

Commit 424ddf3

Browse files
committed
first commit
1 parent 7446a67 commit 424ddf3

27 files changed

Lines changed: 1735 additions & 1 deletion

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
JUSTFORANDI/*

AWSDevOpsTutorialCloudFormationStack.json

Lines changed: 932 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,50 @@
11
# AWSDevOpsTutorial
2-
Unbreakable DevOps Pipeline Tutorial with AWS CodeDeploy, AWS CodePipeline, AWS Lambda, EC2 and Dynatrace
2+
Unbreakable DevOps Pipeline Tutorial with AWS CodeDeploy, AWS CodePipeline, AWS Lambda, EC2 and Dynatrace.
3+
4+
The goal of this tutorial is having a full end-to-end AWS DevOps Pipeline (Staging, Approval, Production) that is fully monitored with Dynatrace. With Dynatrace injected into the pipeline you get the following features
5+
1. Monitor your Staging Environment
6+
2. Automate Approve/Reject Promotion from Staging to Production based on Performance Data
7+
3. Monitor your Production Environment
8+
4. Automatic Deploy of previous revision in case Dynatrace detected problems in Production
9+
10+
Before we launch the CloudFormation stack which will create all required resources (EC2 Instances, Lambdas, CodeDeploy, CodePipeline, API Gateway) lets make sure we have all pre-requisits covered!
11+
12+
## Pre-Requisits
13+
1. You need an AWS account. If you dont have one [get one here](https://aws.amazon.com/)
14+
2. You need a Dynatrace Account. Get your [Free SaaS Trial here!](http://bit.ly/dtsaastrial)
15+
3. You need to clone or copy the content of this GitHub repo to your local disk!
16+
17+
## Preparation
18+
**Amazon**
19+
As we are going to use AWS CodeDeploy, AWS CodePipeline, AWS Lambda, DynamoDB, API Gateway and EC2 make sure the AWS Region you select provides all these services. We have tested this cloud formation on US-West-2a (Oregon) and US-East-2b (Ohio). To be on the safe side we suggest you pick one of these regions!
20+
21+
1. Create an EC2 Key Pair for your AWS Region! Our CloudFormation Template needs an EC2 Key Pair!
22+
1.1. To learn more about Key Pairs and how to connect to EC2 Instances for troubleshooting read [Connect to your Linux Instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AccessingInstances.html)
23+
2. Create a S3 Bucket with the naming scheme: <yourname>-dynatracedevops and enable versioning. See following screenshots for reference
24+
![](./images/preparation_creates3bucket.png)
25+
3. Copy the content from the folder "copytos3" to your newly created S3 bucket. This includes the application package, tests, monspec as well as all Lambda functions
26+
![](./images/preparation_copytos3.png)
27+
28+
**Dynatrace**
29+
We need a couple of things to launch the CloudFormation Template
30+
1. Your *Dynatrace Tenant URL*: For SaaS that would be something like http://<yourtenant>.live.dynatrace.com. For Managed it would be http://<yourserver>/e/<your-env-id>
31+
2. Your *Dynatrace OneAgent for Linux Download URL*: Go to Deploy Dynatrace -> Start Installation -> Linux and copy the URL within the quotes as shown below:
32+
![](./images/preparation_dynatraceoneagenturl.png)
33+
3. A *Dynatrace API Token*: Go to Settings -> Integration -> Dynatrace API and create a new Token
34+
![](./images/preparation_dynatraceapitoken.png)
35+
36+
## Lets create the CloudFormation Stack
37+
You can download the stack definition from [here](./AWSDevOpsTutorialCloudFormationStack.json) - or simply click on one of the following links which will directly get you to the CloudFormation Wizard for the respective region.
38+
39+
Region | Launch Template
40+
------------ | -------------
41+
**N. Virginia** (us-east-1) | [![Launch Dynatrace DevOps Stack into Virginia with CloudFormation](/Images/deploy-to-aws.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=dynatracedevopsstack&templateURL=https://github.com/Dynatrace/AWSDevOpsTutorial/AWSDevOpsTutorialCloudFormationStack.json)
42+
**Ohio** (us-east-2) | [![Launch Dynatrace DevOps Stack into Ohio with CloudFormation](/Images/deploy-to-aws.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-2#/stacks/new?stackName=dynatracedevopsstack&templateURL=https://github.com/Dynatrace/AWSDevOpsTutorial/AWSDevOpsTutorialCloudFormationStack.jsonn)
43+
**Oregon** (us-west-2) | [![Launch Dynatrace DevOps Stack into Oregon with CloudFormation](/Images/deploy-to-aws.png)](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?stackName=dynatracedevopsstack&templateURL=https://github.com/Dynatrace/AWSDevOpsTutorial/AWSDevOpsTutorialCloudFormationStack.json)
44+
**Ireland** (eu-west-1) | [![Launch Dynatrace DevOps Stack into Ireland with CloudFormation](/Images/deploy-to-aws.png)](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new?stackName=dynatracedevopsstack&templateURL=https://github.com/Dynatrace/AWSDevOpsTutorial/AWSDevOpsTutorialCloudFormationStack.json)
45+
**Frankfurt** (eu-central-1) | [![Launch Dynatrace DevOps Stack into Frankfurt with CloudFormation](/Images/deploy-to-aws.png)](https://console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks/new?stackName=dynatracedevopsstack&templateURL=https://github.com/Dynatrace/AWSDevOpsTutorial/AWSDevOpsTutorialCloudFormationStack.json)
46+
**Tokyo** (ap-northeast-1) | [![Launch Dynatrace DevOps Stack into Tokyo with CloudFormation](/Images/deploy-to-aws.png)](https://console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks/new?stackName=dynatracedevopsstack&templateURL=https://github.com/Dynatrace/AWSDevOpsTutorial/AWSDevOpsTutorialCloudFormationStack.json)
47+
**Seoul** (ap-northeast-2) | [![Launch Dynatrace DevOps Stack into Seoul with CloudFormation](/Images/deploy-to-aws.png)](https://console.aws.amazon.com/cloudformation/home?region=ap-northeast-2#/stacks/new?stackName=dynatracedevopsstack&templateURL=https://github.com/Dynatrace/AWSDevOpsTutorial/AWSDevOpsTutorialCloudFormationStack.json)
48+
**Singapore** (ap-southeast-1) | [![Launch Dynatrace DevOps Stack into Singapore with CloudFormation](/Images/deploy-to-aws.png)](https://console.aws.amazon.com/cloudformation/home?region=ap-southeast-1#/stacks/new?stackName=dynatracedevopsstack&templateURL=https://github.com/Dynatrace/AWSDevOpsTutorial/AWSDevOpsTutorialCloudFormationStack.json)
49+
**Sydney** (ap-southeast-2) | [![Launch Dynatrace DevOps Stack into Sydney with CloudFormation](/Images/deploy-to-aws.png)](https://console.aws.amazon.com/cloudformation/home?region=ap-southeast-2#/stacks/new?stackName=dynatracedevopsstack&templateURL=https://github.com/Dynatrace/AWSDevOpsTutorial/AWSDevOpsTutorialCloudFormationStack.json)
50+

app/app.js

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
var port = process.env.PORT || 80,
2+
http = require('http'),
3+
fs = require('fs'),
4+
os = require('os'),
5+
dttags = process.env.DT_TAGS || "<EMPTY>",
6+
dtcustprops = process.env.DT_CUSTOM_PROP || "<EMPTY>",
7+
dtclusterid = process.env.DT_CLUSTER_ID || "<EMPTY>",
8+
html = fs.readFileSync('index.html').toString().replace("HOSTNAME", os.hostname() + " with DT_TAGS=" + dttags + "\nDT_CUSTOM_PROP=" + dtcustprops + "\nDT_CLUSTER_ID=" + dtclusterid);
9+
10+
11+
// ======================================================================
12+
// Here are some global config entries that change the behavior of the app
13+
// ======================================================================
14+
var buildNumber = 1;
15+
var minSleep = 500;
16+
var requestCount = 0;
17+
var inProduction = false;
18+
var invokeRequestCount = 0;
19+
var failInvokeRequestPercentage = 0;
20+
21+
// ======================================================================
22+
// does some init checks and sets variables!
23+
// ======================================================================
24+
var init = function(newBuildNumber) {
25+
// CHECK IF WE ARE RUNNING "In Production"
26+
inProduction = process.env.DEPLOYMENT_GROUP_NAME && process.env.DEPLOYMENT_GROUP_NAME.startsWith("Production");
27+
28+
if(inProduction) {
29+
minSleep = 300; // we just simulate that production is a bit faster than staging, e.g: better hardware!
30+
}
31+
32+
// here are some "problems" we simulate for different builds. Builds are identified via Env Variable BUILD_NUMBER;
33+
// Build # | Problem
34+
// 1 | no problem
35+
// 2 | 50% of requests return HTTP 500 Status Code
36+
// 3 | back to normal
37+
// 4 | no problem in staging but problem in prod -> higher sleep time and 10% of requests fail
38+
// X | any other build number will run like 1 & 3
39+
if(newBuildNumber != null) {
40+
buildNumber = parseInt(newBuildNumber);
41+
}
42+
else if(process.env.BUILD_NUMBER && process.env.BUILD_NUMBER != null) {
43+
buildNumber = parseInt(process.env.BUILD_NUMBER);
44+
}
45+
46+
switch(buildNumber) {
47+
case 2:
48+
failInvokeRequestPercentage = 2;
49+
break;
50+
case 4:
51+
if(inProduction) {
52+
minSleep = minSleep * 2;
53+
failInvokeRequestPercentage = 10;
54+
}
55+
break;
56+
default:
57+
// everything normal here
58+
failInvokeRequestPercentage = 0;
59+
break;
60+
}
61+
62+
console.log("Init: " + buildNumber + "/" + failInvokeRequestPercentage);
63+
}
64+
65+
// ======================================================================
66+
// Background colors for our app depending on the build
67+
// ======================================================================
68+
var backgroundColors = ["#EEA53E", "#73A53E", "#FF0000", "#FFFF00", "#777777"]
69+
var getBackgroundColor = function() {
70+
var buildNumberForBackgroundColor = buildNumber;
71+
if(buildNumber == 0 || buildNumber > 4) buildNumberForBackgroundColor = 1;
72+
73+
return backgroundColors[buildNumberForBackgroundColor];
74+
}
75+
76+
77+
// ======================================================================
78+
// This is for logging
79+
// ======================================================================
80+
var logstream = fs.createWriteStream('./serviceoutput.log');
81+
var SEVERITY_DEBUG = "Debug";
82+
var SEVERITY_INFO = "Info";
83+
var SEVERITY_WARNING = "Warning";
84+
var SEVERITY_ERROR = "Error";
85+
86+
var log = function(severity, entry) {
87+
// console.log(entry);
88+
if (severity === SEVERITY_DEBUG) {
89+
// dont log debug
90+
} else {
91+
var logEntry = new Date().toISOString() + ' - ' + severity + " - " + entry + '\n';
92+
// fs.appendFileSync('./serviceoutput.log', new Date().toISOString() + ' - ' + severity + " - " + entry + '\n');
93+
logstream.write(logEntry);
94+
}
95+
};
96+
97+
// ======================================================================
98+
// Very inefficient way to "sleep"
99+
// ======================================================================
100+
function sleep(time) {
101+
if(time < minSleep) time = minSleep;
102+
var stop = new Date().getTime();
103+
while(new Date().getTime() < stop + time) {
104+
;
105+
}
106+
}
107+
108+
// ======================================================================
109+
// This is our main HttpServer Handler
110+
// ======================================================================
111+
var server = http.createServer(function (req, res) {
112+
if (req.method === 'POST') {
113+
var body = '';
114+
115+
req.on('data', function(chunk) {
116+
body += chunk;
117+
});
118+
119+
req.on('end', function() {
120+
if (req.url === '/') {
121+
log(SEVERITY_DEBUG, 'Received message: ' + body);
122+
} else if (req.url = '/scheduled') {
123+
log(SEVERITY_DEBUG, 'Received task ' + req.headers['x-aws-sqsd-taskname'] + ' scheduled at ' + req.headers['x-aws-sqsd-scheduled-at']);
124+
}
125+
126+
res.writeHead(200, 'OK', {'Content-Type': 'text/plain'});
127+
res.end();
128+
});
129+
} else if (req.url.startsWith("/api")) {
130+
var url = require('url').parse(req.url, true);
131+
var closeResponse = true;
132+
133+
// sleep a bit :-)
134+
var sleeptime = parseInt(url.query["sleep"]);
135+
if(sleeptime === 0) sleeptime = minSleep;
136+
log(SEVERITY_DEBUG, "Sleeptime: " + sleeptime);
137+
sleep(sleeptime);
138+
139+
// figure out which API call they want to execute
140+
var status = "Unkown API Call";
141+
if(url.pathname === "/api/sleeptime") {
142+
// Usage: /api/sleeptime?min=1234
143+
var sleepValue = parseInt(url.query["min"]);
144+
if(sleepValue >= 0 && sleepValue <= 10000) minSleep = sleepValue;
145+
status = "Minimum Sleep Time set to " + minSleep;
146+
}
147+
if(url.pathname === "/api/echo") {
148+
// Usage: /api/echo?text=your text to be echoed!
149+
status = "Thanks for saying: " + url.query["text"];
150+
}
151+
if(url.pathname === "/api/login") {
152+
// Usage: /api/login?username=your user name
153+
status = "Welcome " + url.query["username"];
154+
}
155+
if(url.pathname === "/api/invoke") {
156+
// count the invokes for failed requests
157+
var returnStatusCode = 200;
158+
if(failInvokeRequestPercentage > 0) {
159+
invokeRequestCount++;
160+
var failRequest = (invokeRequestCount % failInvokeRequestPercentage);
161+
if(failRequest == 0) {
162+
returnStatusCode = 500;
163+
invokeRequestCount = 0;
164+
}
165+
}
166+
167+
// Usage: /api/invoke?url=http://www.yourdomain.com
168+
var urlRequest = url.query["url"];
169+
status = "Trying to invoke remote call to: " + urlRequest;
170+
171+
var http = null;
172+
if(urlRequest.startsWith("https")) http = require("https");
173+
else http = require("http");
174+
closeResponse = false;
175+
var options = {
176+
host: urlRequest,
177+
path: '/'
178+
};
179+
var result = http.get(urlRequest, function(getResponse) {
180+
log(SEVERITY_DEBUG, 'STATUS: ' + getResponse.statusCode);
181+
log(SEVERITY_DEBUG, 'HEADERS: ' + JSON.stringify(getResponse.headers));
182+
183+
// Buffer the body entirely for processing as a whole.
184+
var bodyChunks = [];
185+
getResponse.on('data', function(chunk) {
186+
bodyChunks.push(chunk);
187+
}).on('end', function() {
188+
var body = Buffer.concat(bodyChunks);
189+
log(SEVERITY_DEBUG, 'BODY: ' + body);
190+
status = "Request to '" + url.query["url"] + "' returned with HTTP Status: " + getResponse.statusCode + " and response body length: " + body.length;
191+
res.writeHead(returnStatusCode, returnStatusCode == 200 ? 'OK' : 'ERROR', {'Content-Type': 'text/plain'});
192+
res.write(status);
193+
res.end();
194+
}).on('error', function(error) {
195+
status = "Request to '" + url.query["url"] + "' returned in an error: " + error;
196+
res.writeHead(returnStatusCode, returnStatusCode == 200 ? 'OK' : 'ERROR', {'Content-Type': 'text/plain'});
197+
res.write(status);
198+
res.end();
199+
log(SEVERITY_INFO, status);
200+
})
201+
});
202+
}
203+
if(url.pathname === "/api/version") {
204+
if (url.query["newBuildNumber"] && url.query["newBuildNumber"] != null) {
205+
init(url.query["newBuildNumber"]);
206+
}
207+
208+
// usage: /api/version
209+
// simply returns the build number as defined in BUILD_NUMBER env-variable which is specified
210+
status = "Running build number: " + buildNumber;
211+
}
212+
if(url.pathname === "/api/causeerror") {
213+
log(SEVERITY_ERROR, "somebody called /api/causeerror");
214+
status = "We just caused an error log entry"
215+
}
216+
217+
// only close response handler if we are done with work!
218+
if(closeResponse) {
219+
res.writeHead(200, 'OK', {'Content-Type': 'text/plain'});
220+
res.write(status);
221+
res.end();
222+
}
223+
}
224+
else
225+
{
226+
res.writeHead(200, 'OK', {'Content-Type': 'text/html'});
227+
228+
// replace buildnumber and background color
229+
var finalHtml = html.replace("BACKGROUND-COLOR", getBackgroundColor()).replace("BUILD_NUMBER", buildNumber);
230+
res.write(finalHtml);
231+
res.end();
232+
}
233+
234+
requestCount++;
235+
if(requestCount >= 100) {
236+
log(SEVERITY_INFO, "Just served another 100 requests!");
237+
requestCount = 0;
238+
}
239+
});
240+
241+
// first we initialize!
242+
init(null);
243+
244+
// Listen on port 80, IP defaults to 127.0.0.1
245+
server.listen(port);
246+
247+
// Put a friendly message on the terminal
248+
console.log('Server running at http://127.0.0.1:' + port + '/');
249+
log(SEVERITY_INFO, "Service is up and running - feed me with data!");

app/appspec.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
version: 0.0
2+
os: linux
3+
files:
4+
- source: index.html
5+
destination: /home/ec2-user/
6+
- source: app.js
7+
destination: /home/ec2-user/
8+
- source: package.json
9+
destination: /home/ec2-user/
10+
permissions:
11+
- object: /home/ec2-user/
12+
mode: 777
13+
type:
14+
- file
15+
hooks:
16+
BeforeInstall:
17+
- location: scripts/stop_server.sh
18+
timeout: 60
19+
runas: root
20+
ApplicationStart:
21+
- location: scripts/start_server.sh
22+
timeout: 60
23+
runas: root
24+
ApplicationStop:
25+
- location: scripts/stop_server.sh
26+
timeout: 60
27+
runas: root
28+

app/buildspec.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
version: 0.2
2+
3+
phases:
4+
install:
5+
commands:
6+
- echo Installing Mocha...
7+
pre_build:
8+
commands:
9+
- echo Installing source NPM dependencies...
10+
build:
11+
commands:
12+
- echo Build started on `date`
13+
post_build:
14+
commands:
15+
- echo Build completed on `date`
16+
artifacts:
17+
files:
18+
- app.js
19+
- index.html
20+
- package.json

0 commit comments

Comments
 (0)