string(4) "blog"

CONTENTS

2021.5.29

M1 MacでDockerを使った開発環境構築にハマった

written by 山本 竜二
M1-Docker

会社で支給されているPCが、M1チップのMacBookAirになりました。(以後、M1 Macと呼びます)

まずは私一人が人柱になり、自社プロダクトを含め開発に支障が無いかを検証しています。

M1 Macは、Xcodeはもちろんの事、Visual Studio Code(VSCode)など大抵のアプリが動きますが、Dockerを使った開発環境構築は思うようにいきません。

2021/4/15にDocker Desktop for Apple silicon という、M1 Mac(Apple Silicon)に正式対応したバージョンが提供され、何でも動くようになったのかと期待してました。

ところが実際に使ってみると、3つのDockerコンテナのうち、2つが動きませんでした。

やはりプラットフォームに依存するところは対応に時間が掛かるようですね。

試行錯誤しながら何度も「もう無理・・。ローカル環境でDockerなんて使わなくても仕事はできるやん」と別の環境を模索した事もありました。

例えば、AWS Cloud9(クラウド上のIDE)。

Cloud9上にローカル同様のDocker環境を構築するのは、とくにハマる事も無くサクっと終わりました。でも、いざ開発しようとすると、頻繁に接続が切れるし、そもそもブラウザ上で開発するなんて個人的にありえません。

VS Codeの拡張機能でCloud9に接続する事もできますが、やはり頻繁に接続が切れて、仕事に集中できないです。(私のネット環境の問題かもしれません)

やっぱり、頑張ってローカルでDocker使えるようにした方がいいですよね。

あぁ・・そもそも私は人柱だった事を忘れて楽な方に逃げていた事に気づきました。

という事で、今回はDockerについてあまり詳しくない私が、自社プロダクトの開発環境を構築する際に遭遇した色々な問題と対応のお話になります。

開発環境の構成

自社プロダクトの開発環境をザックリ書くと、以下の構成になります。

フロントエンド

  • Dockerイメージ:centos:7
  • JS環境:Node.js 12
  • 起動:NG

バックエンド

  • Dockerイメージ:node:12
  • JS環境:Node.js 12
  • 起動:OK

データベース

  • Dockerイメージ:mysql:5.7.12
  • 起動:NG

上記の3コンテナをdocker-composeでビルドしています。

docker-compose.ymlの中身

version: "3"

services:
  hoge-db:
    build: ./db
    image: hoge-db
    container_name: hoge-db
    ports:
      - 9906:3306
    volumes:
      - ./db/data:/docker-entrypoint-initdb.d
  hoge-api:
    build: ./api
    image: hoge-api
    container_name: hoge-api
    links:
      - hoge-db:mysql
    volumes:
      - /{ローカルパス}/api:/api
    ports:
      - 9999:9999
    command: >
      sh -c '
        cd /api
        npm install
        npm rebuild node-sass
        npm run gulp
      '
  hoge-app:
    build: ./app
    image: hoge-app
    container_name: hoge--app
    links:
      - hoge-api:api
    volumes:
      - /{ローカルパス}/app:/app
    ports:
      - 8080:8080
    privileged: true
    command: >
      sh -c '
        /sbin/init
        cd /app
        npm install
        npm rebuild node-sass
        npm run dev
      '

※ 一部、公開用に編集しています。

それぞれのDockerfileの中身は以下の通りです。

フロントエンド

FROM centos:7

RUN yum -y install curl
RUN curl --silent --location <https://rpm.nodesource.com/setup_10.x> | bash -
RUN yum -y install nodejs
RUN npm install -g n
RUN n 12.20.1

バックエンド

FROM node:12.20.1
EXPOSE 9999

DB

FROM mysql:5.7.12

ENV MYSQL_ROOT_PASSWORD=hogehoge

COPY ./conf/my.cnf /etc/mysql/conf.d/my.cnf

バックエンドは特に何も対応せずとも動いたので、フロントエンドとDBについての問題と対応を見ていきます。

フロントエンドの対応

まずはフロントエンドで行った対応についてです。

Centos7と、アプリのビルドに使っているnode-sassで問題が発生するようです。

