【AWS CDK (Python)】 LambdaのCI/CD環境をIaCで作る（Layer対応版）

## はじめに
本記事は、私自身の備忘録を兼ねてAWS CDKをこれから始める方の一助になればと思い、AWS CDKの使い方等をまとめたものです。
前回、AWS CDKでLambdaのCI/CD環境を作成しましたが、今回は、その構成を少し変更してLambda LayerのCI/CDにも対応するようにしたいと思います。
前回の記事は、[こちら（【AWS CDK (Python)】 LambdaのCI/CD環境をIaCで作る）](https://qiita.com/takataka987/items/50fe40f9db859f7f8e56)を参照してください。
なお、本記事は私自身の経験を基に記載していますが、間違いがあったらすみません。

## 環境
本記事は以下の環境を使用して記載しています。
* 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で作る（Layer対応版）
Layerに対応しますが、構成としてはほぼ変わりありません。Lambdaと同様にCodeCommitにソースコードとbuildspecファイルを用意し、CDKでSAM Templateファイルを作成、パイプラインでBuild、Deployします。

Layer対応版の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

# ↓↓ Layer対応版 追加コード
# LambdaLayer作成用のSAM Templateファイルを作成
def EditSAMLayerTemplate(layer_id):

    sam_template_data = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Transform": "AWS::Serverless-2016-10-31",
        "Description": "An AWS Serverless Specification template describing your layer.",
        "Resources": {
            "TestLambdLayer": {
                "Type": "AWS::Serverless::LayerVersion",
                "Properties": {
                    "LayerName": "",
                    "ContentUri": "",
                    "CompatibleRuntimes": ["python3.10"]
                }
            },
            "TestLayerArnParameter": {
                "Type": "AWS::SSM::Parameter",
                "Properties": {
                    "Name": "",
                    "Type": "String",
                    "Value": { "Ref" : "TestLambdLayer" }
                }
            }
        }
    }

    sam_template_data["Resources"]["TestLambdLayer"]["Properties"]["ContentUri"] = layer_id
    sam_template_data["Resources"]["TestLambdLayer"]["Properties"]["LayerName"] = layer_id
    sam_template_data["Resources"]["TestLayerArnParameter"]["Properties"]["Name"] =\
        "/test/layer_arn/{0}".format(layer_id)

    return sam_template_data
# ↑↑ Layer対応版 追加コード


# LambdaFunction作成用のSAM Templateファイルを作成
# ↓↓ Layer対応版 変更コード
def EditSAMTemplate(lambda_id, lambda_role, layer_id):
# def EditSAMTemplate(lambda_id, lambda_role):
# ↑↑ Layer対応版 変更コード

    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": "",
# ↓↓ Layer対応版 追加コード
                    "AutoPublishAliasAllProperties": true,
# ↑↑ Layer対応版 追加コード
                    "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"}
# ↓↓ Layer対応版 追加コード
    sam_template_data["Resources"]["TestLambdaFunction"]["Properties"]["Environment"]["Variables"]["layer"] =\
        "{{resolve:ssm:" + "/test/layer_arn/{0}".format(layer_id) + "}}"
    sam_template_data["Resources"]["TestLambdaFunction"]["Properties"]["Layers"] =\
        ["{{resolve:ssm:" + "/test/layer_arn/{0}".format(layer_id) + "}}"]
# ↑↑ Layer対応版 追加コード

    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"
# ↓↓ Layer対応版 追加コード
        layer_id = "TestLayer"
# ↑↑ Layer対応版 追加コード
        repository_name = "test-lambda-rep"

        # buildspec、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, layer_id)
        # 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))

# ↓↓ Layer対応版 追加コード
        layer_zip_file = "template_{0}.zip".format(layer_id)
        layer_zip_path = zip_dir + layer_zip_file
        layer_template_file_name = "sam-layer-template"
        # SAM Templateファイルの編集
        sam_layer_template_data = EditSAMLayerTemplate(layer_id)
        with zipfile.ZipFile(layer_zip_path, 'w', zipfile.ZIP_STORED) as z:
            z.writestr(layer_template_file_name + ".json", json.dumps(sam_layer_template_data, indent=4))
