Vertex AI Pipelines 生产实践:自定义 Docker 镜像构建与部署

📅 2026/7/19 6:32:11
Vertex AI Pipelines 生产实践:自定义 Docker 镜像构建与部署
1. 项目概述为什么在 Vertex AI Pipelines 中坚持用自定义 Docker 镜像我带过六支 MLOps 工程团队从金融风控模型到工业缺陷检测系统几乎每个项目上线前都卡在同一个地方Pipeline 运行时环境不一致。不是 Python 版本对不上就是 PyTorch CUDA 编译版本和训练集群不匹配再或者某个小众数据处理库的 C 依赖在默认镜像里压根没装——结果就是 pipeline 在本地 notebook 里跑得飞起一提交到 Vertex AI 就报ModuleNotFoundError或Illegal instruction (core dumped)。这种问题排查起来特别耗神平均要花掉新人两天时间老手也得折腾大半天。后来我们统一立下一条铁规所有生产级 Vertex AI Pipeline 组件必须基于明确声明、可复现、可审计的自定义 Docker 镜像。这不是为了炫技而是把“环境”这个最不可控的变量变成一个可版本化、可测试、可回滚的确定性产物。这篇内容讲的就是这条铁规落地的具体操作。它不是教你怎么写一个“能跑通”的 demo而是带你从零开始亲手构建一个真正能进生产环境的训练镜像和部署镜像。核心关键词是Vertex AI Pipelines、custom Docker images和Towards AI - Medium所代表的那种务实、可复现、面向工程落地的技术风格。它适合三类人第一类是刚接触 Vertex AI 的 ML 工程师还在用kfp.components.load_component_from_url()加载官方组件对环境隔离毫无概念第二类是已经用上 Kubeflow 的团队但 Pipeline 组件还混着用python3命令行脚本和gcr.io/ml-pipeline/官方镜像每次升级都提心吊胆第三类是正在做 MLOps 架构选型的技术负责人需要一份真实、无水分、经得起推敲的容器化实践参考。它解决的问题非常具体如何让你的模型训练代码在 Vertex AI 的任何节点上都以完全相同的方式执行答案不在 YAML 配置里而在你亲手写的那几行FROM、COPY和RUN里。我试过不下十种方案用 Google 官方的gcr.io/deeplearning-platform-release/tf2-cpu.2-8镜像直接改结果发现它内部预装了太多冗余包启动慢、体积大而且版本更新策略不透明也试过用pip install在运行时动态装包结果遇到scikit-learn和numpy的 ABI 兼容性问题模型精度肉眼可见地漂移了 0.3%。最后我们回归本质——Dockerfile 就是你的环境说明书它必须精确到每一个字节。所以这篇文章里你不会看到任何“一键生成”或“自动配置”的黑盒工具只有清晰的步骤、每一步背后的原理以及我踩过的那些坑。比如为什么WORKDIR必须设为/app而不是/workspace为什么requirements.txt里的包版本号后面一定要加而不是为什么gcloud auth configure-docker这个命令看似简单却能在你凌晨三点排查镜像推送失败时成为救命稻草这些细节才是决定一个 Pipeline 是玩具还是生产系统的分水岭。2. 核心设计思路与架构拆解从“能跑”到“稳跑”的底层逻辑2.1 为什么不能直接用官方镜像—— 环境确定性的三重陷阱很多初学者会想“Google 官方不是提供了gcr.io/ml-pipeline/系列镜像吗直接拿来用不省事” 这是个典型的认知误区。官方镜像的设计目标是“通用性”而生产 Pipeline 的核心诉求是“确定性”。这两者在工程实践中存在根本冲突主要体现在三个层面第一层是Python 生态的脆弱性。官方镜像通常基于debian:slim或ubuntu:20.04预装了python3.8和基础pip。但当你在 Pipeline 组件里执行pip install pandas1.5.3时pip会去 PyPI 下载 wheel 包。这个过程受网络、PyPI CDN 节点、甚至pip自身版本的影响。我遇到过最离谱的一次同一份requirements.txt在上午 10 点和下午 3 点提交的 Pipeline因为pip缓存了不同版本的pandaswheel一个带manylinux2014标签一个带manylinux_2_17标签导致最终安装的pandasABI 不兼容DataFrame.to_numpy()方法返回了错误的数据类型。这个问题无法通过pip freeze捕获因为它只发生在运行时。而自定义 Docker 镜像通过docker build这个原子操作把整个依赖树“固化”下来确保每一次构建产出的二进制文件都完全一致。第二层是CUDA 与深度学习框架的耦合陷阱。如果你的模型用到了 TensorFlow 或 PyTorch官方 CPU 镜像里没有 CUDAGPU 镜像里又预装了特定版本的nvidia-cuda-toolkit。但你的训练代码可能依赖tensorflow2.11.0它要求cuda11.2和cudnn8.1.0。而官方tf2-gpu.2-11镜像里装的是cuda11.2.2和cudnn8.1.0.77。表面看版本号一样但补丁号的微小差异就可能导致tf.keras.Model.fit()在 GPU 上出现nan损失值。自定义镜像让你可以精确控制FROM基础镜像的 tag例如nvidia/cuda:11.2.2-cudnn8-devel-ubuntu20.04然后在这个纯净的、已知状态的底座上只安装你真正需要的框架和库彻底切断所有不可控的依赖链。第三层是安全合规的硬性门槛。在金融、医疗等强监管行业生产环境的软件供应链必须可追溯、可审计。官方镜像的Dockerfile是闭源的你无法知道它里面是否包含了某个有 CVE 漏洞的openssl版本或者是否预装了你不被允许使用的第三方库。而你自己的Dockerfile就是一份白纸黑字的“软件物料清单”SBOM。你可以把它放进 Git 仓库和模型代码一起走 Code Review 流程可以用trivy工具扫描出所有已知漏洞甚至可以在 CI 流程中强制要求任何pip install的包都必须经过公司内部的私有 PyPI 仓库代理。这不仅是技术选择更是工程治理的起点。2.2 整体架构蓝框里的闭环——从本地开发到云端执行这篇文章聚焦的“Part 2”其核心架构就是一个围绕Artifact Registry构建的、端到端的镜像生命周期管理闭环。这个闭环不是线性的而是一个可迭代、可验证的反馈环。它的关键节点就是开头提到的“蓝色方框”Dockerfile-Cloud Build-Artifact Registry-Vertex AI Pipeline Component。这个闭环的设计哲学是把“环境”当作一个独立的、可版本化的服务来管理。Dockerfile是它的源代码Cloud Build是它的编译器Artifact Registry是它的二进制仓库而Pipeline Component则是它的消费者。这种分离带来了巨大的工程优势。举个例子当你的模型训练逻辑train.py需要升级xgboost到1.7.5时你不需要修改 Pipeline 的 YAML 定义也不需要重新训练模型你只需要修改requirements.txt将xgboost1.6.2改为xgboost1.7.5提交这个变更触发Cloud Build自动构建新镜像新镜像构建成功后自动更新Pipeline Component的image字段指向新 tag。整个过程Pipeline 的结构、输入输出接口、超参数配置全部保持不变。这正是 MLOps 追求的“模型迭代”与“基础设施迭代”的解耦。我见过太多团队因为一次pip install的小升级导致整个 Pipeline 的 CI/CD 流水线全部中断就是因为没有建立起这样一个清晰的、以镜像为中心的架构。2.3 工具链选型为什么是 Cloud Build Artifact Registry在 GCP 生态里构建和存储 Docker 镜像有多个选项gcloud builds submit、Cloud BuildUI、Artifact Registry、Container Registry已逐步迁移至 AR。我们最终锁定Cloud BuildArtifact Registry的组合是基于三个硬性指标的权衡。首先是构建的可靠性与可观测性。gcloud builds submit命令行虽然简单但它把构建日志、缓存、失败重试等所有细节都封装在一个黑盒里。而Cloud Build提供了一个完整的 Web 控制台你可以看到每一层Dockerfile指令的执行时间、内存占用、网络下载量。更重要的是它支持构建缓存Build Cache这对于pip install这种耗时操作至关重要。我们实测过一个包含 50 个包的requirements.txt首次构建耗时 8 分钟开启缓存后后续构建平均只需 1 分 20 秒。这个缓存是基于指令哈希的只要requirements.txt没变RUN pip install -r requirements.txt这一层就会直接复用跳过所有网络下载和编译。其次是存储的安全性与集成度。Container Registry是旧一代服务而Artifact Registry是其现代化演进。它最大的优势在于“多格式合一”和“细粒度权限”。一个Artifact Registry仓库既可以存 Docker 镜像也可以存 Maven、npm、Python 包。这意味着你的模型训练镜像、特征工程的 Java UDF、以及前端调用的 JS SDK都可以放在同一个逻辑仓库下用一套 IAM 策略统一管理。我们给数据科学家分配artifactregistry.repositories.downloadArtifacts权限给运维工程师分配artifactregistry.repositories.uploadArtifacts权限权限边界清晰审计日志完整。相比之下Container Registry的权限模型相对粗放且不支持跨区域同步。最后是与 Vertex AI 的原生集成。Vertex AI Pipelines在解析ComponentSpec时对镜像 URL 的校验逻辑是深度适配Artifact Registry的。当你在component.yaml里写image: us-central1-docker.pkg.dev/my-project/my-repo/wine-train:v1.0.0时Vertex AI 会直接调用Artifact Registry的 API 去拉取镜像元数据manifest并进行签名验证。这个过程比从公共 Docker Hub 拉取快得多也更安全。我们做过压测在一个拥有 200 个并发 Pipeline 的集群里使用Artifact Registry的平均镜像拉取延迟是 1.2 秒而使用gcr.io是 3.8 秒。对于一个需要快速扩缩容的在线推理服务这 2.6 秒的差距就是 SLA 的生死线。3. 实操详解从零构建一个生产就绪的 Wine Quality 训练镜像3.1 本地环境准备与目录结构一个干净的起点在 Vertex AI Workbench 上动手之前先确保你的工作区是“干净”的。这不是指磁盘空间而是指环境变量和依赖的纯净度。我强烈建议你不要在 Workbench 的默认jupyter用户主目录下直接操作因为那里可能残留着之前 notebook 创建的.local/lib/python3.8/site-packages会干扰pip install的行为。正确的做法是创建一个全新的、隔离的工作目录。# 在 Workbench 的终端里执行 mkdir -p ~/vertex-pipelines-docker/train cd ~/vertex-pipelines-docker/train这个train目录就是你整个镜像构建项目的根。它的结构必须严格遵循以下约定这是为了与Cloud Build的默认行为对齐train/ ├── Dockerfile # 核心定义镜像构建逻辑 ├── requirements.txt # 明确声明所有 Python 依赖 ├── train.py # 你的训练脚本入口点 ├── data/ # 可选存放示例数据用于本地测试 │ └── winequality.csv └── docker_build.sh # 封装构建命令的 shell 脚本提示data/目录在最终的 Docker 镜像里是不应该存在的。它只用于你在本地docker run时进行功能验证。真正的训练数据应该通过 Vertex AI Pipeline 的InputPath参数传入由系统挂载到容器内指定路径。把数据打包进镜像是反模式会导致镜像体积巨大且无法复用。现在我们来创建最关键的Dockerfile。打开一个新文件命名为Dockerfile内容如下# 第一行选择最精简、最可控的基础镜像 FROM python:3.9-slim-bullseye # 设置工作目录这是容器内所有操作的根 WORKDIR /app # 复制 requirements.txt 文件。这一步故意放在 COPY train.py 之前 # 是为了利用 Docker 的构建缓存机制。只要 requirements.txt 不变 # 后续的 pip install 就会直接复用缓存层极大加速构建。 COPY requirements.txt . # 安装 Python 依赖。--no-cache-dir 是关键避免在镜像里留下庞大的 pip 缓存。 # -q 是 quiet 模式减少日志噪音让构建日志更清晰。 RUN pip install --no-cache-dir -q -r requirements.txt # 复制训练脚本。注意这里只复制 .py 文件不复制 .ipynb。 COPY train.py . # 设置容器启动时执行的命令。ENTRYPOINT 定义了容器的“主程序”CMD 是它的默认参数。 # 这样设计的好处是你可以在 Pipeline Component 里通过 args 覆盖 CMD # 例如传入不同的数据路径或超参数而 ENTRYPOINT 保持不变。 ENTRYPOINT [python, train.py]这个Dockerfile看似简单但每一行都有深意。FROM python:3.9-slim-bullseye选择了 Debian Bullseye 的精简版 Python 3.9而不是更常见的ubuntu:20.04。原因在于slim-bullseye镜像只有 120MB而ubuntu:20.04是 70MB但前者预装了更现代的glibc和openssl对scikit-learn的libopenblas依赖兼容性更好。WORKDIR /app是一个行业惯例几乎所有 Python Docker 最佳实践都推荐这个路径因为它简洁、无歧义且与大多数 Python 库的默认行为一致。3.2 requirements.txt 与依赖管理版本锁定的艺术requirements.txt是你镜像的“DNA”。它决定了你的模型在任何地方运行时所依赖的每一个函数、每一个常量、每一个底层 C 库的版本。因此绝对禁止使用或~这样的模糊版本号。下面是你应该写的内容# requirements.txt pandas1.5.3 numpy1.23.5 scikit-learn1.2.2 joblib1.2.0 google-cloud-storage2.9.0让我解释一下每个选择背后的考量pandas1.5.3这是 Pandas 1.x 系列中最后一个支持 Python 3.9 的稳定版本。1.5.4开始要求python3.10而我们的基础镜像是python:3.9版本不匹配会导致pip install失败。numpy1.23.5scikit-learn1.2.2的官方文档明确声明它与numpy1.23.5经过充分测试。更高版本的numpy如1.24.x引入了新的ArrayLike类型可能会导致sklearn.model_selection.train_test_split()返回的对象类型发生变化进而影响下游代码。scikit-learn1.2.2这是sklearn在 2022 年底发布的 LTS长期支持版本Bug 修复充分文档完善社区支持活跃。我们刻意避开了1.3.x系列因为其内部重构了ensemble模块与我们线上一个老模型的预测逻辑有细微差异。joblib1.2.0scikit-learn的模型持久化joblib.dump/load高度依赖joblib。1.2.0是与sklearn1.2.2捆绑测试的版本确保model.pkl文件的序列化格式完全兼容。google-cloud-storage2.9.0这是gcsfs库的上游依赖用于从 GCS 读取训练数据。2.9.0版本与python:3.9-slim-bullseye的glibc版本完美匹配而2.10.0在某些 GCP 区域的节点上会报ImportError: libstdc.so.6: version GLIBCXX_3.4.29 not found。注意你可能会看到一些教程里写了--find-links或--index-url指向私有 PyPI。这在企业环境中是标准做法但为了本教程的普适性我们假设你使用的是公共 PyPI。如果你的公司有私有仓库请务必将pip install命令改为pip install --index-url https://your-private-pypi/simple/ --trusted-host your-private-pypi ...。3.3 train.py一个健壮的训练脚本模板train.py是你镜像的“心脏”。它不仅要完成模型训练更要能优雅地处理各种异常情况并与 Vertex AI 的 Pipeline 环境无缝协作。下面是一个生产环境可用的模板#!/usr/bin/env python3 Wine Quality Training Script for Vertex AI Pipelines. This script is designed to be the ENTRYPOINT of a Docker container. It expects input data path and output model path as command-line arguments. import argparse import logging import os import sys from pathlib import Path import joblib import numpy as np import pandas as pd from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_squared_error, r2_score from sklearn.model_selection import train_test_split # 配置日志这是调试 Pipeline 的生命线 logging.basicConfig( levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s, handlers[ logging.StreamHandler(sys.stdout) # 确保日志输出到 stdoutVertex AI 会捕获它 ] ) logger logging.getLogger(__name__) def parse_args(): 解析命令行参数。Vertex AI Pipeline 会通过 args 传入数据路径。 parser argparse.ArgumentParser() parser.add_argument( --data-path, typestr, requiredTrue, helpGCS path to the input CSV file, e.g., gs://my-bucket/data/winequality.csv ) parser.add_argument( --model-output-path, typestr, requiredTrue, helpGCS path where the trained model will be saved, e.g., gs://my-bucket/models/wine-v1 ) return parser.parse_args() def load_data(data_path: str) - pd.DataFrame: 从 GCS 加载数据。使用 google-cloud-storage 库而非 pandas 内置的 gs:// 协议 因为后者在某些环境下不稳定。 from google.cloud import storage logger.info(fLoading data from {data_path}) # 解析 GCS 路径 if not data_path.startswith(gs://): raise ValueError(fInvalid data path: {data_path}. Must start with gs://) bucket_name, blob_path data_path[5:].split(/, 1) client storage.Client() bucket client.bucket(bucket_name) blob bucket.blob(blob_path) # 下载到内存 content blob.download_as_string() df pd.read_csv(io.StringIO(content.decode(utf-8))) logger.info(fLoaded {len(df)} rows of data) return df def main(): args parse_args() try: # 1. 加载数据 df load_data(args.data_path) # 2. 数据预处理简化版 # 假设 CSV 的最后一列是目标变量 quality X df.drop(quality, axis1) y df[quality] # 3. 划分训练集和测试集 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) # 4. 训练模型 logger.info(Training RandomForestRegressor...) model RandomForestRegressor(n_estimators100, random_state42) model.fit(X_train, y_train) # 5. 评估模型 y_pred model.predict(X_test) mse mean_squared_error(y_test, y_pred) r2 r2_score(y_test, y_pred) logger.info(fModel Evaluation - MSE: {mse:.4f}, R2: {r2:.4f}) # 6. 保存模型 logger.info(fSaving model to {args.model_output_path}) # 使用 joblib 保存 model_bytes joblib.dumps(model) # 上传到 GCS from google.cloud import storage bucket_name, blob_path args.model_output_path[5:].split(/, 1) client storage.Client() bucket client.bucket(bucket_name) blob bucket.blob(blob_path) blob.upload_from_string(model_bytes) logger.info(Model saved successfully.) except Exception as e: logger.error(fTraining failed with exception: {str(e)}) # Vertex AI 会将非零退出码视为任务失败 sys.exit(1) if __name__ __main__: main()这个脚本的关键点在于参数化所有外部输入数据路径、模型输出路径都通过argparse接收而不是硬编码。这使得同一个镜像可以被不同的 Pipeline Component 复用。日志使用logging模块并将StreamHandler指向sys.stdout。Vertex AI 的日志系统会自动捕获stdout你可以在 Cloud Console 的 Pipeline 执行详情页里实时看到这些日志这是排查问题的第一手资料。错误处理try...except块捕获所有异常并调用sys.exit(1)。这是告诉 Vertex AI “这个组件执行失败了”它会停止 Pipeline 的后续步骤并标记该任务为Failed。没有这个一个静默崩溃的脚本会让 Pipeline 卡在“Running”状态直到超时。3.4 构建与推送Cloud Build 的自动化魔法现在所有本地文件都准备好了。下一步是将它们交给Cloud Build让它在云端的、干净的、隔离的环境中执行docker build。为此我们需要一个docker_build.sh脚本#!/bin/bash # docker_build.sh # 请务必替换为你自己的 GCP 项目 ID 和区域 PROJECT_IDyour-gcp-project-id REGIONus-central1 REPO_NAMEvertex-pipelines-repo IMAGE_NAMEwine-train TAGv1.0.0 # 1. 构建镜像。--substitutions 是关键它将 PROJECT_ID 注入到构建上下文中 # 这样你就可以在 cloudbuild.yaml 里引用它。 gcloud builds submit \ --project${PROJECT_ID} \ --region${REGION} \ --configcloudbuild.yaml \ --substitutions_PROJECT_ID${PROJECT_ID},_REGION${REGION},_REPO_NAME${REPO_NAME},_IMAGE_NAME${IMAGE_NAME},_TAG${TAG} \ . echo Build submitted. Check status at https://console.cloud.google.com/cloud-build/builds?project${PROJECT_ID}这个脚本本身不执行构建它只是向Cloud BuildAPI 发送一个请求。真正的构建逻辑定义在cloudbuild.yaml文件里。创建这个文件# cloudbuild.yaml steps: # Step 1: 使用标准的 docker builder 镜像执行 docker build 命令 - name: gcr.io/cloud-builders/docker args: [build, -t, $_REGION-docker.pkg.dev/$_PROJECT_ID/$_REPO_NAME/$_IMAGE_NAME:$_TAG, .] # 指定当前目录为构建上下文 dir: . # Step 2: 将构建好的镜像推送到 Artifact Registry - name: gcr.io/cloud-builders/docker args: [push, $_REGION-docker.pkg.dev/$_PROJECT_ID/$_REPO_NAME/$_IMAGE_NAME:$_TAG] images: # 声明这个构建产物是一个 Docker 镜像这样 Cloud Build 会自动为其生成标签和元数据 - $_REGION-docker.pkg.dev/$_PROJECT_ID/$_REPO_NAME/$_IMAGE_NAME:$_TAG options: # 启用构建缓存这是加速的关键 substitution_option: ALLOW_LOOSEcloudbuild.yaml的设计体现了云原生的最佳实践步骤解耦build和push是两个独立的步骤。如果build成功但push失败你可以单独重试push步骤而不用重新构建整个镜像。变量注入所有敏感信息PROJECT_ID,REGION都通过--substitutions传入而不是硬编码在 YAML 里。这保证了配置的灵活性和安全性。images 声明显式声明images字段能让 Cloud Build 在构建完成后自动在 Artifact Registry 的 UI 里显示这个镜像并生成访问链接。在 Workbench 终端里给脚本添加执行权限并运行chmod x docker_build.sh ./docker_build.sh几分钟后你就能在 Cloud Console 的 Cloud Build 页面看到构建日志。如果一切顺利你会看到类似这样的输出BUILD ... Step 1/5 : FROM python:3.9-slim-bullseye ... Step 2/5 : WORKDIR /app ... Step 3/5 : COPY requirements.txt . ... Step 4/5 : RUN pip install --no-cache-dir -q -r requirements.txt ... Step 5/5 : COPY train.py . ... Successfully built abc123def456 ... PUSH ... Pushed $_REGION-docker.pkg.dev/your-gcp-project-id/vertex-pipelines-repo/wine-train:v1.0.0提示第一次运行时pip install步骤会比较慢因为它要下载所有包。但Cloud Build会自动缓存这一层。下次你只修改train.pypip install这一步会瞬间完成因为requirements.txt没变缓存命中。4. 部署与集成让自定义镜像在 Vertex AI Pipeline 中真正跑起来4.1 在 Artifact Registry 中验证镜像眼见为实构建和推送成功后绝不能直接跳到 Pipeline 集成。必须先手动验证镜像是否真的可用。打开 GCP Console导航到Artifact RegistryRepositories找到你创建的vertex-pipelines-repo。你应该能看到一个名为wine-train的镜像以及它的v1.0.0tag。点击这个 tag进入详情页。这里有两个关键信息你需要确认Image digest这是一个 SHA256 哈希值例如sha256:abc123...。它是镜像内容的唯一指纹。记录下它稍后在 Pipeline 中你可以用这个 digest 来替代 tag实现绝对的、不可变的镜像引用。v1.0.0可能会被覆盖但sha256:abc123...永远不会变。Image size检查镜像大小。一个只装了pandas,numpy,scikit-learn的 Python 镜像理想大小应该在 800MB - 1.2GB 之间。如果它显示 3GB那说明你的Dockerfile里可能不小心COPY了data/目录或者pip install时没有加--no-cache-dir导致 pip 缓存被打包进去了。这时你需要回退检查Dockerfile。为了进行终极验证你可以在本地 Workbench 上用docker pull命令把它拉下来然后docker run一次# 首先确保你有权限拉取 gcloud auth configure-docker # 拉取镜像请替换为你的实际 URL docker pull us-central1-docker.pkg.dev/your-gcp-project-id/vertex-pipelines-repo/wine-train:v1.0.0 # 运行一个临时容器检查它是否能启动 docker run --rm -it us-central1-docker.pkg.dev/your-gcp-project-id/vertex-pipelines-repo/wine-train:v1.0.0 --help如果--help输出了train.py的参数帮助信息恭喜你镜像的ENTRYPOINT和CMD都配置正确了。这证明镜像本身是健康的。4.2 创建 Pipeline ComponentYAML 是你的契约现在镜像已经躺在 Artifact Registry 里了下一步是创建一个ComponentSpec也就是一个 YAML 文件它定义了如何调用这个镜像。创建一个新文件train_component.yaml# train_component.yaml name: Wine Quality Trainer description: Trains a RandomForest model on wine quality dataset. inputs: - name: data_path type: String description: GCS path to the input CSV file. - name: model_output_path type: String description: GCS path where the trained model will be saved. outputs: - name: model_uri type: String description: The GCS URI of the saved model. implementation: container: image: us-central1-docker.pkg.dev/your-gcp-project-id/vertex-pipelines-repo/wine-train:v1.0.0 # args 是传递给 ENTRYPOINT 的参数列表。 # ${{inputs.data_path}} 是 KFP 的语法表示将 Pipeline 的输入 data_path 的值作为第一个参数传入。 args: - --data-path - ${{inputs.data_path}} - --model-output-path - ${{inputs.model_output_path}}这个 YAML 文件就是你和 Vertex AI 之间的“智能合约”。它清晰地声明了输入是什么data_path和model_output_path都是字符串类型。输出是什么model_uri一个字符串表示模型的存储位置。如何执行使用哪个镜像以及如何将 Pipeline 的输入参数映射到容器的命令行参数。注意image字段的 URL 必须和你在 Artifact Registry 里看到的 URL 完全一致。us-central1-docker.pkg.dev中的us-central1是你的 Artifact Registry 仓库所在的区域必须和Cloud Build的区域一致否则Cloud Build无法将镜像推送到那个仓库。4.3 在 Pipeline 中使用组件从定义到执行有了train_component.yaml你就可以在你的 Vertex AI Pipeline 代码中加载并使用它了。下面是一个完整的、可运行的 Pipeline 定义pipeline.pyfrom kfp import dsl from kfp.dsl import component, Input, Output, Dataset, Model from google_cloud_pipeline_components import aiplatform as gcc_aip # 使用 KFP v2 的方式加载组件 component def wine_trainer( data_path: str, model_output_path: str, ) - str: A placeholder function. The real logic is in the Docker image. # 这个函数体在 Pipeline 编译时不会被执行它只是用来生成 ComponentSpec 的类型提示。 # 真正的训练逻辑在 Docker 镜像里。 return model_output_path # 但是为了获得最佳的 IDE 支持和类型安全我们推荐使用上面的 YAML 方式。 # 这里展示如何在 Pipeline 函数中使用它 dsl.pipeline( namewine-quality-pipeline, descriptionA pipeline to train and deploy a wine quality model, ) def wine_quality_pipeline( data_path: str gs://your-bucket/data/winequality.csv, model_display_name: str wine-quality-model, ): # 步骤1加载我们定义的组件 # 这里使用 KFP 的内置函数从 YAML 文件加载 trainer_op gcc_aip.CustomContainerTrainingJobRunOp( display_namewine-trainer, # 这里直接引用我们构建好的镜像 container_urius-central1-docker.pkg.dev/your-gcp-project-id/vertex-pipelines-repo/wine-train:v1.0.0, # 传入参数 args[ --data-path, data_path, --model-output-path, fgs://your-bucket/models/{dsl.PIPELINE_JOB_ID}/model, ], # 指定计算资源 machine_typen1-standard-4, # 指定最大运行时间 timeout3600, ) # 步骤2可选添加一个模型部署步骤使用另一个自定义镜像 #