docker-06-builder

模块概览

模块定位与职责

职责边界

builder 模块负责 Docker 镜像构建,支持传统 Dockerfile 构建和 BuildKit 高级构建:

  1. Dockerfile 解析与执行

    • 词法分析与语法分析
    • 指令验证(FROM/RUN/COPY/ADD/…)
    • 多阶段构建支持
    • ARG 与 ENV 变量替换
  2. 逐层构建

    • 每条指令生成一个镜像层
    • 层缓存机制(复用已有层)
    • 构建上下文管理
    • 临时容器管理
  3. BuildKit 集成

    • 并行构建(independent stages)
    • 高效缓存(cache mounts)
    • 远程缓存导入/导出
    • 多平台构建
    • Dockerfile frontend 支持
  4. 构建上下文

    • 上下文传输(tar 归档)
    • .dockerignore 过滤
    • 远程上下文(Git URL)
    • 上下文缓存

上下游依赖

上游调用方

  • Build Router:处理构建相关的 HTTP API
  • Daemon:镜像构建请求

下游被依赖方

  • ImageService:镜像拉取与存储
  • Container:临时容器创建与执行
  • BuildKit:高级构建引擎
  • Registry:基础镜像拉取

模块架构图

flowchart TB
    subgraph API["API 层"]
        BuildRouter[Build Router]
    end
    
    subgraph Builder["构建器层"]
        BuilderBackend[Builder Backend]
        BuildManager[Build Manager]
    end
    
    subgraph Classic["传统构建路径"]
        DockerfileParser[Dockerfile 解析器]
        BuildContext[构建上下文]
        Dispatcher[指令分发器]
        ImageBuilder[镜像构建器]
    end
    
    subgraph BuildKit["BuildKit 路径"]
        BuildKitController[BuildKit Controller]
        BuildKitWorker[BuildKit Worker]
        BuildKitSolver[BuildKit Solver]
        BuildKitCache[BuildKit Cache]
    end
    
    subgraph Execution["执行层"]
        ContainerExec[容器执行器]
        ImageCommit[镜像提交器]
        LayerBuilder[层构建器]
    end
    
    BuildRouter --> BuilderBackend
    BuilderBackend --> BuildManager
    
    BuildManager --> DockerfileParser
    BuildManager --> BuildKitController
    
    DockerfileParser --> BuildContext
    DockerfileParser --> Dispatcher
    Dispatcher --> ImageBuilder
    
    ImageBuilder --> ContainerExec
    ImageBuilder --> ImageCommit
    ImageCommit --> LayerBuilder
    
    BuildKitController --> BuildKitWorker
    BuildKitWorker --> BuildKitSolver
    BuildKitSolver --> BuildKitCache

架构说明

1. 传统构建路径

  • Dockerfile 解析器

    type Parser struct {
        Directive *parser.Directive
    }
    
    func (p *Parser) Parse(rwc io.Reader) (*parser.Result, error) {
        // 词法分析
        // 语法分析
        // 生成指令树
    }
    

- **指令分发器**

  ```go
  type Dispatcher struct {
      builder Builder
      state   *dispatchState
  }
  
  // 执行单条指令
  func (d *Dispatcher) dispatch(cmd *instructions.Command) error {
      switch cmd.Name {
      case "from":
          return d.dispatchFrom(cmd)
      case "run":
          return d.dispatchRun(cmd)
      case "copy":
          return d.dispatchCopy(cmd)
      // ...
      }
  }
  • 镜像构建器

    type Builder struct {
        imageCache  *imageCache
        containerManager ContainerManager
        imageSource ImageSource
        buildStages []*buildStage
    }
    
    // 逐指令构建
    func (b *Builder) build(ctx context.Context, stages []instructions.Stage) error {
        for _, stage := range stages {
            for _, cmd := range stage.Commands {
                if err := b.dispatch(cmd); err != nil {
                    return err
                }
            }
        }
    }
    

**2. BuildKit 路径**:

- **BuildKit Controller**:

  ```go
  type Controller struct {
      worker    *worker.Worker
      solver    *solver.Solver
      cache     solver.CacheManager
      frontends map[string]frontend.Frontend
  }
  • BuildKit Solver
    • LLB(Low-Level Build)图构建
    • 依赖分析与并行执行
    • 缓存查询与复用
    • 远程缓存支持

Dockerfile 构建完整时序图

sequenceDiagram
    autonumber
    participant Client as docker CLI
    participant Router as Build Router
    participant Backend as Builder Backend
    participant Parser as Dockerfile Parser
    participant ImageSvc as Image Service
    participant Container as Container Manager
    participant Containerd as containerd
    
    Note over Client: docker build -t myapp:v1.0 .
    Client->>Router: POST /build
    Note over Client: 上传构建上下文(tar 归档)
    
    Note over Router: 阶段1: 接收构建上下文
    Router->>Backend: Build(context, options)
    Backend->>Backend: 解压 tar 到临时目录
    Backend->>Backend: 读取 .dockerignore
    Backend->>Backend: 过滤文件
    
    Note over Backend: 阶段2: 解析 Dockerfile
    Backend->>Parser: Parse(dockerfile)
    Parser->>Parser: 词法分析
    Parser->>Parser: 语法分析
    Parser->>Parser: 指令验证
    Parser-->>Backend: 指令列表
    
    Note over Backend: 阶段3: 多阶段分析
    Backend->>Backend: 分析依赖关系
    Backend->>Backend: 确定构建顺序
    
    Note over Backend: 阶段4: 逐指令执行
    loop 遍历每条指令
        alt 指令 = FROM
            Note over Backend: 获取基础镜像
            Backend->>ImageSvc: GetImage(baseImage)
            alt 镜像不存在
                ImageSvc->>ImageSvc: PullImage(baseImage)
            end
            ImageSvc-->>Backend: 镜像信息
            Backend->>Backend: 创建构建阶段
            Backend->>Backend: 设置当前镜像为基础镜像
            
        else 指令 = RUN
            Note over Backend: 执行命令
            Backend->>Backend: 检查缓存(指令 hash)
            alt 缓存命中
                Backend->>Backend: 复用缓存层
            else 缓存未命中
                Backend->>Container: Create(image, command)
                Container-->>Backend: 容器 ID
                
                Backend->>Container: Start(containerID)
                Container->>Containerd: 启动容器
                Containerd-->>Container: 容器运行
                
                Backend->>Container: Attach(containerID)
                Backend->>Backend: 流式输出日志
                
                Container->>Containerd: 等待容器退出
                Containerd-->>Container: 退出码
                
                alt 退出码 != 0
                    Backend-->>Client: 构建失败
                end
                
                Backend->>Container: Commit(containerID)
                Container->>ImageSvc: CreateLayer(containerID)
                ImageSvc->>ImageSvc: 计算层 diff
                ImageSvc->>ImageSvc: 保存新层
                ImageSvc-->>Backend: 新镜像 ID
                
                Backend->>Container: Remove(containerID)
                Backend->>Backend: 更新缓存
                Backend->>Backend: 更新当前镜像
            end
            
        else 指令 = COPY/ADD
            Note over Backend: 复制文件
            Backend->>Backend: 从构建上下文读取文件
            Backend->>Backend: 计算文件 checksum
            Backend->>Backend: 检查缓存
            
            alt 缓存未命中
                Backend->>Container: Create(临时容器)
                Backend->>Container: 复制文件到容器
                Backend->>Container: Commit(容器)
                Container->>ImageSvc: CreateLayer
                ImageSvc-->>Backend: 新镜像 ID
                Backend->>Container: Remove(临时容器)
            end
            
        else 指令 = ENV/LABEL/EXPOSE/...
            Note over Backend: 更新镜像配置(无新层)
            Backend->>Backend: 更新镜像元数据
            Backend->>Backend: config.Env.Add(key, value)
            
        else 指令 = ARG
            Note over Backend: 设置构建参数
            Backend->>Backend: buildArgs[key] = value
            Backend->>Backend: 替换后续指令中的 $ARG
        end
    end
    
    Note over Backend: 阶段5: 生成最终镜像
    Backend->>ImageSvc: CreateImage(config, layers)
    ImageSvc->>ImageSvc: 计算镜像 ID
    ImageSvc->>ImageSvc: 添加 tag (myapp:v1.0)
    ImageSvc-->>Backend: 镜像 ID
    
    Note over Backend: 阶段6: 清理临时资源
    Backend->>Backend: 删除临时目录
    Backend->>Backend: 删除临时容器
    
    Backend-->>Router: 构建成功
    Router-->>Client: 200 OK + 构建日志
    
    Note over Client: 输出示例
    Note over Client: Sending build context to Docker daemon  2.048kB<br/>Step 1/5 : FROM golang:1.20<br/> ---> abc123...<br/>Step 2/5 : WORKDIR /app<br/> ---> Running in def456...<br/> ---> ghi789...<br/>Step 3/5 : COPY . .<br/> ---> jkl012...<br/>Step 4/5 : RUN go build<br/> ---> Running in mno345...<br/> ---> pqr678...<br/>Step 5/5 : CMD ["./myapp"]<br/> ---> Running in stu901...<br/> ---> vwx234...<br/>Successfully built vwx234...<br/>Successfully tagged myapp:v1.0

