【AWS CDK (Python)】 LambdaのCI/CD環境をIaCで作る

## はじめに
本記事は、私自身の備忘録を兼ねてAWS CDKをこれから始める方の一助になればと思い、AWS CDKの使い方等をまとめたものです。
今回は、AWS CDKでLambdaのCI/CD環境を作成します。
なお、本記事は私自身の経験を基に記載していますが、間違いがあったらすみません。

## LambdaのソースコードはCodeCommitで管理したい。けどLambdaの設定を含むインフラはIaC（CDK）で管理したい！
LambdaのCI/CD環境を作成するんですが、前提条件として、
* LambdaのソースコードはCodeCommitで管理する
* VPCやDBなどリソースはIaC（CDK）で作成、管理する（←Lambdaの設定（メモリサイズやVPC設定など）もここに含む）

です。
LambdaのCI/CDのベースは、以下のような構成になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/d7c2abd2-7290-0997-1e44-b1461bf11ae9.png)

概要としては、
1. CodeCommitのリポジトリからLambdaのデプロイに必要なLambdaのソースコード、buildspecファイル、SAMテンプレートファイルを取得する
2. CodeBuildで`aws cloudformation package`を実行し、CloudFormationでデプロイするためのアーティファクト作成しS3にアップロードする
3. アーティファクト、SAMテンプレートに基づいてCloudFormationで変更セットを作成する
4. CloudFormationで変更セットを実行する

になりますが、問題は2.で`aws cloudformation package`に必要になるLambda関数を作成するSAMテンプレートファイル（もしくはCloudFormationテンプレートファイル）です。この構成だと、CodeCommitリポジトリにLambdaのソースコードと一緒に含まなければなりませんが、前提条件の2つ目のように、Lambdaの設定はCDKで管理したいため、CodeCommitリポジトリには含めたくありません。
このため、LambdaのソースコードはCodeCommitで管理しつつ、Lambdaの設定はCDKで管理する構成を考えました。

## 環境
本記事は以下の環境を使用して記載しています。
* AWS Cloud9
* AWS CDK：2.80.0
* Python： 3.10.11
* Node.js： 16.20.0

また、以下の記事に基づいてAWS CDKの環境を作成しています。
* [【AWS CDK/Python】 AWS CDKの始め方](https://qiita.com/takataka987/items/4448dbf06d8c70173305)

## LambdaのCI/CD環境をIaCで作る
では、実際作成したLambdaのCI/CD環境ですが、以下のような構成にしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/bb6bba53-3023-7c86-3c38-8115a6c5d55b.png)

1. **CDKでSAMテンプレートファイルを作成し、S3バケットにアップロードする**
2. CodeCommitのリポジトリからLambdaのデプロイに必要なLambdaのソースコード、buildspecファイル、~~SAMテンプレートファイル~~を取得する
3. **S3バケットからSAMテンプレートファイルを取得する**
4. CodeBuildで`aws cloudformation package`を実行し、CloudFormationでデプロイするためのアーティファクト作成しS3にアップロードする
3. アーティファクト、SAMテンプレートに基づいてCloudFormationで変更セットを作成する
4. CloudFormationで変更セットを実行する

ベースとなった構成との変更点は赤枠の範囲ですね。
SAMテンプレートファイルをインフラ環境と一緒にCDKで作成し、CodeCommitリポジトリに含めずにS3へアップロードします。SAMテンプレートファイルをCodeCommitと別にCodePipelineのSourceとし、CodeBuildの入力になるように設定しました。こうすることで、LambdaのVPCやメモリサイズなどの設定をCDKで管理でき、パイプラインを実行することで、変更の反映が可能です。
最終的なCDKのソースコードは以下のようになりました。
<details><summary>CDKコード全体</summary>

```python:app.py
#!/usr/bin/env python3
import os
import aws_cdk as cdk
from cdk_app.cdk_lambda_cicd_stack import CdkAppStack

app = cdk.App()

CdkAppStack = CdkAppStack(app, "CdkAppStack",
    )

app.synth()
```

