Cloud Native Buildpacks是什么?
Cloud Native Buildpacks(简称CNB)是一种标准化、云原生的容器镜像构建系统,其核心目标是:
- 消除Dockerfile的手动编写
- 提供可重复、安全且高效的容器镜像构建流程
- 实现应用与基础镜像的解耦
- 自动化依赖管理和安全更新
Cloud Native Buildpacks由Heroku构思,在2018年成为CNCF项目,形成现代云原生标准,被Heroku、Cloud Foundry 和其他 PaaS(例如 Google App Engine、Gitlab、Knative、Deis、Dokku 和 Drie)采用。
CNB的架构和原理
核心概念
组件 |
功能说明 |
Lifecycle |
Lifecycle管理整个构建过程,由多阶段构成 |
Builder |
包含Buildpacks、生命周期组件的构建环境(容器) |
Buildpack |
模块化构建逻辑单元(可组合),负责处理应用程序源代码,安装依赖项,配置环境并生成最终的可运行单位 |
CNB Lifecycle核心构建步骤
- Detect(检测)
- 扫描项目代码结构(如pom.xml/build.gradle)
- 匹配适用的Buildpacks(如Java/Maven Buildpack)
- 输出Buildpack执行顺序
- Analyze(分析)
- 对比新旧构建元数据(layer.toml)
- 识别可复用的缓存层(如Maven依赖)
- 决定需要重建的层
- Restore(恢复)
- 从缓存中还原依赖层(如JDK安装、.m2仓库)
- 加速后续构建过程
- Build(构建)
- 执行Buildpacks的编译逻辑(如mvn package)
- 生成新的应用层(如编译后的JAR包)
- 更新层元数据(store.toml)
- Export(导出)
- 将各层打包为OCI镜像
- 注入启动配置(如JAVA_TOOL_OPTIONS)
- 输出最终镜像到镜像仓库
OCI镜像构建机制
CNB通过独特的分层策略构建符合OCI标准的镜像。
每个Buildpack都会检查源代码并提供相关的依赖项。然后,会根据应用程序的源代码和这些依赖项生成一个镜像。
在构建过程中,构建时基础镜像成为执行Buildpack的环境,而运行时基础镜像成为最终应用程序镜像的基础。
Buildpacks可以与特定的构建时基础镜像捆绑在一起,从而生成Builder镜像。Builder提供了一种便捷的Buildpacks分发方式。
实战:使用CNB构建spring-petclinic应用
下面我们使用Spring官方的宠物诊所(spring-petclinic)应用为例演示CNB构建部署应用的过程。
本地构建和运行
CNB相关的镜像托管在DockerHub上。因此要求您从本地可以访问DockerHub。
以下以在阿里云的香港/新加坡等非大陆地域的ECS实例作为试验环境。
- 环境准备
# 在阿里云香港地域购买一台ECS实例。临时使用可以购买抢占式实例,用完即释放 # 使用Alibaba Cloud Linux3公共镜像创建ECS实例,开公网 # 安装docker和常用工具,启动docker yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo yum makecache yum install -y curl git docker-ce systemctl enable docker && systemctl start docker # 安装pack CLI。从yum中安装的pack版本太旧无法使用。需要从github下载最新版本 PACK_VERSION="0.36.4" DOWNLOAD_URL="https://github.com/buildpacks/pack/releases/download/v${PACK_VERSION}/pack-v${PACK_VERSION}-linux.tgz" curl -fsSL "$DOWNLOAD_URL" -o pack-v${PACK_VERSION}-linux.tgz tar xzf pack-v0.36.4-linux.tgz mv pack /usr/local/bin/
- 构建应用
# 克隆代码到本地 git clone https://github.com/spring-projects/spring-petclinic.git cd spring-petclinic # 使用pack将应用代码构建成容器镜像;这里使用了heroku提供的CNB builder pack build petclinic --path . --builder heroku/builder:24
- 构建过程解析
[root@cnb-demo spring-petclinic]# pack build petclinic --path . --builder heroku/builder:24 24: Pulling from heroku/builder Digest: sha256:1f8c4f74030b31af122cbcccd9da975bd136a1076af2e3567bbf1a27c50ac0a3 Status: Image is up to date for heroku/builder:24 24: Pulling from heroku/heroku Digest: sha256:c697e8808410892d54125b850854b908105dbae1c8e335255b9523c7f4e75516 Status: Image is up to date for heroku/heroku:24 ===> ANALYZING Image with name "petclinic" not found ===> DETECTING 2 of 4 buildpacks participating heroku/jvm 6.1.2 heroku/maven 6.1.2 ===> RESTORING ===> BUILDING [Warning: No OpenJDK version specified] Your application does not explicitly specify an OpenJDK version. The latest long-term support (LTS) version will be installed. This currently is OpenJDK 21. This default version will change when a new LTS version is released. Your application might fail to build with the new version. We recommend explicitly setting the required OpenJDK version for your application. To set the OpenJDK version, add or edit the system.properties file in the root directory of your application to contain: java.runtime.version = 21 [Installing OpenJDK 21.0.5] [Installing Maven] Maven wrapper detected, skipping installation. [Executing Maven] $ ./mvnw -DskipTests clean install Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8 [INFO] Scanning for projects... [INFO] Downloading from spring-snapshots: https://repo.spring.io/snapshot/org/springframework/boot/spring-boot-starter-parent/3.4.2/spring-boot-starter-parent-3.4.2.pom [INFO] Downloading from spring-milestones: https://repo.spring.io/milestone/org/springframework/boot/spring-boot-starter-parent/3.4.2/spring-boot-starter-parent-3.4.2.pom [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-starter-parent/3.4.2/spring-boot-starter-parent-3.4.2.pom .......... 此处省略非常多的maven build日志.......... .......... spring-petclinic的依赖很多,需要耐心等待下载和编译.......... [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/com/github/luben/zstd-jni/1.5.6-3/zstd-jni-1.5.6-3.jar (6.7 MB at 48 MB/s) [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 35.909 s [INFO] Finished at: 2025-04-15T12:03:56Z [INFO] ------------------------------------------------------------------------ ===> EXPORTING Adding layer 'heroku/jvm:openjdk' Adding layer 'heroku/jvm:runtime' Adding layer 'buildpacksio/lifecycle:launch.sbom' Added 1/1 app layer(s) Adding layer 'buildpacksio/lifecycle:launcher' Adding layer 'buildpacksio/lifecycle:config' Adding layer 'buildpacksio/lifecycle:process-types' Adding label 'io.buildpacks.lifecycle.metadata' Adding label 'io.buildpacks.build.metadata' Adding label 'io.buildpacks.project.metadata' Setting default process type 'web' Saving petclinic... *** Images (37a5a5d4a590): petclinic Adding cache layer 'heroku/jvm:openjdk' Adding cache layer 'heroku/maven:repository' Successfully built image petclinic
从上面的日志可以看出,从代码到容器镜像,构建过程包含了以下步骤:
- 下载builder镜像
- 分析代码仓库是否存在缓存
- 检测代码仓库适合的buildpack
- 安装依赖的OpenJDK
- 安装依赖的maven,由于此项目使用了maven wrapper,跳过了安装maven
- 执行./mvnw -DskipTests clean install
- 输出运行环境和构建结果到petclinic镜像中
- 镜像验证
我们查看一下本地的docker镜像,可以发现petclinic就是我们刚刚构建出来的目标容器镜像。
而其他3个镜像都是CNB构建使用的镜像。
[root@cnb-demo spring-petclinic]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE heroku/heroku 24 7c60575a1268 7 days ago 493MB buildpacksio/lifecycle 0.20.7 c083cc1d50e2 45 years ago 35.6MB petclinic latest 37a5a5d4a590 45 years ago 821MB heroku/builder 24 2a52b7dc6e23 45 years ago 1.2GB
在本地运行镜像(注意需要指定应用监听的端口):
[root@cnb-demo spring-petclinic]# docker run -p 8080:8080 petclinic Picked up JAVA_TOOL_OPTIONS: -XX:MaxRAMPercentage=80.0 -Dfile.encoding=UTF-8 |\ _,,,--,,_ /,`.-'`' ._ \-;;,_ _______ __|,4- ) )_ .;.(__`'-'__ ___ __ _ ___ _______ | | '---''(_/._)-'(_\_) | | | | | | | | | | _ | ___|_ _| | | | | |_| | | | __ _ _ | |_| | |___ | | | | | | | | | | \ \ \ \ | ___| ___| | | | _| |___| | _ | | _| \ \ \ \ | | | |___ | | | |_| | | | | | | |_ ) ) ) ) |___| |_______| |___| |_______|_______|___|_| |__|___|_______| / / / / ==================================================================/_/_/_/ :: Built with Spring Boot :: 3.4.2 2025-04-15T12:10:51.412Z INFO 1 --- [ main] o.s.s.petclinic.PetClinicApplication : Starting PetClinicApplication v3.4.0-SNAPSHOT using Java 21.0.5 with PID 1 (/workspace/target/spring-petclinic-3.4.0-SNAPSHOT.jar started by heroku in /workspace) 2025-04-15T12:10:51.421Z INFO 1 --- [ main] o.s.s.petclinic.PetClinicApplication : No active profile set, falling back to 1 default profile: "default" 2025-04-15T12:10:53.002Z INFO 1 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. 2025-04-15T12:10:53.176Z INFO 1 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 160 ms. Found 2 JPA repository interfaces. 2025-04-15T12:10:54.196Z INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http) 2025-04-15T12:10:54.216Z INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2025-04-15T12:10:54.216Z INFO 1 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.34] 2025-04-15T12:10:54.263Z INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2025-04-15T12:10:54.265Z INFO 1 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2774 ms 2025-04-15T12:10:54.664Z INFO 1 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2025-04-15T12:10:54.934Z INFO 1 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:3480bd0f-aaa2-489a-9469-99dc4aa2fc52 user=SA 2025-04-15T12:10:54.936Z INFO 1 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. 2025-04-15T12:10:55.120Z INFO 1 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default] 2025-04-15T12:10:55.174Z INFO 1 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.6.5.Final 2025-04-15T12:10:55.219Z INFO 1 --- [ main] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled 2025-04-15T12:10:55.463Z INFO 1 --- [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer 2025-04-15T12:10:55.550Z INFO 1 --- [ main] org.hibernate.orm.connections.pooling : HHH10001005: Database info: Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)'] Database driver: undefined/unknown Database version: 2.3.232 Autocommit mode: undefined/unknown Isolation level: undefined/unknown Minimum pool size: undefined/unknown Maximum pool size: undefined/unknown 2025-04-15T12:10:56.718Z INFO 1 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) 2025-04-15T12:10:56.722Z INFO 1 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' 2025-04-15T12:10:57.213Z INFO 1 --- [ main] o.s.d.j.r.query.QueryEnhancerFactory : Hibernate is in classpath; If applicable, HQL parser will be used. 2025-04-15T12:10:58.875Z INFO 1 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 14 endpoints beneath base path '/actuator' 2025-04-15T12:10:58.974Z INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/' 2025-04-15T12:10:58.991Z INFO 1 --- [ main] o.s.s.petclinic.PetClinicApplication : Started PetClinicApplication in 8.166 seconds (process running for 8.841) 2025-04-15T12:11:07.392Z INFO 1 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2025-04-15T12:11:07.392Z INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2025-04-15T12:11:07.394Z INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
从浏览器可以正常访问:
在阿里云上构建和运行
手工使用pack命令构建在进行部署是比较麻烦的。首先你需要有一个可以访问DockerHub的构建机器,其次还需要有一个docker image registry来托管容器镜像,最后应用部署的工作还要自己来做。
阿里云应用管理支持使用CNB构建应用并部署到云服务器(ECS)上,并支持在代码更新后更新应用,实现GitOps。整个过程完全基于控制台GUI,一键提交部署,非常方便。
应用管理在ECS控制台、OOS控制台都有入口。其中“创建应用”->“通过Git仓库创建”使用了Cloud Native Buildpacks技术。
- 您需要有一个Git平台的账号(Github或Gitee)。输入账号后,可以选择您账号下的代码仓库,也可以输入一个公开仓库的地址(由于Git平台对非登录的API调用限流很严格,所以公开仓库也需要使用您的平台账号去访问)。注意随意找的代码仓库很可能不支持CNB构建,建议您先使用示例的代码库尝试。
- 输入应用名称、分组名称以及分组部署的地域。
- 选择代码分支,会展示该分支最新的Git Commit。这个Commit就代表了我们部署的程序版本。
我们的应用最终会以容器的形式运行,如果代码执行需要参数,运行参数需要通过容器环境变量的形式传入。
如果当前代码是通过别的方式(比如配置文件、命令行参数)传入参数,需要改造成支持环境变量传入。
应用监听端口有以下作用:
- 作为-p参数传递给docker run命令
- 作为环境变量传递给docker run命令
- 会在云服务器安全组中增加规则,放行入方向对该端口的公网访问
下面图示的配置,最终运行容器的命令如下:
docker run -e EnvName=EnvValue -e PORT=8080 -p 8080:8080 <镜像名称>
- 应用管理会新建一台ECS服务器来部署应用,您可以按需调整云服务器配置。
- 点击“创建”按钮后就启动创建了。创建完成后会输出应用访问的URL和登录ECS的URL。由于应用是异步启动的,如果无法访问请稍等片刻。
代码更新后,在“更新应用”->“更新应用程序”里可以拉取最新代码更新容器镜像并重新部署应用。
点击“查看日志”,可以看到CNB构建的日志和云服务器创建的日志:
Cloud Native Buildpacks的局限
CNB看起来很美好,但不是任意代码库都能够被CNB支持。
- 应用需要符合一定的规范,Buildpack才能够识别出应用的依赖、应用的构建过程和运行命令。比如说代码库中常常需要增加一个Procfile文件来指定应用的启动命令。
- CNB支持的语言版本、SDK、运行环境、软件版本是有一定范围的,老旧版本和有安全漏洞的版本可能不被支持。
因此在正式使用CNB之前,应用开发者应该了解CNB实现对应用代码的要求。
以Heroku的CNB Builder构建Java应用来举例:
- 代码仓库的根目录中需要包含
pom.xml
。 - 应用代码支持通过Maven或Gradle构建。
- 应用支持的JDK版本符合要求(目前支持8、11、17、21以及22版本)。
CNB支持的运行时组件的版本会不断更新。如果您在阿里云上使用CNB部署应用遇到问题,可以在应用管理支持钉群(群号:10880003624)里中反馈或提交工单,我们会帮助您解决。
延伸阅读
- Cloud Native Buildpacks官网文档:https://buildpacks.io/docs/
- 阿里云从Git仓库部署应用:https://help.aliyun.com/zh/oos/user-guide/create-an-application-through-a-git-repository