django+nginx+uwsgiの環境構築

本稿はAWS EC2環境にて以下構成でdjango、nginx、uwsgiによる開発環境を構築する際の私的メモです。


## 構成

| 名称         | バージョン | 用途                                 | その他          |
| ------------ | ---------- | ------------------------------------ | --------------- |
| Amazon Linux | 2          | OS                                   |                 |
| nginx        | 1.20.0     | Webサーバ                            |                 |
| uwsgi        | 2.0.20     | APサーバ                             |                 |
| pyenv        | 2.3.3      | Pythonの実行環境管理ツール           |                 |
| python       | 3.8.0      | プログラミング言語                   |                 |
| pip          | 22.2.2     | pythonライブラリパッケージ管理ツール | python3.8.0付属 |
| venv         | ー         | 仮想環境ツール                       | python3.8.0付属 |
| Mysql        | 8.0.30     | DB                                   |                 |

#### 実行環境全体像（デプロイ）

```
browser <-> nginx <-> unix socket <-> uwsgi <-> django
```

## Pyenv

pyenvをインストール、環境変数の設定をする

```bash
sudo su

cd /usr/local/
git clone https://github.com/yyuu/pyenv.git ./pyenv
mkdir -p ./pyenv/versions ./pyenv/shims
cd /usr/local/pyenv/plugins/
git clone https://github.com/yyuu/pyenv-virtualenv.git

echo 'export PYENV_ROOT="/usr/local/pyenv"' | sudo tee -a /etc/profile.d/pyenv.sh
echo 'export PATH="${PYENV_ROOT}/shims:${PYENV_ROOT}/bin:${PATH}"' | sudo tee -a /etc/profile.d/pyenv.sh
source /etc/profile.d/pyenv.sh

cd ~/

cp /etc/sudoers /etc/sudoers.org

PATH_LINE=$(sed -n '/Defaults    secure_path/=' /etc/sudoers)
sed -i -e "s/Defaults    secure_path/#Defaults    secure_path/g" /etc/sudoers
sed -i -e "${PATH_LINE}a Defaults    env_keep += \"PYENV_ROOT\"" /etc/sudoers
sed -i -e "${PATH_LINE}a Defaults    env_keep += \"PATH\"" /etc/sudoers
```

## Python

pythonバージョン3.8.0をpyenvからインストール、設定する

```bash
pyenv install -v 3.8.0
pyenv global 3.8.0

ln -s /lib/python2.7/site-packages/amazon_linux_extras /usr/local/pyenv/versions/3.8.0/lib/python3.8/site-packages/
```

## nginx

nginxをインストールする

```bash
amazon-linux-extras enable nginx1
amazon-linux-extras install -y nginx1

cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.org

# virtualhost用ディレクトリ
mkdir /etc/nginx/sites-available
mkdir /etc/nginx/sites-enabled
```

設定ファイルを編集する

```bash
vim /etc/nginx/nginx.conf
```

includeを追記

```ini
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
```

再起動、自動起動設定

```bash
systemctl stop nginx
systemctl enable nginx
systemctl start nginx
```

## Mysql

mysqlバージョン8をインストール、初期設定する

```bash
# MySQL GPGキーの有効期限切れ対応、インストール時にエラーが起きなければ必要ないです
# rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022

yum-config-manager --disable mysql57-community

# mariadb削除
yum -y remove mariadb-*
rm -rf /var/lib/mysql/

yum localinstall https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm -y
yum -y install --enablerepo=mysql80-community mysql-community-server
yum -y install --enablerepo=mysql80-community mysql-community-devel

cp /etc/my.cnf /etc/my.cnf.org

echo 'character-set-server=utf8' | sudo tee -a /etc/my.cnf
echo 'default_password_lifetime=0' | sudo tee -a /etc/my.cnf

systemctl stop mysqld
systemctl enable mysqld
systemctl start mysqld
```

初期設定

```bash
mysql_secure_installation
```
初期パスワード入力 /var/log/mysqld.logから抽出
```bash
Securing the MySQL server deployment.
Enter password for user root: 
```
新しいパスワードの入力
```bash
The existing password for the user account root has expired. Please set a new password.

New password: ******
Re-enter new password: ******
```
パスワード検証プラグイン
インストール済みなのでここでのパスワード入力は不要

```bash
The 'validate_password' plugin is installed on the server.
The subsequent steps will run with the existing configuration
of the plugin.
Using existing password for root.

Estimated strength of the password: 100 
Change the password for root ? ((Press y|Y for Yes, any other key for No) : n
```

匿名ユーザの削除
```bash
By default, a MySQL installation has an anonymous user,
allowing anyone to log into MySQL without having to have
a user account created for them. This is intended only for
testing, and to make the installation go a bit smoother.
You should remove them before moving into a production
environment.

Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
Success.
```

