Rolling release, graceful shutdown, elastic scaling, application monitoring, and configuration separation in Spring Boot + K8S

Foreword

K8s + SpringBoot achieves zero-downtime release: health check + rolling update + graceful shutdown + elastic scaling + Prometheus monitoring + configuration separation (mirror reuse)

Configuration

Health Check

  • Health check type: readiness probe + liveness probe

  • Probe type: exec (enter the container to execute the script), tcpSocket (detection port), httpGet (call interface)

Business level

Project dependencies pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Define access ports, paths and permissions application.yaml

management:
  server:
    port: 50000 # Enable independent operation and maintenance port
  endpoint: # Open the health endpoint
    health:
      probes:
        enabled: true
  endpoints:
    web:
      exposure:
        base-path: /actuator # Specify the context path and enable the corresponding endpoint
        include: health

Two interfaces, /actuator/health/readiness and /actuator/health/liveness, will be exposed. The access methods are as follows:

http://127.0.0.1:50000/actuator/health/readiness
http://127.0.0.1:50000/actuator/health/liveness
Operation and maintenance level

k8s deployment template deployment.yaml

apiVersion: apps/v1
Kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: {APP_NAME}
        image: {IMAGE_URL}
        imagePullPolicy: Always
        ports:
        - containerPort: {APP_PORT}
        - name: management-port
          containerPort: 50000 # Application management port
        readinessProbe: # Readiness probe
          httpGet:
            path: /actuator/health/readiness
            port: management-port
          initialDelaySeconds: 30 # Delay loading time
          periodSeconds: 10 #Retry interval
          timeoutSeconds: 1 # Timeout time setting
          successThreshold: 1 # Health threshold
          failureThreshold: 6 # Unhealthy threshold
        livenessProbe: # Liveness probe
          httpGet:
            path: /actuator/health/liveness
            port: management-port
          initialDelaySeconds: 30 # Delay loading time
          periodSeconds: 10 #Retry interval
          timeoutSeconds: 1 # Timeout time setting
          successThreshold: 1 # Health threshold
          failureThreshold: 6 # Unhealthy threshold

Rolling updates

Rolling update strategy of k8s resource scheduling. To achieve zero-downtime release, health check must be supported.

apiVersion: apps/v1
Kind: Deployment
metadata:
  name: {APP_NAME}
  labels:
    app: {APP_NAME}
spec:
  selector:
    matchLabels:
      app: {APP_NAME}
  replicas: {REPLICAS} # Number of Pod replicas
  strategy:
    type: RollingUpdate # Rolling update strategy
    rollingUpdate:
      maxSurge: 1 # The maximum number of copies that can be exceeded during the upgrade process than the originally set number.
      maxUnavailable: 1 # The maximum number of PODs that are unable to provide services during the upgrade process
Graceful shutdown

In K8s, before we implement rolling upgrade, we must achieve application-level graceful shutdown. Otherwise, the rolling upgrade will still affect the business. Make the application close the thread, release the connection resources, and then stop the service.

Business level

Project dependencies pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Define access ports, paths and permissions application.yaml

spring:
  application:
    name: <xxx>
  profiles:
    active: @profileActive@
  life cycle:
    timeout-per-shutdown-phase: 30s # Set the shutdown process timeout to 30s. If it exceeds 30s, it will shut down directly.

server:
  port: 8080
  shutdown: graceful # The default is IMMEDIATE, which means immediate shutdown; GRACEFUL means graceful shutdown.

management:
  server:
    port: 50000 # Enable independent operation and maintenance port
  endpoint: # Enable shutdown and health endpoints
    shutdown:
      enabled: true
    health:
      probes:
        enabled: true
  endpoints:
    web:
      exposure:
        base-path: /actuator # Specify the context path and enable the corresponding endpoint
        include: health,shutdown

The /actuator/shutdown interface will be exposed, and the calling method is as follows:

curl -X POST 127.0.0.1:50000/actuator/shutdown
Operation and maintenance level

Make sure the dockerfile template integrates the curl tool, otherwise the curl command cannot be used

FROM openjdk:8-jdk-alpine
#Build parameters
ARG JAR_FILE
ARG WORK_PATH="/app"
ARG EXPOSE_PORT=8080

#Environment variables
ENV JAVA_OPTS=""\
    JAR_FILE=${JAR_FILE}

#Set time zone
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime & amp; & amp; echo 'Asia/Shanghai' >/etc/timezone
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \
     & amp; & amp; apk add --no-cache curl
