今天,我将告诉您一个使Jupyter Docker Stacks能够构建aarch64
图像的故事
如果您现在要阅读它的实现方式,则可以跳到本文的最后一部分。
jupyter docker堆栈是一组即可运行的Docker images,其中包含jupyter应用程序和交互式计算工具。您可以使用堆栈图像执行以下任何一项(以及更多):
- 使用jupyterlab frontend(默认)启动个人jupyter服务器
- 使用jupyterhub
运行团队的jupyterlab- 在本地Docker容器中启动个人Jupyter笔记本服务器
- 写下自己的项目dockerfile
documentation中的更多信息。
首先,历史上的短潜水
jupyter docker堆栈图像是2015年的first introduced,由3个图像组成:minimal-notebook
,r-notebook
和scipy-notebook
。
从一开始,这些图像不是独立的,minimal-notebook
是r-notebook
和scipy-notebook
图像的父映像。
这就是当时的样子:
从那以后发生了很多事情,这就是现在的样子:
minimal-notebook
图像的oldest version似乎在2015年10月24日被推送到Dockerhub。我们将在这里谈论切换CI系统,添加新图像并更改基本图像。相反,我们将专注于如何制作一组Docker图像支持新的体系结构。
构建过程主要基于makefile。我们只是在一台计算机上以正确的顺序运行docker build
命令。这就是它的本质外观:
OWNER?=jupyter
ALL_IMAGES:= \
minimal-notebook \
scipy-notebook
build/%:
docker build -t $(OWNER)/$(notdir $@):latest ./$(notdir $@) --build-arg OWNER=$(OWNER)
test/%:
python3 -m tests.run_tests --short-image-name "$(notdir $@)" --owner "$(OWNER)"
build-test-all: $(foreach I, $(ALL_IMAGES), build/$(I) test/$(I) )
push/%:
docker push --all-tags $(DARGS) $(OWNER)/$(notdir $@)
Since around 2019,我们的用户正在积极询问是否也可以构建ARM图像。截至2022年,我们已经完全完成了这一点。
Docker图像的独特方面
在我们进行实施之前,我们的图像有一些细节,这些细节对于其他Docker图像并不普遍:
- 这些图像是此存储库的最终产品,因此我们必须 测试这些图像
- 我们有很多图像,它们 确实取决于
- 我们 不构建自己的软件 ,但是这些图像主要是安装第三方软件并配置它
- 我们 通过此软件的版本标记图像 ,我们将图像标记了很多(例如,如果我们在
pyspark-notebook
映像中安装了spark
的特定版本,我们将使用我们安装的版本作为标签)。
这些东西使实现多核心图像变得更加困难。
该过程
首先,我们确保我们的Docker图像在aarch64
平台上构建正常。
这是我们所做的事情的(不是完整的)列表:
-
不要用固定的sha标签从图像继承,这将无法工作。
而不是FROM ubuntu:focal-20210609@sha256:376209...
,它在this Pull Request中变成了FROM ubuntu:focal-20210609
。
这稍少安全,但似乎是一个有效的解决方案。
我们最近还搬到了SimpleFROM ubuntu:focal
,每当我们构建图像时会自动获取图像。 -
摆脱Dockerfiles中的Arch Hardcode。
这就是我们使其适用于micromamba
安装程序的方式:
RUN set -x && \
arch=$(uname -m) && \
if [ "${arch}" = "x86_64" ]; then \
# Should be simpler, see <https://github.com/mamba-org/mamba/issues/1437>
arch="64"; \
fi && \
wget "https://micromamba.snakepit.net/api/micromamba/linux-${arch}/latest"
-
我们安装的一些Python软件包缺少
linux-aarch64
的预制软件包。我们asked to add the missing support。 -
当它花费大量时间将此支持添加到外部软件包时,我们只是为
aarch64
平台安装它。
例如,we still only install koude18 on koude19:
# `r-tidymodels` is not easy to install under arm
RUN set -x && \
arch=$(uname -m) && \
if [ "${arch}" == "x86_64" ]; then \
mamba install --quiet --yes \
'r-tidymodels' && \
mamba clean --all -f -y && \
fix-permissions "${CONDA_DIR}" && \
fix-permissions "/home/${NB_USER}"; \
fi;
- 如果目前无法提供图像而没有实质性更改,我们将不提供
aarch64
图像。目前,Conda-Forge上有no koude0 tensorflow Linux package,所以我们不提供此图像。
第一次尝试
我将简要描述我们最初如何实现多架图像以及它最终无法正常工作的原因。
最初,我们决定使用koude22命令将图像构建为this is the recommended way to build multi-platform images。
为了完成这项工作,我们在GitHub Runner中安装了QEMU并启用了buildx
构建。
This is how it looked like(简化版本):
OWNER?=jupyter
MULTI_IMAGES:= \
base-notebook \
minimal-notebook
AMD64_ONLY_IMAGES:= \
r-notebook \
scipy-notebook
build-multi/%:
docker buildx build $(DARGS) -t $(OWNER)/$(notdir $@):latest ./$(notdir $@) --build-arg OWNER=$(OWNER)
docker buildx build $(DARGS) -t build-multi-tmp-cache/$(notdir $@):latest ./$(notdir $@) --build-arg OWNER=$(OWNER) --platform "linux/amd64,linux/arm64"
build-all-multi: $(foreach I, $(MULTI_IMAGES), build-multi/$(I)) $(foreach I, $(AMD64_ONLY_IMAGES), build/$(I))
push-multi/%:
docker buildx build $(DARGS) $($(subst -,_,$(notdir $@))_EXTRA_TAG_ARGS) -t $(OWNER)/$(notdir $@):latest ./$(notdir $@) --build-arg OWNER=$(OWNER) --platform "linux/amd64,linux/arm64"
push-all-multi: $(foreach I, $(MULTI_IMAGES), push-multi/$(I)) $(foreach I, $(AMD64_ONLY_IMAGES), push/$(I))
docker buildx build有一些局限性(使用docker/buildx 0.5.1,这是我们当时使用的):
- 可以同时
- 可以t
--load
多个平台
命令--load
是什么意思?
这意味着可以由docker
CLI引用构建的图像,例如,使用docker tag
或docker push
命令。
由于限制而导致的解决方法:
- 我们始终使用名为
OWNER/<stack>-notebook
的当前系统构建专用图像,因此我们始终可以参考该图像,无论测试期间什么等等。 - 此外,我们始终在构建构建过程中构建多平台图像,它将无法与
docker tag
和docker push
等一起访问,但这将有助于我们测试不同平台上的构建,并为以后提供缓存的层。 - 我们让Push-Multi是指使用
--push
重建多图像。我们现在可以依靠Build-Multi的缓存层,即使我们从未标记过多图像。
有几件事出现了问题,它们是有一种预期的:
- 由于使用QEMU,我们希望构建速度较慢(但是当我们开始添加
aarch64
支持更多图像时,我们的速度大约为10倍,而我们的构建时间约为3个小时)。 。
- 我们在标记
aarch64
图像,就好像它们具有与x86_64
相同的标签 - 我们没有测试
aarch64
图像 - 我们没有为这些图像创建构建清单
最糟糕的是,没有人知道如何通过这种方法克服这些问题。有些事情不起作用,根本不出乎意料:
- 当child process hangs when forking due to glib allocation时有一个QEMU错误。作为解决方法,将
G_SLICE=always-malloc
设置在QEMU的客人中。这意味着我们必须在Dockerfiles中添加一些解决方法。您还可以看一下issue, where it first appeared以及如何在upstream project中减轻它。 - 在QEMU下,某些软件无法正常工作。我们无法在模拟环境中运行
Julia
。
所以,我们决定使用另一种方法。
我们如何做
所以我们决定:
- 使用自托管
aarch64
github跑步者,根本不使用Qemu - 使用简单的
docker build
命令 - 使用许多GitHub作业来实现更好的并发性
- 要传递不同作业之间的图像,我们使用
docker save
和docker load
命令,然后使用精心制作的github操作actions/upload-artifact
和actions/download-artifact
上传/上传/下载相应的文件 - 仅在
aarch64
平台上使用aarch64
跑步者。例如,我们构建图像,对其进行测试,计算标签并在我们的自主跑步者上创建构建清单,但是实际的标签和推动构建清单是在GitHub提供的跑步者上完成的。 - 在很大程度上依靠GitHub可重复使用的工作流和本地动作。这样,几乎没有代码重复。
- 一个特定的github工作流程尽可能少。
我花了几个月的时间,137个工作以及很多小时阅读有关Docker和Github工作流程,跑步者和动作的时间。如果您想查看实现,这是the PR。
在我们深入研究细节之前,我想指出,关于自托管的github跑步者,几乎没有什么可以知道的。
- 默认情况下,工作流是在主机计算机上运行的(不是在孤立的环境中),因此您需要确保,您不保留不同运行之间的环境,,尤其是当工作流失败。我建议在工作流程开始时清理您的环境 - 例如,每当我们需要使用
docker
时,我们都会进行docker system prune --all --force
,并且以前的运行中的缓存是不可取的。 - 每个自主跑步者安装仅允许同时运行。如果您愿意,您可以在一台计算机上安装多个跑步者,但是您必须处理构建缓存和手工艺品。
- 我强烈建议要求所有外部合作者批准。由于PRS也在GitHub自托管环境中运行,因此未经批准就将其运行会损害您的项目的安全性。
我们有几个github工作流,使我们构建所有图像:
- koude51是一个根工作流程,它运行一些作业(调用可重复使用的工作流)并管理其订单。可以将此文件视为配置,也可以将基于GitHub的构建系统视为。
- koude52 - 这是可重复使用的工作流程,用于下载父映像,构建新图像,测试和上传。这是唯一使用自主跑步者的地方。我们还计算标签并在此工作流中编写构建清单文件。 注意:此工作流仅通过一个平台图像进行操纵。
-
koude53用于下载构建的图像(来自GitHub伪像),标记这些图像(使用已经计算的标签),然后按这些图像(使用正确的标签)推动这些图像。
注意:此工作流仅通过一个平台图像来操纵。我们还为所有标签添加
arch
前缀。例如,我们推动koude55标签(尽管无法保证koude56)。 -
koude57实际上是一个地方,我们尝试创建多平台图像并合并标签。为此,我们主要使用
docker manifest
子命令。 - koude59下载构建清单并将其推向GitHub Wiki page。
现在,您知道我们的构建过程的高级实现以及如何构建多架Docker images。