tell-k
BePROUD.inc
情弱プログラマー
最近Q&Aサイトとかで「 本番にあげたらCSS/JS/画像が表示されない! 」というのをよく見かけました
私も最初の頃は良く分からなかくて辛かった
入門したてのころはPythonコードやDBやらを覚えることがいっぱい
何とく表示できたりするので静的ファイル扱いは後回しにされがち
結果、後から失敗して痛い目をみる辛い
そういう経験も踏まえて、DjangoもといWebアプリ開発における静的ファイルの取り回しについてまとめてみようと思った次第
これから Django を学び始める人
Webアプリを作ろうと勉強してる人
Django は少し触ったことがあるけど、 collectstatic
が意味わからなかった人
settings.DEBUG = False
にして静的ファイルが表示されなくて焦った経験がある 私みたいな人
「わかるっ!俺には静的ファイルがわかる!!!」
サンプルコードは全て Python 3.7, Django 2.2.1
クラウドサービスの話は AWS がメインです
HTTPサーバーの話は nginx がメインです
静的ファイルとは
わたしの失敗談
Djangoの静的ファイルの扱い
本番公開のパターン
単一のサーバ
複数台のサーバー
クラウドのストレージの利用
CDNを利用する
静的ファイル関連Tips
参考
静的ファイルとは
そもそも静的ファイルとはなんでしょうか?
よく 「静的ファイルとは CSS, JS、画像ファイル とかです」
みたいなざっくりな説明されますが Python スクリプトだってファイルじゃないですか?静的とは一体?
みたいな疑問をもったりしませんでしたか?
MDN には以下のように書いてあります
静的Webサーバ、またはスタックは、コンピューター (ハードウェア) と HTTP サーバ (ソフトウェア) から構成されます サーバが保持しているファイルをブラウザーへ「そのまま」送るので、「静的」と呼ばれます
一方動的とは以下のように書いてあります
動的Webサーバは、静的Webサーバに加えて追加のソフトウェア、 一般的にはアプリケーションサーバおよびデータベースで構成されます アプリケーションサーバが、 HTTP サーバを通してブラウザーに送信する前に、保持しているファイルを更新するので「動的」と呼ばれます
「動的」はサーバがコンテンツを処理したり、データベースからその場で作成したりすることを意味します この方法はより柔軟性を提供できますが、 技術スタックがより扱いにくくなり、Webサイトを構築することは劇的に複雑になります
Webアプリをつくるためのフレームワークです
動的コンテンツを生成し配信する のが主な役割です
アプリケーションサーバーとしてサーバー内に配置されて稼働します
本番環境では gunicorn
や uWSGI
というライブラリを利用して WSGIアプリケーション
として運用されるのが一般的です
静的ファイルとはHTTPサーバーがそのまま返すファイル群の事を指す
そのまま返すならだいたい全部静的ファイル
静的ファイルは静的コンテンツとも呼ばれたり static
ファイルや assets
などとも呼ばれる
呼び方の話
MDNにならってHTTPサーバーと呼んでいますが、一般的にはこのソフトウェアのことをさしてWebサーバーと呼んだりもします
この発表では HTTPサーバー で統一します
私が失敗した話
Django を触り始めた頃に失敗した話
公開前の本番サーバーにDjangoアプリをデプロイ
アプリが動いたと思ったら
画像もJSもブラウザで読み込めてないよ
ローカル環境や、開発サーバーでは表示されてましたよ?
開発サーバーとの差異は 本番用に設定ファイルを変えたくらいなのに
プロジェクト内のstaticディレクトリを配信元に設定した
そして nginx
再起動したぞー
やったーサイトが見えたー
分からない俺はなにも分からない
そもそも Django AdminのCSS/JSはサーバー内のどこにあるんや??
良く分からないけど調べたら site-packages以下 にあった!
例) djangoのadminアプリの中に静的ファイルがある
/site-packages/django/contrib/admin/static/
└── admin
├── css
├── fonts
├── img
└── js
これを 自分のプロジェクトにコピーして表示できた!ヨシ!
Djangoの静的ファイルの扱い
茶番 はこれくらいにして Django の話に戻りましょう
ここからは、Djangoでの静的ファイルを基本的な扱い方について見ていきましょう
ついでにメディアファイルの話もします
その過程でさっきの何が悪かったのかもを解説します
開発する時は、 python manage.py runserver
でDjangoアプリを起動
デフォルトで何もしなければ 静的ファイル を配信してくれます
settings.DEBUG = True の時だけ有効になります
なぜ?
開発時に静的ファイルが表示されないのは不便だから
だから開発モードの時だけは静的ファイルを配信する
django.contrib.staticfiles
が機能を提供している
あくまで開発の補助用なので 本番で利用することを想定していない
上で述べたたように django.contrib.staticfiles を利用する場合、
DEBUG が True であれば runserver は自動的にこの処理を行います
〜略〜
この機能は本番環境で利用するのに適していません!
一般的なデプロイ方法に関しては 静的ファイルのデプロイ を参照ください
先ほどの 失敗1. CSSが全く当たってない… の理由がこれです
本番で settings.DEBUG = False
に設定したまではよかったが…
この結果、 開発(DEBUG)モードがオフになった ので 静的ファイル配信機能も自動的にオフになった
結果CSSが当たってなかった…
言われてみれば当たり前だけど、当時は良くわかってなかった
Django自体が静的ファイル配信に最適化されてない
なので python manage.py version runserver --insecure
で公開もダメ
デバッグ情報が画面上に出力される
例えばファイルパス、設定内容などが表示される
結果サイトの攻撃者に優位な情報を渡すことになります
それ以外にも
SQLクエリも常に保存するのでメモリの消費も激しい
パフォーマンスの観点でもよくない
via. https://docs.djangoproject.com/ja/2.2/ref/settings/#debug
Q.Django が静的ファイルを配信しないなら、何が配信するの?
A.静的ファイルを配信するHTTPサーバーを用意します
例えば Djangoプロジェクト が 以下のような構成だったら
# settings.py ---
# 静的ファイルの探索はデフォルトでは アプリの staticディレクトリ以下
# ファイルを探したりするのが面倒なので、プロジェクトの直下に
# staticディレクトリをおきます
STATICFILES_DIRS = (
os.path.join(BASE_DIR, '..', 'static'),
)
今回は プロジェクト直下の static
に全ての静的ファイルがあるという前提
myproject
├── .gitignore
├── apps
│ ├── core
│ ├── manage.py
│ ├── polls
│ ├── static # <- デフォはここから探すが、今回はここに置かない
│ └── apps
├── static # <- 全部ここに置く
│ ├── css
│ ├── images
│ └── js
└── templates
├── base.html
└── polls
HTTPサーバーとして Nginxを例にします
server {
server_name example.com;
location = /favicon.ico { access_log off; log_not_found off; }
# http://example.com/static 以下は全てファイルを静的にそのまま返す
location /static/ {
root /path/to/myproject;
}
# それ以外はDjangoアプリにプロキシする
location / {
include proxy_params;
proxy_pass http://unix:/run/uwsgi/myproject.sock;
}
}
この状態はまだ 失敗2. まだ Django Admin が真っ白 の状態です
Django系のアプリは、Pythonパッケージの中に 静的ファイルを一緒に持っています
それらも静的ファイルとして配信する必要があります
django.cotnrib.admin
だけでなく、そういうライブラリが他にもあります
とはいえHTTPサーバーに一個一個設定するのは、現実的ではない
コピペして持ってくるのも微妙
server {
server_name example.com;
location = /favicon.ico { access_log off; log_not_found off; }
# /static/admin だけは site-package以下から配信する <- 流石に辛い
location /static/admin/ {
root /path/to/venv/lib/python3.7/site-packages/django/contrib/admin/;
}
location /static/ {
root /path/to/myproject;
}
# 〜 省略 〜
なので必要な静的ファイルを一つのディレクトリに集約するコマンドがある
それが collectstatic
コマンドです集める場所の設定が STATIC_ROOT
です
# settings.py ---
STATIC_ROOT = os.path.join(BASE_DIR, '..', 'collected_static')
# 追加の静的ファイル探索パス
STATICFILES_DIRS = (
os.path.join(BASE_DIR, '..', 'static'),
)
$ python manage.py collectstatic
https://docs.djangoproject.com/ja/2.2/ref/contrib/staticfiles/#collectstatic
site-packages/django/contrib/admin/static/admin # ... ①
# ~ 省略 ~
├── apps
├── collected_static # <- 全ての静的ファイルが集約される
│ ├── admin # ... ①
│ ├── css # ... ②
│ ├── images
│ └── js
├── static # ... ② 設定で探索対象になっている
│ ├── css
│ ├── images
│ └── js
└── templates
# nginxの設定を変える
# ディレクトリ名が変わったので root -> alias に変更
location /static/ {
alias /path/to/myproject/collcected_static;
}
公開する時は DEBUG = False
にしつつ collectstatc
で静的ファイル集めましょう
集約ディレクトリを静的に配信するようにHTTPサーバーを設定しよう
あとはデプロイツールの中で collectstatic
を毎回実行させると良いです
デプロイの時に Yes/No
を毎回聞かれるのが面倒な場合は --no-input
をつけると良いです
# 毎回集めるかどうかを聞かれずに、実行されます
$ python manage.py collectstatic --no-input
Django では STAIC_
から始まる設定と、 MEDIA_
から始まる設定の2種類あります
STATIC_
は 静的ファイルの配信関連のものです
MEDIA_
は Djangoアプリが管理するファイルための設定です
Djangoアプリが管理するファイル群を メディアファイル とDjangoは呼んでいます
例えば、アップロードファイルや、アップロード画像です
# settings.py ---
# 例) メディアファイル関連の設定例
MEDIA_URL = '/media/' # メディアファイル配信URL
MEDIA_ROOT = os.path.join(BASE_DIR, '..', 'media') # メディアファイルの保存先
メディアファイルの場合は、自分で設定する必要があります
静的ファイルは DEBUG = True
で自動で設定されます
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
# 省略
]
# 開発モードの時だけ、Djangoでメディアファイルを静的配信
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
HTTPサーバーのデフォルト設定だとリクエストボディ(ファイルサイズ)の上限が小さい
例えば Nginx は デフォルトで 1MB が上限です
なので上限に引っかかってエラーになったります
大きなファイルをアップロードする時は、Webサーバーの設定を変えておくと良いです
# 例) nginx で 10MBまで上限をあげる
client_max_body_size 10M;
http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size
本番環境で公開する
本番環境と一口にいっても色々な環境があります
VPSなのか?
AWS使えるのか?
S3は使うのか?
Herokuなのか?
サーバは何台なのか?
採用するクラウドサービス や アーキテクチャによって静的ファイルの取り扱いも大きく異なります
どういうパターンがあるのかみてみましょう
サーバー1台だけ用意して配信するパターン
自分でサーバの設定をしてDjangoアプリを動かす
例えば以下のチュートリアルはこのパターンを想定してます
How To Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu 16.04 | DigitalOcean
Setting up Django and your web server with uWSGI and nginx | uWSGI 2.0 documentation
Nginxの例
server {
server_name example.com;
location /media/ { # <- メディアファイル
root /path/to/myproject;
}
location /static/ { # <- 静的ファイル
alias /path/to/myproject/collcected_static;
}
location / { # <- それ以外はDjangoアプリ
include proxy_params;
proxy_pass http://unix:/run/uwsgi/myproject.sock;
}
# 〜 省略 〜
サイトの負荷が増大したら、DBサーバを別にしたり、サーバーを増やします
サーバーの増えると前段に、ロードバランサー(負荷分散装置)が置かれてたりします
サーバーを増やした場合は以下のようなことを考える必要があります
collecstatic
はどこで実行するのか?
片方のサーバーだけにアップロード されたメディアファイルをどうするか?
静的ファイル
そろぞれのサーバーで collectstatic
コマンドを叩く
もしくは collectstatic
で事前に集めたものを各サーバーに同期する
メディアファイル
どのサーバーにアップロードされるかわからない
アップロードされたら、各サーバーに同期する
どうやって同期するの?
lsyncd
とか rsync
などのツールを使って同期
特定のディレクトリの変更を監視して、ファイルが更新された同期処理を行う
サーバーが増えすぎて同期が追いつかなくなったら、静的ファイル専用のサーバーを用意する
各社クラウドサービスには、静的ファイルを扱えるストレージサービスを持ってます
Amazon S3
Google Cloud Storage
Mirosoft Azure Storage
これらのサービスを使うことで、先ほどの同期作業から解放されます
現代的には割とこちらの方が馴染みがありそうです
ここでは AWS で S3 を使う例をみてみましょう
django-storages というライブラリを使うとできます
S3 以外にも Azure Storage や Google Cloud Storage 等にも対応しています
ほぼデフォクトスタンダードなライブラリな気がします
Djangoの設定は以下の通りです
# settings.py --
AWS_ACCESS_KEY_ID = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
AWS_SECRET_ACCESS_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
AWS_STORAGE_BUCKET_NAME = 'xxxxxxx'
# メディアファイルをS3に保存
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
# collectstatic を実行すると静的ファイルをS3に保存
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
nginx 設定は S3 のバケットのURLに対してプロキシ設定します
location ~ ^/(static/|media/) {
resolver 10.0.0.2;
resolver_timeout 5s;
set $s3_bucket "xxxxxxx.s3.amazonaws.com";
proxy_pass http://$s3_bucket;
}
Nginx は 起動時に名前解決をして IPアドレスをキャッシュ
しかし、S3のURLは一定時間でIPアドレスが動的に変更されてしまいます
定期的に resolver
で名前解決を実行してIPアドレスを更新する必要があります
resolver 10.0.0.2;
resolver_timeout 5s;
S3はURLを吐き出すのでそれをそのまま利用しても良いです
例) https://xxxxx.s3.amazonaws.com/media/hoge4.png
Nginxを介さない分、その分サーバーの負荷は減りそうです
ただ、いざURLが変わるとなった時に取り回しがしにくいので、私はNginxでプロキシしてます
例) S3のURLがテキストデータでどこかに保存されてるとか
Heroku ような PasS の場合
そもそも HTTPサーバー が 置けないです
なので、Django で静的ファイルを配信する必要があります
Herokuの場合、ファイル書き込みができないのでアップロードもできない
以下のチュートリアルは Heroku を想定したものです
静的ファイル
WhiteNoise というライラリを使って配信します
開発用の静的ファイル配信とは違い、パフォーマンスを意識した静的ファイルが可能になります
Django専用ではなく、WSGIアプリケーション全般に利用可能
メディアファイル
S3 にアップロードするのが良いそうです
Djangoの開発用の静的ファイル配信とは違って
Herokkuのような環境で動かすこと念頭に開発されている
静的ファイルの圧縮/キャッシングにも対応している
Django単体で頑張るのでなく、CDNとの連携をしやすいようにしている
同じ PaaS でも 静的ファイルを配信できる設定ができる
runtime: python37
handlers:
- url: /static
static_dir: static/ # <- 静的配信してくれる
- url: /.*
script: auto
Contents Delivery Networkの略
静的ファイルのロード時間はユーザー体験の良し悪しに直結します
なるべく速くユーザーに静的ファイルを届けるための仕組みです
簡単に説明すると
世界中に配信サーバーを持ち、ユーザーに一番近い場所からコンテンツを配信可能
コンテンツをキャッシュするので、オリジン(配信元)の負荷が減る
代表的なサービスは
Amazon CloudFront
Akamai
Fastly
キャッッシュが利用されることでDjangoへの負担が減る
その他Tips
CSS や JS を結合・圧縮してくれるライブラリ
ファイルサイズを小さくして、リクエスト数が減らせる
{% load compress %}
{% compress css %}
<link rel="stylesheet" href="/static/css/one.css" type="text/css" charset="utf-8">
<style type="text/css">p { border:5px solid green;}</style>
<link rel="stylesheet" href="/static/css/two.css" type="text/css" charset="utf-8">
{% endcompress %}
↓ 一つのCSSファイルに圧縮される
<link rel="stylesheet" href="/static/CACHE/css/f7c661b7a124.css" type="text/css" charset="utf-8">
アップロードしたファイルとかは、その認証済みのユーザーしか見えないようにする
Djangoで受けて静的配信しなくて良い方法がある
アプリで閲覧権限をチェックしつつ、配信はHTTPサーバーでできるようにする設定
Nginx なら X-Accel-Redirect
Appache なら X-SendFile
X-SendFile、X-Accel-Redirectの使い方 <https://jyn.jp/x-sendfile-accel-redirect/>_
現場で使える Django の教科書《基礎編》
10.4 静的ファイル関連の設定
10.5 メディアファイル関連の設定
現場で使える Django の教科書《実践編》
第5章開発のヒント(ファイルアップロード)
Webを支える技術
Real World HTTP
Webページ や 書籍 の著者の皆さん 本当に ありがとうございます。m(_ _)m
下記のような話をいたしました。
静的ファイルとは
わたしの失敗談
Djangoの静的ファイルの扱い
本番公開のパターン
静的ファイル関連Tips
静的ファイル配信というものについて何か参考になれば幸いです。
ご静聴ありがとうございました