前提
當我們在生產環境部署了SpringBoot
應用的時候,雖然可以通過Jenkins
的構建狀態和Linux
的ps
命令去感知應用是否在新的一次發布中部署和啟動成功,但是這種監控手段是運維層面的。那么,可以提供一種手段能夠在應用層面感知服務在新的一次發布中的構建部署和啟動是否成功嗎?這個問題筆者花了一點時間想通了這個問題,通過這篇文章提供一個簡單的實現思路。
基本思路
其實基本思路很簡單,一般SpringBoot
應用會使用Maven
插件打包(筆者不熟悉Gradle
,所以暫時不對Gradle
做分析),所以可以這樣考慮:
Maven
插件打包的時候,把構建時間和pom
文件中的版本號都寫到jar
包的描述文件中,正確來說就是MANIFEST.MF
文件中。- 引入
spring-boot-starter-actuator
,通過/actuator/info
端點去暴露應用的信息(最好控制網絡訪問權限為只允許內網訪問)。 - 把第1步中打包到
jar
包中的MANIFEST.MF
文件的內容讀取並且加載到SpringBoot
環境屬性中的info.*
屬性中,以便可以通過/actuator/info
訪問。
思路定好了,那么下面開始實施編碼。
編碼實現
最近剛好在調研螞蟻金服的SofaStack
體系,這里引入SofaBoot
編寫示例。pom
文件如下:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>club.throwable</groupId>
<artifactId>sofa-boot-sample</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>sofa-boot-sample</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<sofa.boot.version>3.2.0</sofa.boot.version>
<spring.boot.version>2.1.0.RELEASE</spring.boot.version>
<maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss.SSS</maven.build.timestamp.format>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alipay.sofa</groupId>
<artifactId>sofaboot-dependencies</artifactId>
<version>${sofa.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alipay.sofa</groupId>
<artifactId>healthcheck-sofa-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<finalName>sofa-boot-sample</finalName>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<addBuildEnvironmentEntries>true</addBuildEnvironmentEntries>
</manifest>
<manifestEntries>
<Application-Name>${project.groupId}:${project.artifactId}:${project.version}</Application-Name>
<Build-Timestamp>${maven.build.timestamp}</Build-Timestamp>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
pom
文件中一些屬性和占位符的設置,可以參考一下這兩個鏈接:Maven-Archiver和Available Variables。SpringBoot
的配置文件application.yaml
如下:
server:
port: 9091
management:
server:
port: 10091
endpoints:
enabled-by-default: false
web:
exposure:
include: info
endpoint:
info:
enabled: true
spring:
application:
name: sofa-boot-sample
這里要注意一點:SpringBoot
應用通過其Maven
插件打出來的jar
包解壓后的目錄如下:
sofa-boot-sample.jar
- META-INF
- MANIFEST.MF
- maven ...
- org
- springframework
- boot ...
- BOOT-INF
- classes ...
- lib ...
了解此解壓目錄是我們編寫MANIFEST.MF
文件的解析實現過程的前提。編寫MANIFEST.MF
文件的解析類:
@SuppressWarnings("ConstantConditions")
public enum ManiFestFileExtractUtils {
/**
* 單例
*/
X;
private static Map<String, String> RESULT = new HashMap<>(16);
private static final Logger LOGGER = LoggerFactory.getLogger(ManiFestFileExtractUtils.class);
static {
String jarFilePath = ClassUtils.getDefaultClassLoader().getResource("").getPath().replace("!/BOOT-INF/classes!/", "");
if (jarFilePath.startsWith("file")) {
jarFilePath = jarFilePath.substring(5);
}
LOGGER.info("讀取的Jar路徑為:{}", jarFilePath);
try (JarFile jarFile = new JarFile(jarFilePath)) {
JarEntry entry = jarFile.getJarEntry("META-INF/MANIFEST.MF");
if (null != entry) {
BufferedReader reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(entry), StandardCharsets.UTF_8));
String line;
while (null != (line = reader.readLine())) {
LOGGER.info("讀取到行:{}", line);
int i = line.indexOf(":");
if (i > -1) {
String key = line.substring(0, i).trim();
String value = line.substring(i + 1).trim();
RESULT.put(key, value);
}
}
}
} catch (Exception e) {
LOGGER.warn("解析MANIFEST.MF文件異常", e);
}
}
public Map<String, String> extract() {
return RESULT;
}
}
可以通過一個CommandLineRunner
的實現把MANIFEST.MF
文件的內容寫到Environment
實例中:
@Component
public class SofaBootSampleRunner implements CommandLineRunner {
@Autowired
private ConfigurableEnvironment configurableEnvironment;
@Override
public void run(String... args) throws Exception {
MutablePropertySources propertySources = configurableEnvironment.getPropertySources();
Map<String, String> result = ManiFestFileExtractUtils.X.extract();
Properties properties = new Properties();
for (Map.Entry<String, String> entry : result.entrySet()) {
String key = "info." + entry.getKey();
properties.setProperty(key, entry.getValue());
}
if (!properties.isEmpty()) {
propertySources.addFirst(new PropertiesPropertySource("infoProperties", properties));
}
}
}
啟動類如下:
@SpringBootApplication
public class SofaBootSampleApplication {
public static void main(String[] args) {
SpringApplication.run(SofaBootSampleApplication.class, args);
}
}
最終效果
在項目的根目錄使用命令mvn package
,打出jar
包后直接啟動:
cd Jar包的目錄
java -jar sofa-boot-sample.jar
調用http://localhost:10091/actuator/info
接口輸出如下:
{
"Spring-Boot-Version": "2.1.0.RELEASE",
"Start-Class": "club.throwable.sofa.SofaBootSampleApplication",
"Main-Class": "org.springframework.boot.loader.JarLauncher",
"Manifest-Version": "1.0",
"Build-Jdk-Spec": "1.8",
"Spring-Boot-Classes": "BOOT-INF/classes/",
"Created-By": "Maven Jar Plugin 3.2.0",
"Build-Timestamp": "2019-12-08 17:41:21.844",
"Spring-Boot-Lib": "BOOT-INF/lib/",
"Application-Name": "club.throwable:sofa-boot-sample:1.0-SNAPSHOT"
}
改變pom
文件中的版本標簽<version>
為1.0.0
,再次打包並且啟動成功后調用http://localhost:10091/actuator/info
接口輸出如下:
{
"Spring-Boot-Version": "2.1.0.RELEASE",
"Start-Class": "club.throwable.sofa.SofaBootSampleApplication",
"Main-Class": "org.springframework.boot.loader.JarLauncher",
"Manifest-Version": "1.0",
"Build-Jdk-Spec": "1.8",
"Spring-Boot-Classes": "BOOT-INF/classes/",
"Created-By": "Maven Jar Plugin 3.2.0",
"Build-Timestamp": "2019-12-08 17:42:07.273",
"Spring-Boot-Lib": "BOOT-INF/lib/",
"Application-Name": "club.throwable:sofa-boot-sample:1.0.0"
}
可見構建時間戳Build-Timestamp
和服務名Application-Name
都發生了變化,達到了監控服務是否正常部署和啟動的目的。如果有多個服務節點,可以添加一個ip
屬性加以區分。
小結
這篇文章通過SpringBoot
一些實用技巧實現了應用層面監控應用是否正常打包部署更新和啟動成功的問題。
原文鏈接
- Github Page:http://throwable.club/2019/12/09/spring-boot-server-deploy-monitor
- Coding Page:http://throwable.coding.me/2019/12/09/spring-boot-server-deploy-monitor
(本文完 e-a-20191209:1:39 c-1-d)
技術公眾號(《Throwable文摘》),不定期推送筆者原創技術文章(絕不抄襲或者轉載):
娛樂公眾號(《天天沙雕》),甄選奇趣沙雕圖文和視頻不定期推送,緩解生活工作壓力: