DjangoをEC2インスタンス上で動かす環境を構築する(その8:静的コンテンツを配信できるようにする。)
概要
参考文献
- 『Pythonプロフェッショナルプログラミング 第3版』の「Chapter 11 環境構築とデプロイの自動化」
- 『現場で使える Django の教科書《実践編》』の「第7章 デプロイ」
- Djangoで静的ファイルとうまくやる
その8:静的コンテンツを配信できるようにする。
前回でDjangoをEC2インスタンス上で動かすところまではできるようになったが、静的コンテンツが404を返すことに気づいたので、静的コンテンツを配信できるようにする。
概要
- 現状で静的コンテンツが配信できていない原因の調査。
- 静的コンテンツを配信できるようにする。
原因の切り分け
原因を調査する前に、以下の動画を見たら、大体答えが説明されていたので、こちらの動画を参照しながら確認していく。
youtu.be
setting.py
のDEBUG
オプションを確認する。
動画によると、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のビルトインであるか否かは今回の事象には関係がない。
python3 manage.py runserver
で起動してみる。
Gunicornで動かしていることが原因かもしれないので、いったんNginxとGunicornを停止して、python3 manage.py runserver
でDjangoを起動してみる。
$ 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
であっても静的コンテンツは配信されない仕様であると考えられる。
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
・静的コンテンツが表示されるようになった。
・画面にもデザインが適用されるようになった。
結論
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のDEBUG
をFalse
に変更する。
$ 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}"]
・静的コンテンツを配置するディレクトリを作成して所有者を設定する。
$ sudo mkdir /var/static $ sudo chown appserver:appserver /var/static $ ls -ld /var/static drwxr-xr-x 2 appserver appserver 6 5月 27 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/;
とディレクトリの末尾にスラッシュをつける。(付けないとエラーになる。)
以下を参考にさせて頂いた。
以下、公式ドキュメント
nginx.org
・NginxとGunicornを起動する。
$ sudo systemctl start nginx.service $ sudo systemctl start gunicorn.service
・動作確認
「http://${EC2インスタンスのグローバルIP}:10080/admin/」にアクセスしてみる。
→ 画面が正常に表示された。
アクセスログをみると、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に続く。