miniconda環境にlocalstack専用のenvironmentを作成する

localstack の実行環境を作成するにあたり、既存のPythonの環境とは分離しておきたいので、miniconda で localstack 専用の environment を作成して、ここにインストールした記録。

environment の作成

$ conda create -n localstack
Fetching package metadata .........
Solving package specifications: 
Package plan for installation in environment /Users/<username>/miniconda3/envs/localstack:

Proceed ([y]/n)? y

environment を activate

$ . activate localstack
(localstack) $

localstack をインストール。

(localstack) $ pip install localstack

localstack を起動

(localstack) $ localstack start

筆者の環境では以下のようなエラーが出て起動に失敗する状態だったため、つぎ示すようなパッチを当てた。

(localstack) $ localstack start
Starting local dev environment. CTRL-C to quit.
Error starting infrastructure: 'zipimport.zipimporter' object has no attribute 'path'
Traceback (most recent call last):
  File "/Users/<username>/miniconda3/envs/localstack/bin/localstack", line 79, in <module>
    infra.start_infra()
  File "/Users/<username>/miniconda3/envs/localstack/lib/python3.6/site-packages/localstack/services/infra.py", line 362, in start_infra
    raise e
  File "/Users/<username>/miniconda3/envs/localstack/lib/python3.6/site-packages/localstack/services/infra.py", line 308, in start_infra
    load_plugins()
  File "/Users/<username>/miniconda3/envs/localstack/lib/python3.6/site-packages/localstack/services/infra.py", line 102, in load_plugins
    file_path = '%s/%s/plugins.py' % (module[0].path, module[1])
AttributeError: 'zipimport.zipimporter' object has no attribute 'path'

object has no attribute 'path' の対策:

--- envs/localstack/lib/python3.6/site-packages/localstack/services/infra.py    2017-07-20 19:57:18.000000000 +0900
+++ envs/localstack/lib/python3.6/site-packages/localstack/services/infra.py    2017-07-20 20:22:30.000000000 +0900
@@ -96,7 +96,8 @@
         if six.PY3 and not isinstance(module, tuple):
             file_path = '%s/%s/plugins.py' % (module.module_finder.path, module.name)
         elif six.PY3 or isinstance(module[0], pkgutil.ImpImporter):
-            file_path = '%s/%s/plugins.py' % (module[0].path, module[1])
+            if hasattr(module[0], 'path'):
+                file_path = '%s/%s/plugins.py' % (module[0].path, module[1])
         if file_path and file_path not in loaded_files:
             load_plugin_from_path(file_path)
             loaded_files.append(file_path)

最初は jar ファイルのダウンロードが行われるため、立ち上がるまでかなり時間がかかるがじっと待つ。

(localstack) $ localstack start
Starting local dev environment. CTRL-C to quit.
Starting mock ES service (http port 4578)...
Starting local Elasticsearch (http port 4571)...
Starting mock S3 (http port 4572)...
Starting mock SNS (http port 4575)...
Starting mock SQS (http port 4576)...
Starting mock SES (http port 4579)...
Starting mock API Gateway (http port 4567)...
Starting mock DynamoDB (http port 4569)...
Starting mock DynamoDB Streams service (http port 4570)...
Starting mock Firehose service (http port 4573)...
Starting mock Lambda service (http port 4574)...
Starting mock Kinesis (http port 4568)...
Starting mock Redshift (http port 4577)...
Starting mock Route53 (http port 4580)...
Starting mock CloudFormation (http port 4581)...
Starting mock CloudWatch (http port 4582)...
WARNING:infra.py:Service "elasticsearch" not yet available, retrying...
WARNING:infra.py:Service "elasticsearch" not yet available, retrying...
WARNING:infra.py:Service "elasticsearch" not yet available, retrying...
Ready.

初期状態では、DynamoDB は -inMemory オプションがついた状態で起動され、localstack を停止するとデータも消滅する。
データを保存するためには、環境変数 DATA_DIR をセットしておく。
この環境変数をセットすると Kinesis と Elasticsearch のデータ保存場所もこのディレクトリの中に変更される。

