为各发行版构建本地仓库

2021-02-01 10:36编辑本页

来由

容器化应用在构建时经常因为下载包而花费很多时间(有时遇到网络抽风,一个版本要多次构建才能成功),而大多依赖包几乎又都是不变更的。于是我有这样一个构建镜像的优化思路:

用一个最小化的容器,将所需的包提前下载到本地,再将这些包构建成一个个的本地仓库。在需要构建的容器中,把软件源替换成本地仓库,就可以节省构建容器的时间(数量级的)。

这里之所以将其记下来,写成博客,还有一个原因:虽然这里用到的全都是现成的工具和软件,但是除了自身的手册和 --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

流程

  1. 基于该发行版最小化的容器,添加一些需要用到的软件源。
  2. 针对不同的发行版,使用对应的包管理工具,下载所需软件包列表的所有软件包以及其依赖包
  3. 将这些包按发行版放置在对应的目录下,使用容器中创建软件仓库的命令来构建本地仓库
  4. 使用一个简单的静态 web server,监听一个本地端口。这样一个本地的 http 软件仓库就搭起来了
  5. 将本地的软件源添加到需要经常更新构建的容器 Dockerfile 中。这里需要注意的是,本地的软件仓库一般没有做签名校验或者 https 之类,需要手动添加信任。

这里只对较为繁琐的步骤进行说明

0x02. 软件包下载

yum / dnf

1
2
$ cd /path/to/dir \
    && yumdownloader --resolve pkg-1 pkg-2 ...
  • 这里首选 yumdownloader,前一个方案试过 dnf install --downloadonly,发现这里的未知的坑不少,其中一个是下载完成后,已经下载到本地的包偶尔会被删掉,感觉是 dnf / yum 本身有一些存储优化策略。
  • --resolve 选项是为了指定让 yumdownloader 下载指定软件包的依赖包
  • --installroot 不推荐使用这个选项来指定下载路径,使用该选项后,软件源配置文件中的宏(变量)都不自动解析了。比如常见的 $releasever 变量,需要额外手动指定。
  • yumdownloader 会直接将包下载到工作目录,直接用 cd 提前切换工作目录即可

apt-get

1
2
3
4
5
$ cd /path/to/dir \
    && apt-get download \
    $(apt-cache depends --recurse --no-recommends --no-suggests \
        --no-conflicts --no-breaks --no-replaces --no-enhances \
        pkg-1 pkg-2 ... | grep "^\w")
  • 如果使用 apt-get install --donwload-only --reinstall 来下载包,那么依赖包如果是当前容器中已经存在的包就不会再下载了。

如 downloader-container (用于下载的容器) 中已存在 ca-certificatesopenssl 两个软件包,此时再执行接下来的命令,结果就是:由于 --reinstall 选项 ca-certificates 会被下载,但是 openssl 作为 ca-certificates 的依赖包,就会被忽略了。

1
2
3
4
$ apt-get download \
        $(apt-cache depends --recurse --no-recommends --no-suggests \
        --no-conflicts --no-breaks --no-replaces --no-enhances \
        ca-certificates | grep "^\w")
  • 这里使用的是 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/

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$ apt-cache depends --recurse --no-recommends \
    --no-suggests --no-conflicts --no-breaks \
    --no-replaces --no-enhances --no-pre-depends \
    gnupg2 | grep -E '^gnupg-agent:i386' -A10

gnupg-agent:i386
 |Depends: pinentry-curses:i386
  Depends: <pinentry:i386>
    mew-beta-bin:i386
    mew-bin:i386
    pinentry-curses:i386
    pinentry-gnome3:i386
    pinentry-gtk2:i386
    pinentry-qt:i386
    pinentry-tty:i386
  Depends: libassuan0:i386
  • apt-get download 也是直接将软件包下载到当前目录的,所以提前用 cd 命令切换工作目录即可

zypper

1
2
3
4
$ zypper --no-gpg-checks --non-interactive \
    --pkg-cache-dir /path/to/dir \
    install -y -f --download-only \
    pkg-1 pkg-2 ...
  • --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 仓库的目录结构如下:

1
2
3
4
5
6
7
base/
├── amazonlinux-1
│   └── x86_64
|       ├── audit-libs-2.6.5-3.28.amzn2.i686.rpm
|       ├── ...
│       └── repodata
...

说明:yum 仓库的结构比较简单,在发行版子目录 -> CPU架构目录下,存放下载的 rpm 包,然后在同目录下创建本地仓库索引。

创建 yum 仓库索引的命令如下:

1
2
cd /path/to/dir \
    && createrepo --update ./

其中,createrepo 还有一个 c 版本的 createrepo_c,速度会更快,使用方法相同。推荐较新的发行版直接使用,比如 centos 8 / fedora 31+ / amazonlinux

1
2
cd /path/to/dir \
    && createrepo_c --update ./

较新的发行版某些包是用 modularity 1的方式构建的,如果想针对这些包构建本地仓库需要额外的命令:

文档详见:https://docs.fedoraproject.org/en-US/modularity/hosting-modules/

1
2
3
4
cd /path/to/dir \
    && createrepo_c --update ./ \
    && repo2module -s stable -n REPO_NAME -d ./ ./repodata/modules \
    && modifyrepo_c --mdtype=modules ./repodata/modules.yaml ./repodata

其中 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 仓库的目录结构如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
ubuntu/
├── dists
│   ├── bionic
│   │   └── base
│   │       └── main
│   │           └── binary-amd64
|  ...
└── pool
    ├── bionic
    │   └── base
    │       └── main
    │           └── binary-amd64
   ...

说明: apt 仓库分 dists/pool/ 两个子目录,dists/ 子目录下存放索引,pool/ 子目录下存放软件包。

创建 apt 仓库索引的命令如下:

这里本地仓库就不再使用 gpg 签名 Release 了,完整命令详见:https://medium.com/sqooba/create-your-own-custom-and-authenticated-apt-repository-1e4a4cf0b864#35dd

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
cd /path/to/dir

apt-ftparchive --arch amd64 packages \
    pool/bionic/base/main/binary-amd64 \
    > dists/base/main/binary-amd64/Packages

gzip -k -c \
    -f dists/base/main/binary-amd64/Packages \
    > dists/base/main/binary-amd64/Packages.gz

apt-ftparchive release dists/bionic/base > dists/bionic/Release

其中 base 是自定义的仓库子目录,这里方便之后扩展。 apt-ftparchive 命令可以通过 apt-get install -y dpkg-dev 安装。

0x05. 添加本地仓库

下面的 host.docker.internal 是通过 docker build--add-host 添加的域名,4891 为本地 openresty 监听的端口

yum

1
2
3
4
5
6
7
8
printf "[local-base]\n\
name=Local Base Repo\n\
baseurl=http://host.docker.internal:4891/base/centos-7/x86_64/\n\
skip_if_unavailable=True\n\
gpgcheck=0\n\
repo_gpgcheck=0\n\
enabled=1\n\
enabled_metadata=1" > /etc/yum.repos.d/local-base.repo

zypper

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
printf "[local-base]\n\
name=Local Base Repo\n\
baseurl=http://host.docker.internal:4891/base/sles-12/x86_64/\n\
skip_if_unavailable=True\n\
gpgcheck=0\n\
repo_gpgcheck=0\n\
enabled=1\n\
enabled_metadata=1" > /root/local-base.repo \
    && zypper -n ar --check --refresh -G file:///root/local-base.repo \
    && zypper -n mr --gpgcheck-allow-unsigned-repo local-base \
    && zypper -n mr --gpgcheck-allow-unsigned-package local-base \
    && rm -f /root/local-base.repo

apt

1
echo "deb [trusted=yes] http://host.docker.internal:4891/ubuntu bionic/base main" > /etc/apt/sources.list

  1. https://docs.pagure.org/modularity/ ↩︎

除另有声明外 本博客文章均采用 知识共享(Creative Commons) 署名 4.0 国际许可协议 进行许可 转载请注明原作者与文章出处


标签: programming

点击加载Disqus评论
Creative Commons © 2013 — 2021 xiaocang | Theme based on fzheng.me & NexT | Hosted by Netlify