docker-composeでFlask+MySQL環境構築してハマった話
はじめに
docker-composeでFlask+MySQLの環境構築を行いました。
主に以下の記事を参考にしました。
– PythonのFlaskでMySQLを利用したRESTfulなAPIをDocker環境で実装する - Qiita
Dockerは触っていましたが今回はじめてdocker-composeを触りまして、盛大にハマったのでメモします。
結論
私がハマった穴を踏まえ、うまくいかないときに見ると良いかもしれないポイントです。
基本
docker-compose logs
でエラーログを確認する
MySQL
- 初期データ投入のSQLファイルに構文エラーがないか
- MYSQL_ROOT_PASSWOORDは設定されているか
- MySQLバージョンに合わせた設定が
my.cnf
に記載されているか - データ永続化用ディレクトリの中身は空か
Flask
- tty: true オプションはついているか
volumes:
のマウントはできているかpip install
は適切な設定か
やりたかったことと発生した事象
Flask用のコンテナと、MySQL用のコンテナを作って、Flask->MySQLに接続できるようにしたい、というのがやりたかったことです。
症状としては、docker-compose up -d
してもどちらのコンテナもExit 0, Exit 1となり立ち上がらない、というものでした。
$ docker-compose up -d
Starting to-buy-list_mysql_1 ... done
Starting to-buy-list_appserver_1 ... done
$ docker-compose ps
Name Command State Ports
----------------------------------------------------------------------
to-buy-list_appserver_1 python3 Exit 0
sto-buy-list_mysql_1 docker-entrypoint.sh mysqld Exit 1
最初の設定は以下でした。
フォルダ構成
.
├── app
│ ├── Dockerfile
│ ├── requirements.txt
│ └── src
├── docker-compose.yml
├── mysql
│ ├── Dockerfile
│ ├── logs
│ ├── my.cnf
│ ├── mysql_data
│ └── sqls
│ └── initialize.sql
.
docker-compose.yml
version: '3'
services:
db:
build: ./mysql/
volumes:
- ./mysql/mysql_data:/var/lib/mysql # データの永続化
- ./mysql/sqls:/docker-entrypoint-initdb.d # 初期データ投入
- ./mysql/logs:/var/log/mysql
environment:
- MYSQL_ROOR_PASSWORD=password
appserver:
build: ./app/
links:
- db
mysql/Dockerfile
FROM mysql:8.0
EXPOSE 3306
ADD ./my.cnf /etc/mysql/conf.d/my.cnf
CMD ["mysqld"]
app/Dockerfile
# ベースイメージ作成
FROM python:3.7
# requirements.txtをコピー
COPY requirements.txt .
# pipアップグレード
RUN pip install --upgrade pip
RUN pip install --user -r requirements.txt
# 作業ディレクトリ指定
WORKDIR /workdir
# 公開ポート指定
EXPOSE 8080
MySQLコンテナの解決
初期データ投入のSQLファイルに構文エラーがないか
初期データ投入用に、mysql/sqls/initialize.sql
を用意し、docker-compose.ymlでそれを読み込むように設定していました。
- ./mysql/sqls:/docker-entrypoint-initdb.d # 初期データ投入
が、そのinitialize.sql
を改めて見てみると、コピペしたときに不要な文字列が紛れ込んでおり、構文エラーを引き起こしていることがわかりました。
MySQLバージョンに合わせた設定がmy.cnf
に記載されているか
前項の修正をしてもいまだ立ち上がらないので、$ docker-compose logs
でエラーログを出してみます。
$ docker-compose logs
Attaching to to-buy-list_db_1, to-buy-list_appserver_1
appserver_1 | Python 3.7.8 (default, Jul 22 2020, 12:48:54)
appserver_1 | [GCC 8.3.0] on linux
appserver_1 | Type "help", "copyright", "credits" or "license" for more information.
appserver_1 | >>> db_1 | 2020-10-31 16:15:24+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.22-1debian10 started.
db_1 | 2020-10-31 16:15:24+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
db_1 | 2020-10-31 16:15:24+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.22-1debian10 started.
db_1 | 2020-10-31 16:15:24+00:00 [ERROR] [Entrypoint]: Database is uninitialized and password option is not specified
db_1 | You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD
You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD
とありますね。「MYSQL_ROOT_PASSWORD/MYSQL_ALLOW_EMPTY_PASSWORD/MYSQL_RANDOM_ROOT_PASSWORDのいずれかが設定されている必要がある」ようです。MYSQL_ROOT_PASSWORD
を設定していたはずですが…と改めてdocker-compose.ymlを見てみると。
environment:
- MYSQL_ROOR_PASSWORD=password
普通にタイポしてました。辛い…
MySQLバージョンに合わせた設定がmy.cnf
に記載されているか
それでもまだ動かないので、MySQLバージョンを8.0にしているのが理由かもと、試しにバージョンを5.7に書き換えてやってみると、うまくいきました。
MySQL8.0以降は、認証周りの初期設定が異なるためか、my.cnf
の設定も8.0以降に合わせて書く必要があったのでした。
MySQL8.0を使いたかったため、以下の記事を参考に、my.cnfを書き換えました。
docker-compose MySQL8.0 のDBコンテナを作成する - Qiita
mysql/my.cnf
[mysqld]
# 文字コード/照合順序の設定
character-set-server = utf8mb4
collation-server = utf8mb4_bin
# タイムゾーンの設定
default-time-zone = SYSTEM
log_timestamps = SYSTEM
# デフォルト認証プラグインの設定
default-authentication-plugin = mysql_native_password
# エラーログの設定
log-error = /var/log/mysql/mysql-error.log
# スロークエリログの設定
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 5.0
log_queries_not_using_indexes = 0
# 実行ログの設定
general_log = 1
general_log_file = /var/log/mysql/mysql-query.log
# mysqlオプションの設定
[mysql]
# 文字コードの設定
default-character-set = utf8mb4
# mysqlクライアントツールの設定
[client]
# 文字コードの設定
default-character-set = utf8mb4
データ永続化用ディレクトリの中身は空か
my.cnfを8.0用に書き換えて再度ビルドしてみるものの、うまくいきません。
ログを見てみると、
db_1 | 2020-10-31T16:32:41.524601Z 1 [ERROR] [MY-012526] [InnoDB] Upgrade after a crash is not supported. This redo log was created with MySQL 5.7.32. Please follow the instructions at http://dev.mysql.com/doc/refman/8.0/en/upgrading.html
db_1 | 2020-10-31T16:32:41.524983Z 1 [ERROR] [MY-012930] [InnoDB] Plugin initialization aborted with error Generic error.
db_1 | 2020-10-31T16:32:41.972222Z 1 [ERROR] [MY-011013] [Server] Failed to initialize DD Storage Engine.
db_1 | 2020-10-31T16:32:41.976898Z 0 [ERROR] [MY-010020] [Server] Data Dictionary initialization failed.
db_1 | 2020-10-31T16:32:41.978094Z 0 [ERROR] [MY-010119] [Server] Aborting
db_1 | 2020-10-31T16:32:41.990226Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.0.22) MySQL Community Server - GPL.
This redo log was created with MySQL 5.7.32. Please follow the instructions at http://dev.mysql.com/doc/refman/8.0/en/upgrading.html
あたりのメッセージを読むに、さきほど一度検証用にMySQL 5.7にしてビルドしたことが影響しているように見えます。
docker-compose.yml
volumes:
- ./mysql/mysql_data:/var/lib/mysql # データの永続化
↑でデータの保存先としてローカルフォルダをマウントしているため、これまで何度もビルドを繰り返してきたログやその他のファイルが格納されていました。
mysql/mysql_data/の中身をすべて削除すると、うまくいきました。
これでようやくDBコンテナのStatusがUpになり、接続して確認ができる状態となりました。
# DBコンテナに接続して確認
$ docker-compose exec db mysql -u root -p
Flaskコンテナの解決
tty: true オプションはついているか
オプションとして、tty: true
を付けることで、コンテナが起動時、すぐに終了してしまうのを防ぐことができます。
tty: true
volumes:
のマウントはできているか
この状態だと、まだボリュームのマウントができていません。
コンテナ単独で立ち上げる時の、以下のコマンドに代替する設定をdocker-composeに書いておく必要があります。
$ docker run -it -p 8080:5000 -v $(pwd)/src:/workdir --name flask-to-buy-list flask-app /bin/bash
合わせてポートのマッピングも設定しておきます。
他にも、
– コンテナ起動するとFlaskが動くようにcommand設定
– FlaskをCLIで動かすための環境変数など設定
なども入れています。
docker-compose.yml
(省略)
appserver:
build: ./app/
ports:
- 8080:5000
volumes:
- ./app/src:/workdir
environment:
TZ: Asia/Tokyo
FLASK_APP: run.py
FLASK_ENV: development
command: flask run -h 0.0.0.0
tty: true
links:
- db
pip install
は適切な設定か
ここまでやると、appserverのコンテナのビルド、起動、ログインまでできるようになりました。
# コンテナにログイン(docker-composeコマンドで入る場合)
$ docker-compose exec appserver bash
# コンテナにログイン(dockerコマンドで入る場合)
$ docker exec -it to-buy-list_appserver_1 bash
しかし、コンテナにログインした後、flask db init
などのflaskコマンドがcommand not foundとなり実行できない問題が発生しました。
command not foundになる理由は、「コンテナにFlaskがインストールできていないから」が一番に考えられるものの、
$ pip install Flask
を実行しても、すでに入っているとのメッセージが出たのと、また
$ python run.py
でFlaskの起動自体はできていたため、どうしたものかと途方にくれていました。
再度コンテナのビルドをやり直したときに、以下のようなエラーが出ていることに気付きました。
WARNING: The script flask is installed in '/root/.local/bin' which is not on PATH.
つまり、「インストールしているけど、PATHが通っていないよ」ということでした。
appserverコンテナのDockerfileを以下のように修正しました
RUN pip install --user -r requirements.txt
↓
RUN pip install -r requirements.txt
--user
オプションは、パッケージのインストール先を、ユーザーのホームディレクトリ配下に指定できるのですが、Dockerコンテナの場合、具体的には以下にインストールすることになります。
>>> import site
>>> site.getusersitepackages()
'/root/.local/lib/python3.7/site-packages'
コンテナの$PATHを確認してみます。
root@c4f97f85d99c:/workdir# echo $PATH
/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
インストール先のディレクトリが、$PATHに含まれていないよ、とWarningが出ているのでした。
なぜ他のパッケージは問題なくいけて、Flaskだけできないのかは不明ですが、この件については--user
オプションをDockerfileの記述から削除することで解決しました。
ここまで乗り越え、ようやくFlaskとMySQL両方のコンテナが無事起動し、動いている状態にすることができました。
終わりに
Docker, docker-compose, MySQL, Pythonそれぞれの要素で少しずつハマり、結果数時間をコンテナ起動に溶かしてしまいました…
同じようなエラーに出くわした方のなにかの参考になれば幸いです。
ディスカッション
コメント一覧
まだ、コメントがありません