iT邦幫忙

2022 iThome 鐵人賽

DAY 30
0
自我挑戰組

Spring In Action系列 第 30

Deploy Spring app

  • 分享至 

  • xImage
  •  

終於到最後的最後了~這篇要來介紹如何部署我們的Spring app,不管前面的篇章說得如何又如何,最終還是得打包放到機器上跑,讓我們辛苦開發的功能能提供服務給user。

目前可能的打包方法有以下三種:

  1. 打包成JAR檔直接在os上的JVM跑
  2. 打包成container image放到如Docker或K8s這樣的容器化服務上跑
  3. 打包成WAR檔放到Java application server如Tomcat上跑

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了。


上一篇
JMX
系列文
Spring In Action30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言