容器化模型审批流程的核心目标,是在模型从训练、打包、推送镜像到部署生产的链条上,建立可追溯、可拦截、可审计的控制点。由于容器镜像只是模型服务的载体,审批必须同时覆盖镜像、模型文件、配置、数据血缘和评估结果,否则很容易出现镜像通过了安全扫描但模型本身未经验证的情况。

一、审批流程需要覆盖的对象和边界
设计容器化模型审批流程时,第一步是明确审批对象。很多团队只对镜像做安全扫描,但模型服务与普通应用服务不同,它的风险不仅来自镜像层,还来自模型文件本身。模型权重可能被篡改,训练数据集可能包含敏感信息,评估指标可能未达到上线标准。因此审批对象至少需要包含模型注册表中的模型版本、镜像的Digest和标签、服务配置文件、训练数据集版本以及评估报告。
审批流程的边界需要与CI/CD流水线对齐。模型训练完成后,触发镜像构建和推送,审批流程从镜像推送到模型注册表之后开始,到Kubernetes准入控制放行结束。中间可以插入自动化校验节点和人工审批节点。自动化节点负责确定性检查,例如镜像签名验证、模型文件哈希比对、漏洞扫描结果是否超过阈值;人工节点则聚焦业务影响、模型风险等级和合规要求。这样做的好处是,不会因为过度自动化而忽略业务判断,也不会因为人工审批太慢而阻塞常规迭代。
另一个重要边界是模型版本与镜像标签的一致性。模型文件和镜像往往分别存储在不同系统,模型注册表保存权重和结构,镜像仓库保存运行时载体。审批时必须校验镜像标签中编码的模型版本与模型注册表中的版本是否一致,否则可能出现镜像通过了审批但实际加载了旧模型的情况。因此审批单需要同时记录模型版本、镜像Digest和审批状态,后续部署时以此为准。
二、用状态机管理审批流转
审批流程可以抽象为有限状态机,避免审批链条混乱和状态丢失。建议将状态划分为待提交、模型校验中、安全扫描中、人工审批中、已批准、已拒绝、发布中、已发布。每个状态对应一组允许的操作和自动触发的任务。例如模型校验中状态会触发模型文件哈希比对和模型注册表版本检查,安全扫描中状态会触发镜像漏洞扫描和依赖审计。
下面是一个用Python定义的审批状态枚举,便于在审批服务中统一使用。
from enum import Enum
class ApprovalState(Enum):
SUBMITTED = "submitted"
MODEL_VALIDATING = "model_validating"
SECURITY_SCANNING = "security_scanning"
MANUAL_REVIEW = "manual_review"
APPROVED = "approved"
REJECTED = "rejected"
PUBLISHING = "publishing"
PUBLISHED = "published"
状态迁移需要明确触发条件。当模型镜像推送到仓库后,审批服务创建审批单,状态为待提交。流水线自动发起模型校验,校验通过后状态变为安全扫描中。扫描完成且无高危漏洞后,状态进入人工审批中。审批人根据模型卡片和评估报告做出批准或拒绝。批准后状态变为发布中,部署完成后变为已发布。任何自动化节点失败,状态都可以直接迁移到已拒绝,并通知相关开发人员修复。
状态机必须持久化,不能只存在于内存或聊天记录中。审批服务需要将每一次状态变更写入数据库,并记录变更时间、操作人、触发原因。这样既能满足审计要求,也能在出现问题时快速定位是哪一个环节放行了不合规模型。同时,审批单ID需要和镜像标签、Git提交号关联,形成完整链路。
三、自动化卡点的实现:镜像签名与准入控制
镜像签名是容器化模型审批流程中的关键自动化卡点。审批通过后,审批服务对镜像进行签名,或者为镜像附加审批通过标签。生产集群只允许部署带有有效签名或审批标签的镜像,这样可以从源头上阻止未审批的模型进入生产环境。常用的签名工具包括cosign,它可以将签名写入镜像仓库,并在部署时进行验证。
下面的bash示例展示了在审批通过后推送镜像、使用cosign签名并附加审批标签的过程。
# 登录镜像仓库 docker login registry.ippipp.com -u $DOCKER_USER -p $DOCKER_PASS # 推送原始镜像 docker push registry.ippipp.com/model-service:v1.2.0 # 使用cosign对镜像签名 cosign sign --key cosign.key registry.ippipp.com/model-service:v1.2.0 # 附加审批通过标签并推送 docker tag registry.ippipp.com/model-service:v1.2.0 registry.ippipp.com/model-service:v1.2.0-approved docker push registry.ippipp.com/model-service:v1.2.0-approved
生产集群侧则需要通过Kubernetes准入控制来验证镜像是否满足审批要求。可以使用OPA/Gatekeeper或自定义ValidatingWebhookConfiguration,在Pod创建前检查镜像名称是否包含审批通过标签,以及镜像是否来自受信任的仓库。下面的Rego策略示例会拒绝所有未携带approved标签的模型镜像。
package k8s.modelapproval
import future.keywords.if
deny[msg] {
input.review.kind.kind == "Pod"
container := input.review.object.spec.containers[_]
not startswith(container.image, "registry.ippipp.com/model-service:")
msg := sprintf("镜像 %v 不在允许的仓库范围内", [container.image])
}
deny[msg] {
input.review.kind.kind == "Pod"
container := input.review.object.spec.containers[_]
not endswith(container.image, "-approved")
msg := sprintf("镜像 %v 未携带审批通过标签", [container.image])
}
这样审批服务、镜像仓库和集群准入三者就形成了闭环。审批通过后签名或打标签,集群只接受符合条件的镜像。即使有人绕过流水线直接推送镜像,也会因为没有审批标签而被准入控制器拦截。需要注意的是,签名密钥和打标签权限必须由审批服务独占,开发人员不应具备直接打approved标签的权限,否则自动化卡点会失效。
四、审计追踪与合规落地建议
审计追踪要求审批记录具备完整性。每次审批通过后,应当记录审批单号、模型名称、模型版本、镜像摘要、数据集版本、评估指标、审批人和审批时间。这些信息最好以结构化JSON格式写入不可变存储,例如对象存储或审计日志系统,方便后续合规检查。下面是一个审批记录的结构示例。
{
"approval_id": "apr-20250314-001",
"model_name": "credit-risk-model",
"model_version": "v3.2.0",
"image_digest": "sha256:9f8e2c1d7b6a5e4f3c2d1e0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0",
"dataset_version": "dataset-20250310",
"metrics": {
"auc": 0.87,
"f1_score": 0.82
},
"approver": "risk-committee",
"approved_at": "2025-03-14T10:30:00Z",
"status": "approved"
}
落地时建议审批单自动生成,模型卡片、数据集版本和评估报告作为附件随审批单流转,审批人可以在系统内直接查看,不需要线下邮件来回传递。权限分离同样重要,模型开发人员不应拥有最终批准权限,审批权限应授予独立的模型风险管理角色或业务负责人。对于紧急发布场景,可以设置临时绿色通道,但必须事后补齐审批记录和复盘材料。
另一个常见误区是只在聊天工具里口头审批,或者只记录镜像标签而不记录模型版本。这样的审批在合规审计时无法还原完整证据链。正确的做法是将审批动作集成到审批系统中,任何批准或拒绝都必须通过系统完成,并自动生成结构化记录。这样即使人员变动,也能通过审批单追溯到当时的模型状态和决策依据。