Slide 1

Slide 1 text

uWSGI/Dockerを利用したWebサービス運用事例 PyConJP 2015, 10/11 Yuri Umezaki

Slide 2

Slide 2 text

#PyConJP_M 2

Slide 3

Slide 3 text

自己紹介 梅崎 裕利 ● 会社 ○ 日本経済新聞社デジタル編成局 ● 主な業務 ○ Ansibleでサーバ管理 ○ Django+Elasticsearch(ES)で検索API作成 ○ Fluentd+ES+Kibanaでログ分析 3

Slide 4

Slide 4 text

社内でのPython活用シーン ● Google App Engine (2010〜) ● New API (Search, Authorization, Logging, etc…) ○ Django, django-rest-frameworkなど ○ 全部Python3系で動いています(すでにほぼv3.5) ● ちょっとしたスクリプト ○ Ansible module ○ Slack通知 ○ データ集計・分析 ○ テストツール 4

Slide 5

Slide 5 text

目次 ● uWSGI・Gunicornとは ○ 特徴紹介 ○ Gunicornとの比較 ● サービスを停止させないGracefulな更新 ○ 更新の課題 ○ uWSGIで用意されている機能 ● Dockerとの併用事例 ○ 複数PaaS対応(Docker, Heroku)方法 ○ uWSGI設定例 ● まとめ 5

Slide 6

Slide 6 text

対象 ● 話すこと ○ Djangoなど従来型のPython WebAppの話 ● 話さないこと ○ aiohttpなど非同期なWeb 6

Slide 7

Slide 7 text

uWSGI・Gunicorn 7

Slide 8

Slide 8 text

Python Webアプリケーションを動かす方法 WSGI対応サーバを利用する ● gunicorn ● uwsgi ● Apache+mod_wsgi gunicornは利用事例が多くシンプル、 uwsgiは最近のトレンドで多機能 8

Slide 9

Slide 9 text

質問 ● Gunicorn使っている人? ○ 会場で20% ぐらい ● uWSGI使っている人? ○ 会場で40% ぐらい 9

Slide 10

Slide 10 text

uWSGIは多機能 ● http以外にもuwsgiプロトコルが話せる ● 実はhttpsやspdy、WebSocketも話せる ● 複数のアプリも動かせる(Emperorモード) ● virtualenvなどが使える ● PythonだけでなくRuby, Perl, Lua, PHPなどにも使える ● スプーラ機能 ● プロファイラ ● キャッシュや静的ファイル配信 ● 設定パラメータを数えたら1200以上あった(!!) 10

Slide 11

Slide 11 text

Gunicornはシンプル ● 簡単に起動できる ○ gunicorn --workers=4 app.wsgi ○ uwsgi --http 127.0.0.1:8000 --wsgi-file app/wsgi.py --master --processes 4 ● 利用事例が多い ● uWSGIほど設定項目は多くないが、必要十分 11

Slide 12

Slide 12 text

uWSGIとGunicornのベンチマーク 12

Slide 13

Slide 13 text

ベンチマーク環境 ● Webサーバ ○ AWS EC2 m4.large ○ Amazon Linux AMI 2015.9 ● app ○ Nginx: 1.8.0 (amazon-repo) ○ debug-server (python3.5) ● 負荷かけツール ○ wrk ○ 別サーバより実行 13

Slide 14

Slide 14 text

ベンチマークに利用したアプリケーション ● debug-server ○ https://www.github.com/bungoume/debug-server ○ リクエスト内容をJSONで返すサーバ ○ Django1.8.5 ○ Python 3.5 (branch) 14

Slide 15

Slide 15 text

ベンチマーク内容 以下の秒間レスポンス数を比較 ● nginx + uwsgi(uwsgi-protocol) ● nginx + uwsgi(http) ● uwsgi直接(http) ● nginx + gunicorn(http) ● gunicorn直接(http) (ワーカー数・スレッド数)は(3, 3)と(4, 1)でテスト 15

Slide 16

Slide 16 text

nginxの設定 (共通) ● ほぼamazon linuxデフォルトのまま 16

Slide 17

Slide 17 text

ベンチマーク: uWSGI + nginx (uwsgi) nginxの設定 uwsgiの設定(改行しています) 17

Slide 18

Slide 18 text

ベンチマーク: uWSGI + nginx (uwsgi) レスポンス内容(tcpdump) 18

Slide 19