```python:cdk_app/cdk_lambda_cicd_stack.py
from aws_cdk import (
    Stack,
    aws_s3 as s3,
    RemovalPolicy,
    aws_s3_deployment as s3deploy,
    aws_iam as iam,
    aws_codebuild as codebuild,
    aws_codepipeline as codepipeline,
    aws_events as events
)
from constructs import Construct
import json
import os
import zipfile

# S3バケットを作成する共通関数
def CreateBucket(scope, bucket_id):

    cfn_bucket = s3.CfnBucket(
        scope,
        bucket_id,
        versioning_configuration = s3.CfnBucket.VersioningConfigurationProperty(
            status = "Enabled"
        ),
        bucket_name = "cdk-test-{0}-c3hw2bm4".format(bucket_id)
    )
    return cfn_bucket

# CodeBuild用のIAMロールを作成
def CreateBuildIAMRole(scope, cfn_pkg_s3bucket, artifact_s3bucket):

    assume_role_policy_document = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": "sts:AssumeRole",
                "Effect": "Allow",
                "Principal": {
                    "Service": "codebuild.amazonaws.com"
                }
            }
        ]
    }

    policy_document = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Resource": [
                    "arn:aws:s3:::{0}/*".format(cfn_pkg_s3bucket.bucket_name),
                    "arn:aws:s3:::{0}".format(cfn_pkg_s3bucket.bucket_name),
                    "arn:aws:s3:::{0}/*".format(artifact_s3bucket.bucket_name),
                    "arn:aws:s3:::{0}".format(artifact_s3bucket.bucket_name)
                ],
                "Action": [
                    "s3:PutObject",
                    "s3:GetObject",
                    "s3:GetObjectVersion",
                    "s3:GetBucketAcl",
                    "s3:GetBucketLocation"
                ]
            },
            {
                "Effect": "Allow",
                "Resource": "*",
                "Action": [
                    "logs:CreateLogGroup",
                    "logs:CreateLogStream",
                    "logs:PutLogEvents"
                ]
            },
            {
                "Action": [
                    "iam:PassRole"
                ],
                "Resource": "*",
                "Effect": "Allow"
            },
            {
                "Action": [
                    "cloudformation:*"
                ],
                "Resource": "*",
                "Effect": "Allow"
            }
        ]
    }

    cfn_role = iam.CfnRole(
            scope,
            "codebuild-role",
            assume_role_policy_document=assume_role_policy_document,
            policies=[
                iam.CfnRole.PolicyProperty(
                    policy_document=policy_document,
                    policy_name="codebuild-policy"
                )
            ],
            role_name="codebuild-role"
        )
    
    return cfn_role

# CodePipeline用のIAMロールを作成
def CreatePipelineIAMRole(scope, source_s3bucket, artifact_s3bucket):

    assume_role_policy_document = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": "sts:AssumeRole",
                "Effect": "Allow",
                "Principal": {
                    "Service": "codepipeline.amazonaws.com"
                }
            }
        ]
    }

    policy_document = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "codecommit:CancelUploadArchive",
                    "codecommit:GetBranch",
                    "codecommit:GetCommit",
                    "codecommit:GetUploadArchiveStatus",
                    "codecommit:UploadArchive"
                ],
                "Resource": "*"
            },
            {
                "Action": [
                    "codebuild:BatchGetBuilds",
                    "codebuild:StartBuild",
                    "codebuild:BatchGetBuildBatches",
                    "codebuild:StartBuildBatch"
                ],
                "Resource": "*",
                "Effect": "Allow"
            },
            {
                "Action": [
                    "s3:Get*",
                    "s3:ListBucket"
                ],
                "Resource": [
                    "arn:aws:s3:::{0}/*".format(source_s3bucket.bucket_name),
                    "arn:aws:s3:::{0}".format(source_s3bucket.bucket_name)
                ],
                "Effect": "Allow"
            },
            {
                "Action": [
                    "s3:Get*",
                    "s3:Put*",
                    "s3:ListBucket"
                ],
                "Resource": [
                    "arn:aws:s3:::{0}/*".format(artifact_s3bucket.bucket_name),
                    "arn:aws:s3:::{0}".format(artifact_s3bucket.bucket_name)
                ],
                "Effect": "Allow"
            },
            {
                "Action": [
                    "cloudformation:DescribeStacks",
                    "cloudformation:DescribeChangeSet",
                    "cloudformation:CreateChangeSet",
                    "cloudformation:ExecuteChangeSet",
                    "cloudformation:DeleteChangeSet"
                ],
                "Resource": [
                    "arn:aws:cloudformation:{0}:{1}:stack/lambda-stack-*".format("ap-northeast-1", os.environ["CDK_DEFAULT_ACCOUNT"])
                ],
                "Effect": "Allow"
            },
            {
                "Action": [
                    "cloudwatch:*"
                ],
                "Resource": "*",
                "Effect": "Allow"
            },
            {
                "Action": [
                    "iam:PassRole"
                ],
                "Resource": "*",
                "Effect": "Allow"
            }
        ]
    }

    cfn_role = iam.CfnRole(
            scope,
            "codepipeline-role",
            assume_role_policy_document=assume_role_policy_document,
            policies=[
                iam.CfnRole.PolicyProperty(
                    policy_document=policy_document,
                    policy_name="codepipeline-policy"
                )
            ],
            role_name="codepipeline-role"
        )
    
    return cfn_role

# LambdaFunction用のIAMロールを作成
def CreateLambdaIAMRole(scope):

    assume_role_policy_document = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": "sts:AssumeRole",
                "Effect": "Allow",
                "Principal": {
                    "Service": "lambda.amazonaws.com"
                }
            }
        ]
    }

    cfn_role = iam.CfnRole(
            scope,
            "lambda-role",
            assume_role_policy_document=assume_role_policy_document,
            managed_policy_arns=[
                "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
            ],
            role_name="lambda-role"
        )
    
    return cfn_role

# EvantBridge用のIAMロールを作成
def CreateExecPipelineIAMRole(scope, pipeline_arn):

    assume_role_policy_document = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": "sts:AssumeRole",
                "Effect": "Allow",
                "Principal": {
                    "Service": "events.amazonaws.com"
                }
            }
        ]
    }

    policy_document = {
        "Version": "2012-10-17",
        "Statement": [
            {
                    "Effect": "Allow",
                    "Action": "codepipeline:StartPipelineExecution",
                    "Resource": pipeline_arn
                }
        ]
    }

    cfn_role = iam.CfnRole(
            scope,
            "exec-codepipeline-role",
            assume_role_policy_document=assume_role_policy_document,
            policies=[
                iam.CfnRole.PolicyProperty(
                    policy_document=policy_document,
                    policy_name="exec-codepipeline-policy"
                )
            ],
            role_name="exec-codepipeline-role"
        )
    
    return cfn_role

# LambdaFunction作成用のSAM Templateファイルを作成
def EditSAMTemplate(lambda_id, lambda_role):

    sam_template_data = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Transform": "AWS::Serverless-2016-10-31",
        "Resources": {
            "TestLambdaFunction": {
                "Type": "AWS::Serverless::Function",
                "Properties": {
                    "Handler": "lambda_function.lambda_handler",
                    "Runtime": "python3.10",
                    "CodeUri": "",
                    "Description": "",
                    "FunctionName": "",
                    "AutoPublishAlias": "",
                    "MemorySize": 128,
                    "Timeout": 3,
                    "Role": "",
                    "Environment": {
                        "Variables": {
                        }
                    },
                    "DeploymentPreference": {
                        "Enabled": True,
                        "Type": "AllAtOnce"
                    }
                }
            }
        }
    }
    
    sam_template_data["Resources"]["TestLambdaFunction"]["Properties"]["CodeUri"] = lambda_id
    sam_template_data["Resources"]["TestLambdaFunction"]["Properties"]["FunctionName"] = lambda_id
    sam_template_data["Resources"]["TestLambdaFunction"]["Properties"]["AutoPublishAlias"] = "Dev"
    sam_template_data["Resources"]["TestLambdaFunction"]["Properties"]["Role"] =\
        "arn:aws:iam::{0}:role/{1}".format(os.environ["CDK_DEFAULT_ACCOUNT"], lambda_role.role_name)
    sam_template_data["Resources"]["TestLambdaFunction"]["Properties"]["Environment"]["Variables"] =\
        {"test_variables": "test"}
    
    return sam_template_data

# CodeCommitリポジトリの変更を検知するEventBridgeルールの作成
def CodecommitEvents(scope, rep_name, pipeline_arn, exec_pipeline_role):

    event_pattern = {
        "source": ["aws.codecommit"],
        "detail-type": ["CodeCommit Repository State Change"],
        "resources": [
            "arn:aws:codecommit:{0}:{1}:{2}".format(
                "ap-northeast-1", os.environ["CDK_DEFAULT_ACCOUNT"], rep_name)
        ],
        "detail": {
            "event": ["referenceCreated", "referenceUpdated"],
            "referenceType": ["branch"],
            "referenceName": ["main"]
        }
    }

    cfn_rule = events.CfnRule(
        scope,
        "change-repository-events",
        event_pattern=event_pattern,
        name="change-repository-events",
        state="ENABLED",
        targets=[
            events.CfnRule.TargetProperty(
                arn=pipeline_arn,
                id="change-repository-events",
                role_arn=exec_pipeline_role.attr_arn,
            )
        ]
    )

class CdkAppStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        lambda_id = "TestFunc"
        repository_name = "test-lambda-rep"

        # SAM TemplateファイルをアップロードするS3バケットを作成
        source_s3bucket = CreateBucket(self, "source-bucket")
        source_s3bucket_l2 =  s3.Bucket.from_bucket_arn(
            self, "bucket_l1tol2-{0}".format(source_s3bucket.bucket_name), source_s3bucket.attr_arn)
        # cloudformation packageのパッケージファイルをアップロードするS3バケットを作成
        cfn_pkg_s3bucket = CreateBucket(self, "cfn-pkg")
        # アーティファクトバケットを作成
        artifact_s3bucket = CreateBucket(self, "artifact-bucket")

        # CodeBuild用のIAMロールの作成
        build_role = CreateBuildIAMRole(self, cfn_pkg_s3bucket, artifact_s3bucket)
        # CodePipeline用のIAMロールの作成
        pipeline_role = CreatePipelineIAMRole(self, source_s3bucket, artifact_s3bucket)
        # LambdaFunction用のIAMロールの作成
        lambda_role = CreateLambdaIAMRole(self)

        zip_dir = "./cdk_app/work/"
        zip_file = "template_{0}.zip".format(lambda_id)
        zip_path = zip_dir + zip_file
        template_file_name = "sam-template"
        # SAM Templateファイルの編集
        sam_template_data = EditSAMTemplate(lambda_id, lambda_role)
        with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_STORED) as z:
            z.writestr(template_file_name + ".json", json.dumps(sam_template_data, indent=4))
        # 作成したSAM TemplateファイルをS3へアップロード
        s3_deploy = s3deploy.BucketDeployment(
            self,
            "BuildspecFileUploadS3",
            sources=[s3deploy.Source.asset(zip_dir)],
            destination_bucket=source_s3bucket_l2,
        )

        # Buildプロジェクトの作成
        cfn_project = codebuild.CfnProject(
            self, 
            "build-lambda-project",
            artifacts=codebuild.CfnProject.ArtifactsProperty(
                type="CODEPIPELINE",
            ),
            environment=codebuild.CfnProject.EnvironmentProperty(
                compute_type="BUILD_GENERAL1_SMALL",
                image="aws/codebuild/standard:7.0",
                type="LINUX_CONTAINER",
                privileged_mode=False,
                environment_variables=[
                    codebuild.CfnProject.EnvironmentVariableProperty(
                        name="TEMPLATE_FILE_NAME",
                        value=template_file_name,
                    ),
                    codebuild.CfnProject.EnvironmentVariableProperty(
                        name="CFN_PKG_BUCKET",
                        value=cfn_pkg_s3bucket.bucket_name,
                    )
                ],
            ),
            service_role=build_role.attr_arn,
            source=codebuild.CfnProject.SourceProperty(
                type="CODEPIPELINE",
                build_spec="buildspec_{0}.yaml".format(lambda_id)
            ),
            logs_config=codebuild.CfnProject.LogsConfigProperty(
                cloud_watch_logs=codebuild.CfnProject.CloudWatchLogsConfigProperty(
                    status="ENABLED",
                ),
            ),
            name="build_lambda_project",
        )

        # CodePipelineの作成
        source_actions = []
        # Sourceアクション（CodeCommit（Lambdaコード、buildspec））の作成
        source_actions.append(
            codepipeline.CfnPipeline.ActionDeclarationProperty(
                action_type_id=codepipeline.CfnPipeline.ActionTypeIdProperty(
                    category="Source",
                    owner="AWS",
                    provider="CodeCommit",
                    version="1"
                ),
                name="Source_codecommit",
                configuration={
                    "RepositoryName": repository_name,
                    "BranchName": "main",
                    "PollForSourceChanges": False
                },
                namespace="SourceVariables_codecommit",
                output_artifacts=[codepipeline.CfnPipeline.OutputArtifactProperty(
                    name="SourceArtifact_codecommit"
                )],
                region="ap-northeast-1",
                run_order=1
            )
        )

        # Sourceアクション（SAM Template）の作成
        source_actions.append(
            codepipeline.CfnPipeline.ActionDeclarationProperty(
                action_type_id=codepipeline.CfnPipeline.ActionTypeIdProperty(
                    category="Source",
                    owner="AWS",
                    provider="S3",
                    version="1"
                ),
                name="Source_template",
                configuration={
                    "S3Bucket": source_s3bucket.bucket_name,
                    "S3ObjectKey": zip_file,
                    "PollForSourceChanges": False
                },
                namespace="SourceVariables_buildspec",
                output_artifacts=[codepipeline.CfnPipeline.OutputArtifactProperty(
                    name="SourceArtifact_template"
                )],
                region="ap-northeast-1",
                run_order=1
            )
        )

        # Buildアクションの作成
        build_actions = []
        build_actions.append(
            codepipeline.CfnPipeline.ActionDeclarationProperty(
                action_type_id=codepipeline.CfnPipeline.ActionTypeIdProperty(
                    category="Build",
                    owner="AWS",
                    provider="CodeBuild",
                    version="1"
                ),
                name="Build",
                configuration={
                    "ProjectName": cfn_project.name,
                    "PrimarySource": "SourceArtifact_codecommit"
                },
                input_artifacts=[
                    codepipeline.CfnPipeline.InputArtifactProperty(
                        name="SourceArtifact_codecommit"
                    ),
                    codepipeline.CfnPipeline.InputArtifactProperty(
                        name="SourceArtifact_template"
                    )
                ],
                namespace="BuildVariables",
                output_artifacts=[codepipeline.CfnPipeline.OutputArtifactProperty(
                    name="BuildArtifact"
                )],
                region="ap-northeast-1",
                run_order=1
            )
        )

        deploy_actions = []
        # Deployアクション（変更セットの作成）の作成
        deploy_actions.append(
            codepipeline.CfnPipeline.ActionDeclarationProperty(
                action_type_id=codepipeline.CfnPipeline.ActionTypeIdProperty(
                    category="Deploy",
                    owner="AWS",
                    provider="CloudFormation",
                    version="1"
                ),
                name="CreateChangeSet",
                configuration={
                    "ActionMode": "CHANGE_SET_REPLACE",
                    "RoleArn": "arn:aws:iam::{0}:role/cdk-hnb659fds-cfn-exec-role-{0}-{1}".\
        format(os.environ["CDK_DEFAULT_ACCOUNT"], "ap-northeast-1"),
                    "StackName": "lambda-stack-{0}".format(lambda_id),
                    "ChangeSetName": "lambda-stack-{0}-changeSet".format(lambda_id),
                    "Capabilities": "CAPABILITY_NAMED_IAM",
                    "TemplatePath": "BuildArtifact::{0}".format("out-{0}.yaml".format(template_file_name))
                },
                input_artifacts=[
                    codepipeline.CfnPipeline.InputArtifactProperty(
                        name="BuildArtifact"
                    )
                ],
                namespace="DeployCreateVariables",
                region="ap-northeast-1",
                run_order=1
            )
        )

        # Deployアクション（変更セットの実行）の作成
        deploy_actions.append(
            codepipeline.CfnPipeline.ActionDeclarationProperty(
                action_type_id=codepipeline.CfnPipeline.ActionTypeIdProperty(
                    category="Deploy",
                    owner="AWS",
                    provider="CloudFormation",
                    version="1"
                ),
                name="ExecuteChangeSet",
                configuration={
                    "ActionMode": "CHANGE_SET_EXECUTE",
                    "ChangeSetName": "lambda-stack-{0}-changeSet".format(lambda_id),
                    "StackName": "lambda-stack-{0}".format(lambda_id)
                },
                input_artifacts=[
                    codepipeline.CfnPipeline.InputArtifactProperty(
                        name="BuildArtifact"
                    )
                ],
                namespace="DeployExecuteVariables",
                region="ap-northeast-1",
                run_order=2
            )
        )
    
        # Pipelineの作成
        cfn_pipeline = codepipeline.CfnPipeline(
            self,
            "pipeline-lambda",
            role_arn=pipeline_role.attr_arn,
            stages=[
                codepipeline.CfnPipeline.StageDeclarationProperty(
                    actions=source_actions,
                    name="Source",
                ),
                codepipeline.CfnPipeline.StageDeclarationProperty(
                    actions=build_actions,
                    name="Build",
                ),
                codepipeline.CfnPipeline.StageDeclarationProperty(
                    actions=deploy_actions,
                    name="Deploy",
                )
            ],
            artifact_store=codepipeline.CfnPipeline.ArtifactStoreProperty(
                location=artifact_s3bucket.ref,
                type="S3",
            ),
            name="pipeline_lambda",
            restart_execution_on_update=False
        )
        cfn_pipeline.node.add_dependency(s3_deploy)

        pipeline_arn = "arn:aws:codepipeline:{0}:{1}:{2}".format(
                "ap-northeast-1", os.environ["CDK_DEFAULT_ACCOUNT"], cfn_pipeline.name)

        # EventBridge用のIAMロールの作成
        exec_pipeline_role = CreateExecPipelineIAMRole(self, pipeline_arn)
        # CodeCommitリポジトリの変更を検知するEventBridgeルールの作成
        CodecommitEvents(self, repository_name, pipeline_arn, exec_pipeline_role)
```
</details>

以下は、CodeCommitリポジトリに用意するファイルです。
※今回の例では、CodeCommitリポジトリはCDKで作成していませんが、CDKに含めても問題ありません。
```python:test-lambda-rep/TestFunc/lambda_function.py
def lambda_handler(event, context):
    print("hello! World!!")
```
```yaml:test-lambda-rep/buildspec_TestFunc.yaml
version: 0.2
phases:
  install:
    commands:
      - aws --version
      - cp -pr ../01/* .
      - ls -lR ./
  build:
    commands:
      - aws cloudformation package --template-file ${TEMPLATE_FILE_NAME}.json --s3-bucket ${CFN_PKG_BUCKET} --output-template-file out-${TEMPLATE_FILE_NAME}.yaml
artifacts:
 files:
    - out-${TEMPLATE_FILE_NAME}.yaml
```


では、処理毎に順に説明します。
* [S3バケットの作成](#s3バケットの作成)
* [IAMロールの作成](#iamロールの作成)
* [SAM Templateファイルの作成](#sam-templateファイルの作成)
* [Sourceアクションの作成](#sourceアクションの作成)
* [CodeBuildプロジェクト、Buildアクションの作成](#codebuildプロジェクトbuildアクションの作成)
* [Deployアクションの作成](#deployアクションの作成)
* [パイプラインの作成](#パイプラインの作成)
* [パイプラインの自動起動設定](#パイプラインの自動起動設定)

#### S3バケットの作成
CI/CDに必要なS3バケットを作成します。（必要に応じてバケットポリシーなどの設定を追加してください。）
必要なバケットは以下のとおりです。
* CDKで作成したSMAテンプレートをアップロードするバケット
* `aws cloudformation package`の実行で作成されるアーティファクトをアップロードするバケット
* パイプラインのアーティファクト用バケット

なお、今回S3バケットの作成はL1コンストラクタを使用していますが、SAMテンプレートをアップロードするバケットは、aws_s3_deploymentを使用する都合上、バケット作成後に`s3.Bucket.from_bucket_arn`を使用することでL2コンストラクタに変換しています。
```python:cdk_app/cdk_lambda_cicd_stack.py

(中略)

# S3バケットを作成する共通関数
def CreateBucket(scope, bucket_id):

    cfn_bucket = s3.CfnBucket(
        scope,
        bucket_id,
        versioning_configuration = s3.CfnBucket.VersioningConfigurationProperty(
            status = "Enabled"
        ),
        bucket_name = "cdk-test-{0}-c3hw2bm4".format(bucket_id)
    )
    return cfn_bucket

(中略)

class CdkAppStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        lambda_id = "TestFunc"
        repository_name = "test-lambda-rep"

        # SAM TemplateファイルをアップロードするS3バケットを作成
        source_s3bucket = CreateBucket(self, "source-bucket")
        source_s3bucket_l2 =  s3.Bucket.from_bucket_arn(
            self, "bucket_l1tol2-{0}".format(source_s3bucket.bucket_name), source_s3bucket.attr_arn)
        # cloudformation packageのパッケージファイルをアップロードするS3バケットを作成
        cfn_pkg_s3bucket = CreateBucket(self, "cfn-pkg")
        # アーティファクトバケットを作成
        artifact_s3bucket = CreateBucket(self, "artifact-bucket")

(中略)
```

#### IAMロールの作成
CI/CDに必要なIAMロールを作成します。
必要なIAMロールは以下のとおりです。
* CodePipelineが使用するロール
* CodeBuildが使用するロール
* CloudFormationが使用するロール
* EventBridge（CodeCommitリポジトリの更新時にパイプラインを実行）が使用するロール
* CI/CDで作成するLambda関数が使用するロール

うち、CloudFormationが使用するロールは、cdkがデプロイに使用するロール（`cdk bootstrap`で作成されるロール）を使用するようにしたため、作成はしていません。
権限は、できるだけ必要なものだけにしているつもりですが、必要に応じて見直しをお願いします。

```python:cdk_app/cdk_lambda_cicd_stack.py

(中略)

# CodeBuild用のIAMロールを作成
def CreateBuildIAMRole(scope, cfn_pkg_s3bucket, artifact_s3bucket):

    assume_role_policy_document = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": "sts:AssumeRole",
                "Effect": "Allow",
                "Principal": {
                    "Service": "codebuild.amazonaws.com"
                }
            }
        ]
    }

    policy_document = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Resource": [
                    "arn:aws:s3:::{0}/*".format(cfn_pkg_s3bucket.bucket_name),
                    "arn:aws:s3:::{0}".format(cfn_pkg_s3bucket.bucket_name),
                    "arn:aws:s3:::{0}/*".format(artifact_s3bucket.bucket_name),
                    "arn:aws:s3:::{0}".format(artifact_s3bucket.bucket_name)
                ],
                "Action": [
                    "s3:PutObject",
                    "s3:GetObject",
                    "s3:GetObjectVersion",
                    "s3:GetBucketAcl",
                    "s3:GetBucketLocation"
                ]
            },
            {
                "Effect": "Allow",
                "Resource": "*",
                "Action": [
                    "logs:CreateLogGroup",
                    "logs:CreateLogStream",
                    "logs:PutLogEvents"
                ]
            },
            {
                "Action": [
                    "iam:PassRole"
                ],
                "Resource": "*",
                "Effect": "Allow"
            },
            {
                "Action": [
                    "cloudformation:*"
                ],
                "Resource": "*",
                "Effect": "Allow"
            }
        ]
    }

    cfn_role = iam.CfnRole(
            scope,
            "codebuild-role",
            assume_role_policy_document=assume_role_policy_document,
            policies=[
                iam.CfnRole.PolicyProperty(
                    policy_document=policy_document,
                    policy_name="codebuild-policy"
                )
            ],
            role_name="codebuild-role"
        )
    
    return cfn_role

# CodePipeline用のIAMロールを作成
def CreatePipelineIAMRole(scope, source_s3bucket, artifact_s3bucket):

    assume_role_policy_document = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": "sts:AssumeRole",
                "Effect": "Allow",
                "Principal": {
                    "Service": "codepipeline.amazonaws.com"
                }
            }
        ]
    }

    policy_document = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "codecommit:CancelUploadArchive",
                    "codecommit:GetBranch",
                    "codecommit:GetCommit",
                    "codecommit:GetUploadArchiveStatus",
                    "codecommit:UploadArchive"
                ],
                "Resource": "*"
            },
            {
                "Action": [
                    "codebuild:BatchGetBuilds",
                    "codebuild:StartBuild",
                    "codebuild:BatchGetBuildBatches",
                    "codebuild:StartBuildBatch"
                ],
                "Resource": "*",
                "Effect": "Allow"
            },
            {
                "Action": [
                    "s3:Get*",
                    "s3:ListBucket"
                ],
                "Resource": [
                    "arn:aws:s3:::{0}/*".format(source_s3bucket.bucket_name),
                    "arn:aws:s3:::{0}".format(source_s3bucket.bucket_name)
                ],
                "Effect": "Allow"
            },
            {
                "Action": [
                    "s3:Get*",
                    "s3:Put*",
                    "s3:ListBucket"
                ],
                "Resource": [
                    "arn:aws:s3:::{0}/*".format(artifact_s3bucket.bucket_name),
                    "arn:aws:s3:::{0}".format(artifact_s3bucket.bucket_name)
                ],
                "Effect": "Allow"
            },
            {
                "Action": [
                    "cloudformation:DescribeStacks",
                    "cloudformation:DescribeChangeSet",
                    "cloudformation:CreateChangeSet",
                    "cloudformation:ExecuteChangeSet",
                    "cloudformation:DeleteChangeSet"
                ],
                "Resource": [
                    "arn:aws:cloudformation:{0}:{1}:stack/lambda-stack-*".format("ap-northeast-1", os.environ["CDK_DEFAULT_ACCOUNT"])
                ],
                "Effect": "Allow"
            },
            {
                "Action": [
                    "cloudwatch:*"
                ],
                "Resource": "*",
                "Effect": "Allow"
            },
            {
                "Action": [
                    "iam:PassRole"
                ],
                "Resource": "*",
                "Effect": "Allow"
            }
        ]
    }

    cfn_role = iam.CfnRole(
            scope,
            "codepipeline-role",
            assume_role_policy_document=assume_role_policy_document,
            policies=[
                iam.CfnRole.PolicyProperty(
                    policy_document=policy_document,
                    policy_name="codepipeline-policy"
                )
            ],
            role_name="codepipeline-role"
        )
    
    return cfn_role

# LambdaFunction用のIAMロールを作成
def CreateLambdaIAMRole(scope):

    assume_role_policy_document = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": "sts:AssumeRole",
                "Effect": "Allow",
                "Principal": {
                    "Service": "lambda.amazonaws.com"
                }
            }
        ]
    }

    cfn_role = iam.CfnRole(
            scope,
            "lambda-role",
            assume_role_policy_document=assume_role_policy_document,
            managed_policy_arns=[
                "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
            ],
            role_name="lambda-role"
        )
    
    return cfn_role

# EvantBridge用のIAMロールを作成
def CreateExecPipelineIAMRole(scope, pipeline_arn):

    assume_role_policy_document = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": "sts:AssumeRole",
                "Effect": "Allow",
                "Principal": {
                    "Service": "events.amazonaws.com"
                }
            }
        ]
    }

    policy_document = {
        "Version": "2012-10-17",
        "Statement": [
            {
                    "Effect": "Allow",
                    "Action": "codepipeline:StartPipelineExecution",
                    "Resource": pipeline_arn
                }
        ]
    }

    cfn_role = iam.CfnRole(
            scope,
            "exec-codepipeline-role",
            assume_role_policy_document=assume_role_policy_document,
            policies=[
                iam.CfnRole.PolicyProperty(
                    policy_document=policy_document,
                    policy_name="exec-codepipeline-policy"
                )
            ],
            role_name="exec-codepipeline-role"
        )
    
    return cfn_role

(中略)

class CdkAppStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

(中略)

        # CodeBuild用のIAMロールの作成
        build_role = CreateBuildIAMRole(self, cfn_pkg_s3bucket, artifact_s3bucket)
        # CodePipeline用のIAMロールの作成
        pipeline_role = CreatePipelineIAMRole(self, source_s3bucket, artifact_s3bucket)
        # LambdaFunction用のIAMロールの作成
        lambda_role = CreateLambdaIAMRole(self)

(中略)

        # EventBridge用のIAMロールの作成
        exec_pipeline_role = CreateExecPipelineIAMRole(self, pipeline_arn)

(中略)
```

#### SAM Templateファイルの作成
Lambdaを作成するためのLambdaの各種設定を定義したSAM Templateファイルを作成します。
今回は必要最低限な定義のみにしていますが、Lambdaの処理に合わせてVPCの設定やLayer、トリガーの設定（AWS::Lambda::Permission）など必要な情報を追加してください。
作成したSAM Templateファイルは、zip化した後、aws_s3_deploymentでS3バケットへアップロードします。
aws_s3_deploymentの使い方は、以下を参照してください。
* [【AWS CDK (Python)】 CDKスタックでS3にファイルをアップロードする方法](https://qiita.com/takataka987/items/4b15d125b30b15d91cc7)

```python:cdk_app/cdk_lambda_cicd_stack.py

(中略)

# LambdaFunction作成用のSAM Templateファイルを作成
def EditSAMTemplate(lambda_id, lambda_role):

    sam_template_data = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Transform": "AWS::Serverless-2016-10-31",
        "Resources": {
            "TestLambdaFunction": {
                "Type": "AWS::Serverless::Function",
                "Properties": {
                    "Handler": "lambda_function.lambda_handler",
                    "Runtime": "python3.10",
                    "CodeUri": "",
                    "Description": "",
                    "FunctionName": "",
                    "AutoPublishAlias": "",
                    "MemorySize": 128,
                    "Timeout": 3,
                    "Role": "",
                    "Environment": {
                        "Variables": {
                        }
                    },
                    "DeploymentPreference": {
                        "Enabled": True,
                        "Type": "AllAtOnce"
                    }
                }
            }
        }
    }
    
    sam_template_data["Resources"]["TestLambdaFunction"]["Properties"]["CodeUri"] = lambda_id
    sam_template_data["Resources"]["TestLambdaFunction"]["Properties"]["FunctionName"] = lambda_id
    sam_template_data["Resources"]["TestLambdaFunction"]["Properties"]["AutoPublishAlias"] = "Dev"
    sam_template_data["Resources"]["TestLambdaFunction"]["Properties"]["Role"] =\
        "arn:aws:iam::{0}:role/{1}".format(os.environ["CDK_DEFAULT_ACCOUNT"], lambda_role.role_name)
    sam_template_data["Resources"]["TestLambdaFunction"]["Properties"]["Environment"]["Variables"] =\
        {"test_variables": "test"}
    
    return sam_template_data

(中略)

class CdkAppStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        lambda_id = "TestFunc"
        repository_name = "test-lambda-rep"

(中略)

        zip_dir = "./cdk_app/work/"
        zip_file = "template_{0}.zip".format(lambda_id)
        zip_path = zip_dir + zip_file
        template_file_name = "sam-template"
        # SAM Templateファイルの編集
        sam_template_data = EditSAMTemplate(lambda_id, lambda_role)
        with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_STORED) as z:
            z.writestr(template_file_name + ".json", json.dumps(sam_template_data, indent=4))
        # 作成したSAM TemplateファイルをS3へアップロード
        s3_deploy = s3deploy.BucketDeployment(
            self,
            "BuildspecFileUploadS3",
            sources=[s3deploy.Source.asset(zip_dir)],
            destination_bucket=source_s3bucket_l2,
        )

(中略)
```

#### Sourceアクションの作成
パイプラインに組み込むSourceアクションを作成します。
Sourceは、LambdaのソースコードとbuildspecがあるCodeCommitリポジトリとSAMテンプレートファイルがあるS3バケットの2つがあるので、アクションも2つ作成します。
```python:cdk_app/cdk_lambda_cicd_stack.py

(中略)

class CdkAppStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        lambda_id = "TestFunc"
        repository_name = "test-lambda-rep"

(中略)

        source_actions = []
        # Sourceアクション（CodeCommit（Lambdaコード、buildspec））の作成
        source_actions.append(
            codepipeline.CfnPipeline.ActionDeclarationProperty(
                action_type_id=codepipeline.CfnPipeline.ActionTypeIdProperty(
                    category="Source",
                    owner="AWS",
                    provider="CodeCommit",
                    version="1"
                ),
                name="Source_codecommit",
                configuration={
                    "RepositoryName": repository_name,
                    "BranchName": "main",
                    "PollForSourceChanges": False
                },
                namespace="SourceVariables_codecommit",
                output_artifacts=[codepipeline.CfnPipeline.OutputArtifactProperty(
                    name="SourceArtifact_codecommit"
                )],
                region="ap-northeast-1",
                run_order=1
            )
        )

        # Sourceアクション（SAM Template）の作成
        source_actions.append(
            codepipeline.CfnPipeline.ActionDeclarationProperty(
                action_type_id=codepipeline.CfnPipeline.ActionTypeIdProperty(
                    category="Source",
                    owner="AWS",
                    provider="S3",
                    version="1"
                ),
                name="Source_template",
                configuration={
                    "S3Bucket": source_s3bucket.bucket_name,
                    "S3ObjectKey": zip_file,
                    "PollForSourceChanges": False
                },
                namespace="SourceVariables_buildspec",
                output_artifacts=[codepipeline.CfnPipeline.OutputArtifactProperty(
                    name="SourceArtifact_template"
                )],
                region="ap-northeast-1",
                run_order=1
            )
        )

(中略)
```

#### CodeBuildプロジェクト、Buildアクションの作成
CodeBuildプロジェクトを作成し、そのCodeBuildプロジェクトを実行するパイプラインのBuildアクションを作成します。
CodeBuildプロジェクトについて、特筆することは特にありませんが、buildspecファイル内で環境変数を使っているで、CodeBuildプロジェクトの設定で環境変数を設定します。（SAMテンプレートファイル名やS3バケット名）
Buildアクションでは、2つ作成したSourceアクションのOutputアーティファクトを両方Inputにするようにします。 Inputが複数ある場合は、PrimarySourceを設定する必要があるので、CodeCommitのSourceアクションのOutputアーティファクトをPrimarySourceに設定します。（buildspecがこちらにあるため）

```python:cdk_app/cdk_lambda_cicd_stack.py

(中略)

class CdkAppStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        lambda_id = "TestFunc"
        repository_name = "test-lambda-rep"

(中略)

        # Buildプロジェクトの作成
        cfn_project = codebuild.CfnProject(
            self, 
            "build-lambda-project",
            artifacts=codebuild.CfnProject.ArtifactsProperty(
                type="CODEPIPELINE",
            ),
            environment=codebuild.CfnProject.EnvironmentProperty(
                compute_type="BUILD_GENERAL1_SMALL",
                image="aws/codebuild/standard:7.0",
                type="LINUX_CONTAINER",
                privileged_mode=False,
                environment_variables=[
                    codebuild.CfnProject.EnvironmentVariableProperty(
                        name="TEMPLATE_FILE_NAME",
                        value=template_file_name,
                    ),
                    codebuild.CfnProject.EnvironmentVariableProperty(
                        name="CFN_PKG_BUCKET",
                        value=cfn_pkg_s3bucket.bucket_name,
                    )
                ],
            ),
            service_role=build_role.attr_arn,
            source=codebuild.CfnProject.SourceProperty(
                type="CODEPIPELINE",
                build_spec="buildspec_{0}.yaml".format(lambda_id)
            ),
            logs_config=codebuild.CfnProject.LogsConfigProperty(
                cloud_watch_logs=codebuild.CfnProject.CloudWatchLogsConfigProperty(
                    status="ENABLED",
                ),
            ),
            name="build_lambda_project",
        )

(中略)

        # Buildアクションの作成
        build_actions = []
        build_actions.append(
            codepipeline.CfnPipeline.ActionDeclarationProperty(
                action_type_id=codepipeline.CfnPipeline.ActionTypeIdProperty(
                    category="Build",
                    owner="AWS",
                    provider="CodeBuild",
                    version="1"
                ),
                name="Build",
                configuration={
                    "ProjectName": cfn_project.name,
                    "PrimarySource": "SourceArtifact_codecommit"
                },
                input_artifacts=[
                    codepipeline.CfnPipeline.InputArtifactProperty(
                        name="SourceArtifact_codecommit"
                    ),
                    codepipeline.CfnPipeline.InputArtifactProperty(
                        name="SourceArtifact_template"
                    )
                ],
                namespace="BuildVariables",
                output_artifacts=[codepipeline.CfnPipeline.OutputArtifactProperty(
                    name="BuildArtifact"
                )],
                region="ap-northeast-1",
                run_order=1
            )
        )

(中略)
```

```yaml:test-lambda-rep/buildspec_TestFunc.yaml
version: 0.2
phases:
  install:
    commands:
      - aws --version
      - cp -pr ../01/* .
      - ls -lR ./
  build:
    commands:
      - aws cloudformation package --template-file ${TEMPLATE_FILE_NAME}.json --s3-bucket ${CFN_PKG_BUCKET} --output-template-file out-${TEMPLATE_FILE_NAME}.yaml
artifacts:
 files:
    - out-${TEMPLATE_FILE_NAME}.yaml
```

#### Deployアクションの作成
パイプラインに組み込むDeployアクションを作成します。
Deployアクションは、CloudFormation変更セットの作成と変更セットの実行の2段になるので、それぞれ作成します。
特に特筆すべき内容はありませんが、前述のとおり実行ロールは、cdkがデプロイに使用するロール（cdk bootstrapで作成されるロール）を使用するようにしています。

```python:cdk_app/cdk_lambda_cicd_stack.py

(中略)

class CdkAppStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        lambda_id = "TestFunc"
        repository_name = "test-lambda-rep"

(中略)

        deploy_actions = []
        # Deployアクション（変更セットの作成）の作成
        deploy_actions.append(
            codepipeline.CfnPipeline.ActionDeclarationProperty(
                action_type_id=codepipeline.CfnPipeline.ActionTypeIdProperty(
                    category="Deploy",
                    owner="AWS",
                    provider="CloudFormation",
                    version="1"
                ),
                name="CreateChangeSet",
                configuration={
                    "ActionMode": "CHANGE_SET_REPLACE",
                    "RoleArn": "arn:aws:iam::{0}:role/cdk-hnb659fds-cfn-exec-role-{0}-{1}".\
        format(os.environ["CDK_DEFAULT_ACCOUNT"], "ap-northeast-1"),
                    "StackName": "lambda-stack-{0}".format(lambda_id),
                    "ChangeSetName": "lambda-stack-{0}-changeSet".format(lambda_id),
                    "Capabilities": "CAPABILITY_NAMED_IAM",
                    "TemplatePath": "BuildArtifact::{0}".format("out-{0}.yaml".format(template_file_name))
                },
                input_artifacts=[
                    codepipeline.CfnPipeline.InputArtifactProperty(
                        name="BuildArtifact"
                    )
                ],
                namespace="DeployCreateVariables",
                region="ap-northeast-1",
                run_order=1
            )
        )

        # Deployアクション（変更セットの実行）の作成
        deploy_actions.append(
            codepipeline.CfnPipeline.ActionDeclarationProperty(
                action_type_id=codepipeline.CfnPipeline.ActionTypeIdProperty(
                    category="Deploy",
                    owner="AWS",
                    provider="CloudFormation",
                    version="1"
                ),
                name="ExecuteChangeSet",
                configuration={
                    "ActionMode": "CHANGE_SET_EXECUTE",
                    "ChangeSetName": "lambda-stack-{0}-changeSet".format(lambda_id),
                    "StackName": "lambda-stack-{0}".format(lambda_id)
                },
                input_artifacts=[
                    codepipeline.CfnPipeline.InputArtifactProperty(
                        name="BuildArtifact"
                    )
                ],
                namespace="DeployExecuteVariables",
                region="ap-northeast-1",
                run_order=2
            )
        )

(中略)
```

#### パイプラインの作成
上記までで作成した、Sourceアクション、Buildアクション、Deployアクションを使用して、パイプラインを作成します。
```python:cdk_app/cdk_lambda_cicd_stack.py

(中略)

class CdkAppStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        lambda_id = "TestFunc"
        repository_name = "test-lambda-rep"

(中略)

        # Pipelineの作成
        cfn_pipeline = codepipeline.CfnPipeline(
            self,
            "pipeline-lambda",
            role_arn=pipeline_role.attr_arn,
            stages=[
                codepipeline.CfnPipeline.StageDeclarationProperty(
                    actions=source_actions,
                    name="Source",
                ),
                codepipeline.CfnPipeline.StageDeclarationProperty(
                    actions=build_actions,
                    name="Build",
                ),
                codepipeline.CfnPipeline.StageDeclarationProperty(
                    actions=deploy_actions,
                    name="Deploy",
                )
            ],
            artifact_store=codepipeline.CfnPipeline.ArtifactStoreProperty(
                location=artifact_s3bucket.ref,
                type="S3",
            ),
            name="pipeline_lambda",
            restart_execution_on_update=False
        )
        cfn_pipeline.node.add_dependency(s3_deploy)

(中略)
```

#### パイプラインの自動起動設定
CodeCommitリポジトリが更新されたらパイプラインが自動起動するようにEventBridgeルールを作成します。

```python:cdk_app/cdk_lambda_cicd_stack.py

(中略)
# CodeCommitリポジトリの変更を検知するEventBridgeルールの作成
def CodecommitEvents(scope, rep_name, pipeline_arn, exec_pipeline_role):

    event_pattern = {
        "source": ["aws.codecommit"],
        "detail-type": ["CodeCommit Repository State Change"],
        "resources": [
            "arn:aws:codecommit:{0}:{1}:{2}".format(
                "ap-northeast-1", os.environ["CDK_DEFAULT_ACCOUNT"], rep_name)
        ],
        "detail": {
            "event": ["referenceCreated", "referenceUpdated"],
            "referenceType": ["branch"],
            "referenceName": ["main"]
        }
    }

    cfn_rule = events.CfnRule(
        scope,
        "change-repository-events",
        event_pattern=event_pattern,
        name="change-repository-events",
        state="ENABLED",
        targets=[
            events.CfnRule.TargetProperty(
                arn=pipeline_arn,
                id="change-repository-events",
                role_arn=exec_pipeline_role.attr_arn,
            )
        ]
    )

class CdkAppStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

(中略)
        # CodeCommitリポジトリの変更を検知するEventBridgeルールの作成
        CodecommitEvents(self, repository_name, pipeline_arn, exec_pipeline_role)

```

## CI/CD環境のデプロイ＆実行確認
以上で準備が整ったので、CI/CD環境のデプロイを行います。デプロイすると自動でパイプラインも実行されます。

```console
(.venv) user_name:~/environment/cdk-app (master) $ cdk deploy
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/de511cf8-1321-b7c4-a0e5-95776332ce58.png)

Lambda関数を確認すると、こちらも作成されていることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/8ef313fc-af0c-c29d-1a02-faa3205098ef.png)

CodeCommitでLambdaのソースコードを変更してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/313dba63-47c9-de66-3a11-73edcbb8a67d.png)

パイプラインが自動で起動しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/240f60f7-4726-9153-912a-7d6492a3210b.png)

パイプラインが完了すると、Lambdaのソースコードが反映されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/0ccd30bd-09ec-6b6f-08a1-1490036b3499.png)

また、CodeDeployのコンソールでもデプロイの確認ができます。（今回はAllAtOnceにしたので、あまり面白くないですが。。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/6e6c0c38-99e3-206d-1c3e-bbe190ebfffa.png)


## まとめ
AWS CDKでLambdaのCI/CD環境を作成しました。インフラをCDKで管理しつつ、LambdaのソースコードはCodeCommitで管理するのはよく想定される構成と思いますが、実際しようとすると結構複雑ですね。。
最後まで読んでいただいてありがとうございます。
少しでも参考になれば幸いです。
#### 参考文献
* [AWS Cloud Development Kit (AWS CDK) v2デベロッパーガイド](https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/home.html)
* [AWS CDK Python Reference](https://docs.aws.amazon.com/cdk/api/v2/python/index.html)