GAミント至上主義

コストにうるさいWEBアプリ開発者。最近はPython, Vue.js, Kubernetesがメイン@株式会社ビズオーシャン。https://github.com/uyamazak/

GKE上の Django + ManifestStaticFilesStorage + Google Cloud Storage でValueError無限ループが起きてハマった話

Templyでは、Djangoのアプリが入ったDockerコンテナをGoogle Kubernetes Engine(GKE)で動かし、ファイルはGoogle Cloud Storage(GCS)で管理している。
temply.bizocean.jp

本番公開してからデプロイ時にcss、jsなどの静的ファイルが更新されず困ったので、よくあるファイル名にハッシュを付ける形にして解決しようとしたところ、下記のようなエラーが連発して、止まってしまうことがあった。

ValueError at /favicon.ico
The file 'send/img/favicon.png' could not be found with <mysite.storage.MyManifestStaticFilesStorage object at 0x7f5fbae7d320>.

問題と解決方法を3行まとめ。

  • staticファイルはGCS上に置くので.dockerignoreで/static/以下をDockerイメージに含めないようにしていた
  • staticfiles.jsonは、STATIC_URL上ではなく、ローカルファイルシステム上STATIC_ROOT直下から読み込もうとする
  • GCSを使う場合でもSTATIC_ROOT/staticfiles.jsonだけはイメージに含めるなどdjango側がローカルで読める必要がある

以下詳細。

ファイル名にハッシュをつけるのはDjangoの標準で入っているManifestStaticFilesStorageを使った。

The staticfiles app | Django documentation | Django

これはsettings.pyに下記のような設定を追加するだけでおk。

STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'

あとはテンプレートでちゃんとstaticを使ってパスを書いていれば、自動的に変換してくれるので導入は簡単。

{% static 'app/css/style.css' %}

仕組みとしては、collectstaticコマンド実行時に元のファイル名と、ハッシュ付きのファイル名のペアが書かれたstaticfiles.jsonを書き出し、それを使ってパスを変換してくれる。

で、開発環境では問題なく動いているのに、本番環境ではエラーが連発しまくってValueErrorのメールがきまくる現象になった。

faviconでエラーが出る原因は、ブラウザが裏で勝手にアクセスし、まずfavicon.icoがないのでエラーを出そうとするんだけど、そのエラーページでもfavicon.icoを呼び出すのでさらにエラーの無限ループだった。

静的ファイルはGCS上に別途アップし配信しているので、staticfiles.jsonもそっちから取ってきてるだろうと思っていたのだが違った。

開発環境ではアプリのディレクトリをまるごとマウントし、開発しやすくしており、/static/以下も入っていたので問題にならなかった。

下記のようにSTATIC_ROOTに指定したパス+staticfiles.jsonを.dockerignoreに追加すればおk。
.dockerignore

!app/django/static/staticfiles.json

いくらDockerといえども、こういった外部ストレージ周りは本番と開発で差異が出てしまうので注意が必要そう。