【Python/Django】よく使うコマンドのまとめ(4)-後編- <本番運用：Amazon EC2 (Amazon Linux 2)>

#### 背景
Python/Djangoで開発をすすめるにあたり、よく使うコマンドをまとめておきます。

先人たちの知恵をお借りするなどして解決できたことを、この場をお借りして感謝するとともに、大変恐縮ですが自分のメモとしても、こちらへまとめておきます。

#### 環境
（本番環境）

- AWS EC2 (Amazon Linux 2)
- Python 3.7.9　　　　※2020/12/10時点のAmazon Linux2でのデフォルト
- Django 3.1.3
- PostgreSQL 11.5　　※同上
- Nginx 1.12　　　　　※同上
- Gunicorn
- Putty 0.74


（開発環境）

- Windwos 10 Pro
- Python 3.9.0
- Django 3.1.3
- PostgreSQL 13.1
- Nginx 1.19.5
- Gunicorn
- Putty 0.74


#### 前提

WEBサーバー、APサーバー、DBサーバーを、Amazon EC2(Elastic Computing Cloud) : Amazon Linux 2 で構築します。

(構成図)
![構成図_基本形.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/902793/788877be-c361-f7e1-e1a3-33257395f350.png)


- EC2インスタンスへSSH接続した状態で、以降のコマンドを実行します。

※ PuTTY を利用して、Amazon EC2(Elastic Compute Cloud)へ接続する際は、「[PuTTY を使用した Windows から Linux インスタンスへの接続](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/putty.html)」をご参照ください。


[【Python/Django】よく使うコマンドのまとめ(4)-前編- ＜本番運用：Amazon EC2 (Amazon Linux 2)＞]()も併せてご参照ください。


## 9. 環境変数を設定する。

#### 9-1. 環境変数を設定するファイルを開きます。

```:ターミナル
$ vi ~/.bash_profile
```

#### 9-2. 環境変数を設定します。

「i」キー押下で編集可能になります。
編集後は「esc」キー押下し「:wq」と入力すると、内容が保存され、編集が終了します。
（保存せずに終了する場合は「:q!」と入力します。）

```:~/.bash_profile
(...略...)

export DJANGO_SECRET_KEY='＜Djnagoプロジェクト作成時に付与されれるSECRET_KEY(50桁)＞'
export DJANGO_SETTINGS_MODULE=＜プロジェクト名＞.settings
export DB_USER=＜任意のユーザー名＞
export DB_PASSWORD=＜任意のパスワード＞
export ALLOWED_HOSTS=＜Elastic IP＞（もしくは、＜独自ドメイン名＞）　※
export AWS_SES_ACCESS_KEY_ID=＜Amazon SESの利用設定時に取得したAccess key ID＞
export AWS_SES_SECRET_ACCESS_KEY=＜Amazon SESの利用設定時に取得したSecret Access key＞
```

#### 9-3. 編集した内容を有効にするために、下記を実行します。

```:ターミナル
$ source ~/.bash_profile
```

#### 9-4. 環境変数の内容を確認します。

```:ターミナル
$ env

(...略...)
XDG_SESSION_ID=514
HOSTNAME=ip-XXX-XXX-XXXX-XXX.ap-northeast-1.compute.internal
AWS_SES_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
TERM=xterm
SHELL=/bin/bash
HISTSIZE=1000
SSH_CLIENT=XXX.XXX.XXX.XXX 57002 22
DJANGO_SECRET_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
SSH_TTY=/dev/pts/0
USER=＜任意のユーザー名＞
AWS_SES_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX
(...中略...)
ALLOWED_HOSTS=＜独自ドメイン名＞
MAIL=/var/spool/mail/＜任意のユーザー名＞
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/＜任意のユーザー名＞/.local/bin:/home/＜任意のユーザー名＞/bin
PWD=/home/＜任意のユーザー名＞
DJANGO_SETTINGS_MODULE=＜プロジェクト名＞.settings
DB_PASSWORD=＜任意のパスワード＞
LANG=ja_JP.UTF-8
HISTCONTROL=ignoredups
SHLVL=1
HOME=/home/＜任意のユーザー名＞
LOGNAME=＜任意のユーザー名＞
DB_USER=＜任意のユーザー名＞
SSH_CONNECTION=XXX.XXX.XXX.XXX 57002 XXX.XXX.XXX.XXX 22
LESSOPEN=||/usr/bin/lesspipe.sh %s
XDG_RUNTIME_DIR=/run/user/1001
_=/usr/bin/env

```

## 10. 静的ファイルを配信用ディレクトリへ配置する。

APサーバーの負荷軽減のため、静的ファイルはWEBサーバー（Nginx）から返します。

#### 10-1. 静的ファイル配置ディレクトリを作成し、所有者を＜任意のユーザー名＞へ変更します。

```:ターミナル
$ sudo mkdir -p /usr/share/nginx/html/static      静的ファイル用
$ sudo chown ＜任意のユーザー＞ /usr/share/nginx/html/static

$ sudo mkdir /usr/share/nginx/html/media          メディアファイル用
$ sudo chown ＜任意のユーザー＞ /usr/share/nginx/html/media
```

