SadServers解説#21 "Melbourne": WSGI with Gunicorn
問題概要
シナリオ
Gunicornを使用したWSGI
問題詳細
/home/admin/wsgi.py
というPython WSGIウェブアプリケーションファイルがあり、そのアプリケーションは文字列 "Hello, world!" を返します。このファイルはGunicornサーバーによって提供され、その手前にはnginxサーバーが配置されています(両方のサーバーはsystemdによって管理されています)。
HTTPリクエストのフローは次のようになります
ウェブクライアント (curl) -> Nginx -> Gunicorn -> wsgi.py
この設定を使用して、localhost(アクセスするポートは、デフォルトポートの80)にcurlコマンドを実行し、「Hello, world!」が返されるようにすることが目標です。
解決判定
Check My Solution
ボタンをクリックしてください。
解答が正解かどうか、コマンドプロンプト上で確認することも可能です。次のコマンドを実行して、以下と同じ出力が得られた場合は正解です。
$ curl -s http://localhost
Hello, world!
問題解決の方針
【表示する】
今回の問題は、WEBアプリケーションのトラブルシューティングです。
コマンドのエラーメッセージを見たり、ログを見たりしながら、一つずつ問題に対処していきましょう。
解決の手順を表示する
-
curl
コマンドを実行し、エラーを読んで対処する -
nginx
、gunicorn
が起動しているか確認し、問題があれば対処する - 問題に対処したら、再度
curl
を実行してエラーメッセージを読んで対処する - 解決せず行き詰ってしまったら、ログを探し、問題に対処する
ヒント
一部、SadServers公式のヒントを改変しています。
ヒント1
まずは、curl
コマンドを実行してみましょう。
実行コマンド
-I
のオプションを指定することで、レスポンスヘッダーを表示することができます。(-s
オプションは、進捗やエラーを表示しないオプションです。)
$ curl -s http://localhost
$ curl -I http://localhost
curl: (7) Failed to connect to localhost port 80: Connection refused
そもそもWEBアプリケーションへの通信が通っていないようです。
ヒント2
nginx
、gunicorn
の状態を確認しましょう。
実行コマンド
nginx
、gunicorn
はsystemd
に登録されているとのことなので、systemctl
コマンドでサービスの状態を確認しましょう。
$ systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; disabled; vendor preset: enabled)
Active: inactive (dead)
Docs: man:nginx(8)
$ systemctl status gunicorn
● gunicorn.service - gunicorn daemon
Loaded: loaded (/etc/systemd/system/gunicorn.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2024-05-16 14:09:40 UTC; 2min 25s ago
TriggeredBy: ● gunicorn.socket
Main PID: 611 (gunicorn)
Tasks: 2 (limit: 524)
Memory: 17.1M
CPU: 296ms
CGroup: /system.slice/gunicorn.service
├─611 /usr/bin/python3 /usr/local/bin/gunicorn --bind unix:/run/gunicorn.sock wsgi
└─681 /usr/bin/python3 /usr/local/bin/gunicorn --bind unix:/run/gunicorn.sock wsgi
May 16 14:09:40 [hostname(マスクしています)] systemd[1]: Started gunicorn daemon.
May 16 14:09:41 [hostname(マスクしています)] gunicorn[611]: [2024-05-16 14:09:41 +0000] [611] [INFO] Starting gunicorn 20.1.0
May 16 14:09:41 [hostname(マスクしています)] gunicorn[611]: [2024-05-16 14:09:41 +0000] [611] [INFO] Listening at: unix:/run/gunicorn.sock (611)
May 16 14:09:41 [hostname(マスクしています)] gunicorn[611]: [2024-05-16 14:09:41 +0000] [611] [INFO] Using worker: sync
May 16 14:09:41 [hostname(マスクしています)] gunicorn[681]: [2024-05-16 14:09:41 +0000] [681] [INFO] Booting worker with pid: 681
nginx
が起動していないので、起動します。
$ sudo systemctl restart nginx
$ systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; disabled; vendor preset: enabled)
Active: active (running) since Thu 2024-05-16 14:12:47 UTC; 3s ago
Docs: man:nginx(8)
Process: 884 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
Process: 885 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
Main PID: 886 (nginx)
Tasks: 3 (limit: 524)
Memory: 11.1M
CPU: 34ms
CGroup: /system.slice/nginx.service
├─886 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
├─887 nginx: worker process
└─888 nginx: worker process
May 16 14:12:47 [hostname(マスクしています)] systemd[1]: Starting A high performance web server and a reverse proxy server...
May 16 14:12:47 [hostname(マスクしています)] systemd[1]: Started A high performance web server and a reverse proxy server.
特にエラーもなく起動できたようです。
ヒント3
nginx
が起動したので、再びcurl
コマンドを実行してみましょう。
$ curl -s http://localhost
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.18.0</center>
</body>
</html>
$
$ curl -I http://localhost
HTTP/1.1 502 Bad Gateway
Server: nginx/1.18.0
Date: Thu, 16 May 2024 14:13:21 GMT
Content-Type: text/html
Content-Length: 157
Connection: keep-alive
エラーメッセージが変わりました。
BadGateway?WEBアプリケーションに到達できてはいますが、何か問題が起きているようです。
ログを調査してみましょう。
実行コマンド1
/var/log
以下のログを見てみます。
$ less /var/log/
alternatives.log btmp daemon.log dpkg.log.1 lastlog runit/ user.log.1
alternatives.log.1 btmp.1 daemon.log.1 faillog messages syslog wtmp
apt/ chrony/ debug journal/ messages.1 syslog.1
auth.log cloud-init-output.log debug.1 kern.log nginx/ unattended-upgrades/
auth.log.1 cloud-init.log dpkg.log kern.log.1 private/ user.log
$ less /var/log/nginx/
access.log access.log.1 error.log error.log.1
$ less /var/log/nginx/error.log
$
$
$
$ cat /var/log/
alternatives.log btmp daemon.log dpkg.log.1 lastlog runit/ user.log.1
alternatives.log.1 btmp.1 daemon.log.1 faillog messages syslog wtmp
apt/ chrony/ debug journal/ messages.1 syslog.1
auth.log cloud-init-output.log debug.1 kern.log nginx/ unattended-upgrades/
auth.log.1 cloud-init.log dpkg.log kern.log.1 private/ user.log
$ cat /var/log/nginx/
access.log access.log.1 error.log error.log.1
$ cat /var/log/nginx/error.log
2024/05/16 14:13:14 [crit] 888#888: *1 connect() to unix:/run/gunicorn.socket failed (2: No such file or directory) while connecting to upstream, client: 127.0.0.1, server: , request: "GET / HTTP/1.1", upstream: "http://unix:/run/gunicorn.socket:/", host: "localhost"
2024/05/16 14:13:21 [crit] 888#888: *3 connect() to unix:/run/gunicorn.socket failed (2: No such file or directory) while connecting to upstream, client: 127.0.0.1, server: , request: "HEAD / HTTP/1.1", upstream: "http://unix:/run/gunicorn.socket:/", host: "localhost"
最後の行を見ると、/run/gunicorn.socket
というファイルで問題が起きているようです。
このファイルを見てみましょう。
実行コマンド2
$ ls -l /run/gunicorn.socket
ls: cannot access '/run/gunicorn.socket': No such file or directory
$ ls -l /run/ | grep gunicorn
srw-rw-rw- 1 root root 0 May 16 14:09 gunicorn.sock
/run/gunicorn.socket
というファイルは存在せず、代わりに/run/gunicorn.sock
というファイルが存在しています。
nginx
の設定内で、指定しているファイル名が間違っているようですね。修正しましょう。
$ sudo cp /etc/nginx/sites-enabled/default /etc/nginx/sites-enabled/default.bak
$ vi /etc/nginx/sites-enabled/default
$ sudo vi /etc/nginx/sites-enabled/default
$ diff /etc/nginx/sites-enabled/default /etc/nginx/sites-enabled/default.bak
6c6
< proxy_pass http://unix:/run/gunicorn.sock;
---
> proxy_pass http://unix:/run/gunicorn.socket;
$ systemctl restart gunicorn
ヒント4
$ curl -s http://localhost
$ curl -I http://localhost
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Thu, 16 May 2024 14:22:20 GMT
Content-Type: text/html
Content-Length: 0
Connection: keep-alive
相変わらず、所望の結果は得られないようですが、エラーメッセージの内容が変わりました。よく見てみましょう。
実行コマンド
Content-Length: 0
と出ています。コンテンツの長さが0?WEBコンテンツの設定がおかしいようです。
$ cat /etc/nginx/sites-enabled/default
$ cat /home/admin/wsgi.py
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html'), ('Content-Length', '0'), ])
return [b'Hello, world!']
設定ファイル内で、Content-Length
というパラメータが0
と指定されています。
この数字を大きな数字に変えてみましょう。今回は、大きめに100
としてみます。
$ cp /home/admin/wsgi.py /home/admin/wsgi.py.bak
$ vi /home/admin/wsgi.py
$ diff /home/admin/wsgi.py /home/admin/wsgi.py.bak
2c2
< start_response('200 OK', [('Content-Type', 'text/html'), ('Content-Length', '100'), ])
---
> start_response('200 OK', [('Content-Type', 'text/html'), ('Content-Length', '0'), ])
$ sudo systemctl restart gunicorn
$ curl -s http://localhost
Hello, world!
curl
が通りました!
「いきなり問題を解き始めても調べるばかりになってしまう…」 「やりたいことが分かっても、コマンドが分からない…」 という方は、下記の記事でLinuxのコマンドを復習してから、SadServersの問題に取り掛かってみてはいかがでしょうか。
余談
$ cat /etc/nginx/sites-enabled/default
$ cat /home/admin/wsgi.py
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html'), ('Content-Length', '0'), ])
return [b'Hello, world!']
WEBアプリケーションのファイルを見ていて、最後の行でb
がコードの構文ミスかと思っていたら、その後の文字列がバイト列であることを示すプレフィックスらしいです。
b
を消しても表示されるか出来心でやってみましたが、やはり表示できませんでした。
$ diff /home/admin/wsgi.py /home/admin/wsgi.py.bak
2,3c2,3
< start_response('200 OK', [('Content-Type', 'text/html'), ('Content-Length', '100'), ])
< return ['Hello, world!']
---
> start_response('200 OK', [('Content-Type', 'text/html'), ('Content-Length', '0'), ])
> return [b'Hello, world!']
$ curl -s http://localhost
$ curl -I http://localhost
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Thu, 16 May 2024 14:52:20 GMT
Content-Type: text/html
Content-Length: 0
Connection: keep-alive
問題一覧はこちら
Discussion