はじめに
以前、httpでpostされたデータをmqttでばらまく簡単なプログラムを書きました。この時はmosquitto(コンテナ) + python(flask + paho-mqtt)という構成で試しましたが、AWS IoT + labmda + api gatewayで書き直してみます。
AWS IoTはhttpプロトコルでメッセージをpublishできるのですが、今回の要件ではIP制限とbasic認証で接続を制限したかったので、labmda + api gatewayという構成にしてみました。
作業環境の前提条件
- aws cliがインストールされていること
- sam cliがインストールされていること
作成手順
1. samテンプレートのダウンロード
sam cliでテンプレートをダウンロードします
$ sam init
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1
What package type would you like to use?
1 - Zip (artifact is a zip uploaded to S3)
2 - Image (artifact is an image uploaded to an ECR image repository)
Package type: 1
Which runtime would you like to use?
1 - nodejs14.x
2 - python3.9
3 - ruby2.7
4 - go1.x
5 - java11
6 - dotnetcore3.1
7 - nodejs12.x
8 - nodejs10.x
9 - python3.8
10 - python3.7
11 - python3.6
12 - python2.7
13 - ruby2.5
14 - java8.al2
15 - java8
16 - dotnetcore2.1
Runtime: 9
Project name [sam-app]: iottest
Cloning from https://github.com/aws/aws-sam-cli-app-templates
AWS quick start application templates:
1 - Hello World Example
2 - EventBridge Hello World
3 - EventBridge App from scratch (100+ Event Schemas)
4 - Step Functions Sample App (Stock Trader)
5 - Elastic File System Sample App
Template selection: 1
-----------------------
Generating application:
-----------------------
Name: iottest
Runtime: python3.8
Architectures: x86_64
Dependency Manager: pip
Application Template: hello-world
Output Directory: .
Next application steps can be found in the README file at ./iottest/README.md
Commands you can use next
=========================
[*] Create pipeline: cd iottest && sam pipeline init --bootstrap
[*] Test Function in the Cloud: sam sync --stack-name {stack-name} --watch
$
私の環境がpython3.8なのでpython3.8を選択、Hello World ExampleではAPIを使用しているのHello World Exampleを選択します。実行すると、以下のようなディレクトリが生成されます。
$ ls -l iottest
total 28
drwxrwxr-x 2 ubuntu ubuntu 4096 Dec 8 07:39 events
drwxrwxr-x 2 ubuntu ubuntu 4096 Dec 8 07:32 hello_world
-rw-rw-r-- 1 ubuntu ubuntu 0 Dec 8 07:32 __init__.py
-rw-rw-r-- 1 ubuntu ubuntu 8349 Dec 8 07:32 README.md
-rw-rw-r-- 1 ubuntu ubuntu 1661 Dec 8 07:32 template.yaml
drwxrwxr-x 4 ubuntu ubuntu 4096 Dec 8 07:32 tests
$
これらファイルについてはこちらをご覧ください。
2. 生成されたファイルを変更する。
生成されたファイルを適宜変更します。
2-1. hello_worldのディレクトリ名変更
$ cd iotttest
$ mv hello_world publisher
2-2. app.pyの変更
import json
import boto3
iot_client = boto3.client('iot-data')
def lambda_handler(event, context):
try:
body = json.loads(event["body"])
if body and "topic" in body and 'payload' in body:
iot_client.publish(
topic=body["topic"],
qos=0,
payload=json.dumps(body["payload"]),
)
return _create_response(200, body)
except Exception as e:
raise e
return _create_response(400, {"message": "Bad Request"})
def _create_response(statusCode, dictbody):
return {
"statusCode": statusCode,
"body": json.dumps(dictbody),
}
2-3. template.yamlの変更
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
iottest
Sample SAM Template for iottest
Globals:
Function:
Timeout: 3
Resources:
PublisherFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: publisher/ # パスをリネームしたディレクトリに変更
Handler: app.lambda_handler
Runtime: python3.8
Architectures:
- x86_64
Events:
Publisher:
Type: Api
Properties:
Path: /publish # 名前を変更
Method: post # postに変更
Role: !GetAtt PublisherFunctionRole.Arn # aws iotにpublishできるroleを指定
PublisherFunctionRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
Effect: "Allow"
Action: "sts:AssumeRole"
Principal:
Service:
- lambda.amazonaws.com
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
Policies:
-
PolicyName: "IoTAllowPublishPolicy"
PolicyDocument:
Statement:
-
Effect: "Allow"
Action: "iot:Publish"
Resource: "*"
CloudWatchへのログアップロードを許可するAWSLambdaBasicExecutionRoleポリシーと、AWS IoTへのPublishを許可するインラインポリシー(IoTAlllowPublishPolicy)をアタッチしたPublisherFunctionRoleをlambda関数に割り当てる設定を追記しています。
3. アプリケーションのbuildとdeploy
本来はtestも修正すべきですが、今回は動作検証が目的なのでこのままdeployします
$ sam build
Building codeuri: /home/ubuntu/sam/iottest/publisher runtime: python3.8 metadata: {} architecture: x86_64 functions: ['PublisherFunction']
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {stack-name} --watch
[*] Deploy: sam deploy --guided
$ sam deploy --guided
Configuring SAM deploy
======================
Looking for config file [samconfig.toml] : Not found
Setting default arguments for 'sam deploy'
=========================================
Stack Name [sam-app]: iottest
AWS Region [ap-northeast-1]:
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: Y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]:
#Preserves the state of previously provisioned resources when an operation fails
Disable rollback [y/N]:
PublisherFunction may not have authorization defined, Is this okay? [y/N]: Y
Save arguments to configuration file [Y/n]:
SAM configuration file [samconfig.toml]:
SAM configuration environment [default]:
Looking for resources needed for deployment:
Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-XXXXXXXXX
A different default S3 bucket can be set in samconfig.toml
Saved arguments to config file
Running 'sam deploy' for future deployments will use the parameters saved above.
The above parameters can be changed by modifying samconfig.toml
Learn more about samconfig.toml syntax at
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
Uploading to iottest/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 451163 / 451163 (100.00%)
Deploying with following values
===============================
Stack name : iottest
Region : ap-northeast-1
Confirm changeset : True
Disable rollback : False
Deployment s3 bucket : aws-sam-cli-managed-default-samclisourcebucket-XXXXXXXX
Capabilities : ["CAPABILITY_IAM"]
Parameter overrides : {}
Signing Profiles : {}
Initiating deployment
=====================
Uploading to iottest/XXXXXXXXXXXXXX.template 1246 / 1246 (100.00%)
Waiting for changeset to be created..
CloudFormation stack changeset
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Operation LogicalResourceId ResourceType Replacement
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add PublisherFunctionPublisherPermissionProd AWS::Lambda::Permission N/A
+ Add PublisherFunctionRole AWS::IAM::Role N/A
+ Add PublisherFunction AWS::Lambda::Function N/A
+ Add ServerlessRestApiDeployment3eeb277620 AWS::ApiGateway::Deployment N/A
+ Add ServerlessRestApiProdStage AWS::ApiGateway::Stage N/A
+ Add ServerlessRestApi AWS::ApiGateway::RestApi N/A
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:XXXXXXXXX:changeSet/samcli-deployXXXX/XXXX-XXXX-XXXX-XXXX-XXXXXXX
Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y
2021-12-08 08:08:48 - Waiting for stack create/update to complete
CloudFormation events from stack operations
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus ResourceType LogicalResourceId ResourceStatusReason
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS AWS::IAM::Role PublisherFunctionRole -
CREATE_IN_PROGRESS AWS::IAM::Role PublisherFunctionRole Resource creation Initiated
CREATE_COMPLETE AWS::IAM::Role PublisherFunctionRole -
CREATE_IN_PROGRESS AWS::Lambda::Function PublisherFunction -
CREATE_IN_PROGRESS AWS::Lambda::Function PublisherFunction Resource creation Initiated
CREATE_COMPLETE AWS::Lambda::Function PublisherFunction -
CREATE_IN_PROGRESS AWS::ApiGateway::RestApi ServerlessRestApi -
CREATE_IN_PROGRESS AWS::ApiGateway::RestApi ServerlessRestApi Resource creation Initiated
CREATE_COMPLETE AWS::ApiGateway::RestApi ServerlessRestApi -
CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeployment3eeb277620 -
CREATE_IN_PROGRESS AWS::Lambda::Permission PublisherFunctionPublisherPermissionProd Resource creation Initiated
CREATE_IN_PROGRESS AWS::Lambda::Permission PublisherFunctionPublisherPermissionProd -
CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeployment3eeb277620 Resource creation Initiated
CREATE_COMPLETE AWS::ApiGateway::Deployment ServerlessRestApiDeployment3eeb277620 -
CREATE_IN_PROGRESS AWS::ApiGateway::Stage ServerlessRestApiProdStage -
CREATE_IN_PROGRESS AWS::ApiGateway::Stage ServerlessRestApiProdStage Resource creation Initiated
CREATE_COMPLETE AWS::ApiGateway::Stage ServerlessRestApiProdStage -
CREATE_COMPLETE AWS::Lambda::Permission PublisherFunctionPublisherPermissionProd -
CREATE_COMPLETE AWS::CloudFormation::Stack iottest -
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Successfully created/updated stack - iottest in ap-northeast-1
$
PublisherFunction may not have authorization defined, Is this okay? [y/N]: Y
APIに認証がないので大丈夫かどうか聞かれていますが、動作検証なのでいったんこのまま進みます。
4. テストの実行
AWS IoTのMQTTテストクライアントを使ってAWS IoTにpublishされているか確認します。
4-1. MQTTテストクライアントでdescribe
aws consoleでAWS IoT > MQTT test clientページを開き、test/topicをdescribeします。
4-2. テスト用イベントデータ作成
AWS Lambda > アプリケーション > iottestページを開き、PublisherFunctionをクリックします。
lambdaファンクションのページが開いたら、テストタブを選択し、テストイベントを作成に取り掛かります。
テンプレートにapigateway-aws-proxyを選ぶとapigateway用のテストイベントテンプレートが表示されるので、bodyのデータを
"body": "{\"topic\": \"test/topic\",\"payload\": {\"message\": \"hello everybody\"}}",
に書き換えます。書き換え後、わかりやすい名前を付けて変更を保存を押します。
4-3. テストの実行と確認
テスト実行します。うまくいくと、以下のようなテストログが表示され、AWS IoTのテストクライアントでメッセージを受け取れます。
*エラーが出る場合
実行時、タイムアウトでエラーとなることがあります。
設定からタイムアウトの時間を伸ばして実行すると、タイムアウトの原因がわかることがあります。
この、certificate verify failedですが、こちらの記事によると、certifiのバージョンの問題のようです。たしかに、エラーとなるときにbuildされた結果を見ると、certifiのバージョンが2021.10.8になっています。
$ ls -l .aws-sam/build/PublisherFunction
total 48
-rw-rw-r-- 1 ubuntu ubuntu 654 Dec 9 21:32 app.py
drwxrwxr-x 2 ubuntu ubuntu 4096 Dec 10 00:44 certifi
drwxrwxr-x 2 ubuntu ubuntu 4096 Dec 10 00:44 certifi-2021.10.8.dist-info
drwxrwxr-x 4 ubuntu ubuntu 4096 Dec 10 00:44 charset_normalizer
drwxrwxr-x 2 ubuntu ubuntu 4096 Dec 10 00:44 charset_normalizer-2.0.9.dist-info
drwxrwxr-x 2 ubuntu ubuntu 4096 Dec 10 00:44 idna
drwxrwxr-x 2 ubuntu ubuntu 4096 Dec 10 00:44 idna-3.3.dist-info
-rw-rw-r-- 1 ubuntu ubuntu 0 Dec 9 21:30 __init__.py
drwxrwxr-x 2 ubuntu ubuntu 4096 Dec 10 00:44 requests
drwxrwxr-x 2 ubuntu ubuntu 4096 Dec 10 00:44 requests-2.26.0.dist-info
-rw-rw-r-- 1 ubuntu ubuntu 9 Dec 10 00:44 requirements.txt
drwxrwxr-x 5 ubuntu ubuntu 4096 Dec 10 00:44 urllib3
drwxrwxr-x 2 ubuntu ubuntu 4096 Dec 10 00:44 urllib3-1.26.7.dist-info
$
これは、テンプレートで生成されたrequirements.txtにrequestsが記載されていることから、関連パッケージとしてcertifiがインストールされていることによるものです。
今回のコードではrequestsを使用していないのでrequirements.txtからrequestsを消すとエラーが解消されます。
もし、requestsを使うコードを書いている場合は、requirements.txtにcertifi==2020.11.8を記載するとうまくいくと思います。
4-4. APIからのテスト
AWS Lambda > アプリケーション > iottestページを開き、ServerlessRestApi をクリックします。
/publishのPOSTメソッドテストを以下のようなリクエスト本文で実行します。
{"topic": "test/topic", "payload": {"message": "hello everyone"}}
うまくいくと、以下のようなレスポンス本文が表示され、AWS IoTのテストクライアントのほうでもメッセージが受け取れます。
4-5. 実際にpostしてテスト
現時点ではAPI Gatewayに制限がかかっていないので、curlでデータをpostして動作確認します。
$ curl -X POST -d '{"topic": "test/topic", "payload": {"messege": "hello world"}}' https://zqw1v6w7oc.execute-api.ap-northeast-1.amazonaws.com/Prod/publish && echo
{"topic": "test/topic", "payload": {"messege": "hello world"}}
$
コマンド実行後に改行されないケースは&& echoを付けると改行されるのでちょっぴり幸せです。
長くなったのでここまで。次回はモノとAPIへのIP制限を追加します。