Spring BootアプリケーションをAWSへデプロイする方法を調べていると、Dockerがよく登場します。
例えば、ECS + Fargateを利用する場合は次のような構成です。
Spring Boot
↓
Docker Image
↓
ECR
↓
ECS
↓
Fargate
しかし、ここで疑問が出てきました。
Spring Bootってjarファイルをサーバーに置いて実行すれば動くのでは?
実際、その通りです。
Dockerを使わなくてもSpring Bootアプリケーションは動かせます。
では、なぜDockerを利用するのでしょうか。
この記事では、
「Dockerを使わず直接jarを実行する方法」と「Dockerを利用する方法」の違い
から、Dockerを利用する理由を整理します。
そもそもSpring BootはDockerなしでも動く
Spring Bootアプリケーションはビルドすると、jarファイルとして出力できます。
Mavenなら、例えば次のようにビルドできます。
./mvnw clean package
すると、
target/
└── application.jar
のようなjarファイルが生成されます。
Javaがインストールされたサーバーへjarファイルを配置すれば、
java -jar application.jar
でSpring Bootアプリケーションを起動できます。
つまり、例えばEC2を使うなら、
Internet
|
v
+-------------------+
| EC2 |
| |
| Java 21 |
| application.jar |
| |
| java -jar ... |
+-------------------+
という構成でもアプリケーションを動かせます。
Apache HTTP Serverは必須ではない
Webアプリケーションというと、
Apache HTTP Server
↓
Javaアプリケーション
のような構成を想像するかもしれません。
しかし、一般的なSpring BootのWebアプリケーションでは、TomcatなどのWebサーバーが組み込まれています。
そのため、
java -jar application.jar
だけでHTTPリクエストを受け取れるWebアプリケーションとして起動できます。
つまり、単純な構成なら、
Linux
↓
Java
↓
Spring Boot jar
だけでも動かせます。
では、なぜわざわざDockerを追加するのでしょうか。
Dockerなしの場合は「サーバー側に環境を作る」
Dockerを使わずEC2へ直接デプロイするとします。
まずEC2を作成します。
その後、
EC2を起動
↓
Javaをインストール
↓
必要な設定をする
↓
jarを配置
↓
環境変数を設定
↓
アプリを起動
という作業が必要になります。
例えばアプリケーションがJava 21を必要としているなら、EC2側にも対応するJava環境が必要です。
Spring Boot Application
|
| Java 21が必要
v
EC2
└── Java 21
これは1台だけなら、それほど問題には見えません。
サーバーが増えると環境管理が大変になる
例えば同じアプリケーションを3台のサーバーで動かすとします。
EC2 A
├── Java 21
├── 設定
└── application.jar
EC2 B
├── Java 21
├── 設定
└── application.jar
EC2 C
├── Java 21
├── 設定
└── application.jar
すべて同じ環境にする必要があります。
しかし、長期間運用していると、
EC2 A
Java 21
EC2 B
Java 21
EC2 C
Java 17 ← あれ?
といった環境差分が発生する可能性があります。
ほかにも、
このサーバーだけ設定が違う
このサーバーだけライブラリが古い
開発環境では動いたのに本番では動かない
といった問題が起こり得ます。
Dockerでは「実行環境」をコードとして定義できる
Dockerを利用すると、アプリケーションを実行するための環境をDockerfileに記述できます。
例えばSpring Bootなら、単純化すると次のように書けます。
FROM eclipse-temurin:21-jre
COPY target/application.jar application.jar
ENTRYPOINT ["java", "-jar", "application.jar"]
ここでは、
Java 21の環境を使う
application.jarを配置する
java -jar application.jar で起動する
という情報をDockerfileとして定義しています。
そしてDockerfileからDocker Imageを作成します。
Dockerfile
|
| docker build
v
Docker Image
このDocker Imageを使ってコンテナを起動します。
Docker Imageには実行環境をまとめられる
Docker Imageには、アプリケーションだけでなく、その実行に必要な環境も含められます。
イメージとしては、
+----------------------+
| Docker Image |
| |
| Java Runtime |
| application.jar |
| 起動方法 |
+----------------------+
となります。
そのため、
サーバー側にJava 21をインストールしておいてください
ではなく、
このDocker Imageを実行してください
という形でアプリケーションを扱いやすくなります。
「自分のPCでは動いたのに」を減らせる
開発ではよく、
開発者PC
↓
動いた!
本番サーバー
↓
動かない!
という問題があります。
原因の一つが実行環境の違いです。
例えば、
開発環境
Java 21
本番環境
Java 17
なら、当然問題が発生する可能性があります。
Dockerを使えば、
開発環境
|
v
Docker Image A
テスト環境
|
v
Docker Image A
本番環境
|
v
Docker Image A
のように、同じイメージを各環境で利用できます。
もちろんOSやCPUアーキテクチャ、外部サービスなどの違いまで完全になくなるわけではありません。
それでも、アプリケーションの実行環境を統一しやすくなるのはDockerの大きなメリットです。
Docker Imageを「アプリの配布物」にできる
Dockerを使わない場合、デプロイするものはjarファイルだけではありません。
場合によっては、
jarを配置する
Javaを準備する
起動方法を設定する
必要な依存関係を準備する
といった作業も必要です。
Dockerを使うと、
Docker Image
をアプリケーションの配布単位として扱えます。
例えば、
application:v1
application:v2
application:v3
のようにイメージへタグを付けて管理できます。
AWSならAmazon ECRなどにDocker Imageを保存できます。
Developer
|
| docker build
v
Docker Image
|
| push
v
+------------------+
| ECR |
+------------------+
|
| pull
v
ECS / Fargate
ECS・FargateとDockerの相性が良い
AWSでECS + Fargateを利用する場合、DockerなどのOCI互換コンテナイメージを利用してアプリケーションを実行できます。
例えば、
Spring Boot
↓
Docker Image
↓
ECR
↓
ECS
↓
Fargate Task
という流れです。
利用者は、
このコンテナイメージを使う
CPUはこれくらい
メモリはこれくらい
環境変数はこれ
といったTask Definitionを設定します。
Fargateを利用する場合、コンテナを動かすためのEC2インスタンスを自分で用意する必要はありません。
Taskが壊れても作り直しやすい
Dockerコンテナを使う構成では、
「動いているサーバーを修理する」より「同じイメージから新しいコンテナを作る」
という考え方を取りやすくなります。
例えばECS Serviceで、
Desired Count = 2
としていたとします。
正常時は、
Task A
Task B
の2つが動いています。
Task Aが停止すると、
Task A
↓
停止
ECS Service
「Taskが1つ足りない」
↓
新しいTaskを起動
↓
Task B
Task C
という形で必要なTask数を維持できます。
新しく作られるTaskも、指定されたコンテナイメージを利用します。
つまり、
壊れたサーバーへSSH
↓
原因を調査
↓
その場で修正
だけではなく、
問題のあるTaskを終了
↓
同じImageから新しいTaskを起動
という運用がしやすくなります。
スケールもしやすい
アクセスが増えてTaskを増やしたい場合も、コンテナ化されていると同じ環境を複製しやすくなります。
Docker Image
|
+--> Task A
|
+--> Task B
|
+--> Task C
すべて同じイメージから起動できます。
ALBと組み合わせれば、
+--> Task A
|
Internet --> ALB +--> Task B
|
+--> Task C
という構成も作れます。
アプリケーションを同じ形で複製しやすいことは、コンテナを利用する大きなメリットです。
ではDockerを使えばすべて解決する?
もちろん、そうではありません。
Dockerを導入すると、新しく覚えることも増えます。
例えば、
Dockerfile
Docker Image
Container
Registry
Volume
Network
Image Tag
Docker Compose
などです。
さらにAWSで利用するなら、
ECR
ECS
Task Definition
Fargate
なども理解する必要があります。
つまりDockerを利用すると、
環境を統一・再現・配布しやすくなる代わりに、コンテナを管理するための知識が必要になる
という側面があります。
小規模アプリならjarを直接置いてもいい
Dockerが便利だからといって、
Dockerを使わない
=
間違い
ではありません。
例えば、
小規模なアプリ
サーバー1台
環境も単純
頻繁にデプロイしない
といった場合、
EC2
↓
Java
↓
application.jar
というシンプルな構成の方が扱いやすい場合もあります。
Dockerを導入することで、逆に構成が複雑になる可能性もあります。
重要なのは、
Dockerを使うこと自体を目的にしないこと
だと思います。
EC2 + jarとECS + Fargateを比較する
かなり単純化して比較すると、次のようになります。
| EC2 + jar | ECS + Fargate + Container | |
|---|---|---|
| Java環境 | サーバー側で準備 | Image内に定義可能 |
| サーバー管理 | 必要 | EC2管理は基本不要 |
| 実行環境の統一 | 自分で管理 | Imageで統一しやすい |
| アプリの複製 | サーバー構築が必要 | Taskを増やしやすい |
| デプロイ単位 | jarなど | Container Image |
| 学習コスト | 比較的低い | 比較的高い |
| 構成 | シンプル | 複雑になりやすい |
どちらが絶対的に優れているという話ではありません。
アプリケーションの規模や運用方法によって適切な構成は変わります。
Dockerの本質は「どこでも同じように動かしやすくすること」
Dockerを勉強し始めると、
Dockerfileを書いて
Imageを作って
Containerを起動する
という操作そのものに目が行きがちです。
しかし重要なのは、
なぜコンテナ化するのか
だと思います。
Dockerを利用することで、
アプリケーション
+
実行環境
+
起動方法
を一定の形としてまとめられます。
そして、そのImageを、
開発
↓
テスト
↓
ステージング
↓
本番
と利用できます。
つまり、
「このサーバーを同じ状態にしてください」
ではなく、
「このImageを実行してください」
という形へ変えられるわけです。
まとめ
Spring Bootアプリケーションを動かすために、Dockerは必須ではありません。
JavaがインストールされたEC2へjarを配置して、
java -jar application.jar
でも動かせます。
それでもDockerが利用される理由として、主に次のようなものがあります。
- 実行環境をDockerfileとして定義できる
- 開発・テスト・本番の環境差を減らしやすい
- Container Imageをアプリケーションの配布単位として扱える
- 同じ環境を複製しやすい
- ECS / Fargateなどのコンテナサービスと組み合わせやすい
- Taskが停止しても同じImageから作り直しやすい
一方でDockerを利用すると、コンテナに関する知識や管理も必要になります。
そのため、
Dockerを使う
=
必ず正解
ではありません。
重要なのは、
アプリケーションをどう実行・配布・更新・スケール・運用したいのか
を考え、その手段としてDockerが適しているか判断することです。
Spring Bootはjarだけでも動きます。
そのうえでDockerを利用する最大の理由は、
アプリケーションだけでなく、その実行環境も含めて一定の形にし、同じものを再現・配布・作り直しやすくするため
と考えると、Dockerを使う意味が理解しやすくなります。