【20分でデプロイ】AWS EC2にDjango+PostgreSQL+Nginx環境を構築してササッと公開

# はじめに

(追記)
この記事は[AWS初心者 Advent Calendar 2018](https://qiita.com/advent-calendar/2018/aws_beginner)2日目の記事です。

ササッと公開することが目的なので、**`SECRET_KEY`など一切そのままでデプロイしてます。**
production環境では必ず環境変数の設定をして`gitignore`に追加するなどの対策をしてください。
あくまで環境構築→公開の流れの確認だと思ってください。


EC2上でDjango2 + Python3 + PostgreSQLが動作する環境を構築します。
APサーバはGunicornでWebサーバはNginxです。

前提として、ローカル開発環境で構築したDjangoによるWebアプリケーションがGitHubなどのホスティングサービスのリポジトリに上がっている必要があります。(ローカルからftpで送ってもいいですが)

EC2上のパッケージのバージョンには気をつけてください。(特にDjango1系とDjango2系)

間違っているところがあるかもしれません。ご指摘いただけたら嬉しいです... :bow:


では、よーいスタート！！:rocket::stopwatch:

# AWS EC2のインスタンスを作成

AWS webコンソールにサインインし、
`サービス > EC2 > インスタンス`でインスタンスの管理画面を開いてください。
[インスタンスの作成] を押下し、AMI(Amazon Machine Image)にUbuntuを選択します。
あとの設定はデフォルトでOKです。(デフォルトで無料枠の設定になっている)

### キー取得
作成時に、インスタンスに接続するのに必要なキーを取得できます。
「新規にキーを作成する」を選択し、わかり易い名前(aws-ubuntuなど)としてダウンロードします。
ダウンロードしたら、ローカルのわかりやすい場所に保管してください。
**キーは絶対に他人に教えたり、なくしたりしないよう注意してください。**


# sshでインスタンスへ接続
**以下はmacの場合。windowsはPuTTYやTeraTermなどのsshクライアント、あるいはGitBashなどを使用してください**

EC2のインスタンス一覧で作成したインスタンスの、「インスタンスの状態」が「running」になっていることを確認します。
作成したインスタンスを選択し、[接続]を押下して、接続方法を確認します。

1.ダウンロードしたキーのディレクトリに行き、パーミッションを変更します。
以下はターミナルでの作業。

```
$ ls #カレントディレクトリにkeyがあることを確認
aws-ubuntu.pem

$ chmod 400 aws-ubuntu.pem #「所有者のみに読み取り権限付与」というパーミッションに切り替える
```
※pemファイルの権限は、400か600にしてください。

2.キーを使ってインスタンスに接続します。

[接続]を押下したときに表示されるコマンドをコピペで実行します。

```
$ ls #カレントディレクトリにkeyがあることを確認
aws-ubuntu.pem

$ ssh -i "aws-ubuntu.pem" ubuntu@ec2-13-231-109-224.ap-northeast-1.compute.amazonaws.com #例です
```
`aws-ubuntu.pem`というキーを使って、
DNSが`ec2-13-231-109-224.ap-northeast-1.compute.amazonaws.com`のインスタンスに、
`ubuntu`というユーザでサインインします、という意味です。
`ubuntu@ec2-13-231-109-224.ap-northeast-1.compute.amazonaws.com`の部分はご自身で設定しているリージョンなどによって変わりますが、気にしないでOKです。

接続しようとしたら、
`Are you sure you want to continue connecting (yes/no)?`
と聞かれるので、`yes`と入力してください。

# 必要なパッケージのインストール・設定

### Python3, PostgreSQL, Nginxをインストール

接続ができたら、ubuntuなどDebian系のディストリビューションで利用される`apt-get`というパッケージマネージャで必要なパッケージをインストールします。
まずは`Python3` `PostgreSQL` `Nginx`を次のコマンドで一気にインストールします。

```
$ sudo apt-get update #サーバーからパッケージ・リストを入手する
.....

$ sudo apt-get install python3-pip python3-dev libpq-dev postgresql postgresql-contrib nginx #数分かかるかもしれません
......
```

パッケージのインストール時に`Do you want to continue? [Y/n]
`と聞かれるので、`Y`と入力して処理を続行してください。

正しくインストールできたか、それぞれ確認します。

```
$ python3 --version
Python 3.5.2

$ psql --version
psql (PostgreSQL) 9.5.14

$ nginx -v
nginx version: nginx/1.10.3 (Ubuntu)
```

上のようにバージョン情報は出力されればOK。

### PostgreSQLの設定

DBの準備をします。
PostgreSQLのインストール時に自動で`postgres`というユーザが自動で作成されるので、これを用いて設定していきます。
sqplというインタプリタ(対話的実行環境)を`postgres`で実行します。

```
$ sudo -u postgres psql

postgres=#   #psqlが起動し、左のようなプロンプトで入力待ちの状態となります。これでSQL文が入力できます。

```

続いてDBの作成や設定をしていきます。

```
postgres=# CREATE DATABASE <DB_NAME: なんでもOK わかりやすいようにプロジェクト名とか>;
CREATE DATABASE

postgres=# CREATE USER <DB_USERNAME: なんでもOK> WITH PASSWORD '<DB_PASSWORD: なんでもOK>'; #<>内はわかりやすく自由に設定。大文字である必要もないです。
CREATE ROLE

postgres=# ALTER ROLE <DB_USERNAME> SET client_encoding TO 'utf8';
ALTER ROLE

postgres=# ALTER ROLE <DB_USERNAME> SET default_transaction_isolation TO 'read committed';
ALTER ROLE

postgres=# ALTER ROLE <DB_USERNAME> SET timezone TO 'UTC+9';
ALTER ROLE

postgres=# GRANT ALL PRIVILEGES ON DATABASE <DB_NAME> TO <DB_USERNAME>;
GRANT

postgres=# ¥q #終了

$  
```

### virtualenvインストール

まずpipをインストール、次にvirtualenvをインストールします。

```
$ sudo -H pip3 install --upgrade pip
Collecting pip
  Downloading https://files.pythonhosted.org/packages/5f/25/e52d3f31441505a5f3af41213346e5b6c221c9e086a166f3703d2ddaf940/pip-18.0-py2.py3-none-any.whl (1.3MB)
    100% |████████████████████████████████| 1.3MB 1.1MB/s 
Installing collected packages: pip
  Found existing installation: pip 8.1.1
    Not uninstalling pip at /usr/lib/python3/dist-packages, outside environment /usr
Successfully installed pip-18.0

$ sudo -H pip3 install virtualenv
Collecting virtualenv
  Downloading https://files.pythonhosted.org/packages/b6/30/96a02b2287098b23b875bc8c2f58071c35d2efe84f747b64d523721dc2b5/virtualenv-16.0.0-py2.py3-none-any.whl (1.9MB)
    100% |████████████████████████████████| 1.9MB 14.5MB/s 
Installing collected packages: virtualenv
Successfully installed virtualenv-16.0.0

```

# 仮想環境の構築

virtualenvを使って仮想環境を作成し、アクティベートします。

```
$ virtualenv python3　#python3は例です。好きな名前を指定できます。
Using base prefix '/usr'
New python executable in /home/ubuntu/python3/bin/python3
Also creating executable in /home/ubuntu/python3/bin/python
Installing setuptools, pip, wheel...done.

$ source python3/bin/activate　#<仮想環境名>/bin にactivateというファイルが生成されるので、`source`で読み込んで仮想環境をアクティベートします

(python3) $ #仮想環境がアクティベートされると、仮想環境の名前がプロンプト($や#のマーク)の左側に表示されます。

```

### Djangoのインストール

仮想環境下で、以下を実行します。

```
(python3) $ pip install django gunicorn psycopg2 psycopg2-binary Pillow
...

Successfully installed django-2.1.1 gunicorn-19.9.0 psycopg2-2.7.5 pytz-2018.5
```

### Webアプリケーションのclone

リモートリポジトリ上にあるご自身のWebアプリケーションをcloneしてきます。

```
(python3) $ pwd #カレントディレクトリが/home/<user>であることを確認
/home/ubuntu

(python3) $ git clone https://github.com/<GitHub_Username>/<PJ_NAME>.git #GitHubの例 <>内はご自身のものを入力してください。
```

### APとDBの接続とテーブル作成(migration)

cloneしたアプリケーションのフォルダに移動し、`settings.py`の設定をします。

```
(python3) $ cd <PJ_NAME>/<PJ_NAME>
(python3) $ vim settings.py
```

で`settings.py`を編集するためにエディタを起動します。
２箇所編集します。

ここで、AWS EC2のwebコンソールに戻り、接続中のインスタンスに動的に割り当てられているパブリックIPを確認してください。
(インスタンス一覧から、接続中のインスタンスを選択すると、画面下あたりに表示されています。)

```settings.py

......

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '**************************************************' #ここは環境変数を設定して外部ファイルから読み込むようにするべきなのですが、ここでは割愛します。

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ['13.231.109.224'] #変えるところ1: ここにEC2インスタンスのパブリックIPを入力。環境によって変わります。



# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles'
]

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',
]

ROOT_URLCONF = '<PJ_NAME>.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['templetes'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = '<PJ_NAME>.wsgi.application'


# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases

DATABASES = {
    'default': {
#        'ENGINE': 'django.db.backends.sqlite3',
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
#        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        'NAME': '<DB_NAME>',
        'USER': '<DB_USERNAME>',
        'PASSWORD': '<DB_PASSWORD>',
        'HOST': 'localhost',
        'PORT': '',
# 変えるところ2: DBエンジンをPostgreSQLにして、NAMEとUSERとPASSWORDをそれぞれ編集・追記してください。
    }
}

......

```

これで、13.231.109.224というホストからの接続を許可するように設定できました。
編集できたらmigrationファイルを生成し、migrateします。

```
(python3) $ pwd #manage.pyのあるディレクトリにいるかを確認
/home/ubuntu/<PJ_NAME>

(python3) $ python3 manage.py makemigrations
No changes detected

(python3) $ python3 manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying posts.0001_initial... OK
  Applying sessions.0001_initial... OK
```

無事PostgreSQLに接続してテーブルを作成できました。

# 開発用サーバの起動(不要なら飛ばして良い)

もうこの段階で開発用サーバであれば公開できます。
開発用サーバとは、Djangoの

```
$ python3 manage.py runserver
```

で動くAPサーバですが、負荷に耐えられないためあくまで開発用です。
(実行時に`Starting development server`と出るし)

開発用サーバでインターネット上にアプリケーションを公開する手順を示します。
通常はこのあたりの設定もしっかりやりますが、今回はササッとデプロイが目的なので、飛ばしてもOKです。

### AWSの設定

AWSに戻ります。
EC2のコンソールの左カラムから、セキュリティグループに行って、画像のようなセキュリティグループを作成してください。
(セキュリティグループ名、説明はなんでも良いです アウトバウンドのルールは変更しなくて良いです)
![スクリーンショット 2018-09-11 0.20.42.png](https://qiita-image-store.s3.amazonaws.com/0/174325/42e1f500-2e48-29ac-0c44-2f3c799b7530.png)

次に、EC2のインスタンス一覧から実行しているインスタンスを選択し、
右クリック(副クリック) ~> ネットワーキング ~> セキュリティグループの変更
で、今作成したセキュリティグループを割り当ててください。

設定できたら、サーバを起動します。

```
(python3) $ ls #カレントディレクトリにmanage.pyがあるか確認。
manage.py

(python3) $ python3 manage.py runserver 0.0.0.0:8000
Performing system checks...

System check identified no issues (0 silenced).
September 11, 2018 - 00:38:30
Django version 2.1.1, using settings 'myblogapp.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.

```

EC2のパブリックIPにブラウザからアクセスして確認します。
IPのあとにポート番号を忘れないでください。

![スクリーンショット 2018-09-11 0.39.35.png](https://qiita-image-store.s3.amazonaws.com/0/174325/dc62b683-f9f8-29b0-aa19-090582bb4834.png)

開発用サーバでインターネット上にアプリケーションを公開できました。
しかし、内蔵サーバは低速で負荷に耐えられないので、GunicornというAPサーバとNginxというWebサーバを動かしましょう。

# Gunicornの設定

ここで一旦virtualenvから抜けます。

```
(python3) $ deactivate

$ #プロンプト左のvirtualenv nameが消える
```

GunicornとはRubyで利用されるAPサーバのUnicornをもとにして作られたPython向けのAPサーバです。
Gunicornのserviceファイルを編集(作成)します。

```
$ sudo vim /etc/systemd/system/gunicorn.service
```

おそらくファイルが存在していないので、以下を貼り付けてください。<>内はご自身の環境で書き換えてください。

```/etc/systemd/system/gunicorn.service
[Unit]
Description=gunicorn daemon
After=network.target

[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/<PJ_NAME>
ExecStart=/home/ubuntu/<VIRTUALENV_NAME>/bin/gunicorn --access-logfile - --workers 3 --bind unix:/home/ubuntu/<PJ_NAME>/<PJ_NAME>.sock <PJ_NAME>.wsgi:application

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

Gunicorn serviceの自動起動を設定します。

```
$ sudo systemctl start gunicorn.service

$ sudo systemctl enable gunicorn
Created symlink from /etc/systemd/system/multi-user.target.wants/gunicorn.service to /etc/systemd/system/gunicorn.service.
```

# Nginxの設定

Nginxの設定ファイルを作成します

```
$ sudo vim /etc/nginx/sites-available/<PJ_NAME>
```


```:/etc/nginx/sites-available/PJ_NAME
server {
        listen 80;
        server_name <EC2のパブリックIP>;

        location = /favicon.ico {access_log off; log_not_found off;}
        location /static/ {
                root /home/ubuntu/<PJ_NAME>;
        }

        location / {
                include proxy_params;
                proxy_pass http://unix:/home/ubuntu/<PJ_NAME>/<PJ_NAME>.sock;
        }
}
```

シンボリックリンクを張ります。

```
$ sudo ln -s /etc/nginx/sites-available/<PJ_NAME> /etc/nginx/sites-enabled/
```

Nginxをリスタートして設定を反映させ、Nginxで利用する80番ポートを開放します。

```
$ sudo systemctl restart nginx

$ sudo ufw delete allow 8000 #8000番はもう使わないので一応削除

$ sudo ufw allow 'Nginx Full'
Rules updated
Rules updated (v6)

```

# EC2の設定

EC2のコンソールに戻り、左カラムから「セキュリティグループ」を選択し、セキュリティグループを以下のように作成してください。
※**開発用サーバの起動(不要なら飛ばして良い)**が済んでいる方は、すでに作成されているセキュリティグループに`タイプ: HTTP`のルールを追加するだけでOKです。

![スクリーンショット 2018-09-12 20.28.27.png](https://qiita-image-store.s3.amazonaws.com/0/174325/fd042982-5baf-bcca-8f52-a10410cee768.png)

(`タイプ: SSH`のソースは、ご自身のPCのアドレスです。インスタンスに接続するために、許可をする必要があります)

次に、EC2のインスタンス一覧から実行しているインスタンスを選択し、
右クリック(副クリック) ~> ネットワーキング ~> セキュリティグループの変更
で、今作成したセキュリティグループを割り当ててください。

# これで設定は完了! ...のはず

お疲れ様でした。
これで必要な設定が完了したはずです！
最後にGunirornをリスタートしておきましょう。

```
$ sudo systemctl restart gunicorn
```

いよいよEC2インスタンスのパブリックIPにアクセスして、確認します！

# ブラウザで確認

EC2インスタンスのパブリックIPをブラウザのアドレスバーに入力してアクセスしてみます。

![スクリーンショット 2018-09-12 20.35.01.png](https://qiita-image-store.s3.amazonaws.com/0/174325/07df9b68-aed8-5054-6220-326bae1c8898.png)

確認できました！！
ブラウザはデフォルトで80番ポートに接続するため、`:80`と明示する必要はありません。

# おわりに

私の手元でWebアプリケーションの公開まで何分でできるか計ってみました...:stopwatch:

**18分！ 早い！**

とにかく早く環境構築したいのであれば、既存のDocker ImageをPullしてきたり、誰かが作ったDockerfileをBuildするだけとかでもいいのですが、手作業でも意外とすぐにできるので、勉強のためにひとつずつ確認しながらやるのもいいんじゃないかなと思いました。

環境やアプリケーションの構成によっては上記手順のどこかでエラーは出ると思います...(特にスタイルシートなどの参照周りとか?)
ですが、調べれば大抵はなんとかなるはず。
エラーログとにらめっこするのも楽しいですよね？:smile: Fight!