Slide 19 text

ベンチマーク: uWSGI + nginx (uwsgi) ベンチマーク結果 19

Slide 20

Slide 20 text

ベンチマーク: uWSGI (http)+ nginx nginxの設定 uwsgiの設定 20

Slide 21

Slide 21 text

ベンチマーク: uWSGI (http)+ nginx レスポンス内容(tcpdump) 21

Slide 22

Slide 22 text

httpプロキシの注意 Python側(uwsgi)でRemote_IPやHost, protocolの値が変わってしまう X-Forwarded-ForやX-Forwarded-Host または、RFC7239のForwarded Headerに入れて利用する 22

Slide 23

Slide 23 text

ベンチマーク: uWSGI (http)+ nginx ベンチマーク結果 23

Slide 24

Slide 24 text

ベンチマーク: Gunicorn + nginx nginxの設定 gunicornの設定 24

Slide 25

Slide 25 text

ベンチマーク: Gunicorn + nginx レスポンス内容 25

Slide 26

Slide 26 text

ベンチマーク: Gunicorn + nginx ベンチマーク結果 26

Slide 27

Slide 27 text

ベンチマーク結果まとめ ● 今回試した限りはuWSGIのほうが早かった ● gunicornはthreadを2以上にしたりeventletを有効にしたりすると遅くなった ● (もちろんアプリや設定、環境などによって異なる可能性はあるので注意) nginx+ uwsgi nginx+ uwsgi(http) uwsgi (http) nginx+ gunicorn gunicorn nginx (参考) worker:3 thread:3 1723.10 1747.53 1656.80 1139.12 1260.17 47868.37 worker:4 thread:1 1500.75 1525.72 1608.57 1357.21 1439.42 (req/s) 27

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

レスポンス内容: Gunicorn 直接 ヘッダ, body, 終端が別パケットで送られている 29

Slide 30

Slide 30 text

レスポンス内容: uWSGI 直接 コネクションが毎回切れている。chunkedになってない 30

Slide 31

Slide 31 text

uWSGI・Gunicorn まとめ ● gunicornは事例多い・シンプル ● uwsgiはトレンド・多機能 ● 今回のベンチマークでは uwsgi > gunicorn ● uwsgiのhttpやgunicornを直接外部に公開するのは注意 ○ nginxなどのリバースプロキシを通すのが良い 31

Slide 32

Slide 32 text

uWSGIでGracefulな更新 32

Slide 33

Slide 33 text

Gracefulな更新とは ● サービスを止めずにアプリケーションを更新する方法 ● ゼロダウンタイム・ゼロウェイトを実現したい ● いろいろな方法がある ○ DNSで切り替える ○ LBでWebサーバを切り替える ■ 複数のサーバを利用する ■ サーバが切り替わるBrue-Greenデプロイ ○ nginxで振り先を切り替える ○ WSGIサーバでリロードする 33

Slide 34

Slide 34 text

イメージ図 切り替え箇所 新アプリ (サーバIP、 Port、 worker) 旧アプリ (サーバIP、 Port、 worker) クライアント 34 ● 切り替える場所の違い ○ DNS, LBはサーバの切り替え ○ nginxはサーバやSocket、Portの切り替え ○ uWSGIは主にWorkerの切り替え

Slide 35

Slide 35 text

更新の注意点 ● サービスを止めない ○ 新しいアプリケーションが起動する間スローダウン ○ 古いアプリケーションを止めるときにエラーが発生 ● なるべく即座に反映できる ○ DNSで切り替えるとクライアントが旧IPにアクセスし続ける恐れ ● 簡単に更新できるようしておく ○ 上段も設定変更が必要になることがある ○ nginxで切り替える場合はデプロイのたびにnginxの設定変更が必要 ○ 結合度が高まってしまう ● ロールバックもできるように 35

Slide 36

Slide 36 text

uWSGI上で利用できる仕組み ● Standard graceful reload ● Worker reload ● Chain reload ● Zerg mode ● Reuse port ● Master forking ● Subscription system http://uwsgi-docs.readthedocs.org/en/latest/articles/TheArtOfGracefulReloading.html 36

Slide 37

Slide 37 text

