本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 [署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh) 本文作者: 苏洋 创建时间: 2022年07月31日 统计字数: 12201字 阅读时间: 25分钟阅读 本文链接: https://soulteary.com/2022/07/31/into-vector-computing-making-openblas-docker-prebuilt-product-images.html ----- # 走进向量计算:制作 OpenBLAS Docker 预构建产物镜像 本篇文章,将聊聊如何在容器中完成支持多 CPU 架构(x86、ARM)、多种 CPU 类型(Intel、AMD)的 OpenBLAS 的 Docker 镜像,来让包括 Milvus 在内的,使用 OpenBLAS 的软件的 Docker 镜像构建可以“又稳又快”。 我会试着在接下来的内容中,持续分享如何让一个拥有着一万多颗“星星”的大型数据库项目的容器构建过程不断提速。 ## 写在前面 有阅读过以往内容的同学,应该会记得,我在之前的介绍“[Milvus 源码编译安装](https://soulteary.com/2022/07/15/building-a-vector-database-from-scratch-source-code-compilation-and-installation-of-milvus-2.html)”的文章中,有提到过“要聊聊如何构建一个可靠、可信赖的 Milvus 容器镜像”。 之前提到过,因为 Milvus 使用的核心搜索引擎 [knowhere](https://github.com/milvus-io/knowhere) 有使用到 OpenBLAS。以及,相信有过本地构建经验、或者参考前面文章“[走进向量计算:从源码编译 OpenBLAS](https://soulteary.com/2022/07/08/into-vector-computing-compiling-openblas-from-source.html#ubuntu-2204-%E7%8E%AF%E5%A2%83%E7%9A%84-openblas)”进行过构建的同学,对于 OpenBLAS 项目的构建时间漫长,应该是**深有体会**的。并且,在不同 CPU 架构(x86、ARM),不同类型 CPU(Intel、AMD)的环境下,OpenBLAS 编译产物也是不同的。 所以,如果我们想搞定支持多种 CPU 架构的 Milvus 容器镜像,自然要先解决多种架构和类型硬件的 OpenBLAS 在容器内的构建。 如果我们使用 GitHub Action (免费版),你会发现 CI [跑一天](https://github.com/soulteary/docker-milvus/actions)都构建不出来 OpenBLAS 这个基础依赖,即使我们采用 8 Cores 这类常见规格的云服务器,也需要“哼哧哼哧”的[跑个一个钟头](https://github.com/soulteary/docker-milvus/actions/runs/2426168622),这里如果我们使用 `docker buildx` 来模拟不同硬件的话,很有可能跑 4~5 个钟头不见得有结果(可以参考 CI 中大量跑了一天以上[被自动取消的任务](https://github.com/soulteary/docker-milvus/actions))。 而如果我们使用预构建的方式,临时采用“按需付费”的方式,找一台配置较高的机器,或者利用本地高配置的机器,花十几分钟到半个小时,提前做好预构建产物的编译。那么,之后的构建时间,通常就能够缩短到只需要“几秒钟”了,因为文件复制的计算量非常少。 所以,想要减少开发和构建 Milvus 所需要的时间,在确定的容器环境中,制作预构建产物来减少重复构建花费的大量时间,就变得十分必要的啦。 ## 准备工作 既然是容器环境的产物预构建,那么,我们自然需要先完成 Docker 运行环境的安装,如果你的构建环境中已经安装过了 Docker,那么可以跳过这个小节,阅读后面的内容。 如果你是桌面运行环境,可以访问官网[下载安装文件](https://www.docker.com/get-started/),如果你使用的是服务端环境,可以参考这篇文章中的“[更简单的 Docker 安装](https://soulteary.com/2022/06/21/building-a-cost-effective-linux-learning-environment-on-a-laptop-the-basics.html#%E6%9B%B4%E7%AE%80%E5%8D%95%E7%9A%84-docker-%E5%AE%89%E8%A3%85)”,来完成 Docker 环境的准备。 当然,如果你没有运行 Linux 系统的设备,使用 macOS 或者在 Windows 中使用虚拟机,也是一样的。 当然,不同硬件架构能够使用的编译参数是有不同的,所以这里,我们需要根据未来计划运行 Milvus 或其他软件所需要的硬件环境,准备对应的硬件,来完成基础依赖的编译。**即使Docker Buildx 只能模拟 CPU 架构来进行 Multi-ARCH 构建,但是却无法模拟 CPU 类型,无法满足不同指令集的硬件产物构建。** 前文中,我们有提到需要构建不止一种产物镜像,简单来说: - 我们需要构建出最新和次新 Ubuntu LTS 环境下的软件,包含目前 Milvus 长期使用的 0.3.9 版本,以及未来考虑升级的最新的稳定的版本:0.3.20 。 - 以及考虑到目前 Milvus 官方镜像还是基于 Ubuntu 18.04,所以构建这个环境下的产物,对于能够平滑替换,进行验证也是必要的。 - 考虑到目前不仅是云环境,还是本地环境, ARM 芯片和 AMD Zen 架构的 CPU 都越来越多,所以,我们也需要考虑这两个环境下的产物构建。 当然,因为 OpenBLAS 不同版本,在ARM 芯片、AMD ZEN 架构 CPU 下存在一些兼容问题,即使我们能够得到构建产物,产物其实也并不能够通过软件的单元测试。所以,我们在构建的过程中,会忽略掉构建结果不能 100% 通过测试的“组合”。 好了,我们先来聊聊最常见的 Intel CPU 的产物构建吧。 ## 基于 Intel x86 架构 CPU 的容器预构建 因为不同类型、架构的 CPU,能够构建不同 OpenBLAS 的可靠产物是不同的,所以我们先来明确要构建哪些版本。在 Intel x86 芯片环境下,我们需要满足下面的需求: - 最新 Ubuntu LTS 版本 22.04 环境下的两个版本的 OpenBLAS:0.3.9 和 0.3.20,满足未来 Milvus 升级到最新的 Ubuntu LTS 时使用。 - 上一个 Ubuntu 稳定 LTS 版本 20.04 环境下的 OpenBLAS:0.3.9 和 0.3.20,满足未来 Milvus 能够升级到次新 LTS 版本时使用。 - 目前 Milvus 官方镜像使用的 Ubuntu LTS 版本 18.04 环境下的 OpenBLAS:0.3.9 和 0.3.20,满足当前版本的 Milvus ,能够平滑切换 OpenBLAS 依赖安装,以及验证最新版本的 OpenBLAS 使用。 简单来说,就是我们需要分别构建 Ubuntu 18.04~22.04 环境下,0.3.9 和 0.3.20 两个版本的 OpenBLAS 镜像,来满足当前状况的 Milvus、过渡期的 Milvus,以及适合长远的 Milvus 使用的 OpenBLAS 预构建产物。 ### 设计 Intel CPU 使用的通用 Dockerfile 镜像文件 即使我们需要根据排列组合做镜像,但可维护性依旧是我们最需要考虑的地方,因此,我们最好是能够使用一个 Dockerfile 文件来管理同 CPU 架构的镜像(按照 CPU 架构拆分镜像): ```bash # 允许使用参数来动态改变我们所使用的基础镜像 ARG LTS=22.04 FROM ubuntu:${LTS} AS Base # (示意) 安装必要的依赖 RUN apt-get update && \ apt-get install ... && \ apt-get remove --purge -y # 设置工作目录 WORKDIR /src # 允许使用参数来指定 OpenBLAS 的版本,从官方发布页面获取软件源码 ARG OPENBLAS_VERSION=0.3.9 ENV OPENBLAS_VERSION=${OPENBLAS_VERSION} RUN wget "https://github.com/xianyi/OpenBLAS/archive/v${OPENBLAS_VERSION}.tar.gz" && \ tar zxvf v${OPENBLAS_VERSION}.tar.gz && rm v${OPENBLAS_VERSION}.tar.gz # 改变工作目录 WORKDIR /src/OpenBLAS-${OPENBLAS_VERSION} # (示意) 使用适合 Intel 芯片的参数,进行编译和安装 RUN make && make install # 将构建后的产物保存到一个干净的空镜像里,为后续使用做准备 FROM scratch ARG OPENBLAS_VERSION=0.3.9 ENV OPENBLAS_VERSION=${OPENBLAS_VERSION} COPY --from=Base /usr/lib/libopenblas-r${OPENBLAS_VERSION}.so /usr/lib/ ``` 上面是隐藏了一些细节(依赖安装、编译命令)的基础镜像设计,首先根据用户传递的构建参数,来确定要使用的基础 Linux 环境,和要构建的 OpenBLAS 产物版本。然后再将构建完毕的内容,复制到一个崭新的空白容器里,来简化容器复杂度,以及方便后续 Milvus 或其他软件的构建过程使用。 或许有小伙伴好奇,为什么一定要使用多阶段构建呢。如果我们没有进行多阶段构建,剥离环境和构建产物,那么我们得到的预构建镜像,大概会是下面这样的“壮观”体积。 ```bash soulteary/milvus-openblas 0.3.20-intel-x86-ubuntu-22.04 3.95GB soulteary/milvus-openblas 0.3.20-intel-x86-ubuntu-20.04 9.03GB soulteary/milvus-openblas 0.3.20-intel-x86-ubuntu-18.04 7.54GB soulteary/milvus-openblas 0.3.9-intel-x86-ubuntu-22.04 3.35GB soulteary/milvus-openblas 0.3.9-intel-x86-ubuntu-20.04 6.68GB soulteary/milvus-openblas 0.3.9-intel-x86-ubuntu-18.04 5.55GB ``` 当采用了多阶段构建之后,即使在不进行额外压缩的前提下,产物镜像尺寸也能够得到非常明显的变化: ```bash soulteary/milvus-openblas 0.3.20-intel-x86-ubuntu-22.04 143MB soulteary/milvus-openblas 0.3.20-intel-x86-ubuntu-20.04 318MB soulteary/milvus-openblas 0.3.20-intel-x86-ubuntu-18.04 266MB soulteary/milvus-openblas 0.3.9-intel-x86-ubuntu-22.04 122MB soulteary/milvus-openblas 0.3.9-intel-x86-ubuntu-20.04 241MB soulteary/milvus-openblas 0.3.9-intel-x86-ubuntu-18.04 201MB ``` 完整文件,我上传到了 GitHub,方便有需要的同学自取:[soulteary/docker-openblas/blob/main/intel-x86/Dockerfile ](https://github.com/soulteary/docker-openblas/blob/main/intel-x86/Dockerfile) 我们将上面的内容保存为 `Dockerfile`,就能够正式进行 Intel x86 CPU 环境下的镜像构建了。 ### Ubuntu 和 Intel 环境下的 OpenBLAS 构建 先来处理 Ubuntu 22.04 系统环境下,0.3.9 版本 OpenBLAS 的构建: ```bash docker build --build-arg=LTS=22.04 --build-arg=OPENBLAS_VERSION=0.3.9 -t soulteary/milvus-openblas:0.3.9-intel-22.04 . ``` 执行命令,经过漫长的等待之后,将能够看到类似下面的输出,包含了一大堆“测试通过”的日志信息和容器“构建完毕”的提示: ```bash ... OPENBLAS_NUM_THREADS=2 ./xscblat1 Real CBLAS Test Program Results Test of subprogram number 1 CBLAS_SDOT ----- PASS ----- Test of subprogram number 2 CBLAS_SAXPY ----- PASS ----- ... cblas_zsyr2k PASSED THE TESTS OF ERROR-EXITS cblas_zsyr2k PASSED THE COLUMN-MAJOR COMPUTATIONAL TESTS ( 1764 CALLS) cblas_zsyr2k PASSED THE ROW-MAJOR COMPUTATIONAL TESTS ( 1764 CALLS) END OF TESTS ... make[1]: Leaving directory '/src/OpenBLAS-0.3.9/exports' OpenBLAS build complete. (BLAS CBLAS LAPACK) OS ... Linux Architecture ... x86_64 BINARY ... 64bit C compiler ... GCC (command line : gcc) Fortran compiler ... GFORTRAN (command line : gfortran) Library Name ... libopenblas-r0.3.9.a (Single threaded) To install the library, you can run "make PREFIX=/path/to/your/installation install". Removing intermediate container 41f18b3da43e ---> c5545163375d Step 10/10 : RUN make PREFIX=/usr NO_STATIC=1 install ---> Running in 80583523b475 make -j 8 -f Makefile.install install make[1]: Entering directory '/src/OpenBLAS-0.3.9' Generating openblas_config.h in /usr/include Generating f77blas.h in /usr/include Generating cblas.h in /usr/include Copying LAPACKE header files to /usr/include Copying the shared library to /usr/lib Generating openblas.pc in /usr/lib/pkgconfig Generating OpenBLASConfig.cmake in /usr/lib/cmake/openblas Generating OpenBLASConfigVersion.cmake in /usr/lib/cmake/openblas Install OK! make[1]: Leaving directory '/src/OpenBLAS-0.3.9' ... ... Removing intermediate container 80583523b475 ---> 980e4e15139b Successfully built 980e4e15139b Successfully tagged soulteary/milvus-openblas:0.3.9-intel-20.04 ``` 如果你的构建环境获取 GitHub Release 中的源码包存在网络问题,可以考虑在构建参数中使用 `--build-arg=https_proxy=YOUR_PROXY_ADDR` ,强制内容获取走你的指定网络来解决问题。 同样的,我们可以执行下面的命令,来搞定 Ubuntu 22.04 环境下,OpenBLAS 0.3.20 版本的镜像构建: ```bash docker build --build-arg=LTS=22.04 --build-arg=OPENBLAS_VERSION=0.3.20 -t soulteary/milvus-openblas:0.3.20-intel-22.04 . ``` 同理,我们可以根据切换参数的内容,来完成 Ubuntu 20.04 和 Ubuntu 18.04 系统版本下 OpenBLAS 0.3.9 和 0.3.20 两个版本的镜像构建: ```bash docker build \ --build-arg=LTS=20.04 \ --build-arg=OPENBLAS_VERSION=0.3.9 \ -t soulteary/milvus-openblas:0.3.9-intel-x86-ubuntu-20.04 . docker build \ --build-arg=LTS=20.04 \ --build-arg=OPENBLAS_VERSION=0.3.20 \ -t soulteary/milvus-openblas:0.3.20-intel-x86-ubuntu-20.04 . ... ``` 为了节约篇幅,完整内容,我已经上传到了 GitHub,有需要的同学可以自取:[soulteary/docker-openblas/blob/main/intel/build.sh](https://github.com/soulteary/docker-openblas/blob/main/intel/build.sh)。 ## 基于 AMD Zen 架构 CPU 的容器预构建 和 Intel x86 小节中的最大不同是,在 AMD Zen 架构的 CPU 的容器构建中,由于比较老的版本的 OpenBLAS 在该架构上的兼容性存在问题,即使能构建出来产物,看着一堆堆的测试报错、警告,以及测试安装时的错误日志,也没有人能放心的使用它们,所以我们只构建 OpenBLAS 0.3.20 版本。 此外,在构建 Intel x86 架构 CPU 的时候,我们的构建参数使用的是 `TARGET=CORE2`,在构建 AMD Zen 架构镜像的时候,需要替换为 `TARGET=ZEN`。完整的镜像文件,我上传到了 GitHub:[soulteary/docker-openblas/blob/main/amd-zen/Dockerfile](https://github.com/soulteary/docker-openblas/blob/main/amd-zen/Dockerfile),有需要可以自取。 构建命令,和构建 Intel x86 时类似,也是通过改变参数,来调整构建环境,和使用软件的版本: ```bash docker build \ --build-arg=LTS=22.04 \ --build-arg=OPENBLAS_VERSION=0.3.20 \ -t soulteary/milvus-openblas:0.3.20-intel-x86-ubuntu-22.04 . docker build \ --build-arg=LTS=20.04 \ --build-arg=OPENBLAS_VERSION=0.3.20 \ -t soulteary/milvus-openblas:0.3.20-intel-x86-ubuntu-20.04 . docker build \ --build-arg=LTS=18.04 \ --build-arg=OPENBLAS_VERSION=0.3.20 \ -t soulteary/milvus-openblas:0.3.20-intel-x86-ubuntu-18.04 . ``` 命令执行完毕,将会得到类似下面的结果: ```bash ... make[1]: Leaving directory '/src/OpenBLAS-0.3.20/exports' OpenBLAS build complete. (BLAS CBLAS LAPACK) OS ... Linux Architecture ... x86_64 BINARY ... 64bit C compiler ... GCC (cmd & version : gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0) Fortran compiler ... GFORTRAN (cmd & version : GNU Fortran (Ubuntu 11.2.0-19ubuntu1) 11.2.0) Library Name ... libopenblas-r0.3.20.a (Single-threading) Supporting multiple x86_64 cpu models with minimum requirement for the common code being ZEN To install the library, you can run "make PREFIX=/path/to/your/installation install". Removing intermediate container f276420c3691 ---> a094af160f34 Step 10/10 : RUN make PREFIX=/usr NO_STATIC=1 install ---> Running in aa4ce3961832 make -j 16 -f Makefile.install install make[1]: Entering directory '/src/OpenBLAS-0.3.20' Generating openblas_config.h in /usr/include Generating f77blas.h in /usr/include Generating cblas.h in /usr/include Copying LAPACKE header files to /usr/include Copying the shared library to /usr/lib Generating openblas.pc in /usr/lib/pkgconfig Generating OpenBLASConfig.cmake in /usr/lib/cmake/openblas Generating OpenBLASConfigVersion.cmake in /usr/lib/cmake/openblas Install OK! make[1]: Leaving directory '/src/OpenBLAS-0.3.20' ... Removing intermediate container aa4ce3961832 ---> 5aebe4bd2ed3 Successfully built 5aebe4bd2ed3 Successfully tagged soulteary/milvus-openblas:0.3.20-amd-zen-ubuntu-22.04 ``` 最终,将镜像都构建完毕之后,我们能够得到下面的结果: ```bash soulteary/milvus-openblas 0.3.20-amd-zen-ubuntu-22.04 143MB soulteary/milvus-openblas 0.3.20-amd-zen-ubuntu-18.04 266MB soulteary/milvus-openblas 0.3.20-amd-zen-ubuntu-20.04 318MB ``` ## 基于 ARMv64 架构 CPU 的容器预构建 依旧是先准备 Dockerfile,和 AMD Zen 架构 CPU 遇到的问题类似,0.3.9 版本和一些 Ubuntu 发行版中,我们会在构建过程和结果中遇到一些报错和警告,虽然能够得到构建产物,但是和上面的原因一样,我们需要的是稳定、可靠的产物,所以,可以排除掉 0.3.9 版本的 OpenBLAS 的所有“镜像组合”。 对于 ARMv64 设备的镜像构建,我们可以使用两种方式。一是使用在以往文章中提到过的 buildx 来进行构建,下面的命令将在构建完毕之后,自动将镜像推送到 DockerHub 中: ```bash docker buildx build -t group/name:version -f ./Dockerfile.armv8 --push --platform=linux/arm64 . ``` 此外,我们还可以选择和上文中构建 Intel / AMD x86 CPU 一样,将构建的事情,放在具有这个硬件架构的设备上完成构建。相比较前者,这样的构建效率有质的不同(快的多的多),恰好这几种 CPU 的设备我手头都有,所以我就选择第二种方案啦。 不过,和上文中 x86 CPU 的构建配置还是有一些不同,我们需要指定构建参数为 `TARGET=ARMV8`,完整 Dockerfile,我上传到了 GitHub [soulteary/docker-openblas/blob/main/armv8/Dockerfile](https://github.com/soulteary/docker-openblas/blob/main/armv8/Dockerfile),有需要可以自取。 在准备好 Dockerfile 之后,我们使用下面的命令进行 ARMv8 环境下的镜像构建: ```bash docker build \ --build-arg=LTS=22.04 \ --build-arg=OPENBLAS_VERSION=0.3.20 \ -t soulteary/milvus-openblas:0.3.20-armv8-ubuntu-22.04 . docker build \ --build-arg=LTS=20.04 \ --build-arg=OPENBLAS_VERSION=0.3.20 \ -t soulteary/milvus-openblas:0.3.20-armv8-ubuntu-20.04 . docker build \ --build-arg=LTS=18.04 \ --build-arg=OPENBLAS_VERSION=0.3.20 \ -t soulteary/milvus-openblas:0.3.20-armv8-ubuntu-18.04 . ``` 相比较 x86 环境下的构建,ARMv8 环境的产物镜像会构建的飞快,并且产物会小巧不少: ```bash soulteary/milvus-openblas 0.3.20-armv8-ubuntu-22.04 88.8MB soulteary/milvus-openblas 0.3.20-armv8-ubuntu-20.04 169MB soulteary/milvus-openblas 0.3.20-armv8-ubuntu-18.04 147MB ``` 好啦,到这里如何在容器中构建 OpenBLAS 就分享完毕啦。 ## 如何使用 镜像的使用本为两部分,第一部分是获取镜像,你既可以使用我制作好的镜像,也可以进行自行构建。第二部分,则是在容器中使用多阶段构建,完成“软件安装”(跨镜像文件 COPY)。 如果你不想花费时间重新构建这几类不同硬件环境的镜像,可以使用我提过的镜像文件,经过 DockerHub 的压缩,这些镜像的尺寸得以进一步变得苗条,最小的镜像不过 20MB,最大的也才 47 MB:[https://hub.docker.com/repository/docker/soulteary/milvus-openblas/tags](https://hub.docker.com/repository/docker/soulteary/milvus-openblas/tags)。 如果你希望自行构建,根据自己的需求改变构建参数,使用起来心里更踏实,也可以参考我的项目 [https://github.com/soulteary/docker-openblas](https://github.com/soulteary/docker-openblas),并结合上文进行构建参数调整,来进行本地构建。 聊完了镜像的获取,我们来看看镜像在容器中如何使用吧。 关于预构建镜像的使用,其实非常简单,就如同我们执行 `make install` 一样,将文件拷贝到正确的目录中,并按照“传统”用软链做好副本的重命名即可,比如这样: ```bash FROM soulteary/milvus-openblas:0.3.20-intel-x86-ubuntu-20.04 AS OpenBLAS FROM ubuntu:20.04 LABEL maintainer=soulteary@gmail.com COPY --from=OpenBLAS /usr/lib/libopenblas-r0.3.20.so /usr/lib/ RUN ln -s /usr/lib/libopenblas-r0.3.20.so /usr/lib/libopenblas.so.0 && \ ln -s /usr/lib/libopenblas.so.0 /usr/lib/libopenblas.so ``` 在使用过程中,我们只需要保证基础镜像(Ubuntu)版本是对号的,CPU 运行环境是对号的,那么就都能够通过上面这种“跨镜像”复制的方式,来减少 Milvus 或者其他软件构建过程中的时间浪费。 ## 最后 既然本文主要聊 OpenBLAS 的容器构建,Milvus 相关的内容,我们还是在之后的文章中,再进行展开吧。 在接下来的向量数据库相关的内容中,我们将继续聊聊之前立过的各种 Flag。以及不断分享如何把一个原本需要非常长时间构建的软件,不断优化到一个喝杯水就能搞定的程度。 毕竟使用这类带有“魔法”的软件完成一些好玩的事情之外,让这些“魔法”能够生效的更快、能量更强,也是一件十分有趣的事情。 --EOF