Herokuにデプロイ(Django)

# Herokuとは
- Herokuは、PaaS(Platform as a Service)
- さまざまな、アプリをデプロイすることで、アプリを世の中に公開することができる
- Herokuのプラットフォームで使用されるコンテナは、軽量Linuxコンテナ「dyno（ダイノ）」です。

※PaaSとは、インターネット上でハードウェアやOSなどを提供するサービスのこと

## Dynoとは
- Dynoとは、Herokuアプリケーションが実際に実行されるプラットフォームのこと。
- その実体は、Amazon EC2の巨大インスタンス上で動作する軽量のLinuxコンテナ。
- ベースとなるOSはUbuntuであり、ユーザーの作成したアプリケーションはすべてDyno上で実行される。

# Herokuインストール
## mac
```
% brew tap heroku/brew && brew install heroku
% heroku --version
% git --version
```
インストール中にエラーが表示された場合、以下を入力
```:インストール中にエラーが表示された場合
$ softwareupdate --all --install --force
$ sudo rm -rf /Library/Developer/CommandLineTools
$ sudo xcode-select --install
```

## Windouws(WSL2 for Ubuntu)
```
$ curl https://cli-assets.heroku.com/install-ubuntu.sh | sh
$ heroku --version
$ git --version 

gitは必要に応じてアップデートすることを推奨
```
- 必要であればGit用のメールアドレスと、ユーザ名を登録しておく（ステージングする際に聞かれることを防ぐため）
```
% git config --global user.email "you@example.com"
% git config --global user.name "Your Name"
```

# パッケージのインストール（仮想環境内に）
- Dynoにインストールする以下のパッケージをインストールする。
    - gunicorn：APサーバー(gunicorn)でアプリを起動するためのパッケージ
    - whitenoise：staticを扱うためのパッケージ
    - django-heroku：Herokuの環境変数に、Djangoの環境変数をセットするためのパッケージ
    - python-dotenv：.envファイルを読み込むためのパッケージ
    - dj-database-url（今回は使用しない）

```
$ pip install django-heroku
$ pip install gunicorn
$ pip install whitenoise
```
```:仮想環境から抜けて以下を実行
$ pip install python-dotenv
```
仮想環境内だとdotenvをimportできない

