AWS CDKでDynamoDB Tableを作成する際に、一緒にCloudWatch AlarmやAuto Scalingを設定する方法(他のリソースにも流用可能)

AWS CDKを使いDynamoDB Tableを作成する際にアラームやAuto Scalingを設定する方法を調べてまとめました。調べた結果、アラームに関してはTableに限らず他のAWSリソースであっても同様に設定が可能だとわかりました。 設定方法が主でどういった値に設定すべきかという話には触れません。
2020.02.05

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

おはようございます、加藤です。今月から大好きだったAWS CDKを使った業務を行う事になりワクワクしています!
今回ブログで紹介するのは、AWS CDKを使いDynamoDB Tableを作成する際にアラームやAuto Scalingを設定する方法です。設定方法が主でどういった値に設定すべきかという話には触れません。

tl;dr

サンプルコード: https://github.com/intercept6/cdk-sample-dynamodb-with-alarm

  • AWS CDKはリソースのメトリクスの取得とアラート発生の仕組みを持っている
  • DynamoDB Tableにはこの仕組みはまだ実装されていない
  • 実装済みのコンストラクタを参考に独自で対応
  • AutoScalingはメトリクスを意識せずに設定が出来て楽(ターゲット追跡ポリシーのみ確認)

今はこのブログの方法を使う必要があるが、今後DynamoDB Tableにアラート関係の仕組みが実装されれば、このブログの大部分は不要になるはずです。

CDKが持つメトリクス取得とアラート発生の仕組み

「DynamoDB Tableにメトリクスを設定したい!」という思いから、どのように解決策に至ったかをまとめます。
最初はドキュメントだろうと、aws-cloudwatch moduleを確認しました。

Metric objects can be constructed directly or are exposed by resources as attributes. Resources that expose metrics will have functions that look like metricXxx() which will return a Metric object, initialized with defaults that make sense.

For example, lambda.Function objects have the fn.metricErrors() method, which represents the amount of errors reported by that Lambda function:

上記を読んで、「リソースの属性として公開されていれば、そこから取り出せば良いし無ければ生成すれば良いよ」と理解しました。
DynamoDB Tableのコードを確認してみると特に公開されていないので生成を行います。公開されていないというのはLambda Functionのコードと比較をし判断しました。

メトリクスの生成

とにかく動くものが欲しかったので、スタックのクラス内にメトリクスオブジェクトを生成するスタティックメソッドを作成しました。
DynamoDB Table向けに作成しており、デフォルトの値をセットしています。分割代入なので利用時に任意の値で上書きが可能です。 

各メトリクスが何を意味するかは下記をご確認ください。
DynamoDB メトリクスとディメンション - Amazon DynamoDB 

private static metricAll(metricName: string, props?: MetricOptions): Metric {
  return new Metric({
    namespace: 'AWS/DynamoDB',
    metricName,
    ...props
  });
}
private static metricProvisionedWriteCapacityUnits(props? : MetricOptions) {
  return this.metricAll(
    'ProvisionedWriteCapacityUnits', 
    {
      statistic: 'avg',
      period: Duration.minutes(5),
      ...props
    }
    )
}
private static metricProvisionedReadCapacityUnits(props? : MetricOptions) {
  return this.metricAll(
    'ProvisionedReadCapacityUnits', 
    {
      statistic: 'avg',
      period: Duration.minutes(5),
      ...props
    }
    )
}
private static metricConsumedReadCapacityUnits(props? : MetricOptions) {
  return this.metricAll(
    'ConsumedReadCapacityUnits', 
    {
      statistic: 'sum',
      period: Duration.minutes(1),
      ...props
    }
    )
}
private static metricConsumedWriteCapacityUnits(props? : MetricOptions) {
  return this.metricAll(
    'ConsumedWriteCapacityUnits', 
    {
      statistic: 'sum',
      period: Duration.minutes(1),
      ...props
    }
    )
}

アラームの作成

メトリクスオブジェクトを使ってアラームを設定します。

  • Provisioned
  • Provisioned AutoScaling
  • PayPerRequest(On-demand)

上記の3パターンについて説明します。

Provisionedの場合

現在の読み取り/書き込みキャパシティユニットが一定の値を超えた時にアラートを設定します。アラートはSNS Topicへ送るところまで作成します。(サブスクライブを設定していないので、通知は行われません)

const topic = new Topic(this, 'Topic');
const snsAction = new SnsAction(topic);