时序图关键点说明

阶段1:接收构建上下文(100-500ms)

  • 构建上下文大小限制:建议 < 50MB
  • .dockerignore 规则:
  .git
  node_modules/
  __pycache__/
  *.log

阶段2:解析 Dockerfile(10-50ms)

# 示例 Dockerfile
FROM golang:1.20 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o /bin/myapp

FROM alpine:latest
COPY --from=builder /bin/myapp /usr/local/bin/
CMD ["myapp"]

解析结果

stages := []Stage{
    {
        Name: "builder",
        Commands: []*Command{
            {Name: "from", Args: []string{"golang:1.20"}},
            {Name: "workdir", Args: []string{"/app"}},
            {Name: "copy", Args: []string{"go.mod", "go.sum", "./"}},
            {Name: "run", Args: []string{"go", "mod", "download"}},
            {Name: "copy", Args: []string{".", "."}},
            {Name: "run", Args: []string{"go", "build", "-o", "/bin/myapp"}},
        },
    },
    {
        Name: "",
        Commands: []*Command{
            {Name: "from", Args: []string{"alpine:latest"}},
            {Name: "copy", Args: []string{"--from=builder", "/bin/myapp", "/usr/local/bin/"}},
            {Name: "cmd", Args: []string{"myapp"}},
        },
    },
}

阶段3:多阶段分析(<1ms)

  • 构建依赖图:
  stage[0] (builder) → stage[1]
  • 并行构建机会(BuildKit):
    • 独立的 stage 可并行执行
    • 依赖的 stage 顺序执行

阶段4:RUN 指令执行(主要耗时)

// 缓存键计算
func cacheKey(instruction *Command, parentImage string) string {
    h := sha256.New()
    h.Write([]byte(parentImage))          // 父镜像 ID
    h.Write([]byte(instruction.String())) // 指令内容
    return hex.EncodeToString(h.Sum(nil))
}

// 缓存查找
if cachedLayer := cache.Get(cacheKey); cachedLayer != nil {
    currentImage = cachedLayer
    fmt.Println(" ---> Using cache")
} else {
    // 执行指令并创建新层
}

COPY 指令优化

// 计算源文件 checksum
func calculateChecksum(files []string) string {
    h := sha256.New()
    for _, file := range files {
        data, _ := os.ReadFile(file)
        h.Write(data)
    }
    return hex.EncodeToString(h.Sum(nil))
}

阶段5:生成最终镜像(10-30ms)

// 镜像配置
config := &ImageConfig{
    Cmd:        []string{"myapp"},
    Env:        []string{"PATH=/usr/local/sbin:...", "GO_VERSION=1.20"},
    WorkingDir: "/app",
    ExposedPorts: map[string]struct{}{
        "8080/tcp": {},
    },
}

// 计算镜像 ID(配置的 SHA256)
imageID := sha256.Sum(marshalConfig(config))

BuildKit 优势

并行构建

传统构建(串行):

FROM base
RUN task1  # 耗时 10s
RUN task2  # 耗时 10s
RUN task3  # 耗时 10s
# 总耗时:30s

BuildKit(并行):

FROM base AS stage1
RUN task1  # 耗时 10s

FROM base AS stage2
RUN task2  # 耗时 10s

FROM base AS stage3
RUN task3  # 耗时 10s

FROM base
COPY --from=stage1 /output1 /
COPY --from=stage2 /output2 /
COPY --from=stage3 /output3 /
# 总耗时:10s(并行执行)

Cache Mounts

# 传统方式:每次构建都重新下载依赖
FROM golang:1.20
COPY go.mod go.sum ./
RUN go mod download  # 每次都重新下载

# BuildKit:缓存依赖到持久化卷
FROM golang:1.20
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
    go mod download  # 依赖持久化缓存

远程缓存

# 导出缓存到 Registry
docker buildx build \
  --cache-to type=registry,ref=myregistry.com/myapp:cache \
  -t myapp:v1.0 .

# 从 Registry 导入缓存
docker buildx build \
  --cache-from type=registry,ref=myregistry.com/myapp:cache \
  -t myapp:v1.0 .

最佳实践

层缓存优化

# ❌ 不推荐:频繁变化的文件先复制
FROM golang:1.20
COPY . .
RUN go mod download

# ✅ 推荐:依赖文件先复制(变化少)
FROM golang:1.20
COPY go.mod go.sum ./
RUN go mod download
COPY . .

多阶段构建

# 编译阶段(大镜像)
FROM golang:1.20 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp

# 运行阶段(小镜像)
FROM alpine:latest
RUN apk --no-cache add ca-certificates
COPY --from=builder /app/myapp /usr/local/bin/
CMD ["myapp"]

.dockerignore

# 排除 Git 文件
.git
.gitignore

# 排除开发文件
*.md
Dockerfile*
docker-compose*.yml

# 排除依赖目录
node_modules/
vendor/
__pycache__/

# 排除构建产物
*.o
*.a
*.so
dist/
build/

构建参数

ARG GO_VERSION=1.20
FROM golang:${GO_VERSION}

ARG BUILD_DATE
ARG VCS_REF
LABEL org.opencontainers.image.created="${BUILD_DATE}" \
      org.opencontainers.image.revision="${VCS_REF}"
docker build \
  --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
  --build-arg VCS_REF=$(git rev-parse --short HEAD) \
  -t myapp:v1.0 .

BuildKit 特性

# 启用 BuildKit
export DOCKER_BUILDKIT=1

# 或使用 buildx
docker buildx build -t myapp:v1.0 .

# 查看构建详情
docker buildx build --progress=plain -t myapp:v1.0 .

# 多平台构建
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t myapp:v1.0 .

API接口

本文档详细描述 Builder 模块对外提供的 HTTP API 接口,包括请求/响应结构、核心代码、调用链路与时序图。


API 目录

序号 API 方法 路径 说明
1 构建镜像 POST /build 从 Dockerfile 构建镜像
2 清理构建缓存 POST /build/prune 清理未使用的构建缓存(v1.31+)
3 取消构建 POST /build/cancel 取消正在进行的构建

1. 构建镜像

基本信息

  • 路径POST /build
  • 用途:从 Dockerfile 构建 Docker 镜像
  • 最小 API 版本:v1.24
  • 幂等性:否
  • Content-Typeapplication/x-tar(请求体为 tar 归档)
  • 响应格式application/json(流式 JSON)

请求参数

Query 参数

参数 类型 必填 默认 说明
dockerfile string Dockerfile Dockerfile 文件名
t string[] [] 镜像标签(可多个)
extrahosts string[] [] 额外的 hosts 映射
remote string "" Git URL(远程构建上下文)
q bool false 静默构建(抑制输出)
nocache bool false 不使用缓存
cachefrom string[] [] 缓存来源镜像
pull bool false 总是拉取基础镜像
rm bool true 构建成功后删除中间容器
forcerm bool false 总是删除中间容器
memory int64 0 内存限制(字节)
memswap int64 0 Swap 限制(字节)
cpushares int64 0 CPU shares
cpusetcpus string "" CPU 集合
cpuperiod int64 0 CPU CFS 周期
cpuquota int64 0 CPU CFS 配额
buildargs JSON {} 构建参数(ARG)
shmsize int64 0 /dev/shm 大小(字节)
squash bool false 压缩层(实验性)
labels JSON {} 镜像标签
networkmode string default 网络模式
platform string "" 目标平台(v1.32+)
target string "" 目标构建阶段
outputs JSON [] 输出配置(v1.40+)
version string 2 构建器版本(1/2)
session string "" Session ID
buildid string "" Build ID

请求体

  • tar 归档包含构建上下文(Dockerfile + 文件)

响应结构体

流式 JSON 响应(逐行):

// 普通日志
type BuildProgressStream struct {
    Stream string `json:"stream,omitempty"` // 构建日志
}

// 构建步骤
type BuildProgressStatus struct {
    Status   string `json:"status,omitempty"`   // 步骤状态
    ID       string `json:"id,omitempty"`       // 步骤 ID
    Progress string `json:"progress,omitempty"` // 进度
}

// 错误
type BuildProgressError struct {
    Error       string `json:"error,omitempty"`
    ErrorDetail struct {
        Message string `json:"message"`
    } `json:"errorDetail,omitempty"`
}

// 辅助信息(最终镜像 ID)
type BuildAux struct {
    Aux struct {
        ID string `json:"ID"` // 镜像 ID
    } `json:"aux"`
}

响应示例

{"stream":"Step 1/5 : FROM alpine:3.14\n"}
{"stream":" ---\u003e 14119a10abf4\n"}
{"stream":"Step 2/5 : RUN apk add --no-cache curl\n"}
{"stream":" ---\u003e Running in 3f8c2b1d5e6a\n"}
{"stream":"fetch http://dl-cdn.alpinelinux.org/alpine/v3.14/main/x86_64/APKINDEX.tar.gz\n"}
{"stream":"OK: 6 MiB in 15 packages\n"}
{"stream":" ---\u003e 8f3c1b2d3e4f\n"}
{"stream":"Step 3/5 : COPY app.sh /app.sh\n"}
{"stream":" ---\u003e 9a2b3c4d5e6f\n"}
{"stream":"Successfully built 9a2b3c4d5e6f\n"}
{"stream":"Successfully tagged myapp:latest\n"}
{"aux":{"ID":"sha256:9a2b3c4d5e6f..."}}

入口函数与核心代码

HTTP Handlerdaemon/server/router/build/build_routes.go):

func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    // 1. 设置响应头
    w.Header().Set("Content-Type", "application/json")
    
    // 2. 创建输出流
    output := ioutils.NewWriteFlusher(w)
    defer output.Close()
    
    // 3. 解析构建选项
    buildOptions, err := newImageBuildOptions(ctx, r)
    buildOptions.AuthConfigs = getAuthConfigs(r.Header)
    
    // 4. 获取进度写入器
    progBuff := &syncBuffer{Buffer: bytes.NewBuffer(nil)}
    buildOptions.ProgressWriter = buildProgressWriter(output, true, createProgressReader)
    
    // 5. 构建配置
    buildConfig := buildbackend.BuildConfig{
        Source:         body,
        Options:        buildOptions,
        ProgressWriter: buildOptions.ProgressWriter,
    }
    
    // 6. 执行构建
    imgID, err := br.backend.Build(ctx, buildConfig)
    
    // 7. 输出镜像 ID
    if imgID != "" {
        aux := &struct {
            ID string `json:"ID"`
        }{ID: imgID}
        output.Write(streamformatter.FormatAux(aux))
    }
    
    return nil
}

解析构建选项

func newImageBuildOptions(ctx context.Context, r *http.Request) (*buildbackend.BuildOptions, error) {
    options := &buildbackend.BuildOptions{
        Version:        build.BuilderV1,  // 默认 Builder V1,可覆盖
        Dockerfile:     r.FormValue("dockerfile"),
        SuppressOutput: httputils.BoolValue(r, "q"),
        NoCache:        httputils.BoolValue(r, "nocache"),
        ForceRemove:    httputils.BoolValue(r, "forcerm"),
        PullParent:     httputils.BoolValue(r, "pull"),
        Memory:         httputils.Int64ValueOrZero(r, "memory"),
        Tags:           r.Form["t"],
        Target:         r.FormValue("target"),
        RemoteContext:  r.FormValue("remote"),
        SessionID:      r.FormValue("session"),
        BuildID:        r.FormValue("buildid"),
    }
    
    // 解析 buildargs(JSON)
    if buildArgsJSON := r.FormValue("buildargs"); buildArgsJSON != "" {
        buildArgs := map[string]*string{}
        json.Unmarshal([]byte(buildArgsJSON), &buildArgs)
        options.BuildArgs = buildArgs
    }
    
    // 解析 labels(JSON)
    if labelsJSON := r.FormValue("labels"); labelsJSON != "" {
        labels := map[string]string{}
        json.Unmarshal([]byte(labelsJSON), &labels)
        options.Labels = labels
    }
    
    // 解析 version(Builder V1 / BuildKit)
    if bv := r.FormValue("version"); bv != "" {
        v, _ := parseVersion(bv)
        options.Version = v
    }
    
    return options, nil
}

Backend 实现daemon/server/buildbackend/build.go):

func (b *Backend) Build(ctx context.Context, config buildbackend.BuildConfig) (string, error) {
    // 1. 根据版本选择构建器
    switch config.Options.Version {
    case build.BuilderBuildKit:
        // BuildKit 构建
        return b.buildkit.Build(ctx, config)
    default:
        // Classic Builder 构建
        return b.builder.Build(ctx, config)
    }
}

Classic Builder 构建daemon/images/builder.go):

func (b *Builder) Build(ctx context.Context, config BuildConfig) (string, error) {
    // 1. 创建构建上下文
    buildContext, err := b.createBuildContext(config.Source)
    
    // 2. 解析 Dockerfile
    dockerfile, err := b.parseDockerfile(buildContext, config.Options.Dockerfile)
    
    // 3. 逐步执行 Dockerfile 指令
    var imageID string
    for _, instruction := range dockerfile.AST.Children {
        switch instruction.Value {
        case "FROM":
            imageID, err = b.processFromInstruction(instruction)
        case "RUN":
            imageID, err = b.processRunInstruction(instruction, imageID)
        case "COPY":
            imageID, err = b.processCopyInstruction(instruction, imageID, buildContext)
        case "ENV":
            imageID, err = b.processEnvInstruction(instruction, imageID)
        // ... 其他指令
        }
        
        // 写入进度
        config.ProgressWriter.Write(fmt.Sprintf("Step %d/%d : %s\n", i+1, total, instruction))
    }
    
    // 4. 打标签
    for _, tag := range config.Options.Tags {
        b.daemon.TagImage(imageID, tag)
    }
    
    return imageID, nil
}

