Morikatron Engineer Blog

モリカトロン開発者ブログ

Cocos Creator + FastAPI + Gunicorn + Apache + EC2のメモ

こんにちは、モリカトロンのチーフエンジニア松原です。
この記事は、自作Pythonサーバーと自作Cocos Creatorクライアントの間でRestAPIとWebSockets通信を行う際の、Amazon EC2上での各種設定やコマンドについてのメモであります。

はじめに

人間、ぼんやりしているうちに毎年1歳ずつ年を取って行きます。私も若い頃は年をとるのが(というより大人になるのが)嬉しゅうございましたが、そのうち本格的に年齢を重ねてまいりますと、だんだん毛髪が白くなってまいりまして(たいした問題じゃない)、白さを極めますと今度はじわじわと薄くなってまいりまして(これだって実害はない)、中途半端な長さだとかえって薄みが目立つのでいっそ丸刈りにしてしまったりするわけですが(頭皮に風を感じ自然との一体感が増してたいへんよろしい)、問題は頭の見てくれよりもその中身なんですよね。つまり、えー、忘れっぽくなる。若い頃は自分で書いたコードやロジックなど数年間は頭の中になんとなく残っておりましたが、近頃はすっかりあきらめが良くなっちゃったみたいで3ヶ月前に書いたコードの記憶があやふや。なので年々コメントが丁寧になってまいります。そしてコードよりもっと早く光速で忘れ去るのが、各種サービスの組み合わせ方ですとかその設定ファイルだとか、といった情報なんですよね。というわけでたいへん前置きが長くなりましたが、本日はメモ、いわゆる備忘録を残します。お題は、FastAPIで作ったPythonのAPI Serverと、Cocos Creatorで作ったWeb Clientとの間でRestAPIとWebSocketsによる通信を行うシステム*1をまず作りまして、そのシステムをAmazon EC2のUbuntu + Apache + Gunicornの元で動かした際の構成とか、設定ファイルとか、使ったコマンド類に関するメモとなります。なお私はWeb技術専門家ではないので、間違いや誤解があるかもしれません。粗相に気づいたらビシッとご指摘いただければ幸いです。

システムの構成

文字で書いててもよくわからないので図示します。

f:id:morika-ma2:20200604134747p:plain

自分なりにいろいろ調べた結果、これでやりたいことができるのではないか、と考えて組んでみた構成です。今の所うまく動いてくれています。

要素をざっくり説明

Amazon EC2

サーバーはAmazon EC2を使います。最小構成(t2.micro)なら無料分でちびちびと試せますし、サーバーの中身をいろいろ構成してみて気に入らなくなったらサクッとやりなおしもできますし、とにかく便利ですね。AWSではロードバランサーに証明書を登録することで簡単にHTTPSを実現できるのが超おすすめですが、注意点としてはWebSocketsを利用する場合はクラシックロードバランサー(CLB)ではなくアプリケーションロードバランサー(ALB)を使う必要があります。などと忠告ぽいことをかっこよく書いていますが、これは実を言うとWebSocketsがなかなか開通できず、変な汗かきながら調べていてわかった情報です。そのうえ弊社の開発で使っているEC2でははじめっからALBを使っていました、というオチ。そもそもいまどきCLBを選ぶ人はいるのでしょうか?選べるようになっているからには何らかの用途があるんだろうな、とは想像できるのですが、ま、なんだっていいや。

Ubuntu

上記のような構成のサービスを作るとき、私はOSにだいたいUbuntu 18.04 LTSを選びます。EC2ではUbuntu以外にもいろいろインストールできるOSがあって、要はなんだっていいわけですが、OSごとにコマンドとか設定ファイルの在りかとか微妙に違ったりしてイライラするので、結局は使いなれたOSが一番ですよね。

Apache

