kamatimaru’s 勉強日記

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

DjangoをEC2インスタンス上で動かす環境を構築する(その8:静的コンテンツを配信できるようにする。)

参考文献

その8:静的コンテンツを配信できるようにする。

前回でDjangoをEC2インスタンス上で動かすところまではできるようになったが、静的コンテンツが404を返すことに気づいたので、静的コンテンツを配信できるようにする。

概要

  • 現状で静的コンテンツが配信できていない原因の調査。
  • 静的コンテンツを配信できるようにする。

原因の切り分け

原因を調査する前に、以下の動画を見たら、大体答えが説明されていたので、こちらの動画を参照しながら確認していく。
youtu.be

setting.pyDEBUGオプションを確認する。

動画によると、DEBUG = Falseになっていると、Djangoアプリケーションは静的コンテンツを配信しない。
※ 当たり前だが、本番環境ではDEBUG = Falseに設定するのが正しい。

確認したところ、現時点ではDEBUG = Trueになっていたので、これが原因ではない。

/polls/〜配下の静的コンテンツでも404が返されるか確認する。

動画によると、/admin/〜配下の静的コンテンツはDjangoのビルトイン機能であるため、/site-packages/〜配下にファイルが存在するとのことであった。

確認したところ、確かに/site-packages/〜配下に存在した。

$ ls /usr/local/lib64/python3.7/site-packages/django/contrib/admin/static/admin/css
autocomplete.css  changelists.css  fonts.css  login.css       responsive_rtl.css  vendor
base.css          dashboard.css    forms.css  responsive.css  rtl.css             widgets.css
$

前回、404が返されることを確認したのは、/static/admin/css/base.cssであった。

従って、/polls/〜配下の静的コンテンツでも404が返されるかを確認して、原因を切り分ける。

以下のURLにアクセスしてみる。

http://${EC2のグローバルIP}/static/polls/style.css

→ この場合でも404が返ってきたので、対象の静的コンテンツがDjangoのビルトインであるか否かは今回の事象には関係がない。

f:id:kamatimaru:20200527115438p:plain

python3 manage.py runserverで起動してみる。

Gunicornで動かしていることが原因かもしれないので、いったんNginxとGunicornを停止して、python3 manage.py runserverDjangoを起動してみる。

$ sudo systemctl stop nginx.service
$ sudo systemctl stop gunicorn.service
$ sudo su appserver
$ python3 manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
May 27, 2020 - 12:03:06
Django version 3.0.6, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

別のターミナルを開いて、curlで静的コンテンツのURLを叩いてみる。

$ curl -I "http://localhost:8000/static/admin/css/base.css"
HTTP/1.1 200 OK
Date: Wed, 27 May 2020 03:07:52 GMT
Server: WSGIServer/0.2 CPython/3.7.6
Content-Type: text/css
Content-Length: 16378
Content-Disposition: inline; filename="base.css"
Last-Modified: Sun, 24 May 2020 19:11:27 GMT

$ curl -I "http://localhost:8000/static/polls/style.css"
HTTP/1.1 200 OK
Date: Wed, 27 May 2020 03:05:56 GMT
Server: WSGIServer/0.2 CPython/3.7.6
Content-Type: text/css
Content-Length: 120
Content-Disposition: inline; filename="style.css"
Last-Modified: Sun, 24 May 2020 21:36:12 GMT

$

上記のように、この場合は200が返ってくるので、DEBUG = Trueの際に静的コンテンツが配信されるのはDjangoの内蔵サーバーの機能によるものであり、Gunicornで起動するとDEBUG = Trueであっても静的コンテンツは配信されない仕様であると考えられる。

原因の切り分けの結果

Djangoの内蔵サーバーを使用しないでGunicornからDjangoアプリケーションを起動した場合は、DEBUG = Trueであっても静的コンテンツは配信されない仕様のようである。

Gunicornを使用してかつDEBUG = Trueで静的コンテンツを配信する。

調べていると、以下のstackoverflowがヒットした。
stackoverflow.com

このQAの回答者の方が貼っているURLはリンク切れだったが、以下の公式ドキュメントが存在した。
docs.djangoproject.com

views.serve(request, path)

This view function serves static files in development.


This view is automatically enabled by runserver (with a DEBUG setting set to True). To use the view with a different local development server, add the following snippet to the end of your primary URL configuration:

from django.conf import settings
from django.contrib.staticfiles import views
from django.urls import re_path

if settings.DEBUG:
    urlpatterns += [
        re_path(r'^static/(?P<path>.*)$', views.serve),
    ]

要約すると以下のようである。

  • views.serve(request, path)というメソッドがDEBUGモードでの静的コンテンツ配信の役割を担っている。
  • このメソッドはpython3 manage.py runserverで起動した場合には自動で有効化される。
  • Djangoの内蔵サーバー以外のサーバーを使用して開発しており、静的コンテンツを配信したい場合は、上記のコードをurls.pyに追加する必要がある。

実際に、上記のコードを追加した上で、GunicornとNginxを起動してみる。

mysite/urls.pyにコードを追加する。

$ diff /tmp/urls.py.bk urls.py
15a16,18
> from django.conf import settings
> from django.contrib.staticfiles import views
> from django.urls import re_path
22a26,29
> if settings.DEBUG:
>     urlpatterns += [
>         re_path(r'^static/(?P<path>.*)$', views.serve),
>     ]
$

・GunicornとNginxを起動する。

$ sudo systemctl start gunicorn.service
$ sudo systemctl start nginx.service

・静的コンテンツが表示されるようになった。
f:id:kamatimaru:20200527125545p:plain

f:id:kamatimaru:20200527125639p:plain

・画面にもデザインが適用されるようになった。
f:id:kamatimaru:20200527125813p:plain

結論

Gunicornを使用してかつDEBUG = Trueで静的コンテンツを配信したい場合は以下のコードをurls.pyに追加する必要がある。

from django.conf import settings
from django.contrib.staticfiles import views
from django.urls import re_path

if settings.DEBUG:
    urlpatterns += [
        re_path(r'^static/(?P<path>.*)$', views.serve),
    ]

Nginxで静的コンテンツを配信する。

動画にもある通り、本番環境では静的コンテンツはpython3 manage.py collectstaticコマンドで特定のディレクトリに集約して、WEBサーバー(Nginx)で配信するのがあるべき姿なので、そのように設定していく。

・settings.pyのDEBUGFalseに変更する。
$ diff /tmp/settings.py.bak settings.py
26c26
< DEBUG = True
---
> DEBUG = False
$
ALLOWED_HOSTS を設定する。

DEBUG = Falseを設定すると、ALLOWED_HOSTSの設定が必須になるので、設定する。

ALLOWED_HOSTS = ["localhost", "127.0.0.1", "${EC2インスタンスのグローバルIP}"]

docs.djangoproject.com

・静的コンテンツを配置するディレクトリを作成して所有者を設定する。
$ sudo mkdir /var/static
$ sudo chown appserver:appserver /var/static
$ ls -ld /var/static
drwxr-xr-x 2 appserver appserver 6  527 04:28 /var/static
・settings.pyにSTATIC_ROOTを設定する。
STATIC_ROOT = "/var/static"
python3 manage.py collectstaticを実行する。
$ sudo su appserver
$ python3 manage.py collectstatic

132 static files copied to '/var/static'.
$ tree /var/static -L 2
/var/static
├── admin
│   ├── css
│   ├── fonts
│   ├── img
│   └── js
└── polls
    ├── images
    └── style.css

7 directories, 1 file
$

→ 静的コンテンツの配置が完了。

・Nginxに以下を設定する。
http {
# ...
    server {
    # ...
        # 追加ここから
        location /static/ {
            alias /var/static/;
        }
       # 追加ここまで

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

・ポイント

  • rootではなくaliasを使う。
  • root /var/static;と書くと、/var/static/static/〜を検索してしまう。
  • rootを使用する場合、root /var;と書くことになるが、/varを指定するのは、実際にセキュリティ上問題があるのかどうかはともかく、気持ちが悪い。
  • aliasを使用する場合は、alias /var/static/;ディレクトリの末尾にスラッシュをつける。(付けないとエラーになる。)

以下を参考にさせて頂いた。

kinjouj.github.io

stackoverflow.com

以下、公式ドキュメント
nginx.org

・NginxとGunicornを起動する。
$ sudo systemctl start nginx.service
$ sudo systemctl start gunicorn.service
・動作確認

http://${EC2インスタンスグローバルIP}:10080/admin/」にアクセスしてみる。
→ 画面が正常に表示された。
f:id:kamatimaru:20200527135515p:plain

アクセスログをみると、Nginxが静的コンテンツを配信していることが確認できる。

$ grep "/static/" /var/log/nginx/access.log | tail | awk -F\" '{ print $2 $3; }'
GET /static/polls/style.css HTTP/1.1 200 120
GET /static/admin/css/base.css HTTP/1.1 200 16378
GET /static/admin/css/login.css HTTP/1.1 200 1233
GET /static/admin/css/responsive.css HTTP/1.1 200 18052
GET /static/admin/css/fonts.css HTTP/1.1 200 423
GET /static/admin/fonts/Roboto-Regular-webfont.woff HTTP/1.1 200 85876
GET /static/admin/fonts/Roboto-Light-webfont.woff HTTP/1.1 200 85692
$

以上で「その8:静的コンテンツを配信できるようにする。」は完了。

その9に続く。