- 投稿日:2020-07-25T20:07:22+09:00
serverlessを使ってLambdaにmultipart/form-dataでバイナリデータをアップロードする
serverlessを使ってLambdaをデプロイする際、Lambda統合の時にバイナリデータをどうやって送るのか地味にハマったのでメモとして残しておきます。
(たぶんうちの若い子達がココ見るはず..)serverless便利ですね。コマンド一発でデプロイから各種AWSリソースをいい感じにセットアップしてくれます。いらなくなったら同じくコマンド一発でまるっと削除してくれます。これを使わない手はないですね。
TL;DR
- serverless.ymlのcustomキー配下にapiBinaryでバイナリメディアタイプを指定する
- serverless.ymlのpluginsキー配下にserverless-apigw-binaryを追加する
- Lambdaに送られてくるリクエストはbase64エンコードされたmultipart/form-dataなのでデコードしてaws-lambda-multipart-parserでマルチパートデータをバイナリにする
serverless.ymlcustom: ...省略 apigwBinary: types: - multipart/form-data ...省略 plugins: - serverless-apigw-binaryserverless.yml
serverless.ymlの記載内容をまとめると以下のようになるかと思います。
- S3バケットの設定
- Lambdaの設定
- APIGatewayの設定
serverless.ymlservice: name: FileUploadTest custom: webpack: webpackConfig: ./webpack.config.js includeModules: true apigwBinary: types: - multipart/form-data # ← バイナリメディアタイプの指定 webSiteName: jp.co.onewedge.test.fileuploadtest s3Sync: - bucketName: ${self:custom.webSiteName} localDir: static # ← プロジェクトのstaticフォルダ配下のファイルをS3にアップロード # Add the serverless-webpack plugin plugins: - serverless-webpack - serverless-apigw-binary # ← APIGatewayのバイナリメディアタイプでアップロードするために必要 - serverless-s3-sync # ← S3に静的ファイルをアップロードするために必要 provider: name: aws runtime: nodejs12.x region: us-east-1 endpointType: REGIONAL apiGateway: minimumCompressionSize: 1024 environment: AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1 functions: fileUpload: handler: index.handler events: - http: method: post path: doUpload cors: false resources: Resources: # ←S3バケットでHTMLを配信するための設定 StaticSite: Type: AWS::S3::Bucket Properties: BucketName: ${self:custom.webSiteName} AccessControl: PublicRead WebsiteConfiguration: IndexDocument: index.html ErrorDocument: error.html StaticSiteS3BucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: Ref: StaticSite PolicyDocument: Statement: - Sid: PublicReadGetObject Effect: Allow Principal: "*" Action: - s3:GetObject Resource: Fn::Join: ["", ["arn:aws:s3:::",{"Ref": "StaticSite"},"/*"]]multipart/form-dataでバイナリデータをアップロードする場合、キモとなるのはAWSコンソールのバイナリメディアタイプとなります。serverlessを利用する場合はserverless-apigw-binaryプラグインの導入と、customキー配下apigwBinaryキーの指定となります。このtypesで指定した値がバイナリメディアタイプとなります。
serverless.ymlcustom: ...省略 apigwBinary: types: - multipart/form-data ...省略 plugins: - serverless-apigw-binaryLambda実装
Labmda側は、通常のLambda統合のリクエストでデータを取得できます。
この際、event.bodyにはbase64エンコードされたmultipart/form-dataが格納されています。
つまり、そのままでは利用できないので受け取ったevent.bodyはデコードしたあとmultipartの処理を行います。index.ts'use strict'; import { APIGatewayProxyHandler, APIGatewayProxyEvent, Context} from 'aws-lambda'; import multipart from 'aws-lambda-multipart-parser'; import { S3 } from 'aws-sdk'; import 'source-map-support/register'; export const handler: APIGatewayProxyHandler = async (event:APIGatewayProxyEvent, _context:Context) => { // 受け取ったevent.bodyがbase64エンコードされているのでデコード const event2:APIGatewayProxyEvent = event; event2.body = Buffer.from(event.body, 'base64').toString('binary'); // multipart/form-dataをパースする const multipartBuffer = multipart.parse(event2, true); // クライアント側で特に指定なき場合、アップロードされたファイルはfile.contentとして取り出せる // ここではアップロードされたファイルをS3に保管します const s3 = new S3(); const params:S3.Types.PutObjectRequest = { Bucket:'jp.co.onewedge.test.fileuploadtest', Key: '保存ファイル名', Body: multipartBuffer.file.content }; await s3.putObject(params).promise(); // ... 省略 }デプロイ
ここまでできたらコマンドラインからいったんデプロイしましょう。
デプロイするとAPIGatewayのエンドポイントがコンソールに表示されたログの中のendpoints欄に表示されるので、メモっておきます。$ sls deploy ...省略 Service Information service: FileUploadTest stage: dev region: us-east-1 stack: FileUploadTest-dev resources: xx api keys: None endpoints: POST - https://xxxx.execute-api.us-east-1.amazonaws.com/dev/doUpload # ←これです functions: fileUpload: fileUpload-dev-doUpload layers: None ...省略HTML作成
HTMLを作成し、先ほどメモったAPIのエンドポイントに向けてファイルをPostするようにしてください。今回はVue.jsとAxiosで作りました。
index.html<html> <head> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> </head> <body> <div id="app"> <form > <input type="file" id="file" ref="file" @change="handledUploadFile" /> <button type="button" @click="onClickUpload">アップロード</button> </form> </div> <script type="text/javascript"> const v = new Vue({ el: '#app', data: function(){ return { file:'' } }, methods:{ handledUploadFile: function(){ this.file = this.$refs.file.files[0]; }, onClickUpload: function(){ const formData = new FormData(); formData.append('file', this.file); axios.post('メモったエンドポイントURL', formData, { headers: { 'Content-Type': 'multipart/form-data' } } ).then(function(data){ console.log(`SUCCESS!!:${data}`); }).catch(function(err){ console.log(`FAILURE!!${err}`); }); } } }) </script> </body> </html>作成したHTMLはstaticディレクトリに配置します。
配置したら再度デプロイしましょう。今度は作成したHTMLがS3にアップロードされるはずです$ sls deploy
だいぶ端折りましたがこんな感じで動くかと思います。
仲間募集!
株式会社ONE WEDGEでは元気なエンジニア募集中です!一緒に「おもしろい」を作りましょう!
- 投稿日:2020-07-25T19:49:32+09:00
JS (TS) で CLI を作る要点メモ
概要
Node.js で CLI を作ったので備忘録として作り方の要点をメモします。
npm 等には公開せず、ローカル環境での実行のみを想定しています。環境
Node.js 12.13.0
要点
package.json
でbin
を指定コマンドラインから実行するコマンドと、コマンドを実行した際に実行されるスクリプトファイルを指定。
package.json{ "bin": { "my-cli-name": "bin/cli.js" } }
bin
で指定したスクリプトを作成シェバンで node を指定して、実行したい処理を記述します。
bin/cli.js#!/usr/bin/env node require('../dist/app.js');その他作法
- プロジェクト内の JSON 等静的テキストファイルは
require
で読み込むようにする。
path.resolve
を使うとバンドルされた後やbin
ディレクトリからの実行時に__dirname
の挙動が変わる。(10割勉強不足)- 静的ファイルは
require
でスクリプト内に埋め込んでしまうのが、時間が無い時は学習コスト掛からず手っ取り早い。- テンポラリファイル等の置き場所の解決はどうするのが良いのかまだ判断ついてない。
作ったコマンドのインストール
# プロジェクトのディレクトリで npm link # または npm install -g .振り返り
cli で動くプログラムをまともに作ったのが初めてだったのだけど、
今回シングルプロセス/シングルスレッド + 同期 I/O で作っちゃったので処理速度が結構遅くなりました。
非同期 I/O と Worker Threads を覚えるとよさそう。
- 投稿日:2020-07-25T16:10:29+09:00
クライアントアプリから Google Cloud Speech-to-Text を使ってみた
WEBアプリの開発をしていて「ユーザの音声から文字を起こしたい」つまりブラウザで音声認識したいという要件で、いろいろ調べものしたときの備忘メモ。
TL;DR
- どの環境、どのブラウザでも音声認識を動作するように作るのはなかなか難しい
- ブラウザのみで完結する音声認識API「Web Speech API」の
SpeechRecognition
は、Chromeなどかなり限定的なブラウザのみでしか動作しないが、かなりお手軽- Google Speech-to-Text ライブラリは、WEB(JavaScript)から利用できるかよく分からない。Google謹製のデモ見ると利用できるように見えるんだけど。→ RESTインタフェースをコールすることはできる、ことは確認済み
- 最終的には
- いろいろなブラウザで汎用的に動いた「Web Audio API」の
AudioContext
をつかって音声を録音し- 音声をwav形式に変換
- wav形式の音声をGoogle Speech-to-Text のRESTインタフェース に渡して、認識結果を得る
は確認できました。
今回のコンテンツ
今回は Google Speech-to-Text をクライアントアプリから利用してみるところまで、の備忘です。
Google Speech-to-Textは、ココにデモがありますが、音声ファイルをアップロードして認識してもらったり、ブラウザからマイクを利用して音声認識などができるサービスです1。前提や環境
% sw_vers ProductName: Mac OS X ProductVersion: 10.15.6 BuildVersion: 19G73 % node --version v10.19.0 % firebase --version 8.6.0 %
今回は音声データの変換などはMacでやっていますが、プログラム自体はWindowsでも動くと思います。
事前作業
FirebaseとGoogle Cloud Platform(GCP) を利用開始します。Firebase や Google Cloud Platformのサインアップ の記事の 「Firebaseのサインアップ」「GCPのサインアップ」 などを参考にしてください。
ソースをCloneする
% git clone https://github.com/masatomix/speech_node_samples.git Cloning into 'speech_node_samples'... Resolving deltas: 100% (75/75), done. % cd speech_node_samples/sample_ts % ls -lrt total 1944 -rw-r--r-- 1 masatomix staff 5919 7 17 21:26 tsconfig.json -rw-r--r-- 1 masatomix staff 890 7 24 01:25 package.json -rw-r--r-- 1 masatomix staff 109687 7 24 01:25 package-lock.json drwxr-xr-x 6 masatomix staff 192 7 24 11:41 src -rw-r--r--@ 1 masatomix staff 7090 7 24 20:56 README.md % npm install ... %
- ココ を参考に サービスアカウントファイル
firebase-adminsdk.json
を取得し、上記の場所に配置します。- Google Cloud Platform のクイックスタート の冒頭を参考に、Cloud Speech-to-Text API を有効にします。2
Google Speech-to-Text をNode.jsのクライアントアプリケーションから実行する
ライブラリを利用する
すべてのクイックスタートにある「クライアント ライブラリの使用」 をやってみましょう。
まずは音声ファイルを準備します。今回はiPhoneのボイスメモで取得した音声ファイル(sample.m4a
)を、Mac上のコンバータafconvert
でwavファイルに変換しました。参考: https://tsukada.sumito.jp/2019/06/11/google-speech-api-japanese/
Windowsをご利用の方は、適宜音声ファイルをご準備ください:-)
% ls -lrt ... 省略 -rw-r--r--@ 1 masatomix staff 39898 7 24 13:31 sample.m4a ← iPhoneのボイスメモで作成したファイル % % afconvert -f WAVE -d LEI16 sample.m4a sample.wav % ls -lrt ... 省略 -rw-r--r--@ 1 masatomix staff 39898 7 24 13:31 sample.m4a -rw-r--r-- 1 masatomix staff 420274 7 24 14:02 sample.wav ← 変換できた %
音声ファイルの変換ができました。以上で、最終的な構成は以下のようになりました。
% ls -lrt total 1944 -rw-r--r-- 1 masatomix staff 5919 7 17 21:26 tsconfig.json -rw-r--r-- 1 masatomix staff 890 7 24 01:25 package.json -rw-r--r-- 1 masatomix staff 109687 7 24 01:25 package-lock.json -rw-r--r--@ 1 masatomix staff 7090 7 24 20:56 README.md drwxr-xr-x 6 masatomix staff 192 7 24 11:41 src -rw-r--r--@ 1 masatomix staff 39898 7 24 13:31 sample.m4a -rw-r--r-- 1 masatomix staff 420274 7 24 14:02 sample.wav -rw-r--r--@ 1 masatomix staff 2335 7 17 15:21 firebase-adminsdk.json
下記のコマンドを実行します。
準備でダウンロードしたfirebase-adminsdk.json
を指定する環境変数を定義して、コードsrc/index.ts
を実行しています。% pwd /xxx/speech_node_samples/sample_ts % export GOOGLE_APPLICATION_CREDENTIALS="`pwd`/firebase-adminsdk.json" % % npx ts-node src/index.ts { results: [ { alternatives: [Array], channelTag: 0 } ] } Transcription: ボイスメモのテストです。 %
ちゃんと音声認識できていますね!
あもちろん結果は音声ファイルによって異なります :-)コードの中身
コードを見ておきましょう。さきほどのクイックスタートのほぼまんまですが。
src/index.tsimport speech from '@google-cloud/speech' import fs from 'fs' async function main() { // Creates a client const client = new speech.SpeechClient() // The name of the audio file to transcribe const fileName = './sample.wav' // Reads a local audio file and converts it to base64 const file: Buffer = fs.readFileSync(fileName) const audioBytes: string = file.toString('base64') // The audio file's encoding, sample rate in hertz, and BCP-47 language code // https://cloud.google.com/speech-to-text/docs/reference/rest/v1/RecognitionConfig const config = { enableAutomaticPunctuation: true, encoding: 'LINEAR16', languageCode: 'ja-JP', model: 'default', } const request: any = { audio: { content: audioBytes, }, config: config, } // Detects speech in the audio file const [response]: Array<any> = await client.recognize(request) console.log(response) const transcription = response.results.map((result: any) => result.alternatives[0].transcript).join('\n') console.log(`Transcription: ${transcription}`) } if (!module.parent) { main().catch(console.error) }流れとしては
- ファイル
sample.wav
を読み込んで- Base64 エンコードして文字列化
- パラメタ
config
情報とともに、Base64 文字列をライブラリのrecognize
メソッドを呼び出す- 結果を得る
というシンプルなモノです。
まずこれで「音声ファイルをもとに、クライアントアプリケーションから、ライブラリを用いて音声認識する」ことができました。REST インタフェースを呼んでみる
つづいて
@google-cloud/speech
のライブラリ経由でなく、RESTインタフェースを直接呼び出してみます。
ちなみにRESTインタフェースの仕様はココ 。追加の準備として、Firebase や Google Cloud Platformのサインアップ の 「Firebaseのプロジェクト内に、アプリを作成する」を実施し、
firebaseConfig
の情報を下記のように保存しておきます。% cat ./src/firebaseConfig.ts export default { apiKey: 'xx', authDomain: 'xx', databaseURL: 'xx', projectId: 'xx', storageBucket: 'xx', messagingSenderId: 'xx', appId: '1:xx', } % ↑こんなファイルを手動で作る
さあ実行です。
% export GOOGLE_APPLICATION_CREDENTIALS= // 環境変数は不要なのでリセット % npx ts-node ./src/index2.ts Transcription: ボイスメモのテストです。 %
またまたちゃんと音声認識できていそうです!
コードの中身
さきほどとコードの構成はほぼおなじではありますが、今度はライブラリは使わずRESTインタフェースを直接呼び出しています。
src/index2.tsimport fs from 'fs' import request from 'request' import firebaseConfig from './firebaseConfig' const createRequestPromise = (option: any): Promise<Array<any>> => { const promise: Promise<any> = new Promise((resolve, reject) => { request(option, function (err: any, response: any, body: string) { if (err) { reject(err) return } if (response.statusCode >= 400) { reject(new Error(JSON.stringify(body))) } resolve(body) }) }) return promise } function main() { const API_KEY = firebaseConfig.apiKey // The name of the audio file to transcribe const fileName = './sample.wav' // Reads a local audio file and converts it to base64 const file: Buffer = fs.readFileSync(fileName) const audioBytes: string = file.toString('base64') // The audio file's encoding, sample rate in hertz, and BCP-47 language code const config = { enableAutomaticPunctuation: true, encoding: 'LINEAR16', languageCode: 'ja-JP', model: 'default', } const request: any = { audio: { content: audioBytes, }, config: config, } const option = { uri: `https://speech.googleapis.com/v1p1beta1/speech:recognize?key=${API_KEY}`, method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, json: request, } createRequestPromise(option) .then((response: any) => { const transcription = response.results.map((result: any) => result.alternatives[0].transcript).join('\n') console.log(`Transcription: ${transcription}`) }) .catch((error) => console.log(error)) } if (!module.parent) { main() }これで「音声ファイルをもとに、クライアントアプリケーションから、RESTを用いて音声認識する」ことができました。
以下蛇足
ちなみにMacでは
afinfo
コマンドなどで音声データの確認が可能です。$ afinfo sample.m4a File: sample.m4a File type ID: m4af Num Tracks: 1 ---- Data format: 1 ch, 48000 Hz, 'aac ' (0x00000000) 0 bits/channel, 0 bytes/packet, 1024 frames/packet, 0 bytes/frame no channel layout. estimated duration: 4.335187 sec audio bytes: 36206 audio packets: 206 bit rate: 65908 bits per second packet size upper bound: 275 maximum packet size: 275 audio data file offset: 44 not optimized audio 208089 valid frames + 2112 priming + 743 remainder = 210944 format list: [ 0] format: 1 ch, 48000 Hz, 'aac ' (0x00000000) 0 bits/channel, 0 bytes/packet, 1024 frames/packet, 0 bytes/frame Channel layout: Mono ----
afplay
コマンドは音声を再生できたりします。% afplay sample.wav % (再生されてます)
参考: https://qiita.com/fromage-blanc/items/32e2ba83b79151e5ecb9
べんりですね。
まとめ
- 音声ファイルをもとに、クライアントアプリケーションから、ライブラリを用いて音声認識する ことができました。
- 音声ファイルをもとに、クライアントアプリケーションから、RESTを用いて音声認識する ことができました。
次回は、マイクを使った音声認識をやってみましょう。
おつかれさまでしたー。
関連リンク
- Speech-to-Text ドキュメント 公式
- RecognitionConfig configで設定できる値の一覧
- 議事録担当なんてなくそうよ。Google Cloud Speech -to-Textを使ってみた
- JavaScript で Google Cloud Speech-to-Text 音声認識
- Recorder.js ライブラリを作っている方もいました(未検証)
- Web Speech APIとは? ここから下のリンクは、今回あつかわなかった「Web Speech API」などの説明
- Vue×Firebase×Web Speech APIでお手軽にText to Speech、Speech to Textなアプリを作ってみる
- MediaRecorder APIを使った簡単なオーディオキャプチャ
- How to Google Speech-to-Text using Blob sent from Browser to Nodejs Server サーバ側でSpeech-to-Textを動かすとして、MediaRecorder でキャプチャした音声をWebSocket でデータ送信する、などを試行錯誤しているサンプル
- ブラウザで取得した音声データを、サーバーでwav形式で保存する こちもWebSocketで送信してます
- ブラウザで録音してwavで保存 マイクで録音したデータをwav形式で保存するサンプル。これで取得した音声データをRESTに渡すことで、いろんなブラウザでうごく音声認識が実装できそうですね。
- safari(ios)でMediaRecorderを使う
- たった17行のコードで音声自動文字起こしを実装する web speech API をつかったシンプルな例
- Google Speech to Text APIを使ってブラウザでリアルタイム文字起こしする Web Speech API + Google Cloud Speech-to-Text の合わせ技。Chrome Onlyになっちゃうけど。
- 音声入力できる入力コンポーネントを作る Web Speech API をつかったVue.jsの例
- 投稿日:2020-07-25T15:19:59+09:00
Azure IoT Data Pipeline - EventHub から Functions で PostgreSQL へ
はじめに
Azure を使って、センサ(Mosquitto)からのデータを PostgreSQL まで持っていくようなデータパイプラインをつくりました1
ここでは、
EventHub
>Functions
>PostgreSQL
のところを設定するにあたってやったことをつらつらと書きますちなみに
Mosquitto
>IoT Hub
周りは、Azure IoT Hub に Mosquitto™ から MQTT なげてみる に書いてありますやったこと
やったことは、こんな感じの流れになります
EventHub
はできていて、データが流れてきている状態からスタート2PostgreSQL
を設定するFunctions
を設定するEventHub から流れてくるデータ
こんな感じの電流値センサからのデータです
{ "id": "11253", "mid": "M_180331", "name": "60Min_W-PDP-I4B-A1 出力回路23 電流 計測", "time": "2020/05/05 08:00:00", "value": "0", "unit": "A", "EventProcessedUtcTime": "2020-07-25T05:06:10.8586288Z", "PartitionId": 0, "EventEnqueuedUtcTime": "2020-07-25T05:06:10.777Z", "IoTHub": { "MessageId": null, "CorrelationId": null, "ConnectionDeviceId": "iot-gw-tk10", "ConnectionDeviceGenerationId": "637294479848408560", "EnqueuedTime": "2020-07-25T05:06:10" } }PostgresSQL を設定する
ここはあまり書くことがありません。以下のドキュメントに従って進めるだけです
Quickstart: Create an Azure Database for PostgreSQL server in the Azure portal
- PostgreSQLの名前やパスワードだけ控えておきます
- 途中、データベースに外部からアクセスできるように、IPアドレスのフィルタを設定します。Azure の ネットワークセキュリティとは独立した設定になるようなので注意が必要です(ネットワークセキュリティの方では穴を開けてなくても、こちらを開けると穴が開いてしまいます)
psql
をインストールして接続(PostgreSQL全体ではなく)
psql
だけをインストール(Mac の場合)brew install libpqインストールしたところにパスをとおす。私の場合は以下
/usr/local/Cellar/libpq/12.3/bin
psql
で接続するpsql --host=*****.postgres.database.azure.com --port=5432 --username=*****@***** --dbname=postgresデータを受け取るテーブルを作成
テーブル作成SQLCREATE TABLE public.iotdata ( deviceid integer NOT NULL, createdate timestamp without time zone NOT NULL DEFAULT now(), data jsonb );データをカラムに分けた方がいいんですが、まずは動かすことに注力して、データは
JSON
のままjsonb
のカラムに放り込む方向性postgres=> CREATE DATABASE iotdemo; postgres=> \c iotdemo iotdemo=> \c psql (12.3, server 10.11) SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off) You are now connected to database "iotdemo" as user "*****@*****". iotdemo=> CREATE TABLE public.iotdata iotdemo-> ( iotdemo(> deviceid integer NOT NULL, iotdemo(> createdate timestamp without time zone NOT NULL DEFAULT now(), iotdemo(> data jsonb iotdemo(> ); CREATE TABLE iotdemo=> iotdemo=> iotdemo=> \dt List of relations Schema | Name | Type | Owner --------+---------+-------+--------- public | iotdata | table | ***** (1 row)Azure Functions を設定する
Azure Functions の理解
他のクラウドで Functions を触ったことがあればそれほど違和感はない3 。初めてであれば、いきなり
EventHub
をターゲットにせずに、ファンクション自体の理解のために、最初に クイック スタート:HTTP 要求に応答する関数を Azure で作成する をやっておくと流れが掴めるのでおすすめ開発環境
以下から選ぶ感じ4
- Webポータル上でコード書く(なにかとつらい)
- ローカルの場合 (おすすめ)
前提条件
ファンクションを書く前に、このあたりを事前に準備しておく
- Azure Functions Core Tools バージョン
2.7.1846
以降の2.x
バージョン- Azure CLI バージョン
2.4
以降5node
はアクティブ LTS およびメンテナンス LTS バージョン (8.11.1
および10.14.1
を推奨)6なんか、準備だけで、だいぶ、面倒くさくなってきてるが.... 進める
ローカルで書く場合のプロジェクト構成
ファンクションは
FunctionApp
>Function
という階層になっているようだ
- 開発言語や実行環境(Win/Linux)などは
FunctionApp
単位で定義FunctionApp
に複数のFunctions
が存在可能- クラウドへのデプロイ(
publish
)はFunctionApp
単位node
の場合はnode_modules
ごとデプロイすることになる7├── FunctionApp │ ├── EventHubTrigger # ファンクション#1 │ │ ├── function.json │ │ └── index.js │ ├── HTTPTrigger # ファンクション#2 │ │ ├── function.json │ │ └── index.js : : ├── host.json ├── local.settings.json ├── node_modules │ ├── buffer-writer │ │ ├── LICENSE : :トリガーとバインドの概念
ここで、Azure Functions でのトリガーとバインドの概念 を読む。今回は、トリガーとしての設定になる。
EventHub
にデータがきたらそれをトリガーにファンクションが動いて、DBに書き込むファンクションの設定
準備が整ったので、いよいよはじめるよ
ストレージ作成
まず、作ったファンクションをおく場所であるストレージを作る
$ az storage account create --name ***** --location japaneast --resource-group *****-poc --sku Standard_LRS$ az storage account show --name ***** -o table AccessTier CreationTime EnableHttpsTrafficOnly Kind Location Name PrimaryLocation ProvisioningState ResourceGroup StatusOfPrimary ------------ -------------------------------- ------------------------ --------- ---------- ------- ----------------- ------------------- --------------- ----------------- Hot 2020-07-23T09:07:56.927438+00:00 True StorageV2 japaneast ***** japaneast Succeeded *****-poc availableFunctionApp をつくる
クラウド側に FunctionApp をつくる
$ az functionapp create --resource-group *****-poc --consumption-plan-location japaneast --runtime node --runtime-version 10 --functions-version 2 --name *****-func-e2p --storage-account *****$ az functionapp list -o table Name Location State ResourceGroup DefaultHostName AppServicePlan ------------------- ---------- ------- ------------------- ------------------------------------- -------------------------- *****-func-e2p Japan East Running *****-poc *****-func-e2p.azurewebsites.net JapanEastPlan$ az functionapp config appsettings list --name *****-func-e2p --resource-group *****-poc$ func azure functionapp list-functions *****-func-e2p --show-keysローカル側で FunctionApp を初期化
func init e2p --javascript├── e2p │ ├── host.json │ ├── local.settings.json │ └── package.json
host.json
のversion
を3
はファンクションランタイムバージョンではない。単純にhost.json
のスキーマのバージョンなので、2
のままで良いここで、
e2p
におりて、ファンクションをつくる今回は、
Azure Event Hub trigger
で作成$ func new Select a number for template: 1. Azure Blob Storage trigger 2. Azure Cosmos DB trigger 3. Durable Functions activity 4. Durable Functions HTTP starter 5. Durable Functions orchestrator 6. Azure Event Grid trigger 7. Azure Event Hub trigger 8. HTTP trigger 9. IoT Hub (Event Hub) 10. Azure Queue Storage trigger 11. SendGrid 12. Azure Service Bus Queue trigger 13. Azure Service Bus Topic trigger 14. SignalR negotiate HTTP trigger 15. Timer trigger Choose option: 7 Azure Event Hub trigger Function name: [EventHubTrigger] Writing /Users/*****/azure/e2p/EventHubTrigger/index.js Writing /Users/*****/azure/e2p/EventHubTrigger/function.json The function "EventHubTrigger" was created successfully from the "Azure Event Hub trigger" template.
function.json
とindex.js
のテンプレが作成されるので、function.json
のパラメータを 接続するEventHub
に合わせて書き換えるfunction.json{ "bindings": [ { "type": "eventHubTrigger", "name": "eventHubMessages", "direction": "in", "eventHubName": "*****", "connection": "*****_RootManageSharedAccessKey_EVENTHUB", "cardinality": "many", "consumerGroup": "$Default" } ] }ここで、よくわからなかったのが、
connection
の値8。結論としては、これはEvent Hubs 名前空間
>共有アクセス ポリシー
にある主キー
の値のこと。実際には主キー
をそのまま書くのではなく、ファンクションの構成
>アプリケーション設定
に環境変数のような感じで書いておくのがベストプラクティスのようだ(以下参照)9ファンクションを書く
やっと、
index.js
に中身を書く... 今回書いたのは超シンプルにこんな感じindex.jsmodule.exports = async function (context, eventHubMessages) { var pg = require('pg'); const config = { host: '*****.postgres.database.azure.com', user: '*****@*****', password: '*****', database: 'iotdemo', port: 5432, ssl: true }; var client = new pg.Client(config); eventHubMessages.forEach((message, index) => { context.log("Processed message: " + JSON.stringify(message)); context.log("id: " + message.id) const sql = 'insert into iotdata(deviceid, data) values(' + message.id + ',' + '\'' + JSON.stringify(message) + '\');'; console.log("SQL: " + sql); client.connect() client.query(sql, (err, res) => { if (err) { console.error(err.stack) } else { console.log(res) console.log('insert completed successfully!'); } client.end() }) }); context.log("eventHubMessages: " + JSON.stringify(eventHubMessages)); };
pg
パッケージをインストールする$ npm install pgとやっておいて、
node_modules
ができるのを確認する。このフォルダも一緒にクラウドにあげることになるクラウドにアップロードする
publish
はhost.json
のあるところで叩く10$ func azure functionapp publish *****-func-e2p$ func azure functionapp publish *****-func-e2p You're trying to use v3 tooling to publish to a non-v3 function app (FUNCTIONS_EXTENSION_VERSION is set to ~2). You can pass --force to force update the app to v3, or downgrade to v1 or v2 tooling for publishing. *****:e2p *****$ func azure functionapp publish *****-func-e2p --force Getting site publishing info... Creating archive for current directory... Uploading 1.21 KB [###############################################################################] Upload completed successfully. Deployment completed successfully. Syncing triggers... Functions in *****-func-e2p: EventHubTrigger - [eventHubTrigger]これで、ひとまず動くはず。
確認する
ポータルの
App Service
>関数
>モニタ
>ログ
でログが確認できる2020-07-25T05:06:11Z [Information] Executing 'Functions.EventHubTrigger' (Reason='', Id=e1066a47-60af-4d8b-a9df-8d1c5a31a76e) 2020-07-25T05:06:11Z [Information] Processed message: {"id":"11253","mid":"M_180331","name":"60Min_W-PDP-I4B-A1 出力回路23 電流 計測","time":"2020/05/05 08:00:00","value":"0","unit":"A","EventProcessedUtcTime":"2020-07-25T05:06:10.8586288Z","PartitionId":0,"EventEnqueuedUtcTime":"2020-07-25T05:06:10.777Z","IoTHub":{"MessageId":null,"CorrelationId":null,"ConnectionDeviceId":"iot-gw-tk10","ConnectionDeviceGenerationId":"637294479848408560","EnqueuedTime":"2020-07-25T05:06:10"}} 2020-07-25T05:06:11Z [Information] id: 11253 2020-07-25T05:06:11Z [Information] eventHubMessages: [{"id":"11253","mid":"M_180331","name":"60Min_W-PDP-I4B-A1 出力回路23 電流 計測","time":"2020/05/05 08:00:00","value":"0","unit":"A","EventProcessedUtcTime":"2020-07-25T05:06:10.8586288Z","PartitionId":0,"EventEnqueuedUtcTime":"2020-07-25T05:06:10.777Z","IoTHub":{"MessageId":null,"CorrelationId":null,"ConnectionDeviceId":"iot-gw-tk10","ConnectionDeviceGenerationId":"637294479848408560","EnqueuedTime":"2020-07-25T05:06:10"}}] 2020-07-25T05:06:11Z [Information] Executed 'Functions.EventHubTrigger' (Succeeded, Id=e1066a47-60af-4d8b-a9df-8d1c5a31a76e)あとは、PostgreSQL に SQL なげてみればデータ入っているはず
今後
Mosquitto
のところを、IoT Edge
にして、コンテナ化して遊ぶStreaming Analytics
でいろいろアノマリーを検知して遊ぶPowerBI
で可視化して遊ぶ- センサの数を増やし、データをよりリアルタイムにして遊ぶ
参考にしたサイト
- Creating IoT applications with Azure Database for PostgreSQL
- Send telemetry from a device to an IoT hub to Azure Database for PostgreSQL
- クイック スタート:Node.js を使用して Azure Database for PostgreSQL - Single Server に接続し、データにクエリを実行する
- Azure functions integration with Postgres in Node.js
こんなリッチな構成が本当に必要なのかと思わなくはない ↩
IoT Hub
>Streaming Analytics
>EventHub
はポータル上でマウスでコチコチやれば比較的簡単につながりますのでやってみてください ↩Firebase のファンクションの方が使いやすいな... という違和感を除く ↩
ただし、今回は言語を JavaScript にしてるので、PostgreSQL と繋げるためにパッケージを入れる必要があることから、ローカルでやることになるのであった ↩
さあ、
brew upgrade azure-cli
をどうそ ↩この記述は古いと思われる。Function のバージョンが
3.x
であればnode 12
が使えるはず 。私はここで、homebrew
で入れた最新の14
を消して、nvm
を入れ、12
を入れる羽目になる。Happy Yac Shaving?! ↩今回 PostgreSQLと接続するために
pg
パッケージを使うので これは必須 ↩Webポータルからファンクションをつくると、GUIで選択できるのでそれほど困らないが、ローカルでファンクション作ると何を書けばいいのかわかりにくい。コンポーネントごとに毎回キーを指定しないといけないのは、Azure の ダウンサイドだな... がんばれ Azure IAM... ↩
CLIでは
az functionapp config appsettings list -g *****-poc --name *****-func-e2p
みたいな感じで確認・設定できる ↩さもなくば、
Unable to find project root. Expecting to find one of host.json in project root.
と怒られる ↩
- 投稿日:2020-07-25T00:22:34+09:00
Node.jsでGCEのインスタンスを作成・削除する
作成
const Compute = require('@google-cloud/compute'); const compute = new Compute(); const option = { .......... }; const zone = compute.zone('asia-northeast1-b'); const [vm, operation] = await zone.createVM(vmName, option); await operation.promise(); console.log(vmName + ' created!');option の中身はGCPのコンソールでポチポチやった時のパラメータ(下記参照)をコピーして使うと良い
削除
const compute = new Compute(); const zone = compute.zone('asia-northeast1-b'); const vm = await zone.vm(vmName); const [operation] = await vm.delete(); await operation.promise(); console.log(vmName + ' deleted!');