Dockerの導入
以前、NGINX + NGINX Unit + Flaskの環境を構築しました。
NGINX + NGINX Unit + Flask で PythonのWeb アプリを動かす
前回はVagrantとVirtualBoxで構築しましたが、今回はDockerで構築していこうと思います。
前回と同様に、WebサーバーにNGINX
を、APサーバーにNGINX Unit
を、フレームワークにFlask
を使用します。
更に今回はデータベースとしてMySQL
を追加し、WEB <-> AP <-> DB
の環境を構築をしていきます。
以下の図のようなイメージです。(本来コンテナはホスト内で起動しますが、わかりやすように分けています)
順を追って構築していくため、できたものをみたい方はGitHubに上げてありますのでそちらをどうぞ。
https://github.com/txkxyx/docker-web
環境
以下の環境で構築していきます。
- ホスト
- OS : macOS Catalina 10.15.3
- Docker : 19.03.5
- コンテナ
- Python : 3.7.3
- Flask : 1.1.1
- NGINX : 1.17.7
- NGINX Unit : 1.14.0
- MySQL : 8.0.18
- Flask SQLAclchemy : 2.4.1
ディレクトリ構成は以下のようにします。
./web
|- db // DB用
|- nginx // NGINX用
|- python // NGINX Unit・ソースファイル用
| |- src
|- docker-compose.yml
では始めていきます。
Dockerfiletとdocker-composeの設定値
Dokerfileとdocker-composeで使用する設定値を簡単にまとめておきます。
Dockerfileの設定値
詳しくは公式のDockerfileリファレンスを参照してください。
https://docs.docker.com/engine/reference/builder/
設定値 | 概要 |
---|---|
FROM | 使用するイメージを指定する。 |
WORKDIR | 作業ディレクトリを指定する。この宣言以降はコンテナ内の指定したパスで作業を行う。 |
COPY | ホストからコンテナに、指定したディレクトリやファイルをコピーする。ホスト コンテナ の順で指定する。.dockerignore で指定したファイルは対象外となる。 |
RUN | 指定したコマンドを現時点のコンテナ内で実行する。(ビルド時に実行するコマンド) |
CMD | コンテナ起動時に実行するコマンドを指定する。(起動時に実行するコマンド) |
docker-composeの設定値
詳しくは公式のリファレンスを参照してください。
https://docs.docker.com/compose/compose-file/
設定値 | 概要 |
---|---|
version | Docker Engineが対応するファイルフォーマットのバージョン |
services | アプリケーションを構成する各要素 |
build | 起動するコンテナのDockerfile があるディレクトリを指定。子要素で、context(DockerfileのあるディレクトリまたはGithubURL)args(Dockerfileに渡す引数)などを指定できる 。 |
image | 起動するコンテナが使用するイメージを指定する。 |
command | docker-compose up を実行した際に実行されるコマンド |
ports | コンテナが公開するポートを指定します。ホスト:コンテナ もしくはコンテナのポートのみ指定します。 |
expose | リンクするコンテナのみに公開するコンテナのポートを指定します。ホストには公開されません。 |
environment | 起動するコンテナの環境変数を指定します。 |
volumes | コンテナにマウントするホストのディレクトリを指定します。ホスト:コンテナ の形式でパスを指定します。 |
container_name | 起動するコンテナのコンテナ名を指定します。 |
depends_on | サービス間の依存関係を指定します。指定したサービス名が先に起動します。 |
DBコンテナの構築
まずはMySQLのコンテナを構築していきます。
イメージはこんな感じです。
docker-compose.yml
を作成します。
version: "3"
services:
db:
image: mysql
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
ports:
- "33306:3306"
expose:
- "3306"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_USER: test
MYSQL_PASSWORD: test
volumes:
- ./db/init:/docker-entrypoint-initdb.d
container_name: app_db
コンテナの初期起動時にデータベースを作成するように、db
ディレクトリ内にinit
ディレクトリを作成し、createdatabase.sqlを作成します。
CREATE DATABASE app;
USE app;
CREATE TABLE users(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
email VARCHAR(255)
);
INSERT INTO users(name,email) VALUES('sample','sample@sample.com');
INSERT INTO users(name,email) VALUES('test','test@test.com');
INSERT INTO users(name,email) VALUES('app','app@app.com');
GRANT ALL ON app.* TO test;
以上の設定でdocker-composeでMySQLのコンテナを起動します。
$ docker-compose up
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bef9a864276c mysql "docker-entrypoint.s…" 4 minutes ago Up 4 minutes 33060/tcp, 0.0.0.0:33306->3306/tcp app_db
docker ps
の結果にapp_db
が表示されればコンテナを起動できています。
一度コンテナ内に入って、データベースが作成されているかを確認します。
$ docker exec -it app_db bash
root@00000000000:/# mysql -u test -p
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| app |
| information_schema |
+--------------------+
2 rows in set (0.00 sec)
mysql> use app;
mysql> select * from users;
+----+--------+-------------------+
| id | name | email |
+----+--------+-------------------+
| 1 | sample | sample@sample.com |
| 2 | test | test@test.com |
| 3 | app | app@app.com |
+----+--------+-------------------+
3 rows in set (0.01 sec)
app
というデータベースが作成されていることが確認できます。更に、createdatabase.sql
のテーブルとデータが作成されていることが確認できます。
MySQLの構築は以上です。
APコンテナの構築
APサーバーとしてNGINX Unit
を、実行環境にPython3
を、フレームワークとしてFlask
を使用したコンテナを構築します。
NGINX Unitの公式ドキュメントを参考に構築していきます。
イメージはこんな感じです。
NGINX Unitのコンテナの起動
まずは、NGINX UnitのイメージからPython3とFlaskの環境を構築します。開発環境の最小単位はこれでいいかもしれません。
web/python
ディレクトリにDockerfile
を追加します。
FROM nginx/unit:1.14.0-python3.7
WORKDIR /usr/src/app
COPY src .
RUN apt update && apt install -y python3-pip \
&& pip3 install --no-cache-dir -r ./requirements.txt \
&& rm -rf /var/lib/apt/lists/*
CMD ["sleep","infinity"]
Docker Hubのnginx/unitのサイトから、NGINX UnitのPython3.7用のイメージを使用します。
次に、pip
でライブラリを一括でインストールできるように、web/python/src
ディレクトリにrequirements.txt
を作成します。
Flask == 1.1.1
flask-sqlalchemy == 2.4.1
PyMySQL == 0.9.3
docker-compose.yml
にNGINX Unitのコンテナの設定を追記します。
version: "3"
services:
db:
image: mysql
ports:
- "33306:3306"
expose:
- "3306"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_USER: test
MYSQL_PASSWORD: test
volumes:
- ./db/init:/docker-entrypoint-initdb.d
container_name: app_db
# ↓↓追記
ap:
build: ./python
ports:
- "8080:8080"
environment:
TZ: "Asia/Tokyo"
container_name: app_ap
depends_on:
- db
作成した'Dockerfile'はweb/python
のディレクトリに存在するので、buildでその場所を指定します。
サーバーのポートは8080
をホストに公開するようにします。
起動しているDockerコンテナを停止してから、docker-compose build
でビルドしてからコンテナを起動します。
$ docker-compose down
$ docker-compose build --no-cache
$ docker-compose up
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
daf4ddc7c11a web_ap "sleep infinity" 41 seconds ago Up 40 seconds 0.0.0.0:8080->8080/tcp app_ap
565eb32e6a39 mysql "docker-entrypoint.s…" 43 seconds ago Up 41 seconds 33060/tcp, 0.0.0.0:33306->3306/tcp app_db
MySQLのコンテナのapp_db
と、NGINX Unitのコンテナのapp_ap
が起動していることが確認できます。
NGINX Unitのコンテナに入り、requirements.txt
のライブラリがインストールされているかを確認します。
$ docker exec -it app_ap bash
root@00000000000:/# python3 -V
Python 3.7.3
root@00000000000:/# pip3 freeze
Flask==1.1.1
Flask-SQLAlchemy==2.4.1
PyMySQL==0.9.3
上記のライブラリ以外にも、SQLAlchemy
やJinja2
などがインストールされています。
ここまでNGINX Unitのコンテナの起動は完了です。続いて、Flaskの実装を行います。
Flaskアプリケーションの実装
Flaskアプリケーションの実装をします。作成するファイルとディレクトリは以下のようになります。
./web
|- db
| |- init
| |- createdatabase.sql
|- nginx
|- python
| |- src
| | |- app.py ← 追加
| | |- config.json ← 追加
| | |- config.py ← 追加
| | |- run.py ← 追加
| | |- users.py ← 追加
| | |- requirements.txt
| | |- templates ← 追加
| | |- list.html ← 追加
| |- Dockerfile ← 更新
|- docker-compose.yml
各ファイルは以下のように実装します。
config.py
まずは、DBの接続先などの設定クラスを実装するconfig.py
です。
ホスト先は、DBコンテナのコンテナ名app_db
で指定します。
class Config(object):
'''
Config Class
'''
# DB URL
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://test:test@app_db:3306/app?charset=utf8'
app.py
次にFlaskアプリケーションを起動するapp.py
です。
config.from_object()
でアプリケーションの設定クラスを呼び込み、SQLAlchemy()
でFlaskアプリケーションでSQLAchemyが使えるように初期化を行います。
from config import Config
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
# Create Flask Application
application = Flask(__name__)
# Set Config Class
application.config.from_object(Config)
# Set DB
db = SQLAlchemy(application)
users.py
次にusersテーブルのModelクラスを作成します。
db.Model
クラスを継承したUsersクラスを作成します。
from app import db
class Users(db.Model):
'''
Users Table Model
'''
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
email = db.Column(db.String(255))
def __init__(self,name,email):
self.name = name
self.email = email
run.py
次に、Flaskアプリケーションの起動とルーティングのモジュールrun.py
です。
レスポンスにテンプレートファイルを使用するので、render_template()
でテンプレートファイルとオブジェクトを指定します。
from app import application
from users import Users
from flask import render_template
@application.route('/list')
def index():
users = Users.query.order_by(Users.id).all()
return render_template('list.html', users=users)
if __name__ == '__main__':
application.run(host='0.0.0.0', port='8080')
list.html
次にテンプレートファイルとなるlist.html
を作成します。
render_template()
でusers
オブジェクトが渡されるので、テンプレートエンジンであるJinja2
を使用して実装します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flask Sample</title>
</head>
<body>
<h1>Flask Sample</h1>
<table border="1" style="border-collapse: collapse">
<thead>
<tr>
<th >Id</th>
<th >Name</th>
<th >EMail</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{user.id}}</td>
<td>{{user.name}}</td>
<td>{{user.email}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
Dockerfile
Dockerfile
を更新します。
FROM nginx/unit:1.14.0-python3.7
WORKDIR /usr/src/app
COPY src .
RUN apt update && apt install -y python3-pip \
&& pip3 install --no-cache-dir -r ./requirements.txt \
&& rm -rf /var/lib/apt/lists/*
# ↓↓削除
config.json
最後にNGINX Unitの設定ファイルconfig.json
を追加します。
{
"listeners": {
"*:8080": {
"pass": "applications/app"
}
},
"applications": {
"app": {
"type": "python",
"processes": 2,
"path": "/usr/src/app/",
"module": "run"
}
}
}
実装は以上です。
ビルドしてからコンテナを起動してみましょう。
$ docker-compose down
$ docker-compose build --no-cache
$ docker-compose up
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
daf4ddc7c11a web_ap "sleep infinity" 41 seconds ago Up 40 seconds 0.0.0.0:8080->8080/tcp app_ap
565eb32e6a39 mysql "docker-entrypoint.s…" 43 seconds ago Up 41 seconds 33060/tcp, 0.0.0.0:33306->3306/tcp app_db
app_ap
のコンテナの起動後に、コンテナにアクセスしてNGINX Unitの設定ファイルを設定します。
$ docker exec -it app_ap bash
root@00000000000:/# curl -X PUT --data-binary @config.json --unix-socket /var/run/control.unit.sock http://localhost/config
{
"success": "Reconfiguration done."
}
ブラウザで、http://localhost:8080/list
にアクセスして画面が表示されます。
これでAPコンテナの構築は終了です。
Webコンテナの構築
最後にWEBサーバーのNGINXのコンテナを構築していきます。
これで NGINX <-> NGINX Unit <-> Flask <-> MySQL
の構成になります。
追加、更新するファイルは以下のようになります。
./web
|- db
| |- init
| |- createdatabase.sql
|- nginx
| |- Dockerfile ← 追加
| |- index.html ← 追加
| |- nginx.conf ← 追加
|- python
| |- src
| | |- __init__.py
| | |- app.py
| | |- config.json
| | |- config.py
| | |- run.py
| | |- users.py
| | |- requirements.txt
| | |- templates
| | |- index.html
| |- Dockerfile
|- docker-compose.yml ← 更新
まずはトップページとなるindex.html
を作成します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Index</title>
</head>
<body>
<h1>Index</h1>
<a href="/list">List</a>
</body>
</html>
次にNGINX
のDockerfile
を作成します。
FROM nginx
WORKDIR /var/www/html
COPY ./index.html ./
CMD ["nginx", "-g", "daemon off;","-c","/etc/nginx/nginx.conf"]
次に、NGINXの設定ファイルを作成します。前回の記事で紹介した設定ファイルから、APサーバーのホストをDocker用に変更します。
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
server_tokens off;
keepalive_timeout 65;
#gzip on;
upstream unit-python {
server app_ap:8080; # container_nameで指定
}
server {
listen 80;
server_name localhost;
# トップページを表示
location / {
root /var/www/html;
}
# /listはAPコンテナにルーティング
location /list {
proxy_pass http://unit-python;
proxy_set_header Host $host;
}
}
}
最後にdocker-composeを更新します。
version: "3"
services:
db:
image: mysql
ports:
- "33306:3306"
expose:
- "3306"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_USER: test
MYSQL_PASSWORD: test
volumes:
- ./db/init:/docker-entrypoint-initdb.d
container_name: app_db
ap:
build:
context: ./python
args:
project_directory: "/src/"
# ↓↓更新
expose:
- "8080"
volumes:
- "./python/src:/projects"
environment:
TZ: "Asia/Tokyo"
container_name: app_ap
depends_on:
- db
# ↓↓追加
web:
build: ./nginx
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
ports:
- "80:80"
environment:
TZ: "Asia/Tokyo"
container_name: "app_web"
depends_on:
- ap
ビルドしてからコンテナを起動してみましょう。
$ docker-compose down
$ docker-compose build --no-cache
$ docker-compose up
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5b0f06b89db4 web_web "nginx -g 'daemon of…" 2 minutes ago Up 23 seconds 0.0.0.0:80->80/tcp app_web
625f3c025a82 web_ap "/usr/local/bin/dock…" 2 minutes ago Up 2 minutes 8080/tcp app_ap
fe5bf54411a2 mysql "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 33060/tcp, 0.0.0.0:33306->3306/tcp app_db
app_apのコンテナの起動後に、コンテナにアクセスしてNGINX Unitの設定ファイルを設定します。
$ docker exec -it app_ap bash
root@00000000000:/# curl -X PUT --data-binary @config.json --unix-socket /var/run/control.unit.sock http://localhost/config
{
"success": "Reconfiguration done."
}
ブラウザで、http://localhost:80
にアクセスするとトップページのindex.html
が表示されます。(8080ポートではアクセスできません)
リンクとなっているList
を押下すると、Flaskアプリケーションのlist.html
が表示されます。
以上でNGINXの構築は終了です。
まとめ
DockerでNGINX + NGINX Unit + MySQLの環境を構築することができました。
あとはアプリケーションを作り込むだけです。