これは?
APIを作リたい。Flask+MySQLで作りたい。そしてDocker化したい。
この手順をメモとして残す。個人的にDocker化するところが良く分かっていないので、ここを集中的に。
やること
- Flask単体で簡単なAPIを作る。
- 1をDocker化する。
- MySQLを用意する。
- FlaskとMySQLの連携をdocker-composeで実行できるようにする。
1. Flask単体で簡単なAPIを作る。
まずなんでもいいから単純なAPIを作る。今回は、会員リスト(仮)から条件にあうデータだけをリストにして返すものを作ることに決めた。
下のファイルから、prefectureがリクエストのパラメータと一致するmail_addressのリストを返却する機能を作る。
mail_address,sex,age,name,prefecture
hoge1@gmail.com,male,18,itirou,tokyo
hoge2@gmail.com,male,23,zirou,osaka
hoge3@gmail.com,male,31,saburou,tokyo
hoge4@gmail.com,female,29,itiko,tokyo
hoge5@gmail.com,mail,11,shirou,osaka
hoge6@gmail.com,female,42,fumiko,tokyo
Flaskを使ったコードはこちら。
超簡単で、app.pyを実行して、http://127.0.0.1:5000/
に?pref=xxxをつけると、pref=xxxに一致するmail_addressがリストで返却される。
次は、このAPIをDocker化する。
2. 1をDocker化する。
「Docker化する」とは、Dockerfileを作ってdocker buildしてDocker imageを作り、docker runでコンテナ内でapp.pyを実行してAPIリクエストが投げられる状態を作ることを目指す。
Dockerfileとは、作りたいコンテナに対し、"ベースとなるイメージを指定し、作成するコンテナの設定を記述して、コンテナ内でコマンドを実行する"動作を記述するもの。
2-1 ベースとなるイメージを指定
pythonでflaskを実行できれば良いので、pythonのイメージをベースにする。
2-2 コンテナの設定を記述
flaskのコードを動かすためには、以下が必要。
- ローカルのソースコードやデータをコンテナに配置する。
- 必要なライブラリをinstallする。
2-3 コンテナ内でコマンドを実行する。
作成したapp.pyを実行する。
これら2-1~2-3を意識して作ったDockerfileが以下。
# 2-1 ベースとなるイメージを指定
FROM python:3.6
# 2-2 コンテナの設定を記述
ARG work_dir=/work # Dockerfile内で扱う変数の作成
ADD pure_flask $work_dir/pure_flask # コードを/work/にコピー(ディレクトリをコピーする時は、右側にディレクトリ名を記述しないといけないので注意)
WORKDIR $work_dir/pure_flask # cd work_directoryのイメージ
RUN pip install -r requirements.txt # requirements.txtで必要なライブラリをインストール
# 2-3 コンテナ内でコマンドを実行
CMD ["python", "/work/pure_flask/app.py"] # CMDは基本的にDockerfile内に1つだけ。
requirements.txtは超簡単にflaskだけ記述。
flask
この状態のDockerfileがあるディレクトリでdocker build -t flask:ver1 .
を実行する。
上記コマンドの.はカレントディレクトリにあるDockerfileを使ってイメージをbuildすることを指す。
次に、docker run -it -d -p 5000:5000 flask:ver1
を実行。
-dはバックグラウンド実行を、-p 5000:5000はローカルの5000番ポートと、コンテナの5000番ポートのポートフォワーディングを指定することを指してます。
この状態でローカルマシンでlocalhost:5000でブラウザで確認すると、APIのreturnが確認できる。
ちなみにこの状態でdocker ps
とdocker images
は以下のようになる。
もしdocker psで何も表示されない場合はエラーでこけてる可能性があるため、docker ps -aで表示したコンテナIDを使って、docker logs [コンテナID]
をしてあげると理由がわかるかも。
$ docker ps
> CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
> dc371c597cef flask:ver1 "python /work/pure_f…" 9 minutes ago Up 9 minutes 0.0.0.0:5000->5000/tcp quizzical_margulis
$ docker images
> REPOSITORY TAG IMAGE ID CREATED SIZE
> flask ver1 b4cda0e56563 9 minutes ago 923MB
> python 3.6 138869855e44 5 days ago 913MB
無事、1. Flask単体で簡単なAPIを作ると同じ動作がDocker上でも確認できた。
次は、MySQLを使うための準備をする。
3. MySQLを用意する。
MySQLもDockerで用意する。今回は、Docker-compose.yamlの利用も同時にする。
Docker-compose.yamlは、Dockerfileと似ているが、複数のコンテナの連携などを記述するためにある。
DBは接続されることが前提のため、Docker-composeで記述するのが自然だと思う。
3-1 ベースとなるイメージを指定
MySQLが使いたいため、MySQLのイメージをベースにする。
3-2 コンテナの設定を記述
MySQLの設定ファイルやデータベースの初期化をする。
3-3 コンテナ内でコマンドを実行する。
MySQLのプロセスを起動する。
Dockerfileは以下。
# 3-1 ベースとなるイメージの指定
FROM mysql:5.7
# 3-2 コンテナの設定を記述
COPY conf.d/mysql.cnf /etc/mysql/conf.d/mysql.cnf # 文字コードの設定のセット
COPY initdb.d/init.sql /docker-entrypoint-initdb.d/init.sql # 初期化用のSQLファイルのセット
Dockerfileの中でcopyしているconfigファイルとinit.sqlはそれぞれ以下。
[mysqld]
character-set-server=utf8
[mysql]
default-character-set=utf8
[client]
default-character-set=utf8
CREATE TABLE `personal_info` (
mail_address VARCHAR(100) NOT NULL,
sex VARCHAR(6) NOT NULL,
age INT NOT NULL,
name VARCHAR(50) NOT NULL,
prefecture VARCHAR(50) NOT NULL,
createdAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updatedAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (mail_address)
);
INSERT INTO `personal_info` (mail_address, sex, age, name, prefecture, createdAt, updatedAt)
VALUES
('hoge1_1@gmail.com', 'male', 18, 'ichirou1' , 'tokyo', current_timestamp(), current_timestamp()),
('hoge2@gmail.com', 'male', 23, 'zirou2', 'osaka', current_timestamp(), current_timestamp()),
('hoge3_3@gmail.com', 'male', 31, 'saburou', 'tokyo', current_timestamp(), current_timestamp()),
('hoge4@gmail.com', 'female', 29, 'itiko', 'tokyo', current_timestamp(), current_timestamp()),
('hoge5_5@gmail.com', 'mail', 11, 'shirou', 'osaka', current_timestamp(), current_timestamp()),
('hoge6@gmail.com', 'female', 42, 'fumiko', 'tokyo', current_timestamp(), current_timestamp());
docker-compose.yamlは以下。
version: '3' # docker-compose.yamlの記述のバージョン
services:
db:
build: mysql # mysqlディレクトリ配下のDockerfileを指定
container_name: db_server # Dockerコンテナの名前
ports:
- '3306:3306' # ポートフォワーディング指定
environment: # 環境変数の設定
MYSQL_ROOT_PASSWORD: pass # MySQLのrootユーザーのパスワード
MYSQL_DATABASE: testdb # MySQLのスキーマ
TZ: Asia/Tokyo # タイムゾーンの指定
volumes: # MySQLのデータの永続化をするためのボリュームマウント
- db-data/:/var/lib/mysql
# 3-3 コンテナ内でコマンドを実行する
command: mysqld # mysqldコマンドの実行
volumes:
db-data:
上記ができたら、docker-compose up -d
でdbコンテナを起動する。
その後、docker exec -it [コンテナID] /bin/bash
でログインし、mysql -u root -p
からパスワードを入力してテーブルを確認し、init.sqlの内容が入っていればOK。
[備考]mysql周りのtips
MySQLのデータを永続化させるためのvolumeにデータが保存されない場合、Dockerホストでdocker volume lsでvolumeを確認して削除してからdocker-compose up -dすると良さげ。
init.sqlは/docker-entrypoint-initdb.d/に固定でコピーする。
次は、flaskとMySQLを接続させる。
4. FlaskとMySQLの連携をdocker-composeで実行できるようにする。
2と4で作成した二つのDockerコンテナを結合させる。
結合させるために必要なことは、以下。
- flaskもdocker-compose.yamlに入れる。
- flaskからMySQLへの接続が可能なようにする(ネットワークを)。
- flaskからMySQLへの接続が可能なようにする(コードを)。
4-1 flaskもdocker-compose.yamlに入れる
flaskのコンテナをMySQLのdocker-compose.yamlに追記したものは以下。
version: '3'
services:
api:
build: python
container_name: api_server
ports:
- "5000:5000"
tty: yes
environment:
TZ: Asia/Tokyo
FLASK_APP: app.py
depends_on: # apiサーバーはdbサーバーが立ってから起動
- db
networks: # apiとdbを接続するための共通ネットワーク指定
- app_net
db:
build: mysql
container_name: db_server
ports:
- '3306:3306'
environment:
MYSQL_ROOT_PASSWORD: pass
MYSQL_DATABASE: testdb
TZ: Asia/Tokyo
volumes:
- ./db-data:/var/lib/mysql
command: mysqld
networks:
- app_net
volumes:
db-data:
networks:
app_net:
driver: bridge
services直下の階層に、dbと同じ階層としてapiを記述。
4-2 flaskからMySQLへの接続が可能なようにする(ネットワークを)。
コンテナを二つ起動し、その間で接続をする場合、二つのコンテナを同一のネットワークで扱う必要がある。
4-1のdocker-compose.yaml内にそれぞれのserviceの中に以下を追記する。
networks:
- app_net
これにより、各コンテナがどのネットワークを利用するかを指定できる。二つを同じネットワークにしていることを意味する。
そして、docker-compose.yamlのトップレベルの階層に以下を記述する。
networks:
app_net:
driver: bridge
これは、dockerのネットワークの作成を意味していて、driverをbridge指定で作る指定。
これでapiとdbのコンテナが同一ネットワーク上となるため、apiからdbへの接続が可能となる。
4-3 - flaskからMySQLへの接続が可能なようにする(コードを)。
flask側のコードとモジュールの修正。
pythonからMySQLに接続する方法は色々ありそうだが、今回はmysqlclientを使った。
pip install mysqlclient
でmysqlclientをインストールし、以下のようなコードで接続して使う。
import MySQLdb
conn = MySQLdb.connect(user='root', passwd='pass', host='db_server', db='testdb')
cur = conn.cursor()
sql = "select * from personal_info;"
cur.execute(sql)
rows = cur.fetchall()
このrowsには、タプルが返却され、レコード数の長さのタプルが取得できる。
(
(1レコード目の1カラム目の値, 1レコード目の2カラム目の値, ...),
(2レコード目の1カラム目の値, 2レコード目の2カラム目の値, ...),
(3レコード目の1カラム目の値, 3レコード目の2カラム目の値, ...)
)
これをpythonで受け取り、リストに格納してjsonで返却するようなイメージ。
修正後のソースはここ。
ディレクトリ構成とか、Dockerfileの位置などもこれを参照してもらえれば。
あとは、pythonで扱いたいモジュールが増えたのでrequirements.txtにmysqlclientを追記する。
これで完了。
docker-compose up -d
で全て起動したら、ローカルでhttp://0.0.0.0:5000/?pref=osaka
などを入れると、結果が確認できる。
以上。