const prvAlarmConsumedRCUHigh = new Alarm(this, 'ConsumedRCUHigh', {
    metric: DynamodbTableStack.metricConsumedReadCapacityUnits({
      dimensions: {
        TableName: table.tableName
      },
    }),
    threshold: 240.0,
    evaluationPeriods: 5,
    comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
    alarmDescription: 'DO NOT EDIT OR DELETE. It\'s created by AWS CDK',
  })
  prvAlarmConsumedRCUHigh.addAlarmAction({
    bind(scope, alarm) {
      return snsAction.bind(scope, alarm)
    }
  })

  const prvAlarmConsumedWCUHigh = new Alarm(this, 'ConsumedWCUHigh', {
    metric: DynamodbTableStack.metricConsumedWriteCapacityUnits({
      dimensions: {
        TableName: table.tableName
      }
    }),
    threshold: 240.0,
    evaluationPeriods: 5,
    comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
    alarmDescription: 'DO NOT EDIT OR DELETE. It\'s created by AWS CDK'
  })
  prvAlarmConsumedWCUHigh.addAlarmAction({
    bind(scope, alarm) {
      return snsAction.bind(scope, alarm)
    }
  })

Provisioned AutoScalingの場合

ターゲット追跡ポリシーでAutoScalingを設定します。Autoscalingを設定すると必要なアラートは自動で設定されます。
他に必要があれば、Provisionedの場合を参考に追加してください。

const readCapacity = table.autoScaleReadCapacity({
  minCapacity: 10,
  maxCapacity: 1000
});
readCapacity.scaleOnUtilization({
  targetUtilizationPercent: 60
});

const writeCapacity = table.autoScaleWriteCapacity({
  minCapacity: 10,
  maxCapacity: 1000
});
writeCapacity.scaleOnUtilization({
  targetUtilizationPercent: 60
});

ターゲット追跡ポリシー以外でAutoScalingを設定したい場合は下記をご確認ください。

aws-applicationautoscaling module · AWS CDK

PayPerRequestの場合

パラメータ以外は、Provisionedの場合と同じです。PayPerRequestは自動でスケールするので、スケールの為にキャパシティユニットを監視を監視する必要はありません。とはいえ、想定以上のキャパシティを使用している場合はB2Cのサービスが何かをきっかけに急激にアクセスが増えた、システムに問題がある無限ループが発生している等色々考えられるので、気づけないのは好ましくないです。

なので、アラートではなく通知という意味で設定すると良いと思います。

const topic = new Topic(this, 'Topic');
const snsAction = new SnsAction(topic);

const pprAlarmConsumedRCUHigh = new Alarm(this, 'ConsumedRCUHigh', {
  metric: DynamodbTableStack.metricConsumedReadCapacityUnits({
    dimensions: {
      TableName: table.tableName
    }
  }),
  threshold: 5.0,
  evaluationPeriods: 3,
  comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
  alarmDescription: 'DO NOT EDIT OR DELETE. It\'s created by AWS CDK'
})
pprAlarmConsumedRCUHigh.addAlarmAction({
  bind(scope, alarm) {
    return snsAction.bind(scope, alarm)
  }
})

const pprAlarmConsumedWCUHigh = new Alarm(this, 'ConsumedWCUHigh', {
  metric: DynamodbTableStack.metricConsumedWriteCapacityUnits({
    dimensions: {
      TableName: table.tableName
    }
  }),
  threshold: 5.0,
  evaluationPeriods: 3,
  comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
  alarmDescription: 'DO NOT EDIT OR DELETE. It\'s created by AWS CDK'
})
pprAlarmConsumedWCUHigh.addAlarmAction({
  bind(scope, alarm) {
    return snsAction.bind(scope, alarm)
  }
})

実際の設定とCDKの対応

サンプルを作っていて、AWS CDKでどこを変更すると何が変わるかわかりにくなぁ。。。と感じました。すぐ忘れてしまいそうなので、マネコンの日本語名と英語名の対応を表にまとめておきます。

番号 日本語 英語
1 しきい値 Threshold
2 名前空間 Namespace
3 メトリクス名 Metric name
4 [Dimensions] [Dimensions]
5 統計 Statistic
6 期間 Period
7 アラームを実行するデータポイント Datapoints to alarm
8 欠落データの処理 Missing data treatment
9 サンプル数が少ないパーセンタイル Percentiles with low samples

まとめ

わかってしまえば、メトリクスの取得・アラームの発生は簡単だった。DynamoDB Tableの場合で書いていますが、メトリクスオブジェクトの作成以降はどのリソースでも共有可能です。

冒頭にも書いたとおり、まずは動くものを作りたかったのでローカルでなんとかする方法を調べました。あるべき姿的な話だと、コントリビュートしてメトリクスオブジェクトを取得できるようにすべき。

テーブルに対してインスタンスメソッドを生やせば良いと思っていたけど、Lambda Functionのコード見ているとスタティックメソッドになっているので???ってなっています(ガイドライン的にはどちらもOKだけど、前者のほうが使い勝手良さそうに思える)
もうちょっと調べて方針決めれたら人生初コントリビュート決めたいお気持ちです。