Lambda(Node.js)の開発、テスト、デプロイ

概要

Lambda(Node.js)関数を作成するには、手元でコードを書き、zipにまとめてコンソールでアップするとドキュメントにも書かれていますが、実際の開発のときにはもうちょっと手間のかからないデプロイのやり方が必要です。また、デプロイの前には手元で動作確認やテストも実行したくなります。

今回は、Dockerを使用して手元でLambda関数の開発、テスト、デプロイを行う方法を紹介します。

開発

開発環境構築

開発環境構築はDockerで行います。 Lambdaの実行環境はAWSドキュメントにありますが、この環境になるべく近い開発環境を立てたいので、ドキュメントを参考にDockerfileを作成します。 また、このDockerにデプロイツールのapex、テスト用ライブラリもインストールします。

docker-lambdaというLambda用のDockerイメージもありますが、今回は使用しません。docker-lambdaにはaws-cliなど今回使用しない機能がはいっているのと、作成するDockerfileがそう複雑ではないためです。
Serverless Frameworkというものもありますが、今回は同じく利用しません。リソース管理は別にCloudFormationで行いたいからです。

作成したDockerfileはこちらです。 これをdocker-compose.ymlを使って立ち上げます。

git clone https://github.com/ropupu/lambda-dev-test-deploy-sample.git
cd lambda-dev-test-deploy-sample/
docker-compose build
docker-compose up -d
docker ps

これで、開発用のlambdaコンテナが立ち上がります。

AWSリソース構築

AWSリソースの構築を行います。リソースはCloudFormationを使用して管理します。 今回は例として、図のようにLambdaとSNSを使い、Lambda->SNSにpublishするためのリソースを作成します。 f:id:ropupu-ropupu:20190120121728p:plain

これを構築するCloudFormationファイルはこちらです。

また、このCloudFormationを使ってリソースを構築すると、パラメーター(SNSトピックをサブスクライブするメールアドレス) の入力を求められます。その後リソース構築が完了すると、入力したメールアドレスに『AWS Notification - Subscription Confirmation』というタイトルで確認メールが届くので、本文中の確認リンクを押しておいてください。

コードを書く

.
├── cloudformation.yml
├── docker
│   └── Dockerfile
├── docker-compose.yml
└── lambda
    └── functions
       └── publishSNSMessage
           └── index.js

このようなディレクトリ構成を作り、lambda/functions/publishSNSMessage/index.jsにファイルを作成します。このindex.jsがLambda関数に対応します。コードの中身は以下のようになります。

const AWS = require('aws-sdk');
const sns = new AWS.SNS({apiVersion: '2010-03-31'});

exports.handler = async function(event, context) {
  const snsTopicArn = process.env['SNS_TOPIC_ARN'];
  const subject = "hello";
  const message = "hello from Lambda!";

  const params = {
    TopicArn: snsTopicArn,
    Subject: subject,
    Message: message
  };

  try {
    await sns.publish(params).promise();
  } catch (err) {
    console.log(err);
    throw err;
  }

  return "success!";
};

環境変数からSNSトピックのARNを取得し、そのトピックに『hello』というメッセージを送信する関数です。

初回デプロイ

apex設定

apexを利用してLambdaをデプロイする前に、いくつか設定を行う必要があります。 まず、docker-compose.ymlと同階層に.envファイルを作成します(.env.exampleファイルをコピーして使ってください)。.envファイルには、以下のパラメーターを入力します。

AWS_ACCESS_KEY_ID=AKIAXXXXXXXXX
AWS_SECRET_ACCESS_KEY=xxxxxx
AWS_REGION=ap-northeast-1

ここで、AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEYは、CloudFormationで作成したIAMユーザー『lambda-deploy-user』のアクセスキーIDとシークレットアクセスキーをAWSコンソールから取得して入力します。入力が終わったら、この設定を反映させるため、docker-composeコマンドを利用してdockerコンテナを再起動させてください。

次に、lambdaディレクトリ内にproject.jsonを作成します。これはapexによるデプロイを行う際の全体設定のためのファイルです。内容はこちらです。

最後に、index.jsと同階層にfunction.jsonを作成します(function.example.jsonファイルをコピーして使ってください)。これはapexによるデプロイを行う際の関数ごとの設定ファイルです。

{
    "description": "publish SNS topic",
    "handler": "index.handler",
    "role": "arn:aws:iam::xxxxxx",
    "environment": {
        "SNS_TOPIC_ARN": "arn:aws:sns:xxxxxx"
    }
}

この『role』と『SNS_TOPIC_ARN』にあたるものは、それぞれCloudFormationで作成したIAMロールとSNSトピックのARNです。コンソールなどで確認し、入力してください。