#### 10-2. Python仮想環境へ入り、manage.pyが存在するディレクトリにて、下記を実行します。

(前提)：

```python:settings.py
STATIC_ROOT = 'usr/share/nginx/html/static'
```

下記の実行により、`/usr/share/nginx/static` ディレクトリに、cssや、jpgなどの静的ファイルが配置されます。

```:ターミナル
(venv_＜プロジェクト名＞)$ python manage.py collectstatic

236 static files copied to '/usr/share/nginx/html/static'.
```

## 11. DBマイグレーションを行なう。

Python仮想環境へ入り、manage.pyが存在するディレクトリにて、下記を実行します。

```:ターミナル
(venv_＜プロジェクト名＞)$ python manage.py migrate
```

## 12. Nignxをインストールし、設定を行なう。

#### 12-1. Nginxのインストール

```:ターミナル
$ amazon-linux-extras list | grep nginx

 38  nginx1=latest            enabled      [ =stable ]
```

nginx1 がインストール可能な最も高いバージョンであることが分かりました。
下記を実行してインストールします。

```:ターミナル
$ sugo amazon-linux-extras install nginx1
```

#### 12-2. Nginxの設定

Nginx用の設定ファイルを編集し、環境設定を行ないます。

「i」キー押下で編集可能になります。
編集後は「esc」キー押下し「:wq」と入力すると、内容が保存され、編集が終了します。
（保存せずに終了する場合は「:q!」と入力します。）

```:ターミナル
$ sudo vi /etc/nginx/nginx.conf
```

```:nginx.conf
(...略...)

    include /etc/nginx/conf.d/*.conf;

    server {
        listen      80;
        listen      [::]:80;
        server_name <Elastic IP もしくは 独自ドメイン名>;
        return 301 https://$host/$request_uri;
    }

    # Setting for a TLS enabled server.    
    server {
        listen      443;
        listen      [::]:443;
        server_name <Elastic IP もしくは 独自ドメイン名>;
        root        /usr/share/nginx/html;

        ssl_certificate "/etc/letsencrypt/live/<独自ドメイン>/fullchain.pem";
        ssl_certificate_key "/etc/letsencrypt/live/<独自ドメイン>/privkey.pem";

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location /static {
          alias /usr/share/nginx/html/static;
        }

        location /media {
          alias /usr/share/nginx/html/media;
        }

        location / {
          proxy_set_header Host $http_host;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Forwarded-proto $scheme;

          proxy_pass http://127.0.0.1:8000;
        }

        location /.well-known/acme-challenge {
          root /usr/share/nginx/html;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
}

(...略...)
```

#### 12-3. Nginxの自動起動設定

```:ターミナル
$ sudo systemctl enable nginx.service
```

#### 12-4. Nginxの起動

```:ターミナル
$ sudo systemctl start nginx.service

$ systemctl status nginx.service

● nginx.service - The nginx HTTP and reverse proxy server
   Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; vendor preset: disabled)
   Active: active (running) since 木 2020-12-10 08:20:46 JST; 2 days ago
  Process: 13919 ExecStart=/usr/sbin/nginx (code=exited, status=0/SUCCESS)
  Process: 13916 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=0/SUCCESS)
  Process: 13915 ExecStartPre=/usr/bin/rm -f /run/nginx.pid (code=exited, status=0/SUCCESS)
 Main PID: 13922 (nginx)
   CGroup: /system.slice/nginx.service
           ├─13922 nginx: master process /usr/sbin/nginx
           └─13923 nginx: worker process
```

#### 12-5. Nginxの再起動

Nginxを再起動する場合は、下記を実行します。

```:ターミナル
$ sudo systemctl reload nginx.service
```

#### 12-6. Nginxの停止

もしも、Nginxを停止する場合は、下記を実行します。

```:ターミナル
$ sudo systemctl stop nginx.service
```


## 13. Gunicornを操作する。

Python仮想環境へ入り、manage.pyが存在するディレクトリにて、下記を実行します。

#### 13-1. Gunicornの起動

```:ターミナル
(venv_プロジェクト名)$ gunicorn --bind 127.0.0.1:8000 ＜プロジェクト名＞.wsgi -D

(venv_プロジェクト名)$ ps ax | grep gunicorn

  671 pts/0    S+     0:00 grep --color=auto gunicorn
13937 ?        S      0:26 /home/＜任意のユーザー名＞/venv_＜プロジェクト名＞/bin/python3 /home/＜任意のユーザー名＞/venv_＜プロジェクト名＞/bin/gunicorn --bind 127.0.0.1:8000 ＜プロジェクト名＞.wsgi -D
13940 ?        S      0:03 /home/＜任意のユーザー名＞/venv_＜プロジェクト名＞/bin/python3 /home/＜任意のユーザー名＞/venv_＜プロジェクト名＞/bin/gunicorn --bind 127.0.0.1:8000 ＜プロジェクト名＞.wsgi -D
```

#### 13-2. Gunicornの停止

もしも、Gunicornを停止する場合は、下記を実行します。

```:ターミナル
(venv_プロジェクト名)$ pkill gunicorn
```

## 14. HTTPS化を行なう

[【Nginx】Let’sEncryptからのSSL証明書がcertbot-autoで取得できない際の対処](https://qiita.com/_whitecat_22/items/14819ce3d6a41cc0d6b2) をご参照ください。


## 15. バッチの定期実行を設定する。

#### 15-1. （例）データベースのバックアップを行なう。

```python:backup_db.py
import csv
import datetime
import os

from django.conf import settings
from django.core.management.base import BaseCommand

from ...models import Post


class Command(BaseCommand):
    help = "Backup Database"

    def handle(self, *args, **options):
        date = datetime.date.today().strftime("%Y%m%d")
        file_path = settings.BACKUP_PATH + 'database_' + date + '.csv'
        os.makedirs(settings.BACKUP_PATH, exist_ok=True)

        with open(file_path, 'w') as file:
            writer = csv.writer(file)
            header = [field.name for field in Post._meta.fields]
            writer.writerow(header)
            blogs = Post.objects.all()
            for customer in customers:
                writer.writerow([str(customer.id,
                                 customer.name,
                                 customer.address,
                                 str(customer.phone_number),
                                 str(customer.created_at),
                                 str(customer.updated_at)])

        files = os.listdir(settings.BACKUP_PATH)
        if len(files) >= settings.NUM_SAVED_BACKUP:
            files.sort()
            os.remove(settings.BACKUP_PATH + files[0])
```

```python:settings.py
BACKUP_PATH ="backup/"
NUM_SAVED_BACKUP = 30
```

#### 15-2. cron機能を用いて定期実行の設定を行なう。

> cron は、登録しておいたコマンドを自動で定期実行してくれるプログラムです。
> Amazon Linux 2 に初めから組み込まれている機能です。

「i」キー押下で編集可能になります。
編集後は「esc」キー押下し「:wq」と入力すると、内容が保存され、編集が終了します。
（保存せずに終了する場合は「:q!」と入力します。）

```:ターミナル
$ crontab -e
```

```:crontabコマンドでの追記内容
 0 4 * * * source ~/venv_＜プロジェクト名＞/bin/activate; cd ~/venv_＜プロジェクト名＞/＜プロジェクト名＞; python manage.py backup_db > ~/cron.log 2>&1
```

**cronの基本書式**
`<分> <時> <日> <月> <曜日> <コマンド>`
上記例は、毎日の午前4時00分(UTC)にコマンドを実行する例です。

＜注意事項＞
cron形式での指定はタイムゾーンがUTCのみで変更できません。
日本標準時(JST)での設定とするには、UTCに対して「-9時間」（9時間を引いた）の値を指定します。
従いまして、上記例は毎日の午後1時(13時)(JST)にコマンドを実行する例となります。

※ DBのバックアップのほか、必要に応じて、SSL証明書（Let's Encrypt）の自動更新、Google pingの発行（サイトマップ.xmlの送信）などを設定すると良いでしょう。


## 16. Djangoのセキュリティチェックを行なう。

HTTPS化が済んだのちに行ないます。
Python仮想環境へ入り、manage.pyが存在するディレクトリにて、下記を実行します。

```python:settings.py
# security.W004
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
# security.W006
SECURE_CONTENT_TYPE_NOSNIFF = True
# security.W007
SECURE_BROWSER_XSS_FILTER = True
# security.W008
SECURE_SSL_REDIRECT = True
# security.W012
SESSION_COOKIE_SECURE = True
# security.W016
CSRF_COOKIE_SECURE = True
# security.W019
X_FRAME_OPTIONS = 'DENY'
# security.W021
SECURE_HSTS_PRELOAD = True
```

```:ターミナル
(venv_＜プロジェクト名＞)$ python manage.py check --deploy
```

WARNINGメッセージが表示されるようであれば、何かしらのセキュリティ対策が不十分とのことです。
通常、上記、プロジェクト設定ファイル（settings.py）の設定内容で、WARNINGは表示されません。

参考：[Djangoにおけるセキュリティ](https://docs.djangoproject.com/ja/3.1/topics/security/)

---
#### (関連)
[【Python/Django】よく使うコマンドのまとめ(1) ＜仮想環境、プロジェクト、アプリケーションの作成＞](https://qiita.com/_whitecat_22/items/6a35200ba958a63eb683)
[【Python/Django】よく使うコマンドのまとめ(2) ＜パッケージのインストール＞](https://qiita.com/_whitecat_22/items/d1d06ea1e280c1665482)
[【Python/Django】よく使うコマンドのまとめ(3) ＜PostgreSQLの操作＞](https://qiita.com/_whitecat_22/items/264b13f9d7c2f020a7ad)
[【Python/Django】よく使うコマンドのまとめ(4)-前編- ＜本番運用：Amazon EC2 (Amazon Linux 2)＞](https://qiita.com/_whitecat_22/items/873befbe9de15421e840)

---
#### (編集後記)
本番運用時のみならず、開発環境としてEC2を利用することもできますので、Dockerの利用も併せて検討していきたいです。