#Copy the jar package in the maven directory to docker and name it for_docker.jar
COPY target/$JAR_FILE $WORK_PATH/


#Set working directory
WORKDIR $WORK_PATH


# Specify the port for external interaction
EXPOSE $EXPOSE_PORT
# Configure the container to make it executable
ENTRYPOINT exec java $JAVA_OPTS -jar $JAR_FILE

k8s deployment template deployment.yaml

Note: After verification, the Java project can omit the configuration of the end callback hook.

In addition, if you need to use a callback hook, you need to ensure that the curl tool is included in the image, and you need to note that the application management port (50000) cannot be exposed to the public network.

apiVersion: apps/v1
Kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: {APP_NAME}
        image: {IMAGE_URL}
        imagePullPolicy: Always
        ports:
        - containerPort: {APP_PORT}
        - containerPort: 50000
        life cycle:
          preStop: # End callback hook
            exec:
              command: ["curl", "-XPOST", "127.0.0.1:50000/actuator/shutdown"]

Elastic scaling

After setting resource limits for the pod, create the HPA

apiVersion: apps/v1
Kind: Deployment
metadata:
  name: {APP_NAME}
  labels:
    app: {APP_NAME}
spec:
  template:
    spec:
      containers:
      - name: {APP_NAME}
        image: {IMAGE_URL}
        imagePullPolicy: Always
        resources: # Container resource management
          limits: # Resource limits (monitor usage)
            CPU: 0.5
            memory: 1Gi
          requests: # Minimum available resources (flexible scheduling)
            CPU: 0.15
            memory: 300Mi
---
kind: HorizontalPodAutoscaler # Elastic scaling controller
apiVersion: autoscaling/v2beta2
metadata:
  name: {APP_NAME}
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    Kind: Deployment
    name: {APP_NAME}
  minReplicas: {REPLICAS} # Zoom range
  maxReplicas: 6
  metrics:
    - type: Resource
      resource:
        name: cpu #Specify resource indicators
        target:
          type: Utilization
          averageUtilization: 50

Prometheus integration

Business level

Project dependencies pom.xml


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

    io.micrometer
    micrometer-registry-prometheus

Define access ports, paths and permissions application.yaml

management:
  server:
    port: 50000 # Enable independent operation and maintenance port
  metrics:
    tags:
      application: ${spring.application.name}
  endpoints:
    web:
      exposure:
        base-path: /actuator # Specify the context path and enable the corresponding endpoint
        include: metrics, prometheus

The /actuator/metric and /actuator/prometheus interfaces will be exposed, and the access methods are as follows:

http://127.0.0.1:50000/actuator/metric
http://127.0.0.1:50000/actuator/prometheus
Operation and maintenance level

deployment.yaml

apiVersion: apps/v1
Kind: Deployment
spec:
  template:
    metadata:
      annotations:
        prometheus:io/port: "50000"
        prometheus.io/path: /actuator/prometheus # Assign value in pipeline
        prometheus.io/scrape: "true" # pod-based service discovery

Configuration separation

Plan: Mount external configuration files through configmap and specify the activation environment to run

Function: Configuration separation to avoid leakage of sensitive information; image reuse to improve delivery efficiency

Generate configmap from file

# Generate yaml files through dry-run
kubectl create cm -n <namespace> <APP_NAME> --from-file=application-test.yaml --dry-run=1 -oyaml > configmap.yaml

# renew
kubectl apply -f configmap.yaml

Mount configmap and specify the activation environment

apiVersion: apps/v1
Kind: Deployment
metadata:
  name: {APP_NAME}
  labels:
    app: {APP_NAME}
spec:
  template:
    spec:
      containers:
      - name: {APP_NAME}
        image: {IMAGE_URL}
        imagePullPolicy: Always
        env:
          - name: SPRING_PROFILES_ACTIVE # Specify activation environment
            value: test
        volumeMounts: # Mount configmap
        - name: conf
          mountPath: "/app/config" # Consistent with the working directory in the Dockerfile
          readOnly: true
      volumes:
      - name: conf
        configMap:
          name: {APP_NAME}

Summary configuration

Business level

Project dependencies pom.xml


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

    io.micrometer
    micrometer-registry-prometheus

Define access ports, paths and permissions application.yaml

spring:
  application:
    name: project-sample
  profiles:
    active: @profileActive@
  life cycle:
    timeout-per-shutdown-phase: 30s # Set the shutdown process timeout to 30s. If it exceeds 30s, it will shut down directly.

server:
  port: 8080
  shutdown: graceful # The default is IMMEDIATE, which means immediate shutdown; GRACEFUL means graceful shutdown.

management:
  server:
    port: 50000 # Enable independent operation and maintenance port
  metrics:
    tags:
      application: ${spring.application.name}
  endpoint: # Enable shutdown and health endpoints
    shutdown:
      enabled: true
    health:
      probes:
        enabled: true
  endpoints:
    web:
      exposure:
        base-path: /actuator # Specify the context path and enable the corresponding endpoint
        include: health,shutdown,metrics,prometheus

Operation and maintenance level

Make sure the dockerfile template integrates the curl tool, otherwise the curl command cannot be used

FROM openjdk:8-jdk-alpine
#Build parameters
ARG JAR_FILE
ARG WORK_PATH="/app"
ARG EXPOSE_PORT=8080

#Environment variables
ENV JAVA_OPTS=""\
    JAR_FILE=${JAR_FILE}

#Set time zone
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime & amp; & amp; echo 'Asia/Shanghai' >/etc/timezone
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \
     & amp; & amp; apk add --no-cache curl
#Copy the jar package in the maven directory to docker and name it for_docker.jar
COPY target/$JAR_FILE $WORK_PATH/


#Set working directory
WORKDIR $WORK_PATH


# Specify the port for external interaction
EXPOSE $EXPOSE_PORT
# Configure the container to make it executable
ENTRYPOINT exec java $JAVA_OPTS -jar $JAR_FILE

k8s deployment template deployment.yaml

apiVersion: apps/v1
Kind: Deployment
metadata:
  name: {APP_NAME}
  labels:
    app: {APP_NAME}
spec:
  selector:
    matchLabels:
      app: {APP_NAME}
  replicas: {REPLICAS} # Number of Pod replicas
  strategy:
    type: RollingUpdate # Rolling update strategy
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      name: {APP_NAME}
      labels:
        app: {APP_NAME}
      annotations:
        timestamp: {TIMESTAMP}
        prometheus.io/port: "50000" # cannot be assigned dynamically
        prometheus.io/path: /actuator/prometheus
        prometheus.io/scrape: "true" # pod-based service discovery
    spec:
      affinity: # Set the scheduling policy and adopt multi-host/multi-availability zone deployment
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                -key: app
                  operator: In
                  values:
                  - {APP_NAME}
              topologyKey: "kubernetes.io/hostname" # Multiple availability zones are "topology.kubernetes.io/zone"
      terminationGracePeriodSeconds: 30 # Graceful termination grace period
      containers:
      - name: {APP_NAME}
        image: {IMAGE_URL}
        imagePullPolicy: Always
        ports:
        - containerPort: {APP_PORT}
        - name: management-port
          containerPort: 50000 # Application management port
        readinessProbe: # Readiness probe
          httpGet:
            path: /actuator/health/readiness
            port: management-port
          initialDelaySeconds: 30 # Delay loading time
          periodSeconds: 10 #Retry interval
          timeoutSeconds: 1 # Timeout time setting
          successThreshold: 1 # Health threshold
          failureThreshold: 9 # Unhealthy threshold
        livenessProbe: # Liveness probe
          httpGet:
            path: /actuator/health/liveness
            port: management-port
          initialDelaySeconds: 30 # Delay loading time
          periodSeconds: 10 #Retry interval
          timeoutSeconds: 1 # Timeout time setting
          successThreshold: 1 # Health threshold
          failureThreshold: 6 # Unhealthy threshold
        resources: # Container resource management
          limits: # Resource limits (monitor usage)
            CPU: 0.5
            memory: 1Gi
          requests: # Minimum available resources (flexible scheduling)
            CPU: 0.1
            memory: 200Mi
        env:
          - name: TZ
            value: Asia/Shanghai
---
kind: HorizontalPodAutoscaler # Elastic scaling controller
apiVersion: autoscaling/v2beta2
metadata:
  name: {APP_NAME}
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    Kind: Deployment
    name: {APP_NAME}
  minReplicas: {REPLICAS} # Zoom range
  maxReplicas: 6
  metrics:
    - type: Resource
      resource:
        name: cpu #Specify resource indicators
        target:
          type: Utilization
          averageUtilization: 50