これらの設定が終わると、プロジェクトのディレクトリ全体はこのようになっています。

├── .env
├── cloudformation.yml
├── docker
│   └── Dockerfile
├── docker-compose.yml
└── lambda
    ├── functions
    │   └── publishSNSMessage
    │       ├── function.json
    │       └── index.js
    └── project.json

デプロイ

docker execを使ってdockerコンテナの中でapexコマンドを実行します。

$ docker exec -it lambda bash
bash-4.2# apex deploy
   • updating config           env= function=publishSNSMessage
   • updating function         env= function=publishSNSMessage
   • created alias current     env= function=publishSNSMessage version=1
   • function updated          env= function=publishSNSMessage name=publishSNSMessage version=1

これでLambda関数がデプロイできました。デプロイした後、apexで関数の実行もできます。

bash-4.2# apex invoke publishSNSMessage
"success!"

"success!"と表示された後、CloudFormationでのリソース構築時に入力したメールアドレス宛メールをチェックしてください。Lambdaから送信されたメールが届いているはずです。

f:id:ropupu-ropupu:20190120155712p:plain

テスト

一旦Lambdaのコード作成、デプロイまでが完了しましたが、コードのテストを行っていません。 ここではテストフレームワークmochaアサーションツールとしてchai、ライブラリのスタブ化を行ってくれるproxyquireを利用してテストを行います。

テスト作成

まず、index.jsと同階層にpackage.jsonを作成します。

{
  "name": "publishsnsmessage",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "devDependencies": {
    "aws-sdk": "^2.290.0"
  }
}

そして、Dockerコンテナに入り、package.jsonをもとにnpm installを行います。

$ docker exec -it lambda bash
bash-4.2# cd functions/publishSNSMessage
bash-4.2# npm install

これは、proxyquireによるライブラリのスタブ化を行う場合、そのライブラリがインストールされている必要があるためです。

次に、index.jsと同階層にtestディレクトリを作成します。その中にtest.jsファイルを作成し、ここにテストコードを書いていきます。 また、test.jsと同じ階層にtest.envファイルも作成します。今回テスト対象であるindex.jsはprocess.envを使用しているため、テスト時に環境変数のセットを行うためです。 test.jsの内容はこちら、test.envの内容はこちらです。

すべての設定が終わると、プロジェクトのディレクトリ全体はこのようになっています。

├── .env
├── cloudformation.yml
├── docker
│   └── Dockerfile
├── docker-compose.yml
└── lambda
    ├── functions
    │   └── publishSNSMessage
    │       ├── function.json
    │       └── index.js
    │       ├── node_modules
    │       ├── package-lock.json
    │       ├── package.json
    │       └── test
    │           ├── test.env
    │           └── test.js
    └── project.json

テスト実行

Dockerコンテナに入り、テスト対象関数(今回はpublishSNSMessage)ディレクトリ内に移動します。そして、mochaコマンドでテストを実行します。

$ docker exec -it lambda bash
bash-4.2# cd functions/publishSNSMessage
bash-4.2# mocha


  publishSNSMessage
    ✓ should return success! when sns publishing succeeds (3526ms)
Error: aws sdk error
    at Object.promise (/var/lambda/functions/publishSNSMessage/test/test.js:25:17)
    at Object.exports.handler (/var/lambda/functions/publishSNSMessage/index.js:16:31)
    at Context.it (/var/lambda/functions/publishSNSMessage/test/test.js:49:25)
    at callFn (/opt/node-v8.10.0-linux-x64/lib/node_modules/mocha/lib/runnable.js:372:21)
    at Test.Runnable.run (/opt/node-v8.10.0-linux-x64/lib/node_modules/mocha/lib/runnable.js:364:7)
    at Runner.runTest (/opt/node-v8.10.0-linux-x64/lib/node_modules/mocha/lib/runner.js:455:10)
    at /opt/node-v8.10.0-linux-x64/lib/node_modules/mocha/lib/runner.js:573:12
    at next (/opt/node-v8.10.0-linux-x64/lib/node_modules/mocha/lib/runner.js:369:14)
    at /opt/node-v8.10.0-linux-x64/lib/node_modules/mocha/lib/runner.js:379:7
    at next (/opt/node-v8.10.0-linux-x64/lib/node_modules/mocha/lib/runner.js:303:14)
    at Immediate._onImmediate (/opt/node-v8.10.0-linux-x64/lib/node_modules/mocha/lib/runner.js:347:5)
    at runCallback (timers.js:794:20)
    at tryOnImmediate (timers.js:752:5)
    at processImmediate [as _immediateCallback] (timers.js:729:5)
    ✓ should throw error when sns publishing fails


  2 passing (4s)

これで、手元でLambda関数の開発、テスト、デプロイを行えるようになりました。