Standard Graceful Reload (Prefork, Lazy-app) 動いているWorkerが止まるのを待って再起動する ● 使い方 ○ FIFOにrを書き込む(--maste-fifo オプションでsocketを要しておきechoなどで書き込む) ○ touch-reloadオプションを使う ○ SIGHUPを送る ○ uwsgi.reload() APIを呼ぶ ● メリット ○ 管理が簡単 ○ 軽量なPreforkでも使える ○ 不整合が起きない ● デメリット ○ 長い待ち時間が発生する 37

Slide 38

Slide 38 text

Worker Reload(秒間レスポンス数) ● 約1分間の停止期間が発生(preforkでテスト) 38

Slide 39

Slide 39 text

Worker Reload(平均レスポンス時間) ● 60秒でタイムアウト(504)が発生している ● アプリケーション起動前の502も発生 39

Slide 40

Slide 40 text

Worker Reload(Lazy-app) Workerだけをリロードする ● 使い方 ○ FIFOにwを書き込む ○ touch-worker-reloadオプションを使う ● メリット ○ 全体の再起動が不要になる ● デメリット ○ コードの更新にしか有効でない 40

Slide 41

Slide 41 text

Worker Reload(レスポンス数) ● 2秒ほど止まるが500エラーなどは出ていない 41

Slide 42

Slide 42 text

Worker Reload(レスポンス時間) ● 一瞬詰まるが問題なさそう 42

Slide 43

Slide 43 text

Chain Reload (Lazy-app) Workerを一つひとつ順番にリロードしていく ● 使い方 ○ FIFOにcを書き込む ○ touch-chain-reloadオプションを使う ● メリット ○ ある程度ワーカー数があれば、クライアントの待ち時間を大幅に削減できる ○ リロード時の負荷が少ない ● デメリット ○ コードの更新にしか有効でない 43

Slide 44

Slide 44 text

Chain Reload (レスポンス数) ● 4workers, 2threadsにて。段階的に切り替わっており総数への影響は少ない 44

Slide 45

Slide 45 text

Chain Reload (平均レスポンス時間) ● 待ちは発生していない 45

Slide 46

Slide 46 text

Worker reload, Chain reloadの問題点 uwsgiの設定更新には使えない ● 依存ライブラリ更新ができない ● Pythonのバージョンをあげられない 46

Slide 47

Slide 47 text

Unix socketをプールとして活用する(Zerg mode) マスターのuwsgiを用意してportをUnix socketに変換し、 socketに複数のインスタンスを紐付けて振り分ける ● 使い方 ○ masterデーモンを用意しておき、し、アプリはzergでsocketに割り当てる ○ 更新時は同じsocketで起動し、古いものを落とす ● メリット ○ uWSGIの設定変更も可能になる ○ 待ち時間が少ない ○ ロールバックしやすい ● デメリット ○ 追加で常駐のmasterデーモンが必要で、リロード時は更にプロセスが必要になる ○ 通常と異なる設定が必要。ちょっとわかりにくい 47

Slide 48

Slide 48 text

Zerg mode (レスポンス数) ● 起動時に減るが、大きな影響はなさそう。終了は問題ない(graceful shutdown) 48

Slide 49

Slide 49 text

Zerg mode (平均レスポンス時間) ● 起動時に若干増える。 49

Slide 50

Slide 50 text

同一ポートを利用 (Reuse-Port) Linux≧3.9かBSD系で使えるSO_REUSEPORTを活用する ● 使い方 ○ --reuse-portオプションを使って起動する ○ 新しいアプリを同じポートで起動し、古いものを落とす ● メリット ○ Zerg modeと同じように、uWSGI自体の設定変更も可能 ○ masterデーモンが不要なので管理が楽 ● デメリット ○ カーネルサポートが必要 複数のプロセスで同じTCPポートをバインドできる夢の機能 LinuxだとKernelがバランシングしてくれる 50

Slide 51

Slide 51 text

Reuse Port (レスポンス数) ● 古いものをSIGINT(強制終了)で止める 51

Slide 52

Slide 52 text

Reuse Port (レスポンス数) ● しかし若干エラー(502)が出ている 52

Slide 53

Slide 53 text

Reuse Port (平均レスポンス時間) ● 起動時と終了時に僅かな待ちが発生している。(緑は502) 53

Slide 54

Slide 54 text

Reuse Port (レスポンス数) ● 古いバージョンをgraceful shutdownすると1分間止まってしまう 54

Slide 55

Slide 55 text

Reuse Portについて ● 試したOSはLinux(4.1.7) ● 複数バインド時にどちらに飛ばすかはKernel次第なので細かい制御は難しい ● 夢の機能は万能ではなかった。 55

