バイナリデータ(画像)をAPIGatewayとLambdaを組み合わせてS3にPUTする

はじめに

以前、SORACOM BeamでバイナリデータをAPIGateway経由でS3にアップロードするという記事を書いたのですが、今回はその記事の続編です。

別のアドカレでもLambdaの素晴らしさを少し語らせていただいたのですが(これ)、Lambdaはかなり便利だと思ってます!。私が今携わっている案件でも、常に十数台のLambdaが立ち上がって処理をしてくれています。

ある日の私

実際にその構成を使って画像を保存していたのですが、ある日
私「だいぶ溜まってきたしCLIでローカルにダウンロードしよっと」

aws s3 cp s3://bucketName/key ~/image

S3「Access deniedやで」
私「ファッ!?」
という一連の流れがあって、もしかしてこの構成間違ってる?ということに気がつきました。

なにがあった?

それまでの構成では、画像ファイルをバイナリのまま、APIGatewayにSORACOMBeamを通してRESTAPIでPOSTして、そのあとS3にPUTするようにしてました。
しかしそれでは、S3にPUTする際に必要なHeader情報が不足しているようでした。(それならそれでエラー吐いて欲しいものですが…)
果たして不足分のHeader情報を全て網羅できるのだろうか。いやできない。(反語)
※必要なHeader情報は公式ドキュメントを参照。

それじゃあどうする?

「便利なものは 使ってしまえ ホトトギス。」(字余り)

ということで、AWS-SDKでPUTすることにしました。SDKを利用すればHeader情報などをよしなに補ってくれますし、おすし。
そして、便利屋Lambdaさんの登場です。
APIGatewayから一度Lambdaにデータを渡して、LambdaからAWS-SDKを使ってPUTする。という流れです。

以前までの構成は前編を参照してください。

APIGatewayからLambdaを呼び出す

スクリーンショット 2017-12-15 9.36.33.png

設定中に「APIGatewayにLambdaをinvokeする権限渡すけどええか?」と聞かれるのでOKしてあげてください。

マッピングテンプレートの設定

Content-TypeごとにどのようなテンプレートでLambdaに渡すかを設定します。
「Method Request passthrough」を選択しておけば問題ないです。

スクリーンショット 2017-12-15 9.41.37.png

バイナリサポートの設定

バイナリデータを送信するときのContent-Typeを設定しておきます

スクリーンショット 2017-12-15 9.44.40.png

Lambdaの作成

ランタイムはpython2.7です。
先ほどのマッピングテンプレートの形でeventに渡されてきます。

import json
import boto3
import base64

def lambda_handler(event, context):
    print json.dumps(event)

    try:
        s3 = boto3.resource('s3')
        bucket = s3.Bucket('bucketName')

        # バイナリがBase64にエンコードされているので、ここでデコード
        imageBody = base64.b64decode(event['body-json'])

        # リクエストパスを組み合わせてKeyにする
        imagePath = event['params']['path']
        key = imagePath['key'] + '/' + imagePath['name']
        print key

        bucket.put_object(
            Body = imageBody,
            Key = key
        )
        print 'success!'

    except Exception as e:
        print 'Error!'
        print e

これでLambda経由でS3に画像が保存されます。
SDKを利用してるので、Header情報やmetaデータはよしなにしてくれてます。

さいごに

やっぱりLambdaは便利ですね。

ではまた!

続きを読む

SORACOM Beam(MQTT → MQTTS変換サービス) で Amazon MQ に接続する

Amazon MQにmosquitto(MQTT)とMQTT over Websocketで接続の続編です

切なる願い

Amazon MQ は 生MQTT はサポートしてないお…
でも TLSがしゃべれない非力なデバイスからも Amazon MQ 使いたいお!!

ビームを飛ばせばいいじゃない

3G/LTE通信が1日10円~ 1回線から契約できる、モノ向け通信サービスのSORACOM (CM色強い) には SORACOM Beam というデータ転送サービスがありまして、それを使って Amazon MQ に接続出来たよって話です

