MXNet 发布流水线实战:基于 Jenkins 的 Scala 构件自动化构建、测试与部署全解析
【免费下载链接】mxnetLightweight, Portable, Flexible Distributed/Mobile Deep Learning with Dynamic, Mutation-aware Dataflow Dep Scheduler; for Python, R, Julia, Scala, Go, Javascript and more项目地址: https://gitcode.com/gh_mirrors/mx/mxnet
导读
本文围绕 MXNet 仓库中的 ci/publish 发布配置,系统讲解 MXNet 官方制品(Artifacts)的持续发布机制:从 Jenkins 三阶段流水线(构建 → 测试 → 部署)的编排逻辑,到 CentOS 7 + Developer Toolset 7 的统一构建环境选型,再到 Scala 包(Maven 构件)从密钥管理、静态编译到发布上线的完整链路。读完本文,你将掌握这套发布体系的目录结构、核心脚本的职责与调用关系,以及各环境变量与配置项的真实含义,可直接据此复现或迁移 MXNet 的制品发布流程。
一、发布体系总览:ci/publish目录做了什么
在 MXNet 仓库根目录下,ci/publish目录承载了所有面向「对外发布制品」的 Jenkins 配置与部署脚本。其内部结构如下:
ci/publish/ ├── Jenkinsfile # Jenkins 声明式/脚本式流水线定义 ├── README.md # 发布配置说明(本文主体依据) ├── scala/ # Scala(Maven)发布所需全部文件 │ ├── build.sh # 构建后端(libmxnet)与 Scala 包 │ ├── buildkey.py # 从密钥服务提取凭据并配置 Maven/GPG │ ├── deploy.sh # 执行 Maven 部署(nightly 仓库) │ ├── fullDeploy.sh # CI 一键完整发布(构建+测试+部署) │ └── test.sh # 对构建产物运行 Scala 测试 ├── python/ # Python 发布(README 标注为 TBD,目录当前为空) └── website/ # 网站发布(内部另有 README 与部署脚本)从源码结构看,发布体系刻意将「脚本层」与「基础设施层」分离:ci/publish/只关注发布动作本身,而运行这些动作的 Docker 镜像与依赖安装脚本统一放在 ci/docker(所有发布用 Dockerfile 均以Dockerfile.publish为前缀,如 Dockerfile.publish.test.centos7)。README 同时指出:Python 构建发布当时仍处于 TBD(待定)状态,当前已完整支持的是 Linux 平台上的 Scala 发布。
二、Jenkins 流水线:三阶段并行发布模型
2.1 流水线骨架
ci/publish/Jenkinsfile 定义了发布作业的核心编排。整条流水线由三个顺序阶段组成,每个阶段内部对不同平台做并行处理:
utils.main_wrapper( core_logic: { stage('Build Packages') { parallel toBuild } stage('Test Packages') { parallel toTest } stage('Deploy Packages') { parallel toDeploy } } , failure_handler: { if (currentBuild.result == "FAILURE") { emailext body: 'Generating the nightly maven has failed. ...', subject: '[NIGHTLY MAVEN FAILED] Build ${BUILD_NUMBER}', ...} })- Build Packages:构建全部依赖,并产出 Scala 包(
scala-package); - Test Packages:将上一阶段产物送入测试环境,专门执行制品级测试;
- Deploy Packages:通过测试的包由对应节点执行部署(
mvn deploy到 Maven 仓库)。
三个阶段在宏观上串行(先构建、再测试、后部署),但在每一阶段内部对 CPU/GPU 平台并行。failure_handler表明该作业是nightly maven发布:一旦失败,会通过邮件发送标题为[NIGHTLY MAVEN FAILED]的告警。
2.2 节点与平台映射
流水线先在一个restricted-utility工具节点上加载通用工具类ci/Jenkinsfile_utils.groovy,随后通过utils.assign_node_labels(...)为各平台分配带restricted-前缀的专用节点(restricted-mxnetlinux-cpu、restricted-mxnetlinux-gpu-p3等)。核心的平台映射如下:
def nodeMap = ['cpu': NODE_LINUX_CPU, 'gpu': NODE_LINUX_GPU_P3] def scalaOSMap = ['cpu': 'linux-x86_64-cpu', 'gpu': 'linux-x86_64-gpu'] def scalaVariantMap = ['cpu': 'cpu', 'gpu': 'cu92']其中scalaVariantMap是关键:cpu变体对应纯 CPU 构建,gpu变体对应 CUDA 9.2(cu92),该值会以$mxnet_variant形式传给 Scala 构建脚本。wrapStep封装器还确保每个阶段运行在独立 workspace 中,并设置120 分钟超时(max_time = 120)。
2.3 触发机制与受支持测试系统
README 明确说明:该发布作业每 24 小时定时触发一次,运行在 Jenkins 的 restricted(受限)实例上,用于生成 nightly 制品。当前发布制品支持的测试系统包括:
| 系统 | 说明 |
|---|---|
| Ubuntu 16.04 | 支持发布产物测试 |
| Ubuntu 18.04 | 支持发布产物测试 |
| Cent OS 7 | 支持发布产物测试(构建基准系统) |
所有包统一在Cent OS 7 + Developer Toolset 7上构建。Developer Toolset 7 为 CentOS 7 提供GCC 7(含 C++17 支持),使构建出的二进制能兼容 2014 年之后发布的所有主流 Linux 发行版——这与 Python Enhancement Proposal 599(PEP 599,即 manylinux2014 规范)的时间口径一致。这一策略的实质是:在较老的 glibc 系统上构建,获得更低的运行时 glibc 依赖,从而让产物具备更广的发行版兼容面。
三、发布用 Docker 与依赖安装
3.1Dockerfile.publish系列镜像
发布相关的 Dockerfile 全部位于 ci/docker,以Dockerfile.publish为前缀。以 Dockerfile.publish.test.centos7 为例,它通过动态ARG BASE_IMAGE(如centos:7、nvidia/cuda:10.2-cudnn7-devel-centos7)构造测试镜像,并安装制品测试所需的最小依赖集:
ARG BASE_IMAGE FROM $BASE_IMAGE WORKDIR /work/deps RUN yum -y check-update || true && \ yum -y install epel-release centos-release-scl && \ yum -y install \ make \ # 运行 ci/publish/scala/test.sh 中的测试 gcc \ # 提供 libgomp.so.1(未来可能随 jar 分发而移除) unzip \ # 运行 org.apache.mxnetexamples.neuralstyle.NeuralStyleSuite rh-maven35 && # 运行 ci/publish/scala/test.sh 所需的 Maven yum clean all ENV PYTHONPATH=./python/ WORKDIR /work/mxnet COPY runtime_functions.sh /work/Dockerfile 中的注释还揭示了各依赖的真实用途:unzip服务于 NeuralStyle 示例测试套件,gcc用于提供 OpenMP 运行时库libgomp.so.1(并注明未来可能改为随 jar 分发)。镜像的基础层配置可通过 docker-compose.yml 中的BASE_IMAGE参数动态指定不同版本组合(如nvidia/cuda:10.1-cudnn8-devel-centos7等 GPU 变体)。
3.2 环境安装脚本
README 指出,创建环境与执行发布的脚本位于ci/docker/install下,并特别提到ubuntu_base.sh用于安装运行已发布包所需的最小依赖。需要说明的是:在当前仓库快照中,ci/docker/install目录实际包含的是 deb_ubuntu_ccache.sh、docker_filepermissions.sh、requirements与 ubuntu_adduser.sh 等文件(ubuntu_base.sh未出现在快照中,可能已随版本演进被调整),因此该目录的职责可概括为:为发布/测试镜像提供最小化、可复现的系统级依赖安装,其中docker_filepermissions.sh还被Dockerfile.publish.test.centos7显式引用以修正工作目录权限。
四、Scala 发布全链路拆解
Scala 发布在 Linux 上已由 Jenkins 完整支持,ci/publish/scala/下的五个脚本构成一条闭环:密钥配置 → 构建 → 测试 → 部署,由fullDeploy.sh一键串起。
4.1fullDeploy.sh:CI 入口,一键完整发布
fullDeploy.sh 是整个流程的编排入口,内容极简但语义清晰:
set -ex ./ci/publish/scala/build.sh ./ci/publish/scala/test.sh ./ci/publish/scala/deploy.sh它按顺序调用构建、测试、部署三个脚本,与 Jenkinsfile 的三个 stage 一一对应。set -ex保证任何一步失败立即中断,杜绝「带病发布」。
4.2build.sh:静态构建后端 + 生成 Scala 包
build.sh 是「构建」阶段的主执行脚本:
set -ex # MAVEN_PUBLISH_OS_TYPE: linux-x86_64-cpu|linux-x86_64-gpu|osx-x86_64-cpu # export MAVEN_PUBLISH_OS_TYPE=linux-x86_64-cpu source tools/staticbuild/build.sh $mxnet_variant cd scala-package mvn -B deploy -DskipTests=true它首先sourcetools/staticbuild/build.sh 完成MXNet 后端的静态构建。该静态构建脚本接收<VARIANT> <BLAS>两个位置参数,并导出大量环境变量:
VARIANT:由$1转为小写(如cpu、cu92、darwin);PLATFORM:由uname探测(linux/darwin);BLAS:默认open(OpenBLAS);- 编译标志统一追加
-fPIC -mno-avx,保证产物具备良好的可移植性(不依赖 AVX 指令集); NUM_PROC自动探测 CPU 核数,并行编译。
构建完成后进入scala-package目录执行mvn -B deploy -DskipTests=true:跳过单元测试、先生成并安装 Scala 包,为下一阶段的制品测试做准备。README 对它的定位是「构建后端以及 Scala 包的主可执行文件」,这里mxnet_variant正是来自 Jenkinsfile 中scalaVariantMap的映射值(cpu/cu92)。
4.3buildkey.py:从密钥服务安全装配 Maven 凭据
buildkey.py 负责在发布节点上安全地装配 Maven 与 GPG 凭据,是整个发布链路中唯一接触机密的环节。其工作流程分四步:
- 从 AWS Secrets Manager 拉取凭据:通过 boto3 客户端读取四个环境变量指定的密钥——
MAVEN_PUBLISH_SECRET_ENDPOINT_URL:Secrets Manager 端点;MAVEN_PUBLISH_SECRET_NAME_CREDENTIALS:存放 Maven 账号/密码/GPG passphrase 的密钥名;MAVEN_PUBLISH_SECRET_NAME_GPG:存放 GPG 私钥(key.asc)的密钥名;DOCKERHUB_SECRET_ENDPOINT_REGION:区域名。 凭据以 JSON 形式返回,包含user、password、masterpass、gpgPassphrase等字段。
- 导入 GPG 密钥:将
key.asc写入~/.m2/key.asc,再用gpg2 --batch --yes --passphrase-fd 0 --import以管道方式传入 passphrase 完成导入,避免口令出现在命令行参数中。 - 生成加密口令:利用
expect脚本自动化执行mvn --encrypt-master-password与mvn --encrypt-password,把明文口令转为 Maven 加密密文。 - 落盘 Maven 配置:
~/.m2/settings-security.xml:写入 master 口令;~/.m2/settings.xml:写入两个 server(apache.snapshots.https与apache.releases.https,即快照仓库与正式发布仓库)的用户名/加密口令,并配置gpgprofile(gpg.executable=gpg2、gpg.passphrase、gpg.skip=true)且设为激活 profile。
这套设计的核心价值在于:密钥不落盘于构建脚本,而是按需从云端密钥服务动态注入,且部署结束后会清理全部临时文件(见下文deploy.sh),最大限度降低凭据泄露面。
4.4deploy.sh:GPG 会话配置 + Maven 部署
deploy.sh 是「部署」阶段的执行脚本:
set -ex # On Jenkins, run python script to configure keys if [[ $BUILD_ID ]]; then python3 ci/publish/scala/buildkey.py fi # 配置 gpg-agent 缓存,避免长发布过程中口令失效 mkdir -p ~/.gnupg echo "default-cache-ttl 14400" > ~/.gnupg/gpg-agent.conf echo "max-cache-ttl 14400" >> ~/.gnupg/gpg-agent.conf echo "allow-loopback-pinentry" >> ~/.gnupg/gpg-agent.conf echo "pinentry-mode loopback" >> ~/.gnupg/gpg-agent.conf export GPG_TTY=$(tty) cd scala-package mvn -B deploy -Pnightly # On Jenkins, clear all password .xml files, exp files, and gpg key files if [[ $BUILD_ID ]]; then rm -rf ~/.m2/*.xml ~/.m2/key.asc ~/.m2/*.exp fi几个值得注意的实现细节:
BUILD_ID是 Jenkins 环境标志:只有在 CI(Jenkins)环境中才执行密钥装配(buildkey.py)与部署后的凭据清理,本地手动运行时跳过,避免误删开发者本机配置;- gpg-agent 缓存调整:将默认缓存 TTL 提升到14400 秒(4 小时),并启用
allow-loopback-pinentry与pinentry-mode loopback,保证长耗时构建期间 GPG 签名不因口令过期而中断; mvn -B deploy -Pnightly:激活nightlyprofile,将制品部署到 nightly(快照)Maven 仓库;- 部署后强制清理:删除
~/.m2下所有*.xml、*.asc、*.exp临时文件,确保口令、密钥不残留。
4.5test.sh:制品级测试(含 GPU 分支)
test.sh 对应「测试」阶段,对已构建的 Scala 包运行集成测试:
set -ex if [ -z "$JAVA_HOME" ]; then source /etc/profile fi cd scala-package/packageTest if [[ $mxnet_variant == cu* ]]; then export SCALA_TEST_ON_GPU=1 make testlocal USE_CUDA=1 CI=1 else make testlocal CI=1 fi脚本通过$mxnet_variant判断运行环境:凡是cu*前缀的变体(如cu92)即视为 GPU 构建,设置SCALA_TEST_ON_GPU=1并以USE_CUDA=1运行make testlocal;纯 CPU 变体则直接make testlocal CI=1。CI=1标志使测试套件以 CI 模式执行(更严格的断言与退出码语义),testlocal目标则保证测试在本地目录运行、不污染外部环境。这与Dockerfile.publish.test.centos7中安装make、rh-maven35、unzip(NeuralStyle 示例套件需要)的依赖选择相互印证。
4.6 环境变量速查表
综合以上脚本,Scala 发布链路涉及的关键环境变量汇总如下:
| 环境变量 | 取值示例 | 作用 |
|---|---|---|
mxnet_variant | cpu/cu92 | 构建变体,决定静态编译目标与测试是否走 GPU 分支(Jenkinsfile 的scalaVariantMap注入) |
MAVEN_PUBLISH_OS_TYPE | linux-x86_64-cpu/linux-x86_64-gpu/osx-x86_64-cpu | 声明发布产物的操作系统与硬件类型(build.sh 头部注释说明) |
MAVEN_PUBLISH_SECRET_ENDPOINT_URL | Secrets Manager 端点 | buildkey.py 拉取密钥的端点 |
MAVEN_PUBLISH_SECRET_NAME_CREDENTIALS | 密钥名 | 存放 Maven 账号/口令/GPG passphrase 的 Secret |
MAVEN_PUBLISH_SECRET_NAME_GPG | 密钥名 | 存放 GPG 私钥的 Secret |
DOCKERHUB_SECRET_ENDPOINT_REGION | AWS 区域 | Secrets Manager 客户端区域 |
BUILD_ID | Jenkins 注入 | 判定是否处于 CI 环境,控制密钥装配与清理逻辑 |
JAVA_HOME | JDK 路径 | test.sh 中若无 JAVA_HOME 则 source/etc/profile恢复环境 |
五、Python 发布与 Website 发布的现状
README 明确注明Python 构建发布仍为 TBD。对应地,ci/publish/python/目录在当前仓库中为空,表明 Python 制品的自动发布尚未接入这套流水线;Python 侧用户仍需依赖 tools/pip 等目录下的构建辅助工具人工处理。
至于网站发布,ci/publish/website/README.md仅给出指向 MXNet Developer Wiki(Building the New Website)的指引,仓库内该目录同时保留了 deploy.sh、beta-deploy.sh、publish_artifacts.sh 三个脚本,与 Jenkins 侧的网站发布作业(Jenkinsfile_website_*系列,见 ci/jenkins)配合使用,负责站点与文档制品的发布。
六、总结:一套可复制的「制品发布」参考范式
MXNet 的发布体系(ci/publish)提供了一套极具参考价值的制品发布范式,其要点可归纳为:
- 流水线分层:构建 → 测试 → 部署三阶段严格串行、阶段内平台并行,配合 120 分钟超时与失败邮件告警,保证发布过程可观测、可止损;
- 兼容性优先:统一在 CentOS 7 + GCC 7(C++17)上构建,对齐 PEP 599 的时间口径,用静态链接(
tools/staticbuild/build.sh,-fPIC -mno-avx)换取跨发行版兼容; - 密钥安全:凭据全部动态注入(AWS Secrets Manager →
buildkey.py→ Maven 加密配置),GPG 密钥与口令文件在部署完成后立即清理; - 环境一致:发布与测试环境全部容器化(
Dockerfile.publish*+docker-compose.yml的BASE_IMAGE参数化),依赖最小化且用途在注释中逐一说明; - 变体驱动:以
mxnet_variant(cpu/cu92)单一变量贯穿构建、测试(GPU 分支)与部署,逻辑收敛、易于扩展新变体。
对于需要构建自己深度学习框架发布链路的团队,这套「受限节点 + 静态构建 + 密钥服务 + 制品测试 + nightly 部署」的组合,是一份可以直接借鉴的工程蓝图。相关全部源码均可从 ci/publish 与 ci/docker 目录继续深入查阅。
【免费下载链接】mxnetLightweight, Portable, Flexible Distributed/Mobile Deep Learning with Dynamic, Mutation-aware Dataflow Dep Scheduler; for Python, R, Julia, Scala, Go, Javascript and more项目地址: https://gitcode.com/gh_mirrors/mx/mxnet
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考