この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
AWS事業本部コンサルティング部の後藤です。
Codeシリーズを使用してLambda関数を更新するとき、SAMを使用したデプロイ構成をよく見かけますがLambda関数単体でコードのみ更新したい場合はCodeBuildのbuildspec.ymlでAWS CLIを実行した方が構成的にもスッキリして簡単に実装出来そう?と思ったので、実際に試してみました。
それでは、やっていきましょう!
構成図
今回はCodeBuildとLambda関数が異なるアカウントになる事を想定していますので、以下のような構成で行っていきます。
CodeBuild等を構築するアカウントをAccount(A)
、Lambda関数を構築するアカウントをAccount(B)
としています。
Account(A) CodeCommit、CodeBuild、S3Bucket作成
Lambda関数を更新するために使用するCodeCommit、CodeBuild、S3BucketをAccount(A)に作成します。特に複雑な設定は行っていないため、今回は説明を省略致します。
Account(B) AssumeRole用IAMロール作成
Account(A)のCodeBuildがAccount(B)の権限でLambda関数を更新出来るようにするため、AssumeRole用のIAMロールを作成します。
今回はLambda関数の更新のみ行うため、以下のIAMポリシーを与えています。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "lambda:UpdateFunctionCode",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"s3:Get*",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::[[Account(A)のS3Bukcet名]]",
"arn:aws:s3:::[[Account(A)のS3Bucket名]]/*"
]
}
]
}
Account(A)のS3Bukcetに対する権限は、CodeBuildでLambda関数をzip化した後S3にアップロードしてから更新コマンドを実行するために付与しています。CodeBuildで実行するbuildspec.ymlに関しては後述します。
AssumeRole用IAMロールには信頼エンティティでAccount(A)からのAssumeRoleを許可するよう、以下のように設定しています。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[[Account(A)のアカウントID]]:root"
},
"Action": "sts:AssumeRole",
"Condition": {}
}
]
}
Account(A) CodeBuild ServiceRoleに権限追加
CodeBuildのServiceRoleからAccount(B)に作成したAssumeRole用IAMロールを使用するため、以下のような権限を追加します。CodeBuildのServiceRole自体は自動生成されたものを使用しています。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AssumeRolePolicy",
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": [
"arn:aws:iam::[[Account(B)のS3アカウントID]]:role/*"
]
}
]
}
Account(A) S3バケットポリシー変更
前述で軽く触れた通り、Lambda関数のコードはCodeBuildで一度Account(A)のS3にアップロードしています。CodeBuildがLambda関数を更新する際にはAccount(B)の権限を使用するため、Account(A)のS3にバケットにAccount(B)からのアクセスを許可するようバケットポリシーを設定する必要があります。
設定したバケットポリシーが以下の通りです。
{
"Version": "2012-10-17",
"Id": "Policy1553183091390",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[[Account(B)のアカウントID]]:root"
},
"Action": "s3:Get*",
"Resource": "arn:aws:s3:::[[Account(A)のS3Bucket名]]/*"
},
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[[Account(B)のアカウントID]]:root"
},
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::[[Account(A)のS3Bucket名]]"
}
]
}
CodeBuildではS3にアップロードされたLambda関数のコードを読み取るだけなので、get
とlist
のみ許可しています。
CodeBuildで実行するbuildspec.yml作成
諸々AWSリソース周りの設定が出来上がってきたので、次はCodeBuildで実行するbuildspec.ymlを作成していきます。
buildspec.yml実行時における権限変更の方法等は、以下ブログを参考に行っています。
buildspec.ymlで実行する内容は主に以下になります。
- AWS認証情報追加(AssumeRole)
- Lambda関数をzip化してS3にアップロード
- Lambda関数の更新
出来上がったbuildspec.ymlは以下の通りです。
version: 0.2
phases:
install:
runtime-versions:
python: 3.9
pre_build:
commands:
- aws sts get-caller-identity
- echo "[profile account-b]" > .awscli-config
- echo "role_arn = ${ASSUME_ROLE_ARN}" >> .awscli-config
- echo "credential_source =EcsContainer" >> .awscli-config
- export AWS_CONFIG_FILE=${CODEBUILD_SRC_DIR}/.awscli-config
- echo "AssumeRole Credential info"
- aws sts get-caller-identity --profile account-b
build:
commands:
- echo "Zipping deployment package"
- cd HelloWorld_Function/
- zip -r HelloWorld_Function.zip .
- aws s3 cp HelloWorld_Function.zip s3://[[Account(A)のS3バケット名]]/
post_build:
commands:
- echo "Update Lambda Function"
- aws lambda update-function-code --function-name HelloWorld_Function --s3-bucket [[Account(A)のS3バケット名]] --s3-key HelloWorld_Function.zip --profile account-b
途中、Account(B)のIAMロール権限を使用出来ているか確認するためaws sts get-caller-identity
を実行しています。
11行目の${ASSUME_ROLE_ARN}
ではCodeBuildの環境変数にAccount(B)のIAMロールARN値を設定しています。
また、CodeCommitにpushするリポジトリの中身は以下のような構造としています。
$ tree
.
├── HelloWorld_Function
│ └── lambda_function.py
└── buildspec.yml
Lambda関数自体はPython3.9のLambda関数を作成した際にデフォルトで作成されるHelloWorldのスクリプトを少し弄ってLambda関数が更新出来ているか確認をしていきます。
CodeBuildを実行してみる
今回はCodePipeline等は使用していないため、CodeCommitにpush後手動でCodeBuildを実行しています。ビルドログにAWS CLI実行ログが出力されれば成功となります。
[Container] 2022/05/11 09:37:54 Running command aws lambda update-function-code --function-name HelloWorld_Function --s3-bucket s3-074547258653 --s3-key HelloWorld_Function.zip --profile account-b
{
"FunctionName": "HelloWorld_Function",
"FunctionArn": "arn:aws:lambda:ap-northeast-1:xxxxx:function:HelloWorld_Function",
"Runtime": "python3.9",
"Role": "arn:aws:iam::xxxxx:role/service-role/HelloWorld_Function-role-0mdbc5ea",
"Handler": "lambda_function.lambda_handler",
"CodeSize": 330,
"Description": "",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "2022-05-11T09:37:54.000+0000",
"CodeSha256": "p6NH9l9B+Z+arGrKNPcayETwgbGRuIyHbBnQiH3g9Jc=",
"Version": "$LATEST",
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "29b4afd6-b2b8-4c7b-8b26-70b924739ba6",
"State": "Active",
"LastUpdateStatus": "InProgress",
"LastUpdateStatusReason": "The function is being created.",
"LastUpdateStatusReasonCode": "Creating",
"PackageType": "Zip",
"Architectures": [
"x86_64"
]
}
Account(B)のLambda関数を確認すると、コードだけ更新されている事が確認出来ると思います。
まとめ
今回はCodeBuildを使用して他アカウントのLambda関数更新をやってみました。Lambda関数のCICD方法は幾つか方法があります。今回の方法ではCFnよりも簡単に実装する事が出来たと思いますが、Lambda関数の細かい設定も一緒にデプロイしたい場合や、他のサーバレスリソースの設定も変更したい要件だとbuildspec.ymlに記述する内容が複雑になりそうなので、環境や管理の方法によって適材適所を見極めて使っていきたいですね。この記事が何方かのお役に立てば幸いです。