背景
我们都知道 Spring Boot 是可以利用 Cloud Native Buildpacks(CNB)构建 Docker 镜像,只需要简单执行命令 ./gradlew bootBuildImage
就可以轻松构建一个完整可用的 Docker 镜像。但是 Halo 目前仍然采用传统的 Dockerfile + BuildKit 构建 Docker 镜像。
优点
对于 Halo 来说,这种方式构建的镜像有以下优点:
更安全。镜像用户都是非 root 用户,可以参考官方文档。
更方便。无需提前打包,无需手动编写 Dockerfile
镜像默认包含了内存计算器。容器在启动的时候,会进行自动计算最优的 JVM 内存参数,这对于小白用户来说非常友好。
缺点
既然通过 Spring Boot Gradle 插件构建 Docker 镜像有这么多优点,Halo 为什么迟迟没有采用呢?
历史遗留问题。在 Halo 2 诞生的时候,镜像运行用户一直是 root,如果我们修改为非 root 用户,将会带来比较严重的破坏性更新。
多架构 Docker 镜像难以支持。Halo 2 同时支持这些系统架构的 Docker 镜像:linux/amd64、linux/arm/v7、linux/arm64/v8、linux/ppc64le、linux/s390x。这一点 Buildpacks 目前似乎无法满足要求。直到现在,The Paketo Buildpack for Spring Boot 还不支持构建多架构 Docker 镜像。尽管我们可以自定义
builder
和buildpacks
并指定imagePlatform
来实现构建 ARM64 架构的 Docker 镜像,但仍然只是临时解决方案,我们期待上游能够彻底解决这个问题。最后,多架构镜像构建是在 Spring Boot 3.4.0-M2 中才得以实现,实现细节可参考这里。
尝试采用 CNB 构建 Docker 镜像
尽管我们目前无法直接应用,但这并不方案我们往这个方向去探索。下面我将稍微介绍一下如何利用 Spring Boot Gradle 插件为 Halo 2.20.6 构建 ARM64 架构(可根据自己的机器自定选择系统架构)的 Docker 镜像。
需要注意的是,Halo 2.20.6 依赖的是 Spring Boot 3.4.0-M3。
克隆 Halo 源码并切换到 Halo 2.20.6
gh repo clone halo-dev/halo git checkout v2.20.6
配置 Gradle 任务 bootBuildImage
tasks.named('bootBuildImage') { environment["BP_JVM_VERSION"] = "21" imageName = "johnniang/halo:${project.version}" imagePlatform = 'linux/arm64' builder = 'paketobuildpacks/builder-jammy-buildpackless-tiny' buildpacks = ['paketobuildpacks/java'] }
开始构建
./gradlew bootBuildImage -Pversion=2.20.6-alpha.1
❯ ./gradlew bootBuildImage -Pversion=2.20.6-alpha.1 > Task :application:bootBuildImage Building image 'docker.io/johnniang/halo:2.20.6-alpha.1' > Pulling builder image 'docker.io/paketobuildpacks/builder-jammy-buildpackless-tiny:latest' for platform 'linux/arm64' .................................................. > Pulled builder image 'paketobuildpacks/builder-jammy-buildpackless-tiny@sha256:4ca31d2c8e8ab91b31470734cf563f636e6217773dab7d5dadfa558c34d469d7' > Pulling run image 'docker.io/paketobuildpacks/run-jammy-tiny:latest' for platform 'linux/arm64' .................................................. > Pulled run image 'paketobuildpacks/run-jammy-tiny@sha256:402b925a81a4c6985438fd37d0b22022ca688e528bbd46a38831a3702067cced' > Pulling buildpack image 'docker.io/paketobuildpacks/java:latest' for platform 'linux/arm64' .................................................. > Pulled buildpack image 'paketobuildpacks/java@sha256:a447ec782abc8c334842f91a69f670d352d86746b710b9dfe6dbd8a5631aab1b' > Executing lifecycle version v0.20.3 > Using build cache volume 'pack-cache-af29c58f1d33.build' > Running creator [creator] ===> ANALYZING [creator] Image with name "docker.io/johnniang/halo:2.20.6-alpha.1" not found [creator] ===> DETECTING [creator] target distro name/version labels not found, reading /etc/os-release file [creator] 6 of 26 buildpacks participating [creator] paketo-buildpacks/ca-certificates 3.8.6 [creator] paketo-buildpacks/bellsoft-liberica 10.9.0 [creator] paketo-buildpacks/syft 2.1.0 [creator] paketo-buildpacks/executable-jar 6.11.3 [creator] paketo-buildpacks/dist-zip 5.8.5 [creator] paketo-buildpacks/spring-boot 5.31.2 [creator] ===> RESTORING [creator] ===> BUILDING [creator] target distro name/version labels not found, reading /etc/os-release file [creator] [creator] Paketo Buildpack for CA Certificates 3.8.6 [creator] https://github.com/paketo-buildpacks/ca-certificates [creator] Build Configuration: [creator] $BP_EMBED_CERTS false Embed certificates into the image [creator] $BP_ENABLE_RUNTIME_CERT_BINDING true Deprecated: Enable/disable certificate helper layer to add certs at runtime [creator] $BP_RUNTIME_CERT_BINDING_DISABLED false Disable certificate helper layer to add certs at runtime [creator] Launch Helper: Contributing to layer [creator] Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper [creator] [creator] Paketo Buildpack for BellSoft Liberica 10.9.0 [creator] https://github.com/paketo-buildpacks/bellsoft-liberica [creator] Build Configuration: [creator] $BP_JVM_JLINK_ARGS --no-man-pages --no-header-files --strip-debug --compress=1 configure custom link arguments (--output must be omitted) [creator] $BP_JVM_JLINK_ENABLED false enables running jlink tool to generate custom JRE [creator] $BP_JVM_TYPE JRE the JVM type - JDK or JRE [creator] $BP_JVM_VERSION 21 the Java version [creator] Launch Configuration: [creator] $BPL_DEBUG_ENABLED false enables Java remote debugging support [creator] $BPL_DEBUG_PORT 8000 configure the remote debugging port [creator] $BPL_DEBUG_SUSPEND false configure whether to suspend execution until a debugger has attached [creator] $BPL_HEAP_DUMP_PATH write heap dumps on error to this path [creator] $BPL_JAVA_NMT_ENABLED true enables Java Native Memory Tracking (NMT) [creator] $BPL_JAVA_NMT_LEVEL summary configure level of NMT, summary or detail [creator] $BPL_JFR_ARGS configure custom Java Flight Recording (JFR) arguments [creator] $BPL_JFR_ENABLED false enables Java Flight Recording (JFR) [creator] $BPL_JMX_ENABLED false enables Java Management Extensions (JMX) [creator] $BPL_JMX_PORT 5000 configure the JMX port [creator] $BPL_JVM_HEAD_ROOM 0 the headroom in memory calculation [creator] $BPL_JVM_LOADED_CLASS_COUNT 35% of classes the number of loaded classes in memory calculation [creator] $BPL_JVM_THREAD_COUNT 250 the number of threads in memory calculation [creator] $JAVA_TOOL_OPTIONS the JVM launch flags [creator] Using Java version 21 from BP_JVM_VERSION [creator] BellSoft Liberica JRE 21.0.4: Contributing to layer [creator] Downloading from https://github.com/bell-sw/Liberica/releases/download/21.0.4+9/bellsoft-jre21.0.4+9-linux-aarch64.tar.gz [creator] Verifying checksum [creator] Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jre [creator] Adding 146 container CA certificates to JVM truststore [creator] Writing env.launch/BPI_APPLICATION_PATH.default [creator] Writing env.launch/BPI_JVM_CACERTS.default [creator] Writing env.launch/BPI_JVM_CLASS_COUNT.default [creator] Writing env.launch/BPI_JVM_SECURITY_PROVIDERS.default [creator] Writing env.launch/JAVA_HOME.default [creator] Writing env.launch/JAVA_TOOL_OPTIONS.append [creator] Writing env.launch/JAVA_TOOL_OPTIONS.delim [creator] Writing env.launch/MALLOC_ARENA_MAX.default [creator] Launch Helper: Contributing to layer [creator] Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/java-opts [creator] Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/jvm-heap [creator] Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/link-local-dns [creator] Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/memory-calculator [creator] Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/security-providers-configurer [creator] Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/jmx [creator] Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/jfr [creator] Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/openssl-certificate-loader [creator] Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/security-providers-classpath-9 [creator] Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/debug-9 [creator] Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/nmt [creator] Java Security Properties: Contributing to layer [creator] Writing env.launch/JAVA_SECURITY_PROPERTIES.default [creator] Writing env.launch/JAVA_TOOL_OPTIONS.append [creator] Writing env.launch/JAVA_TOOL_OPTIONS.delim [creator] [creator] Paketo Buildpack for Syft 2.1.0 [creator] https://github.com/paketo-buildpacks/syft [creator] Downloading from https://github.com/anchore/syft/releases/download/v1.12.2/syft_1.12.2_linux_arm64.tar.gz [creator] Verifying checksum [creator] Writing env.build/SYFT_CHECK_FOR_APP_UPDATE.default [creator] [creator] Paketo Buildpack for Executable JAR 6.11.3 [creator] https://github.com/paketo-buildpacks/executable-jar [creator] Class Path: Contributing to layer [creator] Writing env/CLASSPATH.delim [creator] Writing env/CLASSPATH.prepend [creator] Process types: [creator] executable-jar: java org.springframework.boot.loader.launch.JarLauncher (direct) [creator] task: java org.springframework.boot.loader.launch.JarLauncher (direct) [creator] web: java org.springframework.boot.loader.launch.JarLauncher (direct) [creator] [creator] Paketo Buildpack for Spring Boot 5.31.2 [creator] https://github.com/paketo-buildpacks/spring-boot [creator] Build Configuration: [creator] $BPL_JVM_CDS_ENABLED false whether to enable CDS optimizations at runtime [creator] $BPL_SPRING_AOT_ENABLED false whether to enable Spring AOT at runtime [creator] $BP_JVM_CDS_ENABLED false whether to enable CDS & perform JVM training run [creator] $BP_SPRING_AOT_ENABLED false whether to enable Spring AOT [creator] $BP_SPRING_CLOUD_BINDINGS_DISABLED false whether to contribute Spring Boot cloud bindings support [creator] $BP_SPRING_CLOUD_BINDINGS_VERSION 1 default version of Spring Cloud Bindings library to contribute [creator] Launch Configuration: [creator] $BPL_SPRING_CLOUD_BINDINGS_DISABLED false whether to auto-configure Spring Boot environment properties from bindings [creator] $BPL_SPRING_CLOUD_BINDINGS_ENABLED true Deprecated - whether to auto-configure Spring Boot environment properties from bindings [creator] Creating slices from layers index [creator] dependencies (88.3 MB) [creator] spring-boot-loader (394.4 KB) [creator] snapshot-dependencies (0.0 B) [creator] application (7.4 MB) [creator] Spring Cloud Bindings 2.0.3: Contributing to layer [creator] Downloading from https://repo1.maven.org/maven2/org/springframework/cloud/spring-cloud-bindings/2.0.3/spring-cloud-bindings-2.0.3.jar [creator] Verifying checksum [creator] Copying to /layers/paketo-buildpacks_spring-boot/spring-cloud-bindings [creator] Web Application Type: Contributing to layer [creator] Reactive web application detected [creator] Writing env.launch/BPL_JVM_THREAD_COUNT.default [creator] Launch Helper: Contributing to layer [creator] Creating /layers/paketo-buildpacks_spring-boot/helper/exec.d/spring-cloud-bindings [creator] 4 application slices [creator] Image labels: [creator] org.opencontainers.image.title [creator] org.opencontainers.image.version [creator] org.springframework.boot.version [creator] ===> EXPORTING [creator] Adding layer 'paketo-buildpacks/ca-certificates:helper' [creator] Adding layer 'paketo-buildpacks/bellsoft-liberica:helper' [creator] Adding layer 'paketo-buildpacks/bellsoft-liberica:java-security-properties' [creator] Adding layer 'paketo-buildpacks/bellsoft-liberica:jre' [creator] Adding layer 'paketo-buildpacks/executable-jar:classpath' [creator] Adding layer 'paketo-buildpacks/spring-boot:helper' [creator] Adding layer 'paketo-buildpacks/spring-boot:spring-cloud-bindings' [creator] Adding layer 'paketo-buildpacks/spring-boot:web-application-type' [creator] Adding layer 'buildpacksio/lifecycle:launch.sbom' [creator] Added 5/5 app layer(s) [creator] Adding layer 'buildpacksio/lifecycle:launcher' [creator] Adding layer 'buildpacksio/lifecycle:config' [creator] Adding layer 'buildpacksio/lifecycle:process-types' [creator] Adding label 'io.buildpacks.lifecycle.metadata' [creator] Adding label 'io.buildpacks.build.metadata' [creator] Adding label 'io.buildpacks.project.metadata' [creator] Adding label 'org.opencontainers.image.title' [creator] Adding label 'org.opencontainers.image.version' [creator] Adding label 'org.springframework.boot.version' [creator] Setting default process type 'web' [creator] Saving docker.io/johnniang/halo:2.20.6-alpha.1... [creator] *** Images (416133f64f56): [creator] docker.io/johnniang/halo:2.20.6-alpha.1 [creator] Adding cache layer 'paketo-buildpacks/syft:syft' [creator] Adding cache layer 'paketo-buildpacks/spring-boot:spring-cloud-bindings' [creator] Adding cache layer 'buildpacksio/lifecycle:cache.sbom' Successfully built image 'docker.io/johnniang/halo:2.20.6-alpha.1' Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0. You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins. For more on this, please refer to https://docs.gradle.org/8.10.2/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation. BUILD SUCCESSFUL in 39s 18 actionable tasks: 7 executed, 11 up-to-date
尝试运行
docker run --rm -it --name halo -p 8090:8090 -v ./halo-workdir:/home/cnb/.halo2 -m 1g --cpus 2 johnniang/halo:2.20.6-alpha.1
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx575357K -XX:MaxMetaspaceSize=166018K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 50, Loaded Class Count: 26897, Headroom: 0%) Enabling Java Native Memory Tracking Adding 146 container CA certificates to JVM truststore Spring Cloud Bindings Enabled Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -XX:+ExitOnOutOfMemoryError -XX:MaxDirectMemorySize=10M -Xmx575357K -XX:MaxMetaspaceSize=166018K -XX:ReservedCodeCacheSize=240M -Xss1M -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics -Dorg.springframework.cloud.bindings.boot.enable=true __ __ __ / / / /___ _/ /___ / /_/ / __ `/ / __ \ / __ / /_/ / / /_/ / /_/ /_/\__,_/_/\____/ Version: 2.20.6-alpha.1 2024-10-22T16:22:57.232Z INFO 1 --- [ main] run.halo.app.Application : Starting Application v2.20.6-alpha.1 using Java 21.0.4 with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace) 2024-10-22T16:22:57.234Z INFO 1 --- [ main] run.halo.app.Application : No active profile set, falling back to 1 default profile: "default" 2024-10-22T16:22:57.366Z WARN 1 --- [ main] o.s.c.a.AnnotationBeanNameGenerator : Support for convention-based stereotype names is deprecated and will be removed in a future version of the framework. Please annotate the 'value' attribute in @run.halo.app.theme.finders.Finder with @AliasFor(annotation=Component.class) to declare an explicit alias for @Component's 'value' attribute. 2024-10-22T16:22:58.131Z INFO 1 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode 2024-10-22T16:22:58.132Z INFO 1 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data R2DBC repositories in DEFAULT mode. 2024-10-22T16:22:58.236Z INFO 1 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 94 ms. Found 1 R2DBC repository interface. 2024-10-22T16:22:59.621Z INFO 1 --- [ main] r.h.a.s.a.impl.RsaKeyService : Generating RSA keys for PAT. 2024-10-22T16:23:00.269Z INFO 1 --- [ main] r.h.a.s.a.impl.RsaKeyService : Wrote RSA keys for PAT into /home/cnb/.halo2/keys/pat_id_rsa and /home/cnb/.halo2/keys/pat_id_rsa.pub 2024-10-22T16:23:00.454Z INFO 1 --- [ main] org.pf4j.DefaultPluginStatusProvider : Enabled plugins: [] 2024-10-22T16:23:00.454Z INFO 1 --- [ main] org.pf4j.DefaultPluginStatusProvider : Disabled plugins: [] 2024-10-22T16:23:00.455Z INFO 1 --- [ main] org.pf4j.DefaultPluginManager : PF4J version 3.12.0 in 'deployment' mode WARNING: A restricted method in java.lang.foreign.Linker has been called WARNING: java.lang.foreign.Linker::downcallHandle has been called by the unnamed module WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for this module 2024-10-22T16:23:01.536Z INFO 1 --- [ main] o.a.l.s.MemorySegmentIndexInputProvider : Using MemorySegmentIndexInput and native madvise support with Java 21 or later; to disable start with -Dorg.apache.lucene.store.MMapDirectory.enableMemorySegments=false 2024-10-22T16:23:01.537Z INFO 1 --- [ main] r.h.a.search.lucene.LuceneSearchEngine : Initialized lucene search engine 2024-10-22T16:23:02.156Z INFO 1 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 34 endpoints beneath base path '/actuator' 2024-10-22T16:23:02.857Z INFO 1 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port 8090 (http)
总结
我其实是更看重优点 3 的,可惜无法实现。或许我们应该寻找替代方案来实现这一目标。Buildpacks 社区支持更多的系统架构还需要很长时间,我们仍然需要持续关注它的发展。最后,期望 Halo 3 能够利用 CNB 构建 Docker 镜像。