ちばのてっく

積極的にアウトプット

FlaskをLambda+API Gatewayでホスティングしてみた

Flaskをローカルではなくクラウド上にホスティングする方法を知っておきたい。 ということで、手始めにLambda + API Gatewayホスティングしてみる。

やってみた

Lambda + API Gatewayということで、AWS SAMを使うのが手っ取り早そう。sam initでテンプレートを取得して、それをカスタマイズする方針で進める。

sam init

ウィザードには以下の通りに回答していく。

You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.

# 用意されたテンプレートを使いたいため、1
Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1

# `Hello World Example`が最も簡易な構造な気がしたため、1
Choose an AWS Quick Start application template
        1 - Hello World Example
        2 - Data processing
        3 - Hello World Example with Powertools for AWS Lambda
        4 - Multi-step workflow
        5 - Scheduled task
        6 - Standalone function
        7 - Serverless API
        8 - Infrastructure event management
        9 - Lambda Response Streaming
        10 - Serverless Connector Hello World Example
        11 - Multi-step workflow with Connectors
        12 - GraphQLApi Hello World Example
        13 - Full Stack
        14 - Lambda EFS example
        15 - Hello World Example With Powertools for AWS Lambda
        16 - DynamoDB Example
        17 - Machine Learning
Template: 1

# ランタイムにPythonを選択したかったため、また、パッケージはコンテナイメージではなくZipとするため、y
Use the most popular runtime and package type? (Python and zip) [y/N]: y

# X-Rayは必要ないため、n
Would you like to enable X-Ray tracing on the function(s) in your application?  [y/N]: n

# CloudWatch Application Insightsは必要ないため、y
Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]: n

# ログをJSON型式で出力しなくてもよいため、n
Would you like to set Structured Logging in JSON format on your Lambda functions?  [y/N]: n

# プロジェクト名はデフォルト(sam-app)で問題ないため、何も入力せずEnter
Project name [sam-app]:

    -----------------------
    Generating application:
    -----------------------
    Name: sam-app
    Runtime: python3.9
    Architectures: x86_64
    Dependency Manager: pip
    Application Template: hello-world
    Output Directory: .
    Configuration file: sam-app/samconfig.toml

    Next steps can be found in the README file at sam-app/README.md


Commands you can use next
=========================
[*] Create pipeline: cd sam-app && sam pipeline init --bootstrap
[*] Validate SAM template: cd sam-app && sam validate
[*] Test Function in the Cloud: cd sam-app && sam sync --stack-name {stack-name} --watch

すると、以下のディレクトリ構造がつくられる。これらをベースに、以下のようにカスタマイズしていく。

  • template.yamlの修正
  • layer/requirements.txtの新規追加
  • app.pyの修正
.
└── sam-app
    ├── README.md
    ├── __init__.py
    ├── events
    │   └── event.json
    ├── hello_world
    │   ├── __init__.py
    │   ├── app.py
    │   └── requirements.txt
    ├── samconfig.toml
    ├── template.yaml
    └── tests
        ├── __init__.py
        ├── integration
        │   ├── __init__.py
        │   └── test_api_gateway.py
        ├── requirements.txt
        └── unit
            ├── __init__.py
            └── test_handler.py

template.yamlの修正

主な修正点

  • Flask側でパスルーティングを行うために、すべてのサブリソースに対するリクエストを処理するプロキシリソースを追加。(慣例的にproxy+としたが、任意の文字列にプラス記号がついたものであればOKらしい。)

  • 外部ライブラリをLambdaレイヤーにまとめておくために、Type: AWS::Serverless::LayerVersionのFlaskAppLayer句を追加。

  • AWSLambdaBasicExecutionRoleというCloudWatchへログを出力する基本的な権限+DynamoDBへアクセスするための権限を与えるために、Policies句を追加。

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: flask-app

Globals:
  Function:
    Timeout: 3
    MemorySize: 128

Resources:
  FlaskAppFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.12
      Architectures:
        - x86_64
      Layers: 
        - !Ref FlaskAppLayer
      Events:
        Root:
          Type: Api
          Properties:
            Path: /
            Method: ANY
        Al:
          Type: Api
          Properties:
            Path: /{proxy+}
            Method: ANY
      Policies:
        - AWSLambdaBasicExecutionRole
        - Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - dynamodb:Scan
                - dynamodb:PutItem
              Resource: 
                - !Sub 'arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/temp'
  FlaskAppLayer:
    Type: AWS::Serverless::LayerVersion
    Properties:
      CompatibleRuntimes:
        - python3.12
      ContentUri: layer/
      LayerName: flask-app
    Metadata:
      BuildMethod: python3.12

layer/requirements.txtの新規追加

template.yamlContentUri: layer/と定義したため、template.yamlと同階層にlayerディレクトリを作成し、その中にrequirementes.txtを作成する。なお、requirementes.txtに記載した外部ライブラリは自動で取得され、Lambdaレイヤーが作成される。

requirementes.txt

aws-wsgi
flask

app.pyの修正

Hello worldのような文字列の返却だけではなんとなくさみしいため、AWSリソースへもアクセスさせてみたい。 そこで、先日の記事を参考に、dynamoDBテーブルへscanを行う処理を記載する。

また、Flaskを用いて、LambdaとAPI Gateway間をWSGIでやり取りをするために、AWSGIを利用する。そのため、lambda_handlerのreturnにawsgi.response(app, event, context)を設定する。 (AWSGIもWSGIもよくわかってはいない、、)

app.py

from flask import Flask
import awsgi
import boto3

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello world!</p>"

@app.route("/dynamodb_scan/")
def dynamodb_scan():
    client = boto3.client("dynamodb")
    response = client.scan(
        TableName="temp"
    )
    return response

def lambda_handler(event, context):
    return awsgi.response(app, event, context)  

3点のカスタマイズが完了したら、ビルド、デプロイを行う。

sam build --use-container
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 [sam-app]:
        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]:
        #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]:
        FlaskAppFunction has no authentication. Is this okay? [y/N]: y
        FlaskAppFunction has no authentication. Is this okay? [y/N]: y
        Save arguments to configuration file [Y/n]: 
        SAM configuration file [samconfig.toml]: 
        SAM configuration environment [default]: 

Lambdaコンソール > アプリケーション > sam-app から確認できるAPIエンドポイントへ、ブラウザからアクセスしたところ、期待通りHello world!が表示された。
また、<確認したAPIエンドポイント>/dynamodb_scanにアクセスしたところ、期待通りDynamoDBテーブルのscan結果が表示された。