BuildKit 构建daemon/server/buildbackend/buildkit/builder.go):

func (b *Builder) Build(ctx context.Context, config BuildConfig) (string, error) {
    // 1. 创建 BuildKit session
    session, err := b.sessionManager.Get(ctx, config.Options.SessionID)
    
    // 2. 构建前端(Dockerfile)
    frontend := "dockerfile.v0"
    
    // 3. 构建选项
    opts := map[string]string{
        "filename":   config.Options.Dockerfile,
        "target":     config.Options.Target,
        "no-cache":   strconv.FormatBool(config.Options.NoCache),
        "pull":       strconv.FormatBool(config.Options.PullParent),
    }
    
    // 4. 执行构建
    res, err := b.controller.Build(ctx, control.BuildOptions{
        Inputs: map[string]llb.State{
            "context": llb.Local("context"),
        },
        Frontend:      frontend,
        FrontendAttrs: opts,
        Session:       []session.Attachable{session},
    }, config.ProgressWriter)
    
    // 5. 导出镜像
    imageID, err := res.ExportImage(ctx)
    
    return imageID, nil
}

时序图

sequenceDiagram
    autonumber
    participant Client as Docker Client
    participant API as buildRouter
    participant Backend as Build Backend
    participant Builder as Builder (V1/BuildKit)
    participant ImageStore as Image Store
    participant ContainerD as containerd
    
    Note over Client,ContainerD: 阶段 1:准备构建上下文
    Client->>API: POST /build<br/>Body: build-context.tar
    API->>API: 解析 buildOptions
    API->>API: 创建进度写入器
    
    Note over Backend,Builder: 阶段 2:选择构建器
    API->>Backend: Build(ctx, buildConfig)
    Backend->>Backend: 检查 options.Version
    
    alt version == "2" (BuildKit)
        Backend->>Builder: buildkit.Build()
        Note right of Builder: 使用 BuildKit 引擎
    else version == "1" (Classic)
        Backend->>Builder: builder.Build()
        Note right of Builder: 使用经典构建器
    end
    
    Note over Builder,ImageStore: 阶段 3:解析 Dockerfile
    Builder->>Builder: 提取构建上下文
    Builder->>Builder: 读取 Dockerfile
    Builder->>Builder: 解析为 AST
    
    Note over Builder,ContainerD: 阶段 4:执行构建步骤
    loop 每个 Dockerfile 指令
        alt FROM
            Builder->>ImageStore: 拉取基础镜像
            ImageStore-->>Builder: baseImageID
            Builder->>API: Stream: "Step 1/5 : FROM alpine\n"
        else RUN
            Builder->>ContainerD: 创建容器(基于当前镜像)
            Builder->>ContainerD: 执行命令
            ContainerD-->>Builder: 容器退出
            Builder->>ImageStore: 提交为新层
            ImageStore-->>Builder: newImageID
            Builder->>API: Stream: "Step 2/5 : RUN ...\n"
        else COPY/ADD
            Builder->>Builder: 复制文件到镜像
            Builder->>ImageStore: 提交为新层
            Builder->>API: Stream: "Step 3/5 : COPY ...\n"
        else ENV/LABEL/WORKDIR
            Builder->>Builder: 更新镜像元数据
            Builder->>API: Stream: "Step 4/5 : ENV ...\n"
        end
    end
    
    Note over Builder,ImageStore: 阶段 5:标签与输出
    Builder->>ImageStore: 最终镜像 ID
    loop 每个标签
        Builder->>ImageStore: TagImage(imageID, tag)
        ImageStore-->>Builder: ok
    end
    
    Builder-->>Backend: imageID
    Backend-->>API: imageID
    
    API->>Client: Stream: "Successfully built {imageID}\n"
    API->>Client: {"aux":{"ID":"sha256:..."}}

说明

图意概述

展示镜像构建的完整流程,从上传构建上下文到逐步执行 Dockerfile 指令,最终生成镜像的全过程。

关键步骤详解

阶段 1-2:构建器选择(步骤 1-7)

  • Builder V1(Classic):传统构建器,逐步执行 Dockerfile 指令
  • Builder V2(BuildKit):现代构建器,支持并发构建、缓存优化、多阶段并行

阶段 3:Dockerfile 解析(步骤 8-10)

# 示例 Dockerfile
FROM alpine:3.14
RUN apk add --no-cache curl
COPY app.sh /app.sh
ENV APP_ENV=production
CMD ["/app.sh"]

阶段 4:指令执行(步骤 11-29)

FROM 指令

// 拉取或查找基础镜像
baseImage, err := imageStore.Get(ctx, "alpine:3.14")
currentImageID = baseImage.ID

RUN 指令

# 1. 创建容器
containerID = containerd.Create(currentImageID, cmd)

# 2. 启动并等待
containerd.Start(containerID)
containerd.Wait(containerID)

# 3. 提交为新层
newImageID = imageStore.Commit(containerID, "RUN apk add ...")
currentImageID = newImageID

COPY 指令

// 1. 从构建上下文提取文件
files := buildContext.Extract("app.sh")

// 2. 创建新层
layer := createLayer(files)
newImageID = imageStore.AddLayer(currentImageID, layer)
currentImageID = newImageID

ENV/LABEL 指令

// 仅更新镜像元数据,不创建新层
imageConfig.Env = append(imageConfig.Env, "APP_ENV=production")

边界条件

  • 构建失败:返回错误 JSON,清理中间容器
  • 缓存命中:跳过指令执行,使用缓存层
  • Squash 层:实验性功能,压缩所有层为一层
  • 多平台构建:BuildKit 支持,Classic Builder 不支持

性能指标

  • 小镜像(无 RUN):10-30 秒
  • 中等镜像(5-10 个 RUN):1-5 分钟
  • 大镜像(复杂依赖):5-30 分钟
  • BuildKit 优化:缓存命中可减少 80-90% 构建时间

2. 清理构建缓存

基本信息

  • 路径POST /build/prune
  • 用途:清理未使用的构建缓存
  • 最小 API 版本:v1.31
  • 幂等性:是

请求参数

Query 参数

参数 类型 必填 默认 说明
filters JSON {} 过滤器
all bool false 删除所有缓存(包括正在使用)
keep-storage int64 0 保留的存储空间(字节,已废弃)
reserved-space int64 0 保留空间(字节,v1.48+)
max-used-space int64 0 最大使用空间(字节,v1.48+)
min-free-space int64 0 最小空闲空间(字节,v1.48+)

过滤器选项

过滤器 说明
until 清理指定时间之前的缓存
id 清理指定 ID 的缓存
parent 清理指定父缓存

响应结构体

type CachePruneReport struct {
    // CachesDeleted 已删除的缓存 ID 列表
    CachesDeleted []string `json:"CachesDeleted"`
    
    // SpaceReclaimed 回收的磁盘空间(字节)
    SpaceReclaimed uint64 `json:"SpaceReclaimed"`
}

入口函数与核心代码

HTTP Handler