HTTPサーバーのデファクトスタンダード。Webサービスを作るときはその差配を任せるのにこれほど頼もしいサーバーはありません。どんなに使っていても知らないことがどんどん出てくるという巨大な謎の塊みたいなところもありますが、わからないことはググれば誰かが教えてくれるので、私のような(公式ドキュメントを読み込まないような)不真面目ユーザでもなんなく使えてしまいます。「ユーザが多い」はただそれだけでも価値がある時代ですね(決してディスっているわけではありません)。今はNginxを推す人も増えてきていますので、いずれはそちらを使ってみたいと思っています。

Cocos Creator

Webブラウザで動作する全画面制御プログラム(ゲームとかですね)を開発する際の私の第1選択肢がCocos Creatorです。UnityやUE4のような本格的なゲームエンジンにはおよびませんが、手軽に、無料で使えるUnityライクな統合開発環境です。コードはJavaScriptで書いて、ブラウザで表示・動作させながらデバッグする感じになります。Cocos CreatorはCocos2d-xの流れを組んでいますがいちおう別物的なものになっておりまして、Cocos2d-xとかCocos2d-jsあたりとは搭載しているapiが微妙に違っていたり入り混じっていたりして気持ち悪いところもありますが、まあ公式のDocumentを引きつつ組んで行けばなんとかなります。Cocos Creatorでビルドしたクライアントは、HTML+JavaScriptに変換されるため、UnityのWebGLプレイヤーと違っていわゆる「ローディング動作」がない(よほど大きなファイルを扱う場合はもちろん転送に時間がかかります)ところも、個人的に好ましいところです。コードはJavaScriptですから、普通にjQueryなどのフレームワークも使えますし、WebSocketsやJSONの扱いも簡単で、しかもリファレンスがネットにたくさんあるのも心強いですね。
Cocos Creatorの一番恐ろしいところは「いつか突然開発をやめちゃう可能性」がないこともない、みたいなところです。でもそれを言うとほとんどのサービスを疑い始めることになるので、深く考えないことにします。

Python

サーバーを記述する言語は、まあはっきりいってなんだってかまわないんですが、今回はPythonで書くことにします。その理由は、今回のサービスの中核で使おうと思っているのが、2019年にモリカトロンで研究したAlphaZeroベースのゲームAIコード「MorikaAI」だからです。MorikaAIはPythonでバリバリ大量のコードを書いてありますので、それをそのままシームレスに動かすにはPythonでサーバーコードを書くのが一番簡単ですね。

FastAPI

とはいえAPI(HTTPのポートを経由してクライアントとやり取りする)部分を自作するのはナンセンスですからすでにあるフレームワークを使います。これまでは(MorikaAIでも)Flaskというフレームワークを使っていましたが、ちょっと飽きたのと書き方がいまいち美しく感じないのとで、今回は目新しいFastAPIというフレームワークを使ってみることにしました。FastAPIの特徴は動作が早いことと、APIドキュメントの自動生成機能があることなどいろいろあります(てきとうですいません)。新しさの反面ユーザ(=解説記事とか)が少ないという欠点がありますが、そこは気合いでなんとか。

Gunicorn

FastAPIとかFlaskみたいなAPIフレームワークをサーバーで動かす時、WSGIサーバーというものを経由して動作させると、高速で安定性が高くセキュアなのである、と言われていますのでナルホドと鵜呑みにして、FastAPIの手前にGunicornというWSGIサーバーをかませることにします。 Gunicornは複数のワーカー(子プロセス)の並列実行を制御できるので、CPUやメモリの性能をフルに使いたい場合など性能面のチューニングにも有効なようです。

動作の順序をざっくり説明

