【Python】FlaskアプリのLambdaデプロイ【AWS Lambda】
仕事でAuth0を使うことがあり、取り急ぎの動作サンプルとしてPythonのサンプルアプリをAWS Lambdaにデプロイしようとした(会社的な都合でデプロイ環境はLambda一択)のですが、なかなかうまくいかなかったのでそれの対応を備忘として残しておきます。
FlaskアプリのLambdaデプロイ方法
色々とあるみたいですが、主なところはZappa、Serverless Frameworkを使ってデプロイするという方法みたいです。
もちろんパッケージをzipで固めてaws cliでLambdaに直接アップロードするという方法も出来なくはないですが、外部からhttpリクエストを受け付けるアプリケーションのため、API Gatewayも設定する必要があり、IAMの設定などを考えると自分でやるのではなく何らかのフレームワークの仕組みに乗っかってデプロイしたいなーと思いました。
また、LambdaにデプロイするのであればPythonだとChaliceが便利なので、サンプルアプリをChaliceで書き直してしまえば良いかな(ルーティングなどの記述はほぼそのまま)とも思ったのですが、session管理やOAuthの機能などはChaliceデフォルトでは持っておらず外部ライブラリを導入する必要がありそうで面倒くさそうだったので断念しました。(とりあえず動くものを作るのが最優先だったので)
面倒だからgit clone
したものをそのままデプロイしたい!!と思い、Zappa、ServerlessFrameworkを試してみることにしました。
Zappa
ZappaでのLambdaデプロイは以下の記事を参考にさせていただきました。
基本的には↑の記事の手順そのままで行いました。
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の環境に不適合のため起きているようです。
解消するためにはAmazon Linuxの環境でLambdaパッケージを作成しなくてはならないとのことなのですが、EC2を立ち上げて実施するのも面倒そうですし、わざわざDockerコンテナを立ち上げてaws configを設定して実施するのも面倒なので他の方法で実施することにしました。
Serverless Framework
Serverless Frameworkについては以下の記事を参考にさせていただきました。
こちらも基本的には↑の記事そのままの手順を実施しております。
# 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もありました。
AWS側の対応が出来ていないことが原因のようで、とりあえずFlaskのバージョンを下げれば解消出来るとのこと。
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で最初から書き直してしまったほうが早かったのではという気もしますが、まぁ初めて触った仕組みだったので勉強になったので良しとします。