poudriere buildhost - building for later releases in VMs with shared poudriere infrastructure.

I usually don't run the latest release branch on my servers. This also applies to my buildhosts which usually aren't dedicated buildhosts but also serve as e.g. fileservers.
While my "daily driver" workstations/desktops are also on the same release as my servers, on less important systems like my laptop or on systems with brand new hardware where I need some driver support, I might run the latest release or even snapshots/beta/RC versions.
As I also build my own packages this leads to a hen-egg-problem: I don't have a buildhost that can build for newer releases and the systems that run those releases are (comparably) slow to finish a bulk buildjob, so doing a "quick update to the next RC" (like now with 14.1-RC1) can easily take 24h+ while the buildhost with dual-socket Xeons with 24+ cores, 256 or 512GB RAM would build the same set of packages in ~3-4h.

Of course one can simply run the newer release in a bhyve VM and install and maintain a full poudriere installaiton + ports in that VM, then copy off the built packages to the server that serves those packages to the clients. This also means you have to maintain build options, ports (and possibly patches) and everything else in that VM and keep it in sync with the buildhost (and possibly other VMs) to have consistent port options and dependencies.
In reality, this won't work. At least not for very long - tried that, got bitten by diverging port options more than once.
It also means you waste (a lot of) storage for multiple (identical) ports trees, distfiles, package sets etc.. and have to copy around the packages after the build finishes, which is also cumbersome. The web interface would also need to have its own webserver and configuration and you end up with multiple status pages instead of one for all builds.


Hence, my current solution is as follows:

On the buildhost I export all poudriere datasets via nfsv4, except ".m" (which is used to mount the build jails and should be kept local), and the jails datasets - because the jail dataset needs to be cloned to create build jails. The /etc/exports on the buildhost looks as follows:

Code:
/usr/local/etc/poudriere.d  -alldirs -maproot=root:wheel fbsd14-buildvm
/usr/local/poudriere    -alldirs -maproot=root:wheel fbsd14-buildvm
/usr/local/poudriere  -alldirs -maproot=root:wheel fbsd14-buildvm
/usr/local/poudriere/data       -alldirs -maproot=root:wheel fbsd14-buildvm
/usr/local/poudriere/data  -alldirs -maproot=root:wheel fbsd14-buildvm
# .m is used for jaildata during builds; otherwise empty. keep it local to the buildvm
#/usr/local/poudriere/data/.m  -alldirs -maproot=root:wheel fbsd14-buildvm
/usr/local/poudriere/data/cache  -alldirs -maproot=root:wheel fbsd14-buildvm
/usr/local/poudriere/data/cache/ccache  -alldirs -maproot=root:wheel fbsd14-buildvm
/usr/local/poudriere/data/images  -alldirs -maproot=root:wheel fbsd14-buildvm
/usr/local/poudriere/data/logs  -alldirs -maproot=root:wheel fbsd14-buildvm
/usr/local/poudriere/data/packages  -alldirs -maproot=root:wheel fbsd14-buildvm
/usr/local/poudriere/data/wrkdirs  -alldirs -maproot=root:wheel fbsd14-buildvm
# jails need to be local on the buildhost because poudriere leverages zfs for creating the jails
#/usr/local/poudriere/jails  -alldirs -maproot=root:wheel fbsd14-buildvm
/usr/local/poudriere/ports  -alldirs -maproot=root:wheel fbsd14-buildvm
/usr/local/poudriere/ports/distfiles  -alldirs -maproot=root:wheel fbsd14-buildvm
/usr/local/poudriere/ports/latest  -alldirs -maproot=root:wheel fbsd14-buildvm
/usr/local/poudriere/ports/quarterly  -alldirs -maproot=root:wheel fbsd14-buildvm
V4: /usr/local fbsd14-buildvm

The last line is needed to have a 'root' directory for the V4 mount - The VM only mounts the /poudriere and /etc/poudriere.d directories, but it seems the directory above *has* to be set as the root for the V4 export. I would have liked it better if only /usr/local/poudriere and /usr/local/etc/poudriere.d were exported, but it seems one can only define one 'root' per remote host with V4 shares(?) and you can't use an empty dummy path as the root.
If this is wrong (I think so...) PLEASE give me a hint on how to fix this. I pulled my hair for the better part of a whole day over this...
Since the network connection (bridge) to the VM is only local, I didn't bother optimizing this for better security and also considered it acceptable to use the 'maproot' option.

