ISUCON9 予選問題用のMySQLをDockerで立てる

これはなに

isucon.net ISUCON9予選問題の環境構築をするために上記の公式記事を参考にぽちぽちやっていた最中、 mysqlサーバーをホストに立てるのが環境を汚しそうで嫌だったので、Dockerで立ててみることにしました。

職場でdocker-compose upくらいはするものの、ちゃんと自分でDockerで何かを構築するのは初めてだったので いろいろ調べながらとりあえず課題のWebアプリが普通に動く最低ラインを目指してやったことの作業ログです。

環境

MacOS Catalina MacBook Pro 2017 3.1GHz デュアルコアIntel Corei5 メモリ 16GB

手順

CLIから起動

docker runで、imageからcontainerを起動する。
imageはdocker hubからダウンロードしてくる。

$ docker run --name cli-mysql -e MYSQL_ROOT_PASSWORD=pass -d mysql:latest

ref. mysql - Docker Hub

Starting a MySQL instance is simple: bash $ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag ... where some-mysql is the name you want to assign to your container, my-secret-pw is the password to be set for the MySQL root user and tag is the tag specifying the MySQL version you want. See the list above for relevant tags.

$ docker ps

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                 NAMES
9e3b15e64c77        mysql:latest        "docker-entrypoint.s…"   5 seconds ago       Up 4 seconds        3306/tcp, 33060/tcp   cli-mysql

起動はしており、3306と33060でmysqldの待ち受けはしているが、portのバインドができていないのでローカルからの接続はできない。

docker run --name cli-mysql -e MYSQL_ROOT_PASSWORD=pass -p 3306:3306 -d mysql:latest

--publish , -p : Publish a container’s port(s) to the host

ref. docker run | Docker docs

$ docker ps

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                 NAMES
9e3b15e64c77        mysql:latest        "docker-entrypoint.s…"   5 seconds ago       Up 4 seconds        0.0.0.0:3306->3306/tcp, 33060/tcp   cli-mysql

公開されたようだ。接続してみる。

$ mysql --port=3306 --host=127.0.0.1 --user=root -p

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.19 MySQL Community Server - GPL

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

OK!

DockerfileからImageを作って起動

docker build PATH | URLでimageを自作する。 PATH(a directory on your local filesystem)やURL(Git repository location)はcontextと呼ばれる。
docker buildは、Docker deamonにcontext内のファイル(.dockerignoreで指定されたファイルは除く)を送信し、contextルートに配置されたDockerfileの手順にしたがって、Docker deamon内でimageをbuildする。(そのため、buildに必要のないファイルはきちんと.dockerignoreで指定しておいた方がbuildの速度は早くなる)

Docker deamonは、Dockerfileに記載された命令文を1つずつ独立した状態で実行しながら新しいimageを都度作っていき、最終的に得られたimageを出力する。 DockerfileのCOPY命令などでcontext内のファイルを参照しつつbuildを行う。

FROM mysql:latest
ENV MYSQL_ROOT_PASSWORD=pass
$ docker build .

Successfully built 855977a52a9d

$ docker images

REPOSITORY                     TAG                 IMAGE ID            CREATED             SIZE
<none>                         <none>              855977a52a9d        7 seconds ago       547MB

$ docker run --name file-mysql -p 3306:3306 -d 855977a52a9d # -eオプションの指定が消えた
$ mysql --port=3306 --host=127.0.0.1 --user=root -p

imageのREPOSITORY, TAGの指定は docker build -t ${REPOSITORY}:${TAG}で行う。Dorckerfileに記述するものではない。

$ docker build -t moeka-m/mysql:latest .
$ docker images

EPOSITORY                     TAG                 IMAGE ID            CREATED             SIZE
moeka-m/mysql                  latest              855977a52a9d        16 hours ago        547MB

初期データを自動で読み込むように設定

Initializing a fresh instance

When a container is started for the first time, a new database with the specified name will be created and initialized with the provided configuration variables. Furthermore, it will execute files with extensions .sh, .sql and .sql.gz that are found in /docker-entrypoint-initdb.d. Files will be executed in alphabetical order. You can easily populate your mysql services by mounting a SQL dump into that directory and provide custom images with contributed data. SQL files will be imported by default to the database specified by the MYSQL_DATABASE variable.

ref. mysql - Docker Hub

containerを起動させると、/docker-entrypoint-initdb.dに配置された.sh,.sql,.sql.gzをアルファベット順に実行してDababaseを初期化しますよ、の意。

FROM mysql:latest
ENV MYSQL_ROOT_PASSWORD pass
# 文字コードの指定がないとinitial.sqlの実行で Data too long for column と言われて失敗する
ENV LANG C.UTF-8
COPY . /docker-entrypoint-initdb.d

データの初期投入をするshellは要らなくなったので.dockerignoreに書いておく

init.sh

これで実行してみる。

$ docker build -t moeka-m/mysql:latest .
$ docker run --name file-mysql -p 3306:3306 -d moeka-m/mysql:latest
$ mysql --port=3306 --host=127.0.0.1 --user=isucari -p
mysql > SHOW TABLES FROM isucari;
+-----------------------+
| Tables_in_isucari     |
+-----------------------+
| categories            |
| configs               |
| items                 |
| shippings             |
| transaction_evidences |
| users                 |
+-----------------------+
6 rows in set (0.02 sec)

mysql> SELECT id, name, price FROM isucari.items LIMIT 1;
+----+---------------------------------------------------------------------------------------------------------------------+-------+
| id | name                                                                                                                | price |
+----+---------------------------------------------------------------------------------------------------------------------+-------+
|  1 | 浮くことなく世界中の4段階採用したっぷり入れロースタイルをスタッキング曲げ木の                                       |   100 |
+----+---------------------------------------------------------------------------------------------------------------------+-------+
1 row in set (0.00 sec)

Webappから接続

Hostの環境変数に接続情報を入れておく

$ export MYSQL_HOST=127.0.0.1
$ export MYSQL_PORT=3306
$ export MYSQL_USER=isucari
$ export MYSQL_DBNAME=isucari
$ export MYSQL_PWD=isucari

あとは冒頭リンクの記事通りにWebアプリを起動すると無事に起動!

後記

ちなみにこの状態でベンチマーカーを起動させると

2020/03/28 21:59:01 main.go:180: === final check ===
2020/03/28 21:59:01 main.go:212: 410 0
{"pass":true,"score":410,"campaign":0,"language":"Go","messages":[]}

これくらいのスコアになりました。 公式記事では初期スコアでも3020くらいいってたのでだいぶ遅いです。やっぱdockerで立てると遅くなるのかも。