Slide 56

Slide 56 text

Master forking (黒魔術!) masterを再フォークする ● 使い方 ○ FIFOにfを書き込む ● メリット ○ カーネルサポートも追加プロセスも不要 ○ かなり速い ● デメリット ○ ログやpidなどの一貫性が壊れる 56

Slide 57

Slide 57 text

結果 ● 試した限りではうまく切り替わりませんでした... 57

Slide 58

Slide 58 text

Subscription System サブスクリプションサーバとよばれるロードバランサを用意して紐付ける ● 使い方 ○ uwsgi --fastrouter :1717 --fastrouter-subscription-server 192.168.0.100:2626 ● メリット ○ 簡単かつゼロダウンタイム ○ 構成がシンプル ● デメリット ○ fastserverのようなサブスクリプションサーバが必要 58

Slide 59

Slide 59 text

Gracefulまとめ ● 各機能に特徴があるので要件次第 ● アプリケーションのみを安全に切り替えるならChain Reload ● ライブラリ更新やロールバックを考慮するならZerg Dance ● ごく僅かなエラーより管理のしやすさを優先させるならReuse Port 利点・欠点を見ながら、良いものを選択 59

Slide 60

Slide 60 text

uWSGI+Docker利用事例 60

Slide 61

Slide 61 text

事例 61 ● 新規モバイルアプリ向けAPI ● 規模はまだ小さいサービス ● Djangoを利用して作成

Slide 62

Slide 62 text

優先したこと 62 ● 管理のしやすさ ○ ライブラリを含め、アプリの更新がしやすい ○ 別の環境に移行しやすい ≒ 昨今のコンテナ技術周りに追従しやすい ● 運用のしやすさ ○ サーバにログインしなくても状況がわかるように ○ 壊れたら消せる = ログは別の場所に送っておく

Slide 63

Slide 63 text

最初は優先しない ● 性能 ● 完全にGracefulな更新 63

Slide 64

Slide 64 text

というわけで、 reuse-port を使います 64

Slide 65

Slide 65 text

アプリとインフラの分割単位 ● Herokuを参考 ○ アプリ自体はHerokuでも動かせる形にしておく ● http(またはuwsgiプロトコル)を話すところまでアプリ ○ Pythonバージョンや異存ライブラリはアプリで指定 ● LBやリバースプロキシ、デプロイ自体はインフラ ○ Ansibleやクラウドの機能を利用 65

Slide 66

Slide 66 text

アプリ側のディレクトリ構成 66 ● debug-serverの例 ● heroku,dockerどちらでも動く ○ heroku deploy ○ docker run

Slide 67

Slide 67 text

Dockerfileの中身 ● Dockerfile ● python:3-onbuild 67

Slide 68

Slide 68 text

uWSGIの設定例 68

Slide 69

Slide 69 text

構成図 69

Slide 70

Slide 70 text

現在のデプロイ方法 現在は1ホスト1コンテナの構成 ● 新バージョンのコンテナをビルドする ● 古いバージョンのコンテナIDを控えておく ● reuse-portを使って新バージョンのコンテナを起動する ● 数秒待つ ● 古いコンテナを止める 70

Slide 71

Slide 71 text

運用事例まとめ ● Dockerを1ホスト1コンテナとして利用 ○ 汎用的なVirtualenvとして ● Dockerを利用することでHerokuでもdocker runでも動かせる ● ECSなどのコンテナホスティングにそのまま載せられる ● ログなどの状態は内部に持たないようにしておく 71

Slide 72

Slide 72 text

今後の方針 72 ● nginxやfluentd側もコンテナにする ● デプロイしやすいサービスが出たら乗り換える ○ AWSのECSに移行中

Slide 73

Slide 73 text

まとめ 73

Slide 74

Slide 74 text

74 ● uWSGIは多機能、Gunicornはシンプル ● 今回のベンチではuWSGIのほうがGunicornより速い ● uWSGIはいろいろなGraceful Reload方法がある ● 事例としてはReuse-Portを利用している

Slide 75

Slide 75 text

75 性能・運用のしやすさ・コストなどを 考慮してうまく使うようにしましょう

Slide 76

Slide 76 text

ご清聴ありがとうございました。 76

Slide 77

Slide 77 text

予備スライド 77

Slide 78

Slide 78 text

アクセスログの取り方 78