mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
3365 字
9 分钟
基于 buildpacks 技术的云原生构建
2024-04-15

一、构建往事#

在云计算的发展历程中,第一代正式意义上的代表性 PaaS 平台有 HerokuVMware Cloud Foundry 等。这些 PaaS 平台都采用了 buildpack 技术构建用户应用,虽然细节不尽相同,但本质上都是平台方提前为各类运行时以及框架提供构建流水线支持,用户只需提交代码即可获得新版本应用,随后便可通过平台 API 进行部署。

二、云原生构建的技术背景#

在 Kubernetes 环境下,我们怎样才能做到和以往 PaaS 平台一致,甚至更好的应用构建体验呢?区别于传统 PaaS,在 Kubernetes 中交付的应用统一都是 OCI 镜像格式,这使得它能够轻松支持任意语言和框架。但同时不能忽略的是,大部分开发者并不擅长制作高效且安全的镜像,所以平台方有义务提供镜像的最佳构建实践以及相关的自动化构建措施。

此时,从 Cloud Foundry 延伸而来、基于镜像的 buildpack 技术便应运而生。同时,Cloud Foundry buildpacks 团队为了使用户能从传统环境顺利过渡到 Kubernetes 环境,还提供了 paketo-buildpacks 项目。

三、buildpack 技术#

应用镜像构建,即从「源代码/可执行程序及其运行时」(后文简称「源目标」)配合基础镜像构建成应用镜像的过程。

在传统条件下,需要准备源目标并编写 Dockerfile 文件,使用 docker build 命令为应用镜像堆叠镜像层,使之形成目标镜像。

为了扩大用户面,我们还需要照顾没有编写 Dockerfile 能力的用户群,为他们构建标准的镜像。下面将调研开源项目 buildpacks 是如何解决这个问题的。

3.1 buildpacks 简要介绍#

buildpacks 是一款云原生镜像构建(CNB)技术,它主要通过 builder 自动识别源目标并进行目标镜像的构建。builder 主要由构建包(buildpack)和堆栈(stack)组成。

如果把构建镜像的过程比作烹饪一道菜肴,那么 buildpacks 就像一位经验丰富的大厨:你只需提供食材(源代码),大厨便会根据食材类型自动选择合适的烹饪方式(构建逻辑),最终端出一道色香味俱全的佳肴(OCI 镜像)。而这背后,是一套精密协作的系统在工作。

四、CNB 规范核心概念#

要深入理解 buildpacks,需要先掌握几个核心概念:LifecycleBuildpackBuilderStack。它们之间的关系可以用下图来描述:

graph TB subgraph Builder["Builder 镜像"] B1[Buildpack 1] B2[Buildpack 2] B3[Buildpack N] L[Lifecycle] S[Stack] end subgraph Stack["Stack 组成"] BI[Build Image<br/>构建环境] RI[Run Image<br/>运行环境] end subgraph Process["构建流程"] SC[源代码] --> Detect Detect --> |检出| Build Build --> |产出| App[应用镜像] end B1 --> Detect B2 --> Detect B3 --> Detect L --> Detect L --> Build BI --> Build RI --> App

4.1 Lifecycle:构建生命周期的编排者#

Lifecycle 是 CNB 规范的核心组件,它定义了从源代码到最终镜像的完整构建流程。可以把 Lifecycle 理解为一个精密的流水线管理系统,它负责协调各个 buildpack 按顺序执行,确保构建过程有条不紊。

Lifecycle 包含以下几个关键阶段:

阶段职责说明
Detect检测阶段遍历所有 buildpack,找出能够处理当前源代码的那些
Analyze分析阶段分析之前的构建缓存,加速本次构建
Restore恢复阶段恢复之前构建的缓存层
Build构建阶段执行 buildpack 的构建逻辑,编译应用、安装依赖
Export导出阶段将构建产物打包成 OCI 镜像

Lifecycle 的工作流程如下:

# 简化的 Lifecycle 执行流程
1. /cnb/lifecycle/detector # 检测哪些 buildpack 适用
2. /cnb/lifecycle/analyzer # 分析镜像层,准备缓存
3. /cnb/lifecycle/restorer # 恢复缓存层
4. /cnb/lifecycle/builder # 执行构建
5. /cnb/lifecycle/exporter # 导出最终镜像

这种分阶段的设计有几个显著优势:

  1. 缓存友好:通过 Analyze 和 Restore 阶段,可以复用之前构建的中间产物,大幅加速增量构建
  2. 可观测性强:每个阶段都有明确的输入输出,便于排查构建问题
  3. 可扩展性好:构建流程可以灵活插入自定义阶段

4.2 Buildpack:构建能力的原子单位#

Buildpack 是 CNB 生态中最基础的构建单元,每个 buildpack 专注于特定语言或框架的构建逻辑。比如有针对 Java 的 buildpack、针对 Node.js 的 buildpack、针对 Python 的 buildpack 等。

一个标准的 buildpack 必须包含两个核心脚本:

my-buildpack/
├── bin/
│ ├── detect # 检测脚本:判断是否能够处理当前源代码
│ └── build # 构建脚本:执行实际的构建逻辑
└── buildpack.toml # 元数据配置文件

detect 脚本的作用是「自荐」——告诉 Lifecycle「我能处理这类源代码」。它的逻辑通常是:

#!/bin/bash
# bin/detect 示例
# 检查是否存在 package.json,判断是否为 Node.js 项目
if [ -f "package.json" ]; then
echo "nodejs" # 输出 plan 名称
exit 0
fi
# 如果不能处理,返回非零退出码
exit 1

build 脚本则是「兑现承诺」——实际执行构建工作:

#!/bin/bash
# bin/build 示例
# 设置环境
export PATH="/cnb/process:$PATH"
# 安装依赖
npm install --production
# 设置启动命令
cat > launch.toml <<EOF
[[processes]]
type = "web"
command = "npm start"
EOF

Buildpack 的执行遵循「检测→构建」的两步模式。这种设计看似简单,实则巧妙:它让构建过程可以灵活组合,就像搭积木一样。一个复杂的应用可能需要多个 buildpack 协同工作——比如一个 Node.js 应用可能需要「Node.js buildpack + Nginx buildpack」组合来完成前后端的构建。

4.3 Stack:构建和运行的双镜像架构#

Stack 是 CNB 中一个独特而重要的概念,它定义了构建环境和运行环境的镜像基础。Stack 包含两个镜像:

graph LR subgraph Stack BI["Build Image<br/>(构建环境)"] RI["Run Image<br/>(运行环境)"] end BI --> |包含| BC["构建工具链<br/>编译器、包管理器等"] RI --> |包含| RC["运行时依赖<br/>系统库、运行时等"] BI --> |共享基础层| RI

Build Image(构建镜像):提供编译和打包所需的完整工具链。比如 Java 构建镜像会包含 JDK、Maven/Gradle、Git 等工具;Node.js 构建镜像会包含 Node.js、npm/yarn 等。

Run Image(运行镜像):提供应用运行所需的最小化环境。这是一个「瘦身」后的镜像,只包含应用运行必需的组件,不包含构建工具,从而保证生产环境镜像的精简和安全。

两个镜像之间的关系可以通过下面的 Dockerfile 概念来理解:

# Build Image 的基础
FROM ubuntu:22.04 AS build-image
RUN apt-get update && apt-get install -y \
build-essential \
curl \
git \
# ... 更多构建工具
ENV CNB_USER_ID=1000
ENV CNB_GROUP_ID=1000
# Run Image 的基础(通常共享相同的基础镜像)
FROM ubuntu:22.04 AS run-image
RUN apt-get update && apt-get install -y \
ca-certificates \
# ... 只安装运行时必需的包

这种双镜像架构带来了几个关键好处:

  1. 安全最小化:运行镜像不包含构建工具,攻击面大幅减小
  2. 镜像体积优化:生产镜像可以做到极致精简
  3. 构建环境隔离:构建时的复杂依赖不会污染运行环境
  4. 缓存效率:两个镜像共享基础层,提高拉取和构建效率

4.4 Builder:构建能力的集合体#

Builder 是一个集成了 Lifecycle、多个 buildpack 和 Stack 的 OCI 镜像。它就像一个预装了各种工具和材料的「移动工作间」,用户只需要推送源代码进去,就能得到构建好的镜像。

Builder 的结构可以通过 builder.toml 配置文件来定义:

# builder.toml 示例
# 构建顺序很重要:Lifecycle 会按顺序尝试检测
[[buildpacks]]
id = "paketo-buildpacks/nodejs"
version = "1.0.0"
uri = "docker://gcr.io/paketo-buildpacks/nodejs"
[[buildpacks]]
id = "paketo-buildpacks/npm"
version = "1.0.0"
uri = "docker://gcr.io/paketo-buildpacks/npm"
[[buildpacks]]
id = "paketo-buildpacks/java"
version = "1.0.0"
uri = "docker://gcr.io/paketo-buildpacks/java"
# 定义构建顺序和分组
[[order]]
[[order.group]]
id = "paketo-buildpacks/nodejs"
[[order]]
[[order.group]]
id = "paketo-buildpacks/java"
# 指定 Stack
[stack]
id = "io.buildpacks.stacks.jammy"
build-image = "paketobuildpacks/build-jammy-base"
run-image = "paketobuildpacks/run-jammy-base"

构建一个自定义 Builder 的过程如下:

# 使用 pack CLI 构建 builder
pack builder create my-builder \
--config builder.toml \
--path ./
# 查看构建好的 builder 内容
pack builder inspect my-builder

Builder 的构建流程可以简化为下图:

sequenceDiagram participant User as 用户 participant Pack as pack CLI participant Docker as Docker Daemon participant Registry as 镜像仓库 User->>Pack: pack builder create Pack->>Docker: 构建 builder 镜像 Note over Docker: 1. 拉取基础镜像<br/>2. 注入 lifecycle<br/>3. 添加 buildpacks<br/>4. 设置环境变量 Docker-->>Pack: builder 镜像 ID Pack->>Registry: 推送 builder 镜像 Registry-->>User: builder 可用

五、构建流程详解#

当用户使用 pack build 命令构建应用时,完整的构建流程如下:

sequenceDiagram participant User as 用户 participant Pack as pack CLI participant Lifecycle as Lifecycle participant BP as Buildpacks participant Docker as Docker User->>Pack: pack build my-app --builder my-builder Pack->>Docker: 拉取 builder 镜像 Pack->>Docker: 创建构建容器 Note over Lifecycle: Phase 1: Detect Lifecycle->>BP: 遍历执行 detect 脚本 BP-->>Lifecycle: 返回检测结果 Note over Lifecycle: 确定构建计划 Note over Lifecycle: Phase 2: Analyze Lifecycle->>Docker: 分析之前的构建缓存 Note over Lifecycle: Phase 3: Restore Lifecycle->>Docker: 恢复缓存层 Note over Lifecycle: Phase 4: Build Lifecycle->>BP: 按顺序执行 build 脚本 BP->>BP: 安装依赖 BP->>BP: 编译代码 BP->>BP: 配置启动命令 BP-->>Lifecycle: 构建完成 Note over Lifecycle: Phase 5: Export Lifecycle->>Docker: 创建最终镜像层 Docker-->>Pack: 返回镜像 ID Pack-->>User: 构建成功

5.1 Layer 缓存机制#

CNB 的一个重要特性是其精细的 Layer 缓存机制。每个 buildpack 可以声明自己的缓存策略,将构建产物划分为不同的层:

# 一个典型应用的镜像层结构
├── Layer: base (来自 run image)
├── /bin
├── /lib
└── /usr
├── Layer: node-runtime (Node.js 运行时)
└── /layers/paketo-buildpacks_nodejs/node
├── Layer: npm-cache (npm 缓存)
└── /layers/paketo-buildpacks_npm/cache
├── Layer: app-dependencies (应用依赖)
└── /workspace/node_modules
└── Layer: app (应用代码)
└── /workspace

