@guardian/cdk 48.4.1 → 48.5.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,8 @@
1
+ import { CfnRuleGroupsNamespace } from "aws-cdk-lib/aws-aps";
1
2
  import type { IMetric } from "aws-cdk-lib/aws-cloudwatch";
2
3
  import { Construct } from "constructs";
3
4
  import type { GuStack } from "../../constructs/core";
4
- export interface ErrorBudgetAlarmProps {
5
+ export interface CloudWatchErrorBudgetAlarmProps {
5
6
  /**
6
7
  * The name of the SLO. Assumed to be unique per stack.
7
8
  */
@@ -23,6 +24,21 @@ export interface ErrorBudgetAlarmProps {
23
24
  */
24
25
  snsTopicNameForAlerts: string;
25
26
  }
27
+ export interface PrometheusErrorBudgetAlarmProps {
28
+ /**
29
+ * The name of the SLO. Assumed to be unique per stack.
30
+ */
31
+ sloName: string;
32
+ /**
33
+ * The percentage target for the SLO, expressed as a decimal floating point number. Must be between 0.95 and 0.995.
34
+ */
35
+ sloTarget: number;
36
+ badEvents: string;
37
+ validEvents: string;
38
+ }
39
+ export declare class GuPrometheusErrorBudgetAlarmExperimental extends CfnRuleGroupsNamespace {
40
+ constructor(scope: GuStack, props: PrometheusErrorBudgetAlarmProps);
41
+ }
26
42
  /**
27
43
  * Use this construct to create alarms that will notify you when you are at risk of missing your SLO targets.
28
44
  *
@@ -52,6 +68,6 @@ export interface ErrorBudgetAlarmProps {
52
68
  * and for a description of the props that need to be passed see [[ErrorBudgetAlarmProps]]
53
69
  */
54
70
  export declare class GuErrorBudgetAlarmExperimental extends Construct {
55
- constructor(scope: GuStack, props: ErrorBudgetAlarmProps);
71
+ constructor(scope: GuStack, props: CloudWatchErrorBudgetAlarmProps);
56
72
  }
57
73
  //# sourceMappingURL=error-budget-alarm.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"error-budget-alarm.d.ts","sourceRoot":"","sources":["../../../src/experimental/constructs/error-budget-alarm.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAqB,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAa7E,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AA+BD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,8BAA+B,SAAQ,SAAS;gBAC/C,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,qBAAqB;CAsEzD"}
1
+ {"version":3,"file":"error-budget-alarm.d.ts","sourceRoot":"","sources":["../../../src/experimental/constructs/error-budget-alarm.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAqB,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAa7E,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,WAAW,+BAA+B;IAC9C;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,+BAA+B;IAC9C;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAsCD,qBAAa,wCAAyC,SAAQ,sBAAsB;gBACtE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,+BAA+B;CAuFnE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,8BAA+B,SAAQ,SAAS;gBAC/C,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,+BAA+B;CAsEnE"}
@@ -1,11 +1,92 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GuErrorBudgetAlarmExperimental = void 0;
3
+ exports.GuErrorBudgetAlarmExperimental = exports.GuPrometheusErrorBudgetAlarmExperimental = void 0;
4
4
  const aws_cdk_lib_1 = require("aws-cdk-lib");
5
+ const aws_aps_1 = require("aws-cdk-lib/aws-aps");
5
6
  const aws_cloudwatch_1 = require("aws-cdk-lib/aws-cloudwatch");
6
7
  const aws_cloudwatch_actions_1 = require("aws-cdk-lib/aws-cloudwatch-actions");
7
8
  const aws_sns_1 = require("aws-cdk-lib/aws-sns");
8
9
  const constructs_1 = require("constructs");
10
+ // If we use a single shared Prometheus instance in the Deploy Tools account, this construct would need to be deployed
11
+ // into that account only.
12
+ class GuPrometheusErrorBudgetAlarmExperimental extends aws_aps_1.CfnRuleGroupsNamespace {
13
+ constructor(scope, props) {
14
+ const lowestTargetAllowed = 0.95;
15
+ const highestTargetAllowed = 0.9995;
16
+ if (!(props.sloTarget >= lowestTargetAllowed && props.sloTarget <= highestTargetAllowed)) {
17
+ throw new Error(`ErrorBudgetAlarm only works with SLO targets between ${lowestTargetAllowed} and ${highestTargetAllowed}`);
18
+ }
19
+ const errorBudget = 1 - props.sloTarget;
20
+ const fastBurnRate = {
21
+ speed: "Fast",
22
+ burnRate: 14.4,
23
+ longWindow: "1h",
24
+ shortWindow: "5m",
25
+ };
26
+ const mediumBurnRate = {
27
+ speed: "Medium",
28
+ burnRate: 6,
29
+ longWindow: "6h",
30
+ shortWindow: "30m",
31
+ };
32
+ const slowBurnRate = {
33
+ speed: "Slow",
34
+ burnRate: 3,
35
+ longWindow: "1d",
36
+ shortWindow: "2h",
37
+ };
38
+ const recordingRulePrefix = `sli_for_${props.sloName}`;
39
+ const fastBurnShortWindowRule = `${recordingRulePrefix}_${fastBurnRate.shortWindow}`;
40
+ const fastBurnLongWindowRule = `${recordingRulePrefix}_${fastBurnRate.longWindow}`;
41
+ const mediumBurnShortWindowRule = `${recordingRulePrefix}_${mediumBurnRate.shortWindow}`;
42
+ const mediumBurnLongWindowRule = `${recordingRulePrefix}_${mediumBurnRate.longWindow}`;
43
+ const slowBurnShortWindowRule = `${recordingRulePrefix}_${slowBurnRate.shortWindow}`;
44
+ const slowBurnLongWindowRule = `${recordingRulePrefix}_${slowBurnRate.longWindow}`;
45
+ const fastBurnThreshold = errorBudget * fastBurnRate.burnRate;
46
+ const mediumBurnThreshold = errorBudget * mediumBurnRate.burnRate;
47
+ const slowBurnThreshold = errorBudget * slowBurnRate.burnRate;
48
+ // Currently we assume that the job label refers to a unique app. This works fine with our simple test data but will not scale out properly.
49
+ // Perhaps we will need to sum by (${props.app}, ${scope.stack}, ${scope.stage}) in order to scale up?
50
+ const prometheusRules = `
51
+ groups:
52
+ - name: multiwindow_recording_rules
53
+ rules:
54
+ - record: ${fastBurnShortWindowRule}
55
+ expr: sum by (job) (rate(${props.badEvents}[${fastBurnRate.shortWindow}])) / sum by (job) (rate(${props.validEvents}[${fastBurnRate.shortWindow}]))
56
+ - record: ${mediumBurnShortWindowRule}
57
+ expr: sum by (job) (rate(${props.badEvents}[${mediumBurnRate.shortWindow}])) / sum by (job) (rate(${props.validEvents}[${mediumBurnRate.shortWindow}]))
58
+ - record: ${fastBurnLongWindowRule}
59
+ expr: sum by (job) (rate(${props.badEvents}[${fastBurnRate.longWindow}])) / sum by (job) (rate(${props.validEvents}[${fastBurnRate.longWindow}]))
60
+ - record: ${slowBurnShortWindowRule}
61
+ expr: sum by (job) (rate(${props.badEvents}[${slowBurnRate.shortWindow}])) / sum by (job) (rate(${props.validEvents}[${slowBurnRate.shortWindow}]))
62
+ - record: ${mediumBurnLongWindowRule}
63
+ expr: sum by (job) (rate(${props.badEvents}[${mediumBurnRate.longWindow}])) / sum by (job) (rate(${props.validEvents}[${mediumBurnRate.longWindow}]))
64
+ - record: ${slowBurnLongWindowRule}
65
+ expr: sum by (job) (rate(${props.badEvents}[${slowBurnRate.longWindow}])) / sum by (job) (rate(${props.validEvents}[${slowBurnRate.longWindow}]))
66
+ - name: multiwindow_alerts
67
+ rules:
68
+ - alert: FastBurnAlarm
69
+ expr: (${fastBurnShortWindowRule} > ${fastBurnThreshold}) and (${fastBurnLongWindowRule} > ${fastBurnThreshold})
70
+ - alert: MediumBurnAlarm
71
+ expr: (${mediumBurnShortWindowRule} > ${mediumBurnThreshold}) and (${mediumBurnLongWindowRule} > ${mediumBurnThreshold})
72
+ - alert: SlowBurnAlarm
73
+ expr: (${slowBurnShortWindowRule} > ${slowBurnThreshold}) and (${slowBurnLongWindowRule} > ${slowBurnThreshold})
74
+ `;
75
+ // TODO: figure out how to do alert silencing for lower priority alerts - presumably via a separate config file?
76
+ super(scope, props.sloName, {
77
+ // TODO: it would probably be better to get this ARN from parameter store
78
+ workspace: `arn:aws:aps:${scope.region}:${scope.account}:workspace/ws-e4fe0f0b-2372-4309-901b-9aae06e4d9ba`,
79
+ // N.B. there is an AWS quota limit for unique namespaces. For this simple test we are using a namespace for a
80
+ // single SLO. This model will not scale. If AWS allow us to increase the namespace limit significantly we could
81
+ // potentially use a namespace per service. However, it might be better to model this as one namespace per team.
82
+ // In either case we'd need to generate the data section only (per SLO) and then combine the list of rules under a
83
+ // single namespace.
84
+ name: "test-cdk-rules",
85
+ data: prometheusRules,
86
+ });
87
+ }
88
+ }
89
+ exports.GuPrometheusErrorBudgetAlarmExperimental = GuPrometheusErrorBudgetAlarmExperimental;
9
90
  /**
10
91
  * Use this construct to create alarms that will notify you when you are at risk of missing your SLO targets.
11
92
  *
@@ -1 +1 @@
1
- {"version":3,"file":"error-budget-alarm.js","sourceRoot":"","sources":["../../../src/experimental/constructs/error-budget-alarm.ts"],"names":[],"mappings":"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","sourcesContent":["import { Duration } from \"aws-cdk-lib\";\nimport type { CfnCompositeAlarm, IMetric } from \"aws-cdk-lib/aws-cloudwatch\";\nimport {\n Alarm,\n AlarmRule,\n AlarmState,\n ComparisonOperator,\n CompositeAlarm,\n MathExpression,\n TreatMissingData,\n} from \"aws-cdk-lib/aws-cloudwatch\";\nimport { SnsAction } from \"aws-cdk-lib/aws-cloudwatch-actions\";\nimport type { ITopic } from \"aws-cdk-lib/aws-sns\";\nimport { Topic } from \"aws-cdk-lib/aws-sns\";\nimport { Construct } from \"constructs\";\nimport type { GuStack } from \"../../constructs/core\";\n\nexport interface ErrorBudgetAlarmProps {\n /**\n * The name of the SLO. Assumed to be unique per stack.\n */\n sloName: string;\n /**\n * The percentange target for the SLO, expressed as a decimal floating point number. Must be between 0.95 and 0.995.\n */\n sloTarget: number;\n /**\n * The metric representing the bad events. Can be any IMetric type, like Metric or MathExpression.\n */\n badEvents: IMetric;\n /**\n * The metric representing the events to be considered. Can be any IMetric type, like Metric or MathExpression.\n */\n validEvents: IMetric;\n /**\n * The sns topic name (not the full arn) used by the alarm to send email alerts.\n */\n snsTopicNameForAlerts: string;\n}\n\ninterface BurnRate {\n speed: \"Fast\" | \"Medium\" | \"Slow\";\n burnRate: number;\n}\n\ninterface BurnRateMonitoring extends BurnRate {\n longPeriod: Duration;\n shortPeriod: Duration;\n}\n\ninterface MonitorBurnRateForPeriodProps {\n alarmName: string;\n burnRateSpeed: BurnRate;\n period: Duration;\n errorBudget: number;\n badEvents: IMetric;\n validEvents: IMetric;\n}\n\ninterface CompositeBurnRateAlarmProps {\n sloName: string;\n burnRateMonitoring: BurnRateMonitoring;\n errorBudget: number;\n badEvents: IMetric;\n validEvents: IMetric;\n snsTopic: ITopic;\n suppressorAlarm?: CompositeBurnRateAlarm;\n}\n\n/**\n * Use this construct to create alarms that will notify you when you are at risk of missing your SLO targets.\n *\n * These alarms follow the recommendations in the [SRE Workbook](https://sre.google/workbook/alerting-on-slos/) to\n * implement Multiwindow, Multi-Burn-Rate Alerts.\n *\n * The CloudWatch implementation of this strategy creates nine separate CloudWatch alarms. The main alarms that users\n * need to care about are the three composite alarms. These track different burn rates of error budget consumption:\n * fast (e.g. complete outages), medium (e.g. moderate error percentage over hours, rather than minutes) and slow (e.g.\n * a small percentage of errors sustained over a much longer period). Multiple burn rates are used to ensure that\n * we are able to pick up all scenarios which could threaten the SLO, without interrupting developers with an\n * unnecessary level of urgency.\n *\n * Each composite alarm creates two child alarms; these are used to support multiple windows (or periods in CloudWatch\n * terminology). A composite alarm is configured to send notifications if both of its child alarms are in an alarm state.\n * The child alarms themselves have no actions/notifications configured and users should not need to interact with them\n * directly. Multiple windows are helpful for accuracy and reset time. The long window helps us to avoid sending an\n * alert in scenarios where no intervention is required, for example a very brief spike in errors that is resolved\n * automatically (e.g. when AWS replaces a 'bad' EC2 instance). The short window helps to ensure that the alert is reset\n * (or moved back into OK state, in CloudWatch terminology) promptly once a problem has been resolved.\n *\n * The composite alarms have an awareness of priority, meaning that low priority alarm notifications are suppressed if\n * a higher priority alarm is already firing. That is, if you receive an alert about fast error budget consumption, you\n * should not receive an alert from the medium or slow versions of the alarm.\n *\n * For how to instantiate an ErrorBudgetAlarm check the unit-test in error-budget-alarm.test.ts,\n * and for a description of the props that need to be passed see [[ErrorBudgetAlarmProps]]\n */\nexport class GuErrorBudgetAlarmExperimental extends Construct {\n constructor(scope: GuStack, props: ErrorBudgetAlarmProps) {\n const lowestTargetAllowed = 0.95;\n const highestTargetAllowed = 0.9995;\n if (!(props.sloTarget >= lowestTargetAllowed && props.sloTarget <= highestTargetAllowed)) {\n throw new Error(\n `ErrorBudgetAlarm only works with SLO targets between ${lowestTargetAllowed} and ${highestTargetAllowed}`\n );\n }\n\n super(scope, props.sloName); // The assumption here is that `sloName` will be unique (per stack)\n\n const errorBudget = 1 - props.sloTarget;\n\n const snsTopic: ITopic = Topic.fromTopicArn(\n scope,\n `SnsSloAlarmsFor${props.sloName}`,\n `arn:aws:sns:${scope.region}:${scope.account}:${props.snsTopicNameForAlerts}`\n );\n\n const fastBurnRate: BurnRateMonitoring = {\n speed: \"Fast\",\n burnRate: 14.4,\n longPeriod: Duration.hours(1),\n shortPeriod: Duration.minutes(5),\n };\n\n const fastBurnAlarm = new CompositeBurnRateAlarm(scope, {\n sloName: props.sloName,\n badEvents: props.badEvents,\n burnRateMonitoring: fastBurnRate,\n errorBudget,\n snsTopic,\n validEvents: props.validEvents,\n });\n\n const mediumBurnRateMonitoring: BurnRateMonitoring = {\n speed: \"Medium\",\n burnRate: 6,\n longPeriod: Duration.hours(6),\n shortPeriod: Duration.minutes(30),\n };\n\n const mediumBurnRateAlarm = new CompositeBurnRateAlarm(scope, {\n sloName: props.sloName,\n badEvents: props.badEvents,\n burnRateMonitoring: mediumBurnRateMonitoring,\n errorBudget,\n snsTopic,\n validEvents: props.validEvents,\n suppressorAlarm: fastBurnAlarm,\n });\n\n const slowBurnRate: BurnRateMonitoring = {\n speed: \"Slow\",\n burnRate: 3,\n longPeriod: Duration.days(1),\n shortPeriod: Duration.hours(2),\n };\n\n new CompositeBurnRateAlarm(scope, {\n sloName: props.sloName,\n badEvents: props.badEvents,\n burnRateMonitoring: slowBurnRate,\n errorBudget,\n // TODO - use a different topic for tickets or figure out how to route notifications vs tickets using a single topic\n snsTopic,\n validEvents: props.validEvents,\n suppressorAlarm: mediumBurnRateAlarm,\n });\n }\n}\n\nclass MonitorBurnRateForPeriod extends Alarm {\n constructor(scope: GuStack, props: MonitorBurnRateForPeriodProps) {\n const undesirableErrorBudgetConsumption = props.errorBudget * props.burnRateSpeed.burnRate;\n super(scope, props.alarmName, {\n alarmName: props.alarmName,\n metric: new MathExpression({\n expression: \"badEvents/validEvents\",\n label: \"Observed failure rate\",\n usingMetrics: {\n badEvents: props.badEvents,\n validEvents: props.validEvents,\n },\n period: props.period,\n }),\n evaluationPeriods: 1,\n threshold: undesirableErrorBudgetConsumption,\n comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,\n treatMissingData: TreatMissingData.NOT_BREACHING,\n });\n }\n}\n\nclass CompositeBurnRateAlarm extends CompositeAlarm {\n constructor(scope: GuStack, props: CompositeBurnRateAlarmProps) {\n const alarmName = `${props.sloName}${props.burnRateMonitoring.speed}BurnCompositeAlarm`;\n const sharedIndividualAlarmProps = {\n badEvents: props.badEvents,\n burnRateSpeed: {\n speed: props.burnRateMonitoring.speed,\n burnRate: props.burnRateMonitoring.burnRate,\n },\n errorBudget: props.errorBudget,\n validEvents: props.validEvents,\n };\n super(scope, alarmName, {\n alarmRule: AlarmRule.allOf(\n AlarmRule.fromAlarm(\n new MonitorBurnRateForPeriod(scope, {\n ...sharedIndividualAlarmProps,\n alarmName: `ChildAlarmLongPeriod${alarmName}`,\n period: props.burnRateMonitoring.longPeriod,\n }),\n AlarmState.ALARM\n ),\n AlarmRule.fromAlarm(\n new MonitorBurnRateForPeriod(scope, {\n ...sharedIndividualAlarmProps,\n alarmName: `ChildAlarmShortPeriod${alarmName}`,\n period: props.burnRateMonitoring.shortPeriod,\n }),\n AlarmState.ALARM\n )\n ),\n });\n this.addAlarmAction(new SnsAction(props.snsTopic));\n if (props.suppressorAlarm) {\n const cfnAlarm = this.node.defaultChild as CfnCompositeAlarm;\n cfnAlarm.actionsSuppressor = props.suppressorAlarm.alarmArn;\n cfnAlarm.actionsSuppressorWaitPeriod = 120;\n cfnAlarm.actionsSuppressorExtensionPeriod = props.burnRateMonitoring.shortPeriod.toSeconds();\n }\n }\n}\n"]}
1
+ {"version":3,"file":"error-budget-alarm.js","sourceRoot":"","sources":["../../../src/experimental/constructs/error-budget-alarm.ts"],"names":[],"mappings":"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","sourcesContent":["import { Duration } from \"aws-cdk-lib\";\nimport { CfnRuleGroupsNamespace } from \"aws-cdk-lib/aws-aps\";\nimport type { CfnCompositeAlarm, IMetric } from \"aws-cdk-lib/aws-cloudwatch\";\nimport {\n Alarm,\n AlarmRule,\n AlarmState,\n ComparisonOperator,\n CompositeAlarm,\n MathExpression,\n TreatMissingData,\n} from \"aws-cdk-lib/aws-cloudwatch\";\nimport { SnsAction } from \"aws-cdk-lib/aws-cloudwatch-actions\";\nimport type { ITopic } from \"aws-cdk-lib/aws-sns\";\nimport { Topic } from \"aws-cdk-lib/aws-sns\";\nimport { Construct } from \"constructs\";\nimport type { GuStack } from \"../../constructs/core\";\n\nexport interface CloudWatchErrorBudgetAlarmProps {\n /**\n * The name of the SLO. Assumed to be unique per stack.\n */\n sloName: string;\n /**\n * The percentange target for the SLO, expressed as a decimal floating point number. Must be between 0.95 and 0.995.\n */\n sloTarget: number;\n /**\n * The metric representing the bad events. Can be any IMetric type, like Metric or MathExpression.\n */\n badEvents: IMetric;\n /**\n * The metric representing the events to be considered. Can be any IMetric type, like Metric or MathExpression.\n */\n validEvents: IMetric;\n /**\n * The sns topic name (not the full arn) used by the alarm to send email alerts.\n */\n snsTopicNameForAlerts: string;\n}\n\nexport interface PrometheusErrorBudgetAlarmProps {\n /**\n * The name of the SLO. Assumed to be unique per stack.\n */\n sloName: string;\n /**\n * The percentage target for the SLO, expressed as a decimal floating point number. Must be between 0.95 and 0.995.\n */\n sloTarget: number;\n badEvents: string; // TODO: explain this once it is properly understood!\n validEvents: string; // TODO: explain this once it is properly understood!\n}\n\ninterface BurnRate {\n speed: \"Fast\" | \"Medium\" | \"Slow\";\n burnRate: number;\n}\n\ninterface CloudWatchBurnRateMonitoring extends BurnRate {\n longPeriod: Duration;\n shortPeriod: Duration;\n}\n\ninterface PrometheusBurnRateMonitoring extends BurnRate {\n longWindow: string;\n shortWindow: string;\n}\n\ninterface MonitorBurnRateForPeriodProps {\n alarmName: string;\n burnRateSpeed: BurnRate;\n period: Duration;\n errorBudget: number;\n badEvents: IMetric;\n validEvents: IMetric;\n}\n\ninterface CompositeBurnRateAlarmProps {\n sloName: string;\n burnRateMonitoring: CloudWatchBurnRateMonitoring;\n errorBudget: number;\n badEvents: IMetric;\n validEvents: IMetric;\n snsTopic: ITopic;\n suppressorAlarm?: CompositeBurnRateAlarm;\n}\n\n// If we use a single shared Prometheus instance in the Deploy Tools account, this construct would need to be deployed\n// into that account only.\nexport class GuPrometheusErrorBudgetAlarmExperimental extends CfnRuleGroupsNamespace {\n constructor(scope: GuStack, props: PrometheusErrorBudgetAlarmProps) {\n const lowestTargetAllowed = 0.95;\n const highestTargetAllowed = 0.9995;\n if (!(props.sloTarget >= lowestTargetAllowed && props.sloTarget <= highestTargetAllowed)) {\n throw new Error(\n `ErrorBudgetAlarm only works with SLO targets between ${lowestTargetAllowed} and ${highestTargetAllowed}`\n );\n }\n\n const errorBudget = 1 - props.sloTarget;\n\n const fastBurnRate: PrometheusBurnRateMonitoring = {\n speed: \"Fast\",\n burnRate: 14.4,\n longWindow: \"1h\",\n shortWindow: \"5m\",\n };\n\n const mediumBurnRate: PrometheusBurnRateMonitoring = {\n speed: \"Medium\",\n burnRate: 6,\n longWindow: \"6h\",\n shortWindow: \"30m\",\n };\n\n const slowBurnRate: PrometheusBurnRateMonitoring = {\n speed: \"Slow\",\n burnRate: 3,\n longWindow: \"1d\",\n shortWindow: \"2h\",\n };\n\n const recordingRulePrefix = `sli_for_${props.sloName}`;\n\n const fastBurnShortWindowRule = `${recordingRulePrefix}_${fastBurnRate.shortWindow}`;\n const fastBurnLongWindowRule = `${recordingRulePrefix}_${fastBurnRate.longWindow}`;\n const mediumBurnShortWindowRule = `${recordingRulePrefix}_${mediumBurnRate.shortWindow}`;\n const mediumBurnLongWindowRule = `${recordingRulePrefix}_${mediumBurnRate.longWindow}`;\n const slowBurnShortWindowRule = `${recordingRulePrefix}_${slowBurnRate.shortWindow}`;\n const slowBurnLongWindowRule = `${recordingRulePrefix}_${slowBurnRate.longWindow}`;\n\n const fastBurnThreshold: number = errorBudget * fastBurnRate.burnRate;\n const mediumBurnThreshold: number = errorBudget * mediumBurnRate.burnRate;\n const slowBurnThreshold: number = errorBudget * slowBurnRate.burnRate;\n\n // Currently we assume that the job label refers to a unique app. This works fine with our simple test data but will not scale out properly.\n // Perhaps we will need to sum by (${props.app}, ${scope.stack}, ${scope.stage}) in order to scale up?\n const prometheusRules = `\n groups:\n - name: multiwindow_recording_rules\n rules:\n - record: ${fastBurnShortWindowRule}\n expr: sum by (job) (rate(${props.badEvents}[${fastBurnRate.shortWindow}])) / sum by (job) (rate(${props.validEvents}[${fastBurnRate.shortWindow}]))\n - record: ${mediumBurnShortWindowRule}\n expr: sum by (job) (rate(${props.badEvents}[${mediumBurnRate.shortWindow}])) / sum by (job) (rate(${props.validEvents}[${mediumBurnRate.shortWindow}]))\n - record: ${fastBurnLongWindowRule}\n expr: sum by (job) (rate(${props.badEvents}[${fastBurnRate.longWindow}])) / sum by (job) (rate(${props.validEvents}[${fastBurnRate.longWindow}]))\n - record: ${slowBurnShortWindowRule}\n expr: sum by (job) (rate(${props.badEvents}[${slowBurnRate.shortWindow}])) / sum by (job) (rate(${props.validEvents}[${slowBurnRate.shortWindow}]))\n - record: ${mediumBurnLongWindowRule}\n expr: sum by (job) (rate(${props.badEvents}[${mediumBurnRate.longWindow}])) / sum by (job) (rate(${props.validEvents}[${mediumBurnRate.longWindow}]))\n - record: ${slowBurnLongWindowRule}\n expr: sum by (job) (rate(${props.badEvents}[${slowBurnRate.longWindow}])) / sum by (job) (rate(${props.validEvents}[${slowBurnRate.longWindow}]))\n - name: multiwindow_alerts\n rules:\n - alert: FastBurnAlarm\n expr: (${fastBurnShortWindowRule} > ${fastBurnThreshold}) and (${fastBurnLongWindowRule} > ${fastBurnThreshold})\n - alert: MediumBurnAlarm\n expr: (${mediumBurnShortWindowRule} > ${mediumBurnThreshold}) and (${mediumBurnLongWindowRule} > ${mediumBurnThreshold})\n - alert: SlowBurnAlarm\n expr: (${slowBurnShortWindowRule} > ${slowBurnThreshold}) and (${slowBurnLongWindowRule} > ${slowBurnThreshold})\n `;\n\n // TODO: figure out how to do alert silencing for lower priority alerts - presumably via a separate config file?\n\n super(scope, props.sloName, {\n // TODO: it would probably be better to get this ARN from parameter store\n workspace: `arn:aws:aps:${scope.region}:${scope.account}:workspace/ws-e4fe0f0b-2372-4309-901b-9aae06e4d9ba`,\n // N.B. there is an AWS quota limit for unique namespaces. For this simple test we are using a namespace for a\n // single SLO. This model will not scale. If AWS allow us to increase the namespace limit significantly we could\n // potentially use a namespace per service. However, it might be better to model this as one namespace per team.\n // In either case we'd need to generate the data section only (per SLO) and then combine the list of rules under a\n // single namespace.\n name: \"test-cdk-rules\",\n data: prometheusRules,\n });\n }\n}\n\n/**\n * Use this construct to create alarms that will notify you when you are at risk of missing your SLO targets.\n *\n * These alarms follow the recommendations in the [SRE Workbook](https://sre.google/workbook/alerting-on-slos/) to\n * implement Multiwindow, Multi-Burn-Rate Alerts.\n *\n * The CloudWatch implementation of this strategy creates nine separate CloudWatch alarms. The main alarms that users\n * need to care about are the three composite alarms. These track different burn rates of error budget consumption:\n * fast (e.g. complete outages), medium (e.g. moderate error percentage over hours, rather than minutes) and slow (e.g.\n * a small percentage of errors sustained over a much longer period). Multiple burn rates are used to ensure that\n * we are able to pick up all scenarios which could threaten the SLO, without interrupting developers with an\n * unnecessary level of urgency.\n *\n * Each composite alarm creates two child alarms; these are used to support multiple windows (or periods in CloudWatch\n * terminology). A composite alarm is configured to send notifications if both of its child alarms are in an alarm state.\n * The child alarms themselves have no actions/notifications configured and users should not need to interact with them\n * directly. Multiple windows are helpful for accuracy and reset time. The long window helps us to avoid sending an\n * alert in scenarios where no intervention is required, for example a very brief spike in errors that is resolved\n * automatically (e.g. when AWS replaces a 'bad' EC2 instance). The short window helps to ensure that the alert is reset\n * (or moved back into OK state, in CloudWatch terminology) promptly once a problem has been resolved.\n *\n * The composite alarms have an awareness of priority, meaning that low priority alarm notifications are suppressed if\n * a higher priority alarm is already firing. That is, if you receive an alert about fast error budget consumption, you\n * should not receive an alert from the medium or slow versions of the alarm.\n *\n * For how to instantiate an ErrorBudgetAlarm check the unit-test in error-budget-alarm.test.ts,\n * and for a description of the props that need to be passed see [[ErrorBudgetAlarmProps]]\n */\nexport class GuErrorBudgetAlarmExperimental extends Construct {\n constructor(scope: GuStack, props: CloudWatchErrorBudgetAlarmProps) {\n const lowestTargetAllowed = 0.95;\n const highestTargetAllowed = 0.9995;\n if (!(props.sloTarget >= lowestTargetAllowed && props.sloTarget <= highestTargetAllowed)) {\n throw new Error(\n `ErrorBudgetAlarm only works with SLO targets between ${lowestTargetAllowed} and ${highestTargetAllowed}`\n );\n }\n\n super(scope, props.sloName); // The assumption here is that `sloName` will be unique (per stack)\n\n const errorBudget = 1 - props.sloTarget;\n\n const snsTopic: ITopic = Topic.fromTopicArn(\n scope,\n `SnsSloAlarmsFor${props.sloName}`,\n `arn:aws:sns:${scope.region}:${scope.account}:${props.snsTopicNameForAlerts}`\n );\n\n const fastBurnRate: CloudWatchBurnRateMonitoring = {\n speed: \"Fast\",\n burnRate: 14.4,\n longPeriod: Duration.hours(1),\n shortPeriod: Duration.minutes(5),\n };\n\n const fastBurnAlarm = new CompositeBurnRateAlarm(scope, {\n sloName: props.sloName,\n badEvents: props.badEvents,\n burnRateMonitoring: fastBurnRate,\n errorBudget,\n snsTopic,\n validEvents: props.validEvents,\n });\n\n const mediumBurnRateMonitoring: CloudWatchBurnRateMonitoring = {\n speed: \"Medium\",\n burnRate: 6,\n longPeriod: Duration.hours(6),\n shortPeriod: Duration.minutes(30),\n };\n\n const mediumBurnRateAlarm = new CompositeBurnRateAlarm(scope, {\n sloName: props.sloName,\n badEvents: props.badEvents,\n burnRateMonitoring: mediumBurnRateMonitoring,\n errorBudget,\n snsTopic,\n validEvents: props.validEvents,\n suppressorAlarm: fastBurnAlarm,\n });\n\n const slowBurnRate: CloudWatchBurnRateMonitoring = {\n speed: \"Slow\",\n burnRate: 3,\n longPeriod: Duration.days(1),\n shortPeriod: Duration.hours(2),\n };\n\n new CompositeBurnRateAlarm(scope, {\n sloName: props.sloName,\n badEvents: props.badEvents,\n burnRateMonitoring: slowBurnRate,\n errorBudget,\n // TODO - use a different topic for tickets or figure out how to route notifications vs tickets using a single topic\n snsTopic,\n validEvents: props.validEvents,\n suppressorAlarm: mediumBurnRateAlarm,\n });\n }\n}\n\nclass MonitorBurnRateForPeriod extends Alarm {\n constructor(scope: GuStack, props: MonitorBurnRateForPeriodProps) {\n const undesirableErrorBudgetConsumption = props.errorBudget * props.burnRateSpeed.burnRate;\n super(scope, props.alarmName, {\n alarmName: props.alarmName,\n metric: new MathExpression({\n expression: \"badEvents/validEvents\",\n label: \"Observed failure rate\",\n usingMetrics: {\n badEvents: props.badEvents,\n validEvents: props.validEvents,\n },\n period: props.period,\n }),\n evaluationPeriods: 1,\n threshold: undesirableErrorBudgetConsumption,\n comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,\n treatMissingData: TreatMissingData.NOT_BREACHING,\n });\n }\n}\n\nclass CompositeBurnRateAlarm extends CompositeAlarm {\n constructor(scope: GuStack, props: CompositeBurnRateAlarmProps) {\n const alarmName = `${props.sloName}${props.burnRateMonitoring.speed}BurnCompositeAlarm`;\n const sharedIndividualAlarmProps = {\n badEvents: props.badEvents,\n burnRateSpeed: {\n speed: props.burnRateMonitoring.speed,\n burnRate: props.burnRateMonitoring.burnRate,\n },\n errorBudget: props.errorBudget,\n validEvents: props.validEvents,\n };\n super(scope, alarmName, {\n alarmRule: AlarmRule.allOf(\n AlarmRule.fromAlarm(\n new MonitorBurnRateForPeriod(scope, {\n ...sharedIndividualAlarmProps,\n alarmName: `ChildAlarmLongPeriod${alarmName}`,\n period: props.burnRateMonitoring.longPeriod,\n }),\n AlarmState.ALARM\n ),\n AlarmRule.fromAlarm(\n new MonitorBurnRateForPeriod(scope, {\n ...sharedIndividualAlarmProps,\n alarmName: `ChildAlarmShortPeriod${alarmName}`,\n period: props.burnRateMonitoring.shortPeriod,\n }),\n AlarmState.ALARM\n )\n ),\n });\n this.addAlarmAction(new SnsAction(props.snsTopic));\n if (props.suppressorAlarm) {\n const cfnAlarm = this.node.defaultChild as CfnCompositeAlarm;\n cfnAlarm.actionsSuppressor = props.suppressorAlarm.alarmArn;\n cfnAlarm.actionsSuppressorWaitPeriod = 120;\n cfnAlarm.actionsSuppressorExtensionPeriod = props.burnRateMonitoring.shortPeriod.toSeconds();\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@guardian/cdk",
3
3
  "description": "Generic Guardian flavoured AWS CDK components",
4
- "version": "48.4.1",
4
+ "version": "48.5.0-beta.1",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
7
  "files": [
@@ -30,7 +30,7 @@
30
30
  "devDependencies": {
31
31
  "@guardian/eslint-config-typescript": "1.0.11",
32
32
  "@types/git-url-parse": "^9.0.1",
33
- "@types/jest": "^29.2.0",
33
+ "@types/jest": "^29.2.2",
34
34
  "@types/js-yaml": "^4.0.5",
35
35
  "@types/lodash.camelcase": "^4.3.7",
36
36
  "@types/lodash.kebabcase": "^4.1.7",
@@ -42,7 +42,7 @@
42
42
  "eslint": "^8.27.0",
43
43
  "eslint-plugin-custom-rules": "file:tools/eslint",
44
44
  "gh-pages": "^4.0.0",
45
- "jest": "^29.2.1",
45
+ "jest": "^29.3.1",
46
46
  "prettier": "^2.7.1",
47
47
  "semantic-release": "^19.0.5",
48
48
  "serve": "^14.1.1",
@@ -54,9 +54,9 @@
54
54
  "dependencies": {
55
55
  "@oclif/core": "1.20.4",
56
56
  "aws-cdk-lib": "2.50.0",
57
- "aws-sdk": "^2.1250.0",
57
+ "aws-sdk": "^2.1252.0",
58
58
  "chalk": "^4.1.2",
59
- "codemaker": "^1.70.0",
59
+ "codemaker": "^1.71.0",
60
60
  "constructs": "10.1.155",
61
61
  "git-url-parse": "^13.1.0",
62
62
  "js-yaml": "^4.1.0",
@@ -64,7 +64,7 @@
64
64
  "lodash.kebabcase": "^4.1.1",
65
65
  "lodash.upperfirst": "^4.3.1",
66
66
  "read-pkg-up": "7.0.1",
67
- "yargs": "^17.6.0"
67
+ "yargs": "^17.6.2"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "aws-cdk": "2.50.0",