APIサーバを作るための環境を構築する その1
- dockerでやったほうが楽だと思うけれども、dockerを使えないので…。
- apche + コンソールから起動したuwsgiと連携して動作するまでの確認を行う
構築順序
- OSのインストールと関連するソフトのインストール
- uwsgi + flaskでAPIを起動して動作テスト
- uwsgi + uwsgi.ini + flaskでAPIを起動して動作テスト
- apache + uwsgi(http) + uwsgi.ini + flaskでAPIを起動して動作テスト
- apache + uwsgi(socket) + uwsgi.ini + flaskでAPIを起動して動作テスト
- httpで連携するより多少パフォーマンスが良いらしい?
構築する環境
- CentOS Linux release 7.9.2009 (Core)
- apach(2.4.6-97)
- python(3.6.8)
- uwsgi(2.0.20)
- Flask(2.0.3)
- mod_proxy_uwsgi(2.0.18)
CentOS7をインストール
- テストで利用するユーザーはwww_user
- IPアドレス 192.168.0.150/24
- GW 192.168.0.1
- DNS 192.168.0.1
- SELinuxはdisabled
- yum install httpdでapacheをインストールと自動起動の設定
- 検証なのでfirewalldもストップ
uwsgi + flaskの連携
python3のインストール
- python3.6をepelからインストール
- ついでにpipのバージョンアップ
# yum install -y epel-release # yum install -y python36 # python3 -m pip install --upgrade pip
uWSGIのインストール
- uWSGIのインストールには以下のパッケージが必要
- gcc, python36-devel
# yum install -y gcc python36-devel # python3 -m pip install uWSGI flask # python3 -m pip list
flaskでサンプルのAPIを作成
/, /ok, /ng, /raiseの4個のエンドポイントがある
api.py
from flask import Flask, jsonify app = Flask(__name__) @app.errorhandler(Exception) def error_handler(e): return jsonify(e.args), 500 @app.route("/") def root(): return "root", 200 @app.route("/ok") def ok(): return "OK", 200 @app.route("/ng") def ng(): return "ng", 400 @app.route("/raise") def raise_error(): raise Exception("RAISE ERROR") if __name__ == "__main__": app.run()
uwsgi + flaskでapiのテスト
uwsgiでコマンドラインからapiを起動
- 以下のコマンドでapiを起動した後ブラウザからアクセスして問題がないことを確認する
- http;//192.168.0.150:8080/
$ uwsgi --http=192.168.0.150:8080 --wsgi-file=api.py --callable=app
コマンド省略用のuwsgi.iniファイルを作成してuwsgi + flask apiの動作確認を行う
uwsgi.iniファイルの作成
毎回コマンドを打つのが面倒なので起動用の設定ファイルを作成する
参考
uwsgi.ini
[uwsgi] ######################## # 後で参照するように設定する ######################## base_dir = /home/www_user/api_test app_name = api ######################## # uwsgiの設定 ######################## # APIを起動する前にカレントディレクトリを変更 chdir = %(base_dir) # 起動するIPアドレスとポート番号 http = 192.168.0.150:8080 # 起動するファイル wsgi-file = api.py # 起動するオブジェクト app = Flask(__name__)のappの事 callable = app # 起動するグループID gid = www_user # 起動するユーザーID uid = www_user # 起動するプロセス数 # processes = 1 は workers = 1と同じ意味 processes = 1 # マルチスレットで動作させ、スレッドとして15スレッド起動する threads = 15 # 以下に指定したファイルを更新するとAPIがリロードされる touch-reload = %(base_dir)/api_touch_reload # タイムアウトまでの時間(秒) harakiri = 60 # 基本的に有効とする # 有効にするとうまいことアプリケーションのリロードを行ってくれる master = true ######################## # ログ関係 ######################## # access_logの設定 req-logger = file:/var/log/uwsgi/%(app_name)/access_log # error_logの設定 logger = file:/var/log/uwsgi/%(app_name)/error_log
uwsgi + uwsgi.ini + flaskでapiのテスト
uwsgiでコマンドラインからapiを起動
- 以下のコマンドでapiを起動した後ブラウザからアクセスして問題がないことを確認する
- ログはuwsgi.iniで指定した/var/log/uwsgi/api/access_logと/var/log/uwsgi/api/error_logに出力される
- http;//192.168.0.150:8080/
$ uwsgi uwsgi.ini
apacheとuwsgiの連携
apacheのインストールと必要なモジュールの確認
# yum install httpd # systemctl start httpd # systemctl enable httpd # http -M | grep proxy_http_module ← proxy_http_moduleが組み込まれているか確認 proxy_http_module (shared) ← この表示があれば問題なし(なければ次の項目で組み込みを行う)
apacheとuwsgiをhttpで連携
- /etc/httpd/conf.d/uwsgi.confファイルを新規で作成する
# apacheにproxy_http_moduleを組み込むときは行頭の#を外す # LoadModule proxy_http_module modules/mod_proxy_http.so # mod_proxy_uwsgiをapacheに組み込む LoadModule proxy_uwsgi_module /usr/lib64/httpd/modules/mod_proxy_uwsgi.so # フォワードプロキシをOffにする ProxyRequests Off # /apiに来たリクエストをhttp://192.168.0.150:8080へ転送する ProxyPass /api http://192.168.0.150:8080 ProxyPassReverse /api http://192.168.0.150:8080
- apacheを再起動する
# systemctl reload httpd # systemctl status httpd
uwsgi + uwsgi.ini + flaskでapiのテスト
- 以下のコマンドでapiを起動した後ブラウザからアクセスして問題がないことを確認する
- ログはuwsgi.iniで指定した/var/log/uwsgi/api/access_logと/var/log/uwsgi/api/error_logに出力される
- http;//192.168.0.150/ ← 先程までと違いport番号が不要
$ uwsgi uwsgi.ini
apacheとuwsgiをsocketで連携
- uwsgi.iniの以下のところを書き換える
# 起動するIPアドレスとポート番号 # http = 192.168.0.150:8080 ← コメントアウトする # 以下を追記 socket = 127.0.0.1:8080
- uwsgi.confの以下のところを以下のように書き換える
# apacheにproxy_http_moduleを組み込むときは行頭の#を外す # LoadModule proxy_http_module modules/mod_proxy_http.so # mod_proxy_uwsgiをapacheに組み込む LoadModule proxy_uwsgi_module /usr/lib64/httpd/modules/mod_proxy_uwsgi.so # フォワードプロキシをOffにする ProxyRequests Off # /apiに来たリクエストをhttp://192.168.0.150:8080へ転送する ProxyPass /api uwsgi://192.168.0.150:8080 ProxyPassReverse /api uwsgi://192.168.0.150:8080
- apacheをリロードして設定を再読み込み
# systemctl reload httpd
uwsgi + uwsgi.ini + flaskでapiのテスト
- 以下のコマンドでapiを起動した後ブラウザからアクセスして問題がないことを確認する
- ログはuwsgi.iniで指定した/var/log/uwsgi/api/access_logと/var/log/uwsgi/api/error_logに出力される
- http;//192.168.0.150/ ← httpでの連携と同じくport番号が不要
$ uwsgi uwsgi.ini
httpとsocketのどっちを使った方がいい?(番外編)
- どっちのほうがレスポンスが良いのか負荷をかけて検証してみる
- 環境はHyper-V上に構築したCentOS7 + プロセッサ4 + メモリ 1G
- 利用するuwsgi.iniは以下の内容(上の方に書いたものと殆ど同じ)
- uwsgi_http.iniとuwsgi_socket.iniとして作成する
uwsgi_http.ini
[uwsgi] ######################## # 後で参照するように設定する ######################## base_dir = /home/www_user/api_test app_name = api ######################## # uwsgiの設定 ######################## # APIを起動する前にカレントディレクトリを変更 chdir = %(base_dir) # httpで起動するIPアドレスとポート番号 http = 127.0.0.1:8080 # 起動するファイル wsgi-file = api.py # 起動するオブジェクト app = Flask(__name__)のappの事 callable = app # 起動するグループID gid = www_user # 起動するユーザーID uid = www_user # 起動するプロセス数 # processes = 1 は workers = 1と同じ意味 processes = 1 # マルチスレッドで動作させる threads = 15 # 以下に指定したファイルを更新するとAPIがリロードされる touch-reload = %(base_dir)/api_touch_reload # タイムアウトまでの時間(秒) harakiri = 60 # 基本的に有効とする # 有効にするとうまいことアプリケーションのリロードを行ってくれる master = true ######################## # ログ関係 ######################## # access_logの設定 req-logger = file:/var/log/uwsgi/%(app_name)/http_access_log # error_logの設定 logger = file:/var/log/uwsgi/%(app_name)/http_error_log
uwsgi_socket.ini
[uwsgi] ######################## # 後で参照するように設定する ######################## base_dir = /home/www_user/api_test app_name = api ######################## # uwsgiの設定 ######################## # APIを起動する前にカレントディレクトリを変更 chdir = %(base_dir) # socketで起動するIPアドレスとポート番号 socket = 127.0.0.1:9090 # 起動するファイル wsgi-file = api.py # 起動するオブジェクト app = Flask(__name__)のappの事 callable = app # 起動するグループID gid = www_user # 起動するユーザーID uid = www_user # 起動するプロセス数 # processes = 1 は workers = 1と同じ意味 processes = 1 # マルチスレッドで動作させる threads = 15 # 以下に指定したファイルを更新するとAPIがリロードされる touch-reload = %(base_dir)/api_touch_reload # タイムアウトまでの時間(秒) harakiri = 60 # 基本的に有効とする # 有効にするとうまいことアプリケーションのリロードを行ってくれる master = true ######################## # ログ関係 ######################## # access_logの設定 req-logger = file:/var/log/uwsgi/%(app_name)/socket_access_log # error_logの設定 logger = file:/var/log/uwsgi/%(app_name)/socket_error_log
- apacheの設定の/etc/httpd/conf.d/uwsgi.confは以下の内容
- http://192.168.0.150/http/にアクセスした場合はhttpで連携
- http://192.168.0.150/http/にアクセスした場合はsocketで連携
# mod_proxy_uwsgiをapacheに組み込む LoadModule proxy_uwsgi_module /usr/lib64/httpd/modules/mod_proxy_uwsgi.so # フォワードプロキシをOffにする ProxyRequests Off ProxyPass /http http://127.0.0.1:8080 ProxyPassReverse /http http://127.0.0.1:8080 ProxyPass /unix uwsgi://127.0.0.1:9090 ProxyPassReverse /unix uwsgi://127.0.0.1:9090
- uwsgiコマンドでapiを2個起動する
$ uwsgi uwsgi_http.ini & uwsgi uwsgi_socket.ini
- APIが正常に起動しているか確認する
$ curl http://192.168.0.150/http $ curl http://192.168.0.150/unix
負荷をかけてみる
- httpでの連携からテスト
# ab -c 100 -n 10000 http://192.168.0.150/http This is ApacheBench, Version 2.3 <$Revision: 1843412 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 192.168.0.150 (be patient) Completed 1000 requests Completed 2000 requests Completed 3000 requests Completed 4000 requests Completed 5000 requests Completed 6000 requests Completed 7000 requests Completed 8000 requests Completed 9000 requests Completed 10000 requests Finished 10000 requests Server Software: Apache/2.4.6 Server Hostname: 192.168.0.150 Server Port: 80 Document Path: /http Document Length: 4 bytes Concurrency Level: 100 Time taken for tests: 4.112 seconds Complete requests: 10000 Failed requests: 3555 (Connect: 0, Receive: 0, Length: 3555, Exceptions: 0) Non-2xx responses: 3555 Total transferred: 3314685 bytes HTML transferred: 1401565 bytes Requests per second: 2431.91 [#/sec] (mean) Time per request: 41.120 [ms] (mean) Time per request: 0.411 [ms] (mean, across all concurrent requests) Transfer rate: 787.21 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.3 0 5 Processing: 2 40 11.3 37 94 Waiting: 2 40 11.3 37 94 Total: 5 41 11.3 37 94 Percentage of the requests served within a certain time (ms) 50% 37 66% 42 75% 47 80% 50 90% 59 95% 65 98% 70 99% 73 100% 94 (longest request)
- socketでの連携をテスト
# ab -c 100 -n 10000 http://192.168.0.150/unix This is ApacheBench, Version 2.3 <$Revision: 1843412 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 192.168.0.150 (be patient) Completed 1000 requests Completed 2000 requests Completed 3000 requests Completed 4000 requests Completed 5000 requests Completed 6000 requests Completed 7000 requests Completed 8000 requests Completed 9000 requests Completed 10000 requests Finished 10000 requests Server Software: Apache/2.4.6 Server Hostname: 192.168.0.150 Server Port: 80 Document Path: /unix Document Length: 4 bytes Concurrency Level: 100 Time taken for tests: 5.569 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 2010000 bytes HTML transferred: 40000 bytes Requests per second: 1795.74 [#/sec] (mean) Time per request: 55.687 [ms] (mean) Time per request: 0.557 [ms] (mean, across all concurrent requests) Transfer rate: 352.48 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.3 0 5 Processing: 4 55 14.8 51 126 Waiting: 2 53 14.5 48 118 Total: 5 55 14.9 51 126 Percentage of the requests served within a certain time (ms) 50% 51 66% 57 75% 62 80% 65 90% 75 95% 88 98% 100 99% 106 100% 126 (longest request)
負荷テストの結果
Concurrency Level: 100 Time taken for tests: 4.112 seconds Complete requests: 10000 Failed requests: 3555 (Connect: 0, Receive: 0, Length: 3555, Exceptions: 0) Non-2xx responses: 3555 Total transferred: 3314685 bytes HTML transferred: 1401565 bytes Requests per second: 2431.91 [#/sec] (mean) Time per request: 41.120 [ms] (mean) Time per request: 0.411 [ms] (mean, across all concurrent requests) Transfer rate: 787.21 [Kbytes/sec] received
- /unix
Concurrency Level: 100 Time taken for tests: 5.569 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 2010000 bytes HTML transferred: 40000 bytes Requests per second: 1795.74 [#/sec] (mean) Time per request: 55.687 [ms] (mean) Time per request: 0.557 [ms] (mean, across all concurrent requests) Transfer rate: 352.48 [Kbytes/sec] received
負荷テストのエラーの理由
エラーの理由は以下を参照
Apacheでリバースプロキシ、タイムアウトを上手にコントロール/var/log/httpd/error_logを見ると以下の様なエラーメッセージがたくさんでている
[Thu Jun 30 20:48:53.238914 2022] [proxy_http:error] [pid 3434] (20014)Internal error: [client 192.168.0.252:53796] AH01102: error reading status line from remote server 127.0.0.1:8080 [Thu Jun 30 20:48:53.238937 2022] [proxy:error] [pid 3434] [client 192.168.0.252:53796] AH00898: Error reading from remote server returned by /http [Thu Jun 30 20:48:53.240173 2022] [proxy_http:error] [pid 3421] (20014)Internal error: [client 192.168.0.252:53828] AH01102: error reading status line from remote server 127.0.0.1:8080 [Thu Jun 30 20:48:53.240193 2022] [proxy:error] [pid 3421] [client 192.168.0.252:53828] AH00898: Error reading from remote server returned by /http [Thu Jun 30 20:48:53.242943 2022] [proxy_http:error] [pid 3416] (20014)Internal error: [client 192.168.0.252:53668] AH01102: error reading status line from remote server 127.0.0.1:8080 [Thu Jun 30 20:48:53.242958 2022] [proxy:error] [pid 3416] [client 192.168.0.252:53668] AH00898: Error reading from remote server returned by /http [Thu Jun 30 20:48:53.243063 2022] [proxy_http:error] [pid 3429] (20014)Internal error: [client 192.168.0.252:53840] AH01102: error reading status line from remote server 127.0.0.1:8080 [Thu Jun 30 20:48:53.243084 2022] [proxy:error] [pid 3429] [client 192.168.0.252:53840] AH00898: Error reading from remote server returned by /http [Thu Jun 30 20:48:53.243189 2022] [proxy_http:error] [pid 3405] (20014)Internal error: [client 192.168.0.252:53838] AH01102: error reading status line from remote server 127.0.0.1:8080 [Thu Jun 30 20:48:53.243204 2022] [proxy:error] [pid 3405] [client 192.168.0.252:53838] AH00898: Error reading from remote server returned by /http
対応方法
- 長くなったので次へ続く