# ↑↑ Layer対応版 追加コード

        # 作成したSAM TemplateファイルをS3へアップロード
        s3_deploy = s3deploy.BucketDeployment(
            self,
            "TemplateFileUploadS3",
            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",
        )

# ↓↓ Layer対応版 追加コード
        # Buildプロジェクト（Layer）の作成
        cfn_layer_project = codebuild.CfnProject(
            self, 
            "build-layer-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=layer_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(layer_id)
            ),
            logs_config=codebuild.CfnProject.LogsConfigProperty(
                cloud_watch_logs=codebuild.CfnProject.CloudWatchLogsConfigProperty(
                    status="ENABLED",
                ),
            ),
            name="build_layer_project",
        )
# ↑↑ Layer対応版 追加コード

        # 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_Template",
                output_artifacts=[codepipeline.CfnPipeline.OutputArtifactProperty(
                    name="SourceArtifact_template"
                )],
                region="ap-northeast-1",
                run_order=1
            )
        )
# ↓↓ Layer対応版 追加コード
        # Sourceアクション（LayerのSAM Template）の作成
        source_actions.append(
            codepipeline.CfnPipeline.ActionDeclarationProperty(
                action_type_id=codepipeline.CfnPipeline.ActionTypeIdProperty(
                    category="Source",
                    owner="AWS",
                    provider="S3",
                    version="1"
                ),
                name="Source_layer_template",
                configuration={
                    "S3Bucket": source_s3bucket.bucket_name,
                    "S3ObjectKey": layer_zip_file,
                    "PollForSourceChanges": False
                },
                namespace="SourceVariables_LayerTemplate",
                output_artifacts=[codepipeline.CfnPipeline.OutputArtifactProperty(
                    name="SourceArtifact_layer_template"
                )],
                region="ap-northeast-1",
                run_order=1
            )
        )
# ↑↑ Layer対応版 追加コード

        # 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
            )
        )
# ↓↓ Layer対応版 追加コード
        # Buildアクション（Layer）の作成
        build_actions.append(
            codepipeline.CfnPipeline.ActionDeclarationProperty(
                action_type_id=codepipeline.CfnPipeline.ActionTypeIdProperty(
                    category="Build",
                    owner="AWS",
                    provider="CodeBuild",
                    version="1"
                ),
                name="Build_layer",
                configuration={
                    "ProjectName": cfn_layer_project.name,
                    "PrimarySource": "SourceArtifact_codecommit"
                },
                input_artifacts=[
                    codepipeline.CfnPipeline.InputArtifactProperty(
                        name="SourceArtifact_codecommit"
                    ),
                    codepipeline.CfnPipeline.InputArtifactProperty(
                        name="SourceArtifact_layer_template"
                    )
                ],
                namespace="BuildLayerVariables",
                output_artifacts=[codepipeline.CfnPipeline.OutputArtifactProperty(
                    name="BuildLayerArtifact"
                )],
                region="ap-northeast-1",
                run_order=1
            )
        )
# ↑↑ Layer対応版 追加コード

        deploy_actions = []
# ↓↓ Layer対応版 追加コード
        # Deployアクション（Layerの変更セットの作成）の作成
        deploy_actions.append(
            codepipeline.CfnPipeline.ActionDeclarationProperty(
                action_type_id=codepipeline.CfnPipeline.ActionTypeIdProperty(
                    category="Deploy",
                    owner="AWS",
                    provider="CloudFormation",
                    version="1"
                ),
                name="CreateLayerChangeSet",
                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(layer_id),
                    "ChangeSetName": "lambda-stack-{0}-changeSet".format(layer_id),
                    "Capabilities": "CAPABILITY_NAMED_IAM",
                    "TemplatePath": "BuildLayerArtifact::{0}".format("out-{0}.yaml".format(layer_template_file_name))
                },
                input_artifacts=[
                    codepipeline.CfnPipeline.InputArtifactProperty(
                        name="BuildLayerArtifact"
                    )
                ],
                namespace="DeployCreateLayerVariables",
                region="ap-northeast-1",
                run_order=1
            )
        )

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

        # 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=3
                # 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=4
                # 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リポジトリに用意するファイルです。
