LoginSignup
6
4

More than 3 years have passed since last update.

ServerlessFrameworkとPythonでAWSを触ってみる

Last updated at Posted at 2019-12-09

はじめに

12月も早いもので中盤を迎えましたね。個人的なお話になりますがこの日はお誕生日なのでとってもハッピーな気分でいます。この記事もそんな気持ちで書いています。
最近、ServerlessFrameworkとAWSを触っているのでそれに関して自分なりにまとめてみました。使用したサービスは、LambdaとS3、SQS、API Gatewayなのでそのことについて書いてあります。

今回使用したコードはここにあります。

また、本記事を作成するにあたり以下の記事を参考にしました。
とことんサーバーレス①:Serverless Framework入門編
Serverless FrameworkとS3で超簡単な投票システムを作った話
Serverless FrameworkでLambdaをPython 3.6で使ってみた
Serverless Frameworkの使い方まとめ

準備・環境

Node.jsとPythonの環境が必要です。あとは、AWSのアカウントとかも必要になります。
Node.jsはServerlessFramework、PythonはAWS CLIを利用するために必要です。

1. Serverlessのインストール

npmでインストールします。

$ npm install -g serverless
$ sls -v
Framework Core: 1.54.0
Plugin: 3.1.2
SDK: 2.1.2
Components Core: 1.1.1
Components CLI: 1.4.0

このように表示されればインストール完了です。

2. IAMユーザーの作成とAWS Credentialを設定する

ServerlessFrameworkでAWSを利用するためのIAMを作成する必要があります。

IAMユーザーの作成

IAMのダッシュボードでユーザーからユーザーの作成をクリックすることで作成できます。基本的には、プログラムによるアクセスにチェックを入れることと今回、作成するのに必要な権限を与えれば大丈夫です。また、AWS Credentialのキーはここでしか取得できないので注意します。

AWS Credentialの設定

AWS Credentialの設定を行うためには、AWS CLIをインストールする必要があります。以下のコマンドでインストールできます。

$ pip install aws-sdk
$ aws --version
aws-cli/1.16.276 Python/3.7.3 Darwin/18.7.0 botocore/1.13.12

このように表示されればインストールが完了しています。
以下のようにしてAWSCredentialの設定を行います。

$ aws configure
AWS Access Key ID [None]: 作成したIAMのアクセスキーID
AWS Secret Access Key [None]: 作成したIAMのシークレットアクセスキー
Default region name [None]: ap-northeast-1
Default output format [None]: ENTER

引用: とことんサーバーレス①:Serverless Framework入門編

これでServerlessFrameworkを利用する準備は完了です。

3. ServerlessFrameworkでHelloWorld

まずは、Serverlessのプロジェクトを作成していきます。
以下のコマンドを打つことで作成できます。

$ sls create -t aws-python3 -p ほげほげぷろじぇくと

以下のように表示されれば成功です。

Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/..."
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.54.0
 -------'

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

次に、プロジェクト名のディレクトリにあるhandler.pyを以下のようにします。

handler.py
import json

def hello(event, context):
    body = {
        "message": "Hello, world",
    }

    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }

    return response

また、デプロイ先のリージョンを東京にします。

serverless.yml
service: ほげほげ

provider:
  name: aws
  runtime: python3.7
  region: ap-northeast-1 #東京リージョンにデプロイする

functions:
  hello:
    handler: handler.hello

そして、デプロイをします。

$ sls deploy

実行します。

$ sls invoke -f hello

以下のように表示されれば成功です。

{
    "statusCode": 200,
    "body": "{\"message\": \"Hello, world\"}"
}

また、AWSのLambdaのダッシュボードにデプロイした関数があるので、そこから実行してみても構いません。

4. API GateWayからLambdaを起動する

まず、先ほどデプロイしたhello関数をAPI Gatewayを利用して実行してみます。serverless.ymlを以下のように変更します。

serverless.yml
service: ほげほげ

provider:
 ...

functions:
  hello:
    handler: handler.hello
    # ここから追加
    events:
      - http:
          path: hello
          method: get
          cors: true

引用: https://serverless.com/framework/docs/providers/aws/events/apigateway/

このようにしてからsls deployをすると以下のようにendpointsがでます。

Service Information
...
endpoints:
  GET - https://hogehoge.amazonaws.com/dev/hello
functions:
  hello: hogehoge-dev-hello
layers:
  None

このAPIをcurlコマンドなどで叩くと、HelloWorldが表示されます。

5. S3にファイルを保存してそれを読み取る

S3にファイルを保存する関数の作成

新たに、sthreeput.pyを作成し、以下のように記述します。
ここでは、100までのランダムな数字を10個記述してあるテキストファイルを日付と時刻の名前で保存する関数を作成します。

sthreeput.py
import boto3 #Python用のAWS SDK

import datetime
import random

s3 = boto3.resource('s3')
bucketName = "hogehogebucket" # 自分で決めたS3バケットの名前

def index(event, context):
    dt = datetime.datetime.now()
    text = ""
    for i in range(10):
        num = str(random.randint(0, 100))
        text += num + " "

    key = "{0:%Y-%m-%d-%H-%M-%S}.txt".format(dt) #保存するファイル名になる

    obj = s3.Object(bucketName, key) 
    obj.put(Body=text.encode())

    return {
        "statusCode": 200,
        "body": text
    }

serverless.ymlの設定

ファイルを保存するようのS3バケットを作成します。また、S3にファイルを追加するための関数についても記述します。S3のバケット名は、すでにあるもの(自分以外のものでも)だと使えないので気をつけてください。

serverless.yml

functions:
  hello:
  ....
  #関数名
  sthree-put:
    handler: sthreeput.index #sthreeput.pyのindex関数を起動する

resources:
  Resources:
    SthreeBucket: # S3のバケットを作成する
      Type: AWS::S3::Bucket
      Properties:
        BucketName : "hogehogebucket" # S3のバケットの名前(すでにある名前はつけられない)

これでsls deployして、関数を実行してみましょう。うまくいっていれば、作成したバケットにファイルが保存されています。

S3にファイルが保存されたのをトリガーにしてファイルを見る

新たにsthreereceive.pyを以下のように作成します。

sthreereceive.py
import boto3

s3 = boto3.client('s3')

def index(event, context):
    bucket = event["Records"][0]["s3"]["bucket"]["name"] #バケットの名前
    key = event["Records"][0]["s3"]["object"]["key"] #保存したファイルの名前
    response = s3.get_object(Bucket=bucket, Key=key) #バケットの中にあるオブジェクトの取得
    body = response['Body'].read().decode("utf-8") #ファイルの中の情報を取得してutf-8にデコードする
    print(body)
    return body

S3にフックされてLambda関数が起動すると1番目の引数eventに以下のようなJSONが渡されます。

{
  "Records": [
    {
      "eventVersion": "2.0",
      "eventSource": "aws:s3",
      "awsRegion": "ap-northeast-1",
      "eventTime": "1970-01-01T00:00:00.000Z",
      "eventName": "ObjectCreated:Put",
      "userIdentity": {
        "principalId": "EXAMPLE"
      },
      "requestParameters": {
        "sourceIPAddress": "127.0.0.1"
      },
      "responseElements": {
        "x-amz-request-id": "EXAMPLE123456789",
        "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH"
      },
      "s3": {
        "s3SchemaVersion": "1.0",
        "configurationId": "testConfigRule",
        "bucket": {
          "name": "example-bucket",
          "ownerIdentity": {
            "principalId": "EXAMPLE"
          },
          "arn": "arn:aws:s3:::example-bucket"
        },
        "object": {
          "key": "test/key", 
          "size": 1024,
          "eTag": "0123456789abcdef0123456789abcdef",
          "sequencer": "0A1B2C3D4E5F678901"
        }
      }
    }
  ]
}

この情報からS3バケットの名前とファイル名であるkeyを取得します。

serverless.ymlの設定

関数の設定とS3のイベントの設定を行います。

serverless.yml
...
functions:
  sthree-put:
    handler: sthreeput.index #sthreeput.pyのindex関数を起動する

  sthree-receive:
    handler: sthreereceive.index
    events:
    - s3:
        bucket: "sthreebucket-hogehoge" #バケットの名前
        event: s3:ObjectCreated:* #S3に作られた時に起動
        existing: true #S3のバケットを作成しているためこれをつける

最後のexisting: tureがない場合、バケットが作成されるためすでに同じ名前のバケットがある場合デプロイすることができません。先ほど、バケットを作成したためこのオプションを設定します。serverless.ymlのResourcesの部分を消し、sls removeをしてまっさらな状態にすれば、最後のオプションがなくてもデプロイできます。

さて、これでsls deployを行い、sthreeput関数を起動すれば、バケットにファイルが保存され、sthreereceive関数が起動していることがLambdaのダッシュボードのモニタリングから確認できます。

また、今回はS3に保存されたことをトリガーにして、ファイルの確認を行いましたが、バケットの名前と確認したいファイルのキーを指定してファイルの確認をすることもできます。

6. SQSにメッセージを送信してそのメッセージ受け取る

SQSにメッセージを送る関数の作成

新たにsendmessage.pyを以下のように記述します。

sendmessage.py
import boto3
import random
import json

sqs = boto3.resource('sqs')
queueName = "hogehoge-queue"


def index(event, context):
    queue = sqs.get_queue_by_name(QueueName=queueName)  # キューを取得

    message = ""  # 送信するメッセージ

    for i in range(10):
        num = str(random.randint(0, 100))
        message += num + " "

    print(message)

    queue.send_message(MessageBody=json.dumps({ "message": message })) # メッセージの送信

    return message

serverless.ymlの設定

メッセージを送信するためのキューを作成します。S3のバケットとは違いSQSのキューの名前は、他のアカウントの人と名前が同じでも大丈夫です。

serverless.yml
provider:
  ...
  iamRoleStatements:
  - Effect: "Allow"
    Action:
    - "s3:PutObject"
    - "s3:GetObject"
    - "s3:ListBucket"
    - "sqs:CreateQueue"
    - "sqs:DeleteQueue"
    - "sqs:SendMessage"
    - "sqs:GetQueueUrl"
    Resource: "*"
...
functions:
  sthree-put:
  ...
  sendmessage:
    handler: sendmessage.index

resources:
  Resources:
    SthreeBucket: 
    ...
    hogehogeQueue:
      Type: AWS::SQS::Queue # SQSのキュー作成
      Properties:
        QueueName: "hogehoge-queue"

これでsls deployを行い、sendmessage関数を起動すれば、作成したキューにメッセージが追加されているはずです。

SQSにメッセージを送るのトリガーにメッセージを受け取る

次に、先ほど送信したメッセージを受け取ってみましょう。receivemessage.pyを作成して以下のように記述します。

receivemessage.py
import json

def index(event, context):
    body = json.loads(event["Records"][0]["body"]) 
    message = body["message"]
    print(message)
    return message

SQSにフックされてLambda関数が起動すると1番目の引数eventに以下のようなJSONが渡されます。

{
  "Records": [
    {
      "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
      "receiptHandle": "MessageReceiptHandle",
      "body": "Hello from SQS!", 
      "attributes": {
        "ApproximateReceiveCount": "1",
        "SentTimestamp": "1523232000000",
        "SenderId": "123456789012",
        "ApproximateFirstReceiveTimestamp": "1523232000001"
      },
      "messageAttributes": {},
      "md5OfBody": "7b270e59b47ff90a553787216d55d91d",
      "eventSource": "aws:sqs",
      "eventSourceARN": "arn:aws:sqs:ap-northeast-1:123456789012:MyQueue",
      "awsRegion": "ap-northeast-1"
    }
  ]
}

ここからbodyを取得することによって、送信したメッセージを取得できます。

serverless.ymlの設定

serverlss.yml
...
functions:
  receivemessage:
    handler: receivemessage.index
    events:
      - sqs:
          # arn:aws:sqs:region:accountid:hogehoge-queue みたいになる
          arn:
            Fn::Join:
            - ':'
            - - arn
              - aws
              - sqs
              - Ref: AWS::Region
              - Ref: AWS::AccountId
              - "hogehoge-queue"

これでsls deployをしてsendmessage関数を起動するとreceivemessage関数が起動します。receivemessage関数がエラーを起こしているとなんども起動されるので注意しましょう。この場合、キューのメッセージをクリアすることによって止めることができます。

おわりに

必要なければ削除しましょう。
以下のコマンドできれいさっぱりになります。

sls remove

バケットが上手く消えなかったりするのですがその時は、バケットを空にしたり、直接スタックを削除すると良いかもしれません。

AWSはいろんなサービスが他にもあるのでと触ってみようかなぁと思いました

6
4
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
4