Flask+MySQL on docker

# Flask+MySQL on docker

Webアプリケーションを作りシステム作りが初めてであってもどこからどこまで実施すれば動くシステムができるのかを開発して体験してみます。
FlaskはpythonのWebアプリ開発フレームワークです。Webサーバーを立ち上げてURLに応じた画面を表示したり、リクエストに対する処理をpythonで作り処理結果を返すことができます。


### 勉強会スケジュール

* 開発環境の構築(Python, docker, githubの使い方・フローの説明)​

今回、勉強会で必要なツール等のインストールのサポートと簡単な開発の流れを説明します。

* PythonでRest APIサーバープログラムの作成​

何らかのデータモデルを参照・作成・変更・削除できるAPIを作ります。

各参加者に異なったデータモデルを割り当てるのでDBの設計、sqlの作成、そのモデルを扱うpythonプログラムの作成、apiの入出力の設計、リクエストを受け取って返却するプログラムの作成を行います。

* Vue.jsでフロント画面の作成​

Vue.jsでWebフロントのサンプルプログラムを用意します。それに各自が作ったAPIを呼び出す、Vue.jsを使ったjavascriptプログラムを作成します。

* 本番環境の構築

AWS上にサービスを構築し、公開(リリース)します。
本番に構築する前に、開発環境に構築してプログラム、構築方法が正しいか確認します。
開発環境で問題ないことが確認できたら本番に構築します。また、本番構築後に再度構築が必要になった時、サービスを止めないで構築する方法などについても話をします。

### 事前準備

[Flask+MySQL on dockerを始める準備](https://qiita.com/kaorunix/items/dacb0122898c18ad6b57)を参考に環境を作ってください。

本作業は、ターミナルでコマンド打って実施していきます。
もし、Windowsで実施する場合はGit Bashなどシェルが動くターミナルで実施してください。
環境変数を設定する文法が異なると正しく動かないと思われます。

### Project

今回の勉強会で作っていくシステムを次のリポジトリに公開しました。
プロジェクトはpythonのプログラムである **flask_sv** と後述しますが、DBのdockerコンテナである　**docker_mysql_flask_sv**　　があります。
途中、作業しているディレクトリなどが切り替わりますのでご注意ください。

ここでは **flask_sv** での作業です。
次のコマンドを入力し、リポジトリよりcloneし、そのディレクトリに移動します。

```
$ git clone git@github.com:kaorunix/flask_sv.git
$ cd flask_sv
```

sshのキーを登録していない人は次のコマンドでも良いです。

```
$ git clone https://github.com/kaorunix/flask_sv.git
```

次のコマンドでPipenvを起動します。

```
$ pipenv shell
```

次のコマンドで必要なパッケージをインストールします。

```
$ pipenv install
$ pipenv install --dev
```

プロジェクトは以下の様なディレクトリ構成となっています。

```
.
├── Pipfile
├── Pipfile.lock
├── Readme.md
├── backend
│   ├── src
│   │   ├── api.py
│   │   ├── main.py
│   │   ├── model
│   │   │   ├── Account.py
│   │   │   ├── Authentication.py
│   │   │   ├── Authority.py
│   │   │   ├── Status.py
│   │   │   │ ....
│   │   │   ├── __init__.py
│   │   │   └── db.py
│   │   └── restapi
│   │       ├── __init__.py
│   │       └── AccountApi.py
│   └── tests
│       ├── __init__.py
│       ├── conftest.py
│       └── model
│           ├── __init__.py
│           ├── test_Account.py
│           ├── test_Status.py
│           └── test_db.py
└── tests
    └── __init__.py

```

backend配下にアプリケーションサーバーのソースを入れてあります。
model配下にモデルを配置しています。
MVCモデルに準じてコード設計をしましょう。[Model View Controller](https://ja.wikipedia.org/wiki/Model_View_Controller)参照。
データベースのテーブル単位であったり、取り扱うデータ(例えば複数テーブルをJOINしたもの)単位にコードを書く事にします。

src直下のmain.pyの中にコントローラを実装します。
WEBアプリケーションFWであるflaskを使ってアプリケーションサーバーを実装します。
URL毎に異なった振る舞いをmain.pyを入り口に実装します。

-------

#### docker

アプリケーションを動かすにはDBが必要です。
DBをdockerで用意してありますので次の手順に従って構築してください。
マシンスペックなどでdockerを入れられなかった人はMySQLサーバーをPC上で動かしても作業できます。その場合は、[dockerの代わりにMySQLサーバーを使った場合](https://qiita.com/kaorunix/items/c04d250bda904e9f07f2#docker%E3%81%AE%E4%BB%A3%E3%82%8F%E3%82%8A%E3%81%ABmysql%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9F%E5%A0%B4%E5%90%88)も参考にしてください。

##### 1. dockerインストール

[Windows に Docker Desktop をインストール](https://docs.docker.jp/docker-for-windows/install.html)

##### 2. mysql dockerイメージからコンテナを作成

ここより、**docker_mysql_flask_sv**　の話題となります。

次のリポジトリに今回の開発で必要となるdocker-composeプロジェクトを作ってあります。
次のコマンドでgit cloneします。

```
$ git clone git@github.com:kaorunix/docker_mysql_flask_sv.git
```

ディレクトリ docker_mysql_flask_sv/docker-mysql の配下に .env というファイルを作ります。

```
MYSQL_PASSWORD=password
MYSQL_ROOT_PASSWORD=password
```

passwordのところは自分の環境用のパスワードに変更しておきましょう。
このファイルをはgitに登録しないことでパスワードの漏洩を防ぐことができます。

docker-compose.yml ファイルには次の様に記載されています。
.env で設定した変数を埋め込むことができます。

```docker-compose.yml
# versionは3系が最新版で、versionによって書き方が異なる
version: "3"
services:
  mysql:
    build: ./mysql/    #Dockerfileからビルドすることを示す
    image: mysql:5.7 #original_mysql_world # イメージの名前
    environment:
        MYSQL_DATABASE: flask_sv
        MYSQL_USER: creist
        MYSQL_PASSWORD: ${MYSQL_PASSWORD}
        MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
    volumes:
    - ./mysql/db:/docker-entrypoint-initdb.d  #初期データをマウントする場所
    ports:
    - 3306:3306
```

[Docker-docs-ja Compose ファイル内でのサービス定義](https://docs.docker.jp/compose/gettingstarted.html#id4)などをみて書き方を調べてみてください。

docker-compose.yml があるディレクトリで次のコマンドを打つとmysqlのコンテナが起動されます。

```
$ docker-compose up -d
```

docker psコマンドで実行中のコンテナを確認できます。

```
$ docker ps
CONTAINER ID   IMAGE             COMMAND                  CREATED         STATUS                           PORTS                                         NAMES
4a03b458af46   mysql:5.7         "docker-entrypoint.s…"   11 days ago     Up 3 seconds                     3306/tcp, 33060/tcp, 0.0.0.0:3316->3316/tcp   docker-mysql_mysql_1
```

動いているのが確認できたらmysqlコマンドでログインできることを確認しましょう。

```
$ mysql -h 127.0.0.1 -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 8.0.23 MySQL Community Server - GPL

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>
```

Windowsの場合、クライアントであるmysqlプログラムのパスが通っていないかも知れません。
MySQL実行ファイルは、デフォルトでは次のパスにインストールされています。

```
C:¥Program Files¥MySQL¥MySQL Server 5.7¥bin
```

このパスを環境変数の実行パス `path` に追加しておいてください。

もし、うまくdockerが起動できなかったら次のコマンドでログを確認し、問題を解決します。

```
$ docker logs -f コンテナID
```

docker-compose.ymlの次の行でPCのディレクトリをDockerコンテナの中にマウントしています。

```docker-compose.yml
    volumes:
    - ./mysql/db:/docker-entrypoint-initdb.d  #初期データをマウントする場所
```

docker_mysql_flask_sv/docker-mysql/mysql/db 配下が /docker-entrypoint-initdb.d にマウントされる事になります。

docker mysqlは /docker-entrypoint-initdb.d にsqlファイルを置いておくとコンテナを作成・起動時に実行してくれます。

こちらを参考にしました。[docker mysql(Initializing a fresh instance)](https://hub.docker.com/_/mysql)


###### コンテナが起動できないトラブルシューティング

MySQLをインストールしたときにMySQLサーバーを起動する設定にしているとコンテナが起動できません。コンテナで公開しようとしているポートが既に使われているためです。

OSの設定からMySQLサーバーを起動しない設定に変更してください。

もし、他の用途でMySQLを使っていて、共存させたい場合、次の設定変更でも対応できます。

docker-compose.ymlのポート設定を変更し、自PC上で公開するポートは3307など空いているポートを指定します。

```docker-compose.yml
    ports:
    - 3307:3306
```

この場合、MySQLに接続するコマンドはポート番号を指定するため次の様になります。

```
mysql -h 127.0.0.1 -u root -p　-P 3307
```

他にも接続するアプリケーションのポート番号を変更する必要があります。
ちなみに、flask_svでは、

`flask_sv/backend/model/db.py`

の中でポート番号の設定をしています。

#### dockerの代わりにMySQLサーバーを使った場合

MySQLサーバーにデータベースとユーザーを作成します。
userとpasswordを自分のユーザー名に置き換えてください。ブログの中では`creist`としています。

```sql
$ mysql -u root
mysql> create DATABASE flask_sv;
mysql> CREATE USER user IDENTIFIED BY 'password';
mysql> GRANT ALL ON flask_sv.* TO user;
mysql> FLUSH PRIVILEGES;

```

データベースが作成できたらテーブルを作ります。テーブル作成するSQLが*docker_mysql_flask_sv*  プロジェクトにあります。
次の様にmysqlを実行してSQLを実行しましょう。

```shell
$ mysql -u user flask_sv < $docker_mysql_flask_sv/docker-mysql/mysql/DB/flask_sv.sql
```

-----

#### アプリケーションの起動 

**flask_sv** に戻ります。
[Project](https://qiita.com/kaorunix/items/c04d250bda904e9f07f2#:~:text=%E3%81%A3%E3%81%A6%E3%81%8F%E3%81%A0%E3%81%95%E3%81%84%E3%80%82-,Project,-%E4%BB%8A%E5%9B%9E%E3%81%AE%E5%8B%89%E5%BC%B7)を確認し、 `pipenv shell` や依存パッケージをインストールした状態で始めてください。

[2. mysql dockerイメージからコンテナを作成](https://qiita.com/kaorunix/items/c04d250bda904e9f07f2#2-mysql-docker%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%81%8B%E3%82%89%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%82%92%E4%BD%9C%E6%88%90)で作成したDBのパスワード情報を環境変数に設定するシェルを用意しました。

次の `DOCKER_HOME` に設定するパスを **docker_mysql_flask_sv** のパスに書き換えてください。

```bin/env.sh

DOCKER_HOME=$HOME/projects/docker_mysql_flask_sv/docker-mysql

IFS=$'\n'
for line in `cat $DOCKER_HOME/.env`
do
	export $line
done 
```

このファイルは、使っているシェルに環境変数として読み込ませるので次のコマンドを実行してください。

```
$ source bin/env.sh
```

**flask_sv** アプリケーションは、次のコマンドで実行します。
flask_svのディレクトリから実行してください。

```
$ pipenv run python backend/src/main.py
 * Serving Flask app "main" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

```

実行されると5000ポートでwebサービスが動きます。
http://127.0.0.1:5000 にブラウザでアクセスするとアプリケーションを実行することができます。

現時点ではデータがないのでmysqlでDBに接続し、次のSQLを実行してレコードを追加してください。

```
$ mysql -h 127.0.0.1 -u creist -p flask_sv
mysql> INSERT INTO `account` VALUES (1,'sample_account','2022-05-23 00:00:00','2030-12-31 00:00:00',999,'2022-10-25 23:12:03',999,'2022-10-25 23:12:03',3)
```

APIとしてaccountの取得をするAPIを用意しました。
[http://127.0.0.1:5000/account/get/\<account_id>](http://127.0.0.1:5000/account/get/1)

上記URLへのアクセスに対して次のレスポンスが返ります。

```api
{
  "body": {
     "name": "account",
     "id": <account_id>,
     "account_name": <account_name>,
     "start_on": "2021-01-01 10:00:00",
     "end_on": "2025-12-31 21:00:00"
  },
  "status": {
    "code" : "I0001",
    "message" : "",
    "detail" : ""
  }
}
```

また、登録用のURL
http://127.0.0.1:5000/account/create

```
{
    "account_name":
    "start_on":
    "end_on":
    "opration_account_id":
}
```

のAPIに登録すると、作成日(created_at)、作成者(created_by)、更新日(updated_at)、更新者(updated_by)が更新される。
また、アカウントが作られた時はstatusに "1":"ACTIVE"が入る事にします。

statusは今後のことも考えて使用可能不可能を切り替えられる事にします。

* "0":"NEW"　作成後有効化前の状態
* "1":"ACTIVE"　有効状態
* "2":"INACTIVE"　無効状態
* "3":"DELETE"　削除状態

こういった決め事を考えることが設計です。
APIを正常に受け付けると次のレスポンスを返します。

```
{
  "body" : "",
  "status": {
    "code" : "I0001",
    "message" : "account {} was created.",
    "detail" : ""
  }
}
```

APIの設計を課題とします。後術再度確認します。

----

## DB

現在、accountテーブルを用意してあります。

mysqlではデータベースという論理データベースを選択した後、目的のテーブルなどにアクセスできます。

* show databases
* use <データベース名>
* show tables
* describe

論理データベースの一覧は `show databases`、テーブルの一覧は `show tables`、 テーブル構造を確認するのは `describe`コマンドで確認できます。これらのコマンドはmysql独自のコマンドです。他のデータベースはまた違うコマンドが用意されています。

```
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| flask_sv           |
| information_schema |
+--------------------+
2 rows in set (0.12 sec)

mysql> use flask_sv;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+--------------------+
| Tables_in_flask_sv |
+--------------------+
| account            |
| authentication     |
| authority          |
+--------------------+
3 rows in set (0.01 sec)

mysql>
```

mysqlから確認すると次の様に確認できます。

```sql
mysql> desc account;
+--------------+-------------+------+-----+---------+----------------+
| Field        | Type        | Null | Key | Default | Extra          |
+--------------+-------------+------+-----+---------+----------------+
| id           | int         | NO   | PRI | NULL    | auto_increment |
| account_name | varchar(64) | NO   | UNI | NULL    |                |
| start_on     | datetime    | NO   |     | NULL    |                |
| end_on       | datetime    | NO   |     | NULL    |                |
| created_by   | int         | YES  |     | NULL    |                |
| created_at   | datetime    | YES  |     | NULL    |                |
| updated_by   | int         | YES  |     | NULL    |                |
| updated_at   | datetime    | YES  |     | NULL    |                |
| status       | int         | NO   |     | NULL    |                |
+--------------+-------------+------+-----+---------+----------------+
9 rows in set (0.08 sec)

mysql> 
```

アカウントのテーブルを作るSQLは以下の様になります。
SQLはどのRDBでも基本同じです。ここで慣れておけばOracleや、PostgreSQL、SQLServerなどを使う時に役に立ちます。

```
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
  `id` int NOT NULL AUTO_INCREMENT,
  `account_name` varchar(64) NOT NULL UNIQUE,
  `start_on` datetime NOT NULL,
  `end_on` datetime NOT NULL,
  `created_by` int DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_by` int DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  `status` int NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
```


---

### APIを作る記事 [flaskを使ってRestAPIサーバを作ってみる](https://qiita.com/kaorunix/items/c5fb96124dcdf57c151f) に続きます。