おいしいブログ

ブログ開発後記 Vol.2

前回のエントリーで全体的なゆるふわアーキテクチャが出来上がったので、実際に物を作り始めるところを書き出して行こうと思います。

docker, nginx, mysqlまわり

Amazon ECSでやろうと決めたのでDockerまわりをいじる。

.
├── README.md
├── docker-compose.yml
├── docker/
│   ├── blog-app/
│   │   ├── Dockerfile
│   ├── db/ # 開発用DB
│   │   ├── Dockerfile
│   ├── nginx-proxy/
│   │   ├── Dockerfile
├── misc/
└── volume/
└── log/

良いか悪いかはさておき、ひとまずこんな感じのディレクトリ構成でdockerディレクトリの下にそれぞれ配置。docker-compose.ymlにそれぞれnginx, blog-app, dbを定義。

# docker-compose.yml

version: '3'
services:
nginx-proxy:
build: ./docker/nginx-proxy
image: nginx-proxy
container_name: nginx-proxy
ports:
- 80:80
links:
- blog-app:blog-app
- db:db
volumes:
- ./docker/nginx-proxy/html:/usr/share/nginx/html
- ./volume/log:/app/log/nginx

blog-app:
build: ./docker/blog-app
image: blog-app
container_name: blog-app
command: python /app/main.py
volumes:
- ./docker/blog-app/app/src:/app
environment:
_DB_USER: mysql
_DB_PASSWORD: mysql
_DB_HOST: db
_ENV: local

db:
build: ./docker/db
image: db
container_name: db
volumes:
- ./docker/db/mysql/init:/docker-entrypoint-initdb.d
environment:
MYSQL_USER: mysql
MYSQL_PASSWORD: mysql
MYSQL_ROOT_PASSWORD: mysql

それぞれ、nginx:latest, frolvlad/alpine-python3, mysql:latestから作る。 nginx-proxydbはDockerfileの中ではほとんど何もしておらず、必要なディレクトリを作ったり、ファイルをCOPYしたりしている程度。blog-appだけ、中で monit をインストールして、CMDではmonitを呼び出すようにして、monit経由でgunicornを呼び出す。

nginx-proxyはポート80で待ってpythonにproxy_passしているだけといった構成で、proxy_passにはlinkで指定した値を設定。

# nginx.confの一部

location @proxy {
proxy_pass http://blog-app:3000;
}

location / {
root /path/to/document-root/;
try_files $uri @proxy;
}

dbは、絵文字? が使いたかったのでutf8mb4を、my.cnfに以下を記述。

[mysql]
default-character-set=utf8mb4

[mysqld]
character-set-server = utf8mb4
skip-character-set-client-handshake
character-set-filesystem = utf8mb4
collation-server = utf8mb4_bin
init-connect = SET NAMES utf8mb4

mysql:latestのコンテナ上の/docker-entrypoint-initdb.dに.sqlなりを置いて置くとコンテナ起動時にmysqlにデータベースやテーブルを作ったり、データを追加したりできるので、開発用に作ったデータをmysqldumpで出力し、volumeに配置。

ということで、あとはpythonを書くだけです?

python + flask + gunicorn

python appはflaskを使っていきます。light weightでお手軽簡単に書けるので重宝しています。

# requirements.txt, as of 2017.12

certifi==2017.11.5
chardet==3.0.4
click==6.7
Flask==0.12.2
gunicorn==19.7.1
idna==2.6
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
PyMySQL==0.8.0
requests==2.18.4
SQLAlchemy==1.1.15
urllib3==1.22
Werkzeug==0.13

flask, gunicorn, sqlalchemy, pymysqlpipで入れただけだったと思いますが、何故か requests がいますね・・? 気にせず行きます。

ディレクトリの構成は基本的なMVCのパターンで決めました。MVT?そんなものは知りません? ディレクトリ名がかなり雑なのはご愛嬌です?

.
├── main.py
├── m/
├── v/
└── c/

main.pyappの定義と、エラーハンドルの定義、blueprintの登録を行います。

# main.py

from flask import Flask, render_template
import c.home

# templateの場所を指定
app = Flask(__name__, template_folder='v')
# blueprintの登録
app.register_blueprint(c.home.app)

@app.errorhandler(404)
def error_404(error):
# handle 404

...


if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000, debug=True)

あとはそれぞれページ毎や必要に応じて以下のようにblueprintを作る。

from flask import Blueprint

app = Blueprint('home', __name__, url_prefix="/")
@app.route('/')
def index():
# handle index

今回、ブログにはトップページ、タグページ、記事ページしか無いので、その分のblurprintを作成、それぞれクエリパラメータやパスパラメータに合わせてDBからデータを取得して表示するだけです。完成です!?

最後に gunicorn のコンフィグを書いてアプリは終了です。

import multiprocessing

bind = '0.0.0.0:3000'
backlog = 2048

workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'sync'
worker_connections = 1000
max_requests = 0
timeout = 30
keepalive = 10
debug = False
spew = False

logfile = '/path/to/log.log'
errorlog = '/path/to/error.log'
loglevel = 'info'
logconfig = None

proc_name = 'gunicorn'

開発環境構築所感

あまり迷うところは無いのでやることをやるだけって感じで所感というほどのものもそんなに無いですが、dockerにしろflaskにしろあまり迷わずさっくり開発環境なりwebアプリを作れるのは良いモンですね。Good Good ?