[AWS] Serverless FrameworkでRESTfulAPIを作成する (外部サーバと通信編)

Lambdaから外部のサーバとHTTPによる通信を行ってみます。

とは言っても特別な制約はありませんので基本的に自由行うことができますが、実行時間によって課金される点に注意が必要です。相手方のサーバからレスポンスが中々返って来ないとその時間分だけ課金されます。またVPCの中にLambdaを置いて実行する場合はインターネットゲートウェイなどの設定が必要になります。

では行ってみましょう。

実際に試してみる

今回はNICTが公開しているAPIを利用し、日本標準時を取得するLambda関数を作成します。

準備

基本的な設定方法については過去記事を参照ください。 blog.katsubemakito.net

最初に「jst」という名前のプロジェクトを作成しました。

$ serverless 
Serverless: No project detected. Do you want to create a new one? Yes
Serverless: What do you want to make? AWS Node.js
Serverless: What do you want to call this project? jst

作成されたディレクトリに入ってnpm initしておきます。

$ cd jst
$ npm init

外部と通信を行うために「node-fetch」モジュールをインストールします。requestモジュールが非推奨になってしまったので今後は別の物を使った方が良さそうですね。

$ npm install node-fetch@2.x

node-fetchは3系からESmoduleに変更されました。これまで通りconst fetch = require('node-fetch')とCommonJSを利用したい場合は2系を入れてください。

サンプルコード

serverless.yml

プロジェクトの設定を行います。URL/(stage)/jstをGETでリクエストすると、jst関数を実行します。

service: jst
plugins:
  - serverless-offline
provider:
  name: aws
  runtime: nodejs12.x
  environment:
    TZ: Asia/Tokyo
functions:
  jst:
    handler: handler.jst
    events:
      - http:
          path: jst
          method: get

environmentはLambda実行時の環境変数を指定できる項目です。何となく分かったと思いますが、TZはTimeZoneの略称でここでは日本時間にするよう指定しています。この指定がないとUTCなどになってしまいますので時間を扱う場合はご注意を。

handler.js

実際のコードです。handler.jsの中に以下の関数をそのまま貼り付けます。

'use strict';
const fetch = require('node-fetch');

/**
 * GET /(stage)/jst
 */
module.exports.jst = async event => {
  const json = await getJST();    // 日本標準時を取得

  return {
    statusCode: 200,
    body: JSON.stringify(
      {
        format: getFormatDate(json),
        raw: json,
      },
      null,
      2
    ),
  };
};


/**
 * 日本標準時を取得する
 *
 * @return{object|undefined}
 */
async function getJST(){
  const url = 'https://ntp-b1.nict.go.jp/cgi-bin/json';
  const res = await fetch(url);

  if(res.ok){
    return( res.json() );
  }
  else{
    return( undefined );
  }
}


/**
 * YYYY-MM-DD hh:mm:ss形式で時刻を返却する
 *
 * @param {object} json
 * @return {string|boolean}
 */
function getFormatDate(json){
  if( json === undefined ){
    return(false);
  }

  const d = new Date(json.st * 1000);
  return(
    d.getFullYear() + "-" +
    ("00" + (d.getMonth() + 1)).slice(-2) + "-" +
    ("00" + d.getDate()).slice(-2)
    + " " +
    ("00" + d.getHours()).slice(-2) + ":" +
    ("00" + d.getMinutes()).slice(-2) + ":" +
    ("00" + d.getSeconds()).slice(-2)
  );
}

デプロイ

deployコマンドでAWSへ反映します。

$ serverless deploy

途中でURLが表示されるのでこれをメモします。

endpoints:
  GET - https://q88uum4ys8.execute-api.us-east-1.amazonaws.com/dev/jst

実行する

curlコマンドで挙動を確認します。日本時間が表示されたでしょうか?

$ curl 'https://q88uum4ys8.execute-api.us-east-1.amazonaws.com/dev/jst'
{
  "format": "2020-05-18 15:12:19",
  "raw": {
    "id": "ntp-b1.nict.go.jp",
    "it": 0,
    "st": 1589782339.101,
    "leap": 36,
    "next": 1483228800,
    "step": 1
  }
}

料金の計算を行う

外部サーバとの通信を行う際に怖いのは実行時間が伸びて課金額が跳ね上がるのではないかという点ですね。

計算式

料金の計算は東京リージョンでは以下の通り。Lambdaを起動しっぱなしにしてくれるProvisioned Concurrencyは今回は置いておきます。

  • 100万リクエストあたり 0.20USD
  • メモリ1GB * 実行時間1秒あたり 0.0000125615USD
    • 1ドル110円とすると0.001381765円

シミュレーション

仮に1分に1回リクエストされるとして、処理時間の違いにより月間でどの程度の開きが生まれるのか計算してみます。

  • リクエスト数の課金は省いています
  • Bの計算式は1分に1回実行されるとして 60回 * 24時間 * 30日 = 43,200
  • 料金の計算式は『((A * B * C) / 1000) * 単価』となります。1000をかけているのはミリ秒で掛け算したのを秒に戻すためです。

実行時間が10倍になると単純に料金も10倍になりますねw Lambdaは安いとはいえ限界の15分(900秒)を1分毎に回し続けると5.5万円/月となるようですw

実際の実行時間を確認する

AWSマネジメントコンソールにログインし「API Gateway」から先ほど作成したプロジェクトへ移動します。リージョン毎に表示されますのでお目当ての物が無い場合は切り替えてください。ServerlessFrameworkのデフォルトはバージニア北部です。

以下の画面までたどり着いたらメソッド(GET)をクリック、一番右側の四角(lambda)をクリックします。

Lambda関数の編集画面に遷移するので「モニタリング」をクリック。

グラフ「Duration」に実行時間のグラフが表示されます。この中の「BilledDurationInMS」が課金対象の実行時間(ミリ秒)になります。

その下の方にある表で使用メモリなどさらに詳しい情報を確認できます。

参考ページ