TestFunc/lambda_function.pyとbuildspec_TestFunc.yamlは、Layerの確認のため、少し前回と変更しています。
```python:test-lambda-rep/TestFunc/lambda_function.py
from test_util import test_message
def lambda_handler(event, context):
    print(test_message())
```
```yaml:test-lambda-rep/buildspec_TestFunc.yaml
version: 0.2
phases:
  install:
    commands:
      - aws --version
      - cp -pr ../0[1-9]/* .
      - 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
```
```python:test-lambda-rep/TestLayer/python/test_util.py
def test_message():
    return "test message!"
```
```yaml:test-lambda-rep/buildspec_TestLayer.yaml
version: 0.2
phases:
  install:
    commands:
      - aws --version
      - cp -pr ../0[1-9]/* .
      - 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
```

では、変更箇所を処理毎に順に説明します。
* [SAM Templateファイルの作成](#sam-templateファイルの作成)
* [Sourceアクションの作成](#sourceアクションの作成)
* [CodeBuildプロジェクト、Buildアクションの作成](#codebuildプロジェクトbuildアクションの作成)
* [Deployアクションの作成](#deployアクションの作成)

#### SAM Templateファイルの作成
Lambdaと同じように、Layerの設定を定義したSAM Templateファイルを作成します。
Layerの定義に加えて、LayerのARNをパラメータストアに格納するようにします。併せて、Lambdaの定義にLayerの設定を追加し、ARNをパラメータストアから読み込むようにします。
パラメータストアを使用せずにスタック参照（ExportValue&ImportValue）でもいけるかと思いましたが、Layerの更新の際にエラーになるため、パラメータストアを使用することにしました。
あと、環境変数にLayerのARNを設定するようにしています。Layerだけ更新された場合に、Lambdaのバージョンが作成されない（バグらしいですが。。）ため、Layerに伴って環境変数が変更されることで、Lambdaもバージョンが作成されるようにしています。
作成したSAM Templateファイルは、zip化した後、LambdaのSAM Templateファイルと一緒にaws_s3_deploymentでS3バケットへアップロードします。

```python:cdk_app/cdk_app_stack.py

(中略)

# ↓↓ Layer対応版 追加コード
# LambdaLayer作成用のSAM Templateファイルを作成
def EditSAMLayerTemplate(layer_id):

    sam_template_data = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Transform": "AWS::Serverless-2016-10-31",
        "Description": "An AWS Serverless Specification template describing your layer.",
        "Resources": {
            "TestLambdLayer": {
                "Type": "AWS::Serverless::LayerVersion",
                "Properties": {
                    "LayerName": "",
                    "ContentUri": "",
                    "CompatibleRuntimes": ["python3.10"]
                }
            },
            "TestLayerArnParameter": {
                "Type": "AWS::SSM::Parameter",
                "Properties": {
                    "Name": "",
                    "Type": "String",
                    "Value": { "Ref" : "TestLambdLayer" }
                }
            }
        }
    }

    sam_template_data["Resources"]["TestLambdLayer"]["Properties"]["ContentUri"] = layer_id
    sam_template_data["Resources"]["TestLambdLayer"]["Properties"]["LayerName"] = layer_id
    sam_template_data["Resources"]["TestLayerArnParameter"]["Properties"]["Name"] =\
        "/test/layer_arn/{0}".format(layer_id)

    return sam_template_data
# ↑↑ Layer対応版 追加コード


# LambdaFunction作成用のSAM Templateファイルを作成
# ↓↓ Layer対応版 変更コード
def EditSAMTemplate(lambda_id, lambda_role, layer_id):
# def EditSAMTemplate(lambda_id, lambda_role):
# ↑↑ Layer対応版 変更コード

    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": "",
# ↓↓ Layer対応版 追加コード
                    "AutoPublishAliasAllProperties": true,
# ↑↑ Layer対応版 追加コード
                    "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"}
# ↓↓ Layer対応版 追加コード
    sam_template_data["Resources"]["TestLambdaFunction"]["Properties"]["Environment"]["Variables"]["layer"] =\
        "{{resolve:ssm:" + "/test/layer_arn/{0}".format(layer_id) + "}}"
    sam_template_data["Resources"]["TestLambdaFunction"]["Properties"]["Layers"] =\
        ["{{resolve:ssm:" + "/test/layer_arn/{0}".format(layer_id) + "}}"]
# ↑↑ Layer対応版 追加コード

    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"
# ↓↓ Layer対応版 追加コード
        layer_id = "TestLayer"
# ↑↑ Layer対応版 追加コード
        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, layer_id)
        # 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))

# ↓↓ Layer対応版 追加コード
        layer_zip_file = "template_{0}.zip".format(layer_id)
        layer_zip_path = zip_dir + layer_zip_file
        layer_template_file_name = "sam-layer-template"
        # SAM Templateファイルの編集
        sam_layer_template_data = EditSAMLayerTemplate(layer_id)
        with zipfile.ZipFile(layer_zip_path, 'w', zipfile.ZIP_STORED) as z:
            z.writestr(layer_template_file_name + ".json", json.dumps(sam_layer_template_data, indent=4))
# ↑↑ Layer対応版 追加コード

        # 作成したSAM TemplateファイルをS3へアップロード
        s3_deploy = s3deploy.BucketDeployment(
            self,
            "TemplateFileUploadS3",
            sources=[s3deploy.Source.asset(zip_dir)],
            destination_bucket=source_s3bucket_l2,
        )

(中略)
```

#### Sourceアクションの作成
LayerのSAMテンプレートファイル分のSourceアクションを追加します。（今回、CodeCommitリポジトリはLambdaソースコードと同じにしているので、追加はしていません。）
```python:cdk_app/cdk_app_stack.py

(中略)

class CdkAppStack(Stack):

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

        lambda_id = "TestFunc"
# ↓↓ Layer対応版 追加コード
        layer_id = "TestLayer"
# ↑↑ Layer対応版 追加コード
        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_Template",
                output_artifacts=[codepipeline.CfnPipeline.OutputArtifactProperty(
                    name="SourceArtifact_template"
                )],
                region="ap-northeast-1",
                run_order=1
            )
        )
# ↓↓ Layer対応版 追加コード
        # Sourceアクション（LayerのSAM Template）の作成
        source_actions.append(
            codepipeline.CfnPipeline.ActionDeclarationProperty(
                action_type_id=codepipeline.CfnPipeline.ActionTypeIdProperty(
                    category="Source",
                    owner="AWS",
                    provider="S3",
                    version="1"
                ),
                name="Source_layer_template",
                configuration={
                    "S3Bucket": source_s3bucket.bucket_name,
                    "S3ObjectKey": layer_zip_file,
                    "PollForSourceChanges": False
                },
                namespace="SourceVariables_LayerTemplate",
                output_artifacts=[codepipeline.CfnPipeline.OutputArtifactProperty(
                    name="SourceArtifact_layer_template"
                )],
                region="ap-northeast-1",
                run_order=1
            )
        )
# ↑↑ Layer対応版 追加コード

(中略)
```

#### CodeBuildプロジェクト、Buildアクションの作成
Layer分のCodeBuildプロジェクトを作成し、そのCodeBuildプロジェクトを実行するパイプラインのBuildアクションを作成します。
内容としては、ほぼLambdaと同じです。（すみません。共通化をサボりました。。）
```python:cdk_app/cdk_app_stack.py

(中略)

class CdkAppStack(Stack):

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

        lambda_id = "TestFunc"
# ↓↓ Layer対応版 追加コード
        layer_id = "TestLayer"
# ↑↑ Layer対応版 追加コード
        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",
        )

# ↓↓ Layer対応版 追加コード
        # Buildプロジェクト（Layer）の作成
        cfn_layer_project = codebuild.CfnProject(
            self, 
            "build-layer-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=layer_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(layer_id)
            ),
            logs_config=codebuild.CfnProject.LogsConfigProperty(
                cloud_watch_logs=codebuild.CfnProject.CloudWatchLogsConfigProperty(
                    status="ENABLED",
                ),
            ),
            name="build_layer_project",
        )
# ↑↑ Layer対応版 追加コード

(中略)

        # 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
            )
        )
# ↓↓ Layer対応版 追加コード
        # Buildアクション（Layer）の作成
        build_actions.append(
            codepipeline.CfnPipeline.ActionDeclarationProperty(
                action_type_id=codepipeline.CfnPipeline.ActionTypeIdProperty(
                    category="Build",
                    owner="AWS",
                    provider="CodeBuild",
                    version="1"
                ),
                name="Build_layer",
                configuration={
                    "ProjectName": cfn_layer_project.name,
                    "PrimarySource": "SourceArtifact_codecommit"
                },
                input_artifacts=[
                    codepipeline.CfnPipeline.InputArtifactProperty(
                        name="SourceArtifact_codecommit"
                    ),
                    codepipeline.CfnPipeline.InputArtifactProperty(
                        name="SourceArtifact_layer_template"
                    )
                ],
                namespace="BuildLayerVariables",
                output_artifacts=[codepipeline.CfnPipeline.OutputArtifactProperty(
                    name="BuildLayerArtifact"
                )],
                region="ap-northeast-1",
                run_order=1
            )
        )
# ↑↑ Layer対応版 追加コード

(中略)
```

```yaml:test-lambda-rep/buildspec_TestFunc.yaml
version: 0.2
phases:
  install:
    commands:
      - aws --version
      - cp -pr ../0[1-9]/* .
      - 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
```

```yaml:test-lambda-rep/buildspec_TestLayer.yaml
version: 0.2
phases:
  install:
    commands:
      - aws --version
      - cp -pr ../0[1-9]/* .
      - 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アクションを作成します。
ここもほほLambdaの時と同じですね。
LambdaよりLayerが先にデプロイされるようにrun_orderの値を設定します。
```python:cdk_app/cdk_app_stack.py

(中略)

class CdkAppStack(Stack):

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

        lambda_id = "TestFunc"
# ↓↓ Layer対応版 追加コード
        layer_id = "TestLayer"
# ↑↑ Layer対応版 追加コード
        repository_name = "test-lambda-rep"

(中略)

        deploy_actions = []
# ↓↓ Layer対応版 追加コード
        # Deployアクション（Layerの変更セットの作成）の作成
        deploy_actions.append(
            codepipeline.CfnPipeline.ActionDeclarationProperty(
                action_type_id=codepipeline.CfnPipeline.ActionTypeIdProperty(
                    category="Deploy",
                    owner="AWS",
                    provider="CloudFormation",
                    version="1"
                ),
                name="CreateLayerChangeSet",
                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(layer_id),
                    "ChangeSetName": "lambda-stack-{0}-changeSet".format(layer_id),
                    "Capabilities": "CAPABILITY_NAMED_IAM",
                    "TemplatePath": "BuildLayerArtifact::{0}".format("out-{0}.yaml".format(layer_template_file_name))
                },
                input_artifacts=[
                    codepipeline.CfnPipeline.InputArtifactProperty(
                        name="BuildLayerArtifact"
                    )
                ],
                namespace="DeployCreateLayerVariables",
                region="ap-northeast-1",
                run_order=1
            )
        )

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

        # 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=3
                # 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=4
                # run_order=2
            )
        )
    
(中略)
```

## 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/ed0f326b-5404-4a3b-97d4-0f458f4dcf5b.png)

Layerを確認すると、きちんと作成されていることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/c2f93b09-b875-fa64-86d1-5a7b36bfdc70.png)

Lambdaを実行し、Layerが使用されていることも確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/cf6232d3-c27d-7400-5dcb-cb423bd27e30.png)

CodeCommitでLayerのソースコードを変更してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/9dd7af50-aced-a640-562a-aba519941c20.png)

パイプラインが自動で起動しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/2c1edef9-42c2-7544-ad63-0c66297bc064.png)

パイプラインが完了すると、Layerが更新されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/d36faf13-5f4e-bfff-5415-f655ea0423e7.png)

Lambdaを実行し、更新したLayerが使用されていることが確認できました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3445559/bfc2dafc-5701-052a-1641-2e3e636bbc7b.png)


## まとめ
AWS CDKでLayerに対応したLambdaのCI/CD環境を作成しました。Lambda使うならLayerも使えた方がいいよなぁと思ってしましたが、ほぼLambdaと同じでしたね。
最後まで読んでいただいてありがとうございます。
少しでも参考になれば幸いです。
#### 参考文献
* [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)