func (br *buildRouter) postPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    // 1. 解析过滤器
    fltrs, err := filters.FromJSON(r.Form.Get("filters"))
    
    // 2. 解析选项
    opts := buildbackend.CachePruneOptions{
        All:     httputils.BoolValue(r, "all"),
        Filters: fltrs,
    }
    
    // 3. 解析空间限制(v1.48+)
    version := httputils.VersionFromContext(ctx)
    if versions.GreaterThanOrEqualTo(version, "1.48") {
        opts.ReservedSpace, _ = parseBytesFromFormValue("reserved-space")
        opts.MaxUsedSpace, _ = parseBytesFromFormValue("max-used-space")
        opts.MinFreeSpace, _ = parseBytesFromFormValue("min-free-space")
    } else {
        opts.ReservedSpace, _ = parseBytesFromFormValue("keep-storage")
    }
    
    // 4. 执行清理
    report, err := br.backend.PruneCache(ctx, opts)
    
    return httputils.WriteJSON(w, http.StatusOK, report)
}

Backend 实现

func (b *Backend) PruneCache(ctx context.Context, opts CachePruneOptions) (*build.CachePruneReport, error) {
    // BuildKit 缓存清理
    if b.buildkit != nil {
        return b.buildkit.Prune(ctx, opts)
    }
    
    // Classic Builder 无缓存管理
    return &build.CachePruneReport{}, nil
}

时序图

sequenceDiagram
    autonumber
    participant Client as Docker Client
    participant API as buildRouter
    participant BuildKit as BuildKit
    participant CacheDB as Cache Store
    
    Client->>API: POST /build/prune?all=false
    API->>API: 解析过滤器与选项
    API->>BuildKit: PruneCache(ctx, opts)
    
    BuildKit->>CacheDB: 查询缓存记录
    CacheDB-->>BuildKit: [cache records]
    
    loop 每个缓存记录
        BuildKit->>BuildKit: 检查引用计数
        alt 未使用 && 匹配过滤器
            BuildKit->>BuildKit: 计算缓存大小
            BuildKit->>CacheDB: 删除缓存
            CacheDB-->>BuildKit: ok
            BuildKit->>BuildKit: 累计 deleted & reclaimed
        else 正在使用
            Note right of BuildKit: 跳过正在使用的缓存
        end
    end
    
    BuildKit-->>API: CachePruneReport
    API-->>Client: 200 OK<br/>{CachesDeleted, SpaceReclaimed}

异常与性能

异常场景

  • BuildKit 未启用:返回空报告
  • 过滤器格式错误:400 Bad Request

性能指标

  • 清理 1000 个缓存:5-30 秒

3. 取消构建

基本信息

  • 路径POST /build/cancel
  • 用途:取消正在进行的构建
  • 最小 API 版本:v1.24
  • 幂等性:是

请求参数

Query 参数

参数 类型 必填 说明
id string 构建 ID(buildid)

响应

  • 成功:204 No Content
  • 失败:错误 JSON

入口函数与核心代码

HTTP Handler

func (br *buildRouter) postCancel(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    // 1. 获取构建 ID
    id := r.FormValue("id")
    if id == "" {
        return invalidParam{errors.New("build ID not provided")}
    }
    
    // 2. 取消构建
    return br.backend.Cancel(ctx, id)
}

Backend 实现

func (b *Backend) Cancel(ctx context.Context, buildID string) error {
    // 查找构建任务
    build, exists := b.activeBuilds[buildID]
    if !exists {
        return errdefs.NotFound(errors.Errorf("build ID %s not found", buildID))
    }
    
    // 取消构建上下文
    build.cancelFunc()
    
    return nil
}

时序图

sequenceDiagram
    autonumber
    participant Client1 as Client 1
    participant Client2 as Client 2
    participant API as buildRouter
    participant Backend as Build Backend
    participant Builder as Builder
    
    Note over Client1,Builder: 构建正在进行
    Client1->>API: POST /build (buildid=abc123)
    API->>Backend: Build(ctx, config)
    Backend->>Builder: 执行构建...
    
    Note over Client2,Builder: 取消构建
    Client2->>API: POST /build/cancel?id=abc123
    API->>Backend: Cancel(ctx, "abc123")
    Backend->>Backend: 查找 activeBuilds["abc123"]
    Backend->>Builder: cancelFunc()
    
    Builder->>Builder: context.Canceled
    Builder-->>Backend: error: context canceled
    Backend-->>API: ok
    API-->>Client2: 204 No Content
    
    Backend-->>API: error: context canceled
    API-->>Client1: {"error":"context canceled"}

附录:Builder 版本对比

Classic Builder(V1)

特点

  • 串行执行 Dockerfile 指令
  • 简单的层缓存机制
  • 不支持并发构建
  • 不支持远程缓存

适用场景

  • 简单的单阶段构建
  • Windows 容器构建
  • 向后兼容

BuildKit(V2)

特点

  • 并发执行(依赖图优化)
  • 高级缓存(本地 + 远程)
  • 多平台构建
  • Secrets/SSH 挂载
  • 增量传输

适用场景

  • 复杂的多阶段构建
  • 大规模 CI/CD
  • 多平台镜像

文档版本:v1.0
最后更新:2025-10-04


数据结构

本文档详细描述构建器模块的核心数据结构,包括 UML 类图、字段说明、接口定义与使用场景。


数据结构概览

classDiagram
    class BuildConfig {
        -io.ReadCloser Source
        -ProgressWriter ProgressWriter
        -*BuildOptions Options
    }
    
    class BuildOptions {
        +[]string Tags
        +string RemoteContext
        +string Dockerfile
        +bool NoCache
        +bool PullParent
        +bool Squash
        +map~string,*string~ BuildArgs
        +map~string,string~ Labels
        +[]string CacheFrom
        +string Target
        +string Platform
        +string SessionID
        +string BuildID
        +BuilderVersion Version
        +[]BuildOutput Outputs
        +int64 Memory
        +int64 CPUShares
        +container.Isolation Isolation
    }
    
    class ProgressWriter {
        +io.Writer Output
        +io.Writer StdoutFormatter
        +io.Writer StderrFormatter
        +AuxEmitter AuxFormatter
        +func ProgressReaderFunc
    }
    
    class Builder {
        <<interface>>
        +Build(ctx, BuildConfig) (string, error)
        +PruneCache(ctx, CachePruneOptions) (*CachePruneReport, error)
        +Cancel(ctx, string) error
    }
    
    class ClassicBuilder {
        -*Daemon daemon
        -*Backend backend
        +Build(ctx, BuildConfig) (string, error)
    }
    
    class BuildKitBuilder {
        -*control.Controller controller
        -*session.SessionManager sessionManager
        +Build(ctx, BuildConfig) (string, error)
        +PruneCache(ctx, CachePruneOptions) (*CachePruneReport, error)
    }
    
    class CachePruneOptions {
        +bool All
        +int64 ReservedSpace
        +int64 MaxUsedSpace
        +int64 MinFreeSpace
        +filters.Args Filters
    }
    
    class BuildOutput {
        +string Type
        +map~string,string~ Attrs
    }
    
    BuildConfig "1" *-- "1" BuildOptions : contains
    BuildConfig "1" *-- "1" ProgressWriter : contains
    Builder <|.. ClassicBuilder : implements
    Builder <|.. BuildKitBuilder : implements
    BuildOptions "1" *-- "*" BuildOutput : contains

1. BuildConfig(构建配置)

结构定义

type BuildConfig struct {
    // Source 构建上下文(tar 归档)
    Source io.ReadCloser
    
    // ProgressWriter 进度输出
    ProgressWriter ProgressWriter
    
    // Options 构建选项
    Options *BuildOptions
}

