Lambda関数に加えて、監視リソースも併せて構築するCDK Constructを自作してみました

AWS Lambdaの構築をする際、監視用のリソースも併せて構築できてしまえば都合がいいと思ったため、以下のリソースを作成するCDK Constructを作成してみました。
なお、今回はCDKはV.2、言語はPythonとなります。

- Lambda関数
- Lambda関数のThrottle Metricsを監視するCloudWatch Alarm
- Lambda関数のLogGroupのERRORを監視するSubscription Filter
※AlarmやSubscription filterから先のSNS TopicやLambda関数に関しては引数として受け取る形にしています。
※ここでは、LogGroupのエラー内容をメール本文に記載する目的でSubscription filterの先をLambdaとしています。

全コードはGithubを参照してください。
[Githubはここ](https://github.com/ShinyaT1994/cdk-lmabda-with-monitoring)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/660703/bb42f9d5-9b20-410f-a4e6-354958c3109d.png)

## 前提条件
- AWS CDK v.2 がインストールされていること
- Python 3.x がインストールされていること
- AWSアカウントがあり、AWS CLIが設定されていること

※Cloud9を使うとこの辺りがPassできるため、Cloud9を使って今回の記事の内容は作成しています。


## 構築手順
### 1.CDKアプリの初期化
先ずはCDKアプリの初期化を行います。

```bash
$ mkdir cdk-lambda-with-monitoring
$ cd cdk-lambda-with-monitoring
$ cdk init --language python
```

### 2. 必要なパッケージをインストール
ここでは、CDKアプリを初期化した際に作成された.venvを有効化しています。

```bash
$ source .venv/bin/activate
$ pip install -r requirements.txt
```

### 3. Constructの作成
下のコードで、Lambda関数とSubscription filter、Alarmを作成しています。
```python
import os

from aws_cdk import (
    Duration,
    Stack,
    aws_cloudwatch as cloudwatch,
    aws_cloudwatch_actions as cloudwatch_actions,
    aws_lambda as lambda_,
    aws_logs as logs,
    aws_logs_destinations as logs_destinations,
)
from constructs import Construct


class LambdaWithMonitoring(Construct):
    def __init__(
            self, scope: Construct, construct_id: str, function_props, 
            operation_topic, publish_message_function, **kwargs) -> None:
        """
        Initialize method
        
        parameters
        ----------
        function_props: dict
            Lambda関数のプロパティ
        sns_topic: aws_sns.Topic
            Alarmの内容を通知するTopic
        publish_message_function: lambda.Function
            lambdaのLogGroupのError通知を成形しSNS Topicに渡す関数
        """
        super().__init__(scope, construct_id, **kwargs)
        
        # Create lambda function
        lambda_function = lambda_.Function(
            self, f'{construct_id}LambdaFunction', **function_props
        )
        
        # Throttle error alarm
        throttles_alarm = cloudwatch.Alarm(
            self, f'{construct_id}ThrottlesAlarm',
            metric=cloudwatch.Metric(
                metric_name='Throttles',
                namespace='AWS/Lambda',
                dimensions_map={'FunctionName': lambda_function.function_name},
                period=Duration.minutes(1),
                statistic='Sum',
            ),
            evaluation_periods=1,
            threshold=0,
            comparison_operator \
                =cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
        )
        throttles_alarm.add_alarm_action(
            cloudwatch_actions.SnsAction(operation_topic)
        )
        
        # LogGroup error alarm
        lambda_function.log_group.add_subscription_filter(
            f'{construct_id}SubscriptionFilter',
            destination=logs_destinations.LambdaDestination(
                publish_message_function
            ),
            filter_pattern=logs.FilterPattern.any_term('ERROR'),
        )
```

### 4.実際スタックから呼出してみる
下のスタックの定義の中では、通知用のSNS TopicとLambdaを引数として自作Constructに渡しています。
また、自作Constructを利用して二つのLambda＋監視リソースを作成しています。
なお、Lambda関数のLogGroupのエラー内容をメールで通知する方法は以下を参照してください。
[AWS CDK(ver.2) pythonを使って、Lambdaのエラー内容をメールで通知してみた](https://qiita.com/rm0063vpedc15/items/725cd495b7c617b512f4)

```python
import os

from aws_cdk import (
    Duration,
    Stack,
    aws_lambda as lambda_,
    aws_sns as sns,
    aws_sns_subscriptions as subscriptions,
)
from constructs import Construct

from cdk_lambda_with_monitoring.lambda_with_monitoring import LambdaWithMonitoring


class CdkLambdaWithMonitoringStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        
        # SNS Topic for notification
        operation_topic = sns.Topic(self, 'OperationTopic')
        subscription = operation_topic.add_subscription(
            subscriptions.EmailSubscription('shinya_takaramoto@ulvac.com')
        )
        
        # lambdaのLogGroupのError通知を成形しSNS Topicに渡す関数
        publish_message_function = lambda_.Function(
            self,
            'PublishMessageFunction',
            runtime=lambda_.Runtime.PYTHON_3_9,
            handler='lambda_function.lambda_handler',
            code=lambda_.Code.from_asset(
                os.path.join('lambda', 'publish_message_function')
            ),
            environment={
                'OPERATION_TOPIC_ARN': operation_topic.topic_arn
            },
        )
        operation_topic.grant_publish(publish_message_function)
        
        # First lambda function with monitoring
        function_props_1 = {
            'code': lambda_.Code.from_asset(os.path.join('lambda', 'func1')),
            'handler': 'lambda_function.lambda_handler',
            'runtime': lambda_.Runtime.PYTHON_3_9,
            'function_name': 'test-lambda-func1',
            'memory_size': 128,
            'timeout': Duration.seconds(3)
        }
        
        lambda_with_monitoring_1 = LambdaWithMonitoring(
            self, 'LambdaWithMonitoring1', function_props_1, operation_topic,
            publish_message_function
        )
        
        # Second lambda function with monitoring
        function_props_2 = {
            'code': lambda_.Code.from_asset(os.path.join('lambda', 'func2')),
            'handler': 'lambda_function.lambda_handler',
            'runtime': lambda_.Runtime.PYTHON_3_8,
            'function_name': 'test-lambda-func2',
            'memory_size': 128,
            'timeout': Duration.seconds(10)
        }
        
        lambda_with_monitoring_2 = LambdaWithMonitoring(
            self, 'LambdaWithMonitoring2', function_props_2, operation_topic,
            publish_message_function
        )

```

デプロイが完了すると、Lambda関数と関連した監視リソースが2つ作成されます。

## まとめ
今回は、Lambda＋監視用のリソースをまとめて作成する自作のCDK Constructをご紹介しました。プロジェクトの中で再利用性を全く検討していなかった反省点を活かして、少し再利用性の高そうなものを作ってみた次第です。
どなたかの参考となれば幸いです。