Help us shape the future of Go! Take the 2026 Go Developer Survey

build

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 20, 2026 License: Apache-2.0 Imports: 35 Imported by: 0

README

internal/build — 构建管线(T2.8/T2.9)

包内选型与硬约束见 doc.go;本文件只写实机复跑方法。

单元测试(默认进 CI)

go test ./internal/build/ ./internal/substrate/ ./internal/state/ -race

覆盖:railpack/buildkit 钉版互钉、队列并发上限(假执行器)、E_BUILD_FAILED 信封、preflight 两分支(假镜像端口)、provenance/sbom 关闭参数断言 (buildopt_test.go)。

实机验证(不进 CI;本机 Docker 可用时)

1. preflight 真实 daemon 两分支
go test -tags fleetly_docker ./internal/substrate/ -run TestRealDaemonPreflight -v

用镜像 moby/buildkit:v0.32.2(平台自管 buildkitd 钉版镜像,跑过 fleetlyd 即存在;缺失自动 pull)。验证:可得 → digest 带出;打临时 tag → rmi → preflight 缺失 → E_IMAGE_UNAVAILABLE + W_ROLLBACK_IMAGE_RISK。

2. 端到端真实构建(dind,生产同构拓扑)

fleetly v0.1 的宿主目标是 Linux;railpack 在 Windows 宿主上 plan 生成有 两处上游缺陷(mise zip 解包名不匹配、filepath.Dir 反斜杠泄入 LLB), 故端到端实机验收在 docker:29.8.1-dind 内做(与 e2e/smoke.sh 同拓扑)。

:: 1) 交叉编译 linux 二进制(CGO 关闭,modernc sqlite 纯 Go)
set GOOS=linux& set GOARCH=amd64& set CGO_ENABLED=0
go build -o <acc>\bin-linux\fleetlyd ./cmd/fleetlyd
go build -o <acc>\bin-linux\fleetly ./cmd/fleetly

:: 2) 验收目录(不在仓库根;bind mount 逐字节保真——spike/a FINDINGS #5)
docker run -d --name fleetly-acc-dind --privileged -v "<acc>:/work-src" docker:29.8.1-dind
docker exec -i fleetly-acc-dind sh -c "cat > /tmp/acc.sh" < <acc>\acc.sh
docker exec fleetly-acc-dind sh -c "sed -i 's/\r$//' /tmp/acc.sh"
docker exec fleetly-acc-dind sh /tmp/acc.sh

:: 3) 清理
docker rm -f fleetly-acc-dind

acc.sh(本次验收用脚本,要点):

  • dind 内起 fleetlyd(build.* 全缺省 → 自管 fleetly-buildkit 容器 + 双缓存持久化:内部层缓存命名卷挂 /var/lib/buildkit、local cache 宿主 目录 ./build-cache);
  • fleetly build --service web my-api/compose.yaml 两次(railpack 驱动; 第二次吃缓存,对照耗时);全量 compose 构建(web+worker 并行);fleetly build static-web/compose.yaml(dockerfile 驱动);fleetly builds list --json my-api 查 builds 表;
  • 产物:build-artifacts/<app>/<buildid>/build.log(railpack 另有 railpack-plan.json);镜像 fleetly-local/<app>:<app>-<buildid> 落 dind daemon。

缓存语义勘误(实机验证结论)

buildkit local cache 的导入/导出是客户端侧行为(经 solve session 流式 传输)——src/dest 路径解析在 fleetlyd 宿主,不在 buildkitd 容器内。 持久化因此分两层(config.go 头注释):cache_volume 命名卷挂 /var/lib/buildkit 保 buildkitd 内部层缓存跨容器重建;cache_dir 宿主 目录为 local cache 数据根(跨重建/跨实例可移植)。

上游缺陷备忘(Windows 宿主 railpack,v0.39.0)

  1. core/mise 的 Windows zip 解包按 mise-<版本>.exe 找文件,mise 发布 zip 内是 mise/bin/mise.exe → 下载安装必失败。宿主侧绕过:把 mise.exe 预置到 filepath.Join("/tmp/railpack/mise", "mise-"+版本+".exe")(当前 盘符根 \tmp\railpack\mise\)。
  2. buildkit/build_llb 的 FileCommand 转换用 filepath.Dir(cmd.Path), Windows 编译目标产出 \etc\mise 反斜杠路径进 LLB → solve 报 open /etc/mise/config.toml: no such file or directory。 两条均不影响 linux 宿主(v0.1 生产形态)。

Documentation

Overview

Package build 是构建管线(task-breakdown T2.8 构建管线产品化 + T2.9 镜像身份 v0.1 免 registry):compose 服务的 build/image 两模式到本机 镜像 digest 的生产链——Builder 端口 + Railpack/Dockerfile 双实现 + 构建队列(并发 ≤2、资源限额)+ plan/日志归档 + 本机镜像登记与 preflight(供发布引擎消费)。

── buildkit 接入选型(架构 §2.2 构建行「Railpack + BuildKit」,D6)──

v0.1 单机形态 = 平台自管的 buildkitd 容器(moby/buildkit 钉版镜像, privileged + cgroup 内存/CPU 硬限额 + 缓存持久化卷),经 moby/buildkit/client 以 `docker-container://<name>` connhelper 连接 (connhelper 经 docker CLI `exec -i <ctr> buildctl dial-stdio` 建流, docker CLI 在 PATH 是 fleetly 目标用户「会 Docker」的既定前提)。 理由(2026-09-17 实证):

  1. dockerd 内嵌 buildkit 的直连形态在本机被探针否定:Windows named pipe 上 buildkit gRPC 的 h2c preface 被 HTTP/1.1 应答拒绝 (dockerd 未在主 socket 暴露 Control 服务);
  2. Railpack 产物是 LLB,只能经 buildkit Solve 执行——moby /build 路由无 LLB 通道,且 secrets session 管线只有 buildkit client 成形;
  3. Spike A 的全部证据(本地层缓存 85s→4s、secrets-hash 失效、 1GiB/1.5CPU 限额下构建可完成)都在该形态取得。

端点可配(config build.buildkit_host):指向外部 buildkitd(隔离升级, Spike A 独立 buildkitd 容器形态)或未来 dockerd 原生端点都是纯配置 变更,不动代码。

── Spike A 硬约束落点 ──

  • provenance/sbom 必须关闭(#9:attestation manifest list 与 swarm 本地 digest 引用不兼容,任务崩溃循环):本包导出走 docker 导出器 + 客户端侧管道(docker-format tar 结构上不可能携带 manifest list), 且 frontend attrs 显式不请求任何 attest:*(见 buildopt_test.go 断言);
  • 缓存是默认能力不是优化(双持久化:buildkitd 内部层缓存命名卷 + 宿主侧 local cache 目录,路径可配;缓存键含 secrets-hash,凭证变化 正确失效——E2a 形态。客户端侧机制勘误见 config.go 头注释);
  • 构建在 cgroup 限额内(默认 1GiB/1.5CPU,Spike A E4 实证值);
  • 镜像不自动清理(T2.9 策略:release-semantics §2.4「不自动清理镜像」, 见 imagestore.go)。

核心无第三方概念纪律(架构 §2.8):moby/buildkit、railpack 类型只存在 于本包适配层(solve.go / railpack.go),出口是本包核心类型。

Index

Constants

View Source
const (
	// DefaultBuildkitHost 是 buildkitd 连接端点缺省(docker-container://
	// connhelper 经 docker CLI exec buildctl dial-stdio)。
	DefaultBuildkitHost = "docker-container://" + DefaultDaemonContainerName
	// DefaultDaemonContainerName 是平台自管 buildkitd 容器名。
	DefaultDaemonContainerName = "fleetly-buildkit"
	// BuildkitImage 是 buildkitd 钉版镜像(与 Spike A 一致;升级 = 独立
	// 变更票,不跟随 railpack 传递依赖漂移)。
	BuildkitImage = "moby/buildkit:v0.32.2"
	// DefaultCacheVolume 是 buildkitd 内部工作缓存的持久化命名卷(挂载点
	// InternalCacheMountPath;容器重建不丢层缓存)。
	DefaultCacheVolume = "fleetly-buildkit-cache"
	// InternalCacheMountPath 是 buildkitd 容器内内部工作缓存目录(镜像
	// VOLUME 声明路径;命名卷持久化的挂载点)。
	InternalCacheMountPath = "/var/lib/buildkit"
	// DefaultCacheDir 是 local cache 导入/导出的宿主目录缺省(客户端侧
	// 数据根;相对 fleetlyd 工作目录,与 db_path/artifacts_dir 同约定)。
	DefaultCacheDir = "./build-cache"
	// DefaultConcurrency 是构建队列并发上限缺省(架构 §4.2:并发构建 2)。
	DefaultConcurrency = 2
	// MaxConcurrency 是并发上限配置天花板(防误配打穿宿主;超限取上限)。
	MaxConcurrency = 8
	// DefaultMemoryBytes / DefaultNanoCPUs 是 buildkitd 容器资源限额缺省
	// (Spike A E4 实证:1GiB/1.5CPU 下 node 冷构建可完成、峰值 59% 内存
	// 天花板)。
	DefaultMemoryBytes = int64(1) << 30
	DefaultNanoCPUs    = int64(1_500_000_000)
	// DefaultPollInterval 是队列扫描 builds queued 行的周期(CLI 入队为
	// 跨进程通道;wake 信号提供同进程加速路径)。
	DefaultPollInterval = 2 * time.Second
	// DefaultArtifactsDir 是产物归档缺省根目录(相对 daemon 工作目录)。
	DefaultArtifactsDir = "./build-artifacts"
	// DefaultTimeout 是单条构建执行的超时预算缺省(30min)。预算覆盖执行
	// 全程(buildkitd 就绪收敛(自身 ≤6min)+ solve + inspect);到点未完
	// 成即终态 failed——挂起的执行(网络挂起/buildkitd 半死)不得永久占用
	// 并发槽(并发 2 时两个挂起即堵死全队列)。
	DefaultTimeout = 30 * time.Minute
)

buildkitd 资源与命名的平台缺省(Spike A 钉版与实证值)。

View Source
const ImageRepoPrefix = "fleetly-local"

ImageRepoPrefix 是本机构建镜像的仓前缀(fleetly-local/<app>;v0.1 免 registry——镜像只落本机 daemon,部署以 `名称@sha256:` 引用,D9)。

View Source
const RailpackVersion = "v0.39.0"

RailpackVersion 是平台钉死的 railpack 版本(唯一真源:本常量与 go.mod require 行,单测互钉;平台注入 GenerateBuildPlanOptions.RailpackVersion, 不信任环境)。

View Source
const WarningRollbackImageRisk = "W_ROLLBACK_IMAGE_RISK"

WarningRollbackImageRisk 是镜像缺失时随 E_IMAGE_UNAVAILABLE 携带的警告码 字面量(注册表 W_ROLLBACK_IMAGE_RISK:提示保留镜像或重建)。

Variables

View Source
var ErrImageNotFound = errors.New("image not found in local daemon")

ErrImageNotFound 表示镜像在本机 daemon 不存在(preflight 的 E_IMAGE_UNAVAILABLE 归因来源)。

Functions

func ArtifactsDir

func ArtifactsDir(base, app, buildID string) string

ArtifactsDir 返回一次构建的产物目录(<artifacts>/<app>/<buildid>)。

func CacheKeyPrefix

func CacheKeyPrefix(app string) string

CacheKeyPrefix 返回 railpack 缓存挂载键前缀(按应用稳定:同应用跨构建 复用 cache mount,跨应用隔离)。计算失败仅在 app 名非法时发生(调用方 已受控子集校验),失败回落固定前缀并携带错误说明。

func DriverFor

func DriverFor(svc compose.Service) (state.Driver, bool, error)

DriverFor 按 compose 服务声明裁决构建驱动(架构 §2.4:无 dockerfile → Railpack 自动;有 build.dockerfile → Dockerfile;仅 image → 镜像模式 直通,无构建)。第二返回值 false = 无构建(image 直通)。

func ImageRef

func ImageRef(app, buildID string) (string, error)

ImageRef 返回本机镜像引用 `fleetly-local/<app>:<app>-<buildid>`。

func ImageTag

func ImageTag(app, buildID string) (string, error)

ImageTag 返回构建产物 tag(可追溯:`<app>-<buildid>`,小写化——docker tag 字符集限制)。

func SecretsHash

func SecretsHash(secrets map[string]string) string

SecretsHash 计算构建凭证的稳定失效键(Spike A E2a 形态:缓存键含 secrets-hash,任一凭证值变化即相关层失效;内容不变则哈希稳定命中)。

实现说明:railpack 自带 getSecretsHash 按 map 迭代序拼接值——多凭证时 顺序随机、哈希漂移(上游缺陷)。本实现按 key 排序拼接 `k=v\n`,多凭证 下确定;键集与键名变化同样失效。凭证值只进哈希、不落盘不进日志。

Types

type Builder

type Builder struct {
	// contains filtered or unexported fields
}

Builder 依赖:配置 + 状态库 + 本机镜像端口 + buildkitd 编排端口 + solve 执行器 + 就绪探测。

func NewBuilder

func NewBuilder(cfg Config, store *state.Store, images ImageSource, daemon DaemonManager, log *slog.Logger) *Builder

NewBuilder 构建构建执行器。daemon 可为 nil(装配测试干跑;自管容器形态 下执行前的就绪收敛由 ensureDaemonReady 承载)。

func (*Builder) CacheLocalPath

func (b *Builder) CacheLocalPath() string

CacheLocalPath 返回 buildkit local cache 的宿主数据根(客户端侧路径)。

func (*Builder) Config

func (b *Builder) Config() Config

Config 返回归一化后的配置(服务装配与诊断用)。

func (*Builder) DaemonSpec

func (b *Builder) DaemonSpec() (DaemonSpec, bool)

DaemonSpec 返回平台自管 buildkitd 容器的目标形态(ManageDaemon=false 时 返回 ok=false——外部端点形态不自管容器)。

func (*Builder) EnsureDaemonReady

func (b *Builder) EnsureDaemonReady(ctx context.Context) error

EnsureDaemonReady 是 ensureDaemonReady 的公开形态(服务壳预热路径用; 幂等,可在构建执行前任意次调用)。

func (*Builder) Execute

func (b *Builder) Execute(ctx context.Context, rec state.BuildRecord) (state.BuildRecord, error)

Execute 实现执行器契约。

type Config

type Config struct {
	// BuildkitHost 是 buildkit 端点(docker-container://<name> 或
	// tcp://…/unix://… 直连外部 buildkitd——隔离升级路径)。
	BuildkitHost string
	// ManageDaemon 报告是否由平台自管 buildkitd 容器(EnsureRunning 幂等
	// 拉起钉版容器 + 限额 + 内部缓存卷)。指向外部 buildkitd 时应置 false。
	ManageDaemon bool
	// DaemonContainerName 是自管 buildkitd 容器名。
	DaemonContainerName string
	// CacheVolume 是 buildkitd 内部工作缓存的持久化命名卷(缺省)。
	CacheVolume string
	// CacheDir 是 local cache 导入/导出的宿主目录(客户端侧数据根)。
	CacheDir string
	// ArtifactsDir 是 plan JSON / 构建日志归档根目录。
	ArtifactsDir string
	// Concurrency 是构建队列并发上限(≤0 取缺省 2,天花板 8)。
	Concurrency int
	// MemoryBytes / NanoCPUs 是 buildkitd 容器 cgroup 限额。
	MemoryBytes int64
	NanoCPUs    int64
	// PollInterval 是队列 DB 扫描周期。
	PollInterval time.Duration
	// Timeout 是单条构建执行的超时预算(≤0 取缺省 30min;超时 → 终态
	// failed(E_BUILD_FAILED),由队列在认领执行处包裹生效)。
	Timeout time.Duration
	// ContextRoots 是构建上下文的受管根集合(H14 宿主目录信任边界):
	// 执行侧只接受位于受管根内的 context_dir——builds.request 是跨进程
	// 通道,直写库的越界请求不得把宿主任意目录整目录打进镜像。Normalize
	// 保证系统 temp 根恒在集合内(API 层 compose 暂存/回落基准的落点,
	// MkdirTemp("", …) 所在);daemon 装配再并入 git 根(gitserver 裸
	// 仓库根,v0.2 worktree 物化路径的前缀形态)与显式配置根。越界构建
	// 终态失败(不静默放宽)。v0.2 挂账:CLI 同宿主构建目录(显式
	// base_dir)改为上传/物化到受管根后,该集合即对全部入队来源完备闭合。
	ContextRoots []string
}

Config 是构建管线配置。零值经 Normalize 回落缺省。

func (Config) Normalize

func (c Config) Normalize() Config

Normalize 回落全部缺省(config 缺省值单一事实源)。

type DaemonManager

type DaemonManager interface {
	// EnsureImagePresent 确保镜像在本机 daemon 存在(缺失时拉取并排空
	// 响应流)。
	EnsureImagePresent(ctx context.Context, image string) error
	// EnsureVolumePresent 确保命名卷存在(幂等)。
	EnsureVolumePresent(ctx context.Context, name string) error
	// EnsureContainerRunning 把容器收敛到「存在且运行」。
	EnsureContainerRunning(ctx context.Context, spec DaemonSpec) error
}

DaemonManager 是 buildkitd 容器编排端口(容器形态的平台自管;实现必须 幂等:镜像缺失拉取、容器缺失创建、未运行启动、已在运行 no-op)。

type DaemonSpec

type DaemonSpec struct {
	// Name 是容器名(缺省 fleetly-buildkit)。
	Name string
	// Image 是钉版镜像(moby/buildkit:v0.32.2)。
	Image string
	// MemoryBytes / NanoCPUs 是 cgroup 硬限额(Spike A E4 形态)。
	MemoryBytes int64
	NanoCPUs    int64
	// CacheVolume 是挂到容器内部工作缓存目录(InternalCacheMountPath)的
	// 命名卷——层缓存随容器重建不丢。空 = 无内部缓存挂载。
	CacheVolume string
}

DaemonSpec 是平台自管 buildkitd 容器的目标形态(幂等收敛的期望态)。

type Executor

type Executor interface {
	// Execute 执行一条已 claim(building)的构建记录,终态落库后返回最新
	// 记录;返回的错误已归一为应用错误信封。
	Execute(ctx context.Context, rec state.BuildRecord) (state.BuildRecord, error)
}

Executor 是构建队列消费的执行契约(单测注入假执行器测并发上限)。

type ImageInfo

type ImageInfo struct {
	// ID 是不可变镜像 ID(`sha256:<hex>` 配置摘要,D9 部署引用形态)。
	ID string
}

ImageInfo 是一次本机镜像可见性查询的结果。

type ImageSource

type ImageSource interface {
	// InspectImage 按引用检查本机镜像;缺失返回 ErrImageNotFound(适配器
	// 必须把底座 not-found 归一为该哨兵)。
	InspectImage(ctx context.Context, ref string) (ImageInfo, error)
	// LoadImage 把 docker-format tar 流装入本机 daemon(构建产物落本机;
	// 流由 buildkit docker 导出器客户端侧管道给出)。
	LoadImage(ctx context.Context, dockerTar io.Reader) error
}

ImageSource 是本机镜像可见性端口(T2.9 镜像身份:构建后取 digest、 preflight 检查、digest→ref 登记的消费源)。

type PreflightResult

type PreflightResult struct {
	// Ref 是被检查的镜像引用(tag 或 `名称@sha256:` 形态)。
	Ref string
	// Digest 是本机镜像 ID(`sha256:<hex>`;不可用时为空)。
	Digest string
	// Available 报告镜像在本机 daemon 可得。
	Available bool
	// Warning 是缺失时的警告码(W_ROLLBACK_IMAGE_RISK);可得时为空。
	Warning string
}

PreflightResult 是镜像 preflight 的结果(供发布引擎消费的接口形态)。

func PreflightImage

func PreflightImage(ctx context.Context, images ImageSource, ref string) (PreflightResult, error)

PreflightImage 检查镜像在本机的可得性(release-semantics §2.4 preflight 第一项:镜像可得性,在动底座之前失败)。

  • 可得:返回 Available=true + digest;
  • 缺失:返回 ErrImageNotFound 哨兵包裹的 E_IMAGE_UNAVAILABLE 信封 (context.warning = W_ROLLBACK_IMAGE_RISK),同时返回带警告标注的 结果供调用方渲染;
  • 底座不可达等其他错误:原样返回(引擎侧按 E_RUNTIME_UNAVAILABLE 面 处理,本包不挪用镜像码语义)。

type Queue

type Queue struct {
	// contains filtered or unexported fields
}

Queue 是构建队列调度器(dispatcher 单 goroutine + 信号量限并发执行)。

func NewQueue

func NewQueue(store *state.Store, exec Executor, concurrency int, pollInterval, timeout time.Duration, log *slog.Logger) *Queue

NewQueue 构建队列。concurrency ≤0 回落缺省 2、pollInterval/timeout ≤0 回落各自缺省(Normalize 语义)。

func (*Queue) Concurrency

func (q *Queue) Concurrency() int

Concurrency 返回并发上限(诊断用)。

func (*Queue) Enqueue

func (q *Queue) Enqueue(ctx context.Context, rec state.BuildRecord, audit ...state.AuditEntry) (state.BuildRecord, error)

Enqueue 入队一条构建(builds queued 行 + 唤醒信号加速同进程拾取)。 audit(可选,至多一枚)透传给 state 层 build.create 审计的调用方归因 (M4-8:TriggerBuild 带 API 调用方 token;缺省 = system 归因,与内部 入队路径一致)。variadic 形态保持既有调用点签名兼容。

func (*Queue) Run

func (q *Queue) Run(ctx context.Context) error

Run 是调度主循环(阻塞到 ctx 取消;lynx.Service.Start 的 actor 形态)。 启动先复位中断构建(daemon 重启恢复,见 resetInterrupted),再进入扫描。

func (*Queue) Wake

func (q *Queue) Wake()

Wake 非阻塞唤醒扫描(通道容量 1,合并连续唤醒)。

type Request

type Request struct {
	// BuildID 与 builds.id 一致(产物归档目录与镜像 tag 的可追溯成分)。
	BuildID string `json:"build_id"`
	// AppID / AppName 是平台应用标识与 compose 项目名(镜像 tag 成分)。
	AppID   string `json:"app_id"`
	AppName string `json:"app_name"`
	// Service 是 compose 服务名。
	Service string `json:"service"`
	// Driver 是构建驱动(railpack | dockerfile;由 compose 模式裁决)。
	Driver state.Driver `json:"driver"`
	// ContextDir 是构建上下文绝对路径(compose build.context 相对 compose
	// 文件目录解析后;受控子集内校验过的目录)。
	ContextDir string `json:"context_dir"`
	// Dockerfile 是相对 ContextDir 的 dockerfile 路径(仅 dockerfile 驱动)。
	Dockerfile string `json:"dockerfile,omitempty"`
	// Target 是可选构建目标 stage(v0.1 不开,字段预留 compose 受控子集)。
	Target string `json:"target,omitempty"`
	// SpecHash 是归一化 compose 的 spec_hash(缓存键与可追溯成分)。
	SpecHash string `json:"spec_hash,omitempty"`
	// Platform 是构建目标平台(空 = 宿主默认 linux/amd64)。
	Platform string `json:"platform,omitempty"`
	// Secrets 是构建期凭证(key=值;经 buildkit session secrets 挂载,永不
	// 进镜像层——Spike A E3)。v0.1 构建无凭证输入(compose secrets 是运行
	// 期 Swarm secret),字段存在即契约:加入缓存键 secrets-hash,未来接入
	// 时凭证变化自动正确失效。
	Secrets map[string]string `json:"secrets,omitempty"`
}

Request 是一次构建的执行输入(跨进程契约,JSON 落 builds.request)。

func DecodeRequest

func DecodeRequest(raw string) (Request, error)

DecodeRequest 从 builds.request 列还原请求。

func (Request) Encode

func (r Request) Encode() (string, error)

Encode 序列化为 JSON(落 builds.request 列)。

type Result

type Result struct {
	// ImageRef 是本机镜像引用 fleetly-local/<app>:<tag>。
	ImageRef string
	// ImageDigest 是不可变镜像 ID(`sha256:<hex>` 配置摘要;部署引用取
	// digest,D9)。
	ImageDigest string
	// PlanPath 是 railpack plan JSON 归档路径(dockerfile 驱动为空)。
	PlanPath string
	// LogPath 是构建日志路径。
	LogPath string
	// Duration 是构建净耗时(二次构建加速对照的取证口径)。
	DurationSeconds float64
}

Result 是一次构建的产出(成功路径;失败经 E_BUILD_FAILED 错误信封表达)。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL