今回Lambda
の自動デプロイをクロスアカウントで実行したので覚書として残しておきます。
Lambda
のデプロイにはCloudFormation
で実施しています。
Lambda
のCloudFormatino
の設定については下記を参照してください。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html
完成図としては以下となります。
- ソースコードは開発アカウントの
CodeCommit
で管理します。 - masterブランチにソースコードがPUSHされると、開発環境の
CodePipline
が動作します。 - 開発アカウントの
Codebuild
上で`CloudFormatino
を利用してLambda
のパッケージ化とデプロイが行われます。 - 開発アカウント上のS3に
Lambda
のソースコードが配置されたのをトリガーに検証・本番アカウントのS3にコピーを行います。 - 検証アカウントでは自アカウント内のS3にファイルがアップロードされたのを契機に
CodePipline
を実行してLambda
のデプロイを行います。 - 本番アカウントでは未テストでデプロイされるの防ぐため、手動で
CodePipeline
を実行します。
CodeCommit、CodePipelineの設定
クロスアカウントのCodeCommit
の連携については下記を参考にしました。
https://dev.classmethod.jp/cloud/aws/angular-spa-auto-deploy-across-account/
ほとんどこのままなので以降は各環境でのCodeCommit
の連携、CodePipline
のプロジェクトは作成できている前提で書いていきます。
開発アカウントでの設定
開発アカウントではCodeBuild
上のbuildspec.yaml
でaws cli
を利用してソースコードのパッケージ化とデプロイを行います。
version: 0.2
phases:
install:
runtime-versions:
docker: 18
commands:
- apt-get update
- apt-get install -y zip sed
build:
commands:
- aws cloudformation package --template-file [任意のパス]/[CloudFormatinoテンプレートファイル] --s3-bucket [S3バケット名] --s3-prefix [任意のプレフィックス] --output-template-file [任意のパス]/packaged-template.yml
- aws cloudformation deploy --template-file [任意のパス]/packaged-template.yml --stack-name [スタック名] --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM
上記で開発アカウントでのデプロイは完了です。
aws cloudformation package
でS3にLambda
のソースコードが配置されるので、それをトリガーに設定したLambda関数を作成します。
var AWS = require('aws-sdk');
var s3 = new AWS.S3();
exports.handler = async (event) => {
const copyParams = {
Bucket: [検証アカウントバケット名],
CopySource: event.Records[0].s3.bucket.name + '/' + event.Records[0].s3.object.key,
Key: event.Records[0].s3.object.key,
ACL: 'bucket-owner-full-control'
};
await s3.copyObject(copyParams, function(err, data) {
console.log('検証コピースタート');
if (err) {
console.log('失敗!');
console.log(err);
} else {
console.log('成功!');
}
}).promise();
const copyParamsForProd = {
Bucket: [本番アカウントバケット名],
CopySource: event.Records[0].s3.bucket.name + '/' + event.Records[0].s3.object.key,
Key: event.Records[0].s3.object.key,
ACL: 'bucket-owner-full-control'
};
await s3.copyObject(copyParamsForProd, function(err, data) {
console.log('本番コピースタート');
if (err) {
console.log('失敗!');
console.log(err);
} else {
console.log('成功!');
}
}).promise();
};
コピーしたファイルは、コピー先のアカウントで自由に扱えるようにbucket-owner-full-control
を設定しています。
以上で、開発アカウントでの設定は完了です。
検証アカウントでの設定
開発アカウントで作成したLambda関数が正しく動作していれば、検証アカウントのS3にファイルがアップロードされるので、それをトリガーに設定したLambda関数を作成します。
※開発環境のCodeCommit
と連携しているので、masterにPUSHされた時点でCodePiplineを実行することも可能ですが、なるべく開発環境と同一のファイルを利用してデプロイを行いたいので今回は明示的にその設定をOFFとしています。
PollForSourceChanges: false
var AWS = require('aws-sdk');
var s3 = new AWS.S3();
var codepipeline = new AWS.CodePipeline();
exports.handler = async (event) => {
// オブジェクトキーの取得
const key = event.Records[0].s3.object.key;
const arrKey = key.split('/');
// 要素の末尾であるファイル名を削除
arrKey.pop();
// pipelineのファンクション名となるように'-'区切りで文字列結合
const pipelineName = arrKey.join('-');
const pipelineParams = {
name: `${pipelineName}`
};
// codepipelineの実行フラグ。
var pipelineExecFlg = true;
// codepipelineの実行ステータスを取得。
const data = await codepipeline.getPipelineState(pipelineParams).promise();
console.log(data);
// 実行中であればcodepipelineの実行フラグをOFFにする。
if (data.stageStates[0].latestExecution.status == 'InProgress' || data.stageStates[1].latestExecution.status == 'InProgress') {
pipelineExecFlg = false;
}
// codepipeline実行
if (pipelineExecFlg) {
await codepipeline.startPipelineExecution(pipelineParams, function(err, data) {
if (err) console.log(err, err.stack);
else console.log(data);
}).promise();
}
};
今回はCodePipeline
のプロジェクト名がS3にアップロードされる際のプレフィックス名となるように合わせています。ここは実行できれば何でもいいです。
上記のLambdaが正しく動けば、CodePipeline
が実行されます。
codebuild
で実行する際のbuildspec.ymlは以下のように設定しています。
version: 0.2
phases:
install:
runtime-versions:
docker: 18
commands:
- apt-get update
- apt-get install -y zip sed
build:
commands:
############################## deploy lambda
## 開発アカウントからコピーされたオブジェクトが配置されているバケット名を取得する(本番環境でもこのファイルを使用するため、SSMパラメータから取得するように設定)
- BUCKET_NAME=`aws ssm get-parameters --names [SSMパラメータ名] | jq -r .Parameters[0].Value`
## 最新のオブジェクトを取得
- OBJECKT_KEY=`aws s3api list-objects --bucket $BUCKET_NAME --prefix [オブジェクト配置プレフィックス] | jq -r '.Contents | sort_by(.LastModified) | reverse | .[0] | .Key'`
## テンプレートファイル内で設定したソースコード配置先を上記2つで取得した値に置換する。
- sed -i -e "s@src/@`echo s3://$BUCKET_NAME/$OBJECKT_KEY`@" [CloudFormationテンプレートファイル]
## 本番環境との差異をなくすため、テンプレート内で使用するパラメータの値を取得する。
- CloudFormationParameter=`aws ssm get-parameters --names [SSMパラメータ名] | jq -r .Parameters[0].Value`
## デプロイ実行
- aws cloudformation deploy --template-file [CloudFormationテンプレートファイル] --stack-name [スタック名] --no-fail-on-empty-changeset --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM --parameter-overrides CloudFormationParameter=$CloudFormationParameter
- なるべく開発環境で設定したファイルと同一のファイルをしたいため、検証・本番アカウントでは
aws cloudformation package
は使用せずに、コピーされたファイルを利用してaws cloudformation deploy
のみを行います。 - 本番環境でも同一の
buildspec.yaml
を使用するため、CloudFormation
のパラメータやS3バケットはSSMパラメータ
から取得してくるように設定します。
本番アカウントでの設定
本番アカウントは基本的に検証アカウントと同一です。違いは以下の一点です。
本番アカウントでは手動でのテスト前に自動でデプロイされると困るので、CodePipeline
を実行するLambda
は作成しません。
検証環境でのテストも問題なければ、AWSコンソール上でCodePipline
を開き「変更をリリースする」を押下すれば検証環境と同様にデプロイが行われます。