リモートからの root ユーザでのログイン禁止
```bash
Normally, root should only be allowed to connect from
'localhost'. This ensures that someone cannot guess at
the root password from the network.

Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y
Success.
```

test データベースの削除
```bash
By default, MySQL comes with a database named 'test' that
anyone can access. This is also intended only for testing,
and should be removed before moving into a production
environment.


Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y
 - Dropping test database...
Success.
```

すぐに権限テーブルをリロードして変更を有効にするか
```bash
Reloading the privilege tables will ensure that all changes
made so far will take effect immediately.

Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y
Success.
```
mysql にrootユーザでログインする

```bash
mysql -u root -p
```

パスワードバリデーション設定無効化

```mysql
UNINSTALL COMPONENT 'file://component_validate_password';
```

データベースを作成「django_db」

```mysql
MariaDB [(none)]> CREATE DATABASE django_db CHARACTER SET utf8;

Query OK, 1 row affected (0.00 sec)
```

ユーザー作成、権限設定

```mysql
CREATE USER 'writer'@'%' identified by '******';
CREATE USER 'writer'@'localhost' identified by '******';
GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,ALTER,INDEX,REFERENCES ON *.* TO 'writer'@'%';
GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,ALTER,INDEX,REFERENCES ON *.* TO 'writer'@'localhost';
# migration時に権限エラーとなったので追加
GRANT ALL PRIVILEGES ON first.* TO 'writer'@'localhost' WITH GRANT OPTION;

FLUSH PRIVILEGES;
```

## uwsgi

uwsgiをインストールする

```bash
yes | pip install --upgrade pip
yes | pip install wheel
yes | pip install uwsgi
ln -s /usr/local/pyenv/shims/uwsgi /usr/bin/uwsgi
```

ドキュメントルートディレクトリ作成

```bash
mkdir -m 755 /home/web
```

## Django

venvで仮想環境化する

```bash
cd /home/web
mkdir test && cd test
# venvの仮想環境作成
python -m venv appvenv
# 仮想環境を有効化
source appvenv/bin/activate

# djangoインストール
# pipを更新
(appvenv) pip install --upgrade pip

# 仮想環境にDjangoをインストール
(appvenv) pip install django

# 仮想環境にuWSGIをインストール
(appvenv) pip install uwsgi

# 仮想環境にmysqlclientをインストール
(appvenv) pip install mysqlclient

# django プロジェクト作成
(appvenv) django-admin startproject test_pj
# django アプリ作成
(appvenv) cd test_pj
(appvenv) django-admin startapp test_apps
(appvenv) mkdir static templates
(appvenv) mkdir templates/test_apps
```
Djangoの設定ファイルを編集する

```bash
vim /home/web/test/test_pj/test_pj/settings.py
```

変更、追記を行う

```python
import os

ALLOWED_HOSTS = ['IPアドレスまたはドメイン']

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'django_db',
        'USER': 'writer',
        'PASSWORD': '******',
        'HOST': 'localhost',
        'PORT': '3306',
        'OPTIONS': {
            'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
        },
    }
}

LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'

USE_I18N = True

USE_L10N = True

USE_TZ = False

STATIC_ROOT = os.path.join(BASE_DIR, "static/")
```
Djangoのマイグレーション等を行う

```bash
# マイグレーションを行う
(appvenv) python manage.py makemigrations
(appvenv) python manage.py migrate
```

管理ユーザ作成

```bash
(appvenv) python manage.py createsuperuser

ユーザー名 (leave blank to use 'root'):admin
メールアドレス: admin@sample.com
Password: ******
Password (again): ******
Superuser created successfully.
```

静的ファイルを下記コマンドで集める

```bash
(venvname) python manage.py collectstatic
```

## uwsgiの設定

socketファイルとpidファイルを格納するディレクトリを作成、所有者や権限を変更する

```bash
# ディレクトリ作成
mkdir -p /var/run/uwsgi

# 所有者変更
chown nginx:nginx /var/run/uwsgi

# 権限変更
chmod +w /var/run/uwsgi
```

appvenv配下にuwsgiのための変数を定義するファイルuwsgi_paramsを作成する。

```bash
# 移動
cd /home/web/test/appvenv

# ファイル作成
vim uwsgi_params
```

下記を記載、保存

```bash
uwsgi_param  QUERY_STRING       $query_string;
uwsgi_param  REQUEST_METHOD     $request_method;
uwsgi_param  CONTENT_TYPE       $content_type;
uwsgi_param  CONTENT_LENGTH     $content_length;

uwsgi_param  REQUEST_URI        $request_uri;
uwsgi_param  PATH_INFO          $document_uri;
uwsgi_param  DOCUMENT_ROOT      $document_root;
uwsgi_param  SERVER_PROTOCOL    $server_protocol;
uwsgi_param  REQUEST_SCHEME     $scheme;
uwsgi_param  HTTPS              $https if_not_empty;

uwsgi_param  REMOTE_ADDR        $remote_addr;
uwsgi_param  REMOTE_PORT        $remote_port;
uwsgi_param  SERVER_PORT        $server_port;
uwsgi_param  SERVER_NAME        $server_name;
```

同じくappvenv配下にuwsgi.iniファイルを作成する

```bash
# ファイル作成
vim uwsgi.ini
```

下記を記載、保存

```bash
[uwsgi]
uid = nginx
gid = nginx

base-htdocs = /home/web/test
project-name = test_pj

# 作成したDjangoアプリのルートを指定する
chdir = %(base-htdocs)/%(project-name)

# loadするwsgiモジュールを指定
module = %(project-name).wsgi

# 仮想環境の場所を指定する
home = %(base-htdocs)/appvenv

master = true
processes = 2
threads = 1

# socketとpidファイルの作成場所を指定する
socket = /var/run/uwsgi/%(project-name).sock
pidfile = /var/run/uwsgi/master.pid

chmod-socket = 666
vacuum = true
thunder-lock = true
max-requests = 6000
max-requests-delta = 300

# クライアント送信元IPアドレスをログに出力するか
log-x-forwarded-for = true

# ログ
logto = /var/log/uwsgi/%(project-name).log
deamonize = /var/log/uwsgi/uwsgi-@(exec://date +%Y-%m-%d).log
log-reopen = true

# 開発用リロード設定 ファイル監視、反映のためN秒毎にuwsgiをリロードする
py-autoreload = 1
```

### emperor mode

uwsgiで複数アプリケーションを起動できる仕組み、同一サーバで複数人が開発をする事を想定して使用する

uwsgiの自動起動設定

```bash
# ファイル作成
vim /etc/systemd/system/uwsgi.service
```

下記を記載、保存
```bash
# uwsgi.service
[Unit]
Description=uWSGI
After=syslog.target

[Service]
ExecStartPre=-/bin/mkdir -p /var/log/uwsgi
ExecStartPre=-/bin/chown -R nginx:nginx /var/log/uwsgi
ExecStartPre=-/bin/mkdir -p /var/run/uwsgi
ExecStartPre=-/bin/chown -R nginx:nginx /var/run/uwsgi
ExecStart=/usr/local/pyenv/shims/uwsgi --ini /etc/uwsgi/emperor.ini

Restart=always
Restart=on-failure
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all

[Install]
WantedBy=multi-user.target
```

シンボリックリンク

```bash
mkdir -p /etc/uwsgi/vassals
# シンボリックリンクを設定
ln -s /home/web/test/appvenv/uwsgi.ini /etc/uwsgi/vassals/test.ini
```

emperor mode設定

```bash
vim /etc/uwsgi/emperor.ini
```

下記を記載、保存

```bash
[uwsgi]
emperor = /etc/uwsgi/vassals
uid = nginx
gid = nginx
logto = /var/log/uwsgi/uwsgi.log
touch-logreopen = /var/log/uwsgi/touch-logreopen
master = true
vacuum = true
ignore-sigpipe = true
ignore-write-errors = true
disable-write-exception = true
```

emperor modeになっているか確認

```bash
# 設定を読み込み
systemctl daemon-reload
# 再起動、自動起動をONに
systemctl stop uwsgi
systemctl enable uwsgi
systemctl start uwsgi
# ステータスを確認
systemctl status uwsgi

# ステータスが以下になっていればOK
Status: "The Emperor is governing 1 vassals"
```

## nginxの設定

同一サーバで複数アプリケーションを運用することを想定しているので、バーチャルホストの設定を行う

`/etc/nginx/sites-available`配下に設定ファイルを作成する。

```bash
vim /etc/nginx/sites-available/test
```

下記を記載、保存

```bash
# Djangoの設定
upstream test {
    # uwsgi.iniで設定したソケットファイルの場所を指定する。
    server unix:///var/run/uwsgi/test_pj.sock;
}

# サーバの設定
server {
    # ポート番号
    listen      80;
    # virtual hostやIPを指定
    server_name "ドメイン指定";
    charset     utf-8;

    # Djangoの静的ファイルの設定
    location /static {
        # staticディレクトリが存在する場所を指定する。開発用なのでrootで指定
        root /home/web/test/test_pj;
    }

    location / {
        uwsgi_pass test;
        # uwsgi_paramsの場所を指定する
        include /home/web/test/appvenv/uwsgi_params;
    }
}
```

上記で作成した設定ファイルのシンボリックリンクを`/etc/nginx/sites-enabled`に貼る。

```bash
# シンボリックリンクを設定
ln -s /etc/nginx/sites-available/test /etc/nginx/sites-enabled/test
```

再起動

```bash
systemctl stop nginx
systemctl start nginx
```

**ブラウザでアクセス**
django初期ページが表示されたのを確認