概要
DBコンテナ(MySQL)とApplicationコンテナ(SpringBoot)の2つをdocker-compose.ymlにて管理しようとしました。ビルド実行すると、Applicationコンテナのビルドに失敗したため、以下のエラーについての解決方法を調査検討しました。
エラー内容
Caused by: java.net.UnknownHostException: db: Name or service not known
databaseのmigrationにFlywayを使っていたので、以下のようなエラーも出ていました。
Unable to obtain connection from database: Communications link failure
Error creating bean with name ‘flywayInitializer’ defined in class path resource
最終結果
いろいろ試したのですが、先に最終的に設定したフォルダ構成とコンテナ設定ファイルを載せます。
ソースコード
├── docker-compose.yml
├── mysql(DBコンテナ)
│ ├── Dockerfile
│ ├── data
│ └── settings
└── spring(Appコンテナ)
├── Dockerfile
└── demo
FROM mysql:8.0.28-debian
EXPOSE 3306
FROM openjdk:11
WORKDIR /app
EXPOSE 8080
COPY ./demo /app/
CMD ["sh", "-c", "mvn clean package && java -jar /app/target/demo-0.0.1-SNAPSHOT.jar"]
version: '3.9'
services:
db:
container_name: db
build:
context: ./mysql
ports:
- 3306:3306
command: --default-authentication-plugin=mysql_native_password
volumes:
- ./mysql/settings:/etc/mysql/conf.d/
- ./mysql/data/:/var/lib/mysql/
env_file:
- .env
healthcheck:
test: ["CMD", "mysqladmin" ,"ping", "-h", "${MYSQL_HOST}", "-u${MYSQL_USER}", "-p${MYSQL_PASSWORD}"]
timeout: 20s
retries: 10
networks:
- app-net
app:
build:
context: ./spring
ports:
- 8080:8080
restart: always
depends_on:
db:
condition: service_healthy
networks:
- app-net
networks:
app-net:
driver: bridge
MYSQL_DATABASE=<DB名>
MYSQL_USER=<DBユーザー名>
MYSQL_PASSWORD=<DBパスワード>
MYSQL_HOST=<DBホスト名>
エラーの原因分析と切り分け
ここからは、上記エラーとして考えられる原因、原因切り分けのためにやるべきこと、原因に対する解決方法をまとめます。エラーの原因として以下の3パターンが考えられます。
- dbコンテナが立ち上がる前にアクセスしている
- dbコンテナとappコンテナが同一のネットワークに存在していない
- アプリケーション側のDB設定が正しくない
以下では、それぞれの詳細を述べます。
1. dbコンテナが立ち上がる前にアクセスしている
切り分け
appコンテナが起動できるならば、execで中に入ってmvn clean package
をしてみましょう。正常にビルドされたらコンテナの実行タイミングが問題です。私はビルド時にテスト実行されてDBアクセス不良でエラーになっていたため、テストをスキップすることでコンテナ起動しました。
- RUN mvn clean package
+ RUN mvn -B -DskipTests clean package
解決法
力技で解決してしまった感があるため、正しい解決法があればご教示ください。以下の1-1, 1-2の実施が必要です。
1-1. docker-compose.ymlのdepends_onプロパティを利用する
docker-compose.ymlにdepends_on
プロパティを設定することで、dbコンテナがreadyになったらappコンテナを実行するように設定します。
service_healthy
を設定することで、db serviceのヘルスチェックが成功してからapp serviceを起動させることができます。
version: '3.9'
services:
db:
+ healthcheck:
+ test: ["CMD", "mysqladmin" ,"ping", "-h", "${MYSQL_HOST}", "-u${MYSQL_USER}", "-p${MYSQL_PASSWORD}"]
+ timeout: 20s
+ retries: 10
app:
+ depends_on:
+ db:
+ condition: service_healthy
注意1
docker-composeのversion3以降ではdepends_on の条件形式はサポートされなくなりました。しかし、version3.9以降で復活しています。
適切なversionを設定してください。
https://github.com/docker/compose/issues/8154
注意2
Docker Composeのバージョンが古いとversion3.9が利用できない可能性があります。aptからのインストールはバージョンが古かったので、公式レポジトリからv.1.29.2を落としてインストールしました。(Compose v2はdocker composeコマンドになるため、LocalStackが使えず上記バージョンにしました。問題ない方は最新バージョンでもよいと思います)
https://github.com/docker/compose/releases
.envファイルにDB接続情報を記載してください。DBホスト名はdocker-compose.ymlのservice名(今回であればdb)です。
1.2. CMDでビルド実行する
上記対応でコンテナの起動順は制御できましたが、Dockerイメージのビルドプロセス(RUNコマンドなど)は変わりません。つまり、RUNで定義しているmvn clean package
はMySQLコンテナが起動していなくても実行されてしまうのです。したがって、このコマンドをCMDで実行します。
- RUN mvn clean package
- ENTRYPOINT ["java","-jar","/app/target/demo-0.0.1-SNAPSHOT.jar"]
+ CMD ["sh", "-c", "mvn clean package && java -jar /app/target/demo-0.0.1-SNAPSHOT.jar"]
(補足)CMDとRUNコマンドの違い
RUN:DockerfileからImageを作成するときに一度だけ実行されます
CMD:Docker ImageからDockerコンテナを作成するときに実行されます
2. dbコンテナとappコンテナが同一のネットワークに存在していない
切り分け
appコンテナが起動できるならば、execで中に入ってpingを飛ばします。
今回は失敗したところの直前までビルドして、execして以下を実行しました。
※ dbはdocker-compose.ymlのservice名です
apt-get update && apt-get install iputils-ping
ping db
レスポンスが返ってきていなければ、network設定を見直しましょう。
解決法
2つのcontainerを同一networkに入れましょう。
参考:https://docs.docker.jp/compose/networking.html
services:
db:
+ networks:
+ - app-net
app:
+ networks:
+ - app-net
+ networks:
+ app-net:
+ driver: bridge
3. アプリケーション側のDB設定が正しくない
SpringBootであれば、application.propertiesのdatasource.url
, datasource.username
, datasource.password
が正しく設定されていない可能性があります。
切り分け
Dockerの問題なのかを確かめます。ローカルにMySQLやSpringBoot環境を作って実行してみて、正しく動かなければこの可能性が高いです。ただ、そこまでする前にもう一度設定を見直すのが良いと思います。
解決法
正しく設定しましょう。
参考:https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html
spring.datasource.url=jdbc:mysql://<DBホスト名>:3306/<DB名>
spring.datasource.username=<DBユーザー名>
spring.datasource.password=<DBパスワード>