spring boot 應(yīng)用在 k8s 中的健康檢查(三)

一、概述

在 spring boot 2.3 中引入了容器探針哆窿,也就是增加了 /actuator/health/liveness/actuator/health/readiness 這兩個(gè)健康檢查路徑农曲,對(duì)于部署在 k8s 中的應(yīng)用社搅,spring-boot-actuator 將通過(guò)這兩個(gè)路徑自動(dòng)進(jìn)行健康檢查。本文主要根據(jù)官方文檔的描述實(shí)踐并記錄使用流程乳规,從如下幾個(gè)方面進(jìn)行介紹:

  1. k8s 中的健康檢查
  2. spring-boot-actuator 中的 k8s 探針
  3. spring boot 健康檢查在 k8s 中的實(shí)踐

二形葬、spring boot 健康檢查在 k8s 中的實(shí)踐

本次實(shí)踐的思路來(lái)自下文的參考文章,這里使用 spring boot 2.5.1 進(jìn)行實(shí)踐

1. 實(shí)踐環(huán)境

  • 開(kāi)發(fā)工具:IntelliJ IDEA 2021.1.1 (Ultimate Edition)
  • jdk 1.8
  • Apache Maven 3.6.3
  • docker 20.10.5
  • minikube v1.18.1
  • spring boot 2.5.1

2. 創(chuàng)建一個(gè) spring boot 項(xiàng)目

1. 使用 idea 創(chuàng)建一個(gè) spring boot 項(xiàng)目:

image.png

2. pom.xml 的依賴(lài)配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>probedemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>probedemo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--   用來(lái)做健康檢查的 starter     -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

3. 創(chuàng)建一個(gè)監(jiān)聽(tīng)類(lèi)暮的,可以監(jiān)聽(tīng)存活和就緒狀態(tài)的變化:

package com.example.probedemo.listener;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.AvailabilityState;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

/**
 * 監(jiān)聽(tīng)系統(tǒng)事件的類(lèi)
 *
 * @className: AvailabilityListener
 * @date: 2021/6/15 10:44
 */
@Slf4j
@Component
public class AvailabilityListener {

    /**
     * 基于 spring 的事件監(jiān)聽(tīng)機(jī)制笙以,監(jiān)聽(tīng)系統(tǒng)的消息
     * 當(dāng)監(jiān)聽(tīng)到 AvailabilityChangeEvent 事件會(huì)觸發(fā)此方法的調(diào)用
     * 這里使用日志記錄事件的狀態(tài)
     * @param event
     */
    @EventListener
    public void onStateChange(AvailabilityChangeEvent<? extends AvailabilityState> event) {
        log.info(event.getState().getClass().getSimpleName() + ": " + event.getState());
    }

}

@EventListener 注解說(shuō)明:

將方法標(biāo)記為應(yīng)用程序事件偵聽(tīng)器的注解。

如果帶注解的方法支持單個(gè)事件類(lèi)型冻辩,則該方法可以聲明一個(gè)反映要偵聽(tīng)的事件類(lèi)型的參數(shù)猖腕。如果帶注解的方法支持多個(gè)事件類(lèi)型,則此注解可以使用classes屬性引用一個(gè)或多個(gè)受支持的事件類(lèi)型恨闪。有關(guān)詳細(xì)信息倘感,請(qǐng)參見(jiàn)類(lèi)javadoc。

事件可以是ApplicationEvent實(shí)例咙咽,也可以是任意對(duì)象钠乏。

@EventListener注解的處理通過(guò)內(nèi)部EventListenerMethodProcessor bean執(zhí)行明刷,該bean在使用Java config時(shí)自動(dòng)注冊(cè)挽荠,或者通過(guò)<context:annotation-config/>或者<context:component-scan/>使用XML配置時(shí)的元素良瞧。

