delphij's Chaos

选择chaos这个词是因为~~实在很难找到一个更合适的词来形容这儿了……

16 Apr 2022

用 poudriere 完成包管理

由于使用的 port 的编译选项与官方的往往不一致(例如我非常讨厌 gnutlsavahi 这两个包,此外有时我希望使用一个和官方不太一样的 OpenLDAP 版本, 或者采用不同的编译选项等等),我之前一直是 portmaster(8) 的用户。 portmaster 是 Doug Barton 早年用 shell 脚本写的一个 portupgrade(1) 的替代品,和后者相比,它不需要使用数据库,并且充分利用了 shell 的任务管理功能实现了尽可能利用 CPU 的计算能力,我个人也从这个脚本中学到了不少 shell 脚本的技巧。

不过,使用 portmaster 需要在每一台机器上都有一份 ports tree,并且由于直接操作的是本地的生产环境, 因此对于比较基础的库,如 gettext 之类,或是在升级操作系统时, 由于升级时间较久导致出现问题的可能性相对要大一些。 另一方面,使用 port 来管理第三方软件意味着需要把联编过程中的所有依赖软件包全都都装到生产环境中, 有时这是非常不经济的,例如大部分时候运行环境并不需要完整的跨平台 LLVM,等等,而使用 port 安装的话, 每一个系统中都需要整体重新联编一遍。

我之前已经用过很长时间的 poudriere 了。 这是一款现代化的联编系统,它充分利用了 FreeBSD 的一系列特性,包括 ZFS 快照/克隆、 tmpfs、 jail 等等,支持交叉编译。除此之外它还支持使用 ccache 来减少重复编译,等等。 不过,线上的机器出于习惯^H^H懒惰导致的惯性一直还是在沿用之前采用 portmaster 来进行更新。

最近 libxml2 的 这次 更新导致了必须重新联编所有的包。具体原因是有人把 libxml2 的联编系统从 autotools 换成了 cmake, 而 cmake 这版实现中并未支持带版本符号。由于之前的 libxml2 支持带版本符号, 因此所有依赖它的包也就都使用了这些带版本符号,因此这次升级会导致所有没有更新 PORTREVISION 的包由于缺少带版本符号而失效。

虽然这个问题后来得到了 修正, 但我还是花了不少时间来修复线上的系统。

使用二进制包则不会有这类问题: poudriere 会根据依赖包的版本来决定需要重新联编哪些包,于是依赖包会一次性地全部重新联编一遍。 另一方面,二进制包的安装比现场 build 一遍要快的多,无论是更新还是回退都可以大大缩短潜在的影响服务的时长,配合 ZFS 快照, 更可以轻易地完成回退到之前已知可以工作版本的操作。

准备

首先是需要了解一下需求,通过这次迁移我希望达到的目的是:

  1. 将现有系统不同 jail 中使用的软件包配置加以统一。之前的十几个 jail 中的软件包是不同时间逐渐安装上去的,这个过程中许多地方选择了不同的选项, 这对于标准化管理或是事后调试验尸是不利的。 为了解决这个问题,我希望以主机为单位来为所有的 jail 编译一套二进制包,并且在所有系统中只使用一种版本,同一个软件如果存在不同的包, 其唯一的原因应该是 CPU 不同,而非其它。
  2. 砍掉 jail 中不需要的、用于支持 build 而安装的包。这个基本上可以用 pkg autoremove 来实现。
  3. 把所有的联编操作集中到一台性能较好的服务器上。

为此我需要:

  1. 收集一份无其他依赖的包的列表。例如,beancount 需要 Python,而没有其他包依赖 beancount,则只列出 beancount。这可以通过 pkg prime-origins 来实现。
  2. 将这些列表排序、排重。
  3. 收集不同机器和 jail 中的 port 配置,使用 poudriere options $(cat packages.list) 整体配置一遍。

配置 poudriere

/usr/local/etc/poudriere.conf 是 poudriere 的主要配置。

  1. 配置一个 ZFS。 ZPOOLZROOTFS 分别改为使用的 zpool 的名字,以及顶级的 ZFS dataset 去掉 zpool 名字之后的部分。我关掉了这个 dataset 上的各种保障数据断电时的一致性的措施,例如禁用了 sync,一来我有 UPS,二来所有这些数据都是从源代码生成的,如果真有不一致的话整个重来一遍即可。 BASEFS 配置为 ZPOOL/ZROOTFS 的挂载点。
  2. 配置 FreeBSD 下载站,这台机器就在美国,所以用推荐值 FREEBSD_HOST=https://download.FreeBSD.org 即可。
  3. 生成一个用于签名的 RSA key pair,将私钥指定为 PKG_REPO_SIGNING_KEY
  4. 创建一个用于 ccache 的 ZFS dataset,这个缓存是在不同的 jail 之间共享的,可以事后把 ccache.conf 中配置的尺寸改得稍微大一些,但使用默认值也问题不大。
  5. 设置 ALLOW_MAKE_JOBS=yes。默认情况下, poudriere 会在每个 builder 中使用 -j 1,这么做的考虑是 poudriere 本身会按照 CPU 数量启动 builder,因此如果再允许每个 builder 自己按 CPU 数量去起 build 进程的话就变成 N^2 了,这么做看起来是很有道理的,但实践上,类似于 LLVM 这样的巨兽往往是 build 过程中的瓶颈,而其他包的联编过程则要和平很多,因此如此配置可以显著减少联编需要的时间。
  6. URL_BASE 设置为 poudriere 网站的顶级 URL。poudriere 包含了一套用于观察 build 状况的 Web 页面。

配置 nginx

这部分比较简单,重点如下:

        root   /usr/local/share/poudriere/html;
        location /data {
            alias /ZPOOL/ZROOTFS/data/logs/bulk;
            autoindex on;
        }
        location /packages {
            root /ZPOOL/ZROOTFS/data;
            autoindex on;
        }

将 /ZPOOL/ZROOTFS/ 替换为实际的挂载点。

初始化 port

poudriere port -c

初始化 jail 模板

类似于:

poudriere jail -j <hostname>-amd64 -c -v 13.0-RELEASE -a amd64

配置 make.conf

/usr/local/etc/poudriere.d/make.conf 是全局 make.conf。我的这个 package 站并不对外提供服务, 而我需要一些许可证不允许分发包给第三方的软件包。默认情况下, poudriere 不会生成这些软件包的二进制包, 在其中添加 DISABLE_LICENSES=yes 可以绕过这个问题,但请注意不要分发这些二进制包,既然用了别人的软件, 就应该遵守其许可证中的条款。

除此之外我还对不同系统的 make.conf 进行了一些额外的配置,例如指定 CPUTYPE,或是用 DEFAULT_VERSIONS 来指定使用较早的某个软件的版本。

日常更新和build

日常更新非常简单,首先更新 ports tree:

poudriere ports -u

接下来整个重编

sudo poudriere bulk -j <system>-amd64 $(cat ~/packages.<system>)

poudriere 会自动检测需要重新 build 哪些包。

配置客户机

这主要是 /usr/local/etc/pkg/ 中的配置。

/usr/local/etc/pkg/repos/FreeBSD.conf:

FreeBSD: { enabled: no }

阻止系统使用 FreeBSD 官方的 packages。

/usr/local/etc/pkg/repos/<system>.conf:

<system>: {
	enabled: yes,
	url: "http://packages.infra.example.net/packages/<system>-amd64-default",
	mirror_type: "http",
	signature_type: "pubkey",
	pubkey: "/usr/local/etc/pkg/oikodomos.pub"
}

(此处 oikodomos.pub 是之前生成的签名 RSA key pair 的公钥)。

平时使用时和采用官方的 package 是一样的,pkg upgrade 即可。