掌握SpringBoot-2.3的容器探針:實戰篇
阿新 • • 發佈:2020-09-18
### 歡迎訪問我的GitHub
[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos)
- 內容:原創文章分類彙總,及配套原始碼,涉及Java、Docker、K8S、DevOPS等
經過多篇知識積累終於來到實戰章節,親愛的讀者們,請將裝備就位,一起動手體驗SpringBoot官方帶給我們的最新技術;
### 關於《SpringBoot-2.3容器化技術》系列
- 《SpringBoot-2.3容器化技術》系列,旨在和大家一起學習實踐2.3版本帶來的最新容器化技術,讓咱們的Java應用更加適應容器化環境,在雲端計算時代依舊緊跟主流,保持競爭力;
- 全系列文章分為主題和輔助兩部分,主題部分如下:
1. [《體驗SpringBoot(2.3)應用製作Docker映象(官方方案)》](https://blog.csdn.net/boling_cavalry/article/details/106597358);
2. [《詳解SpringBoot(2.3)應用製作Docker映象(官方方案)》](https://blog.csdn.net/boling_cavalry/article/details/106598189);
3. [《掌握SpringBoot-2.3的容器探針:基礎篇》](https://blog.csdn.net/boling_cavalry/article/details/106605264);
4. [《掌握SpringBoot-2.3的容器探針:深入篇》](https://blog.csdn.net/boling_cavalry/article/details/106606442);
5. [《掌握SpringBoot-2.3的容器探針:實戰篇》](https://blog.csdn.net/boling_cavalry/article/details/106607225);
- 輔助部分是一些參考資料和備忘總結,如下:
1. [《SpringBoot-2.3映象方案為什麼要做多個layer》](https://blog.csdn.net/boling_cavalry/article/details/106600620);
2. [《設定非root賬號不用sudo直接執行docker命令》](https://blog.csdn.net/boling_cavalry/article/details/106590784);
3. [《開發階段,將SpringBoot應用快速部署到K8S》](https://blog.csdn.net/boling_cavalry/article/details/106594392);
### SpringBoot-2.3容器探針知識點小結
經過前面的知識積累,我們知道了SpringBoot-2.3新增的探針規範以及適用場景,這裡做個簡短的回顧:
1. kubernetes要求業務容器提供一個名為livenessProbe的地址,kubernetes會定時訪問該地址,如果該地址的返回碼不在200到400之間,kubernetes認為該容器不健康,會殺死該容器重建新的容器,這個地址就是存活探針;
2. kubernetes要求業務容器提供一個名為readinessProbe的地址,kubernetes會定時訪問該地址,如果該地址的返回碼不在200到400之間,kubernetes認為該容器無法對外提供服務,不會把請求排程到該容器,這個地址就是就緒探針;
3. SpringBoot的2.3.0.RELEASE釋出了兩個新的actuator地址,/actuator/health/liveness和/actuator/health/readiness,前者用作存活探針,後者用作就緒探針,這兩個地址的返回值來自兩個新增的actuator:Liveness State和Readiness State;
4. SpringBoot應用根據特殊環境變數是否存在來判定自己是否執行在容器環境,如果是,/actuator/health/liveness和/actuator/health/readiness這兩個地址就有返回碼,具體的值是和應用的狀態有對應關係的,例如應用啟動過程中,/actuator/health/readiness返回503,啟動成功後返回200;
5. 業務應用可以通過Spring系統事件機制來讀取Liveness State和Readiness State,也可以訂閱這兩個actuator的變更事件;
6. 業務應用可以通過Spring系統事件機制來修改Liveness State和Readiness State,此時/actuator/health/liveness和/actuator/health/readiness的返回值都會發生變更,從而影響kubernetes對此容器的行為(參照第一點和第二點),例如livenessProbe返回碼變成503,導致kubernetes認為容器不健康,從而殺死容器;
小結完畢,接下來開始實打實的編碼和操作實戰,驗證上述理論;
### 實戰環境資訊
本次實戰有兩個環境:開發和執行環境,其中開發環境資訊如下:
1. 作業系統:Ubuntu 20.04 LTS 桌面版
2. CPU :2.30GHz × 4,記憶體:32G,硬碟:1T NVMe
3. JDK:1.8.0_231
4. MAVEN:3.6.3
5. SpringBoot:2.3.0.RELEASE
6. Docker:19.03.10
7. 開發工具:IDEA 2020.1.1 (Ultimate Edition)
執行環境資訊如下:
1. 作業系統:CentOS Linux release 7.8.2003
2. Kubernetes:1.15
事實證明,用Ubuntu桌面版作為開發環境是可行的,體驗十分順暢,IDEA、SubLime、SSH、Chrome、微信都能正常使用,下圖是我的Ubuntu開發環境:
![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202009/485422-20200918074820683-1698100241.jpg)
### 實戰內容簡介
本次實戰包括以下內容:
1. 開發SpringBoot應用,部署在kubernetes;
2. 檢查應用狀態和kubernetes的pod狀態的關聯變化;
3. 修改Readiness State,看kubernetes是否還會把請求排程到pod;
4. 修改Liveness State,看kubernetes會不是殺死pod;
### 原始碼下載
1. 本次實戰用到了一個普通的SpringBoot工程,原始碼可在GitHub下載到,地址和連結資訊如下表所示(https://github.com/zq2599/blog_demos):
| 名稱 | 連結 | 備註|
| :-------- | :----| :----|
| 專案主頁| https://github.com/zq2599/blog_demos | 該專案在GitHub上的主頁 |
| git倉庫地址(https)| https://github.com/zq2599/blog_demos.git | 該專案原始碼的倉庫地址,https協議 |
| git倉庫地址(ssh)| [email protected]:zq2599/blog_demos.git | 該專案原始碼的倉庫地址,ssh協議 |
2. 這個git專案中有多個資料夾,本章的應用在probedemo資料夾下,如下圖紅框所示:
![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202009/485422-20200918074821211-349033275.png)
### 開發SpringBoot應用
1. 請在IDEA上安裝lombok外掛:
![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202009/485422-20200918074821604-448692037.png)
2. 在IDEA上新建名為probedemo的SpringBoot工程,版本選擇2.3.0:
![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202009/485422-20200918074821948-2121997571.png)
3. 該工程的pom.xml內容如下,注意要有spring-boot-starter-actuator和lombok依賴,另外外掛spring-boot-maven-plugin也要增加layers節點:
```xml
```
4. 應用啟動類ProbedemoApplication是個最普通的啟動類:
```java
package com.bolingcavalry.probedemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProbedemoApplication {
public static void main(String[] args) {
SpringApplication.run(ProbedemoApplication.class, args);
}
}
```
5. 增加一個監聽類,可以監聽存活和就緒狀態的變化:
```java
package com.bolingcavalry.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;
/**
* description: 監聽系統事件的類
* date: 2020/6/4 下午12:57
* author: willzhao
* email: [email protected]
* version: 1.0
*/ @Component @Slf4j public class AvailabilityListener { /** * 監聽系統訊息, * AvailabilityChangeEvent型別的訊息都從會觸發此方法被回撥 * @param event */ @EventListener public void onStateChange(AvailabilityChangeEvent event) { log.info(event.getState().getClass().getSimpleName() + " : " + event.getState()); } } ``` 6. 增加名為StateReader的Controller的Controller,用於獲取存活和就緒狀態: ```java package com.bolingcavalry.probedemo.controller; import org.springframework.boot.availability.ApplicationAvailability; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.Date; @RestController @RequestMapping("/statereader") public class StateReader { @Resource ApplicationAvailability applicationAvailability; @RequestMapping(value="/get") public String state() { return "livenessState : " + applicationAvailability.getLivenessState() + "
readinessState : " + applicationAvailability.getReadinessState() + "
" + new Date(); } } ``` 7. 增加名為StateWritter的Controller,用於設定存活和就緒狀態: ```java package com.bolingcavalry.probedemo.controller; 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.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.Date; /** * description: 修改狀態的controller
* date: 2020/6/4 下午1:21
* author: willzhao
* email: [email protected]
* version: 1.0
*/ @RestController @RequestMapping("/staterwriter") public class StateWritter { @Resource ApplicationEventPublisher applicationEventPublisher; /** * 將存活狀態改為BROKEN(會導致kubernetes殺死pod) * @return */ @RequestMapping(value="/broken") public String broken(){ AvailabilityChangeEvent.publish(applicationEventPublisher, StateWritter.this, LivenessState.BROKEN); return "success broken, " + new Date(); } /** * 將存活狀態改為CORRECT * @return */ @RequestMapping(value="/correct") public String correct(){ AvailabilityChangeEvent.publish(applicationEventPublisher, StateWritter.this, LivenessState.CORRECT); return "success correct, " + new Date(); } /** * 將就緒狀態改為REFUSING_TRAFFIC(導致kubernetes不再把外部請求轉發到此pod) * @return */ @RequestMapping(value="/refuse") public String refuse(){ AvailabilityChangeEvent.publish(applicationEventPublisher, StateWritter.this, ReadinessState.REFUSING_TRAFFIC); return "success refuse, " + new Date(); } /** * 將就緒狀態改為ACCEPTING_TRAFFIC(導致kubernetes會把外部請求轉發到此pod) * @return */ @RequestMapping(value="/accept") public String accept(){ AvailabilityChangeEvent.publish(applicationEventPublisher, StateWritter.this, ReadinessState.ACCEPTING_TRAFFIC); return "success accept, " + new Date(); } } ``` 8. 增加名為Hello的controller,此介面能返回當前pod的IP地址,在後面測試時會用到: ```java package com.bolingcavalry.probedemo.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.List; /** * description: hello demo
* date: 2020/6/4 下午4:38
* author: willzhao
* email: [email protected]
* version: 1.0
*/ @RestController public class Hello { /** * 返回的是當前伺服器IP地址,在k8s環境就是pod地址 * @return * @throws SocketException */ @RequestMapping(value="/hello") public String hello() throws SocketException {
* date: 2020/6/4 下午12:57
* author: willzhao
* email: [email protected]
* version: 1.0
*/ @Component @Slf4j public class AvailabilityListener { /** * 監聽系統訊息, * AvailabilityChangeEvent型別的訊息都從會觸發此方法被回撥 * @param event */ @EventListener public void onStateChange(AvailabilityChangeEvent event) { log.info(event.getState().getClass().getSimpleName() + " : " + event.getState()); } } ``` 6. 增加名為StateReader的Controller的Controller,用於獲取存活和就緒狀態: ```java package com.bolingcavalry.probedemo.controller; import org.springframework.boot.availability.ApplicationAvailability; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.Date; @RestController @RequestMapping("/statereader") public class StateReader { @Resource ApplicationAvailability applicationAvailability; @RequestMapping(value="/get") public String state() { return "livenessState : " + applicationAvailability.getLivenessState() + "
readinessState : " + applicationAvailability.getReadinessState() + "
" + new Date(); } } ``` 7. 增加名為StateWritter的Controller,用於設定存活和就緒狀態: ```java package com.bolingcavalry.probedemo.controller; 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.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.Date; /** * description: 修改狀態的controller
* date: 2020/6/4 下午1:21
* author: willzhao
* email: [email protected]
* version: 1.0
*/ @RestController @RequestMapping("/staterwriter") public class StateWritter { @Resource ApplicationEventPublisher applicationEventPublisher; /** * 將存活狀態改為BROKEN(會導致kubernetes殺死pod) * @return */ @RequestMapping(value="/broken") public String broken(){ AvailabilityChangeEvent.publish(applicationEventPublisher, StateWritter.this, LivenessState.BROKEN); return "success broken, " + new Date(); } /** * 將存活狀態改為CORRECT * @return */ @RequestMapping(value="/correct") public String correct(){ AvailabilityChangeEvent.publish(applicationEventPublisher, StateWritter.this, LivenessState.CORRECT); return "success correct, " + new Date(); } /** * 將就緒狀態改為REFUSING_TRAFFIC(導致kubernetes不再把外部請求轉發到此pod) * @return */ @RequestMapping(value="/refuse") public String refuse(){ AvailabilityChangeEvent.publish(applicationEventPublisher, StateWritter.this, ReadinessState.REFUSING_TRAFFIC); return "success refuse, " + new Date(); } /** * 將就緒狀態改為ACCEPTING_TRAFFIC(導致kubernetes會把外部請求轉發到此pod) * @return */ @RequestMapping(value="/accept") public String accept(){ AvailabilityChangeEvent.publish(applicationEventPublisher, StateWritter.this, ReadinessState.ACCEPTING_TRAFFIC); return "success accept, " + new Date(); } } ``` 8. 增加名為Hello的controller,此介面能返回當前pod的IP地址,在後面測試時會用到: ```java package com.bolingcavalry.probedemo.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.List; /** * description: hello demo
* date: 2020/6/4 下午4:38
* author: willzhao
* email: [email protected]
* version: 1.0
*/ @RestController public class Hello { /** * 返回的是當前伺服器IP地址,在k8s環境就是pod地址 * @return * @throws SocketException */ @RequestMapping(value="/hello") public String hello() throws SocketException {