M1 DockerでMySQLを含んだ環境を動かすためにやったこと
この記事を簡単に整理すると
新規開発で使っていた環境が動かなくなった(´・ω・`)
現在副業で刺さっている現場で、Rails使った新規プロダクト開発をやっています。
そこではDocker使って環境を構築しています。実運用環境ではAmazon RDSを使うため、MySQLのバージョンも合わせて8.0.20で構築しています。
これまで私はWindows上のWSLで開発を進めてきましたが、タイトルどおり某PCが発売されてしまった訳です。もともとチームでは私だけLinux(WSL)、他の人Macでの開発をしていたため、2通りの環境で動くように整備をしていました。ただ、最近その差分を吸収するのが手間になってました。となんやかんやしているうちにM1 Macで、DeveloperビルドではありますがDockerが動くようになったため、購入したわけです。
事前に環境設定して、一通りDockerコマンドの動作を確認した後、特に問題はなかったのでそのままdocker-compose build
を叩いたわけですね
8.0.20: Pulling from library/mysql
no matching manifest for linux/arm64/v8 in the manifest list entries
Dockerオフィシャルイメージのmysqlはarm版が存在しない
問題が発生したときに参照しているイメージはDockerのオフィシャルイメージを使っています。
services:
db:
image: mysql:8.0.20
エラーが示す通り、このオフィシャルイメージにはarm版のものが存在していません。
代替として、mysql公式が用意している、mysql-serverイメージを使います。こちらはarm版もあります。
つまりM1の場合は、使うイメージだけ変えてあげれば対応可能です。
services:
db:
image: mysql/mysql-server:8.0.20
単純に差し替えるだけでは解決しなかった
イメージを差し替えて動作確認をしたとき、DBへ接続するタイミングで下記のエラーが表示されます。
Host '172.19.0.4' is not allowed to connect to this MySQL server
てっきり、my.cnfで、bind-adress指定漏れかと思い確認したのですが、しっかり設定済みでした。
[mysqld]
bind-address=0.0.0.0
character-set-server=utf8mb4
[client]
default-character-set=utf8mb4
もともとのイメージではこれだけで解決できていたのですが、切り替え版だともうひと手間必要でした。
回避策
最初はmy.cnfの設定だけでなんとかできないか調査していましたが、not allowed to connect
を解決することができず、結局ホスト名指定を有効にしたユーザーをMySQLに追加する方法を取ることになりました。
つまり
Create USER root@'172.19.%';
GRANT ALL PRIVILEGES ON *.* TO root@'172.19.%' WITH GRANT OPTION;
を実行できれば良い。とはいえ都度環境を作るときに手作業で実行はしたくないわけです。当然こちらも自動で実行するようにしてほしい。それもコンテナ作成時に自動で実行してほしい。
これを叶えるためには2つの対応を行う必要があります。
- Docker側のネットワークのサブネットを固定化する
-
docker-entrypoint-initdb.d
を活用する
Docker側のネットワークのサブネットを固定化する
docker-composeをそのまま立ち上げると、docker networkのサブネット設定に従って環境が立ち上がります。これは(おそらく)各PCでバラバラになるかと思います。どういったサブネット設定がされているかは、下記コマンドを実行するとわかります。
docker network inspect bridge
{
"Name": "bridge",
...
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16"
MySQLにホスト名指定ありユーザーを追加時、このサブネットの範囲のものを設定しなければならないわけですね。しかし、環境ごとに異なるサブネット設定になる可能性がある以上、固定化されていることが望まれます。
docker-compose.ymlでは、サブネット設定が可能です。これで固定化してしまえば良さげです。
networks
キーを追加して下記の設定を追加してしまえばOK。docker-compose
で普段使っているネットワークに対し、固定したいサブネットを入力します。
networks:
default:
ipam:
config:
- subnet: 172.18.0.0/16
今回は、172.18の先頭部分が変動しないように、16bitでレンジを指定しておきます。
docker-entrypoint-initdb.d
を活用する
MySQL系のDockerイメージには、コンテナ作成時に任意のスクリプトを走らせてくれる機能が書かれています。今回使うmysql/mysql-serverの場合、
https://hub.docker.com/r/mysql/mysql-server/dockerfile の
RUN mkdir /docker-entrypoint-initdb.d
が該当します。このフォルダに中に実行したいファイル(sql, sh)を格納しておけば、コンテナを立ち上げるタイミングで、DockerfileのENTRYPOINTとしてentrypoint.sh
を呼び出します。その中で、/docker-entrypoint-initdb.d/*
配下のスクリプトが順番に実行されていきます。
COPY docker-entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
# https://github.com/mysql/mysql-docker/blob/mysql-server/8.0/docker-entrypoint.sh#L149-L156
for f in /docker-entrypoint-initdb.d/*; do
case "$f" in
*.sh) echo "[Entrypoint] running $f"; . "$f" ;;
*.sql) echo "[Entrypoint] running $f"; "${mysql[@]}" < "$f" && echo ;;
*) echo "[Entrypoint] ignoring $f" ;;
esac
echo
done
最終的な対応
# 1_allow-host-ip.sql として保存
Create USER root@'172.18.%';
GRANT ALL PRIVILEGES ON *.* TO root@'172.18.%' WITH GRANT OPTION;
service:
db:
image: mysql/mysql-server:5.7
...
volumes:
- ./1_allow-host-ip.sql:/docker-entrypoint-initdb.d/1_allow-host-ip.sql
...
networks:
default:
ipam:
config:
- subnet: 172.18.0.0/8
Discussion