aws, django, web系

djangoで静的ファイルをS3に置く

djangoをEBのDocker環境で動かしている。この時静的ファイル(css, 画像等)をS3に置く設定。

とりあえず、Storing Django Static and Media Files on Amazon S3の通りにやる。

手順

  1. S3バケットを作成
  2. IAMユーザーを作成
  3. djangoからstatic filesをS3にアップロードする設定

S3バケットを作成

  • バケット名は任意。
  • この時、"Block all public access"のチェックを外しておく
    • ただしこれだけでパブリックアクセスが可能になるわけではなく、あとでポリシーを設定する
  • バージョニングはしない
    • しても良いが、今回の使用方法では、もしS3のデータが全て消えても再度デプロイすれば良いだけだから不要

ポリシー設定

作成後、PermissionsタブのBucket Policyのところに適切なポリシーを設定する。今回は全てのパブリックアクセス(読み取り)を許可するので以下のポリシー。
ただし、example.comの部分は自分で作ったバケット名に変える。
これは、公式ドキュメントの「ステップ 3: バケットポリシーを追加する」の例。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::example.com/*"
            ]
        }
    ]
}

IAMユーザーを作成

EBにDockerをデプロイしたときに、内部で静的ファイルを集めて(djangoのcollectstatic)まとめてS3にアップロードするが、この時認証が必要。S3バケットへのWrite権限を持ったIAMユーザーを作成し、そのaccess key IDとsecrete access keyを認証に使う。

まず、指定のS3バケットに書き込み権限だけを持つポリシーを作成する。
IAMコンソール
--> Policies
--> create policy
--> 自分でJSONを書かなくてもポリシーを作成できるので便利。S3の指定バケットに対してフルアクセス権限を持つポリシーを作成(実際には、書き込みと削除だけで良いかも)

ポリシーはこんな感じ。bucket-nameのところを実際のバケット名に変えれば良い。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::bucket-name",
                "arn:aws:s3:::bucket-name/*"
            ]
        }
    ]
}

次に、IAMユーザーを作ってそのポリシーを与える。
IAMコンソール
--> Users
--> Add User
--> 適当に名前を決めて、"Programmatic access"にチェックを入れる。こうしておけばIDとkeyが発行される。
--> グループを作っても良いが、今回はS3バケットにアップするだけのユーザーだから、先ほど作ったポリシーを直接アタッチする
--> 作成してAccess key IDとSecret Access keyを書き留めておく。

djangoからstatic filesをS3にアップロードする設定

アプリをデプロイするとき、python manage.py collectstaticで静的ファイルを一箇所に集めることができる。この時、settings.pyでSTATIC_ROOT='/var/www/static/'などと設定するとそこに集めてくれるので、Webサーバーでそのディレクトリを公開しておけば静的ファイルを提供できる。静的ファイルを内部から提供する場合はこんな感じ。

アプリのWebサーバーからではなくS3などのクラウドストレージでホスティングすることで、Webサーバーの負荷を抑えられるという大きなメリットがある。その場合、アプリデプロイ時に、collectstaticで集めたファイル群をS3にアップロードする必要がある。アップロードスクリプトを自分で書いても良いが、これをやってくれるパッケージが既にあるのでそれを利用する。今回使うライブラリはdjango-storagesboto3

django-storagesは静的ファイル等の置き場所としてクラウドストレージを利用するためのパッケージ。適切に設定しておけば、collectstaticをしたときに自動でクラウドストレージにアップロードしてくれる。
S3を利用する場合はバックエンドとしてboto3のみサポートされているのでこれが必須。あまり気にしなくて良いが内部でS3Boto3Storageというboto3から提供されているクラスが使われる。

設定

上記2つのパッケージをインストールしておく。

まず、settings.pyのINSTALLED_APPSに'storages'を追加する

INSTALLED_APPS = [
    ...,
    'storages,
]

これでdjango-storagesが使えるようになる。

次に、S3にアップロードするための設定をする。ここは冒頭のサイトのほぼコピペ。(少し違う)

USE_S3 = os.getenv('USE_S3') == 'TRUE'

if USE_S3:
    # aws settings
    AWS_ACCESS_KEY_ID = os.getenv('AWS_ACCESS_KEY_ID')
    AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY')
    AWS_STORAGE_BUCKET_NAME = os.getenv('AWS_STORAGE_BUCKET_NAME')
    AWS_DEFAULT_ACL = 'public-read'
    AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'
    AWS_S3_OBJECT_PARAMETERS = {'CacheControl': 'max-age=86400'}
    # s3 static settings
    AWS_LOCATION = 'static'
    STATIC_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{AWS_LOCATION}/'
    STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
else:
    STATIC_URL = '/static/'
    STATIC_ROOT = os.path.join(BASE_DIR, 'static')

設定後、起動すると500エラーでサイトが見れなくなった

結論としては、django-staitc-md5urlがエラーを出していたから、使うのをやめた。しかし使う方法はあるようだから、そのうち試す。

docker-compose upでは正常に起動するが、アクセスすると500エラーになる。DEBUG=FALSEにしているため、エラー内容が全くわからない。ブラウザでも見れないしコンソールのログにも出ない。S3にアップしたことで何かが起こっているようだが、DEBUG=FALSEにしておかないとcollectstaticをしてくれないからDEBUG=TRUEにできない。色々調べると、500エラーの時は、handler500というハンドラーを使って処理しているらしい。そしてこれはurls.py内で上書きすることができる。ここでDEBUG=TRUEの時と同じようにTracebackをブラウザに表示するように設定すればエラー内容が確認できる。以下のサイトが大変参考になった。
Django Server Error (500)攻略法【2019 アドカレ】

これで設定した結果、django-static-md5urlとdjango-storagesをそのままでは併用できないことがわかった。エラー内容としてはTypeErrorでファイルがNoneになっているというようなエラーがget_md5()みたいなメソッドから出ていた。おそらく、collectstaticでS3にアップするためファイルはローカルにないから、md5urlがファイルからmd5ハッシュを生成しようとするときにエラーになっているのだと思われる。
だからとりあえず、md5urlを使うのをやめた。これは、キャッシュ対策として便利だから入れていたのだが、今回作っているサイトは頻繁に更新する予定はないからしばらくは問題は起きないはず。

もう少し調べると、少し古いが以下のサイトを見つけた。
Djangoでのキャッシュバスティング
これによると、django-storagesのManifestFilesMixinというのを利用してS3BotoStorageを拡張すれば、md5urlが使えるとのこと。しかし今はS3Boto3Storageなので、この新しいクラスでも同じことができるのかは試す必要あり。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です