PythonでもシングルバイナリでProductionReadyなhttpサーバーが欲しいっ！！

#概要

最近のGoなどの言語では，ビルドインのライブラリでhttpサーバーが組み込まれており，簡単にRestFulAPIを作れるようになってます．しかも，プロダクションユースでも耐えうる性能が出せて，しかも1つのバイナリをぽこんとデプロイするだけでリリースできる．

**うらやましい**

というわけでPythonで

- ProductionReadyのhttpサーバーで
- 簡単にRestFulAPIが作れて
- シングルバイナリ

を，**できるだけ簡単に作ってみました．**


#環境準備

```bash
$ pip install tornado flask pyinstaller
```

以上．超簡単．


#ライブラリの紹介
##Tornado

![image.png](https://qiita-image-store.s3.amazonaws.com/0/139643/c55a2456-033a-3ed6-b2d5-76780eb7e946.png)

> TornadoはPythonで書かれたスケーラブルでノンブロッキングなWebサーバ、Webフレームワークである。FriendFeedによって開発され、Facebookに吸収されたのちにオープンソース化された。

引用：[Tornado](https://ja.wikipedia.org/wiki/Tornado_(Web%E3%82%B5%E3%83%BC%E3%83%90))

割とマイナーな部類だと思います．PythonのWeb系のライブラリのQiitaの記事数をちょっと調べた結果が以下の表になっています．

| ライブラリ名 | Qiitaの記事数 |
|:--:|--:|
| Django     |  1,820 |
| Flask      |    775 |
| Bottle     |    602 |
| Tornado    |    177 |
| pyramid    |    132 | 
| Plone      |     31 |
| cherrypy   |     23 |
| TurboGears |      0 |

やはりPythonのWebフレームワークはDjangoが一強ですね．
じゃあTornadoの特徴は？ということなのですが，ちょっと古いデータですが，2011年にWebサーバーのスループットを図ったベンチマークがあります．

![image.png](https://qiita-image-store.s3.amazonaws.com/0/139643/afdc3160-ef18-9e5d-8b72-dffcfb6d7fc6.png)

引用：[最速TCPサーバーの条件 ～逆襲の Erlang と Haskell の挑戦～](http://dsas.blog.klab.org/archives/51993306.html)

これを見ると結構速い部類のようです．
結構一緒くたに議論されている感じがしますが，Pythonの"Webフレームワーク"というものは多いんですが，"httpサーバー"は少ないです．Pythonの標準ライブラリにもHTTPServerがあるんですが，**ブロッキングする**．という性質があったりします．１人がアクセスして処理している間に，もう1人はアクセスすることが出来なかったりします．そのため，割と"さばける"，"httpサーバー"，それに加えて，"Productionで使える"というのは意外になかったりします．


#Flask

![image.png](https://qiita-image-store.s3.amazonaws.com/0/139643/fe61379b-a5cc-d132-6aa1-55266893666e.png)

> Flask（フラスク）は、プログラミング言語Python用の、軽量なウェブアプリケーションフレームワークである。標準で提供する機能を最小限に保っているため、自身を「マイクロフレームワーク」と呼んでいる。Werkzeug WSGIツールキットとJinja2テンプレートエンジンを基に作られている。BSDライセンスで公開されている。

引用：[Flask](https://ja.wikipedia.org/wiki/Flask)

説明不要かもしれませんが，Python用のWebサービス用のマイクロフレームワークです．この辺は完全に好みです．しいて言えば，最小の文字数でサーバーが作れる．といったところでしょうか．自分としては「簡単にWebアプリを作るときに1つのソースで終わらせたい」という要望があります．というのでDjangoが敬遠しがちで，よく使っています．


#PyInstaller
![image.png](https://qiita-image-store.s3.amazonaws.com/0/139643/6b7463d7-09ab-294b-8f8f-833437f534e4.png)

https://www.pyinstaller.org/

>PyInstaller is a program that freezes (packages) Python programs into stand-alone executables, under Windows, Linux, Mac OS X, FreeBSD, Solaris and AIX. Its main advantages over similar tools are that PyInstaller works with Python 2.7 and 3.3—3.6, it builds smaller executables thanks to transparent compression, it is fully multi-platform, and use the OS support to load the dynamic libraries, thus ensuring full compatibility.

雑な訳ですが，「PyInstallerはPythonのファイルをスタンドアローンの実行ファイルに固めるプログラムです．色々なOSかつ，Python2.7, Python3.3 - Python3.6でPyInstallerは動かすことができます．またOS間の完全な互換性のために，動的ライブラリのロードにOSの機構を使っています．」

pythonのスクリプトをイイ感じに解析してくれて，いわゆる1つのexeに固めてくれるソフトウェアです．似たようなものにpy2exeがあります．

#ソースコード

一番簡単なサンプル

```python:main.py
import tornado.wsgi
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

def run():
    container = tornado.wsgi.WSGIContainer(app)
    http_server = tornado.httpserver.HTTPServer(container)
    http_server.listen(8888)
    tornado.ioloop.IOLoop.current().start()

if __name__=="__main__":
    run()
```

**これだけでhttpサーバーが作れます!!**
実際に動かしてみると，

```bash
$ python main.py
```

```bash
$ curl localhost:8888
Hello World!
```

ちゃんと動きます．


#シングルバイナリへ変換

これも簡単です．

```bash
$ pyinstaller --clean --strip --noconfirm --onefile main.py
```

できあがり！distというディレクトリが作成され，この場合だと，mainという実行ファイルが作成されます．
実行もすごく簡単．

```bash
$ dist/main
```

今回作ったファイルとオリジナルのpythonの実行ファイルのサイズを比較してみます．

| ファイル名 | サイズ |
|:-------------------|--------:|
| /usr/bin/python3.6 |  4.4 MB |
| dist/main          |  7.0 MB |

もともとのpythonの実行ファイルに対して，2.6MBぐらい大きくなります．
しかし，TornadoやFlaskのライブラリをバインドしている割には小さい印象があります．


#コンテナ化

ここでできたバイナリからコンテナイメージ(singlePy)を作ってみます.
ここでは，ベースイメージとしてalpine linuxを使います．

```dockerfile
FROM alpine
COPY dist/main /bin/
CMD ["/bin/main"]
```

本当にこれだけ．


ここで比較のために"シングルバイナリでない"普通のコンテナ(generalPy)を作ってみます．

```dockerfile
FROM alpine
RUN apk --update --no-cache add python3 \
    && pip3 install --upgrade pip \
    && pip3 install tornado flask
COPY main.py /
CMD ["python3","main.py"]
```

これでも十分に短いので，記述量としてはシングルバイナリのものとさほど変わらないですね．
今度は，コンテナのサイズを比較してみます．

| コンテナ名          |  サイズ |
|:-------------------|--------:|
| alpine linux       |  4.2 MB |
| singlePy           | 11.4 MB |
| generalPy          | 65.3 MB | 

ベースにしたalpine linux自体が小さいですが，singlePyは先ほどのdist/mainのサイズを足したぐらいですね．
それに比べて一般的なPythonの構成のgeneralPyはサイズが大きいですね．singlePyに比べれば6倍，alpineに比べれば16倍ぐらい大きいです．
**一般的なPythonの構成に比べ，シングルバイナリにするほうが1/6程度のコンテナのサイズになります．**
おそらくですが，Tornadoがそこそこ大きなライブラリだと思います．それが，pyinstallerによって解析され，依存していない部分がすべてそぎ落とされているために，これほど小さなバイナリになっていると思われます．

# 感想

最近だと，シングルバイナリがーとかDevOpsがーとかいろいろと議論されている世の中だと思います．自分としてもサービスを作る際，このあたりのデプロイがめんどくさかったりします．特に，お金をかけずにやろうとすると．
Dockerの登場で昔に比べれば楽になったとはいえ，「開発環境に加え，プロダクション環境も分けて構築して，そのイメージも管理し続けるのめんどくさくね・・・？個人サービスごときで・・・？」という疑問も結構あります．
特にプロダクション環境でよりセキュアに．みたいなことを考えたり，環境構築が嫌いな私にとっては頭が痛かったりします．そんなわけで，開発したプロダクトをいかに楽にプロダクションに載せれるかなーと考えると，この方法は意外にありなんじゃないかなーと思います．特に，人が少ない個人サービスだったり，簡単な社内ツールを作る時には結構重宝しそうです．

一方で，不安な点もあります．

それは，昔，pygameでゲームを作っていた際，py2exeという似たようなシングルバイナリを生成するプロダクトを使っていました．これがなかなか曲者で，ライブラリの解析がよくなかったりして，バイナリを作るのに四苦八苦しました．特に，sys.argvの値が取れない．みたいなバグが当時はあって，そのためにプロダクトのソースコードを書き換えたりして，辛い思いをしました．そういう経験があるため，pyinstallerによるバイナリ化もちょっと不安です．そのため，本当にプロダクションで使いたいなら，e2eテスト的なモノはやっておきたいなーというのが正直なところです．
最近だと，python自体がzipappみたいなシングルバイナリっぽい仕組みを導入しているので，実はそれほど気にしなくてもよい時代なのかもしれませんが・・・

本当の意味で「ProductionReadyである．」ということであれば性能チェックもやりたいのですが，まぁまぁそれは後回しで，今後，時間があれば調査したいと思います．この方式も，本当の意味で「シングルバイナリである．」というと，ちょっと微妙かもしれません．本当のシングルバイナリは，alpineベースで作るべきじゃなくて，scratchベースで作るものがホンモノな気がします(ここでは割と広義的な意味でのシングルバイナリ，1つの実行ファイルであるぐらいの意味合いで言ってます）．ただ手軽さだけは抜群なので，ハッカソンなど，ぺぺぺとAPIを作る際は利用していこうと思います．