天天看點

Spring Boot 容器鏡像分層建構

本文參考文檔
  • ​​Spring Boot 容器鏡像​​
  • ​​Spring Boot Maven 插件參考指南​​
本文使用 Maven 進行配置,Gradle 可以參考下面文檔
  • ​​Spring Boot Gradle 插件參考指南​​

一、場景

最常見的是容器鏡像,将依賴、代碼、配置分層後可以利用容器鏡像層緩存機制加快建構和下載下傳,這個場景使用分層是最優最簡單的。

k8s 移除 Docker 後,文檔中的 Docker 都去掉了…現在也把常說的 Docker 鏡像 改成了 容器鏡像

二、分層配置

如果不需要自定義分層,這一步可以跳過

在項目根目錄中添加 layers.xml 配置檔案,檔案内容如下:

<layers xmlns="http://www.springframework.org/schema/boot/layers"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
                          https://www.springframework.org/schema/boot/layers/layers-2.6.xsd">
    <application>
        <into layer="spring-boot-loader">
            <include>org/springframework/boot/loader/**</include>
        </into>
        <into layer="application"/>
    </application>
    <dependencies>
        <into layer="application">
            <includeModuleDependencies/>
        </into>
        <into layer="snapshot-dependencies">
            <include>*:*:*SNAPSHOT</include>
        </into>
        <into layer="sencond-dependencies">
            <include>com.example:*:*</include>
        </into>
        <into layer="dependencies"/>
    </dependencies>
    <layerOrder>
        <layer>dependencies</layer>
        <layer>spring-boot-loader</layer>
        <layer>sencond-dependencies</layer>
        <layer>snapshot-dependencies</layer>
        <layer>application</layer>
    </layerOrder>
</layers>      

和官方示例相比這裡增加了 sencond-dependencies,算是二方庫依賴,如果公司有自己架構,自己平台,根據依賴的穩定性(修改頻率)進行更細的分層。

依賴分層設計,可以參考 ​​企業 Maven 依賴管理層次結構設計​​。

使用 IDEA,并且下載下傳 layers-2.6.xsd 的情況下,​

​<includeModuleDependencies/>​

​ 會報紅,如下圖所示:

Spring Boot 容器鏡像分層建構

通過檢視官方文檔和 spring boot 代碼,發現文檔、代碼和 xsd 定義存在不一緻的地方,提了 ​​issues#31115​​​ 、 ​​pr#31117​​​、​​pr#31126​​ ,代碼已經合并, xsd經過修改,和文檔保持一緻。

增加上面的配置後,修改插件使用該配置:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>${spring-boot.version}</version>
    <executions>
        <execution>
            <id>repackage</id>
            <goals>
                <goal>repackage</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <layers>
            <enabled>true</enabled>
            <configuration>${project.basedir}/layers.xml</configuration>
        </layers>
    </configuration>
</plugin>      

三、容器鏡像

建構鏡像有多種方式,官方文檔介紹了 Dockerfile 和 Buildpacks 兩種。

3.1 Dockerfile

通過 ​

​layertools​

​ 可以将 fat jar 按照分層定義中的層進行解壓,指令如下:

Usage:
  java -Djarmode=layertools -jar my-app.jar

Available commands:
  list     List layers from the jar that can be extracted
  extract  Extracts layers from the jar for image creation
  help     Help about any command      

通過 ​

​java -Djarmode=layertools -jar my-app.jar extract​

​ 即可解壓 jar 包到目前目錄。為了友善,可以直接通過 Dockerfile 的多階段建構進行,Dockerfile 如下:

FROM eclipse-temurin:11-jre as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract

FROM eclipse-temurin:11-jre
WORKDIR application
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"]      
如果有自定義分層,記得按順序加入到 COPY 部分

一般情況下 target 下面隻有一個 jar 字尾的包,此時可以直接執行下面的 Docker 指令:

docker build --tag imageName:version .      

如果有多個 jar,需要通過參數指定:

docker build  --build-arg JAR_FILE=path/to/myapp.jar --tag imageName:version .      

建構後檢視鏡像資訊:

IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
e1d22f48893d   11 seconds ago   ENTRYPOINT ["java" "org.springframework.boot…   0B        buildkit.dockerfile.v0
<missing>      11 seconds ago   COPY application/application/ ./ # buildkit     55.1kB    buildkit.dockerfile.v0
<missing>      11 seconds ago   COPY application/snapshot-dependencies/ ./ #…   46.1MB    buildkit.dockerfile.v0
<missing>      12 seconds ago   COPY application/spring-boot-loader/ ./ # bu…   252kB     buildkit.dockerfile.v0
<missing>      12 seconds ago   COPY application/dependencies/ ./ # buildkit    216MB     buildkit.dockerfile.v0
<missing>      10 minutes ago   WORKDIR /application                            0B        buildkit.dockerfile.v0
<missing>      2 months ago     /bin/sh -c set -eux;   arch="$(dpkg --print-…   210MB     
<missing>      2 months ago     /bin/sh -c #(nop)  ENV JAVA_VERSION=8u322       0B        
<missing>      2 months ago     /bin/sh -c #(nop)  ENV LANG=C.UTF-8             0B        
<missing>      2 months ago     /bin/sh -c #(nop)  ENV PATH=/usr/local/openj…   0B        
<missing>      2 months ago     /bin/sh -c { echo '#/bin/sh'; echo 'echo "$J…   27B       
<missing>      2 months ago     /bin/sh -c #(nop)  ENV JAVA_HOME=/usr/local/…   0B        
<missing>      2 months ago     /bin/sh -c set -eux;  apt-get update;  apt-g…   4.88MB    
<missing>      2 months ago     /bin/sh -c #(nop)  CMD ["bash"]                 0B        
<missing>      2 months ago     /bin/sh -c #(nop) ADD file:d48a85028743f16ed…   80.4MB          

層資訊:

"RootFS": {
    "Type": "layers",
    "Layers": [
        "sha256:1401df2b50d5de5a743b7bac3238ef3b7ce905ae39f54707b0ebb8eda3ab10bc",
        "sha256:43015d7c36457e91ff0994082e7016335d5aa7690e8b5c799d41c2bab47f086c",
        "sha256:f1bceed991c5891bd4bf3ad6e1ade5334e2c40ada40305b59fbf0a275ebbed17",
        "sha256:7a49a2f5a65e2f57886dd32ffe85542283b249ccefd7a1b5379632030912d804",
        "sha256:8791c93670dee0e973efce4424ea9b33caa49e7ef15c8e0bde1912b51c349524",
        "sha256:94c6796cee53f42ae2478affbfc8510c97c76e65015ec46309f83265df078ef8",
        "sha256:033be8a54968fe881ce71510862eacc0c3f3bdb6eb2af1a9130704bbe7442ae8",
        "sha256:ab0700832472168ad4a9060b3fbedf8cc44f22ff1d074aebb67d9ee466796515",
        "sha256:06a62903d01189112c0c8b6b68debaa170228e9d7cf868e1b9959001e877a4c4"
    ]
}      

對代碼進行簡單修改後,重新建構鏡像,再次檢視:

IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
cc399ec3ba61   13 seconds ago   ENTRYPOINT ["java" "org.springframework.boot…   0B        buildkit.dockerfile.v0
<missing>      13 seconds ago   COPY application/application/ ./ # buildkit     52.9kB    buildkit.dockerfile.v0
<missing>      3 minutes ago    COPY application/snapshot-dependencies/ ./ #…   46.1MB    buildkit.dockerfile.v0
<missing>      3 minutes ago    COPY application/spring-boot-loader/ ./ # bu…   252kB     buildkit.dockerfile.v0
<missing>      3 minutes ago    COPY application/dependencies/ ./ # buildkit    216MB     buildkit.dockerfile.v0
<missing>      13 minutes ago   WORKDIR /application                            0B        buildkit.dockerfile.v0
<missing>      2 months ago     /bin/sh -c set -eux;   arch="$(dpkg --print-…   210MB     
<missing>      2 months ago     /bin/sh -c #(nop)  ENV JAVA_VERSION=8u322       0B        
<missing>      2 months ago     /bin/sh -c #(nop)  ENV LANG=C.UTF-8             0B        
<missing>      2 months ago     /bin/sh -c #(nop)  ENV PATH=/usr/local/openj…   0B        
<missing>      2 months ago     /bin/sh -c { echo '#/bin/sh'; echo 'echo "$J…   27B       
<missing>      2 months ago     /bin/sh -c #(nop)  ENV JAVA_HOME=/usr/local/…   0B        
<missing>      2 months ago     /bin/sh -c set -eux;  apt-get update;  apt-g…   4.88MB    
<missing>      2 months ago     /bin/sh -c #(nop)  CMD ["bash"]                 0B        
<missing>      2 months ago     /bin/sh -c #(nop) ADD file:d48a85028743f16ed…   80.4MB          

層資訊:

"RootFS": {
    "Type": "layers",
    "Layers": [
        "sha256:1401df2b50d5de5a743b7bac3238ef3b7ce905ae39f54707b0ebb8eda3ab10bc",
        "sha256:43015d7c36457e91ff0994082e7016335d5aa7690e8b5c799d41c2bab47f086c",
        "sha256:f1bceed991c5891bd4bf3ad6e1ade5334e2c40ada40305b59fbf0a275ebbed17",
        "sha256:7a49a2f5a65e2f57886dd32ffe85542283b249ccefd7a1b5379632030912d804",
        "sha256:8791c93670dee0e973efce4424ea9b33caa49e7ef15c8e0bde1912b51c349524",
        "sha256:94c6796cee53f42ae2478affbfc8510c97c76e65015ec46309f83265df078ef8",
        "sha256:033be8a54968fe881ce71510862eacc0c3f3bdb6eb2af1a9130704bbe7442ae8",
        "sha256:ab0700832472168ad4a9060b3fbedf8cc44f22ff1d074aebb67d9ee466796515",
        "sha256:4c0f187537195a34793722097d719f0c1247fec1648a6bdcf08f42556348af74"
    ]
}      

和上面相比隻有最上面的一層不同,通過分層盡可能利用Docker層緩存,可以減小鏡像的差異,使得鏡像更新時,隻需要下載下傳有差異的這一小部分。

建構鏡像後,我們通過 ​​grype​​ 檢測鏡像是否存在安全漏洞:

$ grype 鏡像名:版本
 ✔ Vulnerability DB        [no update available]
 ✔ Loaded image            
 ✔ Parsed image            
 ✔ Cataloged packages      [521 packages]
 ✔ Scanned image           [136 vulnerabilities]

NAME                           INSTALLED           FIXED-IN                 TYPE          VULNERABILITY        SEVERITY   
apt                            2.2.4                                        deb           CVE-2011-3374        Negligible  
aviator                        3.3.0                                        java-archive  GHSA-xpv2-8ppj-79hh  Critical    
bsdutils                       1:2.36.1-8+deb11u1                           deb           CVE-2022-0563        Negligible  
coreutils                      8.32-4+b1                                    deb           CVE-2017-18018       Negligible  
coreutils                      8.32-4+b1           (won't fix)              deb           CVE-2016-2781        Low         
e2fsprogs                      1.46.2-2            (won't fix)              deb           CVE-2022-1304        High        
gzip                           1.10-4              1.10-4+deb11u1           deb           CVE-2022-1271        Unknown     
libapt-pkg6.0                  2.2.4                                        deb           CVE-2011-3374        Negligible  
...      

還可以對代碼進行檢查(​

​dir:.​

​ 目前目錄):

$ grype dir:.  
 ✔ Vulnerability DB        [no update available]
 ✔ Indexed .               
 ✔ Cataloged packages      [378 packages]
 ✔ Scanned image           [36 vulnerabilities]

NAME                           INSTALLED     FIXED-IN      TYPE          VULNERABILITY        SEVERITY 
aviator                        3.3.0                       java-archive  GHSA-xpv2-8ppj-79hh  Critical  
maven-aether-provider          3.1.1                       java-archive  CVE-2021-26291       Critical  
maven-artifact                 3.1.1                       java-archive  CVE-2021-26291       Critical  
maven-common-artifact-filters  3.2.0                       java-archive  CVE-2021-26291       Critical  
maven-core                     3.1.1                       java-archive  CVE-2021-26291       Critical  
maven-model                    3.1.1                       java-archive  CVE-2021-26291       Critical  
maven-model-builder            3.1.1                       java-archive  CVE-2021-26291       Critical  
maven-repository-metadata      3.1.1                       java-archive  CVE-2021-26291       Critical  
maven-settings                 3.1.1                       java-archive  CVE-2021-26291       Critical  
maven-settings-builder         3.1.1                       java-archive  CVE-2021-26291       Critical  
maven-shared-utils             3.3.3                       java-archive  CVE-2021-26291       Critical  
minio                          8.3.8                       java-archive  CVE-2021-21390       Medium    
minio                          8.3.8                       java-archive  CVE-2020-11012       High      
minio                          8.3.8                       java-archive  CVE-2021-21287       High      

3.2 Buildpacks

Spring Boot 插件內建了 Buildpacks 功能,插件配置如下:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>build-image</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>      

執行 ​

​mvn org.springframework.boot:spring-boot-maven-plugin:build-image​

​ 即可建構鏡像。

建構完鏡像後,運作時可能會遇到中文亂碼,可以通過下面兩種方式解決:

1 運作鏡像時,通過環境變量指定編碼:

​​

​docker run -e JAVA_OPTS="-Dfile.encoding=UTF-8" <image_name>​

2 配置 spring boot 插件,添加預設的 JVM 配置:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>${spring-boot.version}</version>
    <executions>
        <execution>
            <goals>
                <goal>build-image</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <image>
            <env>
                <BPE_DELIM_JAVA_TOOL_OPTIONS xml:space="preserve"> </BPE_DELIM_JAVA_TOOL_OPTIONS>
                <BPE_APPEND_JAVA_TOOL_OPTIONS>-Dfile.encoding=UTF-8</BPE_APPEND_JAVA_TOOL_OPTIONS>
            </env>
        </image>
    </configuration>
</plugin>      

環境變量配置規則文檔

​​​https://github.com/paketo-buildpacks/environment-variables​​ 上面兩個ENV配置介紹如下:

  1. 追加分隔符使用空格,xml配置保留白格(​

    ​xml:space="preserve"​

    ​)。
  2. 追加JVM參數

四、Jar 包運作

java org.springframework.boot.loader.JarLauncher