見出し画像

S3へのレプリケーションをトリガーにLambdaのコード更新・新規バージョン発行を実行する

はじめに

こんにちは、SHIFT の開発部門に所属しているKatayamaです。今期から転属になり、開発を担当していくことになりました。 ただ、前期はDevOps導入支援等に携わっていた関係で今回はCICD関係の記事を書こうと思います。

今回の記事の内容ですが、バックエンドの Build 成果物(jar)が Deploy 環境にレプリケートされた事をトリガーに、Lambda のコード更新・新規バージョン発行を行う CD の構築を行ったので、それに関する Tips のようなものになります。

(前々回は Cloud Front のキャッシュ削除についての記事、前回は ECS のサービス更新についての記事、でしたが、今回でレプリケーション後の Deploy シリーズ 3 つ目となる Lambda のコード更新・新規バージョン発行についての記事です。)

※今回の記事もの発端も、マルチベンダーなどの場合に開発資材のやり取り(コピー・複製=レプリケーション)が必要になるという事でその要望から始まりました。

構成イメージ

何らかのトリガーで Code Build が Start すると、S3 に Build 成果物である jar が push され、それをトリガーにレプリケートが走る。レプリケーション先の環境でレプリケート後に自動で Deploy(Lambda のコード更新・新規バージョン発行)を実行させる。

図で示すと以下のようになる。

Lambda のコード更新・新規バージョン発行

今回の Deploy は、S3 へのレプリケーションで jar が PUT されたら、Lambda のソースコード更新・新規バージョン発行を行うというもの。

Deploy 自体は、Cloud Front のキャッシュ削除の時と同様に、レプリケーションで S3 の Bucket に新規でオブジェクトが作成されるのでそれをトリガーに Lambda 関数を実行し、以下の AWS CLI コマンドで行っている事と同じ事を AWS SDK(今回は JavaScript)で実行させる、という感じで行う。

aws lambda update-function-code --function {function名} --s3-bucket {jarのあるS3 Bucket名} --s3-key {jarのファイル名}
aws lambda publish-version --function-name {function名}

※{jarのファイル名} について、Bucket の中でフォルダ階層を作っているならルートディレクトリからのパスで書く必要がある
 ex) --s3-key artifact/lambda/hoge.jar

・参考:aws.lambda.update-function-code
・参考:aws.lambda.publish-version

IAM

S3 へのレプリケーションをトリガーに Cloud Front のキャッシュ削除を実行する#IAM と全く同じになるのでそれを参照。

Lambda 関数のトリガーを設定する

S3 へのレプリケーションをトリガーに Cloud Front のキャッシュ削除を実行する#Lambda 関数のトリガーを設定する と全く同じになるのでそれを参照。

Lambda 関数を実装する

ここも基本的には、S3 へのレプリケーションをトリガーに Cloud Front のキャッシュ削除を実行する#Lambda 関数を実装する とほぼ同じだが、Event オブジェクトの中身から Deploy(Lambda UpdateFunctionCode, PublishVersion)を行うのに必要な情報を抜き出す部分が少し異なってくる。

Lambda に渡ってくる Event オブジェクトの中身

S3 へのレプリケーションをトリガーに Cloud Front のキャッシュ削除を実行する#Lambda に渡ってくる Event オブジェクトの中身 と全く同じになるのでそれを参照。

Lambda 関数(index.handler)の実装

今回は Node.js の Lambda 関数で Deploy を実行させるので、実装としては以下のようした。

const {
  LambdaClient,
  UpdateFunctionCodeCommand,
  PublishVersionCommand,
} = require("@aws-sdk/client-lambda");
const client = new LambdaClient({ region: process.env.REGION });

exports.handler = async (event) => {
  try {
    const bucket = event.Records[0].s3.bucket.name;
    const object = event.Records[0].s3.object.key;

    if (!object.includes(process.env.FILE_NAME)) {
      console.log("not applicable. object is ", object);
      return "";
    }

    const updateInput = {
      FunctionName: process.env.FUNCTION_NAME,
      S3Bucket: bucket,
      S3Key: object,
    };

    const updateCommand = new UpdateFunctionCodeCommand(updateInput);
    const updateResponse = await client.send(updateCommand);

    console.log(
      "UpdateFunctionCodeCommand status",
      updateResponse.$metadata.httpStatusCode
    );
    console.log("UpdateFunctionCodeCommand state", updateResponse.State);

    if (process.env.PUBLISH) {
      const publishInput = {
        FunctionName: process.env.FUNCTION_NAME,
      };

      const publishCommand = new PublishVersionCommand(publishInput);
      const publishResponse = await client.send(publishCommand);

      console.log(
        "PublishVersionCommand status",
        publishResponse.$metadata.httpStatusCode
      );
      console.log("PublishVersionCommand state", publishResponse.State);

      return "";
    } else {
      return "";
    }
  } catch (error) {
    return errorHandler(error);
  }
};

const errorHandler = (error) => {
  const obj = {};
  obj["status"] = 500;
  obj["message"] = error.message;
  obj["stack"] = error.stack;
  obj["result"] = "ng";
  if (error.$metadata) {
    obj["status"] = error.$metadata.httpStatusCode;
  }

  console.log("errorHandler", obj);
  return "";
};

※実装について何点か補足すると、

・if (process.env.PUBLISH)
  PubishVersion を行わないパターンも想定し、環境変数に PUBLISH(値は何でもいい)がある時のみ PublishVersion を行うようにしている

・console.log()
 今回は Cloud Watch Events(EventBridge)が Lambda 関数のトリガーであり、return された結果を人が見る事ができないので、何が起きているか分かるように敢えて console.log()で log に出力させるようにした

・return "";
 これも今回 S3 がトリガーで return された結果を人が見れないので""(空文字)を返すようにした(何か JSON を返しても意味ないと判断)。

・参考:Lambda Client - AWS SDK for JavaScript v3
・参考:Class UpdateFunctionCodeCommand
・参考:Class PublishVersionCommand

Lambda 関数の実行ロールの設定

Lambda 関数が他の AWS サービスに対して何らかの操作を行うには、実行ロールでその権限を設定する必要がある。今回は Lambda の UpdateFunctionCode・PublishVersion が実行できればいいので以下のようなインラインポリシーを実行ロールに追加でアタッチするれば OK。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisualEditor0",
      "Effect": "Allow",
      "Action": ["lambda:UpdateFunctionCode", "lambda:PublishVersion"],
      "Resource": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:{lambda関数名}"
    }
  ]
}

まとめとして

前々回は Cloud Front のキャッシュ削除について、前回は ECS のサービス更新について、レプリケーション後の Deploy について過去 2 回で扱ったが、Lambda についても同じような考え方でレプリケーション後にそれをトリガーにして Deploy を実行させる事ができた。

《マガジン》 AWS DevOpsソリューション

__________________________________

執筆者プロフィール:Katayama Yuta
SaaS ERPパッケージベンダーにて開発を2年経験。 SHIFTでは、GUIテストの自動化やUnitテストの実装などテスト関係の案件に従事したり、DevOpsの一環でCICD導入支援をする案件にも従事。 最近開発部門へ移動し、再び開発エンジニアに。座学で読み物を読むより、色々手を動かして試したり学んだりするのが好きなタイプ。

お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/