LoginSignup
6
1

More than 3 years have passed since last update.

AWS_SAMを使ってLNEbotを爆速で開発する

Posted at

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にひもづけ、上記で


Screenshot_2021-02-11_16.30.06.png

Verifyを押下することで接続確認ができます。
LINEBotを友達追加し、適当なメッセージを送るとLamdaのログにwebhookで受け付けたリクエストが表示されます。

Screenshot_2021-02-11_16.33.18.png

{
   "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のチャンネル情報を登録します。

Screenshot_2021-02-11_17.54.25.png

再度ビルド後デプロイすることでLINEbotがオウム返しをします。

Videotogif (1).gif

クリーンアップ_公式より引用

  1. AWS マネジメントコンソール にサインインして、https://console.aws.amazon.com/cloudformation で AWS CloudFormation コンソールを開きます。
  2. 左のナビゲーションペインで [スタック] を選択します。
  3. スタックのリストで、[または作成したスタックの名前] を選択します。
  4. [削除] を選択します。

完了すると、スタックのステータスが 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

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1