Spring BootプロジェクトをDocker上で動かす
概要と前提
タイトルの実現方法を解説していきます。
Spring Initializrで作成したプロジェクトを、Docker上でビルド・起動し、デバッグもできるようにします。データベースとDBクライアントも一緒に立ち上げます。
また、編集環境はVSCodeの拡張機能Remote - Containersを使って用意します。
ひとまずプロジェクトを起動するだけならGitHubリポジトリのREADMEを、構築手順が知りたい場合はこの記事を読んでください。
(GitHubにあげたコード...springboot-mysql-onDocker-withRemotecontainers)
この記事を読むにあたって
- GradleやSpringを扱ったことがある
- Dockerの基礎的な知識がある Docker Composeの基本的な記法がわかる
あたりが前提となります。
記事について指摘や提案があれば、是非コメントください!
プロジェクトのスペック
-
環境
:Docker Desktop for Windows -
サーバー
:Java(Spring Boot) -
プロジェクト管理
:Gradle -
DB
:MySQL -
エディタ
:VSCode(Remote - Containers)
環境構築手順のあらすじは
-
1.実行環境の用意
Docker ComposeでDBとサーバーの実行環境を用意 -
2.Javaプロジェクトの起動
Spring Initializrで作成したプロジェクトをコンテナ上で起動 -
3.VSCodeでコンテナ上のコードを開く
Remote - Containersを使用し、コンテナ内のコード編集などをVSCodeできるようにする -
4.デバッグ
デバッグのための設定を行う
といった流れになります。
ディレクトリ構成
├─.devcontainer # Remote - Containersのconfig
│ devcontainer.json
│
├─docker # Docker関連
│ │ docker-compose.yml
│ │
│ └─mysql # データ永続化のためのディレクトリ
└─spring_prj # Spring Bootプロジェクト本体
1.実行環境の用意
Docker Composeで環境の用意をします。
docker-compose.ymlの用意
構築するサーバーは、MySQL/Java/DBクライアント
用の3つです。
それぞれmysql/java/dbclient
というコンテナ名で用意します。
設定はdocker-compose.yml
の中にすべて一緒に書かれていますが、コンテナごとに分けてみていきます。
MySQL
services:
mysql:
build: ./mysql
container_name: mysql
env_file:
- ./mysql/db.env # MySQL設定ファイル
volumes:
- ./mysql/data:/var/lib/mysql # 実データの永続化
- ./mysql/log:/var/log/mysql # logの永続化
ports:
- 3306:3306
-
volumes:
実データは/var/lib/mysql
、ログは/var/log/mysql
に永続化させてあります。 -
build:
imageの指定ではなく、Dockerfileを用意しています。
FROM mysql:5.7
EXPOSE 3306
# 設定ファイルをコンテナにコピー
COPY ./my.cnf /etc/mysql/my.cnf
# 設定ファイルの権限を変更
RUN chmod 644 /etc/mysql/my.cnf
# データの初期化を行うDDLをコンテナにコピー
COPY ./sql_init /docker-entrypoint-initdb.d
Dockerfileの内容は、docker-compose.ymlでも指定できるものですが、上記のように切り出しています。理由は、ビルドの過程で必要な情報であるからです。
docker-compose.ymlに記載した場合、コンテナへのマウントはビルドが完了した後になります。Dockerfileに記載しておけば、ビルドの過程でファイルを読み取ってくれます。
DBクライアント
dbclient:
image: phpmyadmin/phpmyadmin
container_name: dbclient
environment:
- PMA_ARBITRARY=1 # 任意のサーバーへの接続を許可
- PMA_HOST=mysql # 接続先ホスト名 ここではdbサーバーのコンテナ名を指定
- PMA_USER=root # MySQLの設定と合わせておく
- PMA_PASSWORD=root # MySQLの設定と合わせておく
links:
- mysql
ports:
- 4200:80
volumes:
- ./dbclient/sessions:/sessions
depends_on:
- mysql # 「mysql」の後で起動
DBクライアントツールにはphpMyAdminを採用しました。
DBへの接続設定がメインなので特筆すべき点はないと思います。
ほかにもクライアントツールはありますが、ブラウザで操作でき、作業者ごとにインストールが不要なので採用しました。
Java
java:
image: openjdk:15
container_name: java
env_file:
- ./mysql/db.env # mysqlと同じものを指定
tty: true
working_dir: /app
volumes:
- ../spring_prj:/app # Spring Bootのプロジェクト
ports:
- 8080:8080 # 通常実行
- 5050:5050 # デバッグ用
depends_on:
- mysql # 「mysql」の後で起動
これも特に目新しいことはありません。
~ちょっと動作確認~
ここで一度動作確認します。Javaはまだ動かすものがないので、DBとクライアントに問題がないかを確認します。
次のコマンドでmysql
とdbclient
のコンテナを起動します。
$ cd docker
$ docker-compose up -d dbclient
...
Creating mysql ... done # この表示で起動完了
Creating dbclient ... done
コンテナ名はdbclient
だけで十分です。depends_on
でmysqlを指定しているので、依存先のDBサーバーが勝手に先に立ち上がってくれます。
起動が完了したら、ローカルホストの4200番に接続して、DBが起動しているか、初期データが投入されているか確認してみます。初期データ投入のSQL文は、mysqlコンテナのDockerfileで指定した、docker/mysql/sql_init
以下にあるファイルに記載されています。
01_ddl.sql
でスキーマとテーブルを作成し、02_initial-data.sql
でデータを投入しています。その内容通りに、sample_schema.accountに3つのレコードが登録されていれば確認はOKです。
2.Javaプロジェクトの起動
Spring Initializrでプロジェクト作成
Spring Initializrを使うと、GUIで簡単にSpring Bootのプロジェクトを作成することができます。今回は以下の添付画像のような仕様にしました。
JPAを使います。
適宜入力・選択した後は「GENERATE」を押すとzipがダウンロードされます。
展開したものを、spring_prj
直下に配置します。
コンテナ上でビルド
コンテナ上でビルドします。以下のような流れになります。
まずjavaコンテナを起動します。
$ cd docker
$ docker-compose up -d
...
Creating mysql ... done
Creating dbclient ... done
Creating java ... done
コンテナが起動したらjavaのプロセスに入ります。
$ docker exec -it java /bin/bash
# コンテナが正常に動いていれば、以下のように今いるディレクトリを確認
bash-4.4\# pwd
/app # appディレクトリにいる
すると、コンテナ内のapp
ディレクトリにいるはずです。Gradleでビルドします。
$ sh gradlew build
# ビルドが開始され数分かかる
......
BUILD SUCCESSFUL in *m *s
とりあえずビルドが成功すればOKです。まだコントローラーなど何も用意していないので、何か簡単な機能を用意してからプロジェクトを起動し、動作確認をします。
簡単なDB検索機能を用意する
JPAを使って、検索機能を実装します。
必要なものは、spring_prj/src/main/java/com/example/sample
以下の、下記のファイルたちです。
-
entity/AccountEntity.java
あるテーブルの1レコードを表現するクラス。
そのテーブルのデータをやり取りする上での入れ物にもなります。 -
repository/AccountRepository.java
JpaRepositoryを拡張して実装するインターフェイスで、一つのEntityに対応しています。
この拡張クラスを作るだけで、基本的なCRUDメソッドの実装を省略できます。 -
service/AccountService.java
上記のJpaRepositoryを継承したインターフェイスから、accountテーブルのレコードを全件取得するfindAllメソッドを呼び出します。 -
controller/AccountController.java
上記AccountServiceの検索メソッドを呼び出すコントローラーです。
上記のクラスたちで検索機能が成り立っています。
では、もう一度ビルドして、プロジェクトを起動し動作確認をしましょう。
ビルドと起動
$ sh gradlew build
# …ビルド完了したらjarを実行してアプリケーションを起動
$ java -jar build/libs/sample-0.0.1-SNAPSHOT.jar
起動したら、先ほど実装した検索機能を使って動作確認をします。
APIクライアントがあればそれを使ってもよいですが、下記のようにcurlコマンドで簡単に確認できます。
$ curl http://localhost:8080/showaccount
[{"account_id":1,"email":"mng@hoge","password":"mngpass","user_name":"manager"},
{"account_id":2,"email":"user1@hoge","password":"user1pass","user_name":"MR.user1"},
{"account_id":3,"email":"user2@hoge","password":"user2pass","user_name":"MR.user2"}]
このようにaccountテーブルの全データが表示されればOKです。
3.VSCodeでコンテナ上のコードを開く
ここまでの作業で、コンテナ上でプロジェクトを動かす環境が整いました。なので、順次開発に取り掛かっていきたいところですが、このまま進めていくと大きな難点があることに気が付きます。
現状でコードの編集をしようとすると、ローカルのspring_prj
ディレクトリに配置してあるものを開くことになるとおもいます。しかし、現在ローカル環境にあるのはこのコードだけです。
実物として動いている(ビルド対象となる)コードはコンテナ上にマウントされている方ですし、SDKもコンテナ上に存在し、VSCodeのlanguage serverはそれを参照することができない状態にあります。(※1)これにより以下のことが起き、不便な状態です。
- ライブラリの参照先が見つからない
- コード補完が効かない
- デバッグができない
※1)このことがあまりピンとこない場合は、この辺りを読んでみてください。
Dockerの導入で実行環境の統一ができたのはいいですが、編集環境と実行環境が分離してしまいこのようなことになっています。
以上のことから、環境の用意されているコンテナにマウントしたコードをエディタで開けばそんな不都合は起きないと言えます。そして、それを実現してくれるのがRemote - Containersです。
Remote - Containersを使えば、コンテナ内の環境でコードを編集できるため、ローカルで環境を用意した時と同じように完全な状態でエディタの支援を受けて開発が行えます。
Remote - Containersについてもう少し詳しく知りたい方はこの辺をどうぞ。
では、VSCodeでコンテナ内のファイルを開くための準備を行っていきます。
(一旦アプリケーションを終了して、docker-compose down
でコンテナを停止しておく)
devcontainer.jsonの用意
Remote - Containers向けの設定ファイルを用意します。
場所は.devcontainer/devcontainer.json
です。
{
"name": "remote-java", // 任意の名前
"dockerComposeFile": "../docker/docker-compose.yml", // DockerComposeFileを指定
"service": "java", // DockerComposeFileにあるservice名を指定
"workspaceFolder": "/app", // コンテナに入ったときの作業ディレクトリ
"settings": {
"terminal.integrated.defaultProfile.linux": "bash" // bashでターミナルを起動
},
"extensions": [ // コンテナ内で使いたい拡張機能
"vscjava.vscode-java-pack", // Java関連の拡張機能パック
"pivotal.vscode-boot-dev-pack", // Spring Boot関連の拡張機能パック
- "gabrielbb.vscode-lombok", // (2022/07/22修正 記事コメント参照)
+ "vscjava.vscode-lombok"
]
}
"extensions"
に記載するのはExtension ID
というものですが、VSCodeで拡張機能のページを開いたときにアイコンの右にある歯車のマークをコピーすると、Copy Extension ID
の選択肢が出るのでそこでコピーできます。
また、Extensionsの検索窓にExtension IDを入れても検索できます。それぞれどんなものが入っているか見ておくといいとおもいます。
Remote - Containersで開く
設定ファイルの用意ができたので、いよいよコンテナの環境をVSCodeで開きます。
まずは通常通りにコンテナを立ち上げます。
# dockerディレクトリに移動してから
$ docker-compose up -d
......
Creating mysql ... done
Creating java ... done
立ち上がったら、VSCodeのウインドウ左下の、緑色のマークをクリックします。
すると選択肢が出るので、Attach to Running Container
を選択します。
また選択肢がでるので、/java
を選択します。
これで、今動いているjavaコンテナの環境がVSCodeで開かれました!
(初回だけ開くディレクトリを聞かれるかもしれません。その場合は/app
と指定してOKを押して下さい。)
4.デバッグ
今はRemote - Containersでコンテナ内のコードが展開されている状態です。ターミナルも同じくです。なので、通常実行する場合は、上記手順でやったようにjarを実行すればOKです。
さて、デバッグの方法ですが、これもローカルでやることと一緒です。まずはGradleの設定から。以下を既存のものに追記します。
Gradleの設定
デバッグモードで実行できるように以下のように追記します。
jar { // plan.jarは出力しない
enabled = false
}
bootJar { // archiveFileNameで一項目にまとめることもできる
archiveBaseName = "sample"
version = "0.0.1"
archiveClassifier = 'SNAPSHOT'
archiveExtension = 'jar'
}
bootRun { //debug用にgradleからJVMへ引数を渡す
systemProperties = System.properties // gradleのシステムプロパティをjavaに渡す
// 上記の記述で、以下の引数がjdkに渡される
jvmArgs=["-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5050"]
}
bootJar
内では実行jarの名前を設定しています。build/libsに指定した名前で出力されます。
bootRun
内ではbootRunタスク実行時に、Javaに渡す引数を指定しています。後でbootRun実行したときにこのオプションが付加されます。
引数のうち重要なのは
-
suspend=y
でデバッガーがアタッチされるまで起動を待つ -
address=*:5050
でhost:port
を指定
の二点です。
suspend=n
にしておけばデバッガーを待たずにアプリケーションが立ち上がります。また、ホストの指定(今回は*)は必須で省略すると起動できません。
VSCodeの設定
次に、 spring_prj/.vscode/launch.json
を用意します。
VSCodeのデバッガ構成を記すファイルです。
{
"version": "0.2.0",
"configurations": [
{
"type": "java", // 言語を指定
"name": "java (Attach)", // 任意の名前
"request": "attach", // すでに起動しているプログラムにアタッチ
"hostName": "java", // コンテナ名を指定
"port": 5050 // 接続するポートを指定
}
]
}
デバッグしてみる
まずビルドしなおしておきます。
$ sh gradlew clean
...
BUILD SUCCESSFUL
$ sh gradlew build
...
BUILD SUCCESSFUL
プロジェクトを起動します。
$ sh gradlew bootRun
VSCodeでデバッグビューを開くと、先ほど指定したjava (Attach)
という名前のデバッグ構成がプルダウンの一覧にあるはずです。
それを選択した状態で開始ボタンを押します。すると、アプリケーションもデバッグモードで起動し、デバッグができるようになりました。
AccountService.javaなどにブレークポイントを貼って、上記でやったようにcurlコマンドをたたいたりしてちゃんとデバッグできるか確認しておきます。
終わるときは…
赤いボタンでデバッガーを切り離し、ctrl+Cでアプリケーションを停止します。
コンテナ画面の閉じ方は、開くときに押した緑色のボタンを押し、Close Remote Connection
を選択すると終了します。
おわり
以上のような手順でプロジェクトを立ち上げました。
指摘・提案のコメント歓迎です!
感想
DockerでサーバーやDBの実行環境を用意したりGradleでビルド設定書くのは初めてだけど頑張りました。環境構築であれこれつまずいて開発作業に入れないのがつらい…。でもこれでフロント、サーバー、DBとそろったのでやっと開発できる。
以前書いたReactの環境構築に続いて、自分が忘れないためのメモとして書いてますが、拙いなりに一部でも参考になってたら嬉しいです。
Discussion
いつのまにかLombokの拡張機能のIDが
vscjava.vscode-lombok
に変わったみたいですね