➜ Old React website
Chung Cheuk Hang MichaelJava Web Developer
Spring Boot 配置方式Bruno HTTP API 測試工具

Spring Boot + Docker + K8s

Table of contents

1 Spring Boot 項目

1.1 Maven dependencies

1<parent> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-parent</artifactId> 4 <version>3.2.4</version> 5</parent> 6 7<dependencies> 8 <dependency> 9 <groupId>org.springframework.boot</groupId> 10 <artifactId>spring-boot-starter-web</artifactId> 11 </dependency> 12 <dependency> 13 <groupId>org.springframework.boot</groupId> 14 <artifactId>spring-boot-starter-actuator</artifactId> 15 </dependency> 16</dependencies>

1.2 Main class

1@SpringBootApplication 2public class MainApplication { 3 4 public static void main(String[] args) { 5 SpringApplication.run(MainApplication.class, args); 6 } 7}

1.3 Application 配置

application.yml
1management: 2 endpoints: 3 web: 4 exposure: 5 include: health,info,env,beans,loggers 6 7# 等目前既 HTTP requests 完成曬之後先會 shutdown,等候時間上限 60 秒 8server: 9 shutdown: graceful 10spring: 11 lifecycle: 12 timeout-per-shutdown-phase: 60s

2 Dockerfile

關於 OpenJDK 用 jlink command 保留有需要既 modules,可以睇返呢篇:客製化 JRE

2.1 只有 OpenJDK 既 Dockerfile

1FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS jre-stage 2 3RUN jlink \ 4 --add-modules \ 5java.base,\ 6java.compiler,\ 7java.datatransfer,\ 8java.desktop,\ 9java.instrument,\ 10java.logging,\ 11java.management,\ 12java.management.rmi,\ 13java.naming,\ 14java.net.http,\ 15java.prefs,\ 16java.rmi,\ 17java.scripting,\ 18java.se,\ 19java.security.jgss,\ 20java.security.sasl,\ 21java.smartcardio,\ 22java.sql,\ 23java.sql.rowset,\ 24java.transaction.xa,\ 25java.xml,\ 26java.xml.crypto,\ 27jdk.accessibility,\ 28jdk.charsets,\ 29jdk.crypto.cryptoki,\ 30jdk.crypto.ec,\ 31jdk.dynalink,\ 32jdk.internal.ed,\ 33jdk.internal.le,\ 34jdk.internal.vm.ci,\ 35jdk.internal.vm.compiler,\ 36jdk.internal.vm.compiler.management,\ 37jdk.jdwp.agent,\ 38jdk.jfr,\ 39jdk.jsobject,\ 40jdk.localedata,\ 41jdk.management,\ 42jdk.management.agent,\ 43jdk.management.jfr,\ 44jdk.naming.dns,\ 45jdk.naming.rmi,\ 46jdk.net,\ 47jdk.nio.mapmode,\ 48jdk.random,\ 49jdk.sctp,\ 50jdk.security.auth,\ 51jdk.security.jgss,\ 52jdk.unsupported,\ 53jdk.xml.dom,\ 54jdk.zipfs \ 55 --strip-debug \ 56 --no-man-pages \ 57 --no-header-files \ 58 --compress=2 \ 59 --output /javaruntime 60 61 62 63FROM ubuntu:22.04 64 65# VOLUME /tmp 66EXPOSE 8080 67 68RUN mkdir -m 445 /app && mkdir -m 445 /app/jre 69WORKDIR /app 70 71RUN addgroup --system mygroup && adduser --system --shell /bin/false --ingroup mygroup myuser 72USER myuser 73 74ENV PATH "/app/jre/bin:$PATH" 75COPY --from=jre-stage --chown=root:root --chmod=445 /javaruntime /app/jre 76 77# CMD "java" "-jar" "app.jar" 78ENTRYPOINT ["java", "-jar", "app.jar"] 79 80COPY --chown=root:root --chmod=444 /target/app.jar app.jar