このデータ転送時にプロトコル変換もやってくれるのですが、変換内容に 生のMQTT → MQTTS も入ってます
要するに MQTT Proxy そんなところです (CM色強い)

手順

SORACOM の Webコンソールで “SIMグループ” を作成したら、そのSIMグループの中にある SORACOM Beam で MQTTエントリポイント を選択します

soracom-beam1.png

あとは以下のように設定していくだけです

  • プロトコル: MQTTS
  • ホスト名: Amazon MQ のダッシュボードから得てください
  • ポート番号: 8883 (ねんのため、Amazon MQ のダッシュボードで確認してください)
  • ユーザ名: Amazon MQ のダッシュボードから得てください
  • パスワード: Amazon MQ のダッシュボードから得てください

これでOKです
オプションの IMSI付与 は、ON にすると 例えば my_topic/sensor 宛てに送ると、Amazon MQ では my_topic/sensor/491023123131 と、送信元 SIM の IMSI が付与されますので、送信元を特定することがとても簡単になるのでオヌヌメです

soracom-beam2.png

確認

確認には前回同様 mosquitto と HiveMQ の MQTT over Websocketを使ってみます

ターミナル側(mosquitto_sub)の方が SORACOM Beam を使っている様子です

soracom-beam-works-with-amazon-mq.png

注目ポイント

前回mosquitto_sub の引数に --capath /etc/ssl/certs/ を入れてTLS通信にしていましたが、生MQTTで送るのでコマンドラインも減っています

SORACOM Beam を使うとこんなにメリットが;

  • そもそもTLSが使えない、非力なデバイスでMQTTやりたい
  • 通信データサイズの削減 (TLSと生の違い)
  • デバイス上の証明書更新をしなくてよい
  • Amazon MQ 側の設定変更(インスタンス作り直しに伴う endpoint の変更や、ユーザ/パスワードの変更)が発生しても、デバイス上のプログラムコード変更をしなくてよい

もっと言ってしまうと TLS実装しなくていいんです 面倒から解放されますよ (^^

これが 0.0009円/1リクエスト (in/outでそれぞれ1リクエストが発生するので、感覚的な”1回”だと 0.0018円) で使えるのだから、お得と言わざるを得ない (CM色強い)

あとがき

  • Qiitaのデザインが変わってて驚いた
  • Amazon MQ って Simple Icon マダー?
  • CM色強いですが、費用対効果は抜群なサービスです

現場からは以上です。

続きを読む

Sigfox Shield for ArduinoのデータをAWSに送信する

Sigfox Shield for ArduinoのデータをAWSに送信してみました。
スイッチサイエンス社では在庫が無かったので、SORACOMさんから購入しました。
様々なセンサーがこのシールドには搭載されています。
単体でネットワークに繋がるので、直ぐにIoTを試すことが出来ます。

IMG_0364.png

搭載センサー

  • 加速度センサ(MMA8451Q)
  • 温湿度・気圧センサ(BME280)

必要なもの

準備

SORACOM社から購入しているので、購入後セットアップは下記を参照してください。
https://dev.soracom.io/jp/start/sigfox_hw_unashield-v2s/#_ga=2.210765397.1366760851.1509329125-812912311.1499217908

DynamoDBテーブル作成

まずデータを保存するためのテーブルをAWSのDynamoDBに作成します。

s100.png

s101.png

項目
テーブル名 SigfoxDemo
プライマリキー timestamp(数値型)

s102.png

Lambdaの作成

続いてLambdaの作成を行います。関数の作成をクリックしてください。
s103.png

一から作成をクリック
s104.png

好きなロール名を入力してください。
s105.png

IAMのインラインポリシーを下記のように設定しました。
Resourceのテーブル名はIDは各々の設定を入力してください。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "dynamodb:PutItem"
            ],
            "Resource": "arn:aws:dynamodb:ap-northeast-1:XXXXXXXXXXX:table/SigfoxDemo",
            "Effect": "Allow"
        }
    ]
}

関数コードを入力

関数が出来たら、関数コードを入力部分をクリックしてコードを追加しましょう

s118.png

// initialize SDK and document client
var AWS = require("aws-sdk");
var docClient = new AWS.DynamoDB.DocumentClient();

exports.handler = function(event, context) {

  // SORACOMから送られてくるタイムスタンプ取得
  event.item.timestamp = event.timestamp;

  // build API parameter
  var params = {
    Item: event.item,
    TableName: 'SigfoxDemo'    /*対象のテーブル名*/
  };

  // call PutItem operation
  docClient.put(params, function(err, data){
    if (err)
      context.fail(err);
    else
      context.succeed({Item: event.item, Result:'success'});
  });
};

API Gatewayの設定

APIの作成をクリックしてください。

s107.png

好きなAPI名を入力してください
s108.png

メソッドの作成をクリックしてください
s109.png

POSTを選択しましょう
s110.png

POSTのセットアップ

s111.png

OKをクリックしましょう
s112.png

メソッドリクエストをクリック
s113.png

ヘッダーの追加をクリックして、SORACOMから送られてくる「X-SORACOM-TIMESTAMP」を設定します。
s114.png

APIキーの必要性をtrueにする
s115.png

総合リクエストをクリック
s116.png

本文マッピングテンプレートにあるマッピングテンプレートの追加をクリックして
Content-Typeにapplication/jsonを入力
s117.png

送られてくるデータをこれで取得する

{
    "timestamp":$input.params('X-SORACOM-TIMESTAMP'),
    "item":$input.json('$')
}

テスト

これでうまくDynamoDBに書き込まれるかテストを行ってみます。

s119.png

s120.png

これでDynamoDBに書き込まれたはずです。
ここで書き込まれなかった場合はロールの設定を見直してみましょう。DBアクセス権限が無い場合があります。

s121.png

デプロイする

作成したAPIをデプロイしましょう

s122.png

ステージ名を「prod」にしてデプロイボタンをクリック
s123.png

APIキー作成

APIキーを作成しましょう。

s124.png

好きなAPI名を入力して保存ボタンをクリック
s125.png

「使用量プランに追加」をクリック
s126.png

作成ボタンをクリック
s127.png

2つのチェックを外してください
s128.png

対象のAPIをステージ名を選択します
s129.png

最終的にこうなっていればOK
s130.png

AWSとSORACOM Beamを紐付ける

ここからは、SORACOMさんの管理画面から操作していき、作成したAWSと紐付けていきます。
管理画面のSigfoxグループをクリックしてください。

s131.png

適当にグループ名を入力しましょう
s132.png

SORACOM Air for Sigfox 設定

シールドから送られてくるバイナリデータをパースする為の設定です。
s133.png

SORACOM Beam 設定

HTTPSのエントリポイントを設定します
s134.png

s135.png

事前共有鍵の設定

s136.png

カスタムヘッダを追加

s137.png

s138.png

最終的には下記のような感じです
s139.png

Sigfoxデバイス管理

s140.png

s141.png

s142.png

実行する

ここまで完了したら、Sigfox Shield for Arduinoに電源を繋いで起動してみましょう
するとDynamoDBにセンサー値が書き込まれるかと思います。

s143.png

まとめ

比較的簡単にAWSとの接続が出来ました。しかも1年間は通信料が無料という特典付きです!
USBの電池ボックスとケースを作成すればサーバーの温度を測れる簡易センサーが作れると思います。
是非お試しあれ!

続きを読む

SORACOM Funnelを利用してKinesis FirehoseにJSONを送信してS3にPUTする(ついでにDynamoDBに書き込む)

はじめに

今頃Kinesis Firehoseとか今更感がありますがちゃんと使ってみたかったってのと、FunnelからFirehoseにJSONを投げたいという要望が舞い込んできたので、あんまりニーズはないと思いますが記事にしてみた

