☰
Jupyter docker-stacks 开发环境搭建:本地复刻 CI 的检查、构建与测试流程
2026/9/25 2:51:50 网站建设 项目流程
  • 云原生
  • 开发工具
  • 数据科学

【免费下载链接】docker-stacks

Ready-to-run Docker images containing Jupyter applications

项目地址:https://gitcode.com/gh_mirrors/do/docker-stacks
点击查看免费下载

本文基于仓库中的 开发环境指南 展开,系统讲解如何在本地搭建 docker-stacks 项目的完整开发环境:一次性安装 Python 依赖与 pre-commit 钩子,并在提交 PR 前用make build/<image>构建镜像、用make test/<image>运行“镜像自身 + 所有父镜像”的测试集合。读完本文,你可以独立完成一次贴近 CI 的本地检查,并理解 Makefile 与测试层级体系(IMAGE_PARENT)背后的调用逻辑。

环境与前置条件

在开始之前,本机需要满足以下四个前置条件(来自 dev-setup.md):

依赖说明
Docker构建与测试的核心引擎;pre-commit中的 Hadolint 钩子依赖 Docker 守护进程在运行
Python 3.12+用于运行测试驱动脚本与 tagging 工具链(代码层面也启用了pyupgrade --py312-plus)
GNU Make驱动 Makefile 中的build/%、test/%等模式化目标
Git版本控制与 pre-commit 钩子挂载

一次性安装步骤

克隆仓库并安装 Python 开发依赖

# 克隆仓库后进入目录 cd docker-stacks # 安装 Python 开发依赖 pip install -r requirements-dev.txt # 安装 pre-commit 钩子(git commit 时自动运行 linter) pre-commit install --install-hooks

requirements-dev.txt 中的依赖均为精确锁定版本,其职责可以拆解为三块:

  • 容器与系统交互:docker==7.2.0(Python 版 Docker SDK,测试通过 API 管理容器)、plumbum==2.0.2(tests/run_tests.py 用它拼装并前台执行 pytest 命令);
  • 测试框架:pytest==9.1.1、pytest-rerunfailures==16.7(失败自动重跑,规避容器网络抖动)、pytest-xdist==3.8.0(提供--numprocesses并行执行参数);
  • 工具链:pre-commit==4.6.2、requests、tenacity(重试装饰器,用于 API 轮询等待)、tabulate、python-dateutil。

pre-commit install --install-hooks会把 .pre-commit-config.yaml 中定义的钩子注册到本地 git hooks,此后每次git commit都会对改动文件自动执行 pyupgrade、isort、black、ruff、flake8、shfmt、shellcheck、hadolint、yamllint、markdownlint、nbstripout 等检查。配置中还包含ci: autoupdate_schedule: monthly,说明钩子版本由 pre-commit.ci 每月自动更新。

PR 提交前检查清单(Pre-PR checklist)

原文档给出的三步检查是本地验证的核心流程:

# 1. 运行全部 linter(包括 mypy) pre-commit run --all-files --hook-stage manual # 2. 构建你修改的镜像 make build/<image-name> # 3. 运行该镜像的测试 make test/<image-name>

其中<image-name>替换为你修改的镜像目录名,如docker-stacks-foundation、base-notebook、scipy-notebook。下面结合仓库源码逐条解析其底层行为。

第一步:为什么必须加--hook-stage manual

从 .pre-commit-config.yaml 的源码结构看,mypy和basedpyright两个静态类型钩子均显式声明了stages: [manual]:

# To work around this we run `mypy` only in manual mode # So it won't run as part of `git commit` command, # but it will still be run as part of `pre-commit` workflow and give expected results stages: [manual]

原因是 pre-commit 默认只对 git 暂存区中的变更文件运行钩子,而mypy --follow-imports依赖整个项目的导入图才能得出正确结论,只检查变更文件会漏报。因此git commit时它们被跳过,只有手动执行pre-commit run --all-files --hook-stage manual时才会连同全部文件一起运行——这正是文档中第一步命令的由来。另需注意:Hadolint 钩子(hadolint-docker)通过 Docker 运行镜像执行 lint,执行第一步时Docker 守护进程必须处于运行状态。

第二步:make build/<image-name>在做什么

查看 Makefile,build/%是一个模式匹配目标:

build/%: DOCKER_BUILD_ARGS?= build/%: ROOT_IMAGE?=default_root_image build/%: PYTHON_VERSION?=3.13 build/%: ## build the latest image for a stack using the system's architecture $(CONTAINER_CLI) build $(DOCKER_BUILD_ARGS) \ --tag "$(IMG)" \ "./images/$(notdir $@)" \ --build-arg REGISTRY="$(REGISTRY)" \ --build-arg OWNER="$(OWNER)" \ --build-arg ROOT_IMAGE="$(ROOT_IMAGE)" \ --build-arg PYTHON_VERSION="$(PYTHON_VERSION)"

关键参数(均可在命令行覆盖):

变量默认值含义
REGISTRYquay.io镜像注册表前缀
OWNERjupyter组织名,最终镜像引用为$(REGISTRY)/$(OWNER)/<image>
ROOT_IMAGEdefault_root_image仅对docker-stacks-foundation生效,默认使用 Dockerfile 中 sha 固定的根镜像
PYTHON_VERSION3.13仅对docker-stacks-foundation生效,控制基础层的 Python 版本
DOCKER_BUILD_ARGS空追加任意 build 参数的透传口

Makefile 还有两个值得注意的工程细节:

  • BuildKit 始终启用:export DOCKER_BUILDKIT:=1保证构建使用 BuildKit 执行器;
  • 容器引擎自动探测:CONTAINER_CLI?=$(if $(shell command -v docker),docker,container)会在系统装有docker时优先使用它,否则回退到 Apple 的container框架,并相应调整image ls、image prune的参数差异(见 Makefile)。

第三步:make test/<image-name>与父镜像测试传播

test/%目标并不直接调用 pytest,而是委托给 tests/run_tests.py:

test/%: ## run tests against a stack python3 -m tests.run_tests \ --registry "$(REGISTRY)" \ --owner "$(OWNER)" \ --image "$(notdir $@)"

而 tests/run_tests.py 内部通过 plumbum 执行:

python3 -m pytest --numprocesses auto -m "not info" <test_dirs> \ --registry ... --owner ... --image ...

这里--numprocesses auto即上文pytest-xdist提供的并行能力;-m "not info"会跳过标记为info的信息性测试。真正的关键在于传入的<test_dirs>由 tests/hierarchy/get_test_dirs.py 递归计算:

def get_test_dirs(image: str | None) -> list[Path]: test_dirs = get_test_dirs(IMAGE_PARENT[image]) # 先递归取父镜像的测试目录 current_test_dir = IMAGE_SPECIFIC_TESTS_DIR / image assert current_test_dir.exists(), ... test_dirs.append(current_test_dir) return test_dirs

而IMAGE_PARENT定义在 tests/hierarchy/images_hierarchy.py,即镜像层级关系:

IMAGE_PARENT = { "docker-stacks-foundation": None, "base-notebook": "docker-stacks-foundation", "minimal-notebook": "base-notebook", "scipy-notebook": "minimal-notebook", "r-notebook": "minimal-notebook", "julia-notebook": "minimal-notebook", "tensorflow-notebook": "scipy-notebook", "pytorch-notebook": "scipy-notebook", "datascience-notebook": "scipy-notebook", "pyspark-notebook": "scipy-notebook", "all-spark-notebook": "pyspark-notebook", }

这就解释了原文档中的说明:make test/scipy-notebook会依次把docker-stacks-foundation、base-notebook、minimal-notebook、scipy-notebook四个目录(分别对应tests/by_image/<image>下的测试文件)全部针对scipy-notebook这个镜像运行一遍。也就是说,一个镜像的测试集合 = 自身测试 + 所有祖先镜像的测试,确保基础层的改动不会破坏下游行为。

与之呼应,原文档还提示了一个 CI 与本地的差异:CI 会把同一套测试集合再针对每一个下游镜像各跑一遍,因此在 CI 中修改docker-stacks-foundation会得到跨全部镜像的验证,而本地只需make test/docker-stacks-foundation。

父镜像缓存陷阱

原文档用{note}强调了本地构建的一个隐蔽问题:如果父镜像不在本地,Docker 会直接从注册表拉取它。这意味着修改了docker-stacks-foundation后,若只构建下游镜像而没重新构建父镜像,FROM拉到的仍是注册表上的旧版本,你的改动不会反映到下游。因此下文的“常见场景”示例中,改动基础层时必须按依赖顺序先构建父镜像。

常见场景速查

原文档给出的三组典型命令:

# 场景 1:修改 foundation 镜像(start 脚本、日志等) pre-commit run --all-files --hook-stage manual make build/docker-stacks-foundation make test/docker-stacks-foundation # 场景 2:修改 base-notebook(必须连同父镜像一起构建) pre-commit run --all-files --hook-stage manual make build/docker-stacks-foundation make build/base-notebook make test/base-notebook # 场景 3:构建并测试全部镜像(较慢;仅在修改 # foundation 或 base 镜像、准备开 PR 前使用) make build-all make test-all

从 Makefile 可以看到build-all/test-all展开的ALL_IMAGES是按构建依赖顺序排列的:docker-stacks-foundation→base-notebook→minimal-notebook→scipy-notebook→r-notebook→julia-notebook→tensorflow-notebook→pytorch-notebook→datascience-notebook→pyspark-notebook→all-spark-notebook,这与IMAGE_PARENT映射完全一致。

辅助 Make 目标与延伸阅读

除了三步检查清单,Makefile 中还提供了若干对本地开发很有用的目标(运行make help可查看全部带##注释的目标):

目标用途
make run-shell/<image>/run-sudo-shell/<image>进入容器交互式调试(后者以 root 运行)
make check-outdated/<image>运行 test_outdated.py 生成过期的 mamba/conda 包报告
make cont-clean-all停止并删除所有容器,清理测试残留
make img-rm删除 dangling 镜像与 jupyter 名下镜像,回收磁盘
make hook/<image>运行 tagging 后构建钩子(写 tags/manifest 并应用标签)
make docssphinx-build -W构建 HTML 文档

更多规范可对照仓库文档阅读:Lint 规范(Hadolint 忽略规则与# hadolint ignore=DL3001注释用法)、测试编写指南、贡献流程;钩子行为的 CI 侧对应物见 .github/workflows/pre-commit.yml 与 docker-build-test-upload.yml,前者即本文第一步--hook-stage manual命令的 CI 版本,后者即第二步、第三步的镜像构建与测试流水线。

小结

docker-stacks 的本地开发流程可以浓缩为一句话:装好依赖与钩子后,每次改动都执行「全量 lint → 按依赖序构建镜像 → 运行含父镜像在内的测试集合」三步。理解了IMAGE_PARENT驱动的测试目录递归收集、Makefile 的build/%/test/%参数化设计,以及父镜像“本地无则拉注册表”的缓存行为,你就能在开 PR 前准确预判 CI 的验证范围,避免基础层改动对下游镜像的连锁影响。

  • 云原生
  • 开发工具
  • 数据科学

【免费下载链接】docker-stacks

Ready-to-run Docker images containing Jupyter applications

项目地址:https://gitcode.com/gh_mirrors/do/docker-stacks
点击查看免费下载

相关推荐

上一篇:Inception_v3.tv_in1k进阶技巧:特征图提取与可视化完全指南
下一篇:开源音乐聚合播放器完全指南:5大优势打造你的专属音乐空间

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询