Edited at

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


はじめに

(追記)

この記事はAWS初心者 Advent Calendar 20182日目の記事です。

ササッと公開することが目的なので、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を編集するためにエディタを起動します。

2箇所編集します。

ここで、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のコンソールの左カラムから、セキュリティグループに行って、画像のようなセキュリティグループを作成してください。

(セキュリティグループ名、説明はなんでも良いです アウトバウンドのルールは変更しなくて良いです)

次に、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のあとにポート番号を忘れないでください。

開発用サーバでインターネット上にアプリケーションを公開できました。

しかし、内蔵サーバは低速で負荷に耐えられないので、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です。

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

次に、EC2のインスタンス一覧から実行しているインスタンスを選択し、

右クリック(副クリック) ~> ネットワーキング ~> セキュリティグループの変更

で、今作成したセキュリティグループを割り当ててください。


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

お疲れ様でした。

これで必要な設定が完了したはずです!

最後にGunirornをリスタートしておきましょう。

$ sudo systemctl restart gunicorn

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


ブラウザで確認

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

確認できました!!

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


おわりに

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

18分! 早い!

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

環境やアプリケーションの構成によっては上記手順のどこかでエラーは出ると思います...(特にスタイルシートなどの参照周りとか?)

ですが、調べれば大抵はなんとかなるはず。

エラーログとにらめっこするのも楽しいですよね?:smile: Fight!