はじめに
この記事では、OpenShiftへのデプロイを目的として、Spring BootとOpen Libertyによるシンプルなアプリケーションを作成します。デプロイ先のOpenShift環境は下記の記事で紹介しています。
1. Visual Studio Code準備
アプリケーションはWindows 10のVisual Studio Code(VScode)で作成します。
事前にJavaやGradleと以下のVSCode用プラグインを導入しておきます。
- Java Extension Pack
- Spring Boot Extension Pack
1.1. Spring Bootプロジェクトの作成
(1) コマンドパレットから「Spring Initializer: Create a Gradle Project..」を選択します。
(2) Javaを選択します。
(3) Group Idを「dummy」とします。
(4) Artifact Idを「spring-liberty」とします。
(5) packaging typeを「War」にします。
(6) Java versionを「11」にします。
(7) dependenciesとして「Spring Web」と「Thymeleaf」を選択します。
1.2. ソースコード作成
下表のソースコードを「src/main」以下のディレクトリに追加します。
ディレクトリ | ファイル | 用途 |
---|---|---|
java/dummy/springliberty | SpringLibertyController.java | 要求「/」にindex.htmlを応答。 要求「/healthz」にhealthz.htmlを応答。 |
resources/templates | index.html | ユーザーエージェントとホスト名を表示。 |
resources/templates | healthz.html | OKを表示。 |
package dummy.springliberty;
import java.net.InetAddress;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.ui.Model;
@Controller
public class SpringLibertyController {
@RequestMapping("/")
public String index(Model model, @RequestHeader("User-Agent") String userAgent) {
model.addAttribute("userAgent", userAgent);
try {
InetAddress ia = InetAddress.getLocalHost();
model.addAttribute("hostName", ia.getHostName());
} catch (Exception e) {
e.printStackTrace();
}
return "index";
}
@RequestMapping("/healthz")
public String healthz() {
return "healthz";
}
}
<html><head><title>Spring Boot + Liberty</title></head><body>
<table border="1" style="font-size: 20pt" cellpadding="10">
<tr><th>Version</th><th>20210716_01</th></tr>
<tr><th>User Agent</th><th><p th:text="${userAgent}"></p></th></tr>
<tr><th>Pod</th><th><p th:text="${hostName}"></p></th></tr>
</table>
</body></html>
OK
1.3. Open Liberty用Gradleプラグイン追加
Open Liberty開発支援プラグインを使用するためにbuild.gradleを編集します。
plugins {
id 'org.springframework.boot' version '2.5.2'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'io.openliberty.tools.gradle.Liberty' version '3.2'
id 'java'
id 'war'
}
group = 'dummy'
version = ''
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
libertyRuntime group: 'io.openliberty', name: 'openliberty-runtime', version: '21.0.0.7'
}
test {
useJUnitPlatform()
}
liberty {
server {
name = 'defaultServer'
deploy {
apps = [file('build/libs/spring-liberty-plain.war')]
}
}
}
clean.dependsOn 'libertyStop'
2. Spring Bootアプリケーションのビルドと実行
2.1. Spring Bootアプリケーションのビルド
『gradle build』によりコンパイルされ「build/libs/spring-liberty-plain.war」が作成されます。
gradle build
### 標準出力↓
Starting a Gradle Daemon, 2 busy and 3 incompatible and 1 stopped Daemons could not be reused, use --status for details
BUILD SUCCESSFUL in 8s
7 actionable tasks: 7 up-to-date
2.2. Open Liberty上でアプリケーションを実行
『gradle libertyRun』により以下の処理が実行されます。
- build.gradleで指定されたバージョンのOpen Libertyが存在しないときはダウンロードする。
- Open Libertyのディレクトリ「build/wlp」を作成する。
- src/main/liberty/server.xmlを「build/wlp/user/servers/defaultServer」にコピーする。
- build/libs/spring-liberty-plain.warを「build/wlp/usr/servers/defaultServer/apps」にコピーする。
- Open Liberty をフォアグラウンドで実行する。
gradle libertyRun
### 標準出力↓
> Task :deploy
Application spring-liberty-plain.war was installed as a file as specified. To install as a loose application, specify the plugin or task generating the archive.
> Task :installFeature
> Task :libertyRun
OpenJDK 64-Bit Server VM バージョン 11.0.11+9-LTS (ja_JP) で、defaultServer (Open Liberty 21.0.0.7/wlp-1.0.54.cl210720210629-1900) を起動しています
[監査 ] CWWKE0001I: サーバー defaultServer が起動されました。
[監査 ] CWWKZ0058I: アプリケーションの dropins をモニター中です。
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.ibm.ws.util.ThreadContextAccessor (file:/C:/workspace/source/spring-liberty/build/wlp/lib/com.ibm.ws.container.service_1.0.54.jar) to field java.lang.Thread.contextClassLoader
WARNING: Please consider reporting this to the maintainers of com.ibm.ws.util.ThreadContextAccessor
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
[監査 ] CWWKT0016I: Web アプリケーションが使用可能です (default_host): http://xxx.xxx.xxx.xxx:9080/
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.2)
2021-08-07 10:03:09.803 INFO 11352 --- [cutor-thread-22] dummy.springliberty.ServletInitializer : Starting ServletInitializer using Java 11.0.11 on mark2 with PID 11352 (C:\workspace\source\spring-liberty\build\wlp\usr\servers\defaultServer\apps\expanded\spring-liberty-plain.war\WEB-INF\classes
started by root in C:\workspace\source\spring-liberty\build\wlp\usr\servers\defaultServer)
2021-08-07 10:03:09.803 INFO 11352 --- [cutor-thread-22] dummy.springliberty.ServletInitializer : No active profile set, falling back to default profiles: default
2021-08-07 10:03:10.649 INFO 11352 --- [cutor-thread-22] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 796 m
2021-08-07 10:03:11.092 INFO 11352 --- [cutor-thread-22] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page template: index
2021-08-07 10:03:11.244 INFO 11352 --- [cutor-thread-22] dummy.springliberty.ServletInitializer : Started ServletInitializer in 1.996 seconds (JVM running for 10.896)
[監査 ] CWWKZ0001I: アプリケーション spring-liberty-plain が 4.134 秒で開始しました。
[監査 ] CWWKF0012I: サーバーは次のフィーチャーをインストールしました。[el-3.0, jsp-2.3, servlet-3.1]。
[監査 ] CWWKF0011I: defaultServer サーバーは、Smarter Planet に対応する準備ができました。defaultServer サーバーは 10.786 秒で始動しました。
<?xml version="1.0" encoding="UTF-8"?>
<server description="new server">
<!-- Enable features -->
<featureManager>
<feature>jsp-2.3</feature>
</featureManager>
<!-- To access this server from a remote client add a host attribute to the following element, e.g. host="*" -->
<httpEndpoint id="defaultHttpEndpoint" host="*" httpPort="9080" />
<!-- Automatically expand WAR files and EAR files -->
<applicationManager autoExpand="true"/>
<webApplication contextRoot="/" location="spring-liberty-plain.war" />
</server>
また、『gradle libertyDev』した場合は、Open Libertyがバックグラウンドで実行され、Javaのソースコードが更新されると自動的にOpen Libertyが再起動するようになります。
Open Libertyを起動した状態でブラウザでhttp://localhost:9080/
を開くと以下のような画面が表示されます。OpenShiftにデプロイしていないので、Pod名ではなくホスト名が表示されます。
※上記ではOpen Libertyで「build/libs/spring-liberty-plain.war」を実行しています。「build/libs/spring-liberty.war」に含まれるTomcatでアプリケーションを実行することも可能です。
■ 起動方法
java -jar build/libs/spring-liberty.war
■ URL
http://localhost:8080/
2.3. GitHubの利用
ソースコードをGitHubに公開しました。gitコマンドとJava 11が導入されていれば、下記の手順でSpring BootアプリケーションをOpen Liberty上で実行することがでできます。Gradleも自動的にダウンロードされます。
https://github.com/y-akio/source.git
git clone https://github.com/y-akio/source.git
cd source/spring-liberty
chmod +x gradlew
./gradlew build
### 標準出力↓
Downloading https://services.gradle.org/distributions/gradle-7.1.1-bin.zip
..........10%...........20%...........30%..........40%...........50%...........60%..........70%...........80%...........
90%...........100%
Starting a Gradle Daemon (subsequent builds will be faster)
> Task :compileJava
> Task :processResources
> Task :classes
> Task :bootWarMainClassName
> Task :bootWar
> Task :war
> Task :assemble
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses
> Task :test
> Task :check
> Task :build
BUILD SUCCESSFUL in 1m 18s
7 actionable tasks: 7 executed
./gradlew libertyRun
### 標準出力↓
> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :bootWarMainClassName UP-TO-DATE
> Task :bootWar UP-TO-DATE
> Task :installLiberty
> Task :libertyCreate
> Task :war UP-TO-DATE
> Task :deploy
Application spring-liberty.war was installed as a file as specified. To install as a loose application, specify the plugin or task generating the archive.
> Task :installFeature
> Task :libertyRun
OpenJDK Server VM バージョン 11.0.11+9-LTS (ja_JP) で、defaultServer (Open Liberty 21.0.0.7/wlp-1.0.54.cl210720210629-1900) を起動しています
[監査 ] CWWKE0001I: サーバー defaultServer が起動されました。
[監査 ] CWWKZ0058I: アプリケーションの dropins をモニター中です。
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.ibm.ws.util.ThreadContextAccessor (file:/tmp/source/spring-liberty/build/wlp/lib/com.ibm.ws.container.service_1.0.54.jar) to field java.lang.Thread.contextClassLoader
WARNING: Please consider reporting this to the maintainers of com.ibm.ws.util.ThreadContextAccessor
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
[監査 ] CWWKT0016I: Web アプリケーションが使用可能です (default_host): http://haproxy:9080/
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.2)
2021-08-07 10:23:03.049 INFO 14369 --- [ecutor-thread-4] dummy.springliberty.ServletInitializer : Starting ServletInitializer using Java 11.0.11 on haproxy with PID 14369 (/tmp/
source/spring-liberty/build/wlp/usr/servers/defaultServer/apps/expanded/spring-liberty-plain.war/WEB-INF/classes started by root in /tmp/source/spring-liberty/build/wlp/usr/servers
/defaultServer)
2021-08-07 10:23:03.067 INFO 14369 --- [ecutor-thread-4] dummy.springliberty.ServletInitializer : No active profile set, falling back to default profiles: default
2021-08-07 10:23:08.991 INFO 14369 --- [ecutor-thread-4] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 5318 ms
2021-08-07 10:23:11.566 INFO 14369 --- [ecutor-thread-4] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page template: index
2021-08-07 10:23:12.485 INFO 14369 --- [ecutor-thread-4] dummy.springliberty.ServletInitializer : Started ServletInitializer in 12.681 seconds (JVM running for 35.805)
[監査 ] CWWKZ0001I: アプリケーション spring-liberty-plain が 24.971 秒で開始しました。
[監査 ] CWWKF0012I: サーバーは次のフィーチャーをインストールしました。[el-3.0, jsp-2.3, servlet-3.1]。
[監査 ] CWWKF0011I: defaultServer サーバーは、Smarter Planet に対応する準備ができました。defaultServer サーバーは 35.766 秒で始動しました。
3. OpenShiftへのデプロイ
DockerfileによりOpen Liberty上でSpring Bootアプリケーションを実行するコンテナのイメージを作成し、コンテナイメージからデプロイメント「spring-liberty」を作成します。
3.1. コンテナイメージのビルド
OpenShiftでコンテナイメージをビルドします。結果としてイメージストリームが作成されます。
ls -l
### 標準出力
-rw-r--r--. 1 root root 139 8月 7 10:22 Dockerfile
-rw-r--r--. 1 root root 669 8月 7 10:22 server.xml
-rw-r--r--. 1 root root 13138834 8月 7 10:06 spring-liberty-plain.war
-rw-r--r--. 1 root root 1097 8月 7 10:22 spring-liberty.yaml
oc new-project spring-liberty
oc new-build --name=spring-liberty --strategy=docker --binary
oc start-build spring-liberty --from-dir=. --follow
### 標準出力↓
Uploading directory "." as binary input for the build ...
.
Uploading finished
build.build.openshift.io/spring-liberty-4 started
Receiving source from STDIN as archive ...
Caching blobs under "/var/cache/blobs".
Pulling image open-liberty:21.0.0.7-full-java11-openj9 ...
・・・
STEP 1: FROM open-liberty:21.0.0.7-full-java11-openj9
STEP 2: COPY server.xml /config/
--> bb126b57c0d
STEP 3: COPY spring-liberty-plain.war /config/apps/
--> 5b9a3b886cd
STEP 4: USER 1001
--> fde4d2230c0
STEP 5: EXPOSE 9080
・・・
Successfully pushed image-registry.openshift-image-registry.svc:5000/spring-liberty/spring-liberty@sha256:1ab62ce3019906
cf896185b701053189d01528603f55c7004f103b469354a672
Push successful
oc get is
### 標準出力↓
NAME IMAGE REPOSITORY TAGS UPDATED
spring-liberty image-registry.openshift-image-registry.svc:5000/spring-liberty/spring-liberty latest 7 seconds ago
3.2. コンテナのデプロイ
デプロイメント「spring-liberty」としてコンテナをデプロイします。
oc apply -f spring-liberty.yaml
oc get pod,svc,ing
### 標準出力↓
NAME READY STATUS RESTARTS AGE
pod/spring-liberty-1-build 0/1 Completed 0 2m28s
pod/spring-liberty-84c44ffc5b-24w8n 0/1 Running 0 25s
pod/spring-liberty-84c44ffc5b-js6cg 0/1 Running 0 25s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/spring-liberty ClusterIP 172.30.162.86 <none> 80/TCP 25s
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/spring-liberty <none> spring-liberty.apps.ocp.cloud.vpc router-default.apps.ocp.cloud.vpc 80 25s
FROM open-liberty:21.0.0.7-full-java11-openj9
COPY server.xml /config/
COPY spring-liberty-plain.war /config/apps/
EXPOSE 9080
<?xml version="1.0" encoding="UTF-8"?>
<server description="new server">
<!-- Enable features -->
<featureManager>
<feature>jsp-2.3</feature>
</featureManager>
<!-- To access this server from a remote client add a host attribute to the following element, e.g. host="*" -->
<httpEndpoint id="defaultHttpEndpoint" host="*" httpPort="9080" accessLoggingRef="accessLogging" />
<httpAccessLogging id="accessLogging" filePath="/logs/http_access.log"/>
<!-- Automatically expand WAR files and EAR files -->
<applicationManager autoExpand="true"/>
<webApplication contextRoot="/" location="spring-liberty-plain.war" />
</server>
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-liberty
labels:
app: spring-liberty
spec:
replicas: 2
selector:
matchLabels:
app: spring-liberty
template:
metadata:
labels:
app: spring-liberty
spec:
containers:
- name: spring-liberty
image: image-registry.openshift-image-registry.svc:5000/spring-liberty/spring-liberty
ports:
- containerPort: 9080
readinessProbe:
httpGet:
path: /healthz
port: 9080
---
apiVersion: v1
kind: Service
metadata:
name: spring-liberty
spec:
selector:
app: spring-liberty
type: ClusterIP
ports:
- protocol: TCP
port: 80
targetPort: 9080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: spring-liberty
spec:
rules:
- host: spring-liberty.apps.ocp.cloud.vpc
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: spring-liberty
port:
number: 80
ブラウザでhttp://spring-liberty.apps.ocp.cloud.vpc/
を開くと以下のような画面が表示されます。端末で実行した時と違いPod名が表示されています。