このシステムの動作順を以下に簡単に書きます。

  1. まずはユーザがWebブラウザ(ChromeとかSafariとか)でサイトのURLへアクセスします。たとえば https://hoge/fuga みたいな感じのURLですね。
  2. https://hoge/fugaへのアクセスはApacheが受け取ります。そうするとApacheはファイルシステム内に配置されたCocos Creatorのビルド済みコード一式(HTML + JavaScript + resource)をWebブラウザへ返送してくれます(ファイルシステム中のどれを送り返せばいいかは、Apacheの設定ファイルにVirtualHostとして書いておきます)。Webブラウザが受け取ったコード一式を実行することで、無事にクライアントプログラムが動作することになります。
  3. Webブラウザ下で動作しているクライアントプログラムは、サーバーで動作しているPythonの自作サーバープログラムとの間で、Rest APIやWebSocketsによって通信を行います。この通信はApache、Gunicorn、FastAPIを経由して流れるよう、うまいこと設定をしておくことになります。 

必要な設定を説明

以下に、本システムのファイル配置や設定ファイルの例を記します。

ファイルの置き場所

/var/www/fuga/に以下のファイルを設置します。*2

/var/www/fuga/
 ├ fuga.service (systemdにGunicornを自動起動してもらう設定ファイル)
 ├ gunicorn.py (Gunicornのconfigファイル)
 ├ main.py (自作サーバープログラムのmain.py)
 ├ その他いろいろ (自作サーバーの実行に必要なファイル全部)
 └ web-mobile(Cocos Creatorがビルドしたディレクトリまるごと)
  └ index.htmlやmain.jsなどCocos Creatorがビルドしたファイル全部
Apacheの設定その1 クライアントのためのVirtualHost

Apacheの設定ファイルは /etc/apache2/sites-available/*.conf が読み込まれますので、ここにある設定ファイルにVirtualHostの設定を書き足してしまうのが早道です(キチンと管理したい場合はプロジェクトごとに別の設定ファイルを作ったり丁寧なコメントを書き込んだりするんでしょうね)。

<VirtualHost *:80>
・・・略・・・
        Alias /fuga /var/www/fuga/web-mobile
        <Directory "/var/www/fuga/web-mobile">
                AllowOverride All
                Require all granted
        </Directory>
・・・略・・・
</VirtualHost>
Apacheの設定その2 GunicornのためのProxy

上記のApache設定ファイルの中に、ついでにProxy設定も埋め込んでしまいます。

<VirtualHost *:80>
・・・略・・・
        LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so
        LoadModule proxy_http_module /usr/lib/apache2/modules/mod_proxy_http.so
        LoadModule proxy_wstunnel_module /usr/lib/apache2/modules/mod_proxy_wstunnel.so

        ProxyRequests Off
        ProxyPass /fugaapi/ws_1 ws://localhost:5678/ws_1
        ProxyPassReverse /fugaapi/ws_1 ws://localhost:5678/ws_1
        ProxyPass /fugaapi/ws_2 ws://localhost:5678/ws_2
        ProxyPassReverse /fugaapi/ws_2 ws://localhost:5678/ws_2
        ProxyPass /fugaapi http://localhost:5678
        ProxyPassReverse /fugaapi http://localhost:5678
・・・略・・・
</VirtualHost>

ここでは、WebSocketsのアクセス wss://hoge/fugaapi/ws_1とwss://hoge/fugaapi/ws_2 を自作サーバのWebSockets受信口に転送するよう指示しています。また、https://hoge/fugaapiへのAPIアクセスも自作サーバへ転送します。この例のようにProxyPassは下位のパスから順番に書かないと、うまく働いてくれません。たとえば先頭にProxyPass /fugaapi http://localhost:5678を書いていると、wss://hoge/fugaapi/ws_1がhttp://hoge/fugaapi/ws_1に転送されてしまってWebSocketsがうまく働きません(私数時間これでハマりました)。ここで使っているlocalhost:5678は、自作サーバの待ち受けポートであります(下記の設定で指定しています)。なお、LoadModuleはここに書いてしまっても動きますが、mods-available(とmods-enabled)にあるconfファイルに書くのが正しい?のかもしれません。

あと備忘のためApacheのコマンドをふたつメモっておきます。
Apacheの設定ファイルが正しいかどうかをテストするコマンド

$ sudo apache2ctl configtest

Apacheの設定変更を適用する(Apacheをリスタートする)コマンド

$ sudo apache2ctl restart
systemdの設定 自動起動の設定(fuga.service)

以下はfuga.serviceの例です。Ubuntuを起動したときに、自動的にGunicornを使ってFastAPIの自作サーバを起動するよう仕込んでおく設定ファイルとなります。

[Unit]
Description = Fuga service ( gunicorn - fastapi )
After = network-online.target
ConditionPathExists = /var/www/fuga/

[Service]
WorkingDirectory = /var/www/fuga/
ExecStart = /home/ubuntu/miniconda3/bin/gunicorn --config /var/www/fuga/gunicorn.py main:app
ExecReload = /bin/kill -s HUP $MAINPID
ExecStop = /bin/kill -s TERM $MAINPID
PrivateTmp = true

[Install]
WantedBy = multi-user.target

fuga.serviceファイルは、以下のようにsystemdのディレクトリへコピーし、オーナーとパーミッションを設定しておきます。

$ sudo cp /var/www/fuga/fuga.service /etc/systemd/system
$ sudo chown root:root /etc/systemd/system/fuga.service
$ sudo chmod 644 /etc/systemd/system/fuga.service

以下(自分の)備忘のためsystemd関係のコマンドを書いておきます。
systemdにユニットファイルの更新があったことを通知するコマンド(ユニットファイルを修正したらこれを呼び出しておく事)

$ sudo systemctl daemon-reload

いますぐ起動

$ sudo systemctl start fuga.service

いますぐ停止

$ sudo systemctl stop fuga.service

自動起動を有効にする

$ sudo systemctl enable fuga.service

自動起動を解除する

$ sudo systemctl disable fuga.service

確認

$ sudo systemctl status fuga.service

または

$ sudo systemctl list-unit-files | grep fuga
Gunicornの設定 configファイル(gunicorn.py)

上記fuga.serviceで次のような起動コマンドを設定しています。

ExecStart = /home/ubuntu/miniconda3/bin/gunicorn --config /var/www/fuga/gunicorn.py main:app

ここでGunicornに与えるconfigファイル(gunicorn.py)は以下のような内容です。

import multiprocessing
import os

name = "gunicorn"

accesslog = "/var/www/fuga/log_access.txt"
errorlog = "/var/www/fuga/log_error.txt"

bind = "0.0.0.0:5678"

worker_class = "uvicorn.workers.UvicornWorker"
workers = multiprocessing.cpu_count() * 2 + 1
worker_connections = 1024
backlog = 2048
max_requests = 5120
timeout = 120
keepalive = 2

debug = os.environ.get("DEBUG", "false") == "true"
reload = debug
preload_app = False
daemon = False

configファイルの内容はGunicornの公式(こちら)に詳しいです。

最後に

ざっと以上のような設定を行うことで、自作Pythonサーバーと自作Cocos Creatorクライアント間で、RestAPIとWebSockets通信を動かせるようになりました。むちゃくちゃめんどくさいですね。めんどくさいだけに、メモっておかないとすぐ忘れちゃうので、書いておいてよかったと思います。本記事では、自作サーバーのコードとCocos Creatorのコードは紹介しておりませんが、以下のページを眺めればわりと簡単に実現できますので、興味のあるかたはごらんになってください。

FastAPIの導入
First Steps - FastAPI

FastAPIによるWebSocketsのやりかた
WebSockets - FastAPI

CocosによるWebSocketsクライアント
Networking · GitBook

*1:この自作サーバ&クライアントは現在弊社で開発中の「AIネット対戦システム」です。詳細はいずれ発表できると思います。

*2:私はmacで作業しているのですがscpコマンドがめんどくさいのでCyberduckというGUIアプリでscpファイル転送しています。ものすごくラクです。