Flask + ZappaでサーバーレスWeb App on AWSを作ってみた

A8バナー広告

この記事で書くこと

業務用のユーザー状態管理WebアプリをFlask + Zappaでサーバーレス化した。

だいたいの記事はチュートリアルで終わっていて、業務導入してみてハマったところや、工夫しがいあるところの紹介がなかなか足りない気がした。

そこで、この記事では作業途中でハマってしまって大困りしたことを紹介する。

この記事で書かないこと

チュートリアルレベルのこと。

簡単に書くと

  • ユーザー管理DB部分はRDSなんだけど、どういう構成にしたらいいんだ?
    • サーバーデプロイと同じく、WebアプリのバックエンドにRDSを使える
  • 環境変数のたぐいはどうやって管理したらいいんだ?
    • Zappa設定ファイルjsonに埋め込みできる。
    • または、S3に環境変数ファイルjsonを設置できる。こちらの方がメンテナンス性が良い。
  • SSL証明書の取得とか面倒そうだ。。。
    • AWSでドメイン取得するなら、Route53で証明書を発行して、Zappa設定ファイルjsonに証明書IDを書くだけでいい。
  • デプロイ時になんかエラーになったんだけど、何が悪いのかわからない・・・
    • AWS Cloudwatchを見る。デプロイ時のエラーログもCloudwatchに出力される。
  • 自作のPythonパッケージがAWS Lamdaに送られなくて、deploy時にエラーになる!
    • デプロイ操作前に、deployコマンドを打つ環境で自作Pythonパッケージをすべてインストールしておく。
    • requirements.txtにも依存関係をきちんとすべて書いておくのがベスト
  • AWSにデプロイした後に、ログイン機能が意図どおりに動かない!なんでだ?
    • HTMLのformタグのactionにパスを直書きしていた。url_for関数を使わないとアウト。
    • これは、Zappaの問題ではない。単にぼくがアホだっただけ。
  • RDSに接続ができない・・?どうしてだ?
    • RDSがVPNに存在していて、インターネットに直接の経路がないとき、WebアプリからDBへの通信ができない。
    • Zappa設定ファイルjsonにVPNのsubnet idとsecurity group idを記載すればいい。
  • RDSにつなげるために、VPN設定を書いたら、S3に環境変数ファイルjsonをおいたら、参照できなくなった
    • S3バケットがインターネットに直接の経路がないとき、ZappaはS3からファイルを参照できない。
    • S3 <-> VPNのエンドポイントを作成してやる必要がある。

詰まったところ(詳細)

サーバーレスというからには、データベースもサーバーレスでないといけない?

ただのぼくの思いすぎだった。

Flaskで組まれているWebアプリケーションだけが、AWS Lambdaに置き換えられるだけで、データベースは何ら関係ない。

もちろん、RDSでなくDynamoDBを使って完全サーバーレスにすることも可能。

環境変数のたぐいはどうやって設定したらいいんだろう?

FlaskではDBの接続先などで環境変数を多用すると思う(ぼくだけ?)

Zappaでは、zappa設定ファイルjsonに環境変数を記述することになっている。

別の方法として、S3に環境変数を羅列したJSONを置くこともできる

個人的には後者の方が良いと思う。いちいち設定が変わるたびにアプリのdeployしなおしは面倒だ。

それにDBの接続情報などセキュアな情報を個人管理はしたくない。

セキュアなクラウドに置いてしまったほうが安全。

デプロイ時になんかエラーになったんだけど、何が悪いのかわからない・・・

Zappaデプロイ時にこういうエラーメッセージがでることがある。

Errorなのか、Warningなのか、はっきりしろ!と言いたい。

Zappaのデプロイ仕様では完全にデプロイされた時はURLが表示されるが、この時は表示されなかった。と、いうことはデプロイ失敗している。

ここでハマったのは、「どこの何を調べたらエラー原因がわかるのか?」ということ。

答えはAWS Cloudwatchである。ここにデプロイ時のログも出力されている。

原因はpipに存在しない内部開発のパッケージが見つからないことだった。

自作のPythonパッケージがAWS Lamdaに送られなくて、deploy時にエラーになる!

Zappaはrequirments.txtを読んで、依存パッケージの準備をしている。

なので、個人開発のパッケージがgithubやbitbucketにあるときは、requirements.txtに依存関係を書かなければいけない。

ということは、個人リポジトリの利用には、requirements.txtに個人リポジトリ参照を記述すればいいのではないか?

こういう時はリポジトリが存在する場所を書くための記法がある

1リポジトリ=1パッケージでない時はどうするか?

困ったことに今回は、1リポジトリ内部で複数のパッケージを管理していた。

つまり、今回のdirectory構成は、setup.pyがサブディレクトリの下に位置している。

さて、どうするか?実はrequirements.txtにそういう記法があった。

今回はBitbucketを利用しているので、bitbucketへのpipアクセス式を書く。

それでもzappaのデプロイが失敗する

たぶん、先にローカルのvirtualenvにインストールしておく必要がある。ということで、手順は次の通り。

  1. requirements.txtにもれなく依存関係を書く
  2. ローカルのvirtulenvに依存関係をインストールする

なんだかよくわからないが(エンジニア的にやばいやつ)、うまくいった!

AWSにデプロイした後に、ログイン機能が意図どおりに動かない!なんでだ?

今回のWebアプリでは、ログイン機能が付いている。

で、AWSにデプロイされたアプリでログインをためしてみると・・・・あれ、白い画面に forbitten?なんだこれ?つまりこういう状態。

expected: base-URL/dev/login -> base-URL/dev/profile
actual: base-URL/dev/login -> base-URL/login

そもそもログイン後には/profileに遷移する想定なのに、/loginになるなど意味不明。

ローカルで再検証してみると、確かに、ログイン後にログイン完了画面になる。

いろいろ仮説を考えて見た。

  • FlaskでDomain Prefixを設定しないといけない説
  • Zappaのバグ説 / Zappaの仕様説
    • 一致する現象がまったく見つからない
  • そもそもCustom Domainがないと、Zappaは意図どおりの動作ができない説
    • そんなわけがない

どれも妥当性が低い。自分で調査を進めていくと次のことがわかった。

  • アカウントが正しくても、まちがっていても、同じ現象が発生する。
  • ログイン処理にログを仕込んでみた。ログをたどると、ログイン処理関数に到達していないことが判明。

そこで、ログインフォームのHTMLを見てみると、驚愕の事実が判明(自分の中で)

なんと、login処理のコントローラの宛先がパス直書き指定されているではないか。

ログイン後に正しくページしない原因はこれだった。遷移先が直書きでパス指定されていると、Flaskは強制的にbase-URLの直下に遷移しようとする。結果的に存在しないURLにPOSTを試行していた・・・とそういうわけだ。

正しくは url_for関数をHTMLの中で記述して、遷移先URLを取得しなければいけない。

S3から環境変数設定ファイルが参照できない!

同じ問題がここに出ている

S3をVPCの中に配置すればいい。AWSのドキュメント