caccl-deploy
Version:
A cli tool for managing ECS/Fargate app deployments
311 lines (280 loc) • 8.95 kB
text/typescript
import {
aws_cloudwatch as cloudwatch,
aws_elasticloadbalancingv2 as elb,
CfnOutput,
Stack,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { CacclDbBase } from './db';
import { CacclLoadBalancer } from './lb';
import { CacclScheduledTasks } from './scheduledTasks';
import { CacclService } from './service';
export interface CacclMonitoringProps {
cacclLoadBalancer: CacclLoadBalancer;
cacclService: CacclService;
}
export class CacclMonitoring extends Construct {
dashboard: cloudwatch.Dashboard;
region: string;
constructor(scope: Construct, id: string, props: CacclMonitoringProps) {
super(scope, id);
const { stackName } = Stack.of(this);
const { cacclLoadBalancer, cacclService } = props;
const { loadBalancer } = cacclLoadBalancer;
const { ecsService } = cacclService;
const dashboardName = `${stackName}-metrics`;
this.dashboard = new cloudwatch.Dashboard(this, 'Dashboard', {
dashboardName,
});
this.region = Stack.of(this).region;
const dashboardLink = `https://console.aws.amazon.com/cloudwatch/home?region=${this.region}#dashboards:name=${dashboardName}`;
new CfnOutput(this, 'DashboardLink', {
value: dashboardLink,
exportName: `${stackName}-cloudwatch-dashboard-link`,
});
const lbLink = `https://console.aws.amazon.com/ec2/v2/home?region=${this.region}#LoadBalancers:tag:caccl_deploy_stack_name=${stackName}`;
this.dashboard.addWidgets(
new cloudwatch.TextWidget({
markdown: [
`### Load Balancer: [${loadBalancer.loadBalancerName}](${lbLink})`,
'[Explanation of Metrics](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-cloudwatch-metrics.html)',
].join(' | '),
width: 24,
height: 1,
}),
);
this.dashboard.addWidgets(
new cloudwatch.GraphWidget({
title: 'RequestCount',
left: [cacclLoadBalancer.metrics.RequestCount],
width: 12,
height: 6,
}),
new cloudwatch.GraphWidget({
title: 'TargetResponseTime',
left: [cacclLoadBalancer.metrics.TargetResponseTime],
width: 12,
height: 6,
}),
);
this.dashboard.addWidgets(
new cloudwatch.AlarmStatusWidget({
alarms: cacclLoadBalancer.alarms,
height: 6,
width: 8,
title: 'Load Balancer Alarm States',
}),
new cloudwatch.GraphWidget({
title: 'NewConnectionCount',
left: [cacclLoadBalancer.metrics.NewConnectionCount],
width: 8,
height: 6,
}),
new cloudwatch.GraphWidget({
title: 'ActiveConnectionCount',
left: [cacclLoadBalancer.metrics.ActiveConnectionCount],
width: 8,
height: 6,
}),
);
const httpCodeWidgets = ['2', '3', '4', '5'].map((i) => {
const metricName = `HTTP ${i}xx Count`;
const httpCode = `TARGET_${i}XX_COUNT` as keyof typeof elb.HttpCodeTarget;
return new cloudwatch.GraphWidget({
title: metricName,
left: [loadBalancer.metricHttpCodeTarget(elb.HttpCodeTarget[httpCode])],
});
});
this.dashboard.addWidgets(...httpCodeWidgets);
const serviceLink = `https://console.aws.amazon.com/ecs/home?region=${this.region}#/clusters/${ecsService.cluster.clusterName}/services/${ecsService.serviceName}/details`;
this.dashboard.addWidgets(
new cloudwatch.TextWidget({
markdown: `### ECS Service: [${ecsService.serviceName}](${serviceLink})`,
width: 24,
height: 1,
}),
);
const makeCIMetric = (metricName: string, extraProps = {}) => {
const metric = new cloudwatch.Metric({
metricName,
namespace: 'ECS/ContainerInsights',
dimensionsMap: {
ClusterName: ecsService.cluster.clusterName,
ServiceName: ecsService.serviceName,
},
...extraProps,
});
metric.attachTo(ecsService);
return metric;
};
this.dashboard.addWidgets(
new cloudwatch.GraphWidget({
title: 'CPUUtilization',
left: [
makeCIMetric('CpuUtilized', { unit: cloudwatch.Unit.PERCENT }),
makeCIMetric('CpuReserved', { unit: cloudwatch.Unit.PERCENT }),
],
width: 12,
height: 6,
}),
new cloudwatch.GraphWidget({
title: 'MemoryUtilization',
left: [makeCIMetric('MemoryUtilized'), makeCIMetric('MemoryReserved')],
width: 12,
height: 6,
}),
);
const servcieAlarmWidget = [];
if (cacclService.alarms.length) {
servcieAlarmWidget.push(
new cloudwatch.AlarmStatusWidget({
alarms: cacclService.alarms,
width: 8,
height: 6,
title: 'ECS Service Alarm States',
}),
);
}
this.dashboard.addWidgets(
...servcieAlarmWidget,
new cloudwatch.GraphWidget({
title: 'Storage Read/Write Bytes',
left: [makeCIMetric('StorageReadBytes')],
right: [makeCIMetric('StorageWriteBytes')],
width: 12,
height: 6,
}),
new cloudwatch.GraphWidget({
title: 'Tasks & Deployments',
left: [
makeCIMetric('DesiredTaskCount'),
makeCIMetric('PendingTaskCount'),
makeCIMetric('RunningTaskCount'),
],
right: [ecsService.metric('DeploymentCount')],
width: 12,
height: 6,
}),
);
const makeLogLink = (logGroup: string) => {
const escapedLg = logGroup.split('/').join('$252F');
return `* [${logGroup}](https://console.aws.amazon.com/cloudwatch/home?region=${this.region}#logsV2:log-groups/log-group/${escapedLg})`;
};
this.dashboard.addWidgets(
new cloudwatch.TextWidget({
markdown: [
'### Logs\n',
makeLogLink(`/${stackName}/app`),
makeLogLink(`/${stackName}/proxy`),
].join('\n'),
width: 24,
height: 4,
}),
);
}
addDbSection(db: CacclDbBase): void {
const { dbCluster } = db;
this.dashboard.addWidgets(
new cloudwatch.TextWidget({
markdown: `### Database Cluster: [${
dbCluster.clusterIdentifier
}](${db.getDashboardLink()})`,
width: 24,
height: 1,
}),
);
this.dashboard.addWidgets(
new cloudwatch.GraphWidget({
title: 'Read/Write IOPS',
left: db.metrics.ReadIOPS,
right: db.metrics.WriteIOPS,
width: 12,
height: 6,
}),
new cloudwatch.GraphWidget({
title: 'CPU & Memory',
left: db.metrics.CPUUtilization,
right: db.metrics.FreeableMemory,
width: 12,
height: 6,
}),
);
this.dashboard.addWidgets(
new cloudwatch.GraphWidget({
title: 'Read/Write Latency',
left: db.metrics.ReadLatency,
right: db.metrics.WriteLatency,
width: 12,
height: 6,
}),
new cloudwatch.GraphWidget({
title: 'Transactions/Queries',
left: db.metrics.Transactions,
right: db.metrics.Queries,
width: 12,
height: 6,
}),
);
this.dashboard.addWidgets(
new cloudwatch.AlarmStatusWidget({
alarms: db.alarms,
width: 24,
height: 6,
title: 'Database Alarm States',
}),
);
this.dashboard.addWidgets(
new cloudwatch.GraphWidget({
left: db.metrics.BufferCacheHitRatio,
}),
new cloudwatch.GraphWidget({
left: db.metrics.DatabaseConnections,
}),
new cloudwatch.GraphWidget({
left: db.metrics.DiskQueueDepth,
}),
new cloudwatch.GraphWidget({
left: db.metrics.DatabaseCursorsTimedOut,
}),
);
}
addScheduledTasksSection(scheduledTasks: CacclScheduledTasks): void {
const func = scheduledTasks.taskExecFunction;
const functionUrl = `https://console.aws.amazon.com/lambda/home?region=${this.region}#/functions/${func.functionName}`;
this.dashboard.addWidgets(
new cloudwatch.TextWidget({
markdown: `### Scheduled Tasks Function: [${func.functionName}](${functionUrl})`,
width: 24,
height: 1,
}),
);
this.dashboard.addWidgets(
new cloudwatch.GraphWidget({
title: 'Duration',
left: [func.metricDuration()],
width: 8,
height: 6,
}),
new cloudwatch.GraphWidget({
title: 'Invocations',
left: [func.metricInvocations()],
width: 8,
height: 6,
}),
new cloudwatch.GraphWidget({
title: 'Errors',
left: [func.metricErrors()],
width: 8,
height: 6,
}),
);
this.dashboard.addWidgets(
new cloudwatch.AlarmStatusWidget({
alarms: scheduledTasks.alarms,
width: 24,
height: 6,
title: 'Scheduled Tasks Function Alarm States',
}),
);
}
}