delhi09の勉強日記

技術トピック専用のブログです。自分用のメモ書きの投稿が多いです。あくまで「勉強日記」なので記事の内容は鵜呑みにしないでください。

DjangoをEC2インスタンス上で動かす環境を構築する(その7:Gunicornの前段にNginxを配置する。)

参考文献

その7:Gunicornの前段にNginxを配置する。

概要

  • Gunicornの前段にNginxを配置する。
  • Unixドメインソケットを使用する。
  • masterプロセス・workerプロセス共に「webserver」ユーザーで起動する。

※ デフォルトではmasterプロセスは「root」、workerプロセスは「nginx」ユーザーで起動する。

・公式ドキュメントは以下
docs.gunicorn.org

手順

Nginxの設定ファイルのバックアップをとる。

これからnginx.confを編集するが、変更箇所の差分がわかるようにインストールした初期状態のnginx.confのバックアップを取っておく。

$ sudo cp nginx.conf /tmp/nginx.conf.`date +"%Y%m%d%H%M%S"`.bk
Nginxの設定ファイルを編集する。

nginx.confを以下の内容で上書きする。

user webserver;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx/pid;

include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    include /etc/nginx/conf.d/*.conf;

    server {
        listen       10080;
        server_name  localhost xxx.xxx.xxx.xxx; # EC2インスタンスのグローバルIPアドレス

        include /etc/nginx/default.d/*.conf;

        location / {
            proxy_pass http://unix:/run/gunicorn/gunicorn.sock;
        }
    }
}

・重要な変更点

  • 起動ユーザーをnginxwebserverに変更している。
  • PIDファイルのパスを/run/nginx.pid/run/nginx/pidに変更している。
  • masterプロセスも含めてrootではないユーザー(「webserver」ユーザー)で起動するので、80ポートではなく10080ポートでListenするように設定している。

※ 詳細は以下のstackoverflowのQAを参照
stackoverflow.com

nginx.serviceを編集する。

/etc/systemd/system/〜配下にnginxのserviceファイルが存在しないなと疑問に思い、以下のコマンドで探したところ、/usr/lib/systemd/system/nginx.serviceに存在した。

$ systemctl cat nginx
# /usr/lib/systemd/system/nginx.service
[Unit]
Description=The nginx HTTP and reverse proxy server
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
# Nginx will fail to start if /run/nginx.pid already exists but has the wrong
# SELinux context. This might happen when running `nginx -t` from the cmdline.
# https://bugzilla.redhat.com/show_bug.cgi?id=1268621
ExecStartPre=/usr/bin/rm -f /run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/bin/kill -s HUP $MAINPID
KillSignal=SIGQUIT
TimeoutStopSec=5
KillMode=mixed
PrivateTmp=true

[Install]
WantedBy=multi-user.target
>||

・PIDファイルのパスを/run/nginx.pid/run/nginx/pidに変更する。

$ diff /tmp/nginx.service.bak /usr/lib/systemd/system/nginx.service
7c7
< PIDFile=/run/nginx.pid
---
> PIDFile=/run/nginx/pid
11c11
< ExecStartPre=/usr/bin/rm -f /run/nginx.pid
---
> ExecStartPre=/usr/bin/rm -f /run/nginx/pid
$

・起動ユーザーをwebserverユーザーに設定する。

以下を追加する。

...
[Service]
User=webserver
...

nginx.serviceを変更しているのでリロードする。

$ sudo systemctl daemon-reload
gunicorn.serviceを編集する。

gunicorn.serviceを編集する。

$ cd /etc/systemd/system
$ sudo vi gunicorn.service

・変更点①
gunicorn.socketへの依存関係を設定する。

Description=gunicorn daemon
Requires=gunicorn.socket #この行を追加

・変更点②
起動方法をIP・ポートからUnixドメインソケットに変更する。

$ diff /tmp/gunicorn.service.bak gunicorn.service
15c15
<                 --bind=localhost:8000 \
---
>                 --bind unix:/run/gunicorn/gunicorn.sock \
$
gunicorn.socketを作成する。

/etc/systemd/system直下にgunicorn.socketファイルを作成して、パーミッション644に設定する。

$ pwd
/etc/systemd/system
$ sudo touch gunicorn.socket
$ sudo chmod 644 gunicorn.socket
$ ls -l gunicorn.socket
-rw-r--r-- 1 root root 0  526 16:16 gunicorn.socket

・以下を設定する。

[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn/gunicorn.sock

[Install]
WantedBy=sockets.target
・serviceに登録する。

gunicorn.serviceの設定内容を変更しているのでリロードする。

$ sudo systemctl daemon-reload

gunicorn.socketをserviceに登録する。

$ sudo systemctl enable gunicorn.socket
・Nginx関連の各種ディレクトリの作成および、所有者・パーミッションの設定を行う。

/run/nginxディレクトリを作成して、所有者をwebserver:webserverに変更する。

$ sudo mkdir /run/nginx
$ sudo chown webserver:webserver /run/nginx
$ ls -ld /run/nginx
drwxr-xr-x 2 webserver webserver 40  526 17:05 /run/nginx

/var/log/nginxディレクトリの所有者をwebserver:webserverパーミッション755に変更する。

$ ls -ld /var/log/nginx
drwxrwx--- 2 nginx root 71  526 03:41 /var/log/nginx
$ sudo chown webserver:webserver /var/log/nginx
$ sudo chmod 755 /var/log/nginx
$ ls -ld /var/log/nginx
drwxr-xr-x 2 webserver webserver 6  526 17:45 /var/log/nginx

/var/lib/nginx以下の所有者をwebserver:webserverに変更する。

$ ls -ld /var/lib/nginx
drwxrwx--- 3 nginx root 17  524 18:59 /var/lib/nginx
$ sudo chown -R webserver:webserver /var/lib/nginx
$ ls -ld /var/lib/nginx
drwxrwx--- 3 webserver webserver 17  524 18:59 /var/lib/nginx

/etc/nginx以下のディレクトリの所有者をwebserver:webserverに変更する。

$ ls -ld /etc/nginx
drwxr-xr-x 4 root root 4096  526 16:59 /etc/nginx
$ sudo chown -R webserver:webserver /etc/nginx
$ ls -ld /etc/nginx
drwxr-xr-x 4 webserver webserver 4096  526 16:59 /etc/ngin

※ 今回はキャッシュ機能は使用しないが、キャッシュ機能を使用する場合はキャッシュファイルを保存するディレクトリにもパーミッションの設定が必要な場合がある。

docs.nginx.com

Nginxの設定ファイルをシンタックスチェックする。

シンタックスエラーがないことを確認する。

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$
Nginxを起動する。

・起動

$ sudo systemctl start nginx.service

・status確認

$ sudo systemctl status nginx.service
● nginx.service - The nginx HTTP and reverse proxy server
   Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; vendor preset: disabled)
   Active: active (running) since 火 2020-05-26 17:48:40 UTC; 2min 43s ago
  Process: 2556 ExecStart=/usr/sbin/nginx (code=exited, status=0/SUCCESS)
  Process: 2553 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=0/SUCCESS)
  Process: 2552 ExecStartPre=/usr/bin/rm -f /run/nginx/pid (code=exited, status=0/SUCCESS)
 Main PID: 2559 (nginx)
   CGroup: /system.slice/nginx.service
           ├─2559 nginx: master process /usr/sbin/nginx
           └─2561 nginx: worker process

 526 17:48:39 ip-172-31-16-161.us-west-2.compute.internal systemd[1]: Starting The nginx HTTP and reverse proxy server...
 526 17:48:39 ip-172-31-16-161.us-west-2.compute.internal nginx[2553]: nginx: [warn] the "user" directive makes sense only if the master pro...onf:1
 526 17:48:39 ip-172-31-16-161.us-west-2.compute.internal nginx[2553]: nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
 526 17:48:39 ip-172-31-16-161.us-west-2.compute.internal nginx[2553]: nginx: configuration file /etc/nginx/nginx.conf test is successful
 526 17:48:39 ip-172-31-16-161.us-west-2.compute.internal nginx[2556]: nginx: [warn] the "user" directive makes sense only if the master pro...onf:1
 526 17:48:39 ip-172-31-16-161.us-west-2.compute.internal systemd[1]: Failed to read PID from file /run/nginx/pid: Invalid argument
 526 17:48:40 ip-172-31-16-161.us-west-2.compute.internal systemd[1]: Started The nginx HTTP and reverse proxy server.
Hint: Some lines were ellipsized, use -l to show in full.
$

→ 「Failed to read PID from file /run/nginx/pid: Invalid argument」という警告が気になるが、とりあえず起動はしているように見受けられる。

・プロセス確認

$ ps aux | grep "nginx" | grep -v "grep"
webserv+  2559  0.0  0.2 121432  2168 ?        Ss   17:48   0:00 nginx: master process /usr/sbin/nginx
webserv+  2561  0.0  0.3 121888  3208 ?        S    17:48   0:00 nginx: worker process
$

master processworker processともに「webserver」ユーザーで起動している。

・ログ確認

$ ls -l /var/log/nginx
合計 4
-rw-r--r-- 1 webserver webserver   0  526 17:48 access.log
-rw-r--r-- 1 webserver webserver 326  526 17:48 error.log
$ cat /var/log/nginx/error.log
2020/05/26 17:48:39 [warn] 2553#0: the "user" directive makes sense only if the master process runs with super-user privileges, ignored in /etc/nginx/nginx.conf:1
2020/05/26 17:48:39 [warn] 2556#0: the "user" directive makes sense only if the master process runs with super-user privileges, ignored in /etc/nginx/nginx.conf:1
$

→ ログも正常に出力されている。「nginx.confの「user」ディレクティブはmasterプロセスをrootではないユーザーで起動している場合には無視されますよ」という警告が出ているが、これは害のある警告ではないので無視する。

Guniconを起動する。

・起動

$ sudo systemctl start gunicorn.service

・status確認

$ sudo systemctl status gunicorn.service
● gunicorn.service - gunicorn daemon
   Loaded: loaded (/etc/systemd/system/gunicorn.service; enabled; vendor preset: disabled)
   Active: active (running) since 火 2020-05-26 20:09:34 UTC; 1min 3s ago
  Process: 3394 ExecStop=/bin/kill -s TERM $MAINPID (code=exited, status=0/SUCCESS)
 Main PID: 3417 (gunicorn)
   Status: "Gunicorn arbiter booted"
   CGroup: /system.slice/gunicorn.service
           ├─3417 /usr/bin/python3 /usr/local/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunicorn/gunicorn.sock mysite.wsgi
           ├─3420 /usr/bin/python3 /usr/local/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunicorn/gunicorn.sock mysite.wsgi
           ├─3421 /usr/bin/python3 /usr/local/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunicorn/gunicorn.sock mysite.wsgi
           └─3422 /usr/bin/python3 /usr/local/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunicorn/gunicorn.sock mysite.wsgi

 526 20:09:34 ip-172-31-16-161.us-west-2.compute.internal systemd[1]: Starting gunicorn daemon...
 526 20:09:34 ip-172-31-16-161.us-west-2.compute.internal gunicorn[3417]: [2020-05-26 20:09:34 +0000] [3417] [INFO] Starting gunicorn 20.0.4
 526 20:09:34 ip-172-31-16-161.us-west-2.compute.internal gunicorn[3417]: [2020-05-26 20:09:34 +0000] [3417] [INFO] Listening at: unix:/run/...3417)
 526 20:09:34 ip-172-31-16-161.us-west-2.compute.internal gunicorn[3417]: [2020-05-26 20:09:34 +0000] [3417] [INFO] Using worker: sync
 526 20:09:34 ip-172-31-16-161.us-west-2.compute.internal systemd[1]: Started gunicorn daemon.
 526 20:09:34 ip-172-31-16-161.us-west-2.compute.internal gunicorn[3417]: [2020-05-26 20:09:34 +0000] [3420] [INFO] Booting worker with pid: 3420
 526 20:09:34 ip-172-31-16-161.us-west-2.compute.internal gunicorn[3417]: [2020-05-26 20:09:34 +0000] [3421] [INFO] Booting worker with pid: 3421
 526 20:09:34 ip-172-31-16-161.us-west-2.compute.internal gunicorn[3417]: [2020-05-26 20:09:34 +0000] [3422] [INFO] Booting worker with pid: 3422
Hint: Some lines were ellipsized, use -l to show in full.
]$

Unixドメインソケットの確認

$ ls -l /run/gunicorn/
合計 0
srwxrwxrwx 1 appserver appserver 0  526 20:09 gunicorn.sock
$
curlで動作確認

curlで叩いてみる。

$ curl -I "http://localhost:10080/polls/"
HTTP/1.1 200 OK
Server: nginx/1.16.1
Date: Tue, 26 May 2020 20:13:44 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 109
Connection: keep-alive
X-Frame-Options: DENY
X-Content-Type-Options: nosniff

$

→ 200が返ってくる。

・Nginxのアクセスログ

$ tail /var/log/nginx/access.log
127.0.0.1 - - [26/May/2020:20:13:44 +0000] "HEAD /polls/ HTTP/1.1" 200 0 "-" "curl/7.61.1" "-"
$

Djangoのアプリケーションログ

$ tail /var/log/django/app.log
2020-05-27 05:16:46,366 [INFO] Hello world!
$
画面から動作確認

・セキュリティグループのインバウンドの設定に以下を追加する。
セキュリティホールがあると怖いので、自宅のIPアドレスのみに絞っておく。

タイプ プロトコル ポート範囲 ソース 説明 - オプション
カスタムTCP TCP 10080 ${自宅のIPアドレス}/32 -

・ブラウザでアクセスしてみる。
ドメインはNginxのserver_nameに設定していないので、ドメインではアクセスできないので注意。

http://${EC2インスタンスのグローバルIP}:10080/admin/login/?next=/admin/

・結果
以下のようにコンテンツは取得できたが、CSSが適用されておらず、静的コンテンツが取得できていないように見受けられる。
f:id:kamatimaru:20200527060452p:plain

→ 実際にCSSファイルにアクセスしてみると、以下のように404が返される。

この問題の解決はその8に続く。