CentOS8＋Apache2.4＋pyenv＋mod_wsgi＋Djangoプロジェクトのデプロイ

なかなかに苦戦したのでメモ。

# 前提

 - VMwareを利用して仮想のCentOS8環境を作り、そこにWebサーバーを構築する。
 - Djangoプロジェクトは作成済み。共有フォルダかFTPかなんかを使ってプロジェクトフォルダごとCentOS上（今回は`/var/www/projectname/`）へ。

# Apacheインストール

```shell
$ dnf list | grep httpd
```

httpd本体の他に

 - httpd-tools
 - httpd-devel
 - httpd-manual
 
などがあることがわかる。今後必要になるので本体とともにこれらをインストールする。

```shell
$ dnf -y install httpd httpd-tools httpd-devel httpd-manual
$ dnf list --installed | grep httpd  #インストールされているか確認

$ systemctl start httpd
$ systemctl status httpd  #Apacheを起動できているか確認
$ systemctl enable httpd  #自動起動を有効に
```

## ファイアウォールの設定をする

```shell
$ firewall-cmd --add-service=http --zone=public --permanent
$ firewall-cmd --reload
$ firewall-cmd --list-all  #servicesにhttpが追加されているか確認
```

## 動作確認

まずは http://localhost/ へアクセス。Apacheのテストページが表示されればOK。
次にHTMLファイルを置いて表示されるかの確認。

```shell
$ touch /var/www/html/index.html
$ vi /var/www/html/index.html  #適当なHTMLを書く
```
http://localhost/ へアクセスしてHTMLが表示されればOK。

# Apacheの設定ファイルを編集する

`/etc/hosts`を利用してドメインを指定してみる。

```shell
$ vi /etc/hosts  #127.0.0.1の行に example.com を追加する
$ mkdir /var/www/example.com
$ touch /var/www/example.com/index.html
$ vi /var/www/example.com/index.html  #適当なHTMLを書く
```

Apacheのconfを編集する。後々SSL化したり複数ドメインを使う予定なのでVirtualHostとして設定する。

```shell
$ touch /etc/httpd/conf.d/virtual.conf
$ vi /etc/httpd/conf.d/virtual.conf
$ service httpd configtest  #設定ファイルの文法チェックができる
```
```conf:/etc/httpd/conf.d/virtual.conf
<VirtualHost *:80>
  DocumentRoot /var/www/example.com
  ServerName example.com
  <Directory "/var/www/example.com">
    Require all granted
  </Directory>
</VirtualHost>
```

`configtest`時に

```
Could not reliably determine the server's fully qualified domain name, using localhost.localdomain. Set the 'ServerName' directive globally to suppress this message
```

が出たら、言われた通りに`httpd.conf`にServerNameを指定しておく。

```conf:/etc/httpd/conf/httpd.conf（98～99行目あたり）
ServerName example.com:80
```

最後にApacheを再起動して動作確認

```shell
$ systemctl restart httpd
```

これで http://localhost/ や http://example.com にアクセスすると`/var/www/example.com/index.html`が表示されるようになる。

# pyenv～Pythonインストール

## pyenvインストール

```shell
$ dnf -y install git  #git cloneをするためにgitをインストールする
$ git clone https://github.com/pyenv/pyenv.git /var/www/pyenv
$ vi ~/.bash_profile  #以下を追記
```

```shell:~/.bash_profile（追記）
export PYENV_ROOT="/var/www/pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
```

pyenvのインストール先を初めは`/root/.pyenv`にしていたが、ルート以下だとルート権限でしかアクセスできなくなる（Apacheがアクセスできなくなる）ようなので、インストール先はApacheが権限を持つ`/var/www/`以下にし直した。

```shell
source ~/.bash_profile  #bashを適用
pyenv --version  #動作確認
```

## Python3.x.xインストール

ここからはエラー出しつつ軌道修正しつつ進めるので、もしこの手順を実践しようとするのであれば最後まで読んでから試すことをおすすめします。

まずは普通にPythonをインストールしてみる

```shell
$ pyenv install 3.x.x
```

するとこんな感じのエラーになりました。

```
zipimport.ZipImportError: can't decompress data; zlib not available
```

ということでzlibをインストールします。

```shell
$ dnf -y install zlib zlib-devel
```

おそらく`zlib`はインストール済みになっているかと思います。今後もいくつかパッケージをインストールしなければならないのですが、本体と併せて`-devel`もインストールする必要があります。

気を取り直してPythonをインストールしますがまたエラーになります。

