asobannのAWS ECSデプロイでやったことと引っかかったこと その1

asobannは現在herokuにデプロイしてMongoDBを使っているが、「herokuのMongoDBが11月で使えなくなるよー」という通知が来た。なるほど。また、サーバーをスケールするためにはFlask-SocketIOを複数プロセス起動してロードバランサを置かないといけないのだけど、herokuでそれを実現できない。あるいはすごいお金がかかる(herokuのガイド通りに1プロセス1Dynoにすると、ロードバランサ1つ+メッセージキュー1つ+アプリn個ぶんのDynoが必要になる。処理負荷はぜんぜん小さいのにもったいない)。

もともとパフォーマンス改善を考えていて、複数プロセス構成に載せ替えたかったので、これを機会に取り組んでみることにした。いちおう開発環境レベルでは動くようになったので、そこでやったことを主に自分のメモとして、もしかしたら誰かの参考になる情報として、書いておく。どれも、今さら?と思われそうな新しくない要素だが、初めての人はいつでもいるし(←自分)。ハマったところもたくさんあり、調査不足や勘違いが多いけれど、それも誰かの役に立つといいな。

 

初めてのDocker Composeで、nginxが使えないと知る

Flask-SocketIOを複数並列で動かすには、gunicornのworkerを増やすのでは対応できず、プロセス自体を増やさないとならない。プロセスを増やすということはリバースプロキシあるいはロードバランサが必要になる。またサーバー同士が調整するためのメッセージキューも必要(RedisやRabbitMQなど)。公式ではnginxの設定方法が書いてあるので、これに取り組むことにした。

AWSに上げる前に、まず手元でリバースプロキシ+複数プロセスの構成を実験すべく、Docker Composeを使うことにした。docker-compose.ymlファイルを書くのは今回が初めて。このへんの成果は残っているが、結論としてはnginxをロードバランサにするとどうしても動かなかった。SocketIOがWebSocketに切り替えようとしても切り替わらず、結果としてまったく通信ができない。eventletをgeventに変えたり、gevent-websocketを入れたり、nginx.confの書き方をあちこち調べたりしたけどわからない。ごくシンプルなサンプルを書いて試したけど同じ現象が再現するので、諦めることに。AWS ALB (Application Load Balancer)を使えばなにもせずそのまま動いたので、結果的にはそれで問題なかった。

こんにちはAWS ECS、お前はいつもそうだなSecurity Group

前段の実験でDockerコンテナで動くようにしてあるので、AWS ECS (Elastic Container Service)を使うことにした。ECSはパフォーマンステストを作ったときにも使っているので、初めてではない。Docker Composeで一発デプロイができたら嬉しい気もしたけれど、まだそこまでECSの気持ちがわからないので、まずはWebコンソールからひとつずつ手作りすることにした。Cluster、VPC、Task Definition、Service、Load Balancerと作っていく。

起動タイプはEC2に。Fargateではネットワークが awsvpcになってネットワークの設定がシンプルになるが、軽いタスクをたくさん上げるとherokuのDyno数と同じ問題でお金がかかる。EC2 Launch Typeで1インスタンスに軽いタスクを複数入れて安上がりにしたい。最終的にはEC2のt3.smallインスタンス1個にMongoDB+Redis+アプリ3つが収まって、月15USDくらい(us-east-1リージョン)。

ところがEC2でawsvpcに設定すると、EC2インスタンスのタイプによって持てるネットワークインターフェース(ENI)の数に制限があって、たくさん実行できないと制限があり、今回はここに引っかかった。いくら待ってもTaskが起動せず、コンソール上でServiceのイベントを見ると、条件の合うインスタンスがないからタスクを起動しないよというエラーになっている。調べていたら、ENIの数に制限があるというドキュメントにたどり着いて原因がわかった。いったんawsvpcで作ってしまったので、いまは練習として放置し、EC2インスタンスを2つにしてしのごう。なおいま調べたら2019年にENI制限が緩和されたらしいけれど、NITRO以降のタイプのみだったので関係ない。

今回は練習としてawsvpcのままだが、本番のときはネットワークをbridgeに設定する。こうするとENI数の制約はなくなる。同じIPに複数のTaskが存在して、ポート番号で呼び分けることになる。Service Discoveryを使うとき、AレコードではなくSRVレコードを使う必要が出てくるのだが、これがあとで一手間増える原因となった。

ALB (Application Load Balancer)もServiceを作るときに設定する。ALBはそのままでWebSocketに対応していて追加設定不要だが、SocketIOにはスティッキーセッションが必要なのでそこだけ設定をONにする。設定はしたのだけど、ALBのURLをアクセスしても503になってアプリに到達できない。これはSecurity Groupの設定が悪くて、ALB自体にもSecurity Groupが働くので、ポート80番を開けておかないといけない。Security Groupはいろんなところに影響するのに、設定が悪いと気づける方法がなくて、いつもハメられている気がする。キミの気持ちがわからないよ。

続く

これでいったんAWS ECS上でasobannが動くようになった。固定URLがないし、MongoDBがコンテナで動いているので何かあるとデータが消えてしまうが、開発環境という位置づけならとりあえず大丈夫だ。httpsがないのはちょっとダメかも。

今度は今回手作りしたものを参考に、CloudFormationで自動構築できるようにするのだが……次回に続く

 

 

yattom.hatenablog.com

yattom.hatenablog.com