😸

[SadServers] 解説 "Melbourne": WSGI with Gunicorn

2023/01/19に公開

"Melbourne": WSGI with Gunicorn

SadServersの "Melbourne": WSGI with Gunicorn の解説です。

SadServers って何? って人は、 SadServers解説 を見てください。
一言でいうと、LeetCode (コーディング問題集) の Linuxサーバ設定版のようなものです。

問題

問題を見ていきます。 > の箇所は DeepLでの翻訳です。

Scenario: "Melbourne": WSGI with Gunicorn

Level: Medium

Description: There is a Python WSGI web application file at /home/admin/wsgi.py , the purpose of which is to serve the string "Hello, world!". This file is served by a Gunicorn server which is fronted by an nginx server (both servers managed by systemd). So the flow of an HTTP request is: Web Client (curl) -> Nginx -> Gunicorn -> wsgi.py . The objective is to be able to curl the localhost (on default port :80) and get back "Hello, world!", using the current setup.
> /home/admin/wsgi.py に Python WSGI ウェブアプリケーションファイルがあり、その目的は "Hello, world!" という文字列を提供することです。このファイルは、nginxサーバをフロントとするGunicornサーバによって提供されます(両サーバはsystemdによって管理されています)。つまり、HTTPリクエストの流れはWeb Client (curl) -> Nginx -> Gunicorn -> wsgi.py .目標は、現在の設定で、localhost (on default port :80) を curl して "Hello, world!" を返せるようにすることです。

Test: curl -s http://localhost returns Hello, world! (serving the wsgi.py file via Gunicorn and Nginx)
> curl -s http://localhost return Hello, world!(wsgi.pyファイルをGunicornとNginxで提供する)


Time to Solve: 20 minutes.

OS: Debian 11

