你的SpringBoot應用真的部署更新成功了嗎


前提

當我們在生產環境部署了SpringBoot應用的時候,雖然可以通過Jenkins的構建狀態和Linuxps命令去感知應用是否在新的一次發布中部署和啟動成功,但是這種監控手段是運維層面的。那么,可以提供一種手段能夠在應用層面感知服務在新的一次發布中的構建部署和啟動是否成功嗎?這個問題筆者花了一點時間想通了這個問題,通過這篇文章提供一個簡單的實現思路。

基本思路

其實基本思路很簡單,一般SpringBoot應用會使用Maven插件打包(筆者不熟悉Gradle,所以暫時不對Gradle做分析),所以可以這樣考慮:

  1. Maven插件打包的時候,把構建時間pom文件中的版本號都寫到jar包的描述文件中,正確來說就是MANIFEST.MF文件中。
  2. 引入spring-boot-starter-actuator,通過/actuator/info端點去暴露應用的信息(最好控制網絡訪問權限為只允許內網訪問)。
  3. 把第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-ArchiverAvailable VariablesSpringBoot的配置文件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一些實用技巧實現了應用層面監控應用是否正常打包部署更新和啟動成功的問題。

原文鏈接

(本文完 e-a-20191209:1:39 c-1-d)

技術公眾號(《Throwable文摘》),不定期推送筆者原創技術文章(絕不抄襲或者轉載):

娛樂公眾號(《天天沙雕》),甄選奇趣沙雕圖文和視頻不定期推送,緩解生活工作壓力:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM