
AWS Lambda@EdgeでスケーラブルなIoTバックエンド構築
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
ども、大瀧です。
先週の勉強会で発表した以下のスライドでは、AWSでIoTシステムのバックエンドを構築するときの基本となる部分を解説しました。
デバイス/ゲートウェイからのアクセスを受け付けるサービス(上記記事のスライドにある「ディスパッチサービス」)としてはAWS IoT、API GatewayやAmazon Kinesisが鉄板です。今回はIoTバックエンドのちょっと変わったディスパッチサービスとして、Lambda@Edgeを利用する構成をご紹介します。
AWS Lambda@Edgeとは
Lambda@Edgeは、AWSのCDN(Contents Delivery Network)サービスCloudFrontでAWS Lambdaを実行するサービスです。無印LambdaがAWSの"リージョン"と呼ばれるデータセンターで実行されるのに比べ、Lambda@EdgeはCloudFrontを実行するクライアントに近いデータセンターが利用されるため、よりネットワーク遅延が短くまた分散されたコンピュータリソースで効率的な処理が可能です。
Lambda@Edgeは現在プレビュー段階のため、利用には事前に申請が必要です。また、正式リリース時にサービス仕様が変更される可能性があることをご承知おきください。
Lambda@Edgeの詳細については、弊社中山の以下の記事が詳しいです。
- 【速報】AWS Lambda@Edgeが発表されました! #reinvent | Developers.IO
- ドキュメントを通してre:Invent 2016キーノートで発表されたLambda@Edgeを理解する #reinvent | Developers.IO
- ブループリントからLambda@Edgeの機能を理解する #reinvent | Developers.IO
- re:Growth 2016でLambda@Edgeについて発表してきました #reinvent | Developers.IO
Lambda@Edgeでデータのディスパッチ処理を実装する
さて、Lambda@Edgeにはコードからのネットワーク通信不可という制約があるため、DBサーバにデータを投入する処理は実行することができません。そこで今回は、最近サポートされたログ出力機能を使ってデバイスから送信されるデータをCloudWatch Logsに送ることで実装してみます。以下のような構成です。
ついでに、同時にサポートされたカスタムレスポンス機能も使ってオリジンにリクエストを転送することなく、クライアント-Lambda@Edge間で通信を完結させる構成にもしてみました。コードに記述すると以下になります。
'use strict';
exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    if(request.method == 'PUT') {
        const response = {
          status: '204',
          statusDescription: '204 No Content',
          httpVersion: request.httpVersion,
        };
        console.log(request.headers['X-Takipone-Content']);
        callback(null, response);
    }
    callback(null, request);
};
- 6行目 : リクエストメソッドがPUTの場合
- 7-11行目 : カスタムレスポンスのオブジェクト生成。今回は204 No Contentとする
- 12行目 : CloudWatch Logsへのログ出力。コンソール出力と同じです。HTTPリクエストヘッダX-Takipone-Contentの値を出力
- 13行目 : callback呼び出し。第2引数にresponseオブジェクトを渡すことでカスタムレスポンスを返却する
非常にシンプルですね。ポイントは12行目のところで、Lambda@Edgeでは現状リクエストボディを取得する手段が無いため、データは任意のリクエストヘッダに詰め込む必要があります。ヘッダ名に制限があることに注意しましょう。
この状態だと任意のリクエストを受け付けてしまうため、以下の簡易的な認証を含めても良いでしょう。上記コードの5行目に以下のコードを挿入します。
:
    if (request.headers['Authorization']) {
        let authorized = false;
        const authorities = request.headers['Authorization'];
        for (let i = 0; i < authorities.length; i++) {
            if (authorities[i] === 'secret') {
                authorized = true;
            }
        }
        if (!authorized) {
            callback(null, {status: '403', statusDescription: '403 Forbidden', httpVersion: request.httpVersion});
        }
    } else {
        callback(null, {status: '403', statusDescription: '403 Forbidden', httpVersion: request.httpVersion});
    }
:
このコードでは、Authorizationヘッダにsecretという値が含まれるかを検証、ヘッダが無いもしくは値が合わない場合に403 Forbiddenを返すようにしました。
では、Lambda関数を作成し、CloudFrontと連携させます。CloudFrontのDistributionはあらかじめ作成しておきましょう(S3オリジンのデフォルト設定でOKです)。
Lambdaの管理画面から[Lambda関数の作成]をクリックすると関数作成のウィザードが表示されます。
設計図の選択では、「cloudfront-modify-response-header」(Lambda@Edgeのひな形)をクリックします。
トリガーの設定では、あらかじめ作成したCloudFront Distirbutionを選択し、CloudFrontイベントでは「ビューアーリクエスト」を選択、[次へ]をクリックします。
関数の設定では、任意の関数名を入力し、[Lambda関数のコード]に前述のコードを貼り付けます。今回は認証無しにしてみました。
ロールはCloudWatch Logsへのログ出力の権限を持つ既存のロールを選択すればOKですが、Lambda@Edge APIと信頼関係を設定する必要があるので後述します。残りの項目はデフォルトのままにし、関数を作成しましょう。CloudFront Distributionのステータスが「In progress」に変化するので、「Deployed」になれば設定完了です。
Lambdaに付与するIAMロールについては、IAM管理画面からロールを選択し、[信頼関係] - [信頼関係の編集]から、以下のようなLambda@Edge(edgelambda.amazonaws.com)を追加します。
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    },
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "edgelambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
これでOKです。
動作検証
では動かしてみましょう。curlでCloudFrontのエンドポイント宛てにX-Takipone-Contentヘッダと値を含むリクエストを送出します。
$ curl -X PUT --header 'X-Takipone-Content:{"temperature":"25","humidity":"55"}' http://XXXXXXXXXXXX.cloudfront.net
$
少し待ち、CloudWatch Logsの画面を確認してみると...
データが保存できていますね!CloudWatch LogsのデータはAmazon S3にエクスポートすることができるので、データの可視化や分析を行う場合はS3へのエクスポートと組み合わせると良いでしょう。
注意点
Lambda@Edgeからのログ出力は、最大4KBのサイズ制限があるため、大きなデータの送信には向いていません。バイナリデータについても、BASE64などテキスト形式エンコードする必要があると思います(未検証)。
まとめ
IoTバックエンドの構成例として、Lambda@Edgeによるデータの受け取りとCloudWatch Logsへの保存方法をご紹介しました。
現在はプレビューですので、パフォーマンスについては正式リリースを待ってから負荷試験などで検証するのが良いでしょう。
おまけ: CloudFrontの他の機能やSORACOMとの組み合わせ
CloudFrontの他の機能として、HTTP/2サポートやIPv6での通信と組み合わせることが可能です。
また、CloudFrontのエンドポイントに接続できる任意のデバイスからデータが送信できるので、ソラコムのSORACOM Beamと組み合わせ、デバイス/ゲートウェイからは3G/LTEモバイル閉域網のBeamエントリーポイントまでHTTPでデータを送出、BeamからCloudFrontにはHTTPSで暗号化した形でセキュアに送信するという構成も可能です。


















