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.yamlでContentUri: 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結果が表示された。