初めてのPython環境構築~Dockerでnginx + uwsgi + djangoの環境を構築してみた~

##自己紹介
都内某W大学の文系大学生のtabataitです。[@tabatait](https://twitter.com/tabataitbot)
投稿は久しぶりです。
普段は大学に通いつつ、過去1年半ほどGoogle AdWordsを始めとしたWeb広告運用をやったり、PHPエンジニアとして1年間仕事をしたりと、
勉学に励みつつ、修行をしつつ、仕事をしつつ奮闘しています。

## 今回書いていくこと
* 自分が作りたいアプリケーションの環境構築手順
    * 1年前挫折したDjangoでのチャレンジ
    * 初めてのPythonでの環境構築

## 作りたい環境
![Infrastructure.png](https://qiita-image-store.s3.amazonaws.com/0/155988/d68dfbfe-6fce-b432-8f32-79d35f5c3a34.png)

とりあえず作りたいプロダクトとしてはまだテスト段階で、
そもそもウケるのかどうかもわからないので、
後々サーバーの構築が楽になるような構成にしようと考えています。

具体的には、

* dockerを使ったコンテナベースの環境を作る。
サービスとしてイケるのであれば、Kubernetesなど、
スケーラビリティしやすい環境にも対応できるような構成にしておきたい。
* Infrastructure as codeに則り、
誰でもカンタンに環境を移植し、開発に参加できるようにしておきたい。
* 環境ごとバージョン管理したい。

なので、docker-composeを使って環境全体の構築をしていこうかと思います。

## 前提条件
- MacOS v10.13.3
- Python 3.6.5
- Node.js 8.11.1
- docker 17.12.0-ce-mac55 (23011)
- docker-compose 1.18.0
- pip 10.0.1

ちなみに今回なぜPythonを使用したかというと、普段はPHPを使っているのですが、
諸々の事情があってPHPを使いたくないからです。
(composer重い。。。phpのインストールに時間かかる。。。xcodeやmacのバージョンに強く依存する。。。)


## Djangoセットアップ
まずはdjangoをインストールしてプロジェクトを作ってしまいます。
```
$ pip install django
$ django-admin startproject testApp
```

一旦ここまでで、アプリケーションが立ち上がるかどうか見てみます。

```
$ python manage.py runserver
```
<img width="1160" alt="スクリーンショット 2018-04-28 11.42.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/155988/f081616b-7e71-498d-b3fd-2ade28558c2a.png">

OK!

## 言語のバージョン指定
言語のバージョン指定を行っていきます。
冒頭でも示している通り、Pythonのversionは3.6.5です。
環境ごとの違いをなくしていくためにもPythonのバージョンは指定しておきます。

サーバー側の言語の指定にはpyenvを使いました。

```
$ pyenv install 3.6.5
$ pyenv local 3.6.5
$ python --version
Python 3.6.5
```

ちなみに僕は普段PHPを書くので、anyenvを使って各言語のenvを管理しています。
https://github.com/riywo/anyenv

## Webサーバーコンテナ構築
まずはnginxを起動します。ルートディレクトリにdocker-composeファイルを作成します。

```
vim docker-compose.yml
```

次にwebサーバーコンテナの環境の定義だけ書いてしまいます。

```yaml:docker-compose.yml
version: '2'

services:
  web:
    image: nginx:alpine
```
初期リリース時はそれほどスペックはいらないと思うので、
一旦軽量なベースイメージであるalpineを使用しています。

そして、開発環境用のコンテナの設定も書いておきます。

```yaml:docker-compose.dev.yml
version: '2'

services:
  web:
    ports:
    - "8000:80"
```

本番環境でサーバーの8000番ポートを開けることはないので、
develop用にcomposeファイルを用意し、開発時のみ使用するようにしました。
では、コンテナを立ち上げましょう。

```
$ docker-compose -f docker-compose.yml -f docker-compose.dev.yml up web
```

これでlocalhost:8000にアクセスすると、
<img width="1186" alt="スクリーンショット 2018-04-28 17.01.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/155988/8c6647cf-2967-9718-d0db-1fe6731877d4.png">

無事nginxの起動まで確認できました。

## アプリケーションサーバーコンテナ構築
ここからがなかなか大変。

まずはpython3.6.5を搭載したイメージファイルを探してきます。
https://hub.docker.com/_/python/

次に、python_serverディレクトリを作り、そこにDockerfileを格納していきます。
requirements.txtもビルド時に組み込みたいので、追加します。

```
$ mkdir python_server
$ touch python_server/requirements.txt
$ touch python_server/Dockerfile
```

```txt:requirements.txt
django==2.0.4
uwsgi
```

```dockerfile:Dockerfile
FROM python:3.6.5

MAINTAINER HayateTabata

RUN mkdir /server
WORKDIR /server
COPY requirements.txt /server/requirements.txt

RUN pip install -r requirements.txt
```

次にuwsgiの設定を追加。

```
$ touch ./python_server/uwsgi.ini
```

```ini:uwsgi
[uwsgi]
chdir = /server
module = testApp.wsgi:application

master = true

max-requests = 5000
processes = 5

socket = :5000
```

pythonコンテナ起動時のコマンドについてもここで追加。

```
$ touch python_server/entry.sh
$ chmod 777 ./python_server/entry.sh
```

```sh:entry.sh
#!/bin/bash
uwsgi --ini ./python_server/uwsgi.ini
```

nginx側の設定を追加。

```
$ mkdir web_server
$ touch web_server/development.conf
```

```conf:development.conf
server {
    listen 80;

    server_name localhost;

    location / {
        uwsgi_pass python:5000;
        include uwsgi_params;
    }
}
```

これでコンテナ自体の準備が完了したので、docker-composeにサービスを定義。

```yml:docker-compose.yml
version: '2'

services:
  python:
    build: python_server
    volumes:
    - .:/server
    command: ./python_server/entry.sh
  web:
    image: nginx:alpine
    depends_on:
    - python
```
```yaml:docker-compose.dev.yml
version: '2'

services:
  python:
    expose:
    - "5000"
  web:
    ports:
    - "8090:80"
    volumes:
    - ./web_server/development.conf:/etc/nginx/conf.d/default.conf
```

これで設定が完了したので、ビルドしてコンテナを立ち上げてみます。

```
$ docker-compose -f docker-compose.yml -f docker-compose.dev.yml build
$ docker-compose -f docker-compose.yml -f docker-compose.dev.yml up web
```

localhost:8090へアクセス。
nginxを経由して無事に表示までできました。

## DBコンテナ構築
mysqlのバージョンは最新の8系を使わず、5.7.22を使用。
さすがにメジャーアップデートしたmysqlをいきなり使うのは恐ろしかったので、、、

```yml:docker-compose
version: '2'

services:
  mysql:
    image: mysql:5.7.22
    restart: always
    environment:
    - MYSQL_ROOT_PASSWORD=testApp
    - MYSQL_DATABASE=testApp
    - MYSQL_USER=testApp
    - MYSQL_PASSWORD=testApp
  python:
    build: python_server
    volumes:
    - .:/server
    command: ./python_server/entry.sh
  web:
    image: nginx:alpine
    depends_on:
    - python
```

次に、Djangoとガッチャンコします。

requirements.txtに下記を追加

```txt:requirements.txt
PyMySQL==0.8.0
```

PyMySQLを呼び出して初期化する必要があり、そこでめちゃくちゃハマりました。

manage.pyを叩いて開発サーバーを構築しているのであれば、manage.pyの頭の方で呼び出せばよいのですが、
uwsgiを使用しているため、wsgi.py内で呼び出さないといけないそう。
参考(https://qiita.com/nrryuya/items/506f16a246da74db9157)

そのため、get_wsgi_application関数よりも前で初期化する必要がある。

```py:wsgi.py
import pymysql

pymysql.install_as_MySQLdb()
~略~
application = get_wsgi_application()
```

最後に、settingにmysqlへのアクセスする設定を書いていきます。75行目あたり。

```
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': os.path.join(BASE_DIR, 'testApp'),
        'USER': 'testApp',
        'PASSWORD': 'testApp',
        'HOST': 'mysql',
        'PORT': '3306',
    }
}
```

docker-composeで設定した項目と一致した内容を書いていきます。
今更感はあるが、HOSTについてmysqlというdocker-compose内で定義した名前が使用されていますが、
dockerが名前解決を行ってくれるので、これで大丈夫です。

requirements.txtを更新したので、もう一回ビルド。

```
$ docker-compose -f docker-compose.yml -f docker-compose.dev.yml build
$ docker-compose -f docker-compose.yml -f docker-compose.dev.yml up web
```

localhost:8090へアクセス。

<img width="1192" alt="スクリーンショット 2018-04-28 23.53.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/155988/5b5d68bb-edc5-a258-f5c7-01d6e0b9e4cb.png">
本日何度も見た画面。
無事起動した。。。！！！長かった。。。！！！

## 感想
* 思いの外環境構築にものすごい時間がかかり、特に自分が慣れていない言語やフレームワークに挑戦するのはかなりコストがかかるものであると痛感。楽しいけど意外と疲れた。。。
* さすがにenvで環境変数を分けるくらいのことはやりたい
* セットアップやコンテナ起動の簡略化(シェルスクリプトによるマクロ)はまた今度
* フロント側の環境構築も腰を据えてやりたい(webpackの導入)
* かなりサーバー側の設定が杜撰なので、ここはいずれどうにかしたい。(nginx、uwsgi周り)
* ようやくコーディングをガッツリやれると思うと心が躍る

ようやくコーディングに移れるので、Djangoで気づいたことや、
普段自分が触っているLaravel Frameworkとの感触の違いなどに触れつつ記事を追加していければなと思います。

##参考
https://docs.djangoproject.com/ja/2.0/intro/tutorial01/
http://pythonskywalker.hatenablog.com/entry/2016/11/17/152830