Distributed builds

The service allows to run build on multiple machines and collect packages on main node. There are several ways to achieve it, this section describes officially supported methods.

Remote synchronization and remote server call

This setup requires at least two instances of the service:

  1. Web service (with opt-in authorization enabled), later will be referenced as master node.

  2. Application instances responsible for build, later will be referenced as worker nodes.

In this example the following settings are assumed:

  • Repository architecture is x86_64.

  • Master node address is master.example.com.

Master node configuration

The only requirements for the master node is that API must be available for worker nodes to call (e.g. port must be exposed to internet, or local network in case of VPN, etc) and file upload must be enabled:

[web]
enable_archive_upload = yes

In addition, the following settings are recommended for the master node:

  • As it has been mentioned above, it is recommended to enable authentication (see How to enable basic authorization) and create system user which will be used later. Later this user (if any) will be referenced as worker-user.

  • In order to be able to spawn multiple processes at the same time, wait timeout must be configured:

    [web]
    wait_timeout = 0
    

Worker nodes configuration

  1. First of all, in this setup you need to split your repository into chunks manually, e.g. if you have repository on master node with packages A, B and C, you need to split them between all available workers, as example:

    • Worker #1: A.

    • Worker #2: B and C.

    Hint: repo-tree subcommand provides --partitions argument.

  2. Each worker must be configured to upload files to master node:

    [upload]
    target = remote-service
    
    [remote-service]
    
  3. Worker must be configured to access web on master node:

    [status]
    address = https://master.example.com
    username = worker-user
    password = very-secure-password
    

    As it has been mentioned above, ${status:address} must be available for workers. In case if unix socket is used, it can be passed in the same option as usual. Optional ${status:username}/${status:password} can be supplied in case if authentication was enabled on master node.

  4. Each worker must call master node on success:

    [report]
    target = remote-call
    
    [remote-call]
    manual = yes
    

    After success synchronization (see above), the built packages will be put into directory, from which they will be read during manual update, thus ${remote-call:manual} flag is required.

  5. Change order of trigger runs. This step is required, because by default the report trigger is called before the upload trigger and we would like to achieve the opposite:

    [build]
    triggers = ahriman.core.gitremote.RemotePullTrigger ahriman.core.upload.UploadTrigger ahriman.core.report.ReportTrigger ahriman.core.gitremote.RemotePushTrigger
    

In addition, the following settings are recommended for workers:

  • You might want to wait until report trigger will be completed; in this case the following option must be set:

    [remote-call]
    wait_timeout = 0
    

Dependency management

By default worker nodes don’t know anything about master nodes packages, thus it will try to build each dependency by its own. However, using AHRIMAN_REPOSITORY_SERVER docker variable (or --server flag for setup command), it is possible to specify address of the master node for devtools configuration.

Repository and packages signing

You can sign packages on worker nodes and then signatures will be synced to master node. In order to do so, you need to configure worker node as following, e.g.:

[sign]
target = package
key = 8BE91E5A773FB48AC05CC1EDBED105AED6246B39

Note, however, that in this case, signatures will not be validated on master node and just will be copied to repository tree.

If you would like to sign only database files (aka repository sign), it has to be configured only on master node as usual, e.g.:

[sign]
target = repository
key = 8BE91E5A773FB48AC05CC1EDBED105AED6246B39

Double node minimal docker example

Master node config (master.ini) as:

[auth]
target = configuration

[web]
enable_archive_upload = yes
wait_timeout = 0

Command to run master node:

docker run --privileged -p 8080:8080 -e AHRIMAN_PORT=8080 -v master.ini:/etc/ahriman.ini.d/overrides.ini arcan1s/ahriman:latest web

The user worker-user has been created additionally. Worker node config (worker.ini) as:

[status]
address = http://172.17.0.1:8080
username = worker-user
password = very-secure-password

[upload]
target = remote-service

[remote-service]

[report]
target = remote-call

[remote-call]
manual = yes
wait_timeout = 0

[build]
triggers = ahriman.core.gitremote.RemotePullTrigger ahriman.core.upload.UploadTrigger ahriman.core.report.ReportTrigger ahriman.core.gitremote.RemotePushTrigger

