Prhub

#40653 build: embed image provenance metadata in vLLM containers

原始 PR 作者 alec-flowers 合并时间 2026-04-29 18:07 文件变更 8 提交数 6 评论 14 代码增减 +376 / -10

执行摘要

在容器镜像中嵌入构建来源元数据

当后续发现一个容器镜像(尤其是以 .sqsh 格式存在)时,应能通过检查镜像回答:这个镜像是哪个流水线产生的?哪个 commit?应该拉取哪个公共镜像?用了哪些构建标志?该 PR 提供比完整 SBOM 更轻量的来源信息,使下游工具(如 benchmark runners 和 srt-slurm)能稳定地识别和区分 vLLM 官方镜像与手动构建镜像。

该 PR 值得精读,特别是 docker-build-metadata-args.sh 的 fallback 设计和 Buildkite 流水线的集中化改造。对于维护类似 CI/CD 流水线的团队,这是一个很好的参考模式,展示了如何在构建过程中注入可追溯的元数据。

讨论亮点

Review 聚焦于三个主要问题:

  1. gemini-code-assist 指出 release-pipeline.yaml 中 nightly 构建的 IMAGE_REF 错误使用了内部 staging 引用(如 $STAGING_REF),而应使用公共命名(如 vllm/vllm-openai:nightly-${BUILDKITE_COMMIT}-ubuntu2404),以确保元数据正确。
  2. khluu 认为 image_build.sh 中新增的 COMMITVLLM_BUILD_KIND 等变量目前不需要,alec-flowers 解释这是为了支持将镜像导入其他格式(如 enroot)时保留元数据。
  3. khluu 建议对脚本调用添加 || true 或移除 -e 标志以避免脚本失败阻断构建,alec-flowers 同意简化脚本使其不会失败,但强调脚本必须可靠输出 tag 供后续 push 使用。

实现拆解

  1. 创建 .buildkite/scripts/docker-build-metadata-args.sh:根据环境变量(BUILDKITE_COMMITBUILDKITE_PIPELINE_IDRELEASE_VERSION等)生成 --build-arg--tag 参数,并内建 fallback 逻辑(缺失值回退到 unknownlocal)。
  2. 修改 docker/Dockerfile:添加 ARG 声明(VLLM_BUILD_COMMITVLLM_BUILD_PIPELINEVLLM_BUILD_URLVLLM_IMAGE_TAG),通过 ENVLABEL 命令将元数据嵌入镜像。
  3. 修改 docker/docker-bake.hcl:添加对应变量并在 _common_labels target 中引用,使 bake 构建也获得相同元数据。
  4. 修改 .buildkite/release-pipeline.yaml:将所有发布镜像的 docker build 命令替换为调用 $(bash .buildkite/scripts/docker-build-metadata-args.sh) 生成参数,并删除硬编码的 --tag 逻辑,统一由脚本处理。
  5. 新增 tests/tools/test_docker_build_metadata_args.py.buildkite/test_areas/docker.yaml:自动化测试验证脚本输出是否符合预期(pipeline_id 优先、pipeline_slug 回退、本地覆盖、版本查找失败回退等),并在 CI 中作为单独的测试步骤运行。
文件 模块 状态 重要度
.buildkite/scripts/docker-build-metadata-args.sh 元数据脚本 added 5.6
tests/tools/test_docker_build_metadata_args.py 测试 added 7.6
.buildkite/release-pipeline.yaml 发布流水线 modified 5.54
docker/docker-bake.hcl 构建配置 modified 4.56
docker/Dockerfile Docker 文件 modified 4.35
.buildkite/test_areas/docker.yaml CI 配置 added 3.91
.buildkite/image_build/image_build.sh 辅助脚本 modified 2.0
docker/Dockerfile.cpu Docker 文件 modified 1.84

关键符号

test_release_metadata_args_prefer_pipeline_id test_nightly_metadata_args_fall_back_to_pipeline_slug test_local_metadata_args_use_local_overrides test_release_version_lookup_failure_falls_back_to_commit test_vllm_openai_image_embeds_metadata_contract

关键源码片段

.buildkite/scripts/docker-build-metadata-args.sh core-logic

核心脚本,集中负责根据环境变量生成 Docker 构建参数和标签,是整个元数据注入的入口。

#!/bin/bash
# Build provenance metadata helper
# 根据构建环境生成 Docker build 参数和标签
# 该脚本是 best-effort:缺失的值会回退到本地 / 默认值,不会阻塞构建# 变体后缀(如 cu129、ubuntu2404、cu129-ubuntu2404)
variant="${1:-}"
variant_suffix="${variant:+-${variant}}"image_name="${VLLM_DOCKER_IMAGE_NAME:-vllm/vllm-openai}"
staging_repo="${VLLM_STAGING_IMAGE_REPO:-public.ecr.aws/q9t5s3a7/vllm-release-repo}"
build_commit="${VLLM_BUILD_COMMIT:-${BUILDKITE_COMMIT:-unknown}}"
# 优先使用 VLLM_BUILD_PIPELINE,再回退到 BUILDKITE_PIPELINE_ID,再回退到 BUILDKITE_PIPELINE_SLUG
build_pipeline="${VLLM_BUILD_PIPELINE:-${BUILDKITE_PIPELINE_ID:-${BUILDKITE_PIPELINE_SLUG:-local}}}"
build_url="${VLLM_BUILD_URL:-${BUILDKITE_BUILD_URL:-}}"
tag_commit="${BUILDKITE_COMMIT:-${build_commit}}"if [[ -n "${BUILDKITE:-}" || -n "${BUILDKITE_COMMIT:-}" ]]; then
    release_version="${RELEASE_VERSION:-}"
    # 尝试从 Buildkite agent 获取发布版本元数据
    if command -v buildkite-agent >/dev/null 2>&1; then
        release_version="${release_version:-$(buildkite-agent meta-data get release-version 2>/dev/null)}"
    fi
    release_version="${release_version#v}"
    release_version="${release_version:-${tag_commit}}"    staging_image_ref="${staging_repo}:${tag_commit}-$(uname -m)${variant_suffix}"    if [[ "${NIGHTLY:-}" == "1" ]]; then
        # 夜间构建:生成 nightly- 开头的 tag
        if [[ -z "${variant}" ]]; then
            image_tag="${image_name}:nightly-${tag_commit}"
        elif [[ "${variant}" == cu* ]]; then
            cuda_variant="${variant%%-*}"
            remaining_variant="${variant#${cuda_variant}}"
            image_tag="${image_name}:${cuda_variant}-nightly-${tag_commit}${remaining_variant}"
        else
            image_tag="${image_name}:nightly-${tag_commit}${variant_suffix}"
        fi
    else
        # 发布构建:使用版本号
        image_tag="${image_name}:v${release_version}${variant_suffix}"
    fi
else
    # 本地构建:使用默认值或环境变量覆盖
    image_tag="${VLLM_IMAGE_TAG:-local/vllm-openai:dev}"
    staging_image_ref="${image_tag}"
fi# 输出 build-arg 和 tag 参数
emit_arg() {
    printf -- "--build-arg %s=%s " "$1" "$2"
}
​
emit_arg VLLM_BUILD_COMMIT "${build_commit}"
emit_arg VLLM_BUILD_PIPELINE "${build_pipeline}"
emit_arg VLLM_BUILD_URL "${build_url}"
emit_arg VLLM_IMAGE_TAG "${image_tag}"
printf -- "--tag %s " "${staging_image_ref}"
tests/tools/test_docker_build_metadata_args.py test-coverage

完整的测试覆盖,验证脚本在 release、nightly、local 等场景下的输出正确性,确保元数据契约有效。

import os
import shlex
import subprocess
from pathlib import PathREPO_ROOT = Path(__file__).resolve().parents[2]
HELPER = REPO_ROOT / ".buildkite" / "scripts" / "docker-build-metadata-args.sh"
​
​
def run_helper(
    *args: str,
    env: dict[str, str] | None = None,
    path: str | None = None,
) -> list[str]:
    # 运行辅助脚本并返回解析后的参数列表
    helper_env = {"PATH": path or os.environ["PATH"]}
    if env:
        helper_env.update(env)
    result = subprocess.run(
        ["bash", str(HELPER), *args],
        check=True,
        env=helper_env,
        stdout=subprocess.PIPE,
        text=True,
    )
    return shlex.split(result.stdout)
​
​
def option_values(args: list[str], option: str) -> list[str]:
    # 从参数列表中提取指定选项的值
    return [args[i + 1] for i, arg in enumerate(args[:-1]) if arg == option]
​
​
def build_args(args: list[str]) -> dict[str, str]:
    # 从参数列表中提取所有 --build-arg 键值对
    values = {}
    for value in option_values(args, "--build-arg"):
        key, arg_value = value.split("=", 1)
        values[key] = arg_value
    return values
​
​
def test_release_metadata_args_prefer_pipeline_id() -> None:
    # 验证 release 场景:优先使用 BUILDKITE_PIPELINE_ID
    args = run_helper(
        "cu130-ubuntu2404",
        env={
            "BUILDKITE": "1",
            "BUILDKITE_COMMIT": "abc123",
            "BUILDKITE_PIPELINE_ID": "pipe-uuid",
            "BUILDKITE_PIPELINE_SLUG": "release",
            "BUILDKITE_BUILD_URL": "https://buildkite.example/vllm/builds/1",
            "RELEASE_VERSION": "v0.20.0",
        },
    )
​
    assert build_args(args) == {
        "VLLM_BUILD_COMMIT": "abc123",
        "VLLM_BUILD_PIPELINE": "pipe-uuid",
        "VLLM_BUILD_URL": "https://buildkite.example/vllm/builds/1",
        "VLLM_IMAGE_TAG": "vllm/vllm-openai:v0.20.0-cu130-ubuntu2404",
    }
    # 验证 --tag 参数指向正确的 staging 引用
    expected_tag = (
        "public.ecr.aws/q9t5s3a7/vllm-release-repo:"
        f"abc123-{os.uname().machine}-cu130-ubuntu2404"
    )
    assert option_values(args, "--tag") == [expected_tag]

评论区精华

夜间构建镜像标签错误使用内部 staging 引用 正确性

gemini-code-assist 指出在 release-pipeline.yaml 中,nightly 构建(如 ubuntu2404)的 IMAGE_REF 错误设置为 `$STAGING_REF`(内部引用),而应该使用公共命名如 `vllm/vllm-openai:nightly-${BUILDKITE_COMMIT}-ubuntu2404`,以确保元数据正确。

结论:通过后续提交(build: fix release image metadata refs)已修正,使用 Buildkite pipeline metadata 并设置正确的 nightly tag 格式。 · 已解决

image_build.sh 新增变量是否必要 设计

khluu 认为新增的 `COMMIT`、`VLLM_BUILD_KIND` 等变量当前不需要,建议移除。alec-flowers 回应这些变量用于支持将镜像导入其他格式(如 enroot)时保留 tag 元数据。

结论:作者保留变量,khluu 未进一步反对,讨论结束。 · 已解决

脚本失败应使用 || true 或移除 -e 标志 正确性

khluu 建议在 release-pipeline.yaml 中对脚本调用加上 `|| true` 或移除脚本中的 `-e` 标志,以避免脚本失败阻断 docker build。alec-flowers 回应脚本会简化确保不会失败,但必须可靠输出 tag。

结论:作者同意简化脚本使其不会失败,但未明确采纳 `|| true` 建议。后续提交简化了脚本逻辑。 · 已解决

风险与影响

  1. 构建失败风险:如果 docker-build-metadata-args.sh 在 CI 中意外退出(如未知变量),将导致后续构建步骤缺少关键参数。当前脚本包含 fallback 默认值,但仍需确保 CI 环境变量齐全。
  2. 元数据错误风险:如果环境变量设置不当,脚本可能回退到 unknownlocal,导致难以追溯镜像来源。测试覆盖了回退场景,但 CI 环境一致性仍需保证。
  3. 发布流水线变更风险:重构后的 release-pipeline.yaml 将所有镜像构建的 tag 生成集中到脚本,若脚本有 bug 会同时影响多个构建步骤。通过增加测试和渐近式发布可以降低风险。

用户可以通过 docker inspect 看到额外标签(如 ai.vllm.build.commit)和环境变量,方便溯源。构建系统增加了一个轻量脚本依赖和测试步骤,构建时间几乎无变化。团队将统一使用脚本管理镜像标签,减少硬编码错误。该变更仅影响 CUDA OpenAI 镜像,不影响其他镜像或运行时。

构建脚本单点故障 元数据回退依赖环境变量 发布流水线变更风险

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论