AWS CDKでLambdaとSQSのエラー送信機能を構築

# AWS CDKとは
AWS CDK（Cloud Development Kit）は、Amazon Web Services（AWS）のクラウドリソースをプログラム的に構築およびデプロイするためのフレームワークです。CDKを使用すると、プログラミング言語（TypeScript、Python、Java、C#など）を使用して、AWSインフラストラクチャのコードを記述できます。CDKは、AWSリソースのプロビジョニングと設定をコードで管理するためのツールセットを提供し、インフラストラクチャのコード化による可読性、保守性、再利用性を向上させます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2491433/d530d10b-f141-830b-7282-7e0695a3ea2d.png)

# アーキテクチャー
![スクリーンショット 2023-09-28 9.22.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2491433/c29f317f-278f-2868-221c-1a77b1d40a70.png)

今回実現したい機能はLambdaでエラー発生またはSQSがDLQに溜まってしまった場合、システム管理者へメール送信します。
Lambdaのエラー監視とSQS DLQの数量監視でSNSを用いてメール送信機能を実現しました。

# SNSのTopicの事前作成
エラー通知用のSNS Topicを事前に作成した上で、サブスクリプションのメール通知先のメールアドレスを追加しておきます。
![スクリーンショット 2023-10-03 13.19.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2491433/39daf98f-5bef-8c40-adb9-3e822a84d9e5.png)

```ts
//通知先の作成
const batchResultNotifyTopic = sns.Topic.fromTopicArn(this, <id>, <topic-ARN>);
const batchResultNotifyDestination = new SnsDestination(batchResultNotifyTopic);
```
※SDK

https://docs.aws.amazon.com/cdk/api/v1/docs/@aws-cdk_aws-sns.Topic.html


# CDKの作成
## amplify cli
まずは**amplify cli** を用いてCDKを作成する
```sh
amplify add custom
✔ How do you want to define this custom resource? · AWS CDK
✔ Provide a name for your custom resource · testcdk
✅ Created skeleton CDK stack in amplify/backend/custom/testcdk directory
✔ Do you want to edit the CDK stack now? (Y/n) · yes
Edit the file in your editor: ***custom/testcdk/cdk-stack.ts
? Press enter to continue 
```
```ts
import * as cdk from '@aws-cdk/core';
import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper';
import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref';
//import * as iam from '@aws-cdk/aws-iam';
//import * as sns from '@aws-cdk/aws-sns';
//import * as subs from '@aws-cdk/aws-sns-subscriptions';
//import * as sqs from '@aws-cdk/aws-sqs';

export class cdkStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps, amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps) {
    super(scope, id, props);
    /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */
    new cdk.CfnParameter(this, 'env', {
      type: 'String',
      description: 'Current Amplify CLI env name',
    });
  }
}
```
## Lambda依存関係の追加
```ts
import { AmplifyDependentResourcesAttributes } from "../../types/amplify-dependent-resources-ref";

const dependencies: AmplifyDependentResourcesAttributes = AmplifyHelpers.addResourceDependency(this,
  amplifyResourceProps.category,
  amplifyResourceProps.resourceName,
  [
    {
        category: "function", // api, auth, storage, function, etc.
        resourceName: "alretNotification" //先に作ったエラー通知先のlamda
        // find the resource at "amplify/backend/<category>/<resourceName>"
    }, /* add more dependencies as needed */
    { 
        category: 'custom', 
        resourceName: '<resource-name>' // SQSリソース名
    }
] 
);
```
cdk.Fn.ref関数を使用して、依存関係の出力を参照します
```ts
const myFunctionArn = cdk.Fn.ref(dependencies.function.<resource-name>.Arn)
```

## Roleインポート
```ts
batchFnction.forEach(functionName => {
      //Lamdaの取得
      // Roleインポート
      const targetLamda: lambda.IFunction = lambda.Function.fromFunctionAttributes(this, functionName + 'Lambda', {
        functionArn: cdk.Fn.ref(dependencies.function[functionName].Arn),
        role: iam.Role.fromRoleArn(this, functionName + 'Role', `arn:aws:iam::${cdk.Stack.of(this).account}:role/${cdk.Fn.ref(dependencies.function[functionName].LambdaExecutionRole)}`)
      });
    });
```

## Lambda非同期失敗のSNS通知
```ts
batchFnction.forEach(functionName => {
      //Lamdaの実行結果の送信先設定
      // 非同期処理失敗時SNS送信
      //※sns:Publishのポリシーも自動的に追加される
      targetLamda.configureAsyncInvoke({
        onFailure: batchResultNotifyDestination,// 前述の通知先
      });
```