conda 環境では、activate および deactivate を実行したときに、environment の etc/conda/activate.d/*.sh あるいは etc/conda/deactivate.d/*.sh が自動的に source されるので、自動的にこの環境変数をセットするようにフックを作成する。

ここでは、environment の data を DATA_DIR に指定している。

(localstack) $ cd $CONDA_PREFIX
(localstack) $ mkdir data
(localstack) $ mkdir -p etc/conda/activate.d
(localstack) $ mkdir -p etc/conda/deactivate.d
(localstack) $ echo 'export DATA_DIR=$CONDA_PREFIX/data' >etc/conda/activate.d/env_vars.sh
(localstack) $ echo 'unset DATA_DIR' >etc/conda/deactivate.d/env_vars.sh

いったん deactivate して、再度 activate すれば、環境変数 DATA_DIR がセットされているはずである。

(localstack) $ . deactivate
$ . activate localstack
(localstack) $ echo $DATA_DIR
/Users/<username>/miniconda3/envs/localstack/data

この状態で localstack を起動すれば、DynamoDB のデータも localstack の停止後も消えずに残るようになる。

続きを読む

AWS LambdaでDynamoDBから取得した値に任意の集計をかける(グルーピング処理追加)

以前の投稿の更新版です。前の仕組みでは一つのデータに対しての集計しかけられなかったのですが、それでは実運用上あまりに不便、と思ったので、指定した値でグルーピングできるようにしてみました。

AWS LambdaでDynamoDBから取得した値に任意の集計をかける

インプットデータのフォーマット変更

ほとんど使い方は以前のものと一緒ですが、以下の点だけ変えました。

  1. IDは配列形式([“sensor1”, “sensor2”])で指定するように仕様変更した。
  2. テーブル名を環境変数から取得するようにした。

IDは配列形式([“sensor1”, “sensor2”])で指定するように仕様変更した

こんな感じが最新のフォーマットです。

{
  "label_id": "id",
  "label_range": "timestamp",
  "id": [
    "sensor1",
    "sensor2"
  ],
  "aggregator": "latest",
  "time_from": "2017-04-30T22:00:00.000",
  "time_to": "2017-04-30T22:06:00.000",
  "params": {
    "range": "timestamp"
  }
}

IDの部分を配列にしました。こうすることで指定したIDの最新値を取得することが可能になります。
戻り値はこんな感じになります。

"[{"timestamp": "2017-04-30T22:05:00.000", "score": 0.0, "id": "sensor1"}, {"timestamp": "2017-04-30T22:06:00.000", "score": 1.0, "id": "sensor2"}]"

ちなみにDynamoDBにはこんな感じの値を用意していました。

スクリーンショット 2017-07-19 18.12.43.png

テーブル名を環境変数から取得するようにした

そのまんまです。 handler.pyの os.environ['TABLE'] の部分です。Lambda実行時に環境変数をこんな感じで指定してください。

スクリーンショット 2017-07-19 18.13.46.png

handler.py
import sys
import boto3
import json
import decimal
import os
from boto3.dynamodb.conditions import Key

from aggregator.lambda_aggregator import LambdaAggregator
from aggregator.latest_aggregator import LatestAggregator
from aggregator.max_aggregator import MaxAggregator
from aggregator.min_aggregator import MinAggregator
from aggregator.sum_aggregator import SumAggregator
from aggregator.avg_aggregator import AvgAggregator
from aggregator.count_aggregator import CountAggregator

dynamodb = boto3.resource('dynamodb')
table    = dynamodb.Table(os.environ['TABLE'])

aggregator_map = {}
aggregator_map['latest'] = LatestAggregator()
aggregator_map['max'] = MaxAggregator()
aggregator_map['min'] = MinAggregator()
aggregator_map['sum'] = SumAggregator()
aggregator_map['avg'] = AvgAggregator()
aggregator_map['count'] = CountAggregator()

def run(event, context):
    check_params(event)
    result = []

    for id in event['id']:
        res = table.query(
                KeyConditionExpression=Key(event['label_id']).eq(id) & Key(event['label_range']).between(event['time_from'], event['time_to']),
                ScanIndexForward=False
            )

        return_response = aggregator_map[event['aggregator']].aggregate(res['Items'], event['params'])
        result.append(return_response)

    return json.dumps(result, default=decimal_default)

def decimal_default(obj):
    if isinstance(obj, decimal.Decimal):
        return float(obj)
    raise TypeError

def check_params(params):
    if 'label_id' not in params or 'label_range' not in params or 'id' not in params or 'aggregator' not in params or 'time_from' not in params or 'time_to' not in params or 'params' not in params:
        sys.stderr.write("Parameters for label_id, label_range, id, aggregator, time_from, time_to and params are needed.")
        sys.exit()

ソース

こちらにコミットしています。(以前のものを更新)

https://github.com/kojiisd/lambda-dynamodb-aggregator

続きを読む

実務経験がなくても2ヶ月でAWSソリューションアーキテクトに合格するためにやったこと

ゴールデンウィークくらいからAWSを趣味で触り始めてちょっと面白そうだったので、ソリューションアーキテクトの試験を受けてみようかと思い立ちました。
2017年7月12日になんとかギリギリ69%で合格できていたのでどういう風に勉強していったのかを残してみます。
なお、Qiitaには初投稿になります。

まずAWSに触り始める前事の自分自身の知識としては

  • 普段はエンプラ系の製品開発に従事
  • IPAのネットワークスペシャリストとデータベーススペシャリストを保持
  • AWSの主要サービスがなんなのかくらいはうっすらとわかる
  • たまにAWS関連のニュースを見てる
  • 実務でのAWSの経験は今まで全くない(そしてこれからもなさそう…

という感じでした。

自分としてはネットワークやデータベース周りの基本的な知識はある方だと思っています。
しかし、実際のお仕事ではクラウドのクの字にもかすりもしない、全く縁がない分野の人間です。

また、平日はあまり勉強することはなく(Evernoteに記録したノートを読み返す程度)、主に土日のみ勉強していました。実際には休みの日に4~5時間程度だったと思います。

アカウントを作成する

なにはともあれまずは無料枠を全力で駆使するためにアカウントを作成しました。とはいえ最初は何からやって良いのかわからないので、ざっとチュートリアルを触ってみました。

AWS 10分間チュートリアル

ほんとに10分だけでいろいろと体験できるのでありがたいです。
チュートリアルでは途中でつまづくことものほぼありませんでした。
チュートリアルとしてはとても役に立ったと思います。

試験ガイドを見てみる

AWSソリューションアーキテクトの右側にある試験ガイドのダウンロードからダウンロードして試験の範囲などを確認しました。特に製品名が明示的に書かれているものは重点的勉強する必要があると感じました。

セキュリティの基本的な考え方を知る

セキュリティプロセスの概要を読んで共有責任モデルを始めとする考え方を知っておきます。ページ数が結構あります。

サンプル問題をやってみる

AWS 認定ソリューションアーキテクト – アソシエイトの右側にある[サンプル問題のダウンロード]からダウンロードして解いて見ました。最初には全然わかりませんでした…

AWSのドキュメントを見てみる

基本的には勉強といってもAWS ドキュメントをひたすら読むということが大半でした。あとはAWS用語集も割と目を通しました。
さらに、AWSクラウド活用資料集も必要そうな製品のものについては一通り目を通しました(その内リンク切れしそう…)。
また、よくある質問は確実に目を通しておいたほうが良いと思います(特にページの上の方にある質問。下の方はより細かい質問に対する解答になっている気がしてあまり読みませんでした)。
以下のドキュメントはだいたい目を通しましたが、応用っぽい使い方などについてはあまり深追いはしませんでした。

重要そうな項目や説明はEvernoteの方に残してマーカなどで印をつけていきました(上記だけでも結構な分量になりました)。
上記の製品をいかに組み合わせて問題を解決するのかといった視点が重要になるようです。

AWS Summit 2017に参加してみる

1日だけでしたが、入門系のセッションだけ参加してきた。過去の分も含めて後からPDFでも動画でも確認できるのは非常にありがたいです。モチベーションも上がりました。

AWS Summit Tokyo 2017 セッション資料・動画一覧

また参加していて思ったこととしては、最新情報はある程度キャッチアップしておく必要があると思いました。例えば、「既存のAmazon EC2インスタンスにIAM Roleがアタッチできるようになりました」のように、今までできていなかったものができるようになったのはよくチェックした方が良いと思います。

書籍関連

書籍としては以下のものを読んで、その内のいくつかは実際に書籍の従って動かしてみたりもしました。
たまたまKindle版が半額になっていたものやポイントが多かったりすることもあって購入もしています(運が良かった…)。
購入していないものは図書館で借りて2週間(図書館の期限が2週間なので)で隅から隅まで熟読してました。
実務経験がない以上、とにかく手を動かしていくことが重要だと考えました。

いろいろなリンクを回ってみる

クラスメソッドさんのブログは情報も早くてよく見てました。他にもAWS公式ブログやqiitaの記事なども目を通していました。特にQiitaのソリューションアーキテクトに合格した系の記事は大変参考に、かつモチベーションの維持に効果がありました!

Amazon Web Services ブログ
Developers.IO
Qiita -ソリューションアーキテクト-

また、以下のサイトでは実際に試験のような問題を解くことができます。有料なところもあるので、やるかどうかは自己判断になります。

AWS Web問題集で学習しよう

あまり深く勉強しないものを知っておく

「この製品はなんですか?」「この製品で何ができますか?」くらいの知識でも大丈夫そうなものについては、あまり深く勉強しないようにしました。例えばRedShiftやKinesis、OpsWorkなどがそれに当たるかと思います。
また、SDKやコマンドラインツールなどについては範囲外だと思うので一切見ていません。

模擬試験を受けてみる

1週間前くらいに模擬試験を受けてみた結果、70%でした。
模擬試験は本試験の半分くらいのボリュームでしたが、それでも結構難しく感じました。
マークをつけることで後から見直すことができるので、ちょっとでも不安な問題にはマークをつけて復習できるようにメモっておきました(試験が終わると問題文などに確認はできなくなります)。
この時点ではちょっとまだ本番に自信がなかったので、ここから本試験まではあまり手を動かさずに、ここまで勉強したことの復習をメインに、Evenoteを読み返したりドキュメントの再確認をやり始めました(この時点で書籍はほぼ読了済みの状態)。

実際の試験のときの心構え

試験は午前中にして朝早めにおきて、現地には早めにいき近くの喫茶店で最終確認をしておきました。
本試験では、とにかく問題文をしっかりと読むこと大切だと感じました。
また、ちょっと引っ掛けのようなものも混じっていたりして、うろ覚えではなくキチンと理解することが重要だと感じました。

最後までやってみたところで時間は結構余ったので、読み返しに結構時間が使えました。
採点結果としては実務経験がないこともあって、トラブルシューティングの得点はやはりよくありませんでした(なんと33%…)。
ここはやはり経験が生きるところなのだと思いますが、ドキュメントのトラブルシューティングをもっと読み込んでおけば良かったと後悔しています。

今後

趣味で終わらせるには勿体無い分野だと思うので、クラウド絡みのお仕事したいけど今のところじゃまずそんな仕事はないという…
でもプロフェッショナルもいずれはとってみたいので、やはり実務経験が欲しいところです。

続きを読む

DynamoDB は空の値を登録できないけど DynamoDB document client のコンストラクタにオプションを渡すだけで空の値を NULL 型には変換できるよ?

前書き

DynamoDB は空の値を登録しようとすると ValidationException が発生してしまいます。しかし、DynamoDB は NULL 型をサポートしており、DynamoDB document client には空の値を NULL 型に変換するためのオプトイン機能が存在します。

「んなこともう知ってるよハゲ!:anger:

という方は大丈夫なのですが、空の値を代入せずに済む仕様にしたり、空の値 (空文字) をスペースで登録したり、空の値を NULL 型に変換するためのオプトイン機能を自前で実装したり・・・という記事も存在します:scream:

※ オプトイン機能未実装時に投稿された記事も多々有りましたので仕方ありません。

DynamoDB を始める方がそういうものなのかと思ってしまわないようにここでご紹介したいと思います。

どうやるの?

Class: AWS.DynamoDB.DocumentClient — AWS SDK for JavaScript

Constructor Details

new AWS.DynamoDB.DocumentClient(options) ⇒ void

Creates a DynamoDB document client with a set of configuration options.

Options Hash (options):

  • params (map) — An optional map of parameters to bind to every request sent by this service object.
  • service (AWS.DynamoDB) — An optional pre-configured instance of the AWS.DynamoDB service object to use for requests. The object may bound parameters used by the document client.
  • convertEmptyValues (Boolean) — set to true if you would like the document client to convert empty values (0-length strings, binary buffers, and sets) to be converted to NULL types when persisting to DynamoDB.

convertEmptyValues オプションを true にして DynamoDB document client のコンストラクタに渡すだけです:sunglasses:

こちらの Issue により、

jeskew commented on 24 Jan

@brandonmbanks Converting empty values to null is now an opt-in feature of the document client; you can pass a boolean convertEmptyValues option to the document client constructor to have it do so. I’ll open a PR to improve the documentation of this feature.

空の値を null に変換することは document client のオプトイン機能になりました。convertEmptyValues (Boolean) オプションを document client のコンストラクタに渡すことで変換することができます。

こちらの Pull Request が作成され、マージされたことによりオプトイン機能が使えるようになりました:sparkles:

jeskew commented on 14 Dec 2016

This change causes the document client’s marshaller to render empty strings, buffers, and sets as {NULL: true} instead of {S: ''}, {B: new Buffer('')}, {SS: []}, respectively.

/cc @chrisradek

この変更により、document client の marshaller は、空の strings、buffers、および sets を {S: ''}{B: new Buffer('')}{SS: []} の代わりに {NULL: true} としてレンダリングします。

続きを読む

DynamoDBの「プロビジョニングされた容量」の引き下げ回数の上限緩和(9回まで)は条件付きだった

オートスケーリングと上限緩和

2017年6月にリリースされたDynamoDBの新機能「Auto Scaling for Amazon DynamoDB」は、ネットでも話題になりました。

新機能 – Auto Scaling for Amazon DynamoDBについて

このリリースに伴い、「プロビジョニングされた容量」の変更の上限が緩和されました。

今までは「プロビジョニングされた容量」を引き上げるのは無制限、引き下げについては「4回」の制限がありました。
今回変更があったのは「引き下げ」のほうで、「9回」まで引き下げが可能になりました。

上記のリンク先から引用します。

各テーブルまたはグローバルセカンダリインデックスごとに1日に最大9回まで容量を減らすことができます。

オートスケーリングによって「引き下げ」が必要となるシーンが多くなることを見越してでしょう。

条件があった

ただし、無条件で4回から9回に変更されたわけではないようです。

別のAWSのドキュメントには、下記のように書いてあります。

You can decrease the ReadCapacityUnits or WriteCapacityUnits settings for a table up to four times any time per day.

4回までいいですよ、と。あれ、少ないですね。

続きます。

Additionally, if there was no decrease in the past four hours, an additional dial down is allowed, effectively bringing maximum number of decreases in a day to nine times (4 decreases in the first 4 hours, and 1 decrease for each of the subsequent 4 hour windows in a day)

「4時間以内に引き下げられていなかった場合に、追加の引き下げが可能で、一日の最大引き下げ回数は9回」とあります。

単純に9回までOK、というわけではないのですね。

最後に

この制限を実際に試したわけではないのですが、以前何度か「引き下げ」を行っていたときに上限に達してエラーになってしまいました。「9回もやってない気がするなぁ」という感じだったのですが、こういう理由だったのかもしれないですね。

リリース当初、日本語の記事は読んでいたのですが、英語のドキュメントについては後回しにしていました。
ふと時間ができて読んでみると新しい発見がいくつかあったので、英語の記事も読まないとだなーというそんな話でした。

続きを読む

Alexaに日本語を喋ってもらう

背景

  • Pollyで文字から音声を生成できる
  • Alexa Voice Serviceを使うとmp3が再生できる

つまり日本語を話せるAlexaが作れるのでは…?:thinking:

概要

日本語で話しかける :arrow_right: 日本語で答えてくれる

題材として、「挨拶」と「天気予報を」を選んだ。
「おはよう」
「今日の天気を教えて」
「明日の天気は?」
などに応答してくれる。

こんな感じで会話できます。
(Echo持ってないのでAVSで動かしてます…:cry:)

https://youtu.be/6GKvik7QOXA

Alexa Skills Kit

まず、自分で作ったアプリを登録し、AlexaからLambda関数を読み出せるための設定が必要です。
これらの作業は https://developer.amazon.com/ で行う必要があるため、アカウントを取得して下さい。
また、アカウント作成の際に、日本の通販のAmazonで使っているものと異なるメールアドレスで登録するようお願いします。

Skillの作成

トップページ[ALEXA]タブ :arrow_right:[Add a New Skill]
よりスキルを作成できます。
スキル作成にあたって、設定が必要な箇所があるので簡単に説明します。
(本題とは逸れるのであまりしっかりは説明しません)

・Skill Information

[Name]

表示される名前です(なんでもいい)

[Invocation Name]

Alexaに呼びかけてこのスキルを起動する時の名前です
自分は “nihon go” と入力しているので、 “ask nihon go” でスキルが起動します。

・Interaction Model

Lambda関数と同じくらい大切なところ。

[Intent Schema]

自分の例は以下。
type に入っているものが単語の集合で、この下の [Custom Slot Types] で設定します。 name はその単語をLambdaに渡す際の名前をここで決めています。

{
  "intents": [
    {
      "slots": [
        {
          "name": "greetings",
          "type": "JP_Greetings"
        }
      ],
      "intent": "GreetingWords"
    },
    {
      "slots": [
        {
          "name": "dates",
          "type": "JP_Dates"
        }
      ],
      "intent": "Questions"
    },
    {
      "intent": "AMAZON.HelpIntent"
    }
  ]
}

[Custom Slot Types]

こんなかんじ。(てきとう)

スクリーンショット 2017-07-11 15.50.15.png

[Sample Utterances]

上で定義したインテント名とスロット名を使います。
発話を受け取り、どのタイプの会話であるかを判断します。
<インテント名> <会話内容>という書き方で、”{xxx}”となっているものは、上で設定したスロット名のスロットタイプに含まれる単語の何かしらを受け取れます。
以下書き方の例。

GreetingWords {greetings}
Questions {dates} no tenki o oshiete
Questions {dates} no tenki wa

・Configuration

こんなかんじでLamnda指定。(てきとう)

スクリーンショット 2017-07-11 15.55.45.png

構成

Alexa Voice Service から Alexa Skill Kit(lambda関数) を呼び出して処理しています。
全てus-east-1 (バージニア北部) のリソースを使っています。

先日Lexも公開されたのですが、mp3の再生が出来ないようなので保留。
(Lexのほうがlambda関数を短く書けて楽な上に、Slotなどの管理も手軽なので使いたかった…)

スクリーンショット 2017-06-08 17.14.44.png

処理の流れ

処理としては上の図の3つのLambda関数が軸になっているので、それぞれの関数に沿って説明します。

AVSlink.js

AVSと連携しているlambda関数で、AVSからのリクエストを受け付けています。
音声の入力に応じて、S3に保存されているmp3を再生しています。

▼Lamnda関数

https://github.com/marceline-git/AlexaFunctions/blob/master/AVSlink.js

説明が面倒なので省略しますが コードを見ればなんとなく動きがわかると思います。
一応説明しておくべき場所を以下にまとめました。
cheerio-httpcliを使っているため、npm install cheerio-httpcliしてzipで固めてLambdaにアップロードして下さい。
function onIntentでインテントの名前からどの関数を実行するかを決めています。
<バケット名>/output以下のフォルダに、発話させたいmp3ファイルを置いてます。
・常にshouldEndSession = falseなので、特に終了させる会話などは決めていません。
repromptTextは一定時間発話が無かった際に送られるものです。(聞いたことないけど)

getData.js

天気予報APIを叩いて、発話させたい日本語文をDynamoDBに格納。
Dynamoにはこんな感じでデータが入っています。idを主キーとして、値はunixtimeを入れています。

スクリーンショット 2017-07-11 16.54.41.png

発話するためのmp3を生成する際はここのデータを引っ張ってきています。
データが増えすぎないように、天気の情報を取得するたびに古いものは削除しています。

▼Lamnda関数

https://github.com/marceline-git/AlexaFunctions/blob/master/getData.js

ちょっとした説明。
asyncを使っているため、npm install asyncしてzipで固めてLambdaにアップロードして下さい。
・livedoorの天気APIを使っています。住所などは適当にいじって下さい。
・CloudWatch Eventsをトリガーに、定期実行しています。

convert.js

Pollyの生成するmp3ファイルは、フォーマットの問題で直接AVSから再生できません。
以下のフォーマットに変換する必要があります。

コーデック: mp3
チャンネル数: 2
サンプリング周波数: 16000kHz
ビットレート: 48kbps
* Converting Audio Files to an Alexa-Friendly Format

ドキュメントではffmpegで変換しているのですが、lambdaにffmpegを乗せるのは結構面倒で…。
(ffmpegとlibmp3ame入れつつ軽量化するのが大変)
EC2インスタンス建てっぱなしにするのもスマートじゃないので悩んでいたら、ElasticTranscoderというサービスで解決しそうなので採用。

▼ElasticTranscoderの使い方

Pipelineの作成

[Elastic Transcoder]→[Create New Pipeline]より作成。
PipelineIdは後ほどLambdaで使います。

スクリーンショット 2017-06-08 17.58.18.png

Presetの作成

[Elastic Transcoder]→[Create New Preset]より作成。
PresetIdは後ほどLambdaで使います。

スクリーンショット 2017-06-08 18.15.48.png

Jobの作成

こちらはマネジメントコンソールで作成する必要がありません。Lambda関数内で生成します。

▼Lamnda関数

https://github.com/marceline-git/AlexaFunctions/blob/master/convert.js

ちょっとした説明。
asyncを使っているため、npm install asyncしてzipで固めてLambdaにアップロードして下さい。
・DynamoDBから文章を取ってきて、発話して、一旦mp3を生成して、変換して、古いmp3削除して、Dynamoからも削除して、みたいな流れです。
・発話時にpollyでSampleRate: "16000"を指定しているため、変換時に上のPresetでは[auto]でもうまく動きます。
PipelineIdPresetIdは先程作成したものを利用して下さい。
・CloudWatch Eventsをトリガーに、定期実行しています。

S3のフォルダ構成 :arrow_down:

  • <バケット名>/tmp
    pollyにより発話されたmp3ファイル(変換前)が一旦保存されるフォルダ
  • <バケット名>/output
    tmpフォルダからmp3ファイルを引っ張ってきて、ElasticTranscoderにより変換されたmp3ファイルが保存されるフォルダ

お疲れ様でした :sweat:

一部だけをかいつまんで説明したのですが、ElasticTranscoderなどの説明で結構長くなってしまいました。
大体がaws-sdkを利用しているので、リファレンスを読めばなんとなくわかると思います。
* AWS SDK for JavaScript
わからないことがあれば、コメント欄に書いてもらえれば回答します(多分(暇な時))。

余談

前もって定期実行して発話すべきmp3ファイルを持っておくのはどうなのかと思い、Alexaに問い合わせがあった際にLambda内で動的生成を行うものも作成しました。
が、返事まで30秒近く時間がかかってしまいスマートさが欠片も感じられなかったので使うのはやめました。

続きを読む

AWSのLambdaとDynamoDBとAPIGatewayの連携

2017/7/8 現在の情報です。

作る順番

DynamoDB→Lambda→APIGatewayの順番に作っていきます。

下準備~IAMロールの作成~

IAMにロールの作成が必要です。
今回はLambdaからDynamoDBを呼び出させるので先に以下作業を行っておきます。

IAM > ロール > 新しいロールの作成

ロールタイプの選択画面が出るので、「AWS Lambda」を選択
ポリシーの設定画面が出るので

  • AmazonDynamoDBFullAccess
  • AWSLambdaDynamoDBExecutionRole

を選択します。
※他の記事を読むと、「AWSLambdaDynamoDBExecutionRole」だけで良いと書いてあったんですが
 なんかダメでした。

名前決めろって言われるので適当に「lambda-dynamodb-execution-role」とかにします。

で、ためらわず作成。

DynamoDBの設定

DynamoDBの画面よりテーブルの作成ボタンを押す
テーブル名、PKを決めろって言われるので適当に決める。
今回は適当にHogeテーブル、fugaカラム名にします。

しばらくしたら出来上がるので、項目タブ > 項目作成
ここでキー以外の項目を追加できます。piyoを追加しましょう。
追加する時についでに値も入れられるようです。

分かりにくいので図。

dynamo.png

左の方にあるプラスを押すと項目が増えていきます。
型を設定して、値を入れたら保存。するとpiyoカラムが追加されて、ついでに値も入ります。

はい、これでDynamoDBの設定は終わりです。簡単ですね。

Lambdaの設定

はい、次はLambdaです。

Lambdaの画面でためらわず、Lambda関数の作成を押します。

設計図の選択と言われるので、何言ってるか分からないからBlankFunctionを選びます。
トリガーの設定画面が出ますが、何かよく分からんので次へ。
はい、次の画面、大事です。

名前:fugafuga
説明:ぴよぴよ
ランタイム:Node.js 4.3
Lambda 関数のコード
 コードをインラインで編集 を選ぶ。

こんな感じのコードを書きましょう。

var AWS = require('aws-sdk');
var dynamo = new AWS.DynamoDB({
    region: 'ap-northeast-1'
});
exports.handler = function(event, context) {
    var params = {
        "TableName": "Hoge",
        "KeyConditionExpression":"fuga = :fugafuga",
        "ExpressionAttributeValues": {
            ":fugafuga" : {"S": event.fugafuga}
        }
    };
    console.log("event:", event);
    dynamo.query(params, function(err, data) {
        console.log("dynamo_data:", data);
        console.log("dynamo_err:", err);
        context.done(null, data);
    });

};

node.js読めなくても雰囲気でわかると思います。
Dynamoにつないで、Hogeテーブルのfugaテーブルがevent.fugafugaで取得した値と
同じものを取得しそうですね。

その下の方の「Lambda 関数ハンドラおよびロール」の設定。
ハンドラを「exports.handler」と入力して下さい。
そして、ついに出ました。ロールで最初に作った「lambda-dynamodb-execution-role」を選択します。

次へ、関数を作成をためらわずに押します。

Lambdaのテスト

テストしてみましょう。
関数作ったら上の方に嫌でも目につくテストボタン。押してみます。

謎の入力画面。パラメータを指定できそうなのでこんな感じで入力します。

{
  "fugafuga": "ThisisKey"
}

はい、やったね。上手くいきました。

{
  "Items": [
    {
      "fuga": {
        "S": "ThisisKey"
      },
      "piyo": {
        "S": "piyopiyopiyo"
      }
    }
  ],
  "Count": 1,
  "ScannedCount": 1
}

ぴよぴよ言ってますね。

APIGatewayの設定

はい、APIGatewayです。何のためらいもなく、APIの作成ボタンを押しましょう。

新しいAPIです。API名は適当に「piyopiyo」、説明も適当に「ぴよぴよ」と入力しましょう。
するとこんな画面になります。まずはメソッドを作りましょう。
このセレクトを選んで下さい。

apigateway.png

中身を取得したいのでGETメソッドを作りましょう。
すると何か聞かれるので「統合タイプ:Lambda関数」を選びます。せっかく作ったしね。
リージョンはap-northeast-1、Lambda関数はfugafugaでした。

保存ボタンを押すと、何か怖そうなダイアログが出ますが、怖くない、怖くない。
OKしましょう。

するとなんかイケてる画面になります。
この画面が、どこからどうAPIが呼び出されて、その後何を呼び出して、何を返すのか。
禅問答みたいですね。

apigateway2.PNG

パラメータの設定

APIたるものパラメータを受けて渡す必要があるでしょう。
まず受け方は、↑の図の「メソッドリクエスト」をクリックします。
すると色々設定デキそうですが今回は「URL クエリ文字列パラメータ」を設定します。
適当に「fuga」を設定します。これでfugaパラメータを受け取れるようになりました。

次に「統合リクエスト」をクリックします。
ここでは、APIGatewayからLambdaにパラメータを渡す設定をします。

本文マッピングテンプレートが隠れているので展開して
「リクエスト本文のパススルー」を「テンプレートが定義されていない場合 (推奨) 」を選択
その下の「マッピングテンプレートの追加」を押します。
するとContentTypeが設定できるので「application/json」を選択。
しかしひどいUI。

すると、その下に謎の入力箇所が現れるので、ここでやっと引き渡す値を設定できます。

{
    "fugafuga": "$input.params('fuga')"
}

はい、意味分からんですね。
$input.params(‘fuga’) は、APIGatewayが受ける変数です。?fuga=XXX みたいな感じです。
で、左の”fugafuga”は、Lambdaのeventが受け取る変数名になります。
うえーーの方のLambdaのプログラムでevent.fugafuga って書いてたアレに入ります。

最後。「メソッドレスポンス」を選択。
普通に返却すると日本語が化けるらしく、「本文マッピングテンプレート」にこれを設定します。
「application/json;charset=UTF-8」
もともとあった「application/json」は消します。

これで完成。

APIのデプロイから実行

はい、後はAPIをデプロイすれば使えます。
ここからデプロイを選びましょう。

apigateway3.PNG

デプロイされるステージ:新しいステージ
ステージ名:develop
説明:適当に。
ステージは本番、開発を分けるためのタグですね。
実際は何でも大丈夫です。

はい、デプロイするとURLが発行されます。
https://XXXXXX-api.ap-northeast-1.amazonaws.com/develop
みたいな感じになりますね。

これを押すと、nullが表示されると思います。寂しい。
なぜならパラメータが必要だから。
じゃあパラメータfugaを付けてみましょう。
https://XXXXXX-api.ap-northeast-1.amazonaws.com/develop?fuga=ThisisKey

{"Items":[{"fuga":{"S":"ThisisKey"},
"piyo":{"S":"piyopiyopiyo"}}],"Count":1,"ScannedCount":1}

やった!DynamoDBに入れた値が取れましたね。

はい、これで一連の作業はおしまいです。
結構調べながらやると、管理画面が過去の文言だったりフローが変わってたりと
罠が多かったので2017/7/8時点の情報でまとめました。
誰かの役に立つといいなー:sunny:

続きを読む

[AWS] Lambda + API Gatewayでサーバレスを始める 1

とりあえず、AWS Lambda と API Gateway でサクッとサーバレスをやってみます。
マネジメントコンソールから全部の実装・設定をやってしまいます。
AWS SAM とかは使ってないので、そのあたり(バージョン管理やCLIからのデプロイなど)は別の記事などで補完をお願いします。

ロールの作成

Lambda ファンクション作成時にロールを設定する必要があるので、あらかじめ IAM でロールを作成しておきます。

IAM > ロール > 新しいロールの作成

ロールタイプの選択

AWS Lambda を選択。
スクリーンショット 2017-07-03 9.31.23.png

ポリシーのアタッチ

ここで実行時に必要な権限をアタッチします(DynamoDBとかS3とかのアクセス権限)。
今回は特に何も必要ないので、AWSLambdaBasicExecutionRole をアタッチし、CloudWatch Logs へのアクセス権限のみ付与します。
チェックを入れて「次のステップへ」クリック。
スクリーンショット 2017-07-03 9.45.00.png

確認

ロール名を入力し(必要ならDescriptionを入力し)、ロールの作成をクリック。
スクリーンショット 2017-07-03 9.49.13.png

Lambda ファンクションの作成

先に API Gateway の設定をすることもできますが、ここでは先に Lambda を書きます。

Lambda > Lambda関数の作成 > Blank Function > (トリガーは設定せずに)次へ

関数の設定

関数名を入力し、ランタイムを選択します。
今回は Node.js 6.10 にします。
スクリーンショット 2017-07-03 9.51.41.png

先ほど作成したロールを選択して次へ。
スクリーンショット 2017-07-03 10.07.41.png

確認

内容確認して、「関数の作成」クリック。
スクリーンショット 2017-07-03 10.09.54.png

コードの変更

ひとまずJSONを返却するようにします。

exports.handler = (event, context, callback) => {
    callback(null, {"name": "hoge"});
};

API Gateway の設定

API Gateway > APIの作成

API名を入力して、「APIの作成」クリック。
スクリーンショット 2017-07-03 14.04.15.png

リソース作成

リソースのアクションから、メソッドの作成を選択。
スクリーンショット 2017-07-03 14.07.43.png

GETを選択肢、チェックマークをクリックして確定。
セットアップ画面になるので、Lambdaを作成したリージョン※1とLambdaファンクション名※2を入力して保存。
※1 : コンソールの上部で表示されているリージョンで作成されています。東京なら「ap-northeast-1」を選択。
※2 : 最初の文字を入力すれば、候補が表示されます。
スクリーンショット 2017-07-03 14.10.22.png

リソースが作成されたので、ひとまずテストを実行してみます。
スクリーンショット 2017-07-03 14.16.49.png

「テスト」ボタンをクリックすると、成功していることがわかります。
ステータスが200で、JSONが返ってきていることを確認してください。
スクリーンショット 2017-07-03 14.18.15.png

APIのデプロイ

リソースのアクションから「APIにデプロイ」を選択。
スクリーンショット 2017-07-03 14.44.41.png

「新しいステージ」を選択し、ステージ名を入力。
ここでは「prod」と入力。
スクリーンショット 2017-07-03 14.45.51.png

デプロイすると、APIエンドポイントが作成されます。
スクリーンショット 2017-07-03 14.48.14.png

CORSの設定

CORSの設定は、 API Gateway リソースのアクションから設定できます。
スクリーンショット 2017-07-03 14.54.42.png

設定を変更したあとは、再度APIをデプロイする必要があります。

パスパラメータ

「/prod/:id」ってしたい時の設定方法。

リソース > アクション > リソースの作成
スクリーンショット 2017-07-03 15.15.36.png

新たに子リソースを追加します。
その際、リソースパスの文字列を{}で括ります。
スクリーンショット 2017-07-03 15.17.37.png

リソース作成後、{id}にGETメソッドを作成します。
Lambdaの設定をします。
スクリーンショット 2017-07-03 15.23.44.png

APIのテスト実行画面で、パスが入力可能になっていることを確認します。
スクリーンショット 2017-07-03 15.25.03.png

Lambdaからパラメータを取得できるように設定します。
統合リクエストをクリック。
スクリーンショット 2017-07-03 15.31.25.png

本文マッピングテンプレート > マッピングテンプレートの追加
「application/json」と入力してチェックマーククリックで確定。
スクリーンショット 2017-07-03 15.32.54.png

以下のように、マッピングを記述します。
スクリーンショット 2017-07-03 15.36.18.png

{
  "id": "$input.params('id')"
}

Lambdaでパラメータを取得できるようになったので、以下のように変更します。
パラメータはeventから取得できます。

exports.handler = (event, context, callback) => {
    callback(null, {"id": event.id, "name": "hoge"});
};

テストを実行し、入力値が返ってきていることを確認してください。
スクリーンショット 2017-07-03 15.42.11.png

クエリパラメータ

「/prod?name=:name」ってしたい時の設定方法。

メソッドリクエストから設定します。
スクリーンショット 2017-07-03 15.45.10.png

URL クエリ文字列パラメータに「name」を入力し、チェックマークで確定します。
スクリーンショット 2017-07-03 15.46.17.png

パスパラメータの時と同様、統合リクエストからマッピングテンプレートを設定します。
スクリーンショット 2017-07-03 15.48.54.png

Lambdaを変更します。

exports.handler = (event, context, callback) => {
    callback(null, {"id": event.id, "name": event.name});
};

テストで、クエリ文字列が入力可能になっています。
入力値が返ってきていることを確認してください。
※idはクエリ文字列で入力できないのでundefinedになり、nameだけがJSONで返却されます。
スクリーンショット 2017-07-03 15.57.57.png

文字化けするときは

日本語が文字化けする場合、メソッドレスポンスで文字コードを設定します。
スクリーンショット 2017-07-03 16.02.49.png

200のレスポンス本文の「application/json」を編集(ペンマークをクリック)します。
スクリーンショット 2017-07-03 16.05.20.png

「;charset=UTF-8」を追記し、保存します。
スクリーンショット 2017-07-03 16.07.20.png

その2に続く→

続きを読む