来由
容器化应用在构建时经常因为下载包而花费很多时间(有时遇到网络抽风,一个版本要多次构建才能成功),而大多依赖包几乎又都是不变更的。于是我有这样一个构建镜像的优化思路:
用一个最小化的容器,将所需的包提前下载到本地,再将这些包构建成一个个的本地仓库。在需要构建的容器中,把软件源替换成本地仓库,就可以节省构建容器的时间(数量级的)。
这里之所以将其记下来,写成博客,还有一个原因:虽然这里用到的全都是现成的工具和软件,但是除了自身的手册和 --help
,其他有帮助的文档实在过于分散,而且据我搜索一圈下来,这些现成的工具总会碰到一些文档中未提及,甚至 Stack Overflow 之类 网站都很少碰到的小“坑”,而解决这些“坑”才是最耗时的。
TL;DR
我之后会在 github 上将其部分开源出来,放在这里(TODO)
目前经过测试兼容的发行版有:
- centos 6 / 7 / 8
- fedora 31 / 32 / 33
- amazonlinux 1 / 2
- ubuntu trusty (14.04) / xenial (16.04) / bionic (18.04) / focal (20.04)
- debian jessie (8) / stretch (9) / buster (10)
- opensuse leap 15
流程
- 基于该发行版最小化的容器,添加一些需要用到的软件源。
- 针对不同的发行版,使用对应的包管理工具,下载所需软件包列表的所有软件包以及其依赖包
- 将这些包按发行版放置在对应的目录下,使用容器中创建软件仓库的命令来构建本地仓库
- 使用一个简单的静态 web server,监听一个本地端口。这样一个本地的 http 软件仓库就搭起来了
- 将本地的软件源添加到需要经常更新构建的容器
Dockerfile
中。这里需要注意的是,本地的软件仓库一般没有做签名校验或者 https 之类,需要手动添加信任。
这里只对较为繁琐的步骤进行说明
0x02. 软件包下载
yum / dnf
|
|
- 这里首选
yumdownloader
,前一个方案试过dnf install --downloadonly
,发现这里的未知的坑不少,其中一个是下载完成后,已经下载到本地的包偶尔会被删掉,感觉是dnf / yum
本身有一些存储优化策略。 --resolve
选项是为了指定让yumdownloader
下载指定软件包的依赖包--installroot
不推荐使用这个选项来指定下载路径,使用该选项后,软件源配置文件中的宏(变量)都不自动解析了。比如常见的$releasever
变量,需要额外手动指定。yumdownloader
会直接将包下载到工作目录,直接用 cd 提前切换工作目录即可
apt-get
|
|
- 如果使用
apt-get install --donwload-only --reinstall
来下载包,那么依赖包如果是当前容器中已经存在的包就不会再下载了。
如 downloader-container (用于下载的容器) 中已存在 ca-certificates
和 openssl
两个软件包,此时再执行接下来的命令,结果就是:由于 --reinstall
选项 ca-certificates
会被下载,但是 openssl
作为 ca-certificates
的依赖包,就会被忽略了。
|
|
- 这里使用的是
apt-get download
而不是apt-get --install --donwload-only
,主要原因是在子命令apt-cache depends
中,查询到的依赖包,会有首选和次选(替代),而这两者往往是冲突的,就算apt-get install
使用了--donwload-only
也会导致包下载失败,因为无法解决冲突。
下面列举一个 apt-cache depends
的结果,其中 pinentry-curses
是 <pinentry:i386>
的更优先选择。
详细说明可以参见 https://www.thecodeship.com/gnu-linux/understanding-apt-cache-depends-output/
|
|
apt-get download
也是直接将软件包下载到当前目录的,所以提前用cd
命令切换工作目录即可
zypper
|
|
--non-interactive
主要用于脚本中,防止zypper
等待用户输入直到超时--pkg-cache-dir
用来指定下载目录-f
用来强制下载已经安装的包。这里其实会遇到和apt-get install --download-only
中一样的问题,就是依赖包如果已经安装,则不会下载。目前我暂时这样写,有缺少的基础包就手动加上了。- 对于
zypper
要区分 global arguments 和 subcommand arguments,具体到这条命令就是 install 前面为 global arguments,而后面是 subcommand arguments
0x03. 目录结构
yum
yum 仓库的目录结构如下:
|
|
说明:yum 仓库的结构比较简单,在发行版子目录 -> CPU架构目录下,存放下载的 rpm 包,然后在同目录下创建本地仓库索引。
创建 yum 仓库索引的命令如下:
|
|
其中,createrepo
还有一个 c 版本的 createrepo_c
,速度会更快,使用方法相同。推荐较新的发行版直接使用,比如 centos 8 / fedora 31+ / amazonlinux
|
|
较新的发行版某些包是用 modularity 1的方式构建的,如果想针对这些包构建本地仓库需要额外的命令:
文档详见:https://docs.fedoraproject.org/en-US/modularity/hosting-modules/
|
|
其中 REPO_NAME
是本地仓库的名字
这里一个值得注意的命令是 repo2module
(来自 https://github.com/rpm-software-management/modulemd-tools),因为在上述文档中并未提及如何生成 modules.yaml
文件。
fedora 或者 centos 8 (需要额外添加 epel 仓库) 可以通过 dnf install -y python3-gobject modulemd-tools
来安装 repo2module
命令
apt
apt 仓库的目录结构如下:
|
|
说明: apt 仓库分 dists/
和 pool/
两个子目录,dists/
子目录下存放索引,pool/
子目录下存放软件包。
创建 apt 仓库索引的命令如下:
这里本地仓库就不再使用 gpg 签名 Release 了,完整命令详见:https://medium.com/sqooba/create-your-own-custom-and-authenticated-apt-repository-1e4a4cf0b864#35dd
|
|
其中 base
是自定义的仓库子目录,这里方便之后扩展。
apt-ftparchive
命令可以通过 apt-get install -y dpkg-dev
安装。
0x05. 添加本地仓库
下面的 host.docker.internal
是通过 docker build
的 --add-host
添加的域名,4891 为本地 openresty 监听的端口
yum
|
|
zypper
|
|
apt
|
|