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もストップ
- firewalldを使いたいときは以下のコマンドで(永続化するときは--permanentをつける)
- firewall-cmd --add-port=使うポート番号 --zone=public --permanent
- firewall-cmd --reload ← 永続化したときはリロード必須
uwsgi + flaskの連携
python3のインストール
- python3.6をepelからインストール
- ついでにpipのバージョンアップ
uWSGIのインストール
- uWSGIのインストールには以下のパッケージが必要
flaskでサンプルのAPIを作成
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のテスト
- 以下のコマンドで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]
base_dir = /home/www_user/api_test
app_name = api
chdir = %(base_dir)
http = 192.168.0.150:8080
wsgi-file = api.py
callable = app
gid = www_user
uid = www_user
processes = 1
threads = 15
touch-reload = %(base_dir)/api_touch_reload
harakiri = 60
master = true
req-logger = file:/var/log/uwsgi/%(app_name)/access_log
logger = file:/var/log/uwsgi/%(app_name)/error_log
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:8080/
$ uwsgi uwsgi.ini
apacheのインストールと必要なモジュールの確認
- apacheとuwsgiの連携にはいくつかLoadModuleで必要なモジュールが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ファイルを新規で作成する
LoadModule proxy_uwsgi_module /usr/lib64/httpd/modules/mod_proxy_uwsgi.so
ProxyRequests Off
ProxyPass /api http://192.168.0.150:8080
ProxyPassReverse /api http://192.168.0.150:8080
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で連携
socket = 127.0.0.1:8080
- uwsgi.confの以下のところを以下のように書き換える
LoadModule proxy_uwsgi_module /usr/lib64/httpd/modules/mod_proxy_uwsgi.so
ProxyRequests Off
ProxyPass /api uwsgi://192.168.0.150:8080
ProxyPassReverse /api uwsgi://192.168.0.150:8080
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
chdir = %(base_dir)
http = 127.0.0.1:8080
wsgi-file = api.py
callable = app
gid = www_user
uid = www_user
processes = 1
threads = 15
touch-reload = %(base_dir)/api_touch_reload
harakiri = 60
master = true
req-logger = file:/var/log/uwsgi/%(app_name)/http_access_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
chdir = %(base_dir)
socket = 127.0.0.1:9090
wsgi-file = api.py
callable = app
gid = www_user
uid = www_user
processes = 1
threads = 15
touch-reload = %(base_dir)/api_touch_reload
harakiri = 60
master = true
req-logger = file:/var/log/uwsgi/%(app_name)/socket_access_log
logger = file:/var/log/uwsgi/%(app_name)/socket_error_log
LoadModule proxy_uwsgi_module /usr/lib64/httpd/modules/mod_proxy_uwsgi.so
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 uwsgi_http.ini & uwsgi uwsgi_socket.ini
$ curl http://192.168.0.150/http
$ curl 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: /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)
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
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
負荷テストのエラーの理由
[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
対応方法