.. _deploying_build_cluster: Deploying a Build Cluster ========================= Jolt is designed to be deployed as a set of containers. To deploy a build cluster you typically use a container orchestration environment such as `Kubernetes `_ or `Docker Swarm `_. See their respective documentation for installation instructions. The different components of the build cluster are: - The Jolt scheduler, which is responsible for build and task scheduling. - The Jolt worker, which executes tasks as instructed by the scheduler. - The artifact cache, which is a HTTP server used to cache build artifacts. - The Jolt dashboard, which is a web application used to monitor the build cluster. Each of the components is deployed as a separate container. Information about the images and their configuration environment variables can be found in :ref:`container_images` For service metrics, worker-pool utilization and scrape configuration, see :ref:`monitoring-prometheus`. Adapting Task Definitions ~~~~~~~~~~~~~~~~~~~~~~~~~ Task classes may have to be adapted to work in a distributed execution environment. For example, Jolt will by default not transfer any workspace files to a worker. Such dependencies, typically source repositories, must be listed as task requirements. See the Jolt test suite for examples of how to do this. Another common issue is that workers don't have the required tools installed. Those tools should to be packaged by Jolt tasks and listed as requirements in order to be automatically provisioned on the workers. They can also be installed manually in the worker container image, but this is not recommended as it makes administration of the build cluster more difficult, especially when multiple different versions of the same tool are required. Build Parameters ~~~~~~~~~~~~~~~~ Build submissions carry the ``[params]`` section of the configuration to the scheduler and the workers. The parameters can describe builds, for example the CI job that started them: .. code:: bash $ jolt -c params.origin="$BUILD_URL" -c params.branch=main build -n my-task They can also be set in the configuration file. Values are expanded on the client before the build is submitted, so they may refer to its environment: .. code:: ini [params] origin = {environ[BUILD_URL]} The dashboard shows all parameters of a build, and ``origin`` with its tasks, as a link if it is a URL. Other tools that follow the builds of the scheduler see them too. Jolt doesn't check parameters: whoever submits a build can set them to anything. Who submitted a build is known from its owners instead, the authenticated clients that the scheduler records, see :ref:`build-cluster-auth`. .. warning:: Anyone who can see the builds of the scheduler can see their parameters. Don't put passwords or other secrets in ``[params]``. Changing a parameter doesn't make Jolt rebuild tasks whose artifacts are already in the cache, unless the task asks for it with ``@influence.config("params", "origin")``. The same tasks submitted with different parameters are still listed as separate builds. Docker Swarm ~~~~~~~~~~~~ Docker Swarm is an easy to use container orchestration tool which can be used to deploy and manage the Jolt build cluster. The below Docker stack yaml file will deploy a scheduler and two workers, as well as an artifact cache. .. literalinclude:: ../../docker/swarm/jolt.yaml :language: yaml The Jolt workers are configured in the ``worker.conf`` file: .. literalinclude:: ../../docker/swarm/worker.conf :language: ini The file configures the URIs of the scheduler service and the HTTP cache. In the example, local Docker volumes are used as storage for artifacts. In a real deployment, persistent volumes are recommended. The administrator should also configure the maximum size allowed for the local cache in each node with the ``jolt.cachesize`` configuration key. If multiple workers are deployed on the same node, the local cache may be shared between them in the same directory. Fast SSD storage is recommended for the local cache and the worker workspace. To deploy the system into a swarm, run: .. code:: bash $ docker stack deploy -c jolt.yaml jolt You can then scale up the the number of workers to a number suitable for your swarm: .. code:: bash $ docker service scale jolt_worker=10 Scaling is possible even with tasks in progress as long as they don't cause any side effects. If a task is interrupted because the worker is terminated, the scheduler will redeliver the task execution request to another worker. The newly deployed build cluster is utilized by configuring the Jolt client as follows: .. literalinclude:: ../../docker/swarm/client.conf :language: ini These configuration keys can also be set from command line: .. code:: bash $ jolt config scheduler.grpc_uri tcp://127.0.0.1:9090 $ jolt config scheduler.http_uri http://127.0.0.1:8080 $ jolt config cache.grpc_uri tcp://127.0.0.1:9091 $ jolt config cache.http_uri http://127.0.0.1:8081 If your local machine is not part of the swarm you will need to replace ``127.0.0.1`` with the IP-address of one of the nodes in the swarm or, preferably, a load balancing hostname. To execute a task in the swarm, pass the ``-n/--network`` flag to the build command: .. code:: bash $ jolt build -n Alternatively, if you are using a separate configuration file: .. code:: bash $ jolt -c client.conf build --network Kubernetes ~~~~~~~~~~~~ Kubernetes is a more complex container orchestration tool which can be used to deploy and manage the Jolt build cluster. The below Kubernetes deployment yaml file will deploy a scheduler, two workers, an artifact cache as well as the dashboard. Notice inline ''FIXME'' comments in the yaml file that need to or should be replaced with actual values. .. literalinclude:: ../../docker/kubernetes/jolt.yaml :language: yaml To deploy the system into a Kubernetes cluster, run: .. code:: bash $ kubectl apply -f jolt.yaml You can then scale up the the number of workers to a number suitable for your cluster: .. code:: bash $ kubectl scale deployment jolt-worker --replicas=10 Scaling is possible even with tasks in progress as long as they don't cause any side effects. If a task is interrupted because the worker is terminated, the scheduler will redeliver the task execution request to another worker. The newly deployed build cluster is utilized by configuring the Jolt client as follows: .. literalinclude:: ../../docker/kubernetes/client.conf :language: ini The placeholder hosts should be replaced with the actual hostnames or IPs of the services in the Kubernetes cluster. The services are typically exposed through a load balancer and/or an ingress controller. Both methods are exemplified in the yaml file, but may not work out of the box in all Kubernetes installations. Run the following command to find the ExternalIP addresses of the services: .. code:: bash $ kubectl get services jolt-cache jolt-scheduler The client configuration keys can also be set from command line: .. code:: bash $ jolt config scheduler.grpc_uri tcp://:9090 $ jolt config scheduler.http_uri http://:8080 $ jolt config cache.grpc_uri tcp://:9090 $ jolt config cache.http_uri http://:8080 To execute a task in the cluster, pass the ``-n/--network`` flag to the build command: .. code:: bash $ jolt build -n Alternatively, if you are using a separate configuration file: .. code:: bash $ jolt -c client.conf build --network .. _build-cluster-auth: Authentication ~~~~~~~~~~~~~~ Without configuration, the cache and the scheduler accept requests from anyone who can reach them. Before exposing a build cluster to a network you don't trust, secure the connections with TLS and authenticate clients, either with static tokens or with the accounts of an OpenID Connect provider. The two can be combined, with tokens for CI and workers and personal logins for users. .. _build-cluster-tls: TLS ^^^ The cache and the scheduler serve TLS on both HTTP and gRPC when they are configured with a certificate and private key (``cert`` and ``cert_key``). Clients then connect with ``https://`` and ``tls://`` URIs: .. code:: ini [cache] http_uri = https://cache.example.com:8080 grpc_uri = tls://cache.example.com:9090 [scheduler] grpc_uri = tls://scheduler.example.com:9090 http_uri = https://scheduler.example.com:8080 Clients verify the certificates of services with the authorities of the system. For a private authority, point ``jolt.ca_cert`` or ``JOLT_CA_CERT`` to a PEM file with its certificate. Jolt, its workers, ``schedulerctl``, fstree and the dashboard all use these settings. Tokens are only sent over TLS, since they authenticate anyone who sees them. For a trusted network, ``jolt.allow_insecure_token`` or ``JOLT_ALLOW_INSECURE_TOKEN`` allows sending them without it. .. _build-cluster-tokens: Tokens ^^^^^^ A token is a random string that grants permissions. Create one with: .. code:: bash $ jolt token ci --permission read --permission write The command prints the token and the entry for the ``auth`` section of ``cache.yaml`` or ``scheduler.yaml``, which is the same for both services except for the permissions. The configuration only contains the SHA-256 hash of the token. Once any token is configured, clients without a token have no permissions, unless ``auth.anonymous`` grants them some. The cache grants ``read``, to download and look up artifacts, and ``write``, to upload them. Example, where anyone may download artifacts but only CI may upload them: .. code:: yaml # /etc/jolt/cache.yaml auth: anonymous: [read] tokens: - name: ci sha256: 31f69e6b9c24b00832b59b38a340b5346185c93eb9bb6bb54b2ef985aafdcb8f permissions: [read, write] The scheduler grants these permissions: .. list-table:: :widths: 20 80 :header-rows: 1 :class: tight-table * - Permission - Allows * - ``build`` - Building, cancelling the builds that the client submitted, and running their tasks. Clients that build also need ``read`` to see the logs of their tasks. * - ``worker`` - Executing tasks, required by workers and the executors they start. * - ``read`` - Listing builds and workers, following events and reading logs. * - ``admin`` - Cancelling any build, rescheduling tasks and debugging the scheduler. Administrators also need ``read`` to list builds and workers. Example: .. code:: yaml # /etc/jolt/scheduler.yaml auth: tokens: - name: developers sha256: 9c56cc51b374c3ba189210d5b6d4bf57790d351c96c47c02190ecf1e430635ab permissions: [build, read] - name: workers sha256: 4c1e0fb8de8c8e4c4f5b4ba9c3aa6a9ac5c3d7fb22bb4b8b0f4cbf5e8b0b6c3e permissions: [worker] Clients send their tokens as bearer tokens: - Jolt uses ``cache.token`` and ``scheduler.token``, or the ``JOLT_CACHE_TOKEN`` and ``JOLT_SCHEDULER_TOKEN`` environment variables. - Workers use ``scheduler_token`` and ``cache_token``, and pass them on to the executors they start, which upload artifacts. - ``schedulerctl`` uses ``scheduler_token`` in ``schedulerctl.yaml`` or ``JOLT_SCHEDULER_TOKEN``, and the dashboard ``JOLT_SCHEDULER_TOKEN``. Task logs are opened in a browser, which asks for a user name and password. Enter the token as the password; the user name is ignored. .. _build-cluster-oidc: Single Sign-On ^^^^^^^^^^^^^^ Instead of handing out tokens to users, the services can accept the ID tokens of an OpenID Connect provider, such as Keycloak, Dex or Authentik in front of an LDAP directory, or Microsoft Entra ID. Users sign in once with ``jolt login``, and every service verifies their tokens on its own with the public keys of the provider. The services don't talk to the directory, and they don't share any secret. Register Jolt with the provider as a public client, with the OAuth device authorization grant enabled, and configure the services to accept tokens issued to it. Permissions are granted to all users of the provider and to the members of groups: .. code:: yaml # /etc/jolt/cache.yaml auth: oidc: issuer: https://keycloak.example.com/realms/jolt audience: jolt permissions: [read] groups: - name: jolt-ci permissions: [write] .. code:: yaml # /etc/jolt/scheduler.yaml auth: oidc: issuer: https://keycloak.example.com/realms/jolt audience: jolt permissions: [build, read] groups: - name: jolt-admins permissions: [admin] The services contact the provider when they verify the first token, so they start even when it is unavailable. Tokens configured in ``auth.tokens`` keep working alongside the provider. Users configure the provider in the ``auth`` section and sign in: .. code:: bash $ jolt config auth.issuer https://keycloak.example.com/realms/jolt $ jolt login ``jolt login`` asks for the user name and password in the terminal if the provider allows the OAuth password grant for the client. Otherwise it opens the browser, or prints a URL and a code to enter there. The tokens are stored in the system keyring, and Jolt refreshes them when they are about to expire, so users stay signed in until the refresh token expires or they run ``jolt logout``. A token configured for a service takes precedence over the login. Notes on providers: - **Keycloak:** Groups aren't included in tokens by default. Add a *Group Membership* mapper with the claim name ``groups`` and *Full group path* turned off to the client, or use realm roles with ``groups_claim: realm_access.roles``. Turn on *OAuth 2.0 Device Authorization Grant* for the client, and *Direct access grants* to sign in with a password in the terminal. - **Dex:** Request the ``groups`` scope with ``auth.scopes``. - **Microsoft Entra ID:** Use the issuer of your tenant, ``https://login.microsoftonline.com//v2.0``, and the application ID as the audience and ``auth.client_id``. Turn on *Allow public client flows* for the application. Tokens contain the IDs of groups rather than their names, so prefer app roles assigned to groups, with ``groups_claim: roles``. Users sign in with the browser, which also handles multi-factor authentication.