This is basically all that needs to be configured on the host (except for the bhyve VM of course, but that's out of scope here)


On the VM I needed some more tweaks:
Poudriere insists on creating all the zfs datasets, so all poudriere datasets that are mounted from the host need to stay unmounted. If you destroy them, poudriere wil re-create and mount them next run.
I chose to simply set their "canmount" property to "off" so they won't interfere and keep poudriere happy as the datasets are still there:
Code:
# zfs list -ro name,mountpoint,canmount zroot/poudriere
NAME                                                         MOUNTPOINT                                                       CANMOUNT
zroot/poudriere                                              /usr/local/poudriere                                             off
zroot/poudriere/data                                         /usr/local/poudriere/data                                        off
zroot/poudriere/data/.m                                      legacy                                                           on
zroot/poudriere/data/cache                                   /usr/local/poudriere/data/cache                                  off
zroot/poudriere/data/images                                  /usr/local/poudriere/data/images                                 off
zroot/poudriere/data/logs                                    /usr/local/poudriere/data/logs                                   off
zroot/poudriere/data/packages                                /usr/local/poudriere/data/packages                               off
zroot/poudriere/data/wrkdirs                                 /usr/local/poudriere/data/wrkdirs                                off
zroot/poudriere/jails                                        legacy                                                           on
zroot/poudriere/jails/FreeBSD:14:amd64                       legacy                                                           on
zroot/poudriere/ports                                        /usr/local/poudriere/ports                                       off

As one can see the datasets that are local to the build-VM ('.m', 'jails' and every jail on that VM) have their mountpoint set to "legacy". This is needed because ZFS mounts *before* any (network) filesystems defined in /etc/fstab during boot, so those datasets would be overlayed by the nfs mounts.

The corresponding entries in /etc/fstab of the VM look as follows:
Code:
buildhost:/etc/poudriere.d          /usr/local/etc/poudriere.d              nfs     rw,nfsv4        0       0
buildhost:/poudriere                        /usr/local/poudriere                    nfs     rw,nfsv4        0       0

zroot/poudriere/jails                   /usr/local/poudriere/jails                      zfs     0       0
zroot/poudriere/jails/FreeBSD:14:amd64  /usr/local/poudriere/jails/FreeBSD:14:amd64     zfs     0       0
zroot/poudriere/data/.m                 /usr/local/poudriere/data/.m                    zfs     0       0

And here's the resulting list of mounted filesystems on the VM:
Code:
root@fbsd14-buildvm:~ # mount
zroot/ROOT/default on / (zfs, local, noatime, nfsv4acls)
devfs on /dev (devfs)
/dev/gpt/efiboot0 on /boot/efi (msdosfs, local)
zroot/var/mail on /var/mail (zfs, local, nfsv4acls)
zroot/home on /home (zfs, local, noatime, nfsv4acls)
zroot/var/log on /var/log (zfs, local, noatime, noexec, nosuid, nfsv4acls)
zroot on /zroot (zfs, local, noatime, nfsv4acls)
zroot/tmp on /tmp (zfs, local, noatime, nosuid, nfsv4acls)
zroot/usr/ports on /usr/ports (zfs, local, noatime, nosuid, nfsv4acls)
zroot/var/crash on /var/crash (zfs, local, noatime, noexec, nosuid, nfsv4acls)
zroot/var/tmp on /var/tmp (zfs, local, noatime, nosuid, nfsv4acls)
zroot/var/audit on /var/audit (zfs, local, noatime, noexec, nosuid, nfsv4acls)
zroot/usr/src on /usr/src (zfs, local, noatime, nfsv4acls)
buildhost:/etc/poudriere.d on /usr/local/etc/poudriere.d (nfs, nfsv4acls)
buildhost:/poudriere on /usr/local/poudriere (nfs, nfsv4acls)
zroot/poudriere/jails on /usr/local/poudriere/jails (zfs, local, noatime, nfsv4acls)
zroot/poudriere/jails/FreeBSD:14:amd64 on /usr/local/poudriere/jails/FreeBSD_14_amd64 (zfs, local, noatime, nfsv4acls)
zroot/poudriere/data/.m on /usr/local/poudriere/data/.m (zfs, local, noatime, nfsv4acls)

Now one can use all the existing poudriere configuration (present in the mounted /usr/local/etc/poudriere.d directory) as well as the ports. The VM will also write its logs and html files to the common shared directories, hence the web interface configured on the host is being populated with the data from the VM. Ports trees are shared and not duplicated, making management of local changes much easier and the package repository is available from the same location for all clients without (manually) copying around those files and occupying space twice.
I've been building packages on this VM for my laptop through all the 14.1-BETAs and currently a bulk job with 14.1-RC1 is running. So basically 'it works' but I feel this might need some polishing, which is part of why I started this thread.
A lot of this was found out by 'try & error' and working around errors that came up, especially with the NFSv4 mounts. I still get some "No name and/or group mapping for uid,gid:(0,0)" errors at the start of every poudriere bulk which I couldn't figure out from which shared dataset they come from, but builds are working fine - so I simply ignored them for now...

Apart from this being mainly a braindump about getting this to work (and possibly helping someone achieving the same goal), I still have some open questions I couldn't figure out myself:
- can/should the list of exported filesystems be reduced? I'm not entirely sure if it is helpful to share e.g. the 'wrkdir' or 'cache' datasets
- is there a more elegant way to prevent the nfs mounts to overlay the local filesystems without using 'legacy'?
- does poudriere manipulate any other of its datasets during bulk jobs, except the jail dataset which is cloned for each builder? (i.e. do I need to keep any other dataset local to the VM?)
and of course first and foremost:
- is there a more elegant solution to this? I found some (old...) mailing list discussions about adding bhyve-capabilities to poudriere, but it seems this never lead to any actual code...
 
Interesting post and an interesting problem - to use the more powerful of your machines to compile for other machines with differing versions and whatnot. I think this deserves a good discussion.

I think you [and everyone who wants to organize and maintain their packages] want to have a buildhost VM that you keep upgraded to the latest FreeBSD version. And then, on the buildhost, for each poudriere jail you select FreeBSD version and mirror a target machine's package set and do customization for each.

In reality, this won't work.

Is there problem? Was your issue maintaining the customization for each machine on your buildhost? Any developments on your buildhost situation?
 
Of course one can simply run the newer release in a bhyve VM and install and maintain a full poudriere installaiton + ports in that VM, then copy off the built packages to the server that serves those packages to the clients. This also means you have to maintain build options, ports (and possibly patches) and everything else in that VM and keep it in sync with the buildhost (and possibly other VMs) to have consistent port options and dependencies.
In reality, this won't work. At least not for very long - tried that, got bitten by diverging port options more than once.
It also means you waste (a lot of) storage for multiple (identical) ports trees, distfiles, package sets etc.. and have to copy around the packages after the build finishes, which is also cumbersome. The web interface would also need to have its own webserver and configuration and you end up with multiple status pages instead of one for all builds.

I don't really understand the issue you're describing, can you elaborate? Running poudriere on a CURRENT VM is the way to go in my experience.

In any case, here's how I do it:
  1. a bhyve VM running CURRENT. You can download the official VM image, but I build one by using mdisk, making a zpool, and installing pkgbase into place. This lets me use a locally-built pkgbase instead of upstream if I want.
  2. VM has two zpools: zroot and zdata, each backed by separate files. All the poudriere data is on zdata. This way I can replace the boot disk at any time, while keeping the data.
  3. a CURRENT poudriere jail installed via pkgbase, using same pkgbase repo as the VM.
  4. a RELEASE poudriere jail installed via pkgbase
  5. nginx serving /usr/local/share/poudriere/html so I can view the build logs
  6. nginx serving /usr/local/poudriere/data/packages so I can configure pkg repos to point to subdirs
Now my builds are just poudriere bulk -j current -f myports. Then I configure my hosts to use e.g. https://poudriere.p7x.dev/pkg/15-default/ for the URL. To upgrade CURRENT I do a normal pkgbase upgrade of the VM, followed by poudriere jail -j current -u.

At $JOB we spin up a temporary big GCE instance to do the build, then zfs send the results to a cheap file server with nginx serving everything up.
 
I don't really understand the issue you're describing, can you elaborate? Running poudriere on a CURRENT VM is the way to go in my experience.
I actually run poudriere in a bhyve VM, but I'm not maintaining yet another full ports tree and all my poudriere options etc pp inside the VM, but I mount everything poudriere/build related via NFS.

TBH I only use this for major releases and also only to build my initial, minimal set of packages needed on the buildhost. Then the buildhost is being updated to the new major release and I rebuild all other pkg sets.

I completely forgot about that thread - it was (as I wrote) mainly a braindump of how I built that setup and got it working... I still think it is quite an ugly hack, but with the upgrade to the 15.x branch approaching for my whole infrastructure, I'll soon have to utilize this method once again. So thanks for bringing this thread up again just in time 😅
 
Ah so it seems like maybe you build somewhere other than poudriere? That's probably the difference. I also use nfs, but I never run make locally. I always use poudriere testport -i to build and poke around if I need to.

The only thing I don't like about nfs is that I can't edit files while poudriere is running. So I'm thinking of using a checked out repo instead of git, and just script the push / fetch, so that I can continue to edit while poudriere builds.
 
Ports tree can be null mounted, so isn't it possible to put ports tree in NFS share and maintained by bare-metal host side, then share it by null mounting from each poudriere? Poudriere defaults its work directory outside ports tree. So it may be safe.

And you can share options by symlinking.
Putting actual contents to somewhere in NFS share, and make /var/db/ports directory and all /usr/local/etc/poudriere.d/options in bare metal and BhyveVMs to point to the shared directory.

Parts of my document could help. (Note that the direction of symlink for options are in reverse. /usr/local/etc/poudriere.d/options points to /var/db/ports.)
 
Ports tree can be null mounted, so isn't it possible to put ports tree in NFS share and maintained by bare-metal host side, then share it by null mounting from each poudriere? Poudriere defaults its work directory outside ports tree. So it may be safe.

That's what I do. But I'm hesitant to edit the ports tree while poudriere is building. Especially since I often rebase stuff with jj, sometimes having merge conflicts, etc.

From a mechanics standpoint, sharing ports tree via nfs and null mounting as a poudriere ports tree is straightforward. The tradeoff is concurrency, I can't edit while poudriere builds. To do that, I need separate copies of the tree.
 
Ah so it seems like maybe you build somewhere other than poudriere? That's probably the difference. I also use nfs, but I never run make locally. I always use poudriere testport -i to build and poke around if I need to.
No, I'm building with poudriere - that's what I'm mounting via NFS: the whole poudriere environment (ports, workdirs, caches, config dir...)

I also use poudriere testport for test-builds. The only occasions I need make is when I update/modify ports and I need to make makesum or fix some issues and need a workdir, then I make extract/patch the port and create patches via make makepatch.

Modifying the ports tree while poudriere is running may lead to errors, but that's regardless if poudriere runs 'locally' or in a VM and mounts the tree via NFS. That being said, I was always wondering why it isn't taking a snapshot of all important datasets at the beginning to prevent such edge-cases...
 
But you can't nullmount anything into a bhyve guest... (Some kind of 'shared folder' functionality has been added to bhyve recently, but I haven't looked into that yet)
I've actually never tried, but can't Bhyve mount network file systems (not limiting with NFS) via tap interface? And if (not 100% sure) NFS mounts can be null-mounted, sharing ports tree may be possible if shared.
 
The tradeoff is concurrency, I can't edit while poudriere builds. To do that, I need separate copies of the tree.
Perhaps I'm overlooking all kinds of details but, can't you build with poudriere using a ZFS mounted snapshot of the ports tree, and at the same time have the "current" ports tree available for your jj actions?
 
Perhaps I'm overlooking all kinds of details but, can't you build with poudriere using a ZFS mounted snapshot of the ports tree, and at the same time have the "current" ports tree available for your jj actions?

Yes that would be a nice way of working that I hadn't thought of. However, my ports tree is not on a zfs dataset on the poudriere builder. It's on a zfs dataset on my dev machine where I edit, and I share via nfs to the poudriere machine. So poudriere has it as a nfs volume, not zfs.

Worth thinking about how to flip it around though... it's not quite as simple as putting it zfs on poudriere and nfs to dev machine - git operations are super slow over nfs.

But what could work is having it on zfs locally, then snapshot and send it to poudriere to build. Then poudriere doesn't even need to clone.
 
Back
Top