字段说明

字段 类型 说明
Source io.ReadCloser 构建上下文的 tar 归档(包含 Dockerfile 和文件)
ProgressWriter ProgressWriter 进度输出流(stdout/stderr/aux)
Options *BuildOptions 详细的构建选项

使用场景

// 创建构建配置
buildConfig := buildbackend.BuildConfig{
    Source: tarArchive,  // 构建上下文 tar 流
    ProgressWriter: buildbackend.ProgressWriter{
        Output: responseWriter,  // HTTP 响应流
        AuxFormatter: auxEmitter,
    },
    Options: &buildbackend.BuildOptions{
        Tags:       []string{"myapp:latest"},
        Dockerfile: "Dockerfile",
        NoCache:    false,
        Version:    build.BuilderBuildKit,
    },
}

// 执行构建
imageID, err := builder.Build(ctx, buildConfig)

2. BuildOptions(构建选项)

结构定义

type BuildOptions struct {
    // 镜像标签
    Tags []string  // 例如:["myapp:latest", "myapp:v1.0"]
    
    // Dockerfile 配置
    Dockerfile     string  // Dockerfile 文件名(默认 "Dockerfile")
    RemoteContext  string  // Git URL(远程构建上下文)
    Target         string  // 目标构建阶段(多阶段构建)
    
    // 构建参数
    BuildArgs   map[string]*string  // ARG 变量(*string 用于区分空值与未设置)
    Labels      map[string]string   // 镜像标签
    
    // 缓存控制
    NoCache   bool     // 不使用缓存
    CacheFrom []string // 缓存来源镜像
    
    // 基础镜像
    PullParent bool  // 总是拉取基础镜像
    
    // 层压缩
    Squash bool  // 压缩所有层为一层(实验性)
    
    // 中间容器清理
    Remove      bool  // 构建成功后删除中间容器
    ForceRemove bool  // 总是删除中间容器
    
    // 资源限制
    Memory     int64   // 内存限制(字节)
    MemorySwap int64   // Swap 限制(字节)
    CPUShares  int64   // CPU shares(相对权重)
    CPUQuota   int64   // CPU CFS 配额
    CPUPeriod  int64   // CPU CFS 周期
    CPUSetCPUs string  // CPU 集合(例如:"0-3,8-11")
    CPUSetMems string  // 内存节点集合
    ShmSize    int64   // /dev/shm 大小(字节)
    
    // 网络与隔离
    NetworkMode  string               // 网络模式(default/host/none)
    Isolation    container.Isolation  // 容器隔离(Windows)
    CgroupParent string               // Cgroup 父路径
    
    // 安全与限制
    SecurityOpt []string            // 安全选项
    Ulimits     []*container.Ulimit // 资源限制
    ExtraHosts  []string            // 额外的 hosts 映射
    
    // 认证
    AuthConfigs map[string]registry.AuthConfig  // 注册表认证
    
    // 输出
    SuppressOutput bool  // 静默构建
    
    // 平台
    Platform string  // 目标平台(例如:"linux/amd64")
    
    // 构建器
    Version build.BuilderVersion  // 构建器版本(V1/BuildKit)
    
    // Session(BuildKit)
    SessionID string  // Session ID(BuildKit 专用)
    BuildID   string  // Build ID(用于取消构建)
    
    // 输出配置(BuildKit)
    Outputs []BuildOutput  // 构建输出配置
}

BuildArgs 特殊说明

// BuildArgs 使用 *string 而不是 string 的原因:
// 1. nil: 用户指定了 --build-arg FOO 但 FOO 未设置
// 2. "": 用户指定了 --build-arg FOO="" 或 FOO 为空字符串
// 3. "value": 用户指定了 --build-arg FOO=value

BuildArgs: map[string]*string{
    "NODE_VERSION": StringPtr("18"),    // 明确设置
    "DEBUG":        StringPtr(""),      // 设置为空
    "UNDEFINED":    nil,                // 未定义(会触发警告)
}

使用场景

// 多阶段构建示例
opts := &buildbackend.BuildOptions{
    Tags:       []string{"myapp:latest"},
    Dockerfile: "Dockerfile.multi",
    Target:     "production",  // 只构建到 production 阶段
    BuildArgs: map[string]*string{
        "GO_VERSION": StringPtr("1.21"),
        "APP_ENV":    StringPtr("prod"),
    },
    NoCache:    false,
    CacheFrom:  []string{"myapp:cache"},
    Version:    build.BuilderBuildKit,
}

// 资源限制构建
opts := &buildbackend.BuildOptions{
    Tags:       []string{"heavy-app:latest"},
    Memory:     2 * 1024 * 1024 * 1024,  // 2GB
    CPUShares:  1024,
    CPUSetCPUs: "0-3",
    ShmSize:    512 * 1024 * 1024,  // 512MB
}

// 跨平台构建(BuildKit)
opts := &buildbackend.BuildOptions{
    Tags:     []string{"myapp:latest"},
    Platform: "linux/arm64",
    Version:  build.BuilderBuildKit,
}

3. ProgressWriter(进度写入器)

结构定义

type ProgressWriter struct {
    // Output 主输出流
    Output io.Writer
    
    // StdoutFormatter stdout 格式化器
    StdoutFormatter io.Writer
    
    // StderrFormatter stderr 格式化器
    StderrFormatter io.Writer
    
    // AuxFormatter 辅助信息发射器
    AuxFormatter AuxEmitter
    
    // ProgressReaderFunc 进度读取函数
    ProgressReaderFunc func(io.ReadCloser) io.ReadCloser
}

// AuxEmitter 辅助信息发射器接口
type AuxEmitter interface {
    Emit(string, any) error
}

字段说明

字段 类型 说明
Output io.Writer 主输出流(通常为 HTTP 响应)
StdoutFormatter io.Writer 标准输出格式化(用于 RUN 命令输出)
StderrFormatter io.Writer 标准错误格式化(用于错误信息)
AuxFormatter AuxEmitter 辅助信息发射器(用于镜像 ID 等)
ProgressReaderFunc func 进度读取包装函数

使用场景

// 创建进度写入器
pw := buildbackend.ProgressWriter{
    Output: httpResponseWriter,
    AuxFormatter: &auxEmitter{
        emitFunc: func(msg string, obj any) error {
            return json.NewEncoder(httpResponseWriter).Encode(obj)
        },
    },
}

// 输出构建日志
pw.Output.Write([]byte(`{"stream":"Step 1/5 : FROM alpine\n"}`))

// 输出辅助信息(镜像 ID)
pw.AuxFormatter.Emit("ID", map[string]string{
    "ID": "sha256:abc123...",
})

4. Builder 接口

接口定义

type Builder interface {
    // Build 构建镜像
    Build(ctx context.Context, config BuildConfig) (string, error)
    
    // PruneCache 清理构建缓存
    PruneCache(ctx context.Context, opts CachePruneOptions) (*build.CachePruneReport, error)
    
    // Cancel 取消构建
    Cancel(ctx context.Context, buildID string) error
}

实现类:ClassicBuilder

type ClassicBuilder struct {
    daemon  *Daemon
    backend *Backend
}

func (b *ClassicBuilder) Build(ctx context.Context, config BuildConfig) (string, error) {
    // 1. 提取构建上下文
    buildContext, err := b.extractContext(config.Source)
    
    // 2. 解析 Dockerfile
    dockerfile, err := b.parseDockerfile(buildContext, config.Options.Dockerfile)
    
    // 3. 逐步执行指令
    var imageID string
    for _, instruction := range dockerfile.AST.Children {
        imageID, err = b.processInstruction(ctx, instruction, imageID, config)
        config.ProgressWriter.Output.Write(progressJSON)
    }
    
    // 4. 打标签
    for _, tag := range config.Options.Tags {
        b.daemon.TagImage(imageID, tag)
    }
    
    return imageID, nil
}

