你的SpringBoot應用真的部署更新成功了嗎
前提
當我們在生產環境部署了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