処理フロー

※点線部分はおまけ

funnel_firehose.PNG

Create S3

FirehoseがPUTするためのS3を作成しておきます。(作成する方法は省略します)

Create Kinesis Firehose

コンソールからFirehoseの画面へ移動して「Create delivery stream」からFirehoseを作成します
まだ東京リージョンで利用することはできないので、オレゴンリージョンで作成しました

  1. 「Delivery stream name」に任意の文字列を入力し、そのまま「next」をクリック
  2. FirehoseからLambdaをキックすることはしないのでそのまま「next」をクリック
  3. 「Destination」でS3を選択し(おそらくデフォルトで選択されています)、PUTするバケットを選択
  4. 「S3 buffer conditions」の「Buffer interval」を最小の「60」に設定。これで60秒待ってからS3にPUTします
  5. その他のデータを圧縮するか、暗号化するかなどのを任意に設定して、IAMroleを作成します
  6. 新しいIAMroleを作成すると今までの設定で必要な権限がついているIAMroleが作成されます
  7. 「Status」が「ACTIVE」になれば作成完了です

これでFirehoseにPUTRecordされたデータはS3にPUTObjectされます

※AWS-CLIでテストを行う

aws firehose put-record --delivery-stream-name deliveryStreamName --record Data="test"

SORACOM Funnelの設定

新しくSIMグループを作成し、Funnelの設定を行っていきます

  1. 「転送先サービス」から「Amazon Kinesis Firehose」を選択
  2. 「転送先URL」に「https://firehose.[region].amazonaws.com/[deliveryStreamName] 」を入力
  3. 「認証情報」には、Kinesis Firehoseの実行権限があるKey情報を持ったものを作成し、適用します
  4. 「送信データ形式」には「JSON」を選択します

これで、「http://funnel.soracom.io」にリクエストを送るとFirehoseにデータが送信されます

さぁテストの時間だ

先ほど設定したSIMグループのSIMが刺さったデバイスからPOSTメソッドを送信します

 curl -X POST --data @test.json -H "Content-Type: application/json" http://funnel.soracom.io

送信後S3にデータが保存されていれば成功です

おまけ

S3にPUTが行われたときにLambdaをキックしてDynamoDBにデータを保存する
おおまかなフロー
1. Funnel経由でFirehoseにJSONを送信
2. FirehoseがS3にPutRecord
3. S3がLambdaをキック
4. LambdaがS3にPutされたJSONを取得しDynamoDBにPut

Lambda

LambdaのロールにはDynamoDBへの権限を持たせておきます

index.js
const AWS = require("aws-sdk");
const co = require("co");

const dynamodb = new AWS.DynamoDB.DocumentClient({
  region: "us-west-2"
});
const s3 = new AWS.S3();

const dynamoPutData = require("./lib/dynamo_put_data");
const s3GetObject = require("./lib/s3_get_object");


exports.handler = (event, context, callback) => {
  console.log(JSON.stringify(event));
  co(function *() {
    // S3のBucketNameとPUTされたJSONのKey情報を取得
    const bucketName = event["Records"][0]["s3"]["bucket"]["name"];
    const objectKey = event["Records"][0]["s3"]["object"]["key"];
    // S3からJSONファイルを取得
    const s3GetData = yield s3GetObject.getObject(s3, bucketName, objectKey);
    const item = {
      id: "test",
      payloads: s3GetData
    };
    return yield dynamoPutData.putDynamoDB(dynamodb, item);
  }).then(() => {
    console.log("success");
  }).catch((err) => {
    console.log(err);
  });

};
s3_get_object.js
class s3GetObject {

  /**
   * S3からObjectを取得する
   * @param s3
   * @param bucket
   * @param key
   * @returns {Promise}
   */
  static getObject(s3, bucket, key) {
    return new Promise((resolve, reject) => {
      const params = {
        Bucket: bucket,
        Key: key
      };
      s3.getObject(params, (err, data) => {
        if(err) {
          return reject(err);
        } else {
          const getData = JSON.parse(data.Body.toString());
          return resolve(getData["payloads"]);
        }
      });
    });
  }
}

module.exports = s3GetObject;
dynamo_put_data.js
class dynamoPutData {

   /**
   * DynamoDBにデータを保存する(Put)
   * @param {DocumentClient} dynamoDB
   * @param item
   * @returns {Promise}
   */
  static putDynamoDB(dynamoDB, item) {
    return new Promise((resolve, reject) => {
      const params = {
        TableName: "tableName",
        Item: item
      };
      return dynamoDB.put(params, (err, data) => {
        if (err) {
          console.log(err);
          return reject(err);
        } else {
          console.log(data);
          return resolve(data);
        }
      });
    });
  }
}

module.exports = dynamoPutData;

S3

  1. S3のコンソール画面からプロパティ⇒Eventsを選択
  2. 「Add notification」を選択
  3. 「Name」には任意の文字列、「Events」はPut、「Send to」は「Lambda Function」を選択し、先ほど作成したLambdaを選択します

これで作業完了です。
Funnel経由でJSONを送信し、S3やDynamoDBにデータが保存されているか確認してください

まとめ

とりあえずFirehoseがどうしても1分データを保持してしまうのでStreamに比べたら処理時間がかかってしまうようです
というかリアルタイム性がほしければStream使おうかってなりますよねー
あと早く東京に上陸してくれないだろうかって感じですね

ではまた!

続きを読む

SORACOM BeamでバイナリデータをAPIGateway経由でS3にアップロードする

はじめに

あるお客様からLTEモジュールからS3に画像ファイルをアップロードしたとの要望があったので、いろいろな方法を模索したのですが、AWS-CLIなんて積めないし、HTTPリクエストのヘッダーに必要なAWS認証情報を作成できないし、といった感じでどうやって実現しようかと悩んでいました。(AWS-CLIやAWS-SDKのすばらしさに感動したw)
そんなとき「SORACOM Beamを使ってAPIGateway経由で何とかならんかね?バイナリデータのサポートもされたことだし」という神のお告げが降ってきたので実装してみた

どんな感じ?

apigateway_post.PNG

SORACOM Beamって?

下記公式ページから抜粋

SORACOM Beam(以下、Beam)は、IoT デバイスにかかる暗号化等の高負荷処理や接続先の設定を、クラウドにオフロードできるサービスです。
Beam を利用することによって、クラウドを介していつでも、どこからでも、簡単に IoT デバイスを管理することができます。
大量のデバイスを直接設定する必要はありません。

(参考:https://soracom.jp/services/beam/)

S3バケットの作成

まず画像ファイルをアップロードするバケットを作成しましょう
詳しい手順は割愛しますが、バケットポリシーを設定しないとPutObjectできないのでこんな感じで設定しておきます

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AddPerm",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::[Bucket Name]/*"
        }
    ]
}

APIGatewayの作成

次にAPIGatewayを作成します

  1. APIGatewayのコンソール画面を開き、「Create API」をクリックします
  2. 「API name」に任意の文字列を入力し「Create API」で作成します
  3. リソースを作成します。「Actions」から「Create Resource」を選択し「upload」というリソースを作成します
  4. 「/upload」の子階層に「/{key}」といリソースを作成します。「/{key}」はURLパスパラメータとなり、今回はS3にPutする際のKey情報となります
  5. 「/{key}」を選択した状態で「Actions」から「Create method」を選択し「POST」メソッドを作成します。画面はこのような感じになっていると思います。この時点で「/upload/{key}」に対してPOSTメソッドを受け付けるように設定されています
    apigateway_post_test.PNG
  6. 「Integration type:HTTP」「HTTP method:PUT」「Endpoint URL:S3のエンドポイント(ex:https://s3-ap-northeast-1.amazonaws.com/[Bucket Name]/{key})」を選択・入力し「save」をクリックします。そうするとPOSTメソッドが作成されます
    apigateway_post_method.PNG
    「Method Request」を選択して「API Key Required」を「ture」に変更します
  7. 「Actions」から「Deploy API」を選択し、APIをデプロイします。初めての場合は[New Stage]を選択して新しいステージを作成してください。「Invoke URL」を控えておいてください
  8. バイナリデータを受け取る設定をします。「Binary Support」を選択し、「Edit」をクリックして「application/octet-stream」と「image/png」を追加します
  9. 次にSORACOM BeamからAPIGatewayを利用するにあたってAPIキーが必要になるので、APIキーを作成してAPIGatewayと紐付けます。
  10. 「API Keys」を選択し、「Actions」から「Create API Key」をクリック。「Name」に任意の文字列を入力し、「Save」をクリックします。これでAPIキーが作成されました。「API Key」の文字列をどこかに控えておいてください
  11. 「Usage Plans」を選択し、「Create」をクリック。各必須項目に任意の値を入力して「next」をクリック
  12. 「Add API Stage」をクリックして、先ほどデプロイしたAPIGatewayを選択し「Next」をクリック
  13. 「Add API Key to Usage Plan」を選択し、先ほど作成したAPIキーを入力し「Done」をクリック
  14. これで、APIGatewayとAPIキーが結びつきました

SORACOM Beamの設定

最後にSORACOM Beamの設定をします
1. SORACOMのコンソール画面を開きグループを作成します
2. 作成したグループのBeam設定を開き、プラスのアイコンから「HTTPエントリポイント」を選択します
3. 下記内容を入力・設定していきます

設定名:任意の文字列
エントリポイント
  パス:/
転送先
  ホスト名:APIGatewayのInvoke URL
  パス:/upload/test.png
ヘッダ操作
  IMSIヘッダ付与:ON
  署名ヘッダ付与:ON
  事前共有鍵:作成しておいたものを選択。新たに作成する場合は後ほど記述します
カスタムヘッダ:「アクション:置換」「ヘッダ名:X-API-KEY」「値:APIキーの文字列」

※事前共有鍵の作成
「認証情報ID」と「事前共有鍵」に任意の文字列を入力し「登録」をクリックしてください
soracom_key.PNG

これで「http://beam.soracom.io:8888/ 」に対してリクエストを送信すると、APIGatewayにPOSTメソッドが送信されます

送信テスト

SIMのグループをBeamが設定されているグループに変更し、デバイスにSSHでログインします
テスト送信用のpngファイル(下記コマンドでは「test.png」)があるディレクトリに移動し、下記コマンドを入力しS3に画像がアップロードされているかを確認してください

curl -X POST --data-binary "@test.png" -H  "Content-Type: application/octet-stream" http://beam.soracom.io:8888/

成功していれば、S3の対象バケット内に「test.png」がアップロードされていると思います

まとめ

最近よくIoT案件に関わらせていただいているのですが、そもそもセキュアな通信を前提としていないデバイスをクラウドなどと接続したいという要望が増えてきているように感じてきました
SORACOMさんのようなSIMに認証情報を持たせてセキュアな通信を担保してくれるようなサービスを最大限に活用してそういった要望にこたえていきたいと思う今日この頃でした
ただクラウド側での処理が複雑になればなるほどどこかで破綻するんじゃないかなーとも感じていたりいなかったり

あと、SORACOMBeamでAPIGatewayのパスを変数的に持たせることってできないんだろうか
APIGateway側でURLパスパラメータを使ってS3へアップロードするキー名を決めているので、今回のように固定にしてしまうと、アップロードされる画像がずっと上書きされてしまうので、あまりよろしくない・・・
S3のイベントで画像がアップロードされたタイミングでLambdaキックしてリネームしてアップロードしなおすという手もあるけど・・・
Beamの仕様を漁るか・・・
もし、何かよい方法があればコメント等で教えてください!

ではまた!

続きを読む