※SDK
https://docs.aws.amazon.com/cdk/api/v1/docs/@aws-cdk_aws-lambda.FunctionAttributes.html
https://docs.aws.amazon.com/cdk/api/v1/docs/@aws-cdk_aws-lambda.Function.html


## SQS DLQの検知と送信
Cloud WatchのAlarm機能を利用して、DLQの件数が１を超えたら、SNS送信を行います。
```ts
   //
    //SQS(DLQ)にメッセージ検知し、SNS送信
    //
    const dlqList = [
      { name: <DLQ Name>, Arn: <DLQ ARN> },
    ]

    dlqList.forEach(dlq => {
      //sqsの取得
      const dlqQueue = sqs.Queue.fromQueueArn(this, dlq.name + 'Queue', cdk.Fn.ref(dlq.Arn));

      //アラーム作成(1分間に1件/1データポイント中発生すれば、アラーム)
      const alarm = new cloudwatch.Alarm(this, `Alarm-${dlq.name}`, {
        alarmName: `Alarm-${dlq.name}-${cdk.Fn.ref('env')}`,
        metric: dlqQueue.metricApproximateNumberOfMessagesVisible(),
        period: cdk.Duration.seconds(60), //メトリックまたは式を評価して、アラームの個々のデータポイントを作成する時間の長さ
        evaluationPeriods: 1, //アラーム状態を判断するときに評価する最新の期間またはデータポイントの数
        threshold: 1,
        datapointsToAlarm: 1, //period期間中のEvaluationPeriodsの内、DatapointsToAlarm回発生すれば、アラーム通知
        comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD //評価の比較演算子
      });
      //アラーム通知先設定
      alarm.addAlarmAction(new SnsAction(batchResultNotifyTopic));
    })
```
### Construct Props

|属性|タイプ|説明|
|--|--|--|
|evaluationPeriods|number|アラーム状態を判断するときに評価する最新の期間またはデータポイントの数|
|metric|IMetric|アラームを追加するメトリック|
|threshold|number|閾値、指定された統計が比較される値|
|actionsEnabled?|boolean|このアラームのアクションが有効かどうか。|
|alarmDescription?|string|アラームの説明|
|alarmName?|string|アラームの名前|
|comparisonOperator?|ComparisonOperator|メトリクスが違反しているかどうかを確認するために使用する比較。|
|datapointsToAlarm?|number|アラームをトリガーするために違反する必要があるデータポイントの数。|
|evaluateLowSampleCountPercentile?|string|データ ポイントが少なすぎて統計的に有意でない場合に、データを評価し、アラーム状態を変更する可能性があるかどうかを指定します。|
|period?|Duration|メトリックまたは式を評価して、アラームの個々のデータポイントを作成する時間の長さ|
|statistic?|string|集計に使用する関数。|
|treatMissingData?|TreatMissingDat|このアラームが欠落しているデータ ポイントを処理する方法を設定します。|


### ComparisonOperatorメンバー
|名前|説明|
|--|--|
|GREATER_THAN_OR_EQUAL_TO_THRESHOLD|指定された統計がしきい値以上です。|
|GREATER_THAN_THRESHOLD|指定された統計はしきい値を厳密に超えています。|
|LESS_THAN_THRESHOLD|指定された統計は厳密にしきい値未満です。|
|LESS_THAN_OR_EQUAL_TO_THRESHOLD|指定された統計がしきい値以下です。|
|LESS_THAN_LOWER_OR_GREATER_THAN_UPPER_THRESHOLD| 指定された統計量が異常モデルの帯域よりも小さいか大きいです。|
|GREATER_THAN_UPPER_THRESHOLD|指定された統計量が異常モデルの帯域よりも大きいです。|
|LESS_THAN_LOWER_THRESHOLD|指定された統計量は、異常モデルの帯域よりも低いです。|

※SDK
https://docs.aws.amazon.com/cdk/api/v1/docs/@aws-cdk_aws-cloudwatch.Alarm.html

# トラブルシューティング
遭遇したトラブルをメモします。
1. CloudWatch アラームトリガーの SNS 通知を受信しなかったのはなぜですか?

https://repost.aws/ja/knowledge-center/cloudwatch-receive-sns-for-alarm-trigger

# ご参考
https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/home.html

https://docs.amplify.aws/cli/custom/cdk/

https://docs.aws.amazon.com/cdk/api/v1/docs/aws-construct-library.html