本番環境でDjangoサーバーの静的ファイルをAWS S3に配置するまで

Djangoサーバーを開発環境から本番環境に移行した時の話。

本番用のサーバーインスタンスをEBSで起ち上げ、CircleCIの構成をちょこちょこっといじってハイ終わり！と思っているとCSSが全く読み込まれない自体に。

理由を探してみるとDjangoのドキュメントで以下の内容を発見

```
Serving the files

In addition to these configuration steps, you’ll also need to actually serve the static files.
During development, if you use django.contrib.staticfiles, this will be done automatically by runserver when DEBUG is set to True (see django.contrib.staticfiles.views.serve()).
This method is grossly inefficient and probably insecure, so it is unsuitable for production.
See Deploying static files for proper strategies to serve static files in production environments.
```
(引用: https://docs.djangoproject.com/en/3.0/howto/static-files/)

つまり、本番用にサーバー設定のDebugをオフにすると今まで自動で行われていた静的ファイルの配置が行われなくなるとのこと。
EC2インスタンスから直接ファイルを送信するのは効率悪いし、せっかくなのでここでデプロイ毎に静的ファイルをS3に配置してCloudFrontから配信することに。

## 静的ファイルとsettings.pyの変更
上のドキュメントにもあるように、まずそもそもテンプレートファイルの中でStatic変数を埋め込んでいなかったので変更

変更前

``` templates/login.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <link rel="icon" href="/static/favicon.ico">
    <title>Login</title>
    <link href="/static/css/style.css" rel="stylesheet" type="text/css"/>
</head>
```
開発用setting.pyは以下の通り

```settings/develop.py
STATIC_URL = '/static/'
STATICFILES_DIR = "[os.path.join(BASE_DIR, 'static')]"
```
なので、開発サーバーではDjangoが自動でapp下のstaticフォルダー内のファイルにアクセスしてくれていたっぽい。

続いて、本番用の変更。Static変数を入れ、静的ファイルのURLを動的に変更できるように。

```templates/login.html
{% load static %}<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <link rel="icon" href="{% static 'favicon.ico' %}">
    <title>Login</title>
    <link href="{% static 'css/style.css' %}" rel="stylesheet" type="text/css"/>
</head>
```
＊{% load static %}を冒頭に入れないと、staticが定義されていないと怒られるので注意。

```settings/production.py
# For static files
AWS_LOCATION = 'static'
AWS_ACCESS_KEY_ID = ******************
AWS_SECRET_ACCESS_KEY = *****************
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
AWS_STORAGE_BUCKET_NAME = 'production-bucket'
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
AWS_AUTO_CREATE_BUCKET = True
AWS_S3_REGION_NAME = 'ap-northeast-1'
AWS_DEFAULT_ACL = 'public-read'
STATIC_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{AWS_LOCATION}/'
STATICFILES_DIR = "[os.path.join(BASE_DIR, 'static')]"
```

[django-storages](https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html)のライブラリをインストールして静的ファイルの格納場所にS3Boto3Storageを指定。

## デプロイ時に静的ファイルの配置
デプロイ実行後(migrateやloaddataをやっているところ)に以下のコマンドを実行するよう追加
`python manage.py collectstatic --no-input`

これでAWS_STORAGE_BUCKET_NAMEに指定したバケットのStaticフォルダー内に静的ファイルが配置される。
（EC2インスタンスへのIAM設定などが別途必要ですがここでは割愛）

本番サーバーへブラウザからアクセスすると先のlogin.htmlが以下のようになることを確認

```
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <link rel="icon" href="https://***-production-bucket.s3.amazonaws.com/static/css/style.css">
    <title>Login</title>
    <link href="https://***-production-bucket.s3.amazonaws.com/static/css/style.css" rel="stylesheet" type="text/css"/>
</head>
```
テンプレートファイルはEC2インスタンスから出力、それ以外はS3から読み込まれるように変更されていました。

## S3-> CloudFrontへ
このままでもいいのですが、S3から毎回ダウンロードされるのもお財布に優しくないので、出力をCloudFrontへ以降。（CloudFrontの設定方法は割愛）
CloudFrontからバケットとディレクトリを指定してやり、最後に先程のSTATIC_URLをCloudFrontのドメインへ変更。

```settings/production.py
# For static files
AWS_LOCATION = 'static'
AWS_ACCESS_KEY_ID = ******************
AWS_SECRET_ACCESS_KEY = *****************
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
AWS_STORAGE_BUCKET_NAME = 'production-bucket'
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
AWS_AUTO_CREATE_BUCKET = True
AWS_S3_REGION_NAME = 'ap-northeast-1'
AWS_DEFAULT_ACL = 'public-read'
STATIC_URL = 'https://********.cloudfront.net/static/'
STATICFILES_DIR = "[os.path.join(BASE_DIR, 'static')]"
```

これでデプロイされた際にCloudFrontからファイルが配信されるように。

## 注意点
CloudFrontなどのCDNは配信コンテンツをキャッシュするので、静的ファイルを変更してデプロイしても即座に反映されません（体感30分ぐらいで更新される？）。CSSなんかはそこまで問題にならなかったのですが、フロントで使っているVueJSでハマったので、次回はそちらについて記事を書こうと思います。