The address above (http://172.17.0.1:8080) is somewhat available for worker container.

Command to run worker node:

docker run --privileged -v worker.ini:/etc/ahriman.ini.d/overrides.ini -it arcan1s/ahriman:latest package-add ahriman --now

The command above will successfully build ahriman package, upload it on master node and, finally, will update master node repository.

Check proof-of-concept setup here.

Addition of new package and repository update

Just run on worker command as usual, the built packages will be automatically uploaded to master node. Note that automatic update process must be disabled on master node.

Package removal

This action must be done in two steps:

  1. Remove package on worker.

  2. Remove package on master node.

Delegate builds to remote workers

This setup heavily uses upload feature described above and, in addition, also delegates build process automatically to build machines. Same as above, there must be at least two instances available (master and worker), however, all worker nodes must be run in the web service mode.

Master node configuration

In addition to the configuration above, the worker list must be defined in configuration file (${build:workers} option), i.e.:

[build]
workers = https://worker1.example.com https://worker2.example.com

[web]
enable_archive_upload = yes
wait_timeout = 0

In the example above, https://worker1.example.com and https://worker2.example.com are remote worker node addresses available for master node.

In case if authentication is required (which is recommended way to setup it), it can be set by using status section as usual.

Worker nodes configuration

It is required to point to the master node repository, otherwise internal dependencies will not be handled correctly. In order to do so, the --server argument (or AHRIMAN_REPOSITORY_SERVER environment variable for docker images) can be used.

Also, in case if authentication is enabled, the same user with the same password must be created for all workers.

It is also recommended to set ${web:wait_timeout} to infinite in case of multiple conflicting runs and ${web:service_only} to yes in order to disable status endpoints.

Other settings are the same as mentioned above.

Triple node minimal docker example

In this example, all instances are run on the same machine with address 172.17.0.1 with ports available outside of container. Master node config (master.ini) as:

[auth]
target = configuration

[status]
username = builder-user
password = very-secure-password

[build]
workers = http://172.17.0.1:8081 http://172.17.0.1:8082

[web]
enable_archive_upload = yes
wait_timeout = 0

Command to run master node:

docker run --privileged -p 8080:8080 -e AHRIMAN_PORT=8080 -v master.ini:/etc/ahriman.ini.d/overrides.ini arcan1s/ahriman:latest web

Worker nodes (applicable for all workers) config (worker.ini) as:

[auth]
target = configuration

[status]
address = http://172.17.0.1:8080
username = builder-user
password = very-secure-password

[upload]
target = remote-service

[remote-service]

[report]
target = remote-call

[remote-call]
manual = yes
wait_timeout = 0

[web]
service_only = yes

[build]
triggers = ahriman.core.upload.UploadTrigger ahriman.core.report.ReportTrigger

Command to run worker nodes (considering there will be two workers, one is on 8081 port and other is on 8082):

docker run --privileged -p 8081:8081 -e AHRIMAN_PORT=8081 -v worker.ini:/etc/ahriman.ini.d/overrides.ini arcan1s/ahriman:latest web
docker run --privileged -p 8082:8082 -e AHRIMAN_PORT=8082 -v worker.ini:/etc/ahriman.ini.d/overrides.ini arcan1s/ahriman:latest web

Unlike the previous setup, it doesn’t require to mount repository root for worker nodes, because they don’t use it anyway.

Check proof-of-concept setup here.

Addition of new package, package removal, repository update

In all scenarios, update process must be run only on master node. Unlike the manually distributed packages described above, automatic update must be enabled only for master node.

Automatic worker nodes discovery

Instead of setting ${build:workers} option explicitly it is also possible to configure services to load worker list dynamically. To do so, the ahriman.core.distributed.WorkerLoaderTrigger and ahriman.core.distributed.WorkerTrigger must be used for master and worker nodes respectively. See recipes for more details.

Those triggers have to be installed as a separate package:

yay -S ahriman-triggers

Known limitations

  • Workers don’t support local packages. However, it is possible to build custom packages by providing sources by using ahriman.core.gitremote.RemotePullTrigger trigger.

  • No dynamic nodes discovery. In case if one of worker nodes is unavailable, the build process will fail.

  • No pkgrel bump on conflicts.

  • The identical user must be created for all workers. However, the master node user can be different from this one.