2.1.1 Build script

我地可以配合以下既 build script:
1CALL mvn clean package 2 3PUSHD target 4REN *.jar app.jar 5POPD 6 7docker image build -t spring-boot-3-demo -f Dockerfile-openjdk.txt .
註:將 Dockerfile 保存做 Dockerfile-openjdk.txt

2.2 行埋 Maven build 既 Dockerfile

1FROM maven:3.9.6-eclipse-temurin-17 AS mvn-stage 2RUN mkdir /project 3COPY . /project 4WORKDIR /project 5RUN mvn clean package && mv /project/target/*.jar /project/target/app.jar 6 7 8 9FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS jre-stage 10 11RUN jlink \ 12 --add-modules \ 13java.base,\ 14java.compiler,\ 15java.datatransfer,\ 16java.desktop,\ 17java.instrument,\ 18java.logging,\ 19java.management,\ 20java.management.rmi,\ 21java.naming,\ 22java.net.http,\ 23java.prefs,\ 24java.rmi,\ 25java.scripting,\ 26java.se,\ 27java.security.jgss,\ 28java.security.sasl,\ 29java.smartcardio,\ 30java.sql,\ 31java.sql.rowset,\ 32java.transaction.xa,\ 33java.xml,\ 34java.xml.crypto,\ 35jdk.accessibility,\ 36jdk.charsets,\ 37jdk.crypto.cryptoki,\ 38jdk.crypto.ec,\ 39jdk.dynalink,\ 40jdk.internal.ed,\ 41jdk.internal.le,\ 42jdk.internal.vm.ci,\ 43jdk.internal.vm.compiler,\ 44jdk.internal.vm.compiler.management,\ 45jdk.jdwp.agent,\ 46jdk.jfr,\ 47jdk.jsobject,\ 48jdk.localedata,\ 49jdk.management,\ 50jdk.management.agent,\ 51jdk.management.jfr,\ 52jdk.naming.dns,\ 53jdk.naming.rmi,\ 54jdk.net,\ 55jdk.nio.mapmode,\ 56jdk.random,\ 57jdk.sctp,\ 58jdk.security.auth,\ 59jdk.security.jgss,\ 60jdk.unsupported,\ 61jdk.xml.dom,\ 62jdk.zipfs \ 63 --strip-debug \ 64 --no-man-pages \ 65 --no-header-files \ 66 --compress=2 \ 67 --output /javaruntime 68 69 70 71FROM ubuntu:22.04 72 73# VOLUME /tmp 74EXPOSE 8080 75 76RUN mkdir -m 445 /app && mkdir -m 445 /app/jre 77WORKDIR /app 78 79RUN addgroup --system mygroup && adduser --system --shell /bin/false --ingroup mygroup myuser 80USER myuser 81 82ENV PATH "/app/jre/bin:$PATH" 83COPY --from=jre-stage --chown=root:root --chmod=445 /javaruntime /app/jre 84 85# CMD "java" "-jar" "app.jar" 86ENTRYPOINT ["java", "-jar", "app.jar"] 87 88COPY --from=mvn-stage --chown=root:root --chmod=444 /project/target/app.jar app.jar

2.2.1 Build script

docker image build -t spring-boot-3-demo -f Dockerfile-maven-and-openjdk.txt .
註:將 Dockerfile 保存做 Dockerfile-maven-and-openjdk.txt

2.3 詳細解釋

  • 我地需要用到 Docker 既 multi-stage build 功能黎完成一啲事前準備功夫。
    • 包括:
      • jlink 客製化 OpenJDK,淨係保留對我地有用既 Java modules。
      • mvn 打包個 Spring Boot 項目既 JAR 檔。
    • 呢啲事前準備功夫可以用 intermediate stages 黎達成,而呢啲 stages 所產生出黎既 containers 同最後既 Docker image 冇關係。
    • 我地可以將 intermediate stages 裡面既 output 檔案抄去最終既 Docker image 度。
    • 真正既 base image 係 ubuntu
  • VOLUME instruction 係用黎定義我地個 Docker image 可能有啲需要保存既用戶數據。
    • Docker Desktop 既 behavior 係會自動建立 anonymous volume(s),但如果我地用 docker container run 執行並且加左 --rm,咁個 container 結束既時候,Docker 就會自動刪除相關既 anonymous volume(s)。
    • 如果喺 cloud 既 K8s 上面運行,而 readOnlyFilesystem 設置左係 true,而 containerd 又設置左 ignore_image_defined_volumestrue,咁呢個 VOLUME instruction 會被無視。
      • 喺個 Docker container 裡面執行 mount command 可以睇到 DockerfileVOLUME instructions 有冇令 volume mounts 生效到。
  • EXPOSE instruction 係用黎定義我地個 app 會行喺咩 port 上面。
  • RUN instruction 係用黎執行任意既 Linux commands。
    • 為左減少 layers 既數量,我地要盡量將啲 commands 放到一個 RUN instruction 裡面,用 && 黎串連多個 commands,咁 Docker 就只會為呢一個 RUN instruction 增加一個 layer。
  • mkdir -m <permission> 係用黎創建目錄,同時改變埋目錄既權限。
  • WORKDIR instruction 會改變 container 既 working directory,影響後續 RUNCMDENTRYPOINTCOPY 以及 ADD instructions 既 working directory。
    • docker container exec 或者 kubectl exec 入去個 container 之後最初既 working directory 會係 WORKDIR instruction 定義既 path。
    • pwd command 可以顯示 working directory。
    • 正因為咁,所以最後一句 COPY instruction 會將 app.jar 抄到去個 ubuntu base image 既 /app(working directory)。
  • addgroupadduser 係用黎新增 group、新增 user。
    • 加黎完全係為左安全需要。
    • 因為 Docker 默認會用 root user 黎做曬所有野,我地唔希望個 Docker container 用一個有好大權限既 user。
  • USER instruction 會切換 user,影響後續既 RUN instructions,亦會影響 runtime 既時候執行 CMD 或者 ENTRYPOINT 既 user。
    • 基於安全考慮,user 唔應該用默認既 root,否則就有好多權限。
  • ENV instructions 係用黎設置 environment variable。
    • 我地要重新設置 PATH environment variable 係因為我地只係 base 左 ubuntu image,再將 OpenJDK 既 binaries 抄入去。如果唔設置 PATH 既話,ubuntu 就唔知道可以喺 /app/jre/bin 度 lookup java 執行檔。
      • 修改 PATH 比較正路既做法係將要加入既 path 放喺左邊,然後用 : 同現時既 paths 分隔。放喺左邊既原因係如果有兩個或以上既 paths 存在相同既執行檔,咁 Linux 會用最左邊果個,亦即係話擺喺左邊既呢個方法喺呢個情況下會比較有效。
  • COPY instruction 可以幫我地抄檔案或者目錄。
    • --from 會幫我地由之前既 stage 既 file system 抄 /javaruntime 裡面啲野去 ubuntu/app/jre 裡面。
    • 如果冇 --from,就會由本地既 working directory 抄入去。
    • 可以用 --chown 順便做埋 chown,將 owner、group 改做 <user>:<group>
    • 可以用 --chmod 順便做埋 chmod,修改權限。
      • 例如將權限改做 445,咁 owner、group 只可以 read,而 public(其他 users 或者 groups)只可以 read、execute。
  • # 係用黎寫 comment,咁果句就唔會生效。
  • 我地可以用 CMD 或者 ENTRYPOINT instruction 黎話畀 Docker 知 Docker container 執行既時候應該行咩 commands。
    • CMD 可以被覆蓋。
    • ENTRYPOINT 唔可以被覆蓋,一定會執行。
  • 關於權限,我地既 OpenJDK、app.jar 既 owner、group 都係 root,但因為我地用左 USER instruction 切換左做 myuser,而呢個 user 屬於另一個叫 mygroup 既 group,所以佢地需要開放權限畀 public,咁 myuser 先可以用到佢地。
    • myuser 需要 OpenJDK 既 read、execute 權限。
    • myuser 需要 app.jar 既 read 權限,唔需要 execute 權限係因為佢唔係直接執行 app.jar,而係透過 OpenJDK 裡面既 java

3 執行

3.1 Docker Desktop

docker container run --rm -p 8080:8080 spring-boot-3-demo
Read-only file system 模式:
docker container run --rm -p 8080:8080 --read-only spring-boot-3-demo
註:
  • 用左 --read-only 既話,一定要令到 /tmp 有 volume mount,從而令 /tmp 可以寫入。
    • 否則,如果 Spring Boot 既 embedded Tomcat 喺 runtime 既時候創建唔到目錄或者寫入唔到落 temp folder 既話,就會令成個 Spring Boot microservice 啟動失敗。
    • 用 Docker Desktop 既話可以利用 Dockerfile 既 VOLUME /tmp instruction。

3.2 Kubernetes

我地可以開啟 Docker Desktop 內置既 K8s 功能,或者用某個 cloud provider 既 K8s 服務。
關於 kubectl CLI 既 commands 可以睇返呢篇:kubectl 基本操作

3.2.1 單獨 Pod

雖然可以只係用 K8s Pod,但一般都會用 K8s Deployment 配合 manual 或者 auto 既 horizontal scaling。
k8s-pod.yml
1kind: Pod 2apiVersion: v1 3metadata: 4 name: spring-boot-3-demo 5 namespace: default 6spec: 7 terminationGracePeriodSeconds: 60 # 唔可以少過 Spring Boot 既 graceful shutdown 時限配置 8 containers: 9 - name: spring-boot-3-demo 10 image: spring-boot-3-demo:latest 11 imagePullPolicy: Never # 只限本地測試用 12 securityContext: 13 readOnlyRootFilesystem: true # 安全需要 14 ports: 15 - containerPort: 8080 16 startupProbe: 17 httpGet: 18 path: /actuator/health/liveness 19 port: 8080 20 initialDelaySeconds: 5 21 periodSeconds: 5 22 failureThreshold: 30 23 timeoutSeconds: 5 24 livenessProbe: 25 httpGet: 26 path: /actuator/health/liveness 27 port: 8080 28 initialDelaySeconds: 5 29 periodSeconds: 10 30 failureThreshold: 3 31 timeoutSeconds: 5 32 readinessProbe: 33 httpGet: 34 path: /actuator/health/readiness 35 port: 8080 36 initialDelaySeconds: 5 37 periodSeconds: 10 38 failureThreshold: 3 39 timeoutSeconds: 5 40 resources: 41 limits: 42 cpu: 1000m 43 requests: 44 cpu: 100m 45 args: # 示範用 command line arguments 黎改變 Spring Boot behaviors 46 - --logging.level.root=DEBUG 47 48# 用 emptyDir 方法,令 /tmp 可以寫入 49 volumeMounts: 50 - name: empty-tmp-dir 51 mountPath: /tmp 52 volumes: 53 - name: empty-tmp-dir 54 emptyDir: {}
保存左個檔案之後執行:
kubectl apply -f k8s-pod.yml
之後可以 port-forward 個 K8s Pod
kubectl port-forward spring-boot-3-demo 8080

3.2.2 Deployment

用 K8s Deployment 既話就可以指定某個數量既 replicas,K8s 會幫我地管理啲 Pod
k8s-deployment.yml
1apiVersion: apps/v1 2kind: Deployment 3metadata: 4 name: spring-boot-3-demo 5 namespace: default 6spec: 7 replicas: 2 # 呢個會係固定數量 8 selector: 9 matchLabels: 10 app: spring-boot-3-demo 11 template: 12 metadata: 13 labels: 14 app: spring-boot-3-demo 15 spec: 16 terminationGracePeriodSeconds: 60 # 唔可以少過 Spring Boot 既 graceful shutdown 時限配置 17 containers: 18 - name: spring-boot-3-demo 19 image: spring-boot-3-demo:latest 20 imagePullPolicy: Never # 只限本地測試用 21 securityContext: 22 readOnlyRootFilesystem: true # 安全需要 23 ports: 24 - containerPort: 8080 25 startupProbe: 26 httpGet: 27 path: /actuator/health/liveness 28 port: 8080 29 initialDelaySeconds: 5 30 periodSeconds: 5 31 failureThreshold: 30 32 timeoutSeconds: 5 33 livenessProbe: 34 httpGet: 35 path: /actuator/health/liveness 36 port: 8080 37 initialDelaySeconds: 5 38 periodSeconds: 10 39 failureThreshold: 3 40 timeoutSeconds: 5 41 readinessProbe: 42 httpGet: 43 path: /actuator/health/readiness 44 port: 8080 45 initialDelaySeconds: 5 46 periodSeconds: 10 47 failureThreshold: 3 48 timeoutSeconds: 5 49 resources: 50 limits: 51 cpu: 1000m 52 requests: 53 cpu: 100m 54 args: # 示範用 command line arguments 黎改變 Spring Boot behaviors 55 - --logging.level.root=DEBUG 56 57# 用 emptyDir 方法,令 /tmp 可以寫入 58 volumeMounts: 59 - name: empty-tmp-dir 60 mountPath: /tmp 61 volumes: 62 - name: empty-tmp-dir 63 emptyDir: {}
保存左個檔案之後執行:
kubectl apply -f k8s-deployment.yml
之後可以 port-forward 個 K8s Deployment
kubectl port-forward deployment/spring-boot-3-demo 8080

3.2.3 Service

當我地既 microservice 有多個 K8s Pod 行緊,我地最好整返個 K8s Service,因為:
  • K8s Pod 呢樣野既性質係短暫/動態/流動既,佢地隨時可以因為唔同原因而增加減少,而每一個 K8s Pod 又有自己既 IP,但我地正常都會用 domain name 既 URL,而唔係喺個 downstream microservice 度配置 upstream microservice 既 IP address。
  • K8s Service 可以提供 load balancing 既功能(有可能係隨機分配既實現方式),自動分配 requests 去唔同既 K8s Pod
k8s-service.yml
1kind: Service 2apiVersion: v1 3metadata: 4 name: svc-spring-boot-3-demo 5 namespace: default 6spec: 7 type: ClusterIP 8 selector: 9 app: spring-boot-3-demo 10 ports: 11 - port: 8080 12 targetPort: 8080 13 protocol: TCP
保存左個檔案之後執行:
kubectl apply -f k8s-service.yml
之後可以 port-forward 個 K8s Service
kubectl port-forward service/svc-spring-boot-3-demo 8080

3.3 測試

可以檢查下 Spring Boot Actuator 既 HTTP REST endpoints:
1curl http://localhost:8080/actuator/health 2curl http://localhost:8080/actuator/health/liveness 3curl http://localhost:8080/actuator/health/readiness 4curl http://localhost:8080/actuator/info 5curl http://localhost:8080/actuator/env 6curl http://localhost:8080/actuator/beans 7curl http://localhost:8080/actuator/loggers

3.4 詳細解釋

  • Spring Boot 本身具備 cloud awareness。
    • 喺呢個測試項目裡面,我地用左 Spring Boot Actuator,而個 Spring Boot microservice 又喺 K8s 上面運行。
    • Spring Boot 會 check 到有 KUBERNETES_SERVICE_HOSTKUBERNETES_SERVICE_PORT 呢兩個 environment variables。
    • 所以 Spring Boot 就知道佢而家喺 K8s 上面行緊,就會自動 expose /actuator/health/liveness/actuator/health/readiness 呢兩個 HTTP REST endpoints。
  • 我地配置 K8s 既 terminationGracePeriodSeconds 秒數唔應該少過我地配置既 Spring Boot graceful shutdown 既 spring.lifecycle.timeout-per-shutdown-phase 配置時限。
    • Spring Boot 默認係會即時 shutdown,無視未完成既 HTTP requests。
      • 我地當然唔想有 HTTP requests 突然被中斷,所以我地要指示 Spring Boot 用 graceful shutdown 方式,並且將 shutdown timeout 設置到一個合理既數值。
        • server.shutdown 設置做 graceful
        • spring.lifecycle.timeout-per-shutdown-phase 既默認值係 30 秒。
      • 設置好之後,Spring Boot 會等到當時未完成既 HTTP requests 都完成曬或者到左 shutdown timeout 既時限先至 shutdown。
    • K8s 都有默認 30 秒既 terminationGracePeriodSeconds 配置。
      • 如果我地有配置到比 30 秒長既 Spring Boot shutdown timeout,咁我地都要配置埋 K8s 果個。
    • 例子/測試方法
      1. 我地既 Spring Boot microservice 可以 expose 一個會 Thread.sleep(100_000)(暫停 100 秒)既 HTTP REST endpoint。
      2. 然後設置 Spring Boot graceful shutdown、shutdown timeout 做 120 秒。
      3. 重新 build 個 Spring Boot Docker image。
      4. 然後喺部署 K8s 既 YAML 配置檔度設置 termination grace period 做 120 秒。
      5. kubectl apply 部署個 Spring Boot microservice 既 K8s Deployment
      6. 我地 port-forward 其中一個 Pod,然後用 Postman 或者 Bruno 去 call 個新 endpoint。
      7. 我地即刻執行 kubectl rollout restart deployment spring-boot-3-demo
      8. 我地會見到 Postman 或者 Bruno 可以執行個 HTTP request 長達 120 秒,到成功返回 HTTP response 之後 K8s 先會結束個 Pod,然後用新既去取代佢。
  • K8s 提供左唔同既 probes 配置去驗證個 Pod 到底正唔正常。
    • 佢可以持續咁自動 call 個 Pod 既一啲 HTTP endpoints。
      • 成功既定義係 HTTP response status code 要係 200399
    • Probes
      • 如果 startup probe 既 HTTP endpoint fail 左,K8s 就會回收個 Pod
      • 如果 liveness probe 既 HTTP endpoint fail 左,K8s 就會回收個 Pod
      • 如果 readiness probe 既 HTTP endpoint fail 左,K8s 就會唔畀 traffic 去呢個 Pod 度。
    • initialDelaySeconds 係初始化既時候 K8s 應該等幾耐先開始驗證。
    • periodSeconds 係初始化完成之後 K8s 每隔幾耐需要驗證。
    • failureThreshold 係可以允許既失敗次數既上限。
  • Spring Boot Actuator 提供左對應 K8s liveness、readiness probes 既 HTTP REST endpoints。
    • /actuator/health/liveness 可以用落 K8s 既 liveness probe。
    • /actuator/health/readiness 可以用落 K8s 既 readiness probe。
      • 呢個 endpoint 返回 HTTP response status code 200 既話,即係表示個 HTTP server 已經準備好接受 HTTP requests。
    • Liveness endpoint 返回正面既結果,唔代表 readiness endpoint 會返回正面既結果,因為個 HTTP server 未必準備好接受 HTTP requests。
    • 如果 readiness endpoint 返回正面既結果,咁 liveness endpoint 都應該返回正面既結果。
  • Spring Boot Actuator 冇提供對應 K8s startup probe 既 startup endpoint。
    • 有兩個解決方法:
      1. 我地可以用返 Spring Boot Actuator 既 liveness endpoint 作為 K8s startup probe,然後配置一個好大既 failureThreshold,例如 30 次,配合短既 periodSeconds,例如 5 秒(即係最長 2.5 分鐘)。
        • 呢個做法等於延續 K8s liveness probe 既驗證時間。
      2. 我地可以為 K8s liveness、readiness probes 配置一個好大既 initialDelaySeconds,例如 180 秒,但呢個時間係固定既,時間越長就越會增加 scaling 既成本。
  • 我地可以用 K8s Deploymentreplicas 配置黎指定需要既 Pod 數量。
    • K8s 會幫我地管理 Pod,盡可能保持我地要求既 Pod 數量。
    • K8s 會幫我地自動創建一個 K8s ReplicaSet
    • 不過呢個方法得到既 Pod 既數量係固定既,唔會因應 Pod container 既 CPU、memory、web requests 或者 events 既情況而自動增加減少。
    • 比較好既做法係用 K8s HorizontalPodAutoscaler 做 auto 既 horizontal scaling(需要 metrics server)。
    • 亦都可以用 Kubernetes Event-driven Autoscaling(KEDA)。

3.5 注意事項

  • 設置左 readOnlyRootFilesystemtrue 既話,一定要令到 /tmp 有 volume mount,從而令 /tmp 可以寫入。
    • 否則,如果 Spring Boot 既 embedded Tomcat 喺 runtime 既時候創建唔到目錄或者寫入唔到落 temp folder 既話,就會令成個 Spring Boot microservice 啟動失敗。
    • 用 Docker Desktop 既 K8s 既話,有兩個方法:
      1. 用 K8s 正路既 emptyDir 方法。
      2. Dockerfile 用 VOLUME /tmp instruction。
    • 用 cloud 既 K8s 既話,有兩個方法:
      1. 用 K8s 正路既 emptyDir 方法。
      2. Dockerfile 用 VOLUME /tmp instruction,但 containerd 既 ignore_image_defined_volumes 一定要配合佢而設置做 false

4 附錄

4.1 Snyk Java Dockerfile best practices

4.2 K8s pod lifecycle

4.3 Read-only file system /tmp 寫入問題

以下係如果我地冇整好 volume mount 落 /tmp 會造成既 startup error:
1Caused by: org.springframework.boot.web.server.WebServerException: Unable to create tempDir. java.io.tmpdir is set to /tmp 2 at org.springframework.boot.web.server.AbstractConfigurableWebServerFactory.createTempDir(AbstractConfigurableWebServerFactory.java:241) 3 at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:202) 4 at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:188) 5 at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:162) 6 ... 15 common frames omitted 7Caused by: java.nio.file.FileSystemException: /tmp/tomcat.8080.9381430298818445978: Read-only file system 8 at java.base/sun.nio.fs.UnixException.translateToIOException(Unknown Source) 9 at java.base/sun.nio.fs.UnixException.rethrowAsIOException(Unknown Source) 10 at java.base/sun.nio.fs.UnixException.rethrowAsIOException(Unknown Source) 11 at java.base/sun.nio.fs.UnixFileSystemProvider.createDirectory(Unknown Source) 12 at java.base/java.nio.file.Files.createDirectory(Unknown Source) 13 at java.base/java.nio.file.TempFileHelper.create(Unknown Source) 14 at java.base/java.nio.file.TempFileHelper.createTempDirectory(Unknown Source) 15 at java.base/java.nio.file.Files.createTempDirectory(Unknown Source) 16 at org.springframework.boot.web.server.AbstractConfigurableWebServerFactory.createTempDir(AbstractConfigurableWebServerFactory.java:235) 17 ... 18 common frames omitted

5 參考資料