实现类:BuildKitBuilder

type BuildKitBuilder struct {
    controller     *control.Controller
    sessionManager *session.SessionManager
}

func (b *BuildKitBuilder) Build(ctx context.Context, config BuildConfig) (string, error) {
    // 1. 获取 session
    session, err := b.sessionManager.Get(ctx, config.Options.SessionID)
    
    // 2. 构建选项
    opts := map[string]string{
        "filename": config.Options.Dockerfile,
        "target":   config.Options.Target,
    }
    
    // 3. 执行构建
    res, err := b.controller.Build(ctx, control.BuildOptions{
        Frontend:      "dockerfile.v0",
        FrontendAttrs: opts,
        Session:       []session.Attachable{session},
    }, config.ProgressWriter)
    
    // 4. 导出镜像
    imageID, err := res.ExportImage(ctx)
    
    return imageID, nil
}

func (b *BuildKitBuilder) PruneCache(ctx context.Context, opts CachePruneOptions) (*build.CachePruneReport, error) {
    // BuildKit 缓存清理
    records, err := b.controller.Prune(ctx, opts.Filters, opts.All)
    
    var deleted []string
    var reclaimed uint64
    for _, record := range records {
        deleted = append(deleted, record.ID)
        reclaimed += uint64(record.Size)
    }
    
    return &build.CachePruneReport{
        CachesDeleted:  deleted,
        SpaceReclaimed: reclaimed,
    }, nil
}

5. CachePruneOptions(缓存清理选项)

结构定义

type CachePruneOptions struct {
    // All 删除所有缓存(包括正在使用)
    All bool
    
    // ReservedSpace 保留的存储空间(字节)
    ReservedSpace int64
    
    // MaxUsedSpace 最大使用空间(字节)
    MaxUsedSpace int64
    
    // MinFreeSpace 最小空闲空间(字节)
    MinFreeSpace int64
    
    // Filters 过滤器
    Filters filters.Args
}

使用场景

// 清理所有缓存
opts := CachePruneOptions{
    All: true,
}

// 清理超过 1 周的缓存
opts := CachePruneOptions{
    Filters: filters.NewArgs(
        filters.Arg("until", "168h"),  // 7 days
    ),
}

// 保留 10GB 空间
opts := CachePruneOptions{
    MinFreeSpace: 10 * 1024 * 1024 * 1024,
}

6. BuildOutput(构建输出)

结构定义

type BuildOutput struct {
    // Type 输出类型
    Type string
    
    // Attrs 输出属性
    Attrs map[string]string
}

输出类型

Type 说明 Attrs 示例
image 导出为镜像 {"name":"myapp:latest"}
local 导出到本地目录 {"dest":"/tmp/output"}
tar 导出为 tar 归档 {"dest":"/tmp/output.tar"}
registry 直接推送到注册表 {"name":"registry.io/myapp:latest"}

使用场景(BuildKit)

// 导出到本地目录
opts := &BuildOptions{
    Version: build.BuilderBuildKit,
    Outputs: []BuildOutput{
        {
            Type: "local",
            Attrs: map[string]string{
                "dest": "/tmp/build-output",
            },
        },
    },
}

// 多平台导出
opts := &BuildOptions{
    Version:  build.BuilderBuildKit,
    Platform: "linux/amd64,linux/arm64",
    Outputs: []BuildOutput{
        {
            Type: "image",
            Attrs: map[string]string{
                "name":              "myapp:latest",
                "push":              "true",
                "annotation-key":    "value",
            },
        },
    },
}

数据流图

构建流程

Client
  ↓ tar archive
API Handler
  ↓ BuildConfig
Backend
  ├→ ClassicBuilder (Version=1)
  │   ↓
  │   ├→ 解析 Dockerfile
  │   ├→ 逐步执行指令
  │   └→ 生成镜像 ID
  └→ BuildKitBuilder (Version=2)
      ├→ 创建 BuildKit session
      ├→ 并发执行构建图
      └→ 生成镜像 ID

构建器版本对比

特性 Classic Builder(V1) BuildKit(V2)
执行模式 串行 并发(依赖图)
缓存 简单层缓存 高级缓存(本地+远程)
多平台
Secrets
SSH 挂载
输出控制 ✅(多种格式)
并行构建
增量传输
平台支持 Linux/Windows Linux(Windows 实验性)

文档版本:v1.0
最后更新:2025-10-04


时序图

本文档通过时序图展示构建器模块的典型操作流程,包括镜像构建、缓存管理等关键场景。


时序图目录

  1. Classic Builder 构建流程
  2. BuildKit 构建流程
  3. 多阶段构建流程
  4. 构建缓存命中流程
  5. 构建缓存清理流程

1. Classic Builder 构建流程

时序图

sequenceDiagram
    autonumber
    participant Client as Docker Client
    participant API as Build API
    participant Builder as Classic Builder
    participant Parser as Dockerfile Parser
    participant ImageStore as Image Store
    participant ContainerD as containerd
    
    Note over Client,ContainerD: 阶段 1:准备
    Client->>API: POST /build<br/>tar archive
    API->>Builder: Build(ctx, config)
    Builder->>Builder: 提取构建上下文
    Builder->>Parser: 解析 Dockerfile
    Parser-->>Builder: AST
    
    Note over Builder,ContainerD: 阶段 2:FROM 指令
    Builder->>ImageStore: 查找基础镜像
    alt 镜像不存在
        ImageStore->>ImageStore: 拉取镜像
    end
    ImageStore-->>Builder: baseImageID
    Builder->>API: Stream: "Step 1/5 : FROM alpine\n"
    
    Note over Builder,ContainerD: 阶段 3:RUN 指令
    Builder->>ContainerD: 创建容器(baseImageID, cmd)
    ContainerD-->>Builder: containerID
    Builder->>ContainerD: Start(containerID)
    Builder->>ContainerD: Wait(containerID)
    ContainerD-->>Builder: exitCode
    
    alt exitCode != 0
        Builder-->>API: {"error":"RUN command failed"}
    else exitCode == 0
        Builder->>ImageStore: Commit(containerID)
        ImageStore-->>Builder: newImageID
        Builder->>ContainerD: Remove(containerID)
        Builder->>API: Stream: "Step 2/5 : RUN apk add curl\n"
    end
    
    Note over Builder,ImageStore: 阶段 4:COPY 指令
    Builder->>Builder: 从上下文提取文件
    Builder->>ImageStore: AddLayer(imageID, files)
    ImageStore-->>Builder: newImageID
    Builder->>API: Stream: "Step 3/5 : COPY app.sh /\n"
    
    Note over Builder,ImageStore: 阶段 5:ENV/CMD 指令
    Builder->>Builder: 更新镜像配置
    Builder->>API: Stream: "Step 4/5 : ENV APP=prod\n"
    Builder->>API: Stream: "Step 5/5 : CMD [\"/app.sh\"]\n"
    
    Note over Builder,ImageStore: 阶段 6:标签与输出
    loop 每个标签
        Builder->>ImageStore: TagImage(imageID, tag)
    end
    
    Builder-->>API: imageID
    API->>Client: Stream: "Successfully built {imageID}\n"
    API->>Client: {"aux":{"ID":"sha256:..."}}

说明