帶注解的方法可能具有非void返回類(lèi)型。當(dāng)它們這樣做時(shí),方法調(diào)用的結(jié)果將作為新事件發(fā)送。如果返回類(lèi)型是數(shù)組或集合,則每個(gè)元素將作為新的單個(gè)事件發(fā)送弄诲。

此注解可用作元注解,以創(chuàng)建自定義組合注解娇唯。

  • 異常處理:雖然事件偵聽(tīng)器可以聲明它拋出任意異常類(lèi)型齐遵,但是從事件偵聽(tīng)器拋出的任何選中的異常都將包裝在未聲明的ThrowableException中,因?yàn)槭录l(fā)布器只能處理運(yùn)行時(shí)異常视乐。

  • 異步偵聽(tīng)器:如果希望某個(gè)特定的偵聽(tīng)器異步處理事件洛搀,可以使用Spring的@Async支持,但在使用異步事件時(shí)要注意以下限制佑淀。如果異步事件偵聽(tīng)器拋出異常留美,則不會(huì)將其傳播到調(diào)用方。有關(guān)詳細(xì)信息伸刃,請(qǐng)參閱AsyncUncaughtExceptionHandler谎砾。異步事件偵聽(tīng)器方法無(wú)法通過(guò)返回值來(lái)發(fā)布后續(xù)事件。如果需要作為處理結(jié)果發(fā)布另一個(gè)事件捧颅,請(qǐng)插入ApplicationEventPublisher以手動(dòng)發(fā)布該事件景图。

  • 排序偵聽(tīng)器:還可以定義調(diào)用某個(gè)事件的偵聽(tīng)器的順序。為此碉哑,將Spring的公共@Order注解添加到這個(gè)事件偵聽(tīng)器注解旁邊挚币。

4. 創(chuàng)建一個(gè) stateController 用來(lái)修改狀態(tài)

package com.example.probedemo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.LivenessState;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

/**
 * 測(cè)試修改狀態(tài)的 controller
 *
 * @className: StateWriter
 * @date: 2021/6/15 14:17
 */
@RestController
@RequestMapping("/state")
public class StateController {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    /**
     * 將存活狀態(tài)改為 BROKEN
     * 這會(huì)導(dǎo)致 k8s 殺死 pod,并根據(jù)重啟策略重啟 pod
     *
     * @return
     */
    @GetMapping("broken")
    public String broken() {
        AvailabilityChangeEvent.publish(applicationEventPublisher, this, LivenessState.BROKEN);
        return "success broken, " + new Date();
    }

    /**
     * 將存活狀態(tài)修改為 correct
     * @return
     */
    @GetMapping("correct")
    public String correct() {
        AvailabilityChangeEvent.publish(applicationEventPublisher, this, LivenessState.CORRECT);
        return "success correct, " + new Date();
    }

    /**
     * 將就緒狀態(tài)修改為 ACCEPTING_TRAFFIC (接受流量)
     * k8s 會(huì)將外部請(qǐng)求轉(zhuǎn)發(fā)到此 pod
     * @return
     */
    @GetMapping("accept")
    public String accept() {
        AvailabilityChangeEvent.publish(applicationEventPublisher, this, ReadinessState.ACCEPTING_TRAFFIC);
        return "success accept, " + new Date();
    }

    /**
     * 將就緒狀態(tài)修改為 REFUSING_TRAFFIC
     * k8s 通過(guò)將 service 對(duì)應(yīng)的后端 endpoint 中此 pod 的ip移除來(lái)拒絕外部請(qǐng)求
     * @return
     */
    @GetMapping("refuse")
    public String refuse() {
        AvailabilityChangeEvent.publish(applicationEventPublisher, this, ReadinessState.REFUSING_TRAFFIC);
        return "success refuse, " + new Date();
    }


}


5. 制作 docker 鏡像

在pom.xml所在目錄創(chuàng)建文件Dockerfile扣典,內(nèi)容如下:

# 指定基礎(chǔ)鏡像妆毕,這是多階段構(gòu)建的前期階段
FROM openjdk:11-jre-slim as builder
# 指定工作目錄,目錄不存在會(huì)自動(dòng)創(chuàng)建
WORKDIR /app
# 將生成的 jar 復(fù)制到容器鏡像中
COPY target/*.jar application.jar
# 通過(guò)工具spring-boot-jarmode-layertools從application.jar中提取拆分后的構(gòu)建結(jié)果
RUN java -Djarmode=layertools -jar application.jar extract

# 正式構(gòu)建鏡像
FROM openjdk:11-jre-slim
# 指定工作目錄贮尖,目錄不存在會(huì)自動(dòng)創(chuàng)建
WORKDIR /app
# 前一階段從jar中提取除了多個(gè)文件笛粘,這里分別執(zhí)行COPY命令復(fù)制到鏡像空間中,每次COPY都是一個(gè)layer
COPY --from=builder app/dependencies ./
COPY --from=builder app/spring-boot-loader ./
COPY --from=builder app/snapshot-dependencies ./
COPY --from=builder app/application ./
# 指定時(shí)區(qū)
ENV TZ="Asia/Shanghai"
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 定義一些環(huán)境變量湿硝,方便環(huán)境變量傳參
ENV JVM_OPTS=""
ENV JAVA_OPTS=""
# 指定暴露的端口薪前,起到說(shuō)明的作用,不指定也會(huì)暴露對(duì)應(yīng)端口
EXPOSE 8080
# 啟動(dòng) jar 的命令
ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS org.springframework.boot.loader.JarLauncher"]


使用以下命令編譯構(gòu)建項(xiàng)目:

mvn clean package -U -DskipTests

使用以下命令構(gòu)建 docker 鏡像(最后有一個(gè) . 表示當(dāng)前目錄作為docker構(gòu)建的上下文環(huán)境):

docker build -t probedemo:1.0.0 .

使用下面的命令將 docker 鏡像推送到遠(yuǎn)程倉(cāng)庫(kù)(這里推送到docker hub倉(cāng)庫(kù)关斜,需要自己注冊(cè)一個(gè)賬號(hào)):

# 給鏡像打一個(gè)標(biāo)簽示括,[倉(cāng)庫(kù)地址/鏡像名:鏡像標(biāo)簽]
docker tag probedemo:1.0.0 wangedison98/probedemo:1.0.0
# 推送到遠(yuǎn)程倉(cāng)庫(kù)
docker push wangedison98/probedemo:1.0.0

6. k8s 部署 deployment 和 service

創(chuàng)建名為probedemo.yaml的文件:


apiVersion: apps/v1
kind: Deployment
metadata:
  name: probedemo
  labels:
    app: probedemo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: probedemo
  template:
    metadata:
      labels:
        app: probedemo
    spec:
      containers:
        - name: probedemo
          imagePullPolicy: IfNotPresent
          image: wangedison98/probedemo:1.0.0
          ports:
            - containerPort: 8080
          resources:
            requests:
              memory: "512Mi"
              cpu: "100m"
            limits:
              memory: "1Gi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 5
            failureThreshold: 10
            timeoutSeconds: 10
            periodSeconds: 5
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 5
            timeoutSeconds: 10
            periodSeconds: 5


---
apiVersion: v1
kind: Service
metadata:
  name: probedemo
spec:
  ports:
    - port: 8080
      targetPort: 8080
  selector:
    app: probedemo
  type: NodePort

這里要重點(diǎn)關(guān)注的是 livenessProbeinitialDelaySecondsfailureThreshold 參數(shù),initialDelaySeconds 等于5蚤吹,表示 pod 創(chuàng)建5秒后檢查存活探針例诀,如果10秒內(nèi)應(yīng)用沒(méi)有完成啟動(dòng)随抠,存活探針不返回200裁着,就會(huì)重試10次(failureThreshold等于10)繁涂,每一次等待 5 秒(periodSeconds 等于5),如果重試10次二驰,也就是50秒后扔罪,存活探針依舊無(wú)法返回200,該pod就會(huì)被kubernetes殺死重建桶雀,要是每次啟動(dòng)都耗時(shí)這么長(zhǎng)矿酵,pod就會(huì)不停的被殺死重建,這種情況下可以考慮延長(zhǎng) failureThreshold 失敗重試的次數(shù)矗积。

使用如下命令創(chuàng)建 deployment 和 service:

kubectl apply -f probedemo.yaml

查看運(yùn)行的 pod:

image.png

使用如下命令暴露服務(wù)端口:

kubectl port-forward service/probedemo 8080 8080

調(diào)用存活性檢查的 broken 事件全肮,地址如下:

curl http://localhost:8080/state/broken

等待大概一分鐘,發(fā)現(xiàn) pod 已經(jīng)重啟一次

image.png

請(qǐng)求拒絕流量棘捣,地址如下:

curl http://localhost:8080/state/refuse

可以看到服務(wù)已經(jīng)處于未準(zhǔn)備狀態(tài):

image.png

查看 pod 的事件:

kubectl describe  probedemo-86cb7cc84b-djrjn
image.png

當(dāng)再次調(diào)用接受流量的請(qǐng)求:

curl http://localhost:8080/state/accept

發(fā)現(xiàn)服務(wù)已經(jīng)恢復(fù)正常:

image.png

根據(jù)這個(gè)特性辜腺,可以通過(guò)程序控制什么時(shí)候?qū)ν馓峁┓?wù),當(dāng)處理一些異常情況時(shí)乍恐,可以手動(dòng)拒絕請(qǐng)求评疗,待恢復(fù)正常后再提供服務(wù)。

三茵烈、總結(jié)

通過(guò)上面的實(shí)踐百匆,我們測(cè)試了spring boot 應(yīng)用在 k8s 中的健康檢查,配置非常簡(jiǎn)單:

  1. 只需要引入 spring-boot-starter-actuator 依賴(lài)即可呜投,不需要其他額外配置
  2. 在 k8s 的部署清單中根據(jù)官方文檔做如下配置:
image.png

參考文章

https://blog.csdn.net/boling_cavalry/article/details/106607225

https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints.kubernetes-probes

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末加匈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子仑荐,更是在濱河造成了極大的恐慌雕拼,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件释漆,死亡現(xiàn)場(chǎng)離奇詭異悲没,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)男图,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)示姿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人逊笆,你說(shuō)我怎么就攤上這事栈戳。” “怎么了难裆?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵子檀,是天一觀的道長(zhǎng)镊掖。 經(jīng)常有香客問(wèn)我,道長(zhǎng)褂痰,這世上最難降的妖魔是什么亩进? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮缩歪,結(jié)果婚禮上归薛,老公的妹妹穿的比我還像新娘。我一直安慰自己匪蝙,他們只是感情好主籍,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著逛球,像睡著了一般千元。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上颤绕,一...
    開(kāi)封第一講書(shū)人閱讀 52,736評(píng)論 1 312
  • 那天幸海,我揣著相機(jī)與錄音,去河邊找鬼屋厘。 笑死涕烧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的汗洒。 我是一名探鬼主播议纯,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼溢谤!你這毒婦竟也來(lái)了瞻凤?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤世杀,失蹤者是張志新(化名)和其女友劉穎阀参,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體瞻坝,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蛛壳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了所刀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衙荐。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖浮创,靈堂內(nèi)的尸體忽然破棺而出忧吟,到底是詐尸還是另有隱情,我是刑警寧澤斩披,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布溜族,位于F島的核電站讹俊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏煌抒。R本人自食惡果不足惜仍劈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望摧玫。 院中可真熱鬧耳奕,春花似錦绑青、人聲如沸诬像。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)坏挠。三九已至,卻和暖如春邪乍,著一層夾襖步出監(jiān)牢的瞬間降狠,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工庇楞, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留榜配,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓吕晌,卻偏偏與公主長(zhǎng)得像蛋褥,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子睛驳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361