UNPKG

caccl-deploy

Version:

A cli tool for managing ECS/Fargate app deployments

311 lines (280 loc) 8.95 kB
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', }), ); } }