終於到最後的最後了~這篇要來介紹如何部署我們的Spring app,不管前面的篇章說得如何又如何,最終還是得打包放到機器上跑,讓我們辛苦開發的功能能提供服務給user。
目前可能的打包方法有以下三種:
1.打包成JAR
這是Spring Boot最直觀的打包方式了,只要在我們Spring Project的根目錄底下輸入如下指令:
#Maven
$ mvnw package
#Gradle
$ gradlew build
Maven的話會生成根據pom.xml的和生成如demoapp-0.0.1-SNAPSHOT.jar這樣的JAR檔到./target下。
Gradle的話會根據settings .gradle的rootProject.name和build.gradle的version生成JAR檔到build/libs中。
然後我們就可以來執行這個JAR檔:
$ java -jar demoapp-0.0.1-SNAPSHOT.jar
2.打包成container image
首先以Docker來透過打包好的JAR產生container image為例,我們要先寫一個Dockerfile在Project目錄下:
FROM openjdk:11.0.12-jre
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
然後執行以下指令:
$ docker build . -t somePath/demoapp:0.0.1-SNAPSHOT
指令中的”.”代表了Dockerfile的位置,所以我們這邊的範例是在Dockerfile的目錄下執行指令。產生的container image就會在somePath中。
執行以下指令來跑app:
$ docker run -p8081:8081 somePath/demoapp:0.0.1-SNAPSHOT
-p8081:8081前面的port號代表實體機器的port,後面的port是指container的port。
而在Spring Boot 2.3.0以後的版本我們也可以透過Maven或Gradle來產生container image:
#Maven
$ mvnw spring-boot:build-image
#Gradle
$ gradlew bootBuildImage
接著來介紹如何把image file放到Kubernetes上。
我們會需要定義一個deployment manifest檔案如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app-deploy
labels:
app: demo-app
spec:
replicas: 3
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
- name: demo-app-container
image: demoapp/demoapp:latest
然後執行以下指令來運作:
$ kubectl apply -f deploy.yaml
Kubernetes的特性就是當app掛掉的時候會幫我們把掛掉的container關閉並重啟一個新的container來跑,這時我們為了不讓client有突然掛掉的回應,可以加上:
#application.yaml
server:
shutdown: graceful
這個properties會讓Spring app不會直接shutdown,而是先處理完已經接收的request並停止接收新的request。
而為了更細緻的控制Kubernetes管理container的特性,我們也會需要在Spring Boot上多加一些app掛掉或故障的判斷點,這時就會使用到Actuator的health中的liveness和readiness。在application.properties加上:
#application.yaml
management:
health:
probes:
enabled: true
這時我們打/actuator/health時會出現:
{
"status": "UP",
"groups": [
"liveness",
"readiness"
]
}
我們也可以針對/actuator/health/liveness或/actuator/health/readiness來打:
{
"status": "UP"
}
再加上了liveness和readiness後,來在k8s的deployment manifest中加上livenessProbe和readinessProbe:
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app-deploy
labels:
app: demo-app
spec:
replicas: 3
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
- name: demo-app-container
image: demoapp/demoapp:latest
livenessProbe:
initialDelaySeconds: 2
periodSeconds: 5
httpGet:
path: /actuator/health/liveness
port: 8081
readinessProbe:
initialDelaySeconds: 2
periodSeconds: 5
httpGet:
path: /actuator/health/readiness
port: 8081
而我們要如何在Spring app加入liveness和readiness的邏輯呢? 我們會寫如下的敘述:
AvailabilityChangeEvent.publish(context, ReadinessState.REFUSING_TRAFFIC);
AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
以下的範例是在說我們希望當Spring Boot啟動的時候先不要曝露readiness的狀態:
@Bean
public ApplicationRunner disableLiveness(ApplicationContext context) {
return args -> {
AvailabilityChangeEvent.publish(context, ReadinessState.REFUSING_TRAFFIC);
};
}
若要控制Liveness狀態的話會寫:
AvailabilityChangeEvent.publish(context, LivenessState.BROKEN);
AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
所以當我們從Spring app中發出這些事件後,Kubernetes會聽到這些liveness或readiness的指標,來去重啟container。
3.打包成WAR
若要把Spring Boot app打包成WAR,我們會需要一個DispatcherServlet class,而在Spring Boot中會有以下的類別來做到這件事:
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
public class TacoCloudServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(
SpringApplicationBuilder builder) {
return builder.sources(DemoAppApplication.class);
}
}
然後加入:
#Maven pom.xml
<packaging>war</packaging>
#Gradle build.gradle
apply plugin: 'war'
之後和打包成JAR一樣執行mvnw package或gradlew build,就能打包成WAR了。