CFNのWaitConditionを使用して、Lambdaデプロイパッケージのビルドを待つ
CloudFormationを使用して、Lambda関数を作成することを考えます。
以下のページでご紹介した通り、関数を作成する方法は3パターンあります。
本ページでは、特にZIPファイルのデプロイパッケージをS3バケットに配置する方法に注目します。
CloudFormationにおいて、この方法でLambda関数を作成する場合、関数リソースが生成されるよりも前に、デプロイパッケージをS3バケットに配置しておく必要があります。
しかしパッケージに含める内容によっては、ビルドに時間を要する場合もあり、関数リソース作成までに間に合わないこともあり得ます。
そこで今回はCloudFormationのWaitConditionを使用します。
具体的には、Lambda関数リソースの生成を、デプロイパッケージの作成まで待機します。
構築する環境
以下の流れでLambda関数3を作成します。
- CodeBuildで関数用のデプロイパッケージを作成する。
- パッケージをS3バケットに配置する。
- S3バケットのイベント通知先にEventBridgeを指定する。
- S3のオブジェクト作成に関するEventBridgeルールを作成し、Lambda関数2をトリガーする。
- 関数2では、CloudFormationのWaitConditionのシグナルを発信する。
- WaitConditionのシグナルを受けて、S3バケットのデプロイパッケージを用いて、Lambda関数3が作成する。
CodeBuildプロジェクトを開始するキッカケですが、CloudFormationカスタムリソースを使用します。
具体的には、カスタムリソースに紐づくLambda関数でCodeBuildプロジェクトをトリガーします。
なおLambda関数のランタイム環境はPython3.8とします。
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。
https://github.com/awstut-an-r/awstut-fa/tree/main/142
テンプレートファイルのポイント解説
S3バケット
Resources:
Bucket:
Type: AWS::S3::Bucket
Properties:
AccessControl: Private
BucketName: !Ref Prefix
NotificationConfiguration:
EventBridgeConfiguration:
EventBridgeEnabled: true
Code language: YAML (yaml)
デプロイパッケージを配置するS3バケットです。
ポイントはイベント通知機能です。
今回の構成では、S3バケットにパッケージが配置されたことをトリガーとして、Lambda関数2を実行する必要があります。
NotificationConfigurationプロパティでEventBridge通知機能を有効化します。
なお今回はイベント通知先としてEventBridgeを選択しましたが、直接Lambda関数を呼び出すこともできます。
詳細につきましては、以下のページをご確認ください。
CodeBuild
Resources:
CodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Artifacts:
Type: NO_ARTIFACTS
Cache:
Type: NO_CACHE
Environment:
ComputeType: !Ref ProjectEnvironmentComputeType
EnvironmentVariables:
- Name: BUCKET_NAME
Type: PLAINTEXT
Value: !Ref BucketName
- Name: SOURCE_BUNDLE_NAME
Type: PLAINTEXT
Value: !Ref SourceBundleName
- Name: SOURCE_FILE_NAME
Type: PLAINTEXT
Value: !Ref SourceFileName
Image: !Ref ProjectEnvironmentImage
ImagePullCredentialsType: CODEBUILD
Type: !Ref ProjectEnvironmentType
PrivilegedMode: true
LogsConfig:
CloudWatchLogs:
Status: DISABLED
S3Logs:
Status: DISABLED
Name: !Ref Prefix
ServiceRole: !GetAtt CodeBuildRole.Arn
Source:
Type: NO_SOURCE
BuildSpec: !Sub |
version: 0.2
phases:
pre_build:
commands:
- |
cat << EOF > $SOURCE_FILE_NAME
def lambda_handler(event, context):
return 'hogehoge'
EOF
build:
commands:
- zip $SOURCE_BUNDLE_NAME -r * .[^.]*
- sleep 300
post_build:
commands:
- aws s3 cp $SOURCE_BUNDLE_NAME s3://$BUCKET_NAME/
Visibility: PRIVATE
Code language: YAML (yaml)
CodeBuildを使用して、Lambda関数用のデプロイパッケージを作成します。
BuildSpecプロパティでbuildspec.yamlの中身を記載します。
pre_buildフェーズで関数用のソースコードを作成します。
buildフェーズでデプロイパッケージを作成します。
具体的には、前フェーズで作成したファイルをZIP化します。
なおこのビルドは時間を要するものとするため、sleepコマンドで5分待機させます。
post_buildフェーズで、デプロイパッケージをS3バケットにアップロードします。
以下がCodeBuild用のIAMロールです。
Resources:
CodeBuildRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- codebuild.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: PutS3ObjectPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- s3:PutObject
Resource:
- !Sub "arn:aws:s3:::${BucketName}/*"
Code language: YAML (yaml)
S3バケットにオブジェクトを配置するための権限を付与します。
Lambda関数1
Resources:
CustomResource:
Type: Custom::CustomResource
Properties:
ServiceToken: !GetAtt Function1.Arn
Function1:
Type: AWS::Lambda::Function
Properties:
Architectures:
- !Ref Architecture
Environment:
Variables:
CODEBUILD_PROJECT: !Ref CodeBuildProject
Code:
ZipFile: |
import boto3
import cfnresponse
import os
codebuild_project = os.environ['CODEBUILD_PROJECT']
CREATE = 'Create'
response_data = {}
client = boto3.client('codebuild')
def lambda_handler(event, context):
try:
if event['RequestType'] == CREATE:
response = client.start_build(
projectName=codebuild_project
)
print(response)
cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
except Exception as e:
print(e)
cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
FunctionName: !Sub "${Prefix}-function-01"
Handler: !Ref Handler
Runtime: !Ref Runtime
Role: !GetAtt FunctionRole1.Arn
Code language: YAML (yaml)
Lambda関数1はCodeBuildプロジェクトを自動的に開始するために使用します。
具体的には、CloudFormationカスタムリソースに本関数を関連づけます。
CloudFormationカスタムリソースに関する詳細は、以下のページをご確認ください。
今回はカスタムリソース作成時に、boto3のCodeBuild用クライアントオブジェクトのstart_buildメソッドを実行します。
これで自動的にCodeBuildプロジェクトを開始させます。
以下が本関数用のIAMロールです。
Resources:
FunctionRole1:
Type: AWS::IAM::Role
DeletionPolicy: Delete
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service:
- lambda.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: StartCodeBuildPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- codebuild:StartBuild
Resource:
- !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/${CodeBuildProject}"
Code language: YAML (yaml)
CodeBuildプロジェクトを開始させるための権限を与えます。
EventBridge
Resources:
EventsRule:
Type: AWS::Events::Rule
Properties:
EventBusName: !Ref EventBusName
EventPattern:
source:
- aws.s3
detail-type:
- Object Created
detail:
bucket:
name:
- !Ref BucketName
Name: !Sub "${Prefix}-EventsRule"
State: ENABLED
Targets:
- Arn: !GetAtt Function2.Arn
Id: !Ref Function2
Code language: YAML (yaml)
EventBridgeルールです。
先述のS3バケットにオブジェクトが作成された場合、後述のLambda関数2を実行します。
ターゲットにLambda関数を指定する場合、EventBridgeが関数を呼び出すという挙動になります。
そのためEventBridgeに関数を呼び出す権限を与える必要があります。
Resources:
EventsRulePermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !Ref Function2
Principal: events.amazonaws.com
SourceArn: !GetAtt EventsRule.Arn
Code language: YAML (yaml)
WaitCondition
AWS公式サイトでは、WaitConditionについて以下のように説明されています。
待機条件および待機条件ハンドルを使用して、AWS CloudFormation にスタックの作成を一時停止させ、シグナルを待ってから作成を続行させることができます。
テンプレートでの待機条件の作成
具体的には、以下の通りに両リソースを定義します。
Resources:
WaitConditionHandle:
Type: AWS::CloudFormation::WaitConditionHandle
WaitCondition:
Type: AWS::CloudFormation::WaitCondition
Properties:
Handle: !Ref WaitConditionHandle
Timeout: !Ref WaitConditionTimeout
Code language: YAML (yaml)
WaitConditionのTimeoutプロパティに600を指定しました。
これで最長10分間はシグナル受信を待機します。
なお10分待機してもシグナルを受信しない場合は失敗となり、CloudFormationスタックがロールバックすることになります。
Lambda関数2
Resources:
Function2:
Type: AWS::Lambda::Function
Properties:
Architectures:
- !Ref Architecture
Environment:
Variables:
SIGNAL_URL: !Ref WaitConditionHandle
Code:
ZipFile: |
import json
import os
import urllib3
import uuid
def lambda_handler(event, context):
body = json.dumps({
"Status": "SUCCESS",
"Reason": "Lambda Deploy Package Setup Successed",
"UniqueId": str(uuid.uuid4()),
"Data": "Lambda Deploy Package Setup Successed"
})
http = urllib3.PoolManager()
http.request('PUT', os.environ['SIGNAL_URL'], body=body)
FunctionName: !Sub "${Prefix}-function-02"
Handler: !Ref Handler
Runtime: !Ref Runtime
Role: !GetAtt FunctionRole2.Arn
Code language: YAML (yaml)
本関数の働きはWaitConditionに成功シグナルを通知することです。
実装につきましては、以下のページを参考にしました。
この関数は先述のEventBridgeルールに関連づいています。
つまりCodeBuildによって、S3バケットにデプロイパッケージが配置されたことをトリガーとして、本関数が実行されます。
ですからS3バケットにデプロイパッケージが配置されるまで、CloudFomrationのリソース作成を待機させることができます。
Lambda関数3
Resources:
Function3:
Type: AWS::Lambda::Function
DependsOn:
- WaitCondition
Properties:
Architectures:
- !Ref Architecture
Code:
S3Bucket: !Ref BucketName
S3Key: !Ref SourceBundleName
FunctionName: !Sub "${Prefix}-function-03"
Handler: !Ref Handler
PackageType: Zip
Runtime: !Ref Runtime
Role: !GetAtt FunctionRole2.Arn
Code language: YAML (yaml)
この関数はS3バケットに配置したデプロイパッケージを使用します。
ポイントはDependsOnプロパティです。
先ほどのWaitConditionが指定されています。
つまりWaitConditionに成功シグナルが通知されるまで、本関数の作成は待機されるということです。
WaitConditionを使用することで、S3バケットにデプロイパッケージが配置される前に、リソース作成が開始されることを防止します。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- S3バケット:fa-142
- CodeBuildプロジェクト:fa-142
- Lambda関数1:fa-142-function-01
- Lambda関数2:fa-142-function-02
- Lambda関数3:fa-142-function-03
- EventBridgeルール:fa-142-EventsRule
作成されたリソースをAWS Management Consoleから確認します。
CodeBuildプロジェクトを確認します。
CloudFormationで定義した通りに、リソースが作成されています。
特にポイントなのが、buildspecです。
関数用のデプロイパッケージを実行過程で、300秒待機した上で、S3バケットに配置します。
これでビルドに時間を要する状況を再現しています。
S3バケットを確認します。
EventBridgeへのイベント通知機能が有効化されています。
EventBridgeルールを確認します。
イベントパターンを見ると、先述のS3バケットでオブジェクトが配置された時に、イベント通知する内容であることがわかります。
イベントのターゲットを見ると、後述するLambda関数2が指定されています。
Lambda関数1を確認します。
この関数はCloudFormationカスタムリソースに関連づけて使用します。
CloudFormationスタックの作成時に、本関数が動作します。
この関数の働きは、先述のCodeBuildプロジェクトを開始することです。
Lambda関数2を確認します。
この関数の働きは、WaitConditionに成功シグナルを通知することです。
動作確認
準備が整いましたので、実際の動作を確認します。
改めてCodeBuildプロジェクトを確認します。
プロジェクトが開始されています。
CloudFormationカスタムリソースの働きによって、これに関連づいているLambda関数1が実行されて、本プロジェクトが開始されたということです。
フェーズの詳細を見ると、ビルド中であることがわかります。
ビルドフェーズでsleepコマンドを実行することになっているため、これで待機中ということです。
CloudFormationスタックの作成状況を確認します。
ポイントは2つです。
1点目はWaitConditionが作成中である点です。
これによって、成功シグナルを受信するまで、スタックの作成が待機されている状態です。
2点目はLambda関数3の作成が開始されていない点です。
本関数はWaitConditionが成功シグナルを受信するまで作成されません。
これによってデプロイパッケージがS3バケットに配置されるまで、関数リソースの作成を待機できます。
しばらく待機すると、CodeBuildプロジェクトが正常終了します。
確かにビルドに300秒かかっていますね。
CodeBuildプロジェクトが正常に終了したことによって、S3バケットにデプロイパッケージが配置されているはずです。
S3バケットを確認します。
確かにデプロイパッケージが配置されています。
オブジェクト配置を受けて、EventBridgeがLambda関数2をトリガーします。
改めてCloudFormationスタックの作成状況を確認します。
WaitConditionのステータスが完了とあります。
これを受けて、Lambda関数3の作成が開始されています。
しばらく待機した後に、Lambda関数3を確認します。
確かにLambda関数3が作成されています。
このようにWaitConditionを使用することによって、リソースの作成のタイミングを制御することができます。
まとめ
CloudFormationのWaitConditionを使用sて、Lambda関数リソースの生成を、デプロイパッケージの作成まで待機する方法をご紹介しました。