uWSGI/Dockerを利用したWebサービス運用事例PyConJP 2015, 10/11 Yuri Umezaki
View Slide
#PyConJP_M2
自己紹介梅崎 裕利● 会社○ 日本経済新聞社デジタル編成局● 主な業務○ Ansibleでサーバ管理○ Django+Elasticsearch(ES)で検索API作成○ Fluentd+ES+Kibanaでログ分析3
社内でのPython活用シーン● Google App Engine (2010〜)● New API (Search, Authorization, Logging, etc…)○ Django, django-rest-frameworkなど○ 全部Python3系で動いています(すでにほぼv3.5)● ちょっとしたスクリプト○ Ansible module○ Slack通知○ データ集計・分析○ テストツール4
目次● uWSGI・Gunicornとは○ 特徴紹介○ Gunicornとの比較● サービスを停止させないGracefulな更新○ 更新の課題○ uWSGIで用意されている機能● Dockerとの併用事例○ 複数PaaS対応(Docker, Heroku)方法○ uWSGI設定例● まとめ5
対象● 話すこと○ Djangoなど従来型のPython WebAppの話● 話さないこと○ aiohttpなど非同期なWeb6
uWSGI・Gunicorn7
Python Webアプリケーションを動かす方法WSGI対応サーバを利用する● gunicorn● uwsgi● Apache+mod_wsgigunicornは利用事例が多くシンプル、uwsgiは最近のトレンドで多機能8
質問● Gunicorn使っている人?○ 会場で20% ぐらい● uWSGI使っている人?○ 会場で40% ぐらい9
uWSGIは多機能● http以外にもuwsgiプロトコルが話せる● 実はhttpsやspdy、WebSocketも話せる● 複数のアプリも動かせる(Emperorモード)● virtualenvなどが使える● PythonだけでなくRuby, Perl, Lua, PHPなどにも使える● スプーラ機能● プロファイラ● キャッシュや静的ファイル配信● 設定パラメータを数えたら1200以上あった(!!)10
Gunicornはシンプル● 簡単に起動できる○ gunicorn --workers=4 app.wsgi○ uwsgi --http 127.0.0.1:8000 --wsgi-file app/wsgi.py --master --processes 4● 利用事例が多い● uWSGIほど設定項目は多くないが、必要十分11
uWSGIとGunicornのベンチマーク12
ベンチマーク環境● Webサーバ○ AWS EC2 m4.large○ Amazon Linux AMI 2015.9● app○ Nginx: 1.8.0 (amazon-repo)○ debug-server (python3.5)● 負荷かけツール○ wrk○ 別サーバより実行13
ベンチマークに利用したアプリケーション● debug-server○ https://www.github.com/bungoume/debug-server○ リクエスト内容をJSONで返すサーバ○ Django1.8.5○ Python 3.5 (branch)14
ベンチマーク内容以下の秒間レスポンス数を比較● nginx + uwsgi(uwsgi-protocol)● nginx + uwsgi(http)● uwsgi直接(http)● nginx + gunicorn(http)● gunicorn直接(http)(ワーカー数・スレッド数)は(3, 3)と(4, 1)でテスト15
nginxの設定 (共通)● ほぼamazon linuxデフォルトのまま16
ベンチマーク: uWSGI + nginx (uwsgi)nginxの設定uwsgiの設定(改行しています)17
ベンチマーク: uWSGI + nginx (uwsgi)レスポンス内容(tcpdump)18
ベンチマーク: uWSGI + nginx (uwsgi)ベンチマーク結果19
ベンチマーク: uWSGI (http)+ nginxnginxの設定uwsgiの設定20
ベンチマーク: uWSGI (http)+ nginxレスポンス内容(tcpdump)21
httpプロキシの注意Python側(uwsgi)でRemote_IPやHost, protocolの値が変わってしまうX-Forwarded-ForやX-Forwarded-Hostまたは、RFC7239のForwarded Headerに入れて利用する22
ベンチマーク: uWSGI (http)+ nginxベンチマーク結果23
ベンチマーク: Gunicorn + nginxnginxの設定gunicornの設定24
ベンチマーク: Gunicorn + nginxレスポンス内容25
ベンチマーク: Gunicorn + nginxベンチマーク結果26
ベンチマーク結果まとめ● 今回試した限りはuWSGIのほうが早かった● gunicornはthreadを2以上にしたりeventletを有効にしたりすると遅くなった● (もちろんアプリや設定、環境などによって異なる可能性はあるので注意)nginx+uwsginginx+uwsgi(http)uwsgi(http)nginx+gunicorngunicorn nginx(参考)worker:3thread:31723.10 1747.53 1656.80 1139.12 1260.17 47868.37worker:4thread:11500.75 1525.72 1608.57 1357.21 1439.42(req/s)27
uwsgiやgunicornのhttpを直接使うときの注意点● gunicorn直接だとheaderとコンテンツが分かれて送信される● uwsgi直接だとheaderにchunkedが入らない。keep-aliveにならない● uwsgi直接はkeep-aliveを使うのは少々無理やりな感じになる○ add-header=Connection: Keep-Alive● uwsgiはhttp-soketでなくhttpを使ったほうがよい○ ただし、httpをつかってもkeep-aliveになるわけではない● nginxを挟むとheaderが追加され、パケットも一つになる28
レスポンス内容: Gunicorn 直接ヘッダ, body, 終端が別パケットで送られている29
レスポンス内容: uWSGI 直接コネクションが毎回切れている。chunkedになってない30
uWSGI・Gunicorn まとめ● gunicornは事例多い・シンプル● uwsgiはトレンド・多機能● 今回のベンチマークでは uwsgi > gunicorn● uwsgiのhttpやgunicornを直接外部に公開するのは注意○ nginxなどのリバースプロキシを通すのが良い31
uWSGIでGracefulな更新32
Gracefulな更新とは● サービスを止めずにアプリケーションを更新する方法● ゼロダウンタイム・ゼロウェイトを実現したい● いろいろな方法がある○ DNSで切り替える○ LBでWebサーバを切り替える■ 複数のサーバを利用する■ サーバが切り替わるBrue-Greenデプロイ○ nginxで振り先を切り替える○ WSGIサーバでリロードする33
イメージ図切り替え箇所新アプリ(サーバIP、Port、worker)旧アプリ(サーバIP、Port、worker)クライアント34● 切り替える場所の違い○ DNS, LBはサーバの切り替え○ nginxはサーバやSocket、Portの切り替え○ uWSGIは主にWorkerの切り替え
更新の注意点● サービスを止めない○ 新しいアプリケーションが起動する間スローダウン○ 古いアプリケーションを止めるときにエラーが発生● なるべく即座に反映できる○ DNSで切り替えるとクライアントが旧IPにアクセスし続ける恐れ● 簡単に更新できるようしておく○ 上段も設定変更が必要になることがある○ nginxで切り替える場合はデプロイのたびにnginxの設定変更が必要○ 結合度が高まってしまう● ロールバックもできるように35
uWSGI上で利用できる仕組み● Standard graceful reload● Worker reload● Chain reload● Zerg mode● Reuse port● Master forking● Subscription systemhttp://uwsgi-docs.readthedocs.org/en/latest/articles/TheArtOfGracefulReloading.html36
Standard Graceful Reload (Prefork, Lazy-app)動いているWorkerが止まるのを待って再起動する● 使い方○ FIFOにrを書き込む(--maste-fifo オプションでsocketを要しておきechoなどで書き込む)○ touch-reloadオプションを使う○ SIGHUPを送る○ uwsgi.reload() APIを呼ぶ● メリット○ 管理が簡単○ 軽量なPreforkでも使える○ 不整合が起きない● デメリット○ 長い待ち時間が発生する37
Worker Reload(秒間レスポンス数)● 約1分間の停止期間が発生(preforkでテスト)38
Worker Reload(平均レスポンス時間)● 60秒でタイムアウト(504)が発生している● アプリケーション起動前の502も発生39
Worker Reload(Lazy-app)Workerだけをリロードする● 使い方○ FIFOにwを書き込む○ touch-worker-reloadオプションを使う● メリット○ 全体の再起動が不要になる● デメリット○ コードの更新にしか有効でない40
Worker Reload(レスポンス数)● 2秒ほど止まるが500エラーなどは出ていない41
Worker Reload(レスポンス時間)● 一瞬詰まるが問題なさそう42
Chain Reload (Lazy-app)Workerを一つひとつ順番にリロードしていく● 使い方○ FIFOにcを書き込む○ touch-chain-reloadオプションを使う● メリット○ ある程度ワーカー数があれば、クライアントの待ち時間を大幅に削減できる○ リロード時の負荷が少ない● デメリット○ コードの更新にしか有効でない43
Chain Reload (レスポンス数)● 4workers, 2threadsにて。段階的に切り替わっており総数への影響は少ない44
Chain Reload (平均レスポンス時間)● 待ちは発生していない45
Worker reload, Chain reloadの問題点uwsgiの設定更新には使えない● 依存ライブラリ更新ができない● Pythonのバージョンをあげられない46
Unix socketをプールとして活用する(Zerg mode)マスターのuwsgiを用意してportをUnix socketに変換し、socketに複数のインスタンスを紐付けて振り分ける● 使い方○ masterデーモンを用意しておき、し、アプリはzergでsocketに割り当てる○ 更新時は同じsocketで起動し、古いものを落とす● メリット○ uWSGIの設定変更も可能になる○ 待ち時間が少ない○ ロールバックしやすい● デメリット○ 追加で常駐のmasterデーモンが必要で、リロード時は更にプロセスが必要になる○ 通常と異なる設定が必要。ちょっとわかりにくい47
Zerg mode (レスポンス数)● 起動時に減るが、大きな影響はなさそう。終了は問題ない(graceful shutdown)48
Zerg mode (平均レスポンス時間)● 起動時に若干増える。49
同一ポートを利用 (Reuse-Port)Linux≧3.9かBSD系で使えるSO_REUSEPORTを活用する● 使い方○ --reuse-portオプションを使って起動する○ 新しいアプリを同じポートで起動し、古いものを落とす● メリット○ Zerg modeと同じように、uWSGI自体の設定変更も可能○ masterデーモンが不要なので管理が楽● デメリット○ カーネルサポートが必要複数のプロセスで同じTCPポートをバインドできる夢の機能LinuxだとKernelがバランシングしてくれる50
Reuse Port (レスポンス数)● 古いものをSIGINT(強制終了)で止める51
Reuse Port (レスポンス数)● しかし若干エラー(502)が出ている52
Reuse Port (平均レスポンス時間)● 起動時と終了時に僅かな待ちが発生している。(緑は502)53
Reuse Port (レスポンス数)● 古いバージョンをgraceful shutdownすると1分間止まってしまう54
Reuse Portについて● 試したOSはLinux(4.1.7)● 複数バインド時にどちらに飛ばすかはKernel次第なので細かい制御は難しい● 夢の機能は万能ではなかった。55
Master forking (黒魔術!)masterを再フォークする● 使い方○ FIFOにfを書き込む● メリット○ カーネルサポートも追加プロセスも不要○ かなり速い● デメリット○ ログやpidなどの一貫性が壊れる56
結果● 試した限りではうまく切り替わりませんでした...57
Subscription Systemサブスクリプションサーバとよばれるロードバランサを用意して紐付ける● 使い方○ uwsgi --fastrouter :1717 --fastrouter-subscription-server 192.168.0.100:2626● メリット○ 簡単かつゼロダウンタイム○ 構成がシンプル● デメリット○ fastserverのようなサブスクリプションサーバが必要58
Gracefulまとめ● 各機能に特徴があるので要件次第● アプリケーションのみを安全に切り替えるならChain Reload● ライブラリ更新やロールバックを考慮するならZerg Dance● ごく僅かなエラーより管理のしやすさを優先させるならReuse Port利点・欠点を見ながら、良いものを選択59
uWSGI+Docker利用事例60
事例61● 新規モバイルアプリ向けAPI● 規模はまだ小さいサービス● Djangoを利用して作成
優先したこと62● 管理のしやすさ○ ライブラリを含め、アプリの更新がしやすい○ 別の環境に移行しやすい≒ 昨今のコンテナ技術周りに追従しやすい● 運用のしやすさ○ サーバにログインしなくても状況がわかるように○ 壊れたら消せる = ログは別の場所に送っておく
最初は優先しない● 性能● 完全にGracefulな更新63
というわけで、reuse-portを使います64
アプリとインフラの分割単位● Herokuを参考○ アプリ自体はHerokuでも動かせる形にしておく● http(またはuwsgiプロトコル)を話すところまでアプリ○ Pythonバージョンや異存ライブラリはアプリで指定● LBやリバースプロキシ、デプロイ自体はインフラ○ Ansibleやクラウドの機能を利用65
アプリ側のディレクトリ構成66● debug-serverの例● heroku,dockerどちらでも動く○ heroku deploy○ docker run
Dockerfileの中身● Dockerfile● python:3-onbuild67
uWSGIの設定例68
構成図69
現在のデプロイ方法現在は1ホスト1コンテナの構成● 新バージョンのコンテナをビルドする● 古いバージョンのコンテナIDを控えておく● reuse-portを使って新バージョンのコンテナを起動する● 数秒待つ● 古いコンテナを止める70
運用事例まとめ● Dockerを1ホスト1コンテナとして利用○ 汎用的なVirtualenvとして● Dockerを利用することでHerokuでもdocker runでも動かせる● ECSなどのコンテナホスティングにそのまま載せられる● ログなどの状態は内部に持たないようにしておく71
今後の方針72● nginxやfluentd側もコンテナにする● デプロイしやすいサービスが出たら乗り換える○ AWSのECSに移行中
まとめ73
74● uWSGIは多機能、Gunicornはシンプル● 今回のベンチではuWSGIのほうがGunicornより速い● uWSGIはいろいろなGraceful Reload方法がある● 事例としてはReuse-Portを利用している
75性能・運用のしやすさ・コストなどを考慮してうまく使うようにしましょう
ご清聴ありがとうございました。76
予備スライド77
アクセスログの取り方78