環境は先程も書きましたが、このような構成です。

  • Dockerイメージ:centos:7
  • JS環境:Node.js 12

エラー内容の確認

では、どのようなエラーが発生するか、docker-composeでビルドを行ってみます。

$ docker-compose up -d

フロントエンドに関するログを取得します。

$ docker-compose logs hoge-app
〜省略〜
hoge-app    | 
hoge-app    | Downloading binary from <https://github.com/sass/node-sass/releases/download/v4.14.1/linux-arm64-72_binding.node>
hoge-app    | Cannot download "<https://github.com/sass/node-sass/releases/download/v4.14.1/linux-arm64-72_binding.node>": 
hoge-app    |
〜省略〜

node-sassという外部モジュールのM1 Mac版(linux-arm64)が見つからないようです。

これに対応するには、node-sassのIntel版(x86_64?)がダウンロードされるようにする必要がありそうです。

platformを指定する (NG)

Dockerはマルチプラットフォームに対応していて、プラットフォームを指定して動かす事が可能です。

では、docker-compose.ymlでplatformを指定してみます。

〜省略〜
hoge-app:
    build: ./app
    image: hoge-app
    platform: linux/amd64
〜省略〜

※ platformの指定方法を詳しく理解していませんが、 linux/amd64 とすればIntel版になるようです。

もう一度ビルドしてみます。今回はDockerイメージを変更しているのでイメージもビルドし直します。

$ docker-compose build
$ docker-compose up -d
〜省略〜
Building hoge-app
[+] Building 33.2s (7/9)                                                                                                                       
 => [internal] load build definition from Dockerfile                                                                                      0.0s
 => => transferring dockerfile: 268B                                                                                                      0.0s
 => [internal] load .dockerignore                                                                                                         0.0s
 => => transferring context: 2B                                                                                                           0.0s
 => [internal] load metadata for docker.io/library/centos:7                                                                               1.8s
 => [1/6] FROM docker.io/library/centos:7@sha256:0f4ec88e21daf75124b8a9e5ca03c37a5e937e0e108a255d890492430789b60e                         7.4s
 => => resolve docker.io/library/centos:7@sha256:0f4ec88e21daf75124b8a9e5ca03c37a5e937e0e108a255d890492430789b60e                         0.0s
 => => sha256:e4ca2ed0202e76be184e75fb26d14bf974193579039d5573fb2348664deef76e 529B / 529B                                                0.0s
 => => sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf 2.75kB / 2.75kB                                            0.0s
 => => sha256:2d473b07cdd5f0912cd6f1a703352c82b512407db6b05b43f2553732b55df3bc 76.10MB / 76.10MB                                          5.0s
 => => sha256:0f4ec88e21daf75124b8a9e5ca03c37a5e937e0e108a255d890492430789b60e 1.20kB / 1.20kB                                            0.0s
 => => extracting sha256:2d473b07cdd5f0912cd6f1a703352c82b512407db6b05b43f2553732b55df3bc                                                 2.0s
 => [2/6] RUN yum -y install curl                                                                                                        21.9s
 => [3/6] RUN curl --silent --location <https://rpm.nodesource.com/setup_10.x> | bash -                                                     0.7s
 => ERROR [4/6] RUN yum -y install nodejs                                                                                                 1.2s
------
 > [4/6] RUN yum -y install nodejs:
#7 0.727 Loaded plugins: fastestmirror, ovl
#7 0.955 Loading mirror speeds from cached hostfile
#7 0.958  * base: ftp.riken.jp
#7 0.960  * extras: ftp.riken.jp
#7 0.961  * updates: ftp.riken.jp
#7 1.109 No package nodejs available.
#7 1.180 Error: Nothing to do
------
executor failed running [/bin/sh -c yum -y install nodejs]: exit code: 1
ERROR: Service 'hoge-app' failed to build : Build failed

エラー内容が変わりましたが、 node.jsをインストールするところでエラーが発生しているようです。

ここは色々と試して見ましたが解決に至らず、諦めて次のステップに進みました。

※根本的な解決になっていなくてスイマセン

CentOS7→CentOS8に変更(OK)

ちょっと卑怯な気もしますが、色々と試しても解決しなかったので、CentOSのバージョンを7→8にアップしてみます。

フロントエンド用のDockerfile

#FROM centos:7
FROM centos:8

RUN yum -y install curl
RUN curl --silent --location <https://rpm.nodesource.com/setup_10.x> | bash -
RUN yum -y install nodejs
RUN npm install -g n
RUN n 12.20.1

docker-compose.ymlのplatform指定は前回のままとします。

〜省略〜
hoge-app:
    build: ./app
    image: hoge-app
    platform: linux/amd64
〜省略〜

ではビルドをしてみます。今回もDockerイメージを変更しているのでイメージもビルドし直します。

$ docker-compose build
$ docker-compose up -d

ログを確認します。

$ docker-compose logs hoge-app                                        
Attaching to hoge-app, hoge-api, hoge-db
hoge-app    | System has not been booted with systemd as init system (PID 1). Can't operate.
hoge-app    | Failed to connect to bus: Host is down
〜省略〜
hoge-app    | > node-sass@4.14.1 install /app/node_modules/node-sass
hoge-app    | > node scripts/install.js
hoge-app    | 
hoge-app    | node-sass build Binary found at /app/node_modules/node-sass/vendor/linux-x64-72/binding.node
hoge-app    | 
hoge-app    | > node-sass@4.14.1 postinstall /app/node_modules/node-sass
hoge-app    | > node scripts/build.js
〜省略〜
hoge-app    | [12:27:56] Server started <http://localhost:8080>
hoge-app    | [12:27:56] LiveReload started on port 35729

無事に起動しました!

CentOS 7で動かせなかったのは残念ですが、アプリの動作に影響する事は無いでしょう。(希望的観測)

DBの対応

次にDBで行った対応についてです。

MySQL5.7を使っていますが、これがM1 Macに対応していないようです。

環境は先程も書きましたが、このような構成です。

  • Dockerイメージ:mysql:5.7.12

エラー内容の確認

では、こちらもどのようなエラーが発生するか、docker-composeでビルドを行ってみます。

$ docker-compose up -d
Docker Compose is now in the Docker CLI, try `docker compose up`

Creating network "docker_default" with the default driver
Building hoge-db
[+] Building 1.5s (5/6)                                                                                                                        
 => [internal] load build definition from Dockerfile                                                                                      0.0s
 => => transferring dockerfile: 206B                                                                                                      0.0s
 => [internal] load .dockerignore                                                                                                         0.0s
 => => transferring context: 2B                                                                                                           0.0s
 => [internal] load metadata for docker.io/library/mysql:5.7.12                                                                           1.1s
 => [internal] load build context                                                                                                         0.0s
 => => transferring context: 180B                                                                                                         0.0s
 => ERROR [1/2] FROM docker.io/library/mysql:5.7.12@sha256:85a69965ab9cffd7bbfa70ac2dc2f2915859a91de7377fa4b59b363a9fe8c5d3               0.0s
 => => resolve docker.io/library/mysql:5.7.12@sha256:85a69965ab9cffd7bbfa70ac2dc2f2915859a91de7377fa4b59b363a9fe8c5d3                     0.0s
------
 > [1/2] FROM docker.io/library/mysql:5.7.12@sha256:85a69965ab9cffd7bbfa70ac2dc2f2915859a91de7377fa4b59b363a9fe8c5d3:
------
failed to load cache key: invalid empty config file resolved for docker.io/library/mysql:5.7.12@sha256:85a69965ab9cffd7bbfa70ac2dc2f2915859a91de7377fa4b59b363a9fe8c5d3
ERROR: Service 'hoge-db' failed to build : Build failed

Dockerイメージを取ってくる段階でエラーが発生しました。

うーん。。これもM1 Mac版が存在しないという事でしょうか。

platformを指定する (NG)

docker-compose.ymlでplatformを指定してみます。

〜省略〜
hoge-db:
    build: ./db
    image: hoge-db
    platform: linux/amd64
    container_name: hoge-db
〜省略〜

もう一度ビルドしてみます。

$ docker-compose up -d
Docker Compose is now in the Docker CLI, try `docker compose up`

