Version 2 of the AWS Cloud Development Kit (CDK) shipped a couple weeks ago.
As part of that release AWS have separated stable and experimental cdk libraries. In this blog I am going to build out with some of te new tools and features to learn a little about how to use experimental constructs, and also to share some observability tooling setups for monitoring your websites.
The canary stack can be found here: https://github.com/simonireilly/canary-stack
Once deployed you will have a Dashboard and Alarm for an S3 bucket website.
The architecture that is built once complete is:
Lets begin.
I do the following to get the project configured:
mkdir canary
cd canary
npx [email protected] init app --language typescript
This creates the basic structure.
Previous modules would ship separately, this is still the case for experimental modules.
To add an experimental module you need to install it with yarn or npm.
yarn add @aws-cdk/aws-synthetics-alpha
You know, I could not recommend cdk-dia enough, with will give you a nice diagram of your stack:
yarn add --dev cdk-dia
Get started by adding a website to your stack:
mkdir -p lib/website
touch lib/website/index.html
In there you need some HTML, this will do:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Your website</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<main style="display: flex; flex-direction: column; align-items: center;">
<h1>
Welcome to the canary stack
</h1>
<p>
This is a simple single page website that show cases the aws-cdk.
</p>
</main>
</body>
</html>
Now you can deploy your website from the stack using 2 AWS buckets, one to hold the zipped files, and another to deploy as a public website.
import { Bucket } from 'aws-cdk-lib/aws-s3';
import { BucketDeployment, Source } from 'aws-cdk-lib/aws-s3-deployment';
import { Construct } from 'constructs';
import { Stack, StackProps, CfnOutput } from 'aws-cdk-lib';
import * as path from 'path';
export class CanaryStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const websiteBucket = new Bucket(this, 'WebsiteBucket', {
websiteIndexDocument: 'index.html',
publicReadAccess: true,
});
new BucketDeployment(this, 'DeployWebsite', {
sources: [Source.asset(path.join(__dirname, 'website'))],
destinationBucket: websiteBucket,
});
new CfnOutput(this, 'WebsiteUrl', {
value: websiteBucket.bucketWebsiteUrl,
});
}
}
The output from this will reveal your website URL:
✅ CanaryStack
Outputs:
CanaryStack.WebsiteUrl = http://canarystack-websitebucket75c24d94-z8brk94c8nvu.s3-website-eu-west-1.amazonaws.com
We want to be able to monitor our websites up time, to do that we can add a canary.
The canary code looks like this:
const canary = new Canary(this, 'MyCanary', {
schedule: Schedule.rate(Duration.minutes(5)),
test: Test.custom({
code: Code.fromAsset(path.join(__dirname, 'canary')),
handler: 'index.handler',
}),
runtime: Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_3,
environmentVariables: {
SITE_URL: websiteBucket.bucketWebsiteUrl,
},
});
You need to be specific about where you put the canary javascript. From what I can tell it follows the same patter as when building AWS layers.
mkdir -p lib/canary/nodejs/node_modules
touch lib/canary/nodejs/node_modules/index.js
Now you want to put the canary javascript into that file like so:
const synthetics = require('Synthetics');
const log = require('SyntheticsLogger');
const pageLoadBlueprint = async function () {
// Configure the stage of the API using environment variables
const url = String(process.env.SITE_URL);
const page = await synthetics.getPage();
const response = await page.goto(url, {
waitUntil: 'domcontentloaded',
timeout: 30000,
});
// Wait for page to render. Increase or decrease wait time based on endpoint being monitored.
await page.waitFor(15000);
// This will take a screenshot that will be included in test output artifacts.
await synthetics.takeScreenshot('loaded', 'loaded');
const pageTitle = await page.title();
log.info('Page title: ' + pageTitle);
if (response.status() !== 200) {
throw 'Failed to load page!';
}
};
exports.handler = async () => {
return await pageLoadBlueprint();
};
Now we can deploy again:
yarn cdk deploy
This time we will have a canary to go and see in the dashboard which looks like this:
Monitoring is hard, but it doesn't need to be hard to implement.
The CDK can make creating a dashboard and an alarm for the canary very easy.
Here is the code you need:
const alarm = new Alarm(this, 'CanaryAlarm', {
metric: canary.metricSuccessPercent(),
evaluationPeriods: 2,
threshold: 90,
comparisonOperator: ComparisonOperator.LESS_THAN_THRESHOLD,
});
const alarmWidget = new AlarmWidget({
alarm,
title: 'Canary Alarm',
});
const dashboard = new Dashboard(this, 'MainDashbaord', {
dashboardName: 'Main-Dashboard',
periodOverride: PeriodOverride.AUTO,
widgets: [[alarmWidget]],
});
Thats all it takes to get setup with a dashboard for your site.
AWS just launched Real User Monitoring (RUM). This is a client side tool that tracks the real user journey and page load analytics.
You can read about RUM in Jeff Barr's announcement blog post: https://aws.amazon.com/blogs/aws/cloudwatch-rum/
You should follow the wizard to set this up and chose the option to get a new cognito user pool.
Then you can copy the snippet produced into the <head>
tag within the lib/website/index.html
.
Now we deploy again:
yarn deploy
When we go to the rum dashboard we will see all the stats for a users session:
With a canary in place you have: