Web ページから、AWS Lambda の関数に引数付きでアクセスする

次の AWS Lambda 関数に、API Geteway 経由でアクセスする方法です。
これは、3つの整数、aa,bb,cc を与えて合計 sum を計算するプログラムです。

sum_up.py
# -*- coding: utf-8 -*-
#
#   sum_up.py
#
#                   Oct/20/2017
#
# --------------------------------------------------------------------
import sys
import json

# --------------------------------------------------------------------
def sum_up_handler(event, context):
    sys.stderr.write("*** sum_up_handler *** start ***\n")
#
    aa = 0
    bb = 0
    cc = 0
#
    sys.stderr.write("Received event: " + json.dumps(event, indent=2) + "\n")
    if ('aa' in event):
        sys.stderr.write("aa = " + event['aa'] + "\n")
        aa = int(event['aa'])
#
    if ('bb' in event):
        sys.stderr.write("bb = " + event['bb'] + "\n")
        bb = int(event['bb'])
#
    if ('cc' in event):
        sys.stderr.write("cc = " + event['cc'] + "\n")
        cc = int(event['cc'])
#
    version = "Oct/19/2017 AM 16:42"
#
    sum = aa + bb + cc
#
    sys.stderr.write("version = " + version + "\n")
    sys.stderr.write("sum = " + str(sum) + "\n")
    sys.stderr.write("*** sum_up_handler *** end ***\n")
#
    rvalue = {}
    rvalue['aa'] = aa
    rvalue['bb'] = bb
    rvalue['cc'] = cc
    rvalue['sum'] = sum
    rvalue['version'] = version
    rvalue['language'] = 'Python'
#
    return rvalue
#
# --------------------------------------------------------------------

1) これを、AWS Lambda の関数にする方法は、

python の関数を AWS Lambda で使用する

2) API Gateway の設定は、

API Gateway から Lamda にアクセスする

3) API Gateway で cors を有効にします。

cors_oct1901.png

4) Web ページとスクリプトです。jQuery を使います。

cors.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache" />
<meta http-equiv="CONTENT-TYPE" content="text/html; charset=utf-8" />
<script src="jquery-3.2.1.min.js"></script>
<script src="cors.js"></script>
<title>AWS API Gateway</title>
</head>
<body>

<div id="answer">answer</div>


<hr />
<div id="outarea_aa">outarea_aa</div>
<div id="outarea_bb">outarea_bb</div>
<div id="outarea_cc">outarea_cc</div>
<div id="outarea_dd">outarea_dd</div>
<div id="outarea_ee">outarea_ee</div>
<div id="outarea_ff">outarea_ff</div>
<div id="outarea_gg">outarea_gg</div>
<div id="outarea_hh">outarea_hh</div>


<hr />
Version: Oct/19/2017 PM 15:19<br />

</body>
</html>

次のプログラムの rest_api_id は、変更して下さい。

cors.js
// -----------------------------------------------------------------------
//
//  cors.js
//
//                      Oct/20/2017
//
// -----------------------------------------------------------------------
jQuery (function ()
{
    jQuery("#outarea_aa").text ("*** cors.js *** start *** PM 18:28")

    getPost()

    jQuery("#outarea_hh").text ("*** cors.js *** end ***")
})

// -----------------------------------------------------------------------
function getPost()
{
    const rest_api_id="bbc2fbufm6"

    const url="https://" + rest_api_id + ".execute-api.ap-northeast-1.amazonaws.com/test"

    const params = {'aa': '121','bb': '304','cc': '256'}

    const args = JSON.stringify(params)

    jQuery.post(url,args,function(res)
        {
        const sum = res.sum
        var str_out = ""
        str_out += "aa = " + params['aa'] + "<br />"
        str_out += "bb = " + params['bb'] + "<br />"
        str_out += "cc = " + params['cc'] + "<br />"
        str_out += "sum = " + sum + "<br />"
        jQuery("#answer").html (str_out)
        })
}

// -----------------------------------------------------------------------

続きを読む

python の関数を AWS Lambda で使用する

次の python の関数を、aws の Lambda にアップロードします。

evening_function.py
# -*- coding: utf-8 -*-
#
#   evening_function.py
#
#                   Oct/18/2017
#
# --------------------------------------------------------------------
import sys
import json

# --------------------------------------------------------------------
def evening_handler(event, context):
    sys.stderr.write("*** evening_handler *** start ***\n")
    print("Received event: " + json.dumps(event, indent=2))
    print("name = " + event['name'])
    print("AAA value1 = " + event['key1'])
    print("BBB value2 = " + event['key2'])
    print("CCC value3 = " + event['key3'])
    print("DDD value4 = " + event['key4'])
    print("EEE value5 = " + event['key5'])
    sys.stderr.write("*** evening_handler *** end ***\n")
#
    return event['name']  # Echo back the first key value
    #raise Exception('Something went wrong')
# --------------------------------------------------------------------

1) zip で、固めます。

zip -r evening.zip evening_function.py

2) aws にアップロードします。
 morning_exec_role というロールが作られているとします。 ロールの作り方は、
aws cli でラムダを使う

function_create.sh
aws lambda create-function \
    --function-name evening_function \
    --runtime python3.6 \
    --role arn:aws:iam::178234159025:role/morning_exec_role \
    --handler evening_function.evening_handler \
    --zip-file fileb://evening.zip \
    --region ap-northeast-1

3) CLIからイベントを発火

cli_exec.sh
aws lambda invoke --invocation-type Event \
    --function-name evening_function \
    --region ap-northeast-1 \
    --payload  '{"name": "島崎藤村","key1":" こんにちは", "key2":"おはよう", "key3":"さようなら","key4": "Hello","key5": "Bonjour"}' \
    outputfile.txt

4) CloudWatchLogsにログが出力されているか確認

続きを読む

[AWS][Python3]SESでIAM認証情報を変換してSMTP 認証情報を取得する

SESをSMTPを利用して送信したい

SESを利用するならばboto3を利用するのが一番簡単かと思いますが,
ローカル開発環境ではMailHog等の開発用SMTPサーバを用いて動作確認を行なっている環境で
汎用的なsmtplibをテスト・本番で利用したいというのがそもそものはじまり

SMTPを利用するには

通常のsmtp同様以下のような形で行えます

email.py
def send_mail(self, host, user, password, msg):
  smtp_con = smtplib.SMTP_SSL(host=host)
  smtp_con.ehlo()
  smtp_con.login(user, password)
  result = smtp_con.send_message(msg=msg)
  smtp_con.close()

が、ここで渡すpasswordが曲者

以下のリンクを見るとわかりますが
通常boto3などのAPI経由であれば、パスワードにはIAMのAWS_SECRET_ACCESS_KEYを用いるが
SMTP認証情報にはAWS_SECRET_ACCESS_KEY を Amazon SES SMTPパスワードに変換したものを利用する必要があります
Amazon SES SMTP 認証情報の取得

新たにSMTP認証情報を持つIAMユーザを作る方法もありますが
今回はAWS_SECRET_ACCESS_KEY->SMTPCredentialsの変換を行います

Amazon SES SMTP Credentialsを python3 で生成する

実際に利用したコードがこちら
keyにAWS_SECRET_ACCESS_KEYを渡してあげればSMTP認証に利用できるパスワードが返却される

aws_ses_hash.py
def hash_smtp_pass_from_secret_key(self, key):
    message = "SendRawEmail"
    sig_bytes = bytearray(b'x02')

    h = hmac.new(key.encode(), message.encode(), digestmod=hashlib.sha256)
    digest = h.hexdigest()

    sig_bytes.extend(bytearray.fromhex(digest))

    return base64.b64encode(sig_bytes).decode()

ちなみにpython2であれば以下が参考になります
■[tips][aws][python][ruby] Amazon SES SMTP Credentialsをpythonやrubyで作ってみる

続きを読む

Serverless FrameworkでAWS Lamda関数を作成する

概要

Serverless Frameworkとは、Lambda、API Gateway、DynamoDBなどを作成、管理、デプロイできるツールです。
Frameworkと付いていますが、ツールです。
この記事では、python3でLambda関数を作成します。

環境

  • CentOS7.2
  • serverless 1.23.0
  • node v6.11.3
  • npm 3.10.10
  • OpenSSL 1.0.2k-fips 26 Jan 2017

npmのインストール

以下の記事を参照
npmのインストール手順

Serverless Frameworkのインストール

slsというディレクトリを作成し、そこで作業を行います。

$ mkdir sls
$ cd sls
$ npm init
$ npm install --save serverless

serverlessコマンドのパスを通します

$ npm bin serverless
$ echo 'export PATH="$HOME/sls/node_modules/.bin/:$PATH"' >> ~/.bash_profile
$ source ~/.bash_profile

インストール確認

$ serverless -v
1.23.0

aws credential登録

以下のコマンドで、AWSのキーを登録します。

$ serverless config credentials --provider aws --key XXXXXXXXXXXXEXAMPLE --secret XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEXAMPLEKEY
Serverless: Setting up AWS...
Serverless: Saving your AWS profile in "~/.aws/credentials"...
Serverless: Success! Your AWS access keys were stored under the "default" profile.

Lambda関数の作成

以下のコマンドで、Lambda関数を作成します。

$ serverless create -t aws-python3 -p sample-app

Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/home/vagrant/sls/sample-app"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  ___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.23.0
 -------'

Serverless: Successfully generated boilerplate for template: "aws-python3"

オプションの説明ですが、
-pは、Lambda関数名のprefixとなります。

また、-tで実装する言語を選びます。
以下のいずれかを選択します。

  • -t

    • aws-nodejs
    • aws-python
    • aws-python3
    • aws-java-maven
    • aws-java-gradle
    • aws-scala-sbt
    • aws-csharp
    • openwhisk-nodejs

すると、以下のファイルが生成されます。
handler.pyは、Lambda関数のテンプレート、serverless.ymlは設定ファイルになります。

$ ll sample-app/
total 8
-rw-rw-r--. 1 vagrant vagrant  497 Oct 10 04:42 handler.py
-rw-rw-r--. 1 vagrant vagrant 2758 Oct 10 04:42 serverless.yml

関数の情報を設定

serverless.ymlに関数の設定情報が書かれているので、環境合わせて編集します。

serverless.yml
provider:
  name: aws
  runtime: python3.6

# you can overwrite defaults here
- #  stage: dev
-#  region: us-east-1
+  stage: production
+  region: ap-northeast-1

# *snip*

# 関数名などの定義
functions:
-  hello:
-    handler: handler.hello
+  sample-func:
+    handler: handler.main

serverless.ymlで、関数のメソッド名を変更したので、handler.pyも以下のように編集します。

handler.py
-def hello(event, context):
+def main(event, context):

deploy

以下のコマンドでデプロイをします。

$ cd sample-app
$ serverless deploy

Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (389 B)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
...............
Serverless: Stack update finished...
Service Information
service: sample-app
stage: production
region: ap-northeast-1
stack: sample-app-production
api keys:
  None
endpoints:
  None
functions:
  sample-func: sample-app-production-sample-func

すると、sample-app-production-sample-funcという名前のLambda関数が作成されます。

Lambda関数の実行

deployが出来たら、以下のコマンドで、Lambda関数を実行することができます。

$ serverless invoke -f sample-func

{
    "statusCode": 200,
    "body": "{"message": "Go Serverless v1.0! Your function executed successfully!", "input": {}}"
}

パラメータを付ける場合は、-dオプションで指定します。
戻り値にinputの項目が増えているのが確認できます。

$ serverless invoke -f sample-func -d '{"key":"value"}'

{
    "statusCode": 200,
    "body": "{"message": "Go Serverless v1.0! Your function executed successfully!", "input": {"key": "value"}}"
}

以下のようなjsonファイルを作成して、パラメータを渡すこともできます。

event.json
{
  "key" : "value"
}

-pオプションでjsonファイルを指定して実行

$ serverless invoke -f sample-func -p event.json
{
    "statusCode": 200,
    "body": "{"message": "Go Serverless v1.0! Your function executed successfully!", "input": {"key": "value"}}"
}

Lambda関数の削除

関数の削除は以下のコマンドで行います。
関連するS3のファイルもすべて消してくれます。
AWS Console上で手動でLambda関数を削除すると、S3のファイルなどが残ってしまいます。

関数のあるディレクトリに移動でして実行します。

$ cd sample-function
$ serverless remove -v

-sオプションで、特定のstageのみを削除することもできます。

$ serverless remove -v -s dev

その他

以下のモジュールで、擬似的にローカルでApi Gateway、DynamoDBを使うことができます。

$ npm install aws-sdk
# 擬似的Api Gateway
$ npm install --save-dev serverless-offline
# 擬似的DynamoDB
$ npm install --save-dev serverless-dynamodb-local

参考

続きを読む

AWSでServerlessの環境をCIするための選択肢を調べたメモ

利用するサービスはAWSに限定するとした場合に開発・テスト・デプロイを継続するための選択肢をいくつか調べてみました。

開発したいものは、「API Gateway – Lambda – DynamoDB」で構成されるWebサービスとします。

正直なところ、対象像を少し絞らないと選択肢が多すぎて好みの問題になりそうなので・・・

注意

調査用のメモです。

実際に全ての選択肢で開発をしてみたわけではなく、入門記事やドキュメントを少しみた程度で判断しています。

そのため正確性に欠ける内容となっている可能性が高いことをご了承ください。

共通点

開発ツールで対応していない部分についてのデプロイについては、AWS CLIで対応していくことになるでしょう。

LocalStack

モックサービスの対応数にまず驚いた。

https://github.com/localstack/localstack

LocalStack はローカルでAWSのサービスのモックを動かしてしまうというツールです。
DockerコンテナでAWS各種サービスを一気に立ち上げることができるので、ローカル環境で開発を完結させることが出来ます。

これ自体にはAWSを管理する機能はなく、Lambdaをローカル環境で開発してテストするときに、ローカルで同時にAPI Gateway + DynamoDBを動かしたいという場合に必要となりそうです。
DynamoDB自身はAmazonからDynamoDB Localが提供されているので、どちらを使うかは検証が必要でしょう。

起動コマンドも簡単で、一発で全て立ち上げることが出来ます。

docker-compose up

macの場合は、$TMPDIRにシンボリックリンクがある場合、少しコマンドを変える必要があるようです。

TMPDIR=/private$TMPDIR docker-compose up

DynamoDB Local

ローカル開発用のDynamoDB。
ほとんどのLambda開発ツールがAPI Gatewayもサポートしていることから、DynamoDBを接続するローカル環境ではLocalStackよりはこちらを使用することになるのではないかと。公式提供でもあるし。

ダウンロードとインストールガイドはこちら

aws-sam-local

SAM は Serverless Application Modelのことで、aws-sam-localはAmazon公式がサポートしているローカル開発環境です。今はまだbetaですが、近いうちに良くなりそうです。

https://github.com/awslabs/aws-sam-local

AWS Blogで利用例が紹介されていて、DynamoDB Localを使う場合についても少し触れられています。
https://aws.amazon.com/jp/blogs/news/new-aws-sam-local-beta-build-and-test-serverless-applications-locally/

作成したLambda FunctionをローカルDockerコンテナで実行したり、現時点ではPythonとNode.jsはデバッグ出来るようです。(自分で試したところ、上手くいかなかった)

また、Lambda関数を単純に実行するだけではなく、ローカルのAPI Gatewayを通して実行を確認できます。

PostManで簡単にAPIの動作検証が行えたり、実際のHTTPアクセスの形でLambda関数の検証がローカルで行えます。
また、実行時間やメモリ消費量が表示されるため、AWSにデプロイする前に関数の効率化が出来ます。

Serverless Framework

現状で一番正解に近いと思います。

https://github.com/serverless/serverless

こちらのQitta記事こっちのQiita記事が参考になりそうです。
DynamoDB Localと連携するためのプラグインが存在し、serverless.ymlファイルにAWS上での構成を記載していき、そのままAWSにデプロイ出来るようです。

Apex

Go言語でLambdaが書ける!!!

純粋にLambdaの開発だけで見ればとても良さそうです。
ただし、ローカル実行はサポートしておらず、AWSリソースの操作もLambdaに限定されています。
その辺りは、Terraformで補う考えのようです。

公式

https://github.com/apex/apex

chalice

Python用のマイクロサービスフレームワークです。

https://github.com/aws/chalice

次の様なコードで、APIを定義できます。

chalice
@app.route('/resource/{value}', methods=['PUT'])
def put_test(value):
    return {"value": value}

デプロイでAPI Gateway, Lambdaに反映されるため、コードの見通しが良くなりそうです。
IAM Rolesなどは自動生成される様なので、とにかく簡単にコードを書いて、すぐデプロイということが出来そうです。

インストールからコード記述、デプロイまでの操作がHelloworldならこんなに簡単に出来るようです。

$ pip install chalice
$ chalice new-project helloworld && cd helloworld
$ cat app.py

from chalice import Chalice

app = Chalice(app_name="helloworld")

@app.route("/")
def index():
    return {"hello": "world"}

$ chalice deploy
...
https://endpoint/dev

$ curl https://endpoint/api
{"hello": "world"}

Zappa

こちらも、とにかく簡単にPythonで作成した関数をAWSにデプロイ出来ることがウリのようです。

https://github.com/Miserlou/Zappa

gifアニメーションで実演が付いています。(README.mdから引用しました)
README.md

Zappa Demo Gif

REST APIを作成する以外にも、AWS Eventsに対応するものや、Asynchronous Task Executionといって、別のLamdba関数を非同期に呼び出すことが出来る機能を持っているようです。
というか、chaliceに比べると非常に多彩。

また、ZappaはWSGI(Web Server Gateway Interface)をサポートしているので、他のアプリケーションサーバーにデプロイすることも可能です。

chalice vs Zappa

こちらに比較記事がありました。

続きを読む

Djangoの既存プロジェクトをec2にデプロイ

概要

詰まりまくったのでメモ。Dockerで開発していたのでそのままデプロイしたろ!と思っていたが意味わからなすぎて断念。(おそらく?)一般的な方法でデプロイした。もっと良い方法があれば教えて欲しい限りです。

環境

OS: Amazon Linux AMI release 2017.09
ローカル: Docker
Python: 3.6.2
Django: 1.11.5
Gunicorn: 19.7.1
Nginx: 1.12.1

AWSの設定

インスタンスの作成

AWSの設定はAmazon Web Services 基礎からのネットワーク&サーバー構築を参考にした。

AWSに登録してコンソール > EC2からインスタンスを作成する。全部デフォルト。t2microなら無料。
VPC、サブネット、ルートテーブル、ゲートウェイ、セキュリティグループやらが作成される(はず)。なかったら作ってVPCに紐付ける。sshキーをダウンロードまたは登録しておく。

ポートの開放

EC2 > セキュリティグループからポートが開放できる。セキュリティグループを選択 -> インバウンド -> 編集 -> ルールの追加で80番ポート、8000番ポート(確認用、あとで閉じる)を開く。タイプはカスタムTCP、ソースは0.0.0.0/0で良い。
ここで EC2 > インスタンス から作ったインスタンスの詳細が確認できる。右側のパブリックDNSでドメイン、IPv4パブリックIPでIPが確認できる。

nginxのインストール

AWSにsshでログイン。キーペアをダウンロードした場合は~/.sshに置いて別のキー名を指定する。

# ssh -i ~/.ssh/id_rsa ec2-user@(インスタンスのIP)

以下ではrootで作業する。

$ sudo su -

以下、EC2にNginx + Gunicorn + SupervisorでDjangoアプリケーションをデプロイするを参考にnginxをインストール。

nginx入れる。

$ yum install nginx

nginxを起動する。

$ nginx

nginxの自動起動設定

$ chkconfig --add nginx
$ chkconfig nginx on

自動起動設定できているか確認する。

$ chkconfig | grep nginx
nginx           0:off   1:off   2:on    3:on    4:on    5:on    6:off

また、http://(パブリックDNS)を確認してnginxが起動しているか確認する。

Djangoプロジェクトの起動

AWSにプロジェクトを送る。

scpでzipで固めたDjangoプロジェクトを送る。また送る前にrequirements.txtは用意しておく。

# pip freeze > requirements.txt
# scp -i ~/.ssh/id_rsa ~/path/to/project.zip ec2-user@yourIP:home/ec2-user/

送ったものがhome/ec2-userに落ちているはず。解凍する

$ unzip project.zip

ほんとはgitで落とせばいいんだろうけどプライベートリポジトリにしてるので新しいuser登録して新しくssh登録してってしないといけないのかな。誰か教えてください。

pythonとかを入れる

色々考えるのがめんどくさかったのでEC2サーバにPython3環境構築を参考にした。

gitとpyenv入れる。-yが全部yesって答えるオプションらしい。初めて知った。

$ yum install git -y
$ git clone https://github.com/yyuu/pyenv.git ~/.pyenv

path通す。

$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile

コマンド通るか確認。

$ pyenv -V

依存関係を入れる。

$ sudo yum install gcc zlib-devel bzip2 bzip2-devel readline readline-devel sqlite sqlite-devel openssl openssl-devel -y

本体を入れる。

$ pyenv install 3.6.2

pythonを切り替える。これはrootのpythonなので、sudo su -後でないとデフォルトのpythonに戻ってしまうので注意。

$ pyenv global 3.6.2
$ pyenv rehash
$ python --version
Python 3.6.2

Django、その他諸々のプロジェクトに必要なライブラリをインストールする。

$ pip install --upgrade -r project/requirements.txt

requirements.txtにGunicornが入ってなければGunicornを入れる。
Gunicornとはwsgiサーバーのことで、nginxとDjangoを繋ぐものみたいなイメージ。

$ pip install gunicorn

manage.pyの上でDjangoを起動させる。

$ gunicorn your_project.wsgi --bind=0.0.0.0:8000

http://(パブリックDNS):8000を確認すると、ALLOWED_HOSTSに追加してね!と出るので追加する。

/your_project/settings.py
# 中略
ALLOWED_HOSTS = ['(パブリックDNS)']
# 以下略

もう一回確認してプロジェクトが見えれば成功。

Nginxの設定の変更

再びEC2にNginx + Gunicorn + SupervisorでDjangoアプリケーションをデプロイするを丸パクリ。本当にありがとうございました
/etc/nginx.confとあるが、Amazon Linuxでは/etc/nginx/nginx.confにあった。
以下引用

/etc/nginx.confを以下の通り編集する

/etc/nginx.conf
〜中略〜

http {
   〜中略〜

   upstream app_server {
       server 127.0.0.1:8000 fail_timeout=0;
    }

   server {
        #以下4行はコメントアウト
        #listen       80 default_server;
        #listen       [::]:80 default_server;
        #server_name  localhost;
        #root         /usr/share/nginx/html;

       # 以下3行を追加
        listen    80;
        server_name     IPアドレス or ドメイン;
        client_max_body_size    4G;

       # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

       location / {
            # 以下4行を追加
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_redirect off;
            proxy_pass   http://app_server;
        }

   〜以下略〜

nginxの再起動

$ service nginx restart
Stopping nginx:                                            [  OK  ]
Starting nginx:                                            [  OK  ]

Djangoを再びGunicornで立ち上げる

$ gunicorn your_project.wsgi --bind=0.0.0.0:8000

http://(パブリックDNS)で確認できたら成功。

daemon化

今はコマンドからGunicornを起動しているだけなので、ログアウトしたら終了してしまう。そこでGunicornをデーモン化して常駐するようにしたい。
ただ色々な所を見ると、Surpervisorというツールでそれが可能なようだが、SurpervisorはPython3系に対応していないので2系の環境をもう一つ作れとのこと。んなアホな。
もうちょっと調べてみると、普通に-Dというオプションをつけるだけでデーモンとして起動できるらしい。

起動

$ gunicorn your_project.wsgi --bind=0.0.0.0:8000 -D

終了するときは

$ ps -ef | grep gunicorn
root     17419     1  0 Oct07 ?        00:00:08 /root/.pyenv/versions/3.6.2/bin/python3.6 /root/.pyenv/versions/3.6.2/bin/gunicorn your_project.wsgi --bind=0.0.0.0:8000 -D
root     17422 17419  0 Oct07 ?        00:00:01 /root/.pyenv/versions/3.6.2/bin/python3.6 /root/.pyenv/versions/3.6.2/bin/gunicorn your_project.wsgi --bind=0.0.0.0:8000 -D
root     21686 21594  0 08:05 pts/0    00:00:00 grep --color=auto gunicorn
$ kill 17419

スクリプトで起動

ただこのままだと毎回めんどくさいので、シェルスクリプトでサーバーを起動、停止するにあるスクリプトを使わせてもらう。

Flaskとかで作ったちょっとしたサーバーの起動/停止用のシェルスクリプト。
gunicornでデーモン状態にしている。
startで起動、stopで終了、restartでstop+start。

your_project.sh
#!/bin/sh
PROGNAME=`basename $0`
BASEDIR=`dirname $0`
PIDFILE=$BASEDIR/$PROGNAME.pid

start() {
  echo "Starting server..."
  cd $BASEDIR
  gunicorn flaskhello:app -p $PIDFILE -D
}

stop() {
  echo "Stopping server..."
  kill -TERM `cat $PIDFILE`
  rm -f $PIDFILE
}

usage() {
  echo "usage: $PROGNAME start|stop|restart"
}

if [ $# -lt 1 ];  then
  usage
  exit 255
fi

case $1 in
  start)
    start
    ;;
  stop)
    stop
    ;;
  restart)
    stop
    start
    ;;
esac

以下のgunicorn flaskhello:app -p $PIDFILE -D
gunicorn your_project.wsgi --bind=0.0.0.0:8000 -Dに書き換える。

$ sh your_project.sh start   #起動
$ sh your_project.sh stop    #終了
$ sh your_project.sh restart #再起動

sshを切ってもhttp://(パブリックDNS)が見えたら成功。ひとまず見えるようになった。

staticファイルが見つからない

ただここまで追ってもcss,jsなどstaticファイルは見つかってないはず。以下で見えるようにする。
setting.pyのSTATIC_URL、STATIC_PATHは設定済みでローカルでは見える状態とする。
詳しい解説 -> Django での static files の扱い方まとめ

staticファイルのコピー

manage.pyの上でstaticファイルを指定したディレクトリにコピー

$ python manage.py collectstatic

nginx.confをいじる

nginx.confにstaticファイルのディレクトリを登録する。

/etc/nginx/nginx.conf
server{
    # 〜中略〜
    location /static {
         alias /home/ec2-user/your_project/static;
         #settings.pyで設定したのと同じ場所を記述
    }
    # 〜以下略〜
}

パーミッション変更

ここでnginxとgunicornを再起動する。

$ sh your_project.sh stop
Stopping server...
$ service nginx restart
Stopping nginx:                                            [  OK  ]
Starting nginx:                                            [  OK  ]
$ sh your_project.sh start
Starting server...

するととpermission deniedが出た。これはec2-userにotherからの権限がないせいだった。
参考によると、このディレクトリに移動する権限が必要らしい。
/home/でec2-userに権限を与える。

$ ls -l
drwx------  5 ec2-user ec2-user 4096 Oct  6 04:19 ec2-user
$ chmod o+x ec2-user
$ ls -l
drwx-----x  5 ec2-user ec2-user 4096 Oct  6 04:19 ec2-user

gunicornを再起動すると無事にstaticファイルにアクセスできて終了。

参考

EC2にNginx + Gunicorn + SupervisorでDjangoアプリケーションをデプロイする
EC2サーバにPython3環境構築
シェルスクリプトでサーバーを起動、停止する
Django での static files の扱い方まとめ
Nginxでつくる、どシンプルな静的コンテンツサーバ

続きを読む

AWS GreengrassでLチカ:クラウドとエッジについて考える(2)

前回記事(AWS GreengrassでLチカ:クラウドとエッジについて考える(1))の続きになります.
取り組む課題や環境,クラウドタイプのやり方についてはそちらを参照ください.

2. AWS Greengrassにてネットワーク型信号機を実現する

本記事で取り組む課題は”ネットワーク型信号機の設計”ですが,クラウドの考え方を素直に適用した場合,いろいろと問題が発生しそうなことについては前回記事にて述べました.今回はそれらの問題を解消すべく,エッジの考え方を導入したネットワーク型信号機をAWS Greengrassを使って実現します.

今回もできるだけ単純なモデルとするため,構成は以下としました.

p2.png

大きな違いはメッセージの発行元です.

  • クラウドタイプ : インターネットをはさんだサーバー上で稼働するLambda関数がメッセージ発行元
  • エッジタイプ : RasPi3自身(正確にはRasPi3上で動作するLambda関数)がメッセージ発行元

構成を見る限り自立して稼働しそうな気がします…なんだかエッジぽいです.
早速作成してみます.

信号機側にデプロイされるLambda関数

まずはこれを作成することにします.作成はAWS Lambdaのコンソール上で実施しました.AWS Lambdaのコンソールから [関数の作成]選択,”Greengrass”で検索すると現状では2件ヒットします.うちPython版の”greengrass-hello-world”をテンプレートにして作業を進めます.
今回は,テンプレートを少しだけ変更して以下としました.

import greengrasssdk
import platform
from threading import Timer
import time

client = greengrasssdk.client('iot-data')

my_platform = platform.platform()

def greengrass_hello_world_run():
    # (追加部分)緑点灯のためのメッセージ発行,後5秒待ち
    client.publish(topic='io/led', payload='{"red":0,"green":1}')
    time.sleep(5)
    # (追加部分)赤点灯のためのメッセージ発行,後5秒待ち
    client.publish(topic='io/led', payload='{"red":1,"green":0}')
    # 関数自身を呼ぶことで無限ループ
    Timer(5, greengrass_hello_world_run).start()

# 実行開始
greengrass_hello_world_run()

def function_handler(event, context):
    return

※本来はデバイスシャドウを利用すべきかもしれませんが,簡素化のために直にメッセージを発行する形にしています

AWS Greengrass を通じてこのLambda関数がエッジ側(RasPi3)にデプロイされると,(無限ループのおかげで)動き続けることになります.エッジ側で動くLambda関数は通常のLambda関数と異なり,動作時間上限がありません.デプロイの過程やセキュリティの担保はGreengrassが面倒をみてくれるので,いろいろ考えなくてすみます.

信号機側S/W

前回記事にて作成したS/Wを改良する形で進めます.AWS Greengrassのサンプルを参考にしました.

主な変更点はMQTTクライアントの接続先を,サーバー側ではなく,RasPi3上で動作するGreengrassコア相手にする事です.接続先情報はサーバー側に問い合わせる機構となっているようです.

# -*- coding: utf-8 -*-

# AWS Greengrass を取り扱うためのモジュール
from AWSIoTPythonSDK.core.greengrass.discovery.providers import DiscoveryInfoProvider
from AWSIoTPythonSDK.core.protocol.connection.cores import ProgressiveBackOffCore
from AWSIoTPythonSDK.exception.AWSIoTExceptions import DiscoveryInvalidRequestException

from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
import RPi.GPIO as GPIO
import time
import json
import sys
import uuid
import os

# gpio pins
LED_R = 14
LED_G = 15
LEVEL = [GPIO.LOW, GPIO.HIGH]

# host
host = "a364yta89jsfw8.iot.ap-northeast-1.amazonaws.com"
port = 8883

# certs
rootca_path = "./certs/root-ca.pem"
certificate_path = "./certs/thing-certificate.pem.crt"
privatekey_path = "./certs/thing-private.pem.key"
thing_name = "RasPi3"

def onMessage(message):    # メッセージを受け取った際に呼ばれる
    print(message.payload)
    payload = json.loads(message.payload.decode('utf-8'))
    print(" > Message : {}".format(payload))
    print("--------------n")   

    try:
        red_lv = int(payload['red'])    
        green_lv = int(payload['green'])
        GPIO.output(LED_R, LEVEL[red_lv])
        GPIO.output(LED_G, LEVEL[green_lv])
    except:
        print(" ! Invalid message payload")     

def getGgCoreInfo():    # エッジ上Greengrass Coreの情報取得
    GROUP_CA_PATH = "./groupCA/"

    # Greengrass coreの検索
    discovery_info_provider = DiscoveryInfoProvider()
    discovery_info_provider.configureEndpoint(host)
    discovery_info_provider.configureCredentials(rootca_path, certificate_path, privatekey_path)
    discovery_info_provider.configureTimeout(10)  # 10 sec  

    discovery_info = discovery_info_provider.discover(thing_name)
    ca_list = discovery_info.getAllCas()
    core_list = discovery_info.getAllCores()

    # 見つかった1つめのCoreの情報を取得
    group_id, ca = ca_list[0]
    core_info = core_list[0]
    print("Discovered GGC: %s from Group: %s" % (core_info.coreThingArn, group_id))

    print("Now we persist the connectivity/identity information...")
    group_ca = GROUP_CA_PATH + group_id + "_CA_" + str(uuid.uuid4()) + ".crt"
        # GroupCAの取得(...よく理解していない)
    if not os.path.exists(GROUP_CA_PATH):
        os.makedirs(GROUP_CA_PATH)
    group_ca_file = open(group_ca, "w")
    group_ca_file.write(ca)
    group_ca_file.close()

    return (group_ca,core_info)

if __name__ == "__main__":

    # gpio setup
    GPIO.setwarnings(False) 
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(LED_R, GPIO.OUT)
    GPIO.setup(LED_G, GPIO.OUT)

    # mqtt setup
    mqtt_client = AWSIoTMQTTClient(thing_name)
    #mqtt_client.configureEndpoint(host,port)
    #mqtt_client.configureCredentials(rootca_path, privatekey_path, certificate_path)

    mqtt_client.configureOfflinePublishQueueing(0)
    mqtt_client.configureDrainingFrequency(2)
    mqtt_client.configureConnectDisconnectTimeout(120)
    mqtt_client.configureMQTTOperationTimeout(5)

    # Register an onMessage callback
    mqtt_client.onMessage = onMessage

    # 取得した接続先に対して順に接続を試みる
    connected = False
    (rootca_path, core_info) = getGgCoreInfo()
    for connectivity_info in core_info.connectivityInfoList:
        current_host = connectivity_info.host
        current_port = connectivity_info.port
        print(" * Trying to connect to core at %s:%d" % (current_host, current_port))
        mqtt_client.configureEndpoint(current_host, current_port)
        mqtt_client.configureCredentials(rootca_path, privatekey_path, certificate_path)
        try:
            mqtt_client.connect()
            connected = True
            break
        except BaseException as e:
            print(" * Error in connect!")
            print(" * Type: %s" % str(type(e)))
            print(" * Error message: %s" % e.message)

    if not connected:
        print(" * Cannot connect to core %s. Exiting..." % core_info.coreThingArn)
        sys.exit(-2)    

    mqtt_client.connect()
    time.sleep(2)
    print(" * connection success")  
    mqtt_client.subscribe("io/led", 1, None)
    print(" * start subscribing...")

    while True:
        time.sleep(3)

公式のサンプルではGreengrass Coreの検索自体をリトライする構成になっています.これは “RasPi3側にLambda関数をデプロイした後でしか接続先を発見できない”ためです.今回はコード簡略化のため”Lambda関数はすでにデプロイされている前提”で進めます.

信号機側H/W

前回記事と同じなので省略します.

動かしてみる

(本来の動きとは逆になりますが)まずはRasPi3側にLambda関数をデプロイします.注意点としてデプロイ前に

  • RasPi3上でGreengrass Coreを起動しておく
  • Lambda関数を作成後,[アクション]→[新しいバージョンを発行]を実行しておく
  • サブスクリプション(メッセージが配送される経路)を適切に設定しておく

が必要です.Greengrass Coreは以下で起動できます.起動に失敗する場合はGreengrass Coreの設定が間違っている可能性が高いです.

pi@raspi3-nobu_e753:/greengrass/ggc/core $ pwd
/greengrass/ggc/core
pi@raspi3-nobu_e753:/greengrass/ggc/core $ sudo ./greengrassd start
Setting up greengrass daemon
Validating execution environment
Found cgroup subsystem: cpu
...
Starting greengrass daemon
Greengrass successfully started with PID: 2856

サブスクリプションの経路は以下としました(自信なし).ターゲットに”IoT Cloud”を設けておくことで,AWS IoTのコンソール側([テスト]→[トピックへサブスクライブする])でも発行メッセージを確認することができます.

aws03s.png

設定が完了したらデプロイを実行します.あらかじめ[Lambda]タブで指定された関数がエッジ側へロードされます.ランプが緑に変わったら完了です.

aws04s.png

次にRasPi3側のプログラムを動かします.

pi@raspi3-nobu_e753:~/Workspace/aws $ ls
certs/  led_blink_awsgg.py  led_blink_awsiot.py
pi@raspi3-nobu_e753:~/Workspace/aws $ python led_blink_awsgg.py 
Discovered GGC: arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:thing/GgcGroup0_Core from Group: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Now we persist the connectivity/identity information...
 * Trying to connect to core at xxxx::xxx:xxxx:xxxx:xxxx:8883 # 接続先1トライ
 * Error in connect!
 * Type: <class 'socket.error'>
 * Error message: 
 * Trying to connect to core at 127.0.0.1:8883 # 接続先2トライ
 * connection success
 * start subscribing...    # エッジ側のLambda関数が発行するメッセージを受け,以下が出力される
{"red":1,"green":0}
 > Message : {u'green': 0, u'red': 1}
--------------

{"red":0,"green":1}
 > Message : {u'green': 1, u'red': 0}
--------------

{"red":1,"green":0}
 > Message : {u'green': 0, u'red': 1}
--------------

この状態で,RasPi3に接続されたLEDの色が5秒ごとに赤→緑→赤…と変わるのが確認できると思います.

最後に

エッジ側だけの情報で制御がなされていることを確認するためRasPi3のネットワーク接続を切断してみます(RasPi3のWifiを切断,ルーターの電源をおとす..などどのような方法でもよいです).その後もLEDの色が切り替わり続けるはずです.

このエッジタイプ信号機であれば,クラウドタイプ信号機の問題点を解決できそうですね.

わからなかった箇所

エッジ側へデプロイするLambda関数内で利用可能なコード
例えば,時刻の取得 datetime.now() やプラットフォーム情報の取得
platform.platform() は実行可能でしたが,ディレクトリ構成の取得 os.listdir() は実行できませんでした.何が実行できて何が実行できるのでしょう…

まとめ

AWS Greengrassを使い,信号機に見立てたLEDの制御を行うシステムを2通り作成しました.これを通じて,クラウドとエッジの違いを感覚的につかむことができました.

続きを読む

AWS GreengrassでLチカ:クラウドとエッジについて考える(1)

できたもの

LED blink by AWS Greengrass controll pic.twitter.com/sdBzLaifdf

— nobu_e753 (@nobu_e753) 2017年10月7日

  • AWS Greengrassのコンソール上からLambda関数をRasPi3へデプロイすると,RasPi3につないであるLEDが点滅する(5秒毎に赤→緑→赤…)
  • その後,RasPi3のネットワークを切断してもLEDは既定のパターンで点滅し続ける

はじめに

先日,JAWS-UG主催のAWS Greengrassハンズオンに参加したのですが,機材トラブルや難度の問題もあり,半端な状態で終わってしまいました.あまり理解できず終わってしまったことが悔しかったので,自宅にて自分なりの課題を立て進めてみることにしました.本記事はそのまとめです.

※「自分なりに理解すること」を優先したので,公式のやり方に沿わないもしくは語弊がある部分もあるかもしれません

下準備

機材は以下で進めました.こちらにGreengrassに対応している機材一覧があります.

  • RaspberryPi 3
  • PC

またとりくみ前に以下のチュートリアルを実施しました.内容そのものの理解というより”なんとなくオペレーションを理解しておく”ためです.

言語はPythonですすめました.あと,あえて述べておくと私のAWSに関する知識は「EC2やDynamoDBを仕事でつかうものの利用は基本的な範囲にとどまる」レベルです.

とりくみ内容

AWSを利用して以下のような「ネットワーク型信号機」を設計する,ことをお題にしました(実際は信号機にみたてたRasPi3+LEDを制御・点灯させます).

problem.png

この課題に取り組むにあたり

  1. AWS IoT にて実現(クラウドの考え方)
  2. AWS Greengrass にて実現(エッジの考え方)

の双方を試すことにより,それらの違い,そしてクラウドとエッジの考え方を感覚的に理解することを目標にしました.本記事では1つめ,続編で2つめに取り組みます.

1. AWS IoTにてネットワーク型信号機を実現する

まずは構成検討です.できるだけ単純なモデルにするため以下にしました.

p1.png

  • 制御はAWS側から相当するMQTTメッセージを送信
  • RasPi3側でメッセージを受信,信号機に見立てたLEDを制御(赤/緑の2色)

赤/緑だけの2色信号機をネットワーク経由で制御します(最初に「赤を点灯」に相当するメッセージを発信,60秒後に「緑を点灯」に相当するメッセージを発信…).信号の切り替え間隔を変える場合も,サーバー(AWS)側の制御プログラムを変更するだけで済みます.なんかクラウドっぽいです…
早速作成してみます.

信号機側S/W

  • MQTTメッセージを受信
  • メッセージに合わせGPIO経由でLED制御

機能としてはこれだけです.コードは以下としました.AWS IoTのサンプルに毛の生えた程度です.各種アドレスやファイルパスはすべて埋め込みとしました.AWS IoTに接続するための証明書はあらかじめ作成し,./certs/ フォルダ以下に格納しておくものとします.
GPIO経由でLEDを点灯させることについてはこちらの記事などが参考になります.

led_brink_iot.py
# -*- coding: utf-8 -*-
from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
import RPi.GPIO as GPIO
import time
import json

# gpio pins
LED_R = 14
LED_G = 15
LEVEL = [GPIO.LOW, GPIO.HIGH]

# host
host = "xxxxxxxxxxxxxx.iot.ap-northeast-1.amazonaws.com"
port = 8883

# certs
rootca_path = "./certs/root-ca.pem"
certificate_path = "./certs/thing-certificate.pem.crt"
privatekey_path = "./certs/thing-private.pem.key"
thing_name = "RasPi3"

def onMessage(message):    # メッセージを受け取った際に呼ばれる
    payload = json.loads(message.payload.decode('utf-8'))
    print(" > Message : {}".format(payload))
    print("--------------n")   

    try:    # GPIOの制御
        red_lv = int(payload['red'])    
        green_lv = int(payload['green'])
        GPIO.output(LED_R, LEVEL[red_lv])
        GPIO.output(LED_G, LEVEL[green_lv])
    except:
        print(" ! Invalid message")     

if __name__ == "__main__":

    # gpio setup
    GPIO.setwarnings(False) 
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(LED_R, GPIO.OUT)
    GPIO.setup(LED_G, GPIO.OUT)

    # mqtt setup
    mqtt_client = AWSIoTMQTTClient(thing_name)
    mqtt_client.configureEndpoint(host,port)    # MQTTクライアントの接続先を上で指定したhost/portにする
    mqtt_client.configureCredentials(rootca_path, privatekey_path, certificate_path)

    mqtt_client.configureOfflinePublishQueueing(0)
    mqtt_client.configureDrainingFrequency(2)
    mqtt_client.configureConnectDisconnectTimeout(120)
    mqtt_client.configureMQTTOperationTimeout(5)

    # Register an onMessage callback
    mqtt_client.onMessage = onMessage    # メッセージを受信したときのコールバック関数設定

    # start subscribe
    mqtt_client.connect()    # 接続
    time.sleep(2)
    print(" * Connection successful")   

    mqtt_client.subscribe("io/led", 1, None)
    print(" * Start subscribe..")

    while True:
        time.sleep(3)    # 無限ループでメッセージ受信を待つ

サーバー側からのメッセージは以下のMQTTトピック&JSON形式としました.0なら消灯,1なら点灯です.

# MQTT topic
io/led
# Message format
{
    "red":0,
    "green":1
}

信号機側H/W

GPIO-LED間の接続は以下としました(抵抗には手元にあった330Ωを使用).

20170929_krs_qiita.png

動かしてみる

まずはRasPi3側のプログラムを動かします.

pi@raspi3-nobu_e753:~/Workspace/aws $ ls
certs/  led_blink_awsgg.py  led_blink_awsiot.py
pi@raspi3-nobu_e753:~/Workspace/aws $ python3 led_blink_awsiot.py 
 * connection success
 * start subscribing...    # AWS側からメッセージを発行すると以下が出力される
 > Message : {'green': 0, 'red': 1}
--------------

 > Message : {'green': 1, 'red': 0}
--------------

 > Message : {'green': 0, 'red': 1}
--------------

待ち受け状態になったらAWS IoTのコンソールからMQTTメッセージを発行してみます.

aws01s.png

aws02.png

メッセージを発行すると,コンソールにその内容が表示されると当時にRasPi3に接続されているLEDの点灯状況が変わるはずです.このメッセージを発行手順を,手動ではなく,AWS Lambdaで記述し,これを定期実行するようにすれば,「サーバーからのメッセージによって切り替えを行うクラウドタイプ信号機」が実現できます(AWS Lambdaによる定期発行のコードは省略します).

クラウドタイプ信号機の問題点

さて,もし世の中の信号機が上記の構成で実現されていたらかなり危ないことに気づくと思います.

1. ネットワークが切れたら終わり
まずこれです.信号機の制御はサーバーから送信されにはてくるメッセージによっているので,ネットワークが切れた瞬間に制御できなくなります.事故が多発するに違いありません.

2. 通信費がバカにならない
日本にある信号機は全部で約20万基だそうです(ちなみに,コンビニ5万件,歯医者7万件,美容院23万件!だそうです).これらをすべて制御する場合,一体どれだけの費用がかかるか想像もしたくありません.

どうやらこの構成だと問題が大きそうです

ではどうしたら?

上記問題を解決するには

  • 信号機側である程度自立して動くようにする
  • ただしネットワークを通じて制御内容をアップデートできるようにする

としたいです.すると信号機側にアップデーを受け入れる機構や,新設定にしたがって動き出す機構,アップデートが失敗した場合の手当てを行う機構などなど,本題ではないところでいろいろと面倒なことが発生します.

ううむ面倒だ,誰かやってもらえないだろうか…

→ AWS Greengrassに面倒をみてもらおう!(という意図のサービスだと私は理解しました

記事2に続きます.

続きを読む

Aws Lambda をローカルで実行する

必要なソフトのインストール

sudo pip install python-lambda-local

イベントの定義

event.json
{
  "key1": "おはよう",
  "key2": "こんにちは",
  "key3": "テスト",
  "key4": "Hello",
  "key5": "Good Afternoon"
}

プログラム

lambda_function.py
# -*- coding: utf-8 -*-
#
#   lambda_function.py
#
#                   Oct/6/2017
#
# --------------------------------------------------------------------
import sys
import json

# --------------------------------------------------------------------
def lambda_handler(event, context):
    sys.stderr.write("*** lambda_handler *** start ***n")
    print("Received event: " + json.dumps(event, indent=2))
    print("AAA value1 = " + event['key1'])
    print("BBB value2 = " + event['key2'])
    print("CCC value3 = " + event['key3'])
    print("DDD value4 = " + event['key4'])
    print("EEE value5 = " + event['key5'])
    sys.stderr.write("*** lambda_handler *** end ***n")
#
    return event['key1']  # Echo back the first key value
    #raise Exception('Something went wrong')
# --------------------------------------------------------------------

実行コマンド

python-lambda-local -f lambda_handler lambda_function.py event.json

実行結果

$ python-lambda-local -f lambda_handler lambda_function.py event.json
[root - INFO - 2017-10-06 10:15:38,666] Event: {'key1': 'おはよう', 'key2': 'こんにちは', 'key3': 'テスト', 'key4': 'Hello', 'key5': 'Good Afternoon'}
[root - INFO - 2017-10-06 10:15:38,667] START RequestId: 2218d928-ed76-4d6e-b290-dc597505fd4d
*** lambda_handler *** start ***
Received event: {
  "key1": "u304au306fu3088u3046",
  "key2": "u3053u3093u306bu3061u306f",
  "key3": "u30c6u30b9u30c8",
  "key4": "Hello",
  "key5": "Good Afternoon"
}
AAA value1 = おはよう
BBB value2 = こんにちは
CCC value3 = テスト
DDD value4 = Hello
EEE value5 = Good Afternoon
*** lambda_handler *** end ***
[root - INFO - 2017-10-06 10:15:38,667] END RequestId: 2218d928-ed76-4d6e-b290-dc597505fd4d
[root - INFO - 2017-10-06 10:15:38,667] RESULT:
おはよう
[root - INFO - 2017-10-06 10:15:38,667] REPORT RequestId: 2218d928-ed76-4d6e-b290-dc597505fd4d  Duration: 0.35 ms
$ 

続きを読む