AWS_SAMを使ってオウム返しLINEbotを作る
こんにちは!
突然ですが、サーバレスは好きですか?僕は大好きです。
ただ、クラウドのリソース管理とかよくわからず、なかなか最初は手が出しにくい気がします。
今回はAWSのSAM(Serverless Application Model)を使ってサーバレスAPIを構築してLINEbotを作っていきます。
構築したリソースは一括削除ができるため、サーバレスアプリを手軽に体験することができます。
- aws_samとは
- サーバレスアプリケーションモデル
- サーバレスアプリケーション構築用のオープンソースフレームワーク
- yamlファイルをもちいてモデリング
- デプロイ中sam構文をCloudFormation構文に変換および拡張することで、サーバレスアプリケーションの構築を高速化することができる
★サーバレスアプリ構築用のフレームワークのようなもの、AWSに自動でサーバレスAPIの構築を行ってくれます。
★YAMLに記述したSAM構文をCloudFundationに適用していくことでLambdaやAPIGatewayの設定を自動で行ってくれます。
★不要になればスタックを削除して無駄な料金を省くことができます。(後述)
(※以下MACにて実施しています。手順はおおむね変わりありませんがWindowsの方はコマンドを読み替えていただければと思います。)
AWS_SAMをインストール
※下記サイトの手順参照
AWS サーバーレスアプリケーションモデル - アマゾン ウェブ サービス
Hello Worldテンプレートでアプリの雛形作成
既存のtemplateを編集して実装を進めていきます。
その前に使用するテンプレートの動作確認を行っていきます。
今回runtime(言語)はpython3.8を選択しました。
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.8
3 - ruby2.7
4 - go1.x
5 - java11
6 - dotnetcore3.1
7 - nodejs12.x
8 - nodejs10.x
9 - python3.7
10 - python3.6
11 - python2.7
12 - ruby2.5
13 - java8.al2
14 - java8
15 - dotnetcore2.1
Runtime: 2
Project name [sam-app]: LINEbot_Fitness
Cloning app templates 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: LINEbot_Fitness
Runtime: python3.8
Dependency Manager: pip
Application Template: hello-world
Output Directory: .
Next steps can be found in the README file at ./LINEbot_Fitness/README.md
LINEbot_Fitness/
├── README.md
├── events/
│ └── event.json
├── hello_world/
│ ├── __init__.py
│ ├── app.py #Contains your AWS Lambda handler logic.
│ └── requirements.txt #Contains any Python dependencies the application requires, used for sam build
├── template.yaml #Contains the AWS SAM template defining your application's AWS resources.
└── tests/
└── unit/
├── __init__.py
└── test_handler.py
-
チュートリアルに沿ってデプロイまで実行
ビルド
sam build Building codeuri: hello_world/ runtime: python3.8 metadata: {} functions: ['HelloWorldFunction'] 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 [*] Deploy: sam deploy --guided
デプロイ
sam deploy --guided Configuring SAM deploy ====================== Looking for config file [samconfig.toml] : Found Reading default arguments : Success Setting default arguments for 'sam deploy' ========================================= Stack Name [LINEbot_Fitness]: LINEbotFitness 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]: y HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y Save arguments to configuration file [Y/n]: y SAM configuration file [samconfig.toml]: SAM configuration environment [default]: Looking for resources needed for deployment: Found! Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-1lsd1f1o23h47 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 LINEbotFitness/7e23deb2b18497df06aec41e21909939 584056 / 584056.0 (100.00%) Deploying with following values =============================== Stack name : LINEbotFitness Region : ap-northeast-1 Confirm changeset : True Deployment s3 bucket : aws-sam-cli-managed-default-samclisourcebucket-1lsd1f1o23h47 Capabilities : ["CAPABILITY_IAM"] Parameter overrides : {} Signing Profiles : {} Initiating deployment ===================== HelloWorldFunction may not have authorization defined. Uploading to LINEbotFitness/3a56556d2a056416039674cf9f7ee7f1.template 1113 / 1113.0 (100.00%) Waiting for changeset to be created.. CloudFormation stack changeset --------------------------------------------------------------------------------------------------------------------- Operation LogicalResourceId ResourceType Replacement --------------------------------------------------------------------------------------------------------------------- + Add HelloWorldFunctionHelloWorl AWS::Lambda::Permission N/A dPermissionProd + Add HelloWorldFunctionRole AWS::IAM::Role N/A + Add HelloWorldFunction AWS::Lambda::Function N/A + Add ServerlessRestApiDeployment AWS::ApiGateway::Deployment N/A 47fc2d5f9d + Add ServerlessRestApiProdStage AWS::ApiGateway::Stage N/A + Add ServerlessRestApi AWS::ApiGateway::RestApi N/A --------------------------------------------------------------------------------------------------------------------- Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:056822081946:changeSet/samcli-deploy1613024810/08b59885-e90b-4d4b-953b-91ae3ecab8bd Previewing CloudFormation changeset before deployment ====================================================== Deploy this changeset? [y/N]: y 2021-02-11 15:27:05 - Waiting for stack create/update to complete CloudFormation events from changeset --------------------------------------------------------------------------------------------------------------------- ResourceStatus ResourceType LogicalResourceId ResourceStatusReason --------------------------------------------------------------------------------------------------------------------- CREATE_IN_PROGRESS AWS::IAM::Role HelloWorldFunctionRole - CREATE_IN_PROGRESS AWS::IAM::Role HelloWorldFunctionRole Resource creation Initiated CREATE_COMPLETE AWS::IAM::Role HelloWorldFunctionRole - CREATE_IN_PROGRESS AWS::Lambda::Function HelloWorldFunction - CREATE_COMPLETE AWS::Lambda::Function HelloWorldFunction - CREATE_IN_PROGRESS AWS::Lambda::Function HelloWorldFunction Resource creation Initiated 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 ServerlessRestApiDeployment - 47fc2d5f9d CREATE_IN_PROGRESS AWS::Lambda::Permission HelloWorldFunctionHelloWorl Resource creation Initiated dPermissionProd CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeployment Resource creation Initiated 47fc2d5f9d CREATE_IN_PROGRESS AWS::Lambda::Permission HelloWorldFunctionHelloWorl - dPermissionProd CREATE_COMPLETE AWS::ApiGateway::Deployment ServerlessRestApiDeployment - 47fc2d5f9d 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 HelloWorldFunctionHelloWorl - dPermissionProd CREATE_COMPLETE AWS::CloudFormation::Stack LINEbotFitness - --------------------------------------------------------------------------------------------------------------------- CloudFormation outputs from deployed stack ----------------------------------------------------------------------------------------------------------------------- Outputs ----------------------------------------------------------------------------------------------------------------------- Key HelloWorldFunctionIamRole Description Implicit IAM Role created for Hello World function Value arn:aws:iam::056822081946:role/LINEbotFitness-HelloWorldFunctionRole-JP6UNGHDH2RA Key HelloWorldApi Description API Gateway endpoint URL for Prod stage for Hello World function Value https://ppp8jf5dic.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/ Key HelloWorldFunction Description Hello World Lambda Function ARN Value arn:aws:lambda:ap-northeast-1:056822081946:function:LINEbotFitness- HelloWorldFunction-5KDQ6WQ06FEZ ----------------------------------------------------------------------------------------------------------------------- Successfully created/updated stack - LINEbotFitness in ap-northeast-1
動作確認
curl https://ppp8jf5dic.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/ {"message": "hello world"}%
Hello World Templateの動作確認ができました。
ローカルで動作確認
sam local start-api
curl http://127.0.0.1:3000/hello
{"message": "hello world"}
※変更後は再度buildしstart-api
LINEbotのwebhook用にYAML修正します。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
LINEbot_Fitness
Sample SAM Template for LINEbot_Fitness
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 3
Resources:
LINEbotFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: fitness/
Handler: app.lambda_handler
Runtime: python3.8
Events:
LINEbot:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /fitness
Method: post
Outputs:
LINEbotApi:
Description: "API Gateway endpoint URL for Prod stage for LINEbot fitness function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/fitness/"
LINEbotFunction:
Description: "LINEbot fitness Lambda Function ARN"
Value: !GetAtt LINEbotFunction.Arn
LINEbotFunctionIamRole:
Description: "Implicit IAM Role created for LINEbot fitness function"
Value: !GetAtt LINEbotFunctionRole.Arn
実行するLambda関数
import json
# import requests
def lambda_handler(event, context):
print(event)
return {
"statusCode": 200,
"body": json.dumps({
"message": event,
# "location": ip.text.replace("\n", "")
}),
}
build後、Localで起動します。
sam build
sam local satart-api
動作確認をします。
curl -X POST http://127.0.0.1:3000/fitness
{
"message":{
"body":null,
"headers":{
"Accept":"*/*",
"Host":"127.0.0.1:3000",
"User-Agent":"curl/7.71.1",
"X-Forwarded-Port":"3000",
"X-Forwarded-Proto":"http"
},
"httpMethod":"POST",
"isBase64Encoded":false,
"multiValueHeaders":{
"Accept":[
"*/*"
],
"Host":[
"127.0.0.1:3000"
],
"User-Agent":[
"curl/7.71.1"
],
"X-Forwarded-Port":[
"3000"
],
"X-Forwarded-Proto":[
"http"
]
},
"multiValueQueryStringParameters":null,
"path":"/fitness",
"pathParameters":null,
"queryStringParameters":null,
"requestContext":{
"accountId":"123456789012",
"apiId":"1234567890",
"domainName":"127.0.0.1:3000",
"extendedRequestId":null,
"httpMethod":"POST",
"identity":{
"accountId":null,
"apiKey":null,
"caller":null,
"cognitoAuthenticationProvider":null,
"cognitoAuthenticationType":null,
"cognitoIdentityPoolId":null,
"sourceIp":"127.0.0.1",
"user":null,
"userAgent":"Custom User Agent String",
"userArn":null
},
"path":"/fitness",
"protocol":"HTTP/1.1",
"requestId":"f7af1c8c-2897-4ce8-b88a-b4a34d805ca0",
"requestTime":"11/Feb/2021:07:21:09 +0000",
"requestTimeEpoch":1613028069,
"resourceId":"123456",
"resourcePath":"/fitness",
"stage":"Prod"
},
"resource":"/fitness",
"stageVariables":null,
"version":"1.0"
}
}"%"
AWSにデプロイします。
sam deploy
成功するとTerminalにリクエスト用のURLが表示されるため、LINEのwebhookに紐づけしていきます。
LINEbotのwebhookにひもづけ、上記で
Verifyを押下することで接続確認ができます。
LINEBotを友達追加し、適当なメッセージを送るとLamdaのログにwebhookで受け付けたリクエストが表示されます。
{
"resource":"/fitness",
"path":"/fitness/",
"httpMethod":"POST",
"headers":{
"CloudFront-Forwarded-Proto":"https",
"CloudFront-Is-Desktop-Viewer":"true",
"CloudFront-Is-Mobile-Viewer":"false",
"CloudFront-Is-SmartTV-Viewer":"false",
"CloudFront-Is-Tablet-Viewer":"false",
"CloudFront-Viewer-Country":"JP",
"content-type":"application/json; charset=utf-8",
"Host":"ppp8jf5dic.execute-api.ap-northeast-1.amazonaws.com",
"User-Agent":"LineBotWebhook/2.0",
"Via":"2.0 f90df03a8129371b68786cdf0a407d89.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id":"sRjdd5w93zl6dpKhLVBj_ySYPMeVM6AAZByzctHcWUeNV2iJrFtb1g==",
"X-Amzn-Trace-Id":"Root=1-6024dd24-7dbc07d771a0ba0708f04f31",
"X-Forwarded-For":"147.92.150.198, 64.252.167.145",
"X-Forwarded-Port":"443",
"X-Forwarded-Proto":"https",
"x-line-signature":"uiS1/an5ll7ZyWHkqwIeNl/YZ3V71l4yzM+GE3LucYA="
},
"multiValueHeaders":{
"CloudFront-Forwarded-Proto":[
"https"
],
"CloudFront-Is-Desktop-Viewer":[
"true"
],
"CloudFront-Is-Mobile-Viewer":[
"false"
],
"CloudFront-Is-SmartTV-Viewer":[
"false"
],
"CloudFront-Is-Tablet-Viewer":[
"false"
],
"CloudFront-Viewer-Country":[
"JP"
],
"content-type":[
"application/json; charset=utf-8"
],
"Host":[
"ppp8jf5dic.execute-api.ap-northeast-1.amazonaws.com"
],
"User-Agent":[
"LineBotWebhook/2.0"
],
"Via":[
"2.0 f90df03a8129371b68786cdf0a407d89.cloudfront.net (CloudFront)"
],
"X-Amz-Cf-Id":[
"sRjdd5w93zl6dpKhLVBj_ySYPMeVM6AAZByzctHcWUeNV2iJrFtb1g=="
],
"X-Amzn-Trace-Id":[
"Root=1-6024dd24-7dbc07d771a0ba0708f04f31"
],
"X-Forwarded-For":[
"147.92.150.198, 64.252.167.145"
],
"X-Forwarded-Port":[
"443"
],
"X-Forwarded-Proto":[
"https"
],
"x-line-signature":[
"uiS1/an5ll7ZyWHkqwIeNl/YZ3V71l4yzM+GE3LucYA="
]
},
"queryStringParameters":"None",
"multiValueQueryStringParameters":"None",
"pathParameters":"None",
"stageVariables":"None",
"requestContext":{
"resourceId":"9ndb9o",
"resourcePath":"/fitness",
"httpMethod":"POST",
"extendedRequestId":"akd9vGFENjMFXWQ=",
"requestTime":"11/Feb/2021:07:30:44 +0000",
"path":"/Prod/fitness/",
"accountId":"056822081946",
"protocol":"HTTP/1.1",
"stage":"Prod",
"domainPrefix":"ppp8jf5dic",
"requestTimeEpoch":1613028644617,
"requestId":"1022d09d-c140-4b1c-9dc8-61eebc302e80",
"identity":{
"cognitoIdentityPoolId":"None",
"accountId":"None",
"cognitoIdentityId":"None",
"caller":"None",
"sourceIp":"147.92.150.198",
"principalOrgId":"None",
"accessKey":"None",
"cognitoAuthenticationType":"None",
"cognitoAuthenticationProvider":"None",
"userArn":"None",
"userAgent":"LineBotWebhook/2.0",
"user":"None"
},
"domainName":"ppp8jf5dic.execute-api.ap-northeast-1.amazonaws.com",
"apiId":"ppp8jf5dic"
},
"body":"{\"events\":[{\"type\":\"message\",\"replyToken\":\"8447990a2e764180be549ac585187480\",\"source\":{\"userId\":\"Ub3460cc5512a5791102446b8d075b066\",\"type\":\"user\"},\"timestamp\":1613028644559,\"mode\":\"active\",\"message\":{\"type\":\"text\",\"id\":\"13538493890029\",\"text\":\"fsd\"}}],\"destination\":\"Ued4095ce4cf48cdb76b561d079297db7\"}",
"isBase64Encoded":false
}
LINEbotにオウム返し実装
templateのapp.py
にオウム返しのロジックを入力していきます。
import json
import os
# import requests
from linebot import (LineBotApi, WebhookHandler)
from linebot.exceptions import (LineBotApiError, InvalidSignatureError)
from linebot.models import (MessageEvent, TextMessage, TextSendMessage,)
def lambda_handler(event, context):
# 環境変数取得
YOUR_CHANNEL_ACCESS_TOKEN = os.environ["YOUR_CHANNEL_ACCESS_TOKEN"]
YOUR_CHANNEL_SECRET = os.environ["YOUR_CHANNEL_SECRET"]
line_bot_api = LineBotApi(YOUR_CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(YOUR_CHANNEL_SECRET)
# get X-Line-Signature header value
signature = event["headers"]['x-line-signature']
# get request body as text
body = event["body"]
print("Request body: " + body)
@handler.add(MessageEvent, message=TextMessage)
def message(line_event):
text = line_event.message.text
line_bot_api.reply_message(
line_event.reply_token, TextSendMessage(text=text))
try:
handler.handle(body, signature)
except LineBotApiError as e:
logger.error("Got exception from LINE Messaging API: %s\n" % e.message)
for m in e.error.details:
logger.error(" %s: %s" % (m.property, m.message))
except InvalidSignatureError:
logger.error("sending message happen error")
Lambdaに環境変数でLINEBotのチャンネル情報を登録します。
再度ビルド後デプロイすることでLINEbotがオウム返しをします。
クリーンアップ_公式より引用
- AWS マネジメントコンソール にサインインして、https://console.aws.amazon.com/cloudformation で AWS CloudFormation コンソールを開きます。
- 左のナビゲーションペインで [スタック] を選択します。
- スタックのリストで、[または作成したスタックの名前] を選択します。
- [削除] を選択します。
完了すると、スタックのステータスが DELETE_COMPLETE に変わります。
または、次の AWS CloudFormation コマンドを実行して AWS CLI スタックを削除することもできます。
aws cloudformation delete-stack --stack-name sam-app --region region
削除されたスタックの確認
どちらの方法でも AWS CloudFormation スタックを削除する場合は、AWS CloudFormation コンソールに移動して削除済みであることを確認できます。左のナビゲーションペインで、[スタック] を選択し、検索ボックスの横にあるドロップダウンリストから [削除済み] を選択します。削除されたスタックのリストにスタックの名前が表示されます。
参考
AWS サーバーレスアプリケーションモデル - アマゾン ウェブ サービス
チュートリアル: Hello World アプリケーションのデプロイ
Python + HerokuでLINE BOTを作ってみた - Qiita
[新ツール] AWS Serverless Application Model (AWS SAM) を使ってサーバーレスアプリケーションを構築する | DevelopersIO