はじめに
こんにちは。アプリケーションサービス部の保田(ほだ)です。
たまに Python 製の軽量サーバーレスアプリケーションのデプロイツールである Zappa を使う場面があるのですが、誰も Frank Zappa の話をしないので少し寂しいです。
ちなみに Pound for a Brown という曲が好きです。
そんな訳で今回は Zappa の tips についてお話します。
要約
- README.md には書いてないけど、zappa_settings.json に
alb_vpc_config.Scheme
という設定項目があり、ここを設定すれば内部 ALB も外部 ALB も作れる
"alb_vpc_config": { "CertificateArn": "arn:aws:acm:ap-northeast-1:012345678901:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "SubnetIds": ["subnet-xxxxx", "subnet-yyyy"], "SecurityGroupIds": ["sg-zzzzz"], "Scheme": "internal" // コレ( internet-facing | internal) },
- ただし、内部 ALB、外部 ALB 問わず、ここ の
Delay
の値を大きくしないと高確率でデプロイに失敗する
目次
詳細
内容としては上に書いたことがすべてですので、以降は細かいハマり所の解説を交えつつ理解を深めながら Zappa のお話をしていきます。
Zappa とは
まず、 Zappa は Python 製のサーバーレスアプリケーションのデプロイツールです。
API Gateway + Lambda が基本構成となっており、Flask や Django と組みあわせてサーバーサイドのロジックを構築するのがよくある使い方となっています。
それ以外にも S3 へのファイルアップロードや CloudWatch Events による定期実行など、イベント駆動の場合にも対応しています。
後者のイベント駆動の場合は、 Lambda 関数のロジックとしては特に通常とコード量は変わりませんが、前者の Flask や Django と組み合わせた場合は劇的にコード量が削減できます。
削減できるというより、普通の Flask や Django の書き方さえ分かれば OK といった感じになります。
また、 CloudFormation でいうところのテンプレートにあたるものは zappa_settings.json
というファイルで定義でき、これもシンプルに書けるように作られています。
詳しくは公式の README.md を見て頂ければと思います。
前提といくつかの注意事項
現在(以降も執筆時点 2021/08/27の意味で使います)で最新の Zappa を使うとします。
また、 Flask は現在 2 系まで出ていますが、 1 系じゃないと Zappa と Flask の双方が依存するライブラリ Werkzeug のバージョンが合致せず pip install 時にエラーが出るので注意してください。
そして、Zappa が依存するライブラリの一つである troposphere も最新版は 3 系なのですが、 3 系だと Zappa と合わないみたいなので注意です。
まとめると、以下のライブラリを使っていることを前提とします。
Flask==1.1.4 zappa==0.53.0 troposphere==2.7.1
概して Zappa 側が色々追い付いていないような感じがありますね。
また、サーバーサイドのロジックは以下とし、ファイル名は main.py とします。
from flask import Flask app = Flask(__name__) @app.route('/v1/', methods=['GET']) def v1(): return 'ok'
lambda_hanlder(event, context)
の関数はどこ?と思うかもしれませんが、それは Zappa のライブラリ側にありそこから上で書いたコードがよしなに呼び出されるという感じですね。
外部 ALB + Lambda の構成を作る
さて説明の都合上、まず外部 ALB + Lambda の構成を作ります。
Advanced Settings を参考にすれば、 ALB + Lambda の構成は例えば以下のように定義できます。
{ "dev": { "app_function": "main.app", "aws_region": "ap-northeast-1", "profile_name": "default", "project_name": "zappa-example", "runtime": "python3.8", "s3_bucket": "zappa-xxxxxx", "apigateway_enabled": false, // API Gateway を無効に "alb_enabled": true, // ALB を有効に "alb_vpc_config": { // ALB の基本設定 "CertificateArn": "arn:aws:acm:ap-northeast-1:012345678901:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", // 証明書 "SubnetIds": ["subnet-xxxxx", "subnet-yyyy"], // 紐づけるサブネット "SecurityGroupIds": ["sg-zzzzz"], // セキュリティグループ } } }
ではデプロイしてみましょう。
$ zappa deploy dev Calling deploy for stage dev.. Downloading and installing dependencies.. - pyyaml==5.4.1: Downloading 100%|██████████████████████████████████████| 662k/662k [00:00<00:00, 7.28MB/s] - markupsafe==2.0.1: Downloading 100%|████████████████████████████████████| 30.6k/30.6k [00:00<00:00, 4.46MB/s] Packaging project as zip. Uploading zappa-example-dev-1630036951.zip (6.0MiB).. 100%|████████████████████████████████████| 6.32M/6.32M [00:05<00:00, 1.16MB/s] Deploying ALB infrastructure... Waiting for load balancer [arn:aws:elasticloadbalancing:ap-northeast-1:012345678901:loadbalancer/app/zappa-example-dev/xxxxxxxxxxxxxxxx] to become active.. Oh no! An error occurred! :( ============== Traceback (most recent call last): File "/home/xxxx/.local/share/virtualenvs/zappa-example-xxxx/lib/python3.8/site-packages/zappa/cli.py", line 3422, in handle sys.exit(cli.handle()) File "/home/xxxx/.local/share/virtualenvs/zappa-example-xxxx/lib/python3.8/site-packages/zappa/cli.py", line 588, in handle self.dispatch_command(self.command, stage) File "/home/xxxx/.local/share/virtualenvs/zappa-example-xxxx/lib/python3.8/site-packages/zappa/cli.py", line 630, in dispatch_command self.deploy(self.vargs["zip"], self.vargs["docker_image_uri"]) File "/home/xxxx/.local/share/virtualenvs/zappa-example-xxxx/lib/python3.8/site-packages/zappa/cli.py", line 947, in deploy self.zappa.deploy_lambda_alb(**kwargs) File "/home/xxxx/.local/share/virtualenvs/zappa-example-xxxx/lib/python3.8/site-packages/zappa/core.py", line 1609, in deploy_lambda_alb waiter.wait(LoadBalancerArns=[load_balancer_arn], WaiterConfig={"Delay": 3}) File "/home/xxxx/.local/share/virtualenvs/zappa-example-xxxx/lib/python3.8/site-packages/botocore/waiter.py", line 53, in wait Waiter.wait(self, **kwargs) File "/home/xxxx/.local/share/virtualenvs/zappa-example-xxxx/lib/python3.8/site-packages/botocore/waiter.py", line 362, in wait raise WaiterError( botocore.exceptions.WaiterError: Waiter LoadBalancerAvailable failed: Max attempts exceeded. Previously accepted state: For expression "LoadBalancers[].State.Code" we matched expected path: "provisioning" at least once ============== Need help? Found a bug? Let us know! :D File bug reports on GitHub here: https://github.com/Zappa/Zappa And join our Slack channel here: https://zappateam.slack.com Love!, ~ Team Zappa!
多分エラーが起きると思います。
原因は、ここ です。
waiter.wait(LoadBalancerArns=[load_balancer_arn], WaiterConfig={"Delay": 3})
引用した部分の少し上の処理を見ていただくと分かりますが、 Zappa 側で boto3 を使って ALB を作成 しています。
# Create load balancer # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/elbv2.html#ElasticLoadBalancingv2.Client.create_load_balancer kwargs = dict( Name=lambda_name, Subnets=alb_vpc_config["SubnetIds"], SecurityGroups=alb_vpc_config["SecurityGroupIds"], Scheme=alb_vpc_config["Scheme"], # TODO: Tags might be a useful means of stock-keeping zappa-generated assets. # Tags=[], Type="application", # TODO: can be ipv4 or dualstack (for ipv4 and ipv6) ipv4 is required for internal Scheme. IpAddressType="ipv4", ) response = self.elbv2_client.create_load_balancer(**kwargs)
そして、ALB のリスナーに Lambda 関数を登録するために、作成した ALB が Active になるまでの待機処理が実行されます。
要するにデフォルトでは 15 秒おきに最大 40 回、 DescribeLoadBalancers API を実行して、ステータスが Active になるのを待つ処理になっています。
つまるところ、上記で引用した処理ではステータスをチェックする時間間隔 Delay
が 3 秒になっており、最大の待機時間を超えてしまったみたいです。
おそらく調子(?)が良ければこれでも間に合う(だからプルリクがマージされてる)と思うのですが、手元で動かしてみる限り全然ダメなので、直接書き直してやるしかありません。
試しに 5 秒にするだけで一応成功しましたが、デフォルトが 15 秒なのでどうせなら WaiterConfig
自体消してもいいんじゃないかなと思います。
- zappa/core.py
waiter.wait(LoadBalancerArns=[load_balancer_arn])
これで、(一度環境を作ってしまった場合は zappa undeploy dev
で環境を消してから※)デプロイし直せば成功するはずです。
※ 初回デプロイ時以降は zappa update dev
のように実行するコマンドが変わります。
が、上記のエラーで失敗した場合は update コマンドを実行しても ALB が Active になっていれば OK! みたいな形でデプロイが終わってしまう(本当はリスナーとして Lambda を登録しないと使えるようにならない)ようです。
だから一度環境を消して、作り直す必要があったんですね。
Internal ALB の場合もできるんです
それではようやく本題です。
先ほどの zappa_settings.json の ALB に関する設定情報を見て頂くと、問答無用で外部 ALB(internet-facing)しか使えないように見えます。
"alb_vpc_config": { // ALB の基本設定 "CertificateArn": "arn:aws:acm:ap-northeast-1:012345678901:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", // 証明書 "SubnetIds": ["subnet-xxxxx", "subnet-yyyy"], // 紐づけるサブネット "SecurityGroupIds": ["sg-zzzzz"], // セキュリティグループ }
しかし、 ALB を作成しているところのソースコード を見ると、内部 ALB(internal)も出来るように見えます。
# Create load balancer # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/elbv2.html#ElasticLoadBalancingv2.Client.create_load_balancer kwargs = dict( Name=lambda_name, Subnets=alb_vpc_config["SubnetIds"], SecurityGroups=alb_vpc_config["SecurityGroupIds"], Scheme=alb_vpc_config["Scheme"], # TODO: Tags might be a useful means of stock-keeping zappa-generated assets. # Tags=[], Type="application", # TODO: can be ipv4 or dualstack (for ipv4 and ipv6) ipv4 is required for internal Scheme. IpAddressType="ipv4", ) response = self.elbv2_client.create_load_balancer(**kwargs)
本家ソースコードのコメント内でも言及されていますが、boto3 の create_load_balancer を使っているだけです。
したがって上記の Scheme
が 'internet-facing'
なら外部 ALB、 'internal'
なら内部 ALB が作れる、ということになります。
実際の zappa_settings.json としては例えば次のようになります。
{ "dev": { "app_function": "main.app", "aws_region": "ap-northeast-1", "profile_name": "default", "project_name": "zappa-example", "runtime": "python3.8", "s3_bucket": "zappa-xxxxxx", "apigateway_enabled": false, "alb_enabled": true, "alb_vpc_config": { "CertificateArn": "arn:aws:acm:ap-northeast-1:012345678901:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "SubnetIds": ["subnet-xxxxx", "subnet-yyyy"], "SecurityGroupIds": ["sg-zzzzz"], "Scheme": "internal" // Schema を定義( internet-facing | internal) } } }
Lambda 自体は(実用上の要件に合うかは別として) VPC 内になくても大丈夫です。
ちなみに本家 GitHub の Issue でも「内部 ALB 作れるようになってるけどまだ README.md には書いてないね」と言われています。
デプロイ時は waiter
の設定値を弄らないと高確率で失敗するのは外部 ALB の場合でも同じです。
また、一度外部 ALB で構築した場合は後からこの Scheme
を変えても外部 ALB から内部 ALB に作り変えてくれません。
これまた一旦環境を削除してから再度構築する必要があります。
さいごに
Zappa の最新版のリリースが 2020 年なので、更新頑張れ~~という感じですね。