[AWS CDK]CDKで環境構築＋Lambdaを生成する方法

## はじめに
PythonのCDKでスタックからリソースを作成、CloudFormationで確認する流れを一通りまとめました。

特にローカル環境構築ではまる部分が多く、CDKを使うまでの過程が初学者としては難しかったため、以下の手順を参考にしていただければと思います。

## AWS CDKとは
AWS CDK（AWS Cloud Development Kit）は、AWSリソースをプログラムで定義するためのオープンソースのフレームワークです。AWS CDKを使用すると、AWSのインフラストラクチャをコードで作成、構成、およびデプロイができます。
> AWS CDK を使用すると、プログラミング言語の優れた表現力を活かして、信頼性が高く、スケーラブルで、コスト効率の高いアプリケーションをクラウドで構築できます。
https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/home.html

## Amazon Lambdaとは
AWS Lambdaは、アプリケーションコードを実行するために必要なサーバーや仮想マシンを操作する必要がなく、アプリケーションの処理を自動的にスケーリングすることができます。
> AWS Lambda は、サーバーをプロビジョニングまたは管理せずにコードを実行できるようにするコンピューティングサービスです。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/welcome.html

## 環境
```sh
npm -v
8.19.2

python version
3.9.6

aws-cdk version
2.45.0
```

## 環境構築

#### ディレクトリ構成

下記のディレクトリ構成に合わせて、ファイル等の作成を行う。

```
.
├── app.py
├── cdk
│   └── cdk_stack
│       ├── __init__.py
│       └── cdk_stack.py
├── cdk.json
├── package-lock.json
├── package.json
├── poetry.lock
├── pyproject.toml
└── src
    └── index
        ├── main.py
        └── requirements.txt
```

#### package.jsonを作成
`aws-cdk`を記載する。
```json:package.json
{
  "dependencies": {
    "aws-cdk": "^2.45.0"
  }
}
```

#### pyproject.tomlを作成
Python プロジェクトに関する情報を設定する。
```toml:pyproject.toml
[tool.poetry]
name = "test-project"
version = "0.1.0"
description = "test-projectの作成"
authors = ["test <test@gmail.com>"]

[tool.poetry.dependencies]
python = "~3.9.0, <3.10.0"
aws-lambda-powertools = "1.27.0"
aws-cdk-lib = "2.45.0"
constructs = "^10.1.43"
"aws-cdk.aws-lambda-python-alpha" = "^2.30.0-alpha.0"
```
- name : プロジェクトの名前
- version : プロジェクトのバージョン
- description : プロジェクトの説明
- authors : プロジェクトの作者情報（名前とメールアドレス）

https://python-poetry.org/docs/pyproject/

#### CDKをインストール
`Node.js`パッケージマネージャからCDKをインストール。
```sh
npm install cdk
```

#### Poetryをインストール
Pythonのパッケージマネージャーである  `pip`を使用して、
Poetryと呼ばれるパッケージ管理ツールをインストール。
```sh
python -m pip install -U --no-cache-dir poetry
```

#### Pythonパッケージのインストール
`pyproject.toml`で管理されているプロジェクトの依存関係をインストール。
```sh
poetry install --no-interaction
```

#### cdk.jsonの作成
プロジェクト名、アカウント情報など記載する。
```json:cdk.json
{
  "app": "python3 app.py",
  "watch": {
    "include": [
      "**"
    ],
    "exclude": [
      "cdk*.json",
      "**/__init__.py",
      "python/__pycache__"
    ]
  },
  "context": {
    "project_name": "test-project"
  }
}
```
- context : `cdk.json`ファイルに書かれたcontextキーの値を`try_get_context(key)`で取得できる。

## 実装

