🧑‍💻

M1 DockerでMySQLを含んだ環境を動かすためにやったこと

2020/12/27に公開

この記事を簡単に整理すると

新規開発で使っていた環境が動かなくなった(´・ω・`)

現在副業で刺さっている現場で、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版のものが存在していません。
https://hub.docker.com/_/mysql?tab=tags&page=1&ordering=last_updated

代替として、mysql公式が用意している、mysql-serverイメージを使います。こちらはarm版もあります。
https://hub.docker.com/layers/mysql/mysql-server/8.0.20/images/sha256-1ad56c783589bece03225650c0929b8f34a8b39527902bdced3dc5ea286d5669?context=explore

つまり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では、サブネット設定が可能です。これで固定化してしまえば良さげです。
https://runnable.com/docker/docker-compose-networking#custom-networks

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