图意概述

展示 Classic Builder 的串行构建流程,从解析 Dockerfile 到逐步执行每个指令,最终生成镜像。

关键步骤

FROM 指令执行

  • 查找本地镜像
  • 本地不存在则拉取
  • 设置为当前构建基础

RUN 指令执行

# 1. 创建容器
docker create <imageID> /bin/sh -c "apk add --no-cache curl"

# 2. 启动容器
docker start <containerID>

# 3. 等待退出
docker wait <containerID>

# 4. 提交为新层
docker commit <containerID> → newImageID

# 5. 删除容器
docker rm <containerID>

COPY 指令执行

  • 从构建上下文提取文件
  • 创建新的镜像层
  • 更新镜像 ID

ENV/CMD 指令执行

  • 仅更新镜像元数据
  • 不创建新层

性能指标

  • 小镜像(2-3 层):10-30 秒
  • 中等镜像(5-10 层):1-5 分钟
  • 大镜像(20+ 层):10-30 分钟

2. BuildKit 构建流程

时序图

sequenceDiagram
    autonumber
    participant Client as Docker Client
    participant API as Build API
    participant BuildKit as BuildKit Builder
    participant Frontend as Dockerfile Frontend
    participant LLB as LLB Executor
    participant Cache as Cache Store
    participant Snapshotter as Snapshotter
    
    Note over Client,Snapshotter: 阶段 1:初始化
    Client->>API: POST /build<br/>version=2
    API->>BuildKit: Build(ctx, config)
    BuildKit->>Frontend: 解析 Dockerfile
    Frontend->>Frontend: 生成 LLB 依赖图
    
    Note over Frontend,LLB: 阶段 2:生成执行图
    Frontend-->>LLB: LLB Graph
    LLB->>LLB: 分析依赖关系
    LLB->>LLB: 确定可并行步骤
    
    Note over LLB,Cache: 阶段 3:并发执行
    par 并发执行基础镜像
        LLB->>Cache: 检查缓存(FROM alpine)
        Cache-->>LLB: cache hit
    and 并发执行独立步骤
        LLB->>Cache: 检查缓存(RUN apk update)
        alt cache miss
            LLB->>Snapshotter: 执行 RUN
            Snapshotter-->>LLB: snapshot
            LLB->>Cache: 保存缓存
        end
    end
    
    Note over LLB,Snapshotter: 阶段 4:合并结果
    LLB->>Snapshotter: 合并所有层
    Snapshotter-->>LLB: finalSnapshotID
    
    Note over BuildKit: 阶段 5:导出镜像
    LLB-->>BuildKit: build result
    BuildKit->>BuildKit: 导出为镜像
    BuildKit-->>API: imageID
    API->>Client: {"aux":{"ID":"sha256:..."}}

说明

图意概述

展示 BuildKit 的并发构建流程,通过依赖图分析实现最大程度的并行化。

BuildKit 优势

并发执行

# Dockerfile
FROM alpine:3.14 AS base1
RUN apk add --no-cache curl

FROM alpine:3.14 AS base2
RUN apk add --no-cache wget

FROM base1 AS final
COPY --from=base2 /usr/bin/wget /usr/bin/

执行顺序

base1 和 base2 并发执行 ────┐
                         final

性能提升

  • 并发构建:减少 50-70% 构建时间
  • 高级缓存:命中率提升 30-50%
  • 增量传输:减少 60-80% 网络传输

3. 多阶段构建流程

时序图

sequenceDiagram
    autonumber
    participant Client as Client
    participant Builder as Builder
    participant Stage1 as Stage: builder
    participant Stage2 as Stage: runtime
    
    Note over Client,Stage2: Dockerfile 示例
    Note right of Client: FROM golang:1.21 AS builder<br/>WORKDIR /app<br/>COPY . .<br/>RUN go build -o app<br/><br/>FROM alpine:3.14 AS runtime<br/>COPY --from=builder /app/app /<br/>CMD ["/app"]
    
    Client->>Builder: 构建多阶段镜像
    
    Note over Builder,Stage1: 阶段 1:builder
    Builder->>Stage1: FROM golang:1.21
    Builder->>Stage1: COPY . .
    Builder->>Stage1: RUN go build -o app
    Stage1-->>Builder: builderImageID
    
    Note over Builder,Stage2: 阶段 2:runtime
    Builder->>Stage2: FROM alpine:3.14
    Builder->>Stage1: 提取 /app/app
    Stage1-->>Builder: app binary
    Builder->>Stage2: COPY app /
    Stage2-->>Builder: runtimeImageID
    
    Note over Builder: 仅保留最终阶段
    Builder-->>Client: runtimeImageID
    
    Note right of Client: 最终镜像:alpine + app binary<br/>构建工具未包含在内

说明

图意概述

展示多阶段构建如何分离构建环境和运行环境,减小最终镜像大小。

多阶段构建优势

镜像大小对比

  • 单阶段(golang:1.21):800+ MB
  • 多阶段(alpine):10-20 MB

安全性提升

  • 构建工具未包含在最终镜像
  • 减少攻击面

4. 构建缓存命中流程

时序图

sequenceDiagram
    autonumber
    participant Builder as Builder
    participant Cache as Cache Store
    participant Executor as Executor
    
    Note over Builder,Executor: 第一次构建
    Builder->>Cache: 查找缓存(FROM alpine)
    Cache-->>Builder: cache miss
    Builder->>Executor: 拉取 alpine
    Executor-->>Builder: imageID
    Builder->>Cache: 保存缓存(key=FROM alpine)
    
    Builder->>Cache: 查找缓存(RUN apk add curl)
    Cache-->>Builder: cache miss
    Builder->>Executor: 执行 RUN
    Executor-->>Builder: newImageID
    Builder->>Cache: 保存缓存(key=RUN+parent)
    
    Note over Builder,Executor: 第二次构建(未修改)
    Builder->>Cache: 查找缓存(FROM alpine)
    Cache-->>Builder: cache hit (imageID)
    Note right of Builder: 跳过执行
    
    Builder->>Cache: 查找缓存(RUN apk add curl)
    Cache-->>Builder: cache hit (imageID)
    Note right of Builder: 跳过执行
    
    Note right of Builder: 构建时间:5s → 0.5s

说明

缓存键计算

Classic Builder

cacheKey = hash(instruction + parentImageID + context files)

BuildKit

cacheKey = hash(LLB vertex + input snapshots + metadata)

缓存失效场景

  • Dockerfile 指令修改
  • 上下文文件变更(COPY/ADD)
  • 父层缓存失效(级联失效)
  • 使用 --no-cache 选项

5. 构建缓存清理流程

时序图

sequenceDiagram
    autonumber
    participant Client as Client
    participant API as API
    participant BuildKit as BuildKit
    participant CacheDB as Cache DB
    
    Client->>API: POST /build/prune?all=false
    API->>BuildKit: PruneCache(opts)
    
    BuildKit->>CacheDB: 查询所有缓存记录
    CacheDB-->>BuildKit: [cache records]
    
    loop 每个缓存记录
        BuildKit->>BuildKit: 检查引用计数
        alt 未使用 && 匹配过滤器
            BuildKit->>BuildKit: 计算缓存大小
            BuildKit->>CacheDB: 删除缓存记录
            CacheDB-->>BuildKit: ok
            BuildKit->>BuildKit: 累计已删除与空间
        else 正在使用
            Note right of BuildKit: 跳过
        end
    end
    
    BuildKit-->>API: PruneReport
    API-->>Client: 200 OK<br/>{CachesDeleted, SpaceReclaimed}