缓存的优势在于:

  1. 增量构建速度:只有变化的层需要重新构建
  2. 镜像共享:不同应用的相同基础层可以共享存储
  3. 快速回滚:历史版本可以快速恢复

六、Paketo Buildpacks#

Paketo 是由 Cloud Foundry 基金会维护的一套开源 buildpacks 集合,它完全遵循 CNB 规范,提供了开箱即用的构建能力。

6.1 Paketo 的优势#

相比传统 Dockerfile 方式,Paketo buildpacks 具有以下优势:

1. 零 Dockerfile 的构建体验

传统方式需要为每个项目维护 Dockerfile:

# 传统 Dockerfile 示例
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["npm", "start"]

使用 Paketo 后,只需一行命令:

# 自动检测语言、安装依赖、构建镜像
pack build my-app --builder paketobuildpacks/builder-jammy-base

2. 安全更新自动化

当基础镜像发现安全漏洞时,传统方式需要:

  1. 修改所有项目的 Dockerfile
  2. 重新构建所有项目
  3. 重新部署所有应用

使用 Paketo:

# 只需重新构建,buildpack 自动应用最新的安全补丁
pack build my-app --builder paketobuildpacks/builder-jammy-base

3. 最佳实践内置

Paketo 团队持续跟踪各种语言和框架的最佳实践:

最佳实践传统 DockerfilePaketo Buildpacks
多阶段构建手动编写自动应用
安全扫描需额外配置内置 SBOM
最小化镜像需经验判断自动优化
依赖缓存手动设计自动分层缓存
启动命令手动配置自动检测

4. 软件物料清单(SBOM)

Paketo 自动生成 SBOM,满足供应链安全合规要求:

# 构建时自动生成 SBOM
pack build my-app --builder paketobuildpacks/builder-jammy-base
# 查看镜像中的 SBOM
pack inspect my-app --sbom

SBOM 输出示例:

{
"sbom": {
"spdxid": "SPDXRef-DOCUMENT",
"documentNamespace": "https://paketo.io/sbom/my-app",
"packages": [
{
"name": "node",
"version": "18.17.0",
"license": "MIT"
},
{
"name": "npm",
"version": "9.6.7",
"license": "Artistic-2.0"
}
]
}
}

6.2 Paketo Buildpack 家族#

Paketo 提供了丰富的语言和框架支持:

graph TB subgraph Languages["语言运行时"] Java[Java Buildpack] Node[Node.js Buildpack] Go[Go Buildpack] Python[Python Buildpack] Ruby[Ruby Buildpack] PHP[PHP Buildpack] NET[.NET Buildpack] end subgraph Frameworks["框架支持"] Spring[Spring Boot] React[React/Vue/Angular] Django[Django] Rails[Rails] end subgraph Features["附加能力"] Procfile[Procfile 支持] Health[健康检查] Config[配置注入] Secrets[密钥管理] end Languages --> Frameworks Languages --> Features

6.3 Builder 变体选择#

Paketo 提供了三种 Builder 变体,适用于不同场景:

Builder基础镜像大小适用场景
builder-jammy-baseUbuntu 22.04~1GB通用场景,支持最多语言
builder-jammy-fullUbuntu 22.04~2GB需要完整工具链的复杂应用
builder-jammy-tinyUbuntu 22.04~100MB最小化镜像,仅支持静态编译语言

选择建议:

# 大多数 Web 应用
pack build my-web-app --builder paketobuildpacks/builder-jammy-base
# Go、Rust 等静态编译语言(最小镜像)
pack build my-cli-app --builder paketobuildpacks/builder-jammy-tiny
# 需要 .NET、PHP 等完整运行时
pack build my-dotnet-app --builder paketobuildpacks/builder-jammy-full

七、与 Dockerfile 的对比#

为了更直观地理解 buildpacks 的价值,通过一个实际案例来对比两种方式。

7.1 场景:构建一个 Spring Boot 应用#

传统 Dockerfile 方式:

# Dockerfile
FROM eclipse-temurin:17-jdk-alpine AS builder
WORKDIR /app
COPY . .
RUN ./gradlew build -x test
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

构建和运行:

docker build -t my-spring-app .
docker run -p 8080:8080 my-spring-app

问题:

  • 需要维护 Dockerfile 文件
  • JVM 参数调优需要修改 Dockerfile
  • 安全更新需要重新构建基础镜像
  • 没有依赖缓存机制,每次都要下载依赖

Paketo Buildpacks 方式:

# 一行命令完成构建
pack build my-spring-app \
--builder paketobuildpacks/builder-jammy-base \
--env BP_JVM_VERSION=17

优势:

  • 无需维护 Dockerfile
  • 自动检测 Gradle/Maven
  • 依赖自动缓存
  • JVM 参数可通过环境变量配置
  • 自动生成 SBOM
  • 自动配置 Spring Boot 启动参数

7.2 功能对比表#

功能DockerfileCNB/Paketo
学习曲线中等
灵活性
最佳实践需手动实现内置
安全更新手动自动
缓存机制手动设计自动分层
多语言支持需为每种语言编写开箱即用
SBOM 支持需额外工具内置
调试能力直接进入容器专用工具
可移植性依赖 DockerOCI 兼容

7.3 何时选择哪种方式#

选择 Dockerfile 的场景:

  1. 需要极度定制化的构建流程
  2. 现有 buildpack 不支持的技术栈
  3. 需要复杂的构建参数控制
  4. 团队已熟悉 Dockerfile 最佳实践

选择 CNB/Paketo 的场景:

  1. 标准化的微服务架构
  2. 多语言项目统一构建
  3. 需要 SBOM 和供应链安全
  4. 开发团队不熟悉容器最佳实践
  5. CI/CD 流水线需要简化

八、实战:完整构建流程#

下面通过一个完整的 Node.js 应用示例,演示 CNB 的构建流程。

8.1 环境准备#

# 安装 pack CLI
# macOS
brew install buildpacks/tap/pack
# Linux
curl -sSL https://github.com/buildpacks/pack/releases/download/v0.32.1/pack-v0.32.1-linux.tgz | tar -C /usr/local/bin --no-same-owner -xzv pack
# 验证安装
pack version

8.2 准备示例应用#

# 创建示例 Node.js 应用
mkdir my-node-app && cd my-node-app
# package.json
cat > package.json << 'EOF'
{
"name": "my-node-app",
"version": "1.0.0",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.18.2"
}
}
EOF
# server.js
cat > server.js << 'EOF'
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.json({ message: 'Hello from CNB!' });
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
EOF

8.3 执行构建#

# 使用 Paketo builder 构建
pack build my-node-app \
--builder paketobuildpacks/builder-jammy-base
# 输出示例:
# ==> DETECTING
# 5 of 18 buildpacks participating
# paketo-buildpacks/ca-certificates 3.6.1
# paketo-buildpacks/node-engine 1.2.3
# paketo-buildpacks/npm-install 1.0.1
# paketo-buildpacks/node-start 1.0.1
#
# ==> ANALYZING
# Restoring 3 layers from cache
#
# ==> BUILDING
# Installing Node.js v18.17.0
# Installing npm dependencies
#
# ==> EXPORTING
# Adding layer 'paketo-buildpacks/node-engine'
# Adding layer 'paketo-buildpacks/npm-install'
# Writing config
# Adding label 'io.buildpacks.lifecycle.metadata'

8.4 运行和验证#

# 运行构建好的镜像
docker run -p 3000:3000 my-node-app
# 测试应用
curl http://localhost:3000
# 输出: {"message":"Hello from CNB!"}
# 查看镜像信息
pack inspect my-node-app
# 查看 SBOM
pack sbom my-node-app

8.5 配置自定义参数#

CNB 支持通过环境变量配置构建行为:

# 指定 Node.js 版本
pack build my-node-app \
--builder paketobuildpacks/builder-jammy-base \
--env BP_NODE_VERSION=20
# 设置构建时变量
pack build my-node-app \
--env NPM_CONFIG_PRODUCTION=true \
--env BP_NODE_PROJECT_PATH=./backend
# 配置运行时环境变量
pack build my-node-app \
--env BPE_MY_VAR=my-value

常用环境变量:

环境变量说明
BP_NODE_VERSION指定 Node.js 版本
BP_NODE_PROJECT_PATH指定项目路径
BP_JVM_VERSION指定 JVM 版本
BP_GO_VERSION指定 Go 版本
BPE_*运行时环境变量
BP_LAUNCHPOINT指定启动入口

8.6 与 Kubernetes 集成#

在 Kubernetes 环境中,可以使用 Tektonkpack 来集成 CNB:

# kpack Image 资源示例
apiVersion: kpack.io/v1alpha2
kind: Image
metadata:
name: my-node-app
spec:
tag: registry.example.com/my-node-app
builder:
name: my-builder
kind: ClusterBuilder
source:
git:
url: https://github.com/myorg/my-node-app
revision: main

kpack 会自动监听代码仓库变化,触发增量构建,实现真正的「推送即部署」体验。

九、最佳实践#

9.1 Builder 版本管理#

# 使用固定版本的 builder
pack build my-app \
--builder paketobuildpacks/builder-jammy-base:0.4.0
# 而不是 latest
pack build my-app \
--builder paketobuildpacks/builder-jammy-base:latest # 不推荐

9.2 利用缓存加速构建#

# 启用卷缓存(本地开发)
pack build my-app \
--builder paketobuildpacks/builder-jammy-base \
--volume ~/.pack/cache:/cache
# CI 环境使用镜像缓存
pack build my-app \
--cache-image registry.example.com/cache:my-app

9.3 多环境配置#

# 开发环境
pack build my-app --env BP_NODE_VERSION=18 --env NODE_ENV=development
# 生产环境
pack build my-app \
--env BP_NODE_VERSION=18 \
--env NODE_ENV=production \
--env NPM_CONFIG_PRODUCTION=true

9.4 安全最佳实践#

# 扫描镜像漏洞
pack build my-app \
--builder paketobuildpacks/builder-jammy-base \
--run-image paketobuildpacks/run-jammy-base:latest
# 使用最小化 run image
pack build my-app \
--builder paketobuildpacks/builder-jammy-tiny

十、总结#

Cloud Native Buildpacks 代表了容器构建技术的一次重要演进。它将传统 PaaS 平台的便捷体验带入了 Kubernetes 时代,通过以下核心价值点解决了开发者的痛点:

  1. 开发者友好:无需编写 Dockerfile,提交代码即可构建
  2. 安全合规:自动生成 SBOM,支持供应链安全审计
  3. 最佳实践内置:多阶段构建、缓存优化、最小化镜像开箱即用
  4. 生态丰富:Paketo 提供了主流语言和框架的完整支持
  5. 标准化:遵循 CNB 规范,不同实现之间兼容互通

对于云原生平台团队而言,CNB 提供了一种标准化的构建抽象,可以大幅降低开发者使用容器技术的门槛,同时确保企业级的安全和合规要求。对于个人开发者而言,Paketo 等开箱即用的解决方案可以让你专注于业务代码,而不必深究容器最佳实践。

当然,CNB 并非银弹。对于有特殊构建需求的项目,Dockerfile 仍然提供了最灵活的控制能力。在实际项目中,可以根据团队的技术栈、运维能力和业务需求,选择最合适的构建方式。

十一、参考资料#


参考#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

基于 buildpacks 技术的云原生构建
https://blog.souloss.com/posts/kubernetes/k8s-cloud-native-buildpacks/
作者
Souloss
发布于
2024-04-15
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时