# デプロイに必要なファイルの準備
- Dynoに対して、実行する以下の3つのファイルを作成する。
    - requirements.txt：仮想環境内の全てのパッケージをDynoにインストールするためのファイル。
    - [Procfile](https://kewton.blog/archives/604)(Heroku専用)：Dynoで扱うアプリをどのサーバから配信するのかを設定するファイル。今回は、gunicorn(APサーバ)からアプリを配信。
    - runtime.txt：(Heroku専用)Dyno上で使用するPythonのバージョンを知らせるためのファイル。
## requirements.txt（パッケージ一覧）
- **manage.pyと同じ階層で以下のコマンドを実行。**
- プロジェクトdir直下に、requirements.txtファイルが作成される。
- 仮想環境内にインストールされているパッケージとそのバージョンがrequirements.txtファイルに記述される。
```
$ pip freeze > requirements.txt
```

## Procfile（APサーバーの設定）
- **manage.pyと同じ階層で以下のコマンドを実行。**
- プロジェクトdir直下に、Procfileファイルが作成される。
```
$ echo "web: gunicorn プロジェクト設定dir名.wsgi --log-file -" > Procfile
```
- 本番環境(Heroku)で使用するAPサーバの設定を行う。つまり、Djangoのアプリを配信するサーバの設定を行う。今回は、APサーバとしてgunicornを使用する。
- Procfileファイルには、Herokuで使用している軽量Linuxコンテナdynoで実行するコマンドを記述する。
- web:に続けて、gunicornに接続するためのgunicornコマンドを記述する。
- gunicornに対して、プロジェクト設定dir/wsgi.pyを実行する
- ""内をProcfileファイルに書き込む

※ローカルで、テストするには、$python3 manage.py runserverではなく`$ gunicorn プロジェクト設定dir名.wsgi`を実行する。
※開発サーバに複数のユーザがアクセスできないので、本番環境では使用しないこと。
※一応、開発サーバを接続先に指定する場合は、`$ echo "web: python manage.py runserver" > Procfile`を指定する。

## runtime.txtファイルを作成
```
$ python --version
Python 3.10.7

$ echo python-3.10.7 > runtime.txt
```
- **manage.pyと同じ階層で以下のコマンドを実行。**
- プロジェクトdir直下に、runtime.txtファイルが作成される。
- [HerokuがサポートしているPythonのバージョン](https://devcenter.heroku.com/articles/python-support#supported-runtimes)である必要がある。

# settings.py
- SECRET_KEYの設定：.env
- git管理しないファイルの設定：.gitignore
- （開発環境と本番環境の設定の分離）
    - 開発環境：settings_local.py
    - 本番環境：settings.py

## SECRET_KEYの設定(.env)
- SECRET_KEYとは：ページ下の補足を確認。
- manage.pyと同じ階層で以下のコマンドを入力する。
    - .envファイルが作成される。
    - SECREST_KEYの値をコピペする。（''は含めない）
```
$ echo "SECREST_KEY=ここにコピペ" > .env
```
- セキュリティ上SECRET_KEYを外部に公開してはいけない。
- そのため、.envファイルを作成し、.envをgitの管理から外す。（以下で説明）
- .envには、環境変数（SECRET_KEY）を記述する。
- .envに、settings.py内のSECRET_KEYをコピペする。
- **後半のHerokuの環境変数にセットした後、settings.py内のSECRET_KEYは削除する。**

※SECREST_KEY以外にもDATABASESなど、外部に公開したくないものは、全て.envに記述する。記述したもを使用する際は、取得することを忘れずに。（以下で説明）

## git管理しないファイルの設定
- プロジェクトの直下（manage.pyと同じ階層）に .gitignoreファイルを作成。
- 以下を入力する。
```:.gitignore
.env
db.sqlite3
```
- .gitignoreには、gitで管理しないファイルを記述する。
- このファイルに記述したものは、gitで管理されないため、GitHubに公開されないことになる。
- つまり、Herokuには反映されない。

## settings_local.py（開発環境用）
- settigs.pyと同じ階層に、開発環境用の設定ファイルsettings_local.pyを作成。
- 以下を入力する。

```python:settings_local.py
'''
settings.py内の全ての環境変数を読み込み、DEBUG, ALLOWED_HOSTS, DATABASESをオーバーライド
※importしたモジュール（Path, load_dotenvなど）も含まれる
'''
from .settings import *

DEBUG = True
ALLOWED_HOSTS = ['*']

#開発環境用のDB
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
```
- settings.py内の全ての環境変数を読み込み、DEBUG, ALLOWED_HOSTS, DATABASESを開発環境用に上書きする。
- SECRET_KEYがないと、アプリを表示することができないため、settings.pyからimportする。

※.envは、git管理から外したので、SECRET_KEYが外部に公開されることはない。


## settings.py（本番環境用）
- 以下2つの設定を行う（どちらともsettings.pyの末尾に追記）
    - .env内のSECRET_KEYを取得
    - DEBUG = Falseの設定
    - ALLOWED_HOSTSの設定
    - whitenoiseの設定
    - staticの設定
        - collectstatic コマンド

### .env内のSECRET_KEYを取得
- 以下をsettings.pyに追加する。
- SECRET_KEYがないと、アプリを表示することができないため`.env`から取得する。
- 念の為、settings.py内のSECRET_KEYが削除されていることを確認する。
```python:settings.py
import os
import dotenv

dotenv.load_dotenv() # .env ファイルを読み込む
SECRET_KEY = os.getenv('SECREST_KEY') # .env内の環境変数を取得
```
- .envをgit管理から外すので、リポジトリに.envが存在しない。
- そのため、os.getenv('SECREST_KEY')によって値が取得できない。
- しかし、エラーではなくNoneを返す。
- そのため本番環境では、Heroku上の環境変数（SECREST_KEY）設定し、用いる。
    - Heroku上の環境変数の設定方法は以下で説明。

※.envは、git管理から外したので、SECRET_KEYが外部に公開されることはない。
※今回のように、.envで環境変数を管理したアプリを`git clone`する際は、
上記の **「SECRET_KEYの設定(.env)」** で説明した手順で.envを作成をmanage.pyと同じ階層に作成し、環境変数（SECRET_KEY）を記述する。
※SECREST_KEYを公開してしまった、または無くしてしまった際は、再生成する必要がある（以下の補足で説明）。

### DEBUG = Falseの設定
- settings.pyの末尾に以下を追加する。
- 本番環境では、DEBUG = Falseに変更する。
- DEBUG = Falseであれば、Herokuの環境変数にsettings.py内の全ての環境変数をセットする。
```python:settings.py
DEBUG = False # Falseに修正

if not DEBUG:
    import django_heroku
    django_heroku.settings(locals())
```
- DEBUG = Falseにする意味
    - Falseにすることで、処理のエラー時に開発環境では表示された黄色い画面が表示されなくなる
    - この画面が表示されると、内部の情報を知ることができるためセキュリティ的に良くない
- django_heroku.settings(locals())：Herokuの環境変数に、locals()の戻り値をセットする。
    - [Herokuのsettings](https://devcenter.heroku.com/ja/articles/django-app-configuration#settings-py-changes)
    - locals()は、pythonのビルトイン関数で、ローカル変数を辞書型にして返す。
        - ここでは、settings.py内の全ての環境変数を辞書型にして返し、それをHerokuにセットしている。



## ALLOWED_HOSTS
- 既存のものを以下に修正する。
    - デプロイアプリ名.herokuapp.comは、
- 本番環境では、['*']にしてはいけない。
```python:settings.py
ALLOWED_HOSTS = ['127.0.0.1', 'デプロイアプリ名.herokuapp.com']
```
- ALLOWED_HOSTSは、クライアントからのリクエストを受け付けるサーバのアドレスを登録しておくための環境変数。
- クライアントからのリクエストに含まれるサーバアドレスがALLOWED_HOSTSの値と一致しない場合は、400エラー。
- ネット上のサーバを用いてアプリを公開する際は、サーバに指定されているアドレスを登録する。

## whitenoise
- MIDDLEWAREの末尾にwhitenoiseを追加する。
```python:settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware', # 追加
]
```
- `python3 manage.py collectstatic`を実行後、STATIC_ROOTで指定したdirに、プロジェクト内の全てのstaticディレクトリが集められる（以下で説明）。
- アプリ起動時に、この静的ファイルを集めたdir(STATIC_ROOT)をwhitenoiseが読み込み配信する。

## static
- STATICFILES_DIRS, STATIC_ROOTを追加する。
- manage.pyと同じ階層にstaticディレクトリを作成。
    - staticの中に`.gitkeep`という空のファイルを作成しておく。

※.gitkeepを作成する理由については、ページしたの補足を確認。
```python:settings.py
STATIC_URL = 'static/'
# 以下2つの環境変数を追加
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
```
|環境変数|説明|
|-|-|
|STATIC_URL|静的ファイル配信用のURL。ほとんど'/static/'から変更することはない。|
|STATICFILES_DIRS|アプリごとではなく、別の場所で静的ファイルを管理するディレクトリを作成したい場合に設定する。上記では、プロジェクトdir直下にstaticという名の静的ファイルを管理するdirを作成。|
|STATIC_ROOT|python manage.py collectstaticコマンドによって、プトジェクト内の全ての静的ファイルが集められるdirを指定。コピーして集められる。デプロイ時に必須。|
|STATICFILES_STORAGE||

### collectstatic コマンド
- 静的ファイルを集める場所（settings.STATIC_ROOT）が設定されているかを確認する。
- 以下のコマンドを実行し、Djangoプロジェクト内の全、static内にある静的ファイルを1箇所にまとめる。
- staticfiles/.gitkeepを作成する。
```
$ python3 manage.py collectstatic
```
- whitenoiseまたは、静的ファイルサーバを用いてデプロイする際は、静的ファイルを一箇所に集める必要がある。
- それがcollectstaticコマンド。
- collectstaticする前に、集める場所をsettings.pyに設定する必要がある。(今回は、プロジェクト/staticfilesを指定。)
- 静的ファイルサーバ（Amazon S3など）を用いる場合は、まとめたディレクトリごと静的ファイルサーバに移動させる。

# Herokuにデプロイ
- Herokuにログイン
    - 公開するアプリを作成（空のアプリ）
    - 作成したアプリの環境変数を設定する（SECRET_KEY）
- コミットとデプロイ
- Dynoを起動
    - マイグレーション
    - 管理サイトスーパーユーザ作成
- アプリ起動

## Herokuにログイン
- Herokuにログインする。
- 配信するアプリを立ち上げる。
- Herokuで作成したアプリの環境変数に.envに記述したSECRET_KEYの値をコピペして、セットする。
    - **settings.py内のSECRET_KEYは削除する。**
- **ALLOWED_HOSTSにアプリ名を修正する。**
```
$ heroku login
$ heroku create Herokuプロジェクト名
$ heroku config:set SECRET_KEY='**************************'
```
## コミットとデプロイ
- manage.pyと同じ階層に移動して以下を実行
```
$ git init
$ git add .
$ git commit -m "initial commit"
$ git push heroku master
```
- エラーによって、デプロイできない場合は以下を見直す。
    - [Herokuがサポートしているpythonのバージョン](https://devcenter.heroku.com/articles/python-support#supported-runtimes)なのか？
    - ローカルではアプリを表示することができるか？（以下の補足を参照）
- それでも解決しない場合は、Githubと連携してデプロイする方法で試す。
## Dynoを起動
- Dynoを起動する。
- マイグレーションを行いモデルをテーブルに反映させる。
- 管理サイトのスーパーユーザを作成する。
```
$ heroku ps:scale web=1 Dynoを起動する
$ heroku run python manage.py migrate
$ heroku run python manage.py createsuperuser
```
## アプリを起動
```
$ heroku open
```

# 補足

## staticディレクトリ内に.gitkeepを作成する理由
- 静的ファイルを配信するために、staticディレクトリを作成する必要がある。
- 空のディレクトリはgitで管理することができないため、空のディレクトリ（static）内に.gitkeepを作成することで、gitでの管理を可能にする。

## 静的ファイルの管理
静的ファイルを配信する方法は、主に以下の２通り。
1. Djangoアプリと同じサーバから静的ファイルを配信
    - dj_static.Cling(wsgi.py)
    - whitenoise
2. 静的ファイルサーバから静的ファイルを配信
    - Amazon S3

本番環境において、1.の方法だとライブラリを使ってAPサーバ(gunicornなど)から静的ファイルを配信する。そのため、静的ファイル数に応じて全体スピードは遅くなると推測。だが、静的ファイル配信用の別のサーバを用意する手間が省ける。大規模なアプリ出なければ、静的ファイルサーバは不要で、Herokuを用いて本番運用することができる。

※本記事では、whitenoiseを使用


## staticディレクトリ内で静的ファイルを管理する意味

Djangoが動いているサーバーから静的ファイルを配信すると、負担が大きくて全体の処理スピードが落ちてしまう。そのため静的ファイルをstaticディレクトリに集めて、別のサーバに配置する。

## 静的ファイルとは
Djangoの場合、htmlファイルは静的ファイルではない。なぜならhtmlファイルの中にDjangoで使うコードが入っているため。{{object.title}}など。つまり、Djangoでいう静的ファイルとはDjangoを介さないで
そのまま配信できる（別のサーバーから送ってもいい）ファイルのことです。
htmlはDjangoで使うのでstaticではなくtemplatesディレクトリに入ってます。 

## デプロイ前にローカルでテスト
デプロイ先と可能な限り同じ条件で動作するかを、あらかじめローカル環境で確認する
python manage.py runserverのケースだと開発サーバを起動するため、wsgi.pyを使用しない。つまり、このファイルが正しく機能しているかの確認ができない。そのため、実際にheroku等で使う以下のコマンドを事前に実行しておくとトラブルを未然に防ぐことが出来ます。
```
$ gunicorn プロジェクト設定dir名.wsgi
```
開発サーバではなく、本番と同じgunicorn（APサーバ）を起動するコマンド

## SECRET_KEY(環境変数)とは
- Djangoプロジェクトを利用するユーザのパスワードに加えられる文字列が指定されている
- プロジェクトを作成した際に、自動で作成される
- 文字列は、プロジェクトごとに異なる
- 外部に公開してはいけない
## get_random_secret_key()とSECRET_KEYの違い
- [SECRET_KEY](https://docs.djangoproject.com/ja/4.1/ref/settings/#std:setting-SECRET_KEY)
    - プロジェクト作成時に、settings.pyに設定されているデフォルトの環境変数
    - 機密にするように警告されている
    - git管理など、外部に公開しない
- get_random_secret_key()
    - SECRET_KEYを外部に公開してしまった際に、SECRET_KEYを再作成するための関数


ECRET_KEY=django.core.management.utils.get_random_secret_key()とした場合、アプリが起動/再起動するたびに新しい秘密鍵を取得することになり、既存のすべてのセッション、Cookie などが無効になります。それがあなたが使用したり気にかけたりするものでなければ、それはうまくいくかもしれませんが、それでもやるべきではないように感じます.

## get_random_secret_key()でSECRET_KEYの再生成
- 以下に当てはまる場合に、この関数を実行しSECRET_KEYを再度作成する
    - SECRET_KEYの値を外部に公開してしまった
    - SECRET_KEYの値を間違えて削除、無くしてしまった
- 作成手順
    - Djangoのshellを起動して
    - 関数をimportし、実行する


※SECRET_KEYが設定されていないと、アプリを表示することができない（ローカルでも本番でも）
```
$ python3 manage.py shell
```
```python
>>> from django.core.management.utils import get_random_secret_key
>>> get_random_secret_key()
'xxx-xxxx'
```

## HerokuのDB
- Herokuでは、PostgreSQLが用意されている。
- また、常にDBサーバが起動し、アプリに接続されている状態。
    - DBの起動を気にせず、いつでもアプリを使うことができる。
- デプロイ後はに、DBの中身を確認することもできる。