https://github.com/zq2599/blog_demosjava
通過前面的知識積累,咱們知道了SpringBoot-2.3新增的探針規範以及適用場景,這裏作個簡短的回顧:node
小結完畢,接下來開始實打實的編碼和操做實戰,驗證上述理論;nginx
本次實戰有兩個環境:開發和運行環境,其中開發環境信息以下:git
運行環境信息以下:程序員
事實證實,用Ubuntu桌面版做爲開發環境是可行的,體驗十分順暢,IDEA、SubLime、SSH、Chrome、微信都能正常使用,下圖是個人Ubuntu開發環境:github
本次實戰包括如下內容:web
名稱 | 連接 | 備註 |
---|---|---|
項目主頁 | https://github.com/zq2599/blog_demos | 該項目在GitHub上的主頁 |
git倉庫地址(https) | https://github.com/zq2599/blog_demos.git | 該項目源碼的倉庫地址,https協議 |
git倉庫地址(ssh) | git@github.com:zq2599/blog_demos.git | 該項目源碼的倉庫地址,ssh協議 |
<?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.3.0.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.bolingcavalry</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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.3.0.RELEASE</version> <!--該配置會在jar中增長layer描述文件,以及提取layer的工具--> <configuration> <layers> <enabled>true</enabled> </layers> </configuration> </plugin> </plugins> </build> </project>
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); } }
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: 監聽系統事件的類 <br> * date: 2020/6/4 下午12:57 <br> * author: willzhao <br> * email: zq2599@gmail.com <br> * version: 1.0 <br> */ @Component @Slf4j public class AvailabilityListener { /** * 監聽系統消息, * AvailabilityChangeEvent類型的消息都從會觸發此方法被回調 * @param event */ @EventListener public void onStateChange(AvailabilityChangeEvent<? extends AvailabilityState> event) { log.info(event.getState().getClass().getSimpleName() + " : " + event.getState()); } }
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() + "<br>readinessState : " + applicationAvailability.getReadinessState() + "<br>" + new Date(); } }
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 <br> * date: 2020/6/4 下午1:21 <br> * author: willzhao <br> * email: zq2599@gmail.com <br> * version: 1.0 <br> */ @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(); } }
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 <br> * date: 2020/6/4 下午4:38 <br> * author: willzhao <br> * email: zq2599@gmail.com <br> * version: 1.0 <br> */ @RestController public class Hello { /** * 返回的是當前服務器IP地址,在k8s環境就是pod地址 * @return * @throws SocketException */ @RequestMapping(value="/hello") public String hello() throws SocketException { List<Inet4Address> addresses = getLocalIp4AddressFromNetworkInterface(); if(null==addresses || addresses.isEmpty()) { return "empty ip address, " + new Date(); } return addresses.get(0).toString() + ", " + new Date(); } public static List<Inet4Address> getLocalIp4AddressFromNetworkInterface() throws SocketException { List<Inet4Address> addresses = new ArrayList<>(1); Enumeration e = NetworkInterface.getNetworkInterfaces(); if (e == null) { return addresses; } while (e.hasMoreElements()) { NetworkInterface n = (NetworkInterface) e.nextElement(); if (!isValidInterface(n)) { continue; } Enumeration ee = n.getInetAddresses(); while (ee.hasMoreElements()) { InetAddress i = (InetAddress) ee.nextElement(); if (isValidAddress(i)) { addresses.add((Inet4Address) i); } } } return addresses; } /** * 過濾迴環網卡、點對點網卡、非活動網卡、虛擬網卡並要求網卡名字是eth或ens開頭 * @param ni 網卡 * @return 若是知足要求則true,不然false */ private static boolean isValidInterface(NetworkInterface ni) throws SocketException { return !ni.isLoopback() && !ni.isPointToPoint() && ni.isUp() && !ni.isVirtual() && (ni.getName().startsWith("eth") || ni.getName().startsWith("ens")); } /** * 判斷是不是IPv4,而且內網地址並過濾迴環地址. */ private static boolean isValidAddress(InetAddress address) { return address instanceof Inet4Address && address.isSiteLocalAddress() && !address.isLoopbackAddress(); } }
以上就是該SpringBoot工程的全部代碼了,請確保能夠編譯運行;spring
# 指定基礎鏡像,這是分階段構建的前期階段 FROM openjdk:8u212-jdk-stretch as builder # 執行工做目錄 WORKDIR application # 配置參數 ARG JAR_FILE=target/*.jar # 將編譯構建獲得的jar文件複製到鏡像空間中 COPY ${JAR_FILE} application.jar # 經過工具spring-boot-jarmode-layertools從application.jar中提取拆分後的構建結果 RUN java -Djarmode=layertools -jar application.jar extract # 正式構建鏡像 FROM openjdk:8u212-jdk-stretch WORKDIR application # 前一階段從jar中提取除了多個文件,這裏分別執行COPY命令複製到鏡像空間中,每次COPY都是一個layer COPY --from=builder application/dependencies/ ./ COPY --from=builder application/spring-boot-loader/ ./ COPY --from=builder application/snapshot-dependencies/ ./ COPY --from=builder application/application/ ./ ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
mvn clean package -U -DskipTests
sudo docker build -t bolingcavalry/probedemo:0.0.1 .
SpringBoot的鏡像準備完畢,接下來要讓kubernetes環境用上這個鏡像;docker
此時的鏡像保存在開發環境的電腦上,能夠有如下三種方式加載到kubernetes環境:shell
以上三種方法的優缺點整理以下:
個人kubernetes環境只有一臺電腦,所以用的是方法三,參考命令以下(建議安裝sshpass,就不用每次輸入賬號密碼了):
# 將鏡像保存爲tar文件 sudo docker save bolingcavalry/probedemo:0.0.1 > probedemo.tar # scp到kubernetes服務器 sshpass -p 888888 scp ./probedemo.tar root@192.168.50.135:/root/temp/202006/04/ # 遠程執行ssh命令,加載docker鏡像 sshpass -p 888888 ssh root@192.168.50.135 "docker load < /root/temp/202006/04/probedemo.tar"
apiVersion: v1 kind: Service metadata: name: probedemo spec: type: NodePort ports: - port: 8080 nodePort: 30080 selector: name: probedemo --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: probedemo spec: replicas: 2 template: metadata: labels: name: probedemo spec: containers: - name: probedemo image: bolingcavalry/probedemo:0.0.1 tty: true 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 ports: - containerPort: 8080 resources: requests: memory: "512Mi" cpu: "100m" limits: memory: "1Gi" cpu: "500m"
至此,從編碼到部署都完成了,接下來驗證SpringBoot-2.3.0.RELEASE的探針技術;
kubernetes所在機器的IP地址是192.168.50.135,所以SpringBoot服務的訪問地址是http://192.168.50.135:30080/xxx
訪問地址http://192.168.50.135:30080/actuator/health/liveness,返回碼以下圖紅框,可見存活探針已開啓:
curl http://10.233.90.195:8080/statewriter/accept