Django2デプロイ(centos7 apache2.4 mod_wsgi)

# 概要

某Djangoアプリをサーバにデプロイしたときの手順・設定です。

デプロイが完了してから反省したこと。

- Djangoの公式ドキュメントに必要な情報が網羅されているのに回り道した

「Django デプロイ Apache」みたいな感じでググって見つかった記事を参考にしたのですがかえって効率悪かったです。
デーモン実行でなかったり、古かったり、不正確（というか嘘）だったりで約２時間はまりました。涙

## 公式ドキュメントのデプロイ手順
- [Django を Apache と mod_wsgi とともに使うには？](https://docs.djangoproject.com/ja/2.1/howto/deployment/wsgi/modwsgi/)
- [デプロイチェックリスト](https://docs.djangoproject.com/ja/2.1/howto/deployment/checklist/)

# 環境

- 某VPS
- CentOS 7.5.1804
- apache2.4.6
- Django 2.1.7
- Python3.6

また、以下の特徴・留意点があります。

- 当該サーバでは他のサービスも動いているので、**Apacheバーチャルホスト**上で実行
- **デーモンプロセス**として動作させる。

# 手順

## Apacheのインストール

様々なサイトで紹介されているので省略

## mod_wsgiのインストール

以下を参考にさせていただきました。（本記事のwsgiの設定はデーモン実行でないタイプです）
[centos7へapache2.4とmod_wsgiインストールし、djangoデプロイまで](https://qiita.com/dekosuke-menti/items/e416f198980c0fd6e75b)

重要な点として、当該モジュールは**pip**でも**yum**でもインストールできるのですが、yumでインストールすると、centos標準のPython2でしか動作しなくなるそうです。本プロジェクトはPython3系（Djago2系以降はPython2は非サポート）なので**pip**でインストールします。

### wsgiインストール

```
pip install mod_wsgi
```

### Apache用共有オブジェクトファイル（拡張子so)を探す

locateコマンドで探しました。

```bash
$ locate *mod_wsgi*.so
<パッケージインストール先ディレクトリ>/site-packages/mod_wsgi/server/mod_wsgi-py36.cpython-36m-x86_64-linux-gnu.so
```

### 共有オブジェクトファイルをApacheのmodulesディレクトリにコピー

```
cp <soファイルのパス> /etc/httpd/modules/<soファイル名>
```

### soファイル名を控える

のちほどApache設定ファイルのLoadModuleディレクティブで指定するので控えておきます。
当方の場合は以下でした。

```
mod_wsgi-py36.cpython-36m-x86_64-linux-gnu.so
```

## 配置

- プロジェクトソースコードを任意の場所に配置します。
```
```
- 仮想環境(venv)を作成・アクティベート・拡張インストール

```
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```
- アプリディレクト全体の所有設定（所有者はApache、グループにユーザ）  
後述するバーチャルホスト設定やWSGI設定で指定します。
所有者やパーミションを適宜調整します。


## logsディレクトリ作成（option）

ログをプロジェクトルート直下のlogsディレクトリに出力する設定にしているので、logsディレクトリを作成します。**apache**ユーザが書き込めるように所有者・パーミッションを適切に設定します。

## 静的ファイルをSTATIC_ROOTに作成

```
$ python3 manage.py collectstatic
```

自身がSTATIC_ROOT下に実装したのは**css**と**js**だけでしたが、このコマンドを実行することでDjango本体と拡張ライブラリによるファイルが配置されます。(例えば,adminやdebug_toolbarやdjango_extensionsなど）

```bash
<project_root>/static/   ← 設定ファイルのSTATIC_ROOTで指定したパス
  ├── admin
  ├── css
  ├── js
  ├── debug_toolbar
  └── django_extensions
```

## Apache設定

Apacheの定義ファイルに以下の設定を行います。(Apache本体の設定ファイルhttpd.confで名前ベースの仮想ホスト～NameVirtualHost *:443～を有効化している前提です）

- mod_wsgiの読込み設定（LoadModule)
- 仮想ホスト設定（VirtualHost)

仮想ホストは一般的な設定に加えて２つの重要な設定があります。



<dl>
  <dt>WSGI設定</dt>
  <dd>DjangoアプリをWSGIで動かす設定です。wsgi.py は django-admin startproject xxxx コマンドを実行した時に設定ディレクトリ直下に自動生成されたものを使っています。
</dd>
  <dt>静的ファイル配信設定</dt>
  <dd>この定義がなかったりパスが間違っていたり、collectstatic忘れて静的ファイルが配置されていない場合はCSSやJSが適用されないので変な画面が表示されます。</dd>
</dl>

- [WSGIDaemonProcess（mod_wsgi公式）](https://modwsgi.readthedocs.io/en/develop/configuration-directives/WSGIDaemonProcess.html)
- [デーモンモードでmod_wsgiを動作させる（Django公式）](https://docs.djangoproject.com/ja/2.1/howto/deployment/wsgi/modwsgi/#using-mod-wsgi-daemon-mode)
- [ファイルを配信する(Django公式)](https://docs.djangoproject.com/ja/2.1/howto/deployment/wsgi/modwsgi/#serving-files)

```{conf:/etc/httpd/conf.d/wsgi_django_apps.conf}
LoadModule wsgi_module modules/mod_wsgi-py36.cpython-36m-x86_64-linux-gnu.so

<VirtualHost *:443>
  ServerName example.com
  DocumentRoot /var/www/<project_root>

  …省略（SSL定義、カスタムログとか）…

  <directory /var/www/<project_root>>
    AllowOverride all
    Require all granted
    Options FollowSymlinks
  </directory>

  # WSGIをデーモンで実行
  WSGIScriptAlias / /var/www/<project_root>/<設定ディレクトリ>/wsgi.py
  WSGIDaemonProcess example.com user=apache group=apache processes=2 threads=25 display-name=%{GROUP} python-home=/usr python-path=/var/www/<project_root> lang=ja_JP.utf8
  WSGIProcessGroup example.com

  <directory /var/www/<project_root>/<設定ディレクトリ>>
   <Files wsgi.py>
    Require all granted
   </Files>
  </directory>

 # 静的ファイルの配信先を設定
 # settings.pyのSTATIC_ROOTで指定したパスと一致していること
 Alias /static/ /var/www/<project_root>/static/

 <Directory /var/www/<project_root>/static>
  Require all granted
 </Directory>
</VirtualHost>
```

- wsgi.pyはstartprojectで作成されるものを利用する（読み込む設定ファイルパスは分離後に合わせる）

## トラブルシューティング

いろんな箇所で躓きました。整理すると以下のようにすると良いのではないでしょうか。

### 準備

- アプリケーションに必要なDjangoや拡張ライブラリのインストール(`$ pip3 install -r requirements.txt`)
- 静的ファイルの配置 `$ python manage.py collectstatic`を実行
- 権限の確認
 - プロジェクトルート以下がWSGIDaemonProcess で指定したユーザ・グループからアクセスできるように設定されているか？
- ALLOWD_HOSTSの確認
 - デフォルトだと空になっていて全てのアクセスが拒否されます。127.0.0.1と動作確認するクライアントのグローバルIPを設定します。
- アプリケーションコードに問題がないか？を確認
 - `$ python manage.py runserver`で開発モードで実行。ローカルから`curl 127.0.0.1:8000`を実行して正常なレスポンスがあるか？

### 問題別対処法

#### Apache再起動でエラー（`systemctl restart httpd.service`)

設定ファイルが間違っている可能性が高いです。`systemctl status httpd.service`でエラー内容を見て調整します。
例えば、WSGIPythonPathやWSGIPythonHomeというディレクトティブはVirtulaHostでは設定できません。

#### VirtulaHostのデフォルトサイトにリダイレクトされてしまう

アプリケーションが動作していません。
Apacheのログ（カスタムログを設定していなければ `/var/log/httpd/error_log`)を確認します。以下のエラーに遭遇しました。

- wsgi.pyの場所が正しいか？
- 設定ファイルが読み込まれていない ー> パスが
- **WSGIDaemonProcess**の`python-path`がプロジェクトのルートディレクトリををさしていない。（あるサイトでは、pythonライブラリのインストール先(site-packages)を指定するという不正な記述がありました） この場合、`no module named プロジェクト名`というエラーが出力されてたように思います。

Apacheの**error_log**に何も出力されなければwsgi設定はひとまずOKです。

#### 実行エラーが発生する

wsgiがOK（=apacheのerror_logに何も出力されない）になった後のエラーはアプリケーションログを確認します。（要Logging設定）　ありがちなもの（というか当方が体験したもの）だと以下のようなものがあります。

- Djangoの必須設定SECRET_KEYが読み込めない。
 - ベスト・プラクティスとして当該設定などを構成管理対象外ファイル（例えば`.env`）に設定して、django-environで読み込む方法があります。この場合、そのファイルが存在するか確認します。
- ログ出力先が存在しない・権限不足
 - ログをFILE出力指定している場合、出力先ディレクトリが存在するか、書込権限があるかを確認します。
- `no named module <any>`が発生する
 - 必要なライブラリがインストールされていない可能性があります。requirements.txtの内容を確認して漏れていれば追加インストールします。
- 画面が表示されるがレイアウトがおかしい
 - `$ python manage.py collectstatic`を実行していなければ実行する。
 - Djangoの設定とApacheの設定の一致を確認します
 - **Django設定は以下の定義が整合性とれているか確認します**
   - BASE_DIRはこの定義をしている設定ファイルから見てプロジェクトルートを指すように

```conf
BASE_DIR = `os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))`
STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
STATIC_ROOT = os.path.join(BASE_DIR,'/var/www/<project_root>/static')
```