#### スタックの作成
AWS CDKで作成する。
```python:app.py
import aws_cdk as cdk
from cdk.cdk_stack.cdk_stack import CdkStack

app = cdk.App()

# cdk.jsonの値を取得
construct_name: dict = app.node.try_get_context("project_name")

# スタックファイルの作成
CdkStack(
    app,
    construct_name,
    f"{construct_name}-main-stack",
)

app.synth()
```
```python:cdk_stack.py
import os
from aws_cdk import (
    Stack,
    aws_lambda as lambda_,
    aws_lambda_python_alpha as python_lambda,
)
from constructs import Construct


class CdkStack(Stack):
    def __init__(
        self,
        scope: Construct,
        construct_name: str,
        stack_name: str,
        **kwargs,
    ) -> None:
        """
        Initialize

        Parameters
        ----------
        scope: Construct
            scope: cdk.App()
        construct_name: str
            プロジェクト名: "test-project"
        stack_name: str
            スタック名: "test-project-main-stack"
        Returns
        -------
        None
        """
        super().__init__(scope, stack_name, **kwargs)

        self.construct_name = construct_name

        # Lambda
        self.handler = self.create_lambda(
            id=construct_name,
            name="index",
            handler_path="index",
        )

    def create_lambda(
        self,
        id: str,
        name: str,
        handler_path: str,
    ) -> python_lambda.PythonFunction:
        """
        Lambdaスタック作成

        Parameters
        ----------
        id: str
            プロジェクト名: test-project
        name: str
            Lambda名: index
        handler_path: str
            lambda_handlerの場所: index
        Returns
        -------
        Lambdaスタック
        """
        # 環境変数を設定
        environment_values = {"test_parameter": "test_parameter_value"}

        return python_lambda.PythonFunction(
            self,
            id=f"{id}-{name}-lambda",
            entry=os.path.join(os.getcwd(), "src", handler_path),
            runtime=lambda_.Runtime.PYTHON_3_9,
            handler="lambda_handler",
            index="main.py",
            function_name=f"{id}-{name}-lambda",
            environment=environment_values,
        )

```
- 今回使用したもの
    - id : Lambda関数のID
    - entry : Lambda関数の実行可能ファイルが格納されているディレクトリへのパス
        - os.getcwd : Python スクリプトが実行されている現在の作業ディレクトリを取得
        - os.path.join : 現在の作業ディレクトリのパス、`src`、および `index`パスを結合
    - runtime: Lambda関数を実行するために使用されるランタイム（Python 3.9）
    - handler: Lambda関数のエントリポイントとなる関数の名前
        - エントリポイント : プログラムが実行される際に最初に呼び出される関数、今回は`lambda_handler`
    - index: Lambda関数のエントリポイントとなるファイル名
    - function_name: Lambda関数の名前
    - environment: Lambda関数の環境変数

- 他に指定できるもの
    - memory_size: Lambda関数のメモリ割り当てサイズ（デフォルトは128MB）
    - timeout: Lambda関数の最大実行時間
    - tracing: Lambda関数のトレースを有効にするかどうか
    - retry_attempts: Lambda関数が失敗した場合に再試行する回数

https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_lambda_python_alpha/PythonFunction.html


#### Lambdaの作成
AWS Lambdaが起動された際に呼び出される関数名を確認する。

```python:cdk_stack.py
    handler="lambda_handler",
```

indexで指定したエントリポイントとなるファイル名を作成しhandlerで指定した関数を生成する。
```python:main.py
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.utilities.data_classes import APIGatewayProxyEvent


def lambda_handler(event: APIGatewayProxyEvent, context: LambdaContext):
    return "Hello World"
```

#### requirements.txtの作成
Lambda上で使用される必要なパッケージをインストールすることが可能。
```txt:requirements.txt
aws-lambda-powertools
```

#### デプロイ
CloudFormationへデプロイする。
もしロールがあれば --profileをつけて指定する。
```sh
poetry run npx cdk deploy スタック名
```

#### コンソールで確認
CloudFormationを開いて生成したスタックが存在しているか確認する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952491/366fd3c4-ef6a-376f-c15f-b35bde7b1090.png)

次にLambdaを開いてリソースが生成されているか確認する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952491/2604139b-680d-5e31-dcae-424e5a15a79b.png)

また、`environment_values`で指定した環境変数が設定されているかも確認する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952491/370f29d4-71b4-01b6-0282-2212235c5764.png)

最後にLambda関数をテストして`"Hello World"`が返ってきたら完了。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952491/ff3b62f9-695b-fed8-35bb-07680c75f66c.png)


## おわりに
最初から作成することでCDKの流れを学ぶことができました。
今回作成した環境から様々なリソースの検証をしていこうと思います。