Building hoge-db
[+] Building 2.3s (5/6)                                                                                                                        
 => [internal] load build definition from Dockerfile                                                                                      0.0s
 => => transferring dockerfile: 37B                                                                                                       0.0s
 => [internal] load .dockerignore                                                                                                         0.0s
 => => transferring context: 2B                                                                                                           0.0s
 => [internal] load metadata for docker.io/library/mysql:5.7.12                                                                           2.0s
 => ERROR [1/2] FROM docker.io/library/mysql:5.7.12@sha256:85a69965ab9cffd7bbfa70ac2dc2f2915859a91de7377fa4b59b363a9fe8c5d3               0.0s
 => => resolve docker.io/library/mysql:5.7.12@sha256:85a69965ab9cffd7bbfa70ac2dc2f2915859a91de7377fa4b59b363a9fe8c5d3                     0.0s
 => [internal] load build context                                                                                                         0.0s
 => => transferring context: 56B                                                                                                          0.0s
------
 > [1/2] FROM docker.io/library/mysql:5.7.12@sha256:85a69965ab9cffd7bbfa70ac2dc2f2915859a91de7377fa4b59b363a9fe8c5d3:
------
failed to load cache key: invalid empty config file resolved for docker.io/library/mysql:5.7.12@sha256:85a69965ab9cffd7bbfa70ac2dc2f2915859a91de7377fa4b59b363a9fe8c5d3
ERROR: Service 'hoge-db' failed to build : Build failed

プラットフォームを指定しても同じエラーになります。

MySQL5.7 → MySQL Server5.7に変更 (OK)

海外の情報で、「MySQL5.7はM1 Macに対応していないから、MySQL Server5.7を使うといいよ。」というネタを発見しました。

DBのDockerfileを修正してMySQL Server5.7のイメージを使うようにします。

DB用のDockerfile

#FROM mysql:5.7.12
FROM mysql/mysql-server:5.7.28

ENV MYSQL_ROOT_PASSWORD=hogehoge

COPY ./conf/my.cnf /etc/mysql/conf.d/my.cnf

docker-compose.ymlのplatform指定は前回のままとします。

〜省略〜
hoge-db:
    build: ./db
    image: hoge-db
    platform: linux/amd64
    container_name: hoge-db
〜省略〜

ではビルドをしてみます。Dockerイメージを変更していますが、そもそもイメージのビルドに失敗しているので、コンテナのビルドだけ行います。

$ docker-compose up -d

ログを確認します。

$ Attaching to hoge-app, hoge-api, hoge-db
hoge-db     | [Entrypoint] MySQL Docker Image 5.7.28-1.1.13
hoge-db     | [Entrypoint] Initializing database
hoge-db     | [Entrypoint] Database initialized
hoge-db     | Warning: Unable to load '/usr/share/zoneinfo/iso3166.tab' as time zone. Skipping it.
hoge-db     | Warning: Unable to load '/usr/share/zoneinfo/leapseconds' as time zone. Skipping it.
hoge-db     | Warning: Unable to load '/usr/share/zoneinfo/tzdata.zi' as time zone. Skipping it.
hoge-db     | Warning: Unable to load '/usr/share/zoneinfo/zone.tab' as time zone. Skipping it.
hoge-db     | Warning: Unable to load '/usr/share/zoneinfo/zone1970.tab' as time zone. Skipping it.
hoge-db     | 
hoge-db     | [Entrypoint] Server shut down
hoge-db     | 
hoge-db     | [Entrypoint] MySQL init process done. Ready for start up.
hoge-db     | 
hoge-db     | [Entrypoint] Starting MySQL 5.7.28-1.1.13

無事にビルドされました。

動作確認

ようやくDockerコンテナが全て起動したので、ここからはフロント、バックエンド、DBの動作確認をします。

APIからDBへの疎通に失敗

ブラウザを立ち上げて、フロントからバックエンド、DBへと繋がる事を確認してみると、APIからDBへの疎通に失敗していました。

バックエンドのログを確認してみます。

hoge-api    | ConnectionError [SequelizeConnectionError]: Host '172.18.0.3' is not allowed to connect to this MySQL server
hoge-api    |     at /api/node_modules/sequelize/lib/dialects/mysql/connection-manager.js:149:19
hoge-api    | From previous event:
hoge-api    |     at ConnectionManager.connect (/api/node_modules/sequelize/lib/dialects/mysql/connection-manager.js:136:13)
hoge-api    |     at /api/node_modules/sequelize/lib/dialects/abstract/connection-manager.js:338:50
hoge-api    | From previous event:
hoge-api    |     at ConnectionManager._connect (/api/node_modules/sequelize/lib/dialects/abstract/connection-manager.js:338:8)
hoge-api    |     at ConnectionManager.getConnection (/api/node_modules/sequelize/lib/dialects/abstract/connection-manager.js:266:46)
hoge-api    |     at /api/node_modules/sequelize/lib/sequelize.js:557:34

バックエンドのコンテナから、MySQL Serverへの接続が許可されていない事が分かります。

MySQL5.7の時はこのエラーが発生していなかったので、MySQL Server5.7に変更した事でセキュリティが厳しくなったものと考えられます。

MySQLに接続してユーザーの権限を確認してみます。

$ docker-compose exec hoge-db bash
bash-4.2# mysql -u root -p
Enter password: 
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
mysql> select user, host from mysql.user;
+---------------+-----------+
| user          | host      |
+---------------+-----------+
| healthchecker | localhost |
| mysql.session | localhost |
| mysql.sys     | localhost |
| root          | localhost |
+---------------+-----------+
4 rows in set (0.01 sec)

ローカル開発環境では、rootユーザーを使ってバックエンドから接続したいのですが、DBコンテナの外からのアクセスが禁じられています。(localhostしか許可していない)

そこで、rootユーザーは何処からでもアクセスできるようにします。

コンテナに入って、直接権限周りの変更をしても良いですが、DBコンテナを作り直した時に、同じ作業をやりたくありません。環境構築をできる限り自動化するのは重要だと考えます。

MySQLのDockerには初期スクリプトを実行する仕組みがあるので、これを使って対応します。

docker-compose.ymlを見てみましょう。

〜省略〜
  hoge-db:
    build: ./db
    image: hoge-db
    container_name: hoge-db
    ports:
      - 9906:3306
    volumes:
      - ./db/data:/docker-entrypoint-initdb.d
〜省略〜

volumesでローカル環境のフォルダ(./db/data)を、/docker-entrypoint-initdb.dにマウントしています。

MySQLのDockerは、/docker-entrypoint-initdb.dにあるSQLファイルを初期化スクリプトとして読み込んで実行してくれます。

以下のファイルを、db/dataフォルダに作成します。

ファイル名は拡張子がsqlになっていれば何でも良いです。複数ある場合はファイル名の順に実行されます。今回は、db/data/01_init_db_for_m1mac.sql としておきます。

use mysql;
truncate table user;
flush privileges;
create user 'root' identified by 'hogehoge';
grant all privileges on *.* to 'root';
flush privileges;

MySQLのUserテーブルを一度全削除して後、rootユーザーを作成し、どこからでも入れる権限を与えるという事をしています。

初期化スクリプトを反映させます。普通にビルドしても反映されなかったので、キャッシュを使わずにイメージをビルドし直しました。

$ docker-compose build --no-cache hoge-db
$ docker-compose up -d

念の為、Dockerコンテナに入って、MySQLの状態を確認しておきます。

$ docker-compose exec hoge-db bash
bash-4.2# mysql -u root -p
Enter password: 
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
mysql> select user, host from mysql.user;
+------+------+
| user | host |
+------+------+
| root | %    |
+------+------+
1 row in set (0.01 sec)

rootユーザーがどこからでもアクセス可能になっている事が確認できました。

MySQL Server側の設定だけなのでDockerの再起動は不要です。

これでバックエンドからDBへの接続が問題無く行えるようになり、今回の対応はすべて完了という事になりました。

最後に

Dockerの仕組みに詳しくなかった為かなり苦労しましたが、何とかDockerを使ったローカル開発環境を構築する事ができました。

M1 Mac周りの環境は日々整備されていて、今回のような対応が不要となる日も近いかもしれませんが、同じようにハマっている方のお役に立てれば幸いです。

一覧に戻る
お問合わせ