Python WSGIというのは、Webサーバと Pythonアプリケーションサーバを結びつける規格です。
[Gunicorn](https://github.com/benoitc/gunicorn)というのは、Python WSGIを実装している、アプリケーションサーバで、wsgi.pyを実行しています。
WebサーバNginxは、Python WSGIに対応しているので、WSGI対応のGunicornと連携することができます。
つまり、次のような関係となっています。

解説

早速、curlを実行します。

admin@ip-172-31-43-152:/$ curl -s http://localhost
# 何も返ってこない。
# 詳細を見るために、-s の代わりに -v を指定する。
admin@ip-172-31-43-152:/$ curl -v http://localhost
*   Trying 127.0.0.1:80...
* connect to 127.0.0.1 port 80 failed: Connection refused
* Failed to connect to localhost port 80: Connection refused
* Closing connection 0
curl: (7) Failed to connect to localhost port 80: Connection refused

port 80に接続できていません。 port 80 で待ち受けるアプリは、Nginxなので、Nginxが正しく動作しているかを確認します。

admin@ip-172-31-43-152:/$ systemctl status nginx
● nginx.service - A high performance web server and a reverse prox>
     Loaded: loaded (/lib/systemd/system/nginx.service; disabled; >
     Active: inactive (dead)
       Docs: man:nginx(8)
Active: inactive (dead)

となっているので、Nginxは起動していません。 なので、起動します。

# nginxの起動
admin@ip-172-31-43-152:/var/log/nginx$ sudo systemctl  start nginx

# 起動したか確認する。
admin@ip-172-31-43-152:/var/log/nginx$ sudo 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 2023-01-19 07:48:03 UTC; 6s ago
       Docs: man:nginx(8)
    Process: 964 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited>
    Process: 965 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=>
   Main PID: 966 (nginx)
      Tasks: 3 (limit: 524)
     Memory: 11.2M
        CPU: 41ms
     CGroup: /system.slice/nginx.service
             ├─966 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
             ├─967 nginx: worker process
             └─968 nginx: worker process

Jan 19 07:48:03 ip-172-31-43-152 systemd[1]: Starting A high performance web server and a reverse>
Jan 19 07:48:03 ip-172-31-43-152 systemd[1]: Started A high performance web server and a reverse >

正しく起動しました。では、curlを再度実行します。

admin@ip-172-31-43-152:/etc/nginx$ curl -v http://localhost
*   Trying 127.0.0.1:80...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: localhost
> User-Agent: curl/7.74.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 502 Bad Gateway
< Server: nginx/1.18.0
< Date: Thu, 19 Jan 2023 08:53:46 GMT
< Content-Type: text/html
< Content-Length: 157
< Connection: keep-alive
< 
<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>
* Connection #0 to host localhost left intact

port 80には接続できましたが、502エラーとなっています。
Gunicornとの接続ができているか確認するために、Nginxの設定を見ます。

# Nginx の設定ファイルのあるフォルダに移動
admin@ip-172-31-43-152:/$ cd /etc/nginx

# gunicorn関連の設定があるか確認。
admin@ip-172-31-43-152:/etc/nginx$ grep guni *
grep: conf.d: Is a directory
grep: modules-available: Is a directory
grep: modules-enabled: Is a directory
grep: sites-available: Is a directory
grep: sites-enabled: Is a directory
grep: snippets: Is a directory

# 無いので、サブフォルダを確認
admin@ip-172-31-43-152:/etc/nginx$ grep guni */*
sites-available/default:        proxy_pass http://unix:/run/gunicorn.socket;
sites-enabled/default:        proxy_pass http://unix:/run/gunicorn.socket;

/etc/nginx/sites-available/default/etc/nginx/sites-enable/defaultの 2ファイルにgunicorn関連の設定があります。 どうやら、Unixドメインソケット unix:/run/gunicorn.socket に接続しているようです。

Gunicorn側の設定を見てみます。設定ファイルなどよくわからないので、とりあえず、ps で 実行時の引数を見てみます。

admin@ip-172-31-43-152:/etc/nginx$ ps auxww | grep guni
admin        592  0.0  4.7  27480 21980 ?        Ss   07:43   0:00 /usr/bin/python3 /usr/local/bin/gunicorn --bind unix:/run/gunicorn.sock wsgi
admin        653  0.0  4.1  27560 19308 ?        S    07:43   0:00 /usr/bin/python3 /usr/local/bin/gunicorn --bind unix:/run/gunicorn.sock wsgi

どうやら、 Gunicornは、 Unixドメインソケット unix:/run/gunicorn.sock で待ち受けているようです。

unix:/run/gunicorn.sockunix:/run/gunicorn.socket で違いがありますね。こちらを修正します。

/etc/nginx/sites-available/defaultviで開いて、

proxy_pass http://unix:/run/gunicorn.socket;

proxy_pass http://unix:/run/gunicorn.sock;

に修正します。/etc/nginx/sites-enable/default/etc/nginx/sites-available/defaultのシンボリックリンクなので修正不要です。

Nginxの設定を変更したので、Nginxを再起動して、curlを実行します。

# Nginxの再起動
admin@ip-172-31-43-152:/$ sudo systemctl restart  nginx

# curl実行
admin@ip-172-31-43-152:/etc/nginx/sites-available$ curl -v http://localhost
*   Trying 127.0.0.1:80...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: localhost
> User-Agent: curl/7.74.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.18.0
< Date: Thu, 19 Jan 2023 07:56:53 GMT
< Content-Type: text/html
< Content-Length: 0
< Connection: keep-alive
< 
* Connection #0 to host localhost left intact

200が返ってくるようになりました。ただ、Hello, world!は返ってきていません。

/home/admin/wsgi.py ファイルを見ます。

admin@ip-172-31-43-152:~$ cd /home/admin/
admin@ip-172-31-43-152:~$ cat 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となっています。 返す文字列 Hello, world!は 13バイト (13文字)なので、ここを 13viで修正します。

admin@ip-172-31-43-152:~$ cat wsgi.py 
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html'), ('Content-Length', '13'), ])
    return [b'Hello, world!']

curlを再度実行します。

# curl実行
admin@ip-172-31-43-152:/etc/nginx/sites-available$ curl -v http://localhost
*   Trying 127.0.0.1:80...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: localhost
> User-Agent: curl/7.74.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.18.0
< Date: Thu, 19 Jan 2023 07:56:53 GMT
< Content-Type: text/html
< Content-Length: 0
< Connection: keep-alive
< 
* Connection #0 to host localhost left intact

おや、 Content-Length: 0 のままです。 wsgi.pyの修正が反映されていないようなので、Gunicornの再起動します。

[update] Gunicornをsystemctlで再起動するように修正。

admin@ip-172-31-44-185:~$ sudo systemctl restart gunicorn 
admin@ip-172-31-44-185:~$ sudo systemctl status  gunicorn 
● gunicorn.service - gunicorn daemon
     Loaded: loaded (/etc/systemd/system/gunicorn.service; enabled; vendor preset: enabled)
     Active: active (running) since Sun 2023-02-05 14:06:19 UTC; 5s ago
TriggeredBy: ● gunicorn.socket
   Main PID: 962 (gunicorn)
      Tasks: 2 (limit: 524)
     Memory: 16.8M
        CPU: 198ms
     CGroup: /system.slice/gunicorn.service
             ├─962 /usr/bin/python3 /usr/local/bin/gunicorn --bind unix:/run/gunicorn.sock wsgi
             └─963 /usr/bin/python3 /usr/local/bin/gunicorn --bind unix:/run/gunicorn.sock wsgi

Feb 05 14:06:19 ip-172-31-44-185 systemd[1]: Started gunicorn daemon.
Feb 05 14:06:20 ip-172-31-44-185 gunicorn[962]: [2023-02-05 14:06:20 +0000] [962] [INFO] Starting gunico>
Feb 05 14:06:20 ip-172-31-44-185 gunicorn[962]: [2023-02-05 14:06:20 +0000] [962] [INFO] Listening at: u>
Feb 05 14:06:20 ip-172-31-44-185 gunicorn[962]: [2023-02-05 14:06:20 +0000] [962] [INFO] Using worker: s>
Feb 05 14:06:20 ip-172-31-44-185 gunicorn[963]: [2023-02-05 14:06:20 +0000] [963] [INFO] Booting worker >
a

再起動したら、 curlを実行

admin@ip-172-31-43-152:~$ curl -v http://localhost
*   Trying 127.0.0.1:80...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: localhost
> User-Agent: curl/7.74.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.18.0
< Date: Thu, 19 Jan 2023 08:00:51 GMT
< Content-Type: text/html
< Content-Length: 13
< Connection: keep-alive
< 
* Connection #0 to host localhost left intact
Hello, world!admin@ip-172-31-43-152:~$ 

正しく、 Hello, world!が返ってきました。

Check My Solutionで確認して終わりです。

Discussion