用 poudriere 完成包管理

• 本文约 2102 字,阅读大致需要 5 分钟 | Development | #jail | #FreeBSD | #Poudriere | #Package Management | #Ports | #ZFS | #nginx | #包管理

由于使用的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

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

1
2
3
4
5
6
7
8
9
        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:

1
2
3
4
5
6
7
<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即可。