```:（抜粋）
WARNING: The Python bz2 extension was not compiled. Missing the bzip2 lib?
WARNING: The Python readline extension was not compiled. Missing the GNU readline lib?
ERROR: The Python ssl extension was not compiled. Missing the OpenSSL lib?
```

ERRORとWARNINGがありますが最終的にはどちらも解決する必要があるので、不足しているパッケージをインストールします。

```shell:
$ dnf -y install bzip2 bzip2-devel readline readline-devel openssl openssl-devel
```

再びPythonのインストール。一応成功はするが警告が出る。

```
WARNING: The Python sqlite3 extension was not compiled. Missing the SQLite3 lib?
```

こちらも後々困ったりするのでインストールしておきます。

```shell
$ dnf -y install sqlite sqlite-devel
```

これでやっとPythonがインストールできました。

```shell
pyenv versions  #3.x.xがインストールされていることを確認
pyenv global 3.x.x  #Pythonのバージョンを全体に適用
python --version  #3.x.xが動作することを確認

# pipも更新と確認をしておく
pip install --upgrade pip
pip --version
```

# Djangoインストール

```shell
pip install django
```

忘れがち。

# mod_wsgiの導入

mod_wsgiは[PyPI](https://pypi.org/project/mod-wsgi/)のDownload filesからwgetで入手。mod_wsgiの入手方法は調べたところ色々とあるみたいだが、とりあえず今回こちらの方法で成功したので記載しておく。

```shell
$ cd /var/www/
$ wget https://files.pythonhosted.org/packages/...略.../mod_wsgi-x.x.x.tar.gz
$ tar -zxvf mod_wsgi-x.x.x.tar.gz 
$ cd mod_wsgi-x.x.x/
$ ./configure --with-python=/var/www/pyenv/versions/3.x.x/bin/python
```

ここで`make`コマンドを打とうとすると以下のエラーが出る。

```
エラー: /usr/lib/rpm/redhat/redhat-hardened-cc1: そのようなファイルやディレクトリはありません
```

なのでさらに`redhat-rpm-config`をインストールする。

```shell
$ dnf -y install redhat-rpm-config
```

再度`make` `make install`を実行すると以下のエラーが出る

```
'xxx' can not be used when making a shared object; recompile with -fPIC
```

このエラーはPythonのインストールの際に`CFLAGS="-fPIC"`をつけることで解消する。ということでまたPythonのインストールし直し。

```shell
$ CFLAGS="-fPIC" pyenv install 3.x.x
$ make
$ make install
```

これで`/etc/httpd/modules/mod_wsgi.so`が生成される。
やっとWSGIのインストールが完了したので、プロジェクトファイルを`/var/www/projectname`として置いて再度Apacheの設定を編集し再起動する。

```conf:/etc/httpd/conf.d/virtual.conf
LoadModule wsgi_module /etc/httpd/modules/mod_wsgi.so
WSGIPythonHome /var/www/pyenv/versions/3.x.x
WSGIPythonPath /var/www/pyenv/versions/3.x.x/lib/python3.x/site-packages
WSGIPythonPath /var/www/projectname

<VirtualHost *:80>
  DocumentRoot /var/www/projectname
  ServerName example.com

  WSGIScriptAlias / /var/www/projectname/projectname/wsgi.py
  <Directory "/var/www/projectname">
    Require all granted
  </Directory>

  # djangoプロジェクト側でcollect staticしておく
  Alias /static/ /var/www/projectname/static/
  <Directory "/var/www/projectname/static">
    Require all granted
  </Directory>
</VirtualHost>
```

`WSGIPythonHome`はPythonスクリプトで`sys.prefix`が指すもの。
`WSGIPythonPath`のひとつは`sys.path`のうち最後が`site-packages`で終わるもの。もう一つはDjangoプロジェクトのホームディレクトリで、両方記述する必要があるらしい。

ここからはエラーを以下で確認しながら進める。

```
tail /etc/httpd/logs/error_log
```

さっそく以下のようなエラーが出るので対応する。

```
failed to map segment from shared object
Unable to configure formatter 'django.server'
```

これらはSELinuxを無効にし、`/var/www/`以下のファイルの所有者とグループをapacheにすることで解消できる。

```
setenforce 0
chown -R apache:apache /var/www/
```

Apacheを再起動したら http://localhost でDjangoプロジェクトが表示されるはず。
http://example.com にアクセスするとDjango側で`DisallowedHost`エラーが出るが、エラーメッセージの通り`settings.py`の`ALLOWED_HOSTS`に`example.com`を追記すれば表示される。