MENU

【Python】FlaskアプリのLambdaデプロイ【AWS Lambda】

仕事でAuth0を使うことがあり、取り急ぎの動作サンプルとしてPythonのサンプルアプリAWS Lambdaにデプロイしようとした(会社的な都合でデプロイ環境はLambda一択)のですが、なかなかうまくいかなかったのでそれの対応を備忘として残しておきます。

FlaskアプリのLambdaデプロイ方法

色々とあるみたいですが、主なところはZappaServerless Frameworkを使ってデプロイするという方法みたいです。
もちろんパッケージをzipで固めてaws cliでLambdaに直接アップロードするという方法も出来なくはないですが、外部からhttpリクエストを受け付けるアプリケーションのため、API Gatewayも設定する必要があり、IAMの設定などを考えると自分でやるのではなく何らかのフレームワークの仕組みに乗っかってデプロイしたいなーと思いました。
また、LambdaにデプロイするのであればPythonだとChaliceが便利なので、サンプルアプリChaliceで書き直してしまえば良いかな(ルーティングなどの記述はほぼそのまま)とも思ったのですが、session管理やOAuthの機能などはChaliceデフォルトでは持っておらず外部ライブラリを導入する必要がありそうで面倒くさそうだったので断念しました。(とりあえず動くものを作るのが最優先だったので)

github.com

面倒だからgit cloneしたものをそのままデプロイしたい!!と思い、ZappaServerlessFrameworkを試してみることにしました。

Zappa

ZappaでのLambdaデプロイは以下の記事を参考にさせていただきました。

dev.classmethod.jp

基本的には↑の記事の手順そのままで行いました。
profileは会社のAWSアカウントのRoleがあるのでそこに合わせたものを指定し、S3 Bucketも既存のものを選択したぐらいです。
アプリケーション名は{{ pythonのファイル名 }}.{{ app名 }}ですが、サンプルアプリではserver.pyというファイルの中に、

app = Flask(__name__, static_url_path='/public', static_folder='./public')

という形でアプリが作成されているので、server.appとしています。

初期化が完了するとzappa_settings.jsonが作成されて以下の内容となります。

# zappa_settings.json
{
    "dev": {
        "app_function": "server.app",
        "aws_region": "us-west-2",
        "profile_name": "your-profile-name",
        "project_name": "your-project-name-",
        "runtime": "python3.8",
        "s3_bucket": "your-s3bucket-name"
    }
}

以下コマンドでデプロイします。

$ zappa deploy dev

するとAPI Gatewayの接続でエラーが出ているようです。

〜略〜

Deploying API Gateway..
Scheduling..
Unscheduled ddbe0735534d6bd0f7e78d48d34976715d7ca-handler.keep_warm_callback.
Scheduled ddbe0735534d6bd0f7e78d48d34976715d7ca-handler.keep_warm_callback with expression rate(4 minutes)!
Error: Warning! Status check on the deployed lambda failed. A GET request to '/' yielded a 502 response code.

zappa tailでログを確認すると、以下のエラーが出ていました。

$ zappa tail

〜略〜
[1623504007679] [DEBUG] 2021-06-12T13:20:07.679Z cdd125e4-8885-4e93-ab87-3675a29ac599 Zappa Event: {}
[1623504249069] Instancing..
[1623504250243] [ERROR] ImportError: /var/task/cryptography/hazmat/bindings/_padding.abi3.so: invalid ELF header
Traceback (most recent call last):
  File "/var/task/handler.py", line 602, in lambda_handler
    return LambdaHandler.lambda_handler(event, context)
  File "/var/task/handler.py", line 245, in lambda_handler
    handler = cls()
  File "/var/task/handler.py", line 139, in __init__
    self.app_module = importlib.import_module(self.settings.APP_MODULE)
  File "/var/lang/lib/python3.8/importlib/__init__.py", line 127, in import_module

〜略〜

Authlibでinvalid ELF headerのエラー

これを色々と調べてみると、cryptographyのソースコードの中にネイティブバイナリファイルが含まれているためのようです。
zappaはローカルのMacの環境でパッケージをダウンロード&インストールしてLambdaによしなにデプロイしてくれる、という仕組みのため、Macコンパイルされたバイナリが、Lambda側のAmazon Linuxの環境に不適合のため起きているようです。

github.com

dev.classmethod.jp

解消するためにはAmazon Linuxの環境でLambdaパッケージを作成しなくてはならないとのことなのですが、EC2を立ち上げて実施するのも面倒そうですし、わざわざDockerコンテナを立ち上げてaws configを設定して実施するのも面倒なので他の方法で実施することにしました。

Serverless Framework

Serverless Frameworkについては以下の記事を参考にさせていただきました。

blog.serverworks.co.jp

こちらも基本的には↑の記事そのままの手順を実施しております。

# serverless.yml
service: your-app-name

plugins:
  - serverless-python-requirements
  - serverless-wsgi

custom:
  wsgi:
    app:server.app
    packRequirements: false
  pythonRequirements:
    dockerizePip: true

provider:
  name: aws
  runtime: python3.8
  stage: dev
  region: us-west-2
  profile: your-profile-name


functions:
  app:
    handler: wsgi.handler
    events:
      - http: ANY /
      - http: 'ANY {proxy+}'

前述の通りaws環境のprofileがあるのでprofileを設定しました。
以下コマンドでデプロイします。

$ SLS_DEBUG=* sls deploy -v
Serverless: Deprecation warning: Detected ".env" files. In the next major release variables from ".env" files will be automatically loaded into the serverless build process. Set "useDotenv: true" to adopt that behavior now.
            More Info: https://www.serverless.com/framework/docs/deprecations/#LOAD_VARIABLES_FROM_ENV_FILES
Serverless: To ensure safe major version upgrades ensure "frameworkVersion" setting in service configuration (recommended setup: "frameworkVersion: ^2.45.2")

Serverless: Load command interactiveCli
Serverless: Load command config
Serverless: Load command config:credentials
Serverless: Load command config:tabcompletion
Serverless: Load command config:tabcompletion:install
Serverless: Load command config:tabcompletion:uninstall
Serverless: Load command create
Serverless: Load command install
Serverless: Load command package
Serverless: Load command deploy
Serverless: Load command deploy:function
Serverless: Load command deploy:list
Serverless: Load command deploy:list:functions
Serverless: Load command invoke
Serverless: Load command invoke:local
Serverless: Load command info
Serverless: Load command logs
Serverless: Load command metrics
Serverless: Load command print
Serverless: Load command remove
Serverless: Load command rollback
Serverless: Load command rollback:function
Serverless: Load command slstats
Serverless: Load command plugin
Serverless: Load command plugin
Serverless: Load command plugin:install
Serverless: Load command plugin
Serverless: Load command plugin:uninstall
Serverless: Load command plugin
Serverless: Load command plugin:list
Serverless: Load command plugin
Serverless: Load command plugin:search
Serverless: Load command config
Serverless: Load command config:credentials
Serverless: Load command upgrade
Serverless: Load command uninstall
Serverless: Load command requirements
Serverless: Load command requirements:clean
Serverless: Load command requirements:install
Serverless: Load command requirements:cleanCache
Serverless: Load command wsgi
Serverless: Load command wsgi:serve
Serverless: Load command wsgi:install
Serverless: Load command wsgi:clean
Serverless: Load command wsgi:command
Serverless: Load command wsgi:command:local
Serverless: Load command wsgi:exec
Serverless: Load command wsgi:exec:local
Serverless: Load command wsgi:manage
Serverless: Load command wsgi:manage:local
Serverless: Load command wsgi:flask
Serverless: Load command wsgi:flask:local
Serverless: Load command login
Serverless: Load command logout
Serverless: Load command generate-event
Serverless: Load command test
Serverless: Load command dashboard
Serverless: Load command output
Serverless: Load command output:get
Serverless: Load command output:list
Serverless: Load command param
Serverless: Load command param:get
Serverless: Load command param:list
Serverless: Load command studio

〜中略〜

Serverless: Invoke aws:info
Service Information
service: xxxxxxxxxxx
stage: dev
region: us-west-2
stack: xxxxxxxxxxx
resources: 12
api keys:
  None
endpoints:
  ANY - https://xxxxxxxxxxx.execute-api.us-west-2.amazonaws.com/dev
  ANY - https://xxxxxxxxxxx.execute-api.us-west-2.amazonaws.com/dev/{proxy+}
functions:
  app: xxxxxxxxxx
layers:
  None

Stack Outputs
AppLambdaFunctionQualifiedArn: arn:aws:lambda:us-west-2:xxxxxxxxxxxxx:function:cf-auth0-login-app-dev-app:9
ServiceEndpoint: https://xxxxxxxxxxi.us-west-2.amazonaws.com/dev
ServerlessDeploymentBucketName: xxxxxxxxxxxxxxxserverlessdeploymentbucket-wwfp1x062m4w

Serverless: Invoke aws:deploy:finalize

無事デプロイに成功したようです。

ところがServiceEndpoint: https://xxxxxxxxxxi.us-west-2.amazonaws.com/devで表示されているURLにブラウザからアクセスするとInternal Server Errorが返ってきてしまいました。

ClouwWatchのログを見ると以下のようなエラーが。。

[ERROR] Runtime.ImportModuleError: Unable to import module 'wsgi_handler': No module named 'werkzeug._compat'
Traceback (most recent call last):

Unable to import module 'wsgi': No module named 'werkzeugのエラー

こっちでもか〜。。という感じなのですが、また何らかのエラーを踏んでしまったようです。
以下のようなissueもありました。

github.com

github.com

AWS側の対応が出来ていないことが原因のようで、とりあえずFlaskのバージョンを下げれば解消出来るとのこと。

stackoverflow.com

requirements.txtにて特にそれぞれのバージョンは指定しなかったので依存関係が成り立つ最新バージョンがインストールされていたのですが、Flask==1.1.4を指定することで解消しました。

〜略〜
certifi
cffi
cfn-flip
chardet
click
cryptography
durationpy
Flask==1.1.4
future
hjson
idna
itsdangerous
Jinja2
〜略〜

ちなみに

Flask==1.1.4を指定すると、Zappaでのデプロイ時に起きていたinvalid ELF headerのエラーも起きなくなりました。
Flaskのバージョンを下げても依存関係にあるcryptographyは変わらず3.4.7のままなので謎です。。

おわりに

とりあえず動くものはデプロイ出来たもののなんやかんややり方調査から含めて5時間ぐらいかかってしまったのでなんだかんだChaliceで最初から書き直してしまったほうが早かったのではという気もしますが、まぁ初めて触った仕組みだったので勉強になったので良しとします。