こんにちは。ただいまAWS CDK絶賛勉強中です。
ちょっと前に書いた上記の記事では、AWS CDKをつかってECSを構築しました。その際は 公式のnginxのイメージをつかって、静的なhtmlを表示させるところまでやりましたが、こんどはSpringBootのイメージを自分で作成して、それを動かすようにしてみます。
SpringBootのサンプルコード自体は、遠い昔に
でHelloWorld的なモノを作成したので、そのソースを流用します1。
前提条件
今回は
- SpringBootアプリをMavenから起動
- SpringBootアプリをまるごとjar化して起動
- そのjarを内包したDocker イメージを作成して、ローカルのDocker上で起動
って流れで進めるので、Java、Maven、Docker などが動く環境が必要です。
構築方法は割愛しますが、たとえば下記のサイトあたりが良さそうです。
- Java
うまくいかないときは
$ sudo apt update
$ sudo apt upgrade -y
とかするとイイかも。
- Maven
- Docker
今回も記事の流れ上WSL上で動かしてみようと思いますが、結果的に、
$ java --version
openjdk 17.0.8.1 2023-08-24
OpenJDK Runtime Environment (build 17.0.8.1+1-Ubuntu-0ubuntu122.04)
OpenJDK 64-Bit Server VM (build 17.0.8.1+1-Ubuntu-0ubuntu122.04, mixed mode, sharing)
$ mvn --version
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 17.0.8.1, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: en, platform encoding: UTF-8
OS name: "linux", version: "5.15.90.1-microsoft-standard-wsl2", arch: "amd64", family: "unix"
$ docker --version
Docker version 24.0.6, build ed223bc
$
とかなっていればOKです2。
やってみる
ソースの取得から、Maven経由の起動まで
$ git clone --branch 0.0.3 https://github.com/masatomix/spring-boot-sample-tomcat.git
$ cd spring-boot-sample-tomcat
spring-boot-sample-tomcat$ mvn spring-boot:run
。..
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.1.2)
2023/10/02 13:29:08 INFO [background-preinit] - HV000001: Hibernate Validator 8.0.1.Final
2023/10/02 13:29:08 INFO [main] - Starting SampleTomcatApplication using Java 17.0.8.1 with PID 2572 (/tmp/spring-boot-sample-tomcat/target/classes started by sysmgr in /tmp/spring-boot-sample-tomcat)
...
2023/10/02 13:29:13 INFO [main] - Tomcat started on port(s): 8080 (http) with context path ''
2023/10/02 13:29:13 INFO [main] - Started SampleTomcatApplication in 5.827 seconds (process running for 6.655)
起動できたらアクセスしてみましょう。
$ curl http://localhost:8080/actuator/health -i
HTTP/1.1 200
Content-Type: application/vnd.spring-boot.actuator.v3+json
Transfer-Encoding: chunked
Date: Mon, 25 Sep 2023 15:17:50 GMT
{"status":"UP"}
$
よさそうですね。
SpringBootアプリをjar化して起動
つづいてSpringBootアプリを jar化して起動してみます。jar化は mvnのコマンドをつかいます。
spring-boot-sample-tomcat$ mvn package
...
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------< nu.mine.kino:spring-boot-sample-tomcat >---------------
[INFO] Building spring-boot-sample-tomcat 0.1.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------< nu.mine.kino:spring-boot-sample-tomcat >---------------
[INFO] Building spring-boot-sample-tomcat 0.1.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from spring-snapshots: https://repo.spring.io/snapshot/org/apache/maven/plugins/maven-surefire-plugin/3.0.0/maven-surefire-plugin-3.0.0.pom
....
[INFO] --- maven-jar-plugin:3.3.0:jar (default-jar) @ spring-boot-sample-tomcat ---
[INFO] Building jar: /tmp/spring-boot-sample-tomcat/target/app.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:3.1.2:repackage (repackage) @ spring-boot-sample-tomcat ---
[INFO] Replacing main artifact /tmp/spring-boot-sample-tomcat/target/app.jar with repackaged archive, adding nested dependencies in BOOT-INF/.
[INFO] The original artifact has been renamed to /tmp/spring-boot-sample-tomcat/target/app.jar.original
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6.138 s
[INFO] Finished at: 2023-10-02T13:31:18+09:00
[INFO] ------------------------------------------------------------------------
spring-boot-sample-tomcat$ ls -lrt ./target/
total 36896
...
-rw-r--r-- 1 sysmgr sysmgr 37739139 Oct 2 13:31 app.jar
spring-boot-sample-tomcat$
jar化できました。ではこのjarを使ってアプリを起動してみます。
spring-boot-sample-tomcat$ java -jar ./target/app.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.1.2)
2023/10/02 13:33:23 INFO [background-preinit] - HV000001: Hibernate Validator 8.0.1.Final
2023/10/02 13:33:23 INFO [main] - Starting SampleTomcatApplication v0.1.1-SNAPSHOT using Java 17.0.8.1 with PID 2694 (/tmp/spring-boot-sample-tomcat/target/app.jar started by sysmgr in /tmp/spring-boot-sample-tomcat)
...
2023/10/02 13:33:30 INFO [main] - Tomcat started on port(s): 8080 (http) with context path ''
2023/10/02 13:33:30 INFO [main] - Started SampleTomcatApplication in 8.088 seconds (process running for 8.647)
よさそうですね。さっきみたいにcurlでアクセスしてみてください。
jarを内包したDockerイメージを作成してローカルのDocker上で起動
最後にDockerです。先ほど取ってきたソース群に以下のDockerfile がありますので、
FROM openjdk:17-alpine
VOLUME /tmp
COPY target/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]
これを使って Dockerのイメージを作成します。ちなみに、ココで作成したDockerのイメージは、最終的にECSにデプロイされるモノとなります。
spring-boot-sample-tomcat$ ECR_REPOSITORY_NAME=spring-boot-sample-tomcat
spring-boot-sample-tomcat$ version=0.1.1-SNAPSHOT ← 便宜上 pom.xmlのバージョンとそろえておく
spring-boot-sample-tomcat$ docker image build -t myorg/${ECR_REPOSITORY_NAME}:${version} .
...
Sending build context to Docker daemon 38MB
Step 1/5 : FROM openjdk:17-alpine
---> 264c9bdce361
Step 2/5 : VOLUME /tmp
---> Using cache
---> 881669e9c06a
Step 3/5 : COPY target/app.jar app.jar
---> Using cache
---> 1d8cd0cd0381
Step 4/5 : EXPOSE 8080
---> Using cache
---> 8f18719b124c
Step 5/5 : ENTRYPOINT ["java","-jar","/app.jar"]
---> Using cache
---> 84d2e22f35bb
Successfully built 84d2e22f35bb
Successfully tagged myorg/spring-boot-sample-tomcat:0.1.1-SNAPSHOT
spring-boot-sample-tomcat$ docker images
myorg/spring-boot-sample-tomcat 0.1.1-SNAPSHOT 84d2e22f35bb 2 minutes ago 363MB
spring-boot-sample-tomcat$
イメージができたようなので、起動します。
spring-boot-sample-tomcat$ docker container run --rm -p 8080:8080 \
--name app \
myorg/${ECR_REPOSITORY_NAME}:${version}
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.1.2)
2023/10/02 04:42:14 INFO [background-preinit] - HV000001: Hibernate Validator 8.0.1.Final
2023/10/02 04:42:15 INFO [main] - Starting SampleTomcatApplication v0.1.1-SNAPSHOT using Java 17-ea with PID 1 (/app.jar started by root in /)
2023/10/02 04:42:15 DEBUG [main] - Running with Spring Boot v3.1.2, Spring v6.0.11
2023/10/02 04:42:15 INFO [main] - No active profile set, falling back to 1 default profile: "default"
...
2023/10/02 04:42:28 INFO [main] - Tomcat started on port(s): 8080 (http) with context path ''
2023/10/02 04:42:28 INFO [main] - Started SampleTomcatApplication in 16.476 seconds (process running for 18.073)
起動できたようですね!curlで確認もやっておきましょう。
蛇足: TIPSというか、サンプルの使い道
環境構築をやっていて「構築うまくいったかな?テストアプリをうごかして確認してみよ」なんてときありますよね。今回動かしてみたアプリは、そんなときのテストアプリとして使えたり、ほかにもこの機能どうつかうんだっけ?みたいなのをサンプルとして実装したりしています。
蛇足ですがその辺の備忘メモです。
ヘルスチェックにつかう
先の確認で
$ curl http://localhost:8080/actuator/health -i
HTTP/1.1 200
Content-Type: application/vnd.spring-boot.actuator.v3+json
Transfer-Encoding: chunked
Date: Mon, 25 Sep 2023 15:17:50 GMT
{"status":"UP"}
$
などとしたとおり、ヘルスチェックのサンプルを組み込んでます。というかこのヘルスチェックは spring-boot-actuator という便利ライブラリの1機能です。spring-boot-actuator はそのほかにも、環境変数を表示したり(/actuator/env
)、Loggerの定義情報を表示・変更したり(/actuator/loggers
)など、さまざまな機能を持っています。
参考: https://spring.pleiades.io/spring-boot/docs/current/reference/html/actuator.html
プロファイル機能で、環境ごとに設定を切り替える
SpringBoot標準の機能ですが、アプリ起動時に--spring.profiles.active
オプションを指定して、プロファイルを切り替えることができます。具体的には、
- 通常の起動の場合
$ java -jar ./target/app.jar --spring.profiles.active=dev
- Docker上で起動する場合
$ docker container run --rm -p 8080:8080 \
-e --spring.profiles.active=dev \
--name app \
myorg/${ECR_REPOSITORY_NAME}:${version}
などプロファイル名「dev
」などを渡してあげることで、環境設定ファイルを切り替えることができます。
上記「dev
」の場合、通常読み込まれる設定ファイル/src/main/resources/application.properties
にくわえ、プロファイル名に従って/src/main/resources/application-dev.properties
も読み込まれます。各設定ファイルに同じプロパティ名の情報があった場合はdev側が優先される、そんな機構です。
jarやDockerイメージにあらかじめ各プロファイル用の設定ファイルを同梱しておいて、先の起動オプションでプロファイルを切り替えることで、環境によって異なる設定でアプリを起動できるってことですね。なるほど3。。
ログのフォーマットを環境によって変更したい
先のプロファイル機構の延長で、Loggerライブラリの挙動も切り替える方法です。
取得したソースコード内のLogger の設定ファイル /src/main/resources/logback-spring.xml
をみてみると、
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="logFileName" value="app" />
<!--ローカル環境用設定 -->
<springProfile name="default">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<encoder>
<charset>UTF-8</charset>
<pattern>%d{yyyy/MM/dd HH:mm:ss} %-5level [%thread] - %msg%n</pattern>
</encoder>
</appender>
</springProfile>
<!--develop環境用設定 -->
<springProfile name="dev">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<encoder>
<charset>UTF-8</charset>
<pattern>%d{yyyy/MM/dd HH:mm:ss} %-5level [%thread] - %msg%n</pattern>
</encoder>
</appender>
</springProfile>
<!--production環境用設定 -->
<springProfile name="production">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<pattern>[%-5level] %c{0} %date{yyyy/MM/dd HH:mm:ss.SSS} %msg%n</pattern>
</encoder>
</appender>
</springProfile>
<root>
<appender-ref ref="STDOUT" />
</root>
</configuration>
となっています。 <springProfile name="dev">
、 <springProfile name="production">
などの要素がありますが、先のプロファイル名にしたがって、読まれるLoggerの設定が切り替わるようになっています。
プロファイル名が「dev
」の場合は 通常のLogger
<springProfile name="dev">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<encoder>
<charset>UTF-8</charset>
<pattern>%d{yyyy/MM/dd HH:mm:ss} %-5level [%thread] - %msg%n</pattern>
</encoder>
</appender>
</springProfile>
が設定され、プロファイル名が「production
」の場合は
<springProfile name="production">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<pattern>[%-5level] %c{0} %date{yyyy/MM/dd HH:mm:ss.SSS} %msg%n</pattern>
</encoder>
</appender>
</springProfile>
net.logstash.logback.encoder.LogstashEncoder
というJSON形式でログ出力する設定が適用されます。
実際のログはこんな感じです。
プロファイル名が「dev
」の場合
2023/10/04 16:49:47 INFO [http-nio-8080-exec-4] - hostname: 4d1ad5e6f9f7
2023/10/04 16:49:47 INFO [http-nio-8080-exec-4] - Default Message.
プロファイル名が「production
」の場合
{"@timestamp":"2023-10-04T16:51:38.530015247Z","@version":"1","message":"hostname: 78ef74fd7fde","logger_name":"nu.mine.kino.web.EchoController","thread_name":"http-nio-8080-exec-4","level":"INFO","level_value":20000}
{"@timestamp":"2023-10-04T16:51:38.533395968Z","@version":"1","message":"Default Message.","logger_name":"nu.mine.kino.web.EchoController","thread_name":"http-nio-8080-exec-4","level":"INFO","level_value":20000}
こんなふうに、開発などの通常時はログを普通に出力し、production環境(AWS本番とかです) ではJSON形式で構造的にログを出力することができたりします。開発時にJSON形式のログだと普通に読みづらいわけですが、本番のAWS上ではログをJSONで出せたりすると何かと便利4だったりするので、このような切替機能はとっても便利ですね。
次回は先ほど作成したDockerイメージを ECRにアップロードして、ECSから利用してみます。
以上お疲れさまでした!
関連リンク
- AWS CDKのTIPS集
- AWS CDK で Infrastructure as Code する: VPC編
- AWS CDK で Infrastructure as Code する: EC2編
- AWS CDK で Infrastructure as Code する: ECS編
-
参考 https://github.com/masatomix/spring-boot-sample-tomcat.git ↩
-
Javanのバージョンは17以上じゃないとSpringBoot起動時にエラーになっちゃいました ↩
-
プロファイル名が未指定の場合はプロファイル名は「
default
」となり/src/main/resources/application.properties
が読み込まれます。 ↩ -
AWS上のログを操作する機構( fluentbit とか) をもちいて、JSONの特定のプロパティを用いた処理ができたりします。たとえばJSONのプロパティ
loglevel
が「ERROR」のヤツだけを取り出して、そのログはCloudWatchに送ってアラームを発報するとか、そんな感じです。時間をみていつか記事にしようと思います。 ↩