2
0

More than 3 years have passed since last update.

クロスアカウントでLambdaの自動デプロイをしてみた

Posted at

今回Lambdaの自動デプロイをクロスアカウントで実行したので覚書として残しておきます。
LambdaのデプロイにはCloudFormationで実施しています。
LambdaCloudFormatinoの設定については下記を参照してください。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html
完成図としては以下となります。
アーキ図.png

  • ソースコードは開発アカウントの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.yamlaws cliを利用してソースコードのパッケージ化とデプロイを行います。

buildspec.yaml
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関数を作成します。

index.js
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
index.js
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は以下のように設定しています。

buildspec.yaml
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を開き「変更をリリースする」を押下すれば検証環境と同様にデプロイが行われます。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0