Compare commits

..
Author SHA1 Message Date
Kilian BouteandCopilot ef9876072a Add isolated local pipeline load-test stack
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-09-18 12:47:29 +00:00
33 changed files with 515 additions and 625 deletions
+31 -11
View File
@@ -8,7 +8,6 @@ jobs:
deploy:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, ubuntu-24.04]
#k3d: [v5.5.2, v5.6.0, v5.7.5]
@@ -30,6 +29,8 @@ jobs:
kubectl get no
kubectl get pods -A -o wide
kubectl get sc
- name: Checkout repository
uses: actions/checkout@v2
- name: Install OpenEBS
run: |
kubectl apply -f https://openebs.github.io/charts/openebs-operator.yaml
@@ -49,11 +50,16 @@ jobs:
kubectl wait --for=condition=available deployment/prometheus-operator -n default --timeout=300s
kubectl get crd
kubectl get po -A -o wide
- name: Install Garage S3
id: install-garage
- name: Install Minio
id: install-minio
run: |
bash scripts/install-garage.sh
bash scripts/check-garage.sh
git clone --depth 1 --branch v6.0.1 https://github.com/minio/operator.git && kubectl apply -k operator/
kubectl apply -f ./base/minio/minio-tenant-base.yaml
echo "Waiting for resources to be created..."
sleep 10
kubectl wait --for=jsonpath='{.status.readyReplicas}'=1 statefulset/myminio-pool-0 -n minio-tenant --timeout=300s
kubectl get po -A -o wide
kubectl get po -A -o wide | grep myminio-pool-0-0 | awk '{print $3}' | grep -q '2/2' && echo "myminio-pool-0-0 pod is running with status 2/2" || (echo "myminio-pool-0-0 pod is not running with status 2/2" && exit 1)
- name: Install MongoDB
id: install-mongodb
run: |
@@ -103,20 +109,34 @@ jobs:
kubectl create namespace kerberos-hub
helm install hub kerberos/hub --values ./base/hub/kerberos-hub-values.yaml -n kerberos-hub --create-namespace
sleep 10
kubectl wait --for=condition=available deployment --all -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/admin -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-api -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-cleanup -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-frontend -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-frontend-demo -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-monitor-device -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-reactivate-subscription -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-analysis -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-counting -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-dominantcolor -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-event -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-export -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-monitor -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-notify -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-notify-test -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-sequence -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-sprite -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-throttler -n kerberos-hub --timeout=300s
kubectl get pods -A -o wide
- name: Collect deployment diagnostics
if: failure()
run: bash scripts/diagnose-deployment.sh
- name: Send Slack Notification on Success
if: success()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
bash scripts/notify-slack.sh "✅ K3d deployment successful on ${{ matrix.os }} (${{ matrix.k3d }}) - triggered by ${{ github.actor }}"
curl -X POST -H 'Content-type: application/json' --data "{\"text\": \"✅ K3d deployment successful on ${{ matrix.os }} (${{ matrix.k3d }}) - triggered by ${{ github.actor }}\"}" $SLACK_WEBHOOK_URL
- name: Send Slack Notification on Failure
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
bash scripts/notify-slack.sh "❌ K3d deployment failed on ${{ matrix.os }} (${{ matrix.k3d }}) - <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>"
curl -X POST -H 'Content-type: application/json' --data "{\"text\": \"❌ K3d deployment failed on ${{ matrix.os }} (${{ matrix.k3d }}) - <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>\"}" $SLACK_WEBHOOK_URL
+30 -16
View File
@@ -8,7 +8,6 @@ jobs:
deploy:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, ubuntu-24.04]
#kind: [v0.21.0, v0.22.0, v0.23.0, v0.24.0, v0.25.0]
@@ -27,11 +26,7 @@ jobs:
kubectl get pods -A -o wide
kubectl get sc
- name: Checkout repository
uses: actions/checkout@v4
- name: Check promoted Hub runtime
run: |
helm repo add kerberos https://charts.kerberos.io
bash scripts/check-hub-runtime.sh
uses: actions/checkout@v2
- name: Install OpenEBS
run: |
kubectl apply -f https://openebs.github.io/charts/openebs-operator.yaml
@@ -51,11 +46,16 @@ jobs:
kubectl wait --for=condition=available deployment/prometheus-operator -n default --timeout=300s
kubectl get crd
kubectl get po -A -o wide
- name: Install Garage S3
id: install-garage
- name: Install Minio
id: install-minio
run: |
bash scripts/install-garage.sh
bash scripts/check-garage.sh
git clone --depth 1 --branch v6.0.1 https://github.com/minio/operator.git && kubectl apply -k operator/
kubectl apply -f ./base/minio/minio-tenant-base.yaml
echo "Waiting for resources to be created..."
sleep 10
kubectl wait --for=jsonpath='{.status.readyReplicas}'=1 statefulset/myminio-pool-0 -n minio-tenant --timeout=300s
kubectl get po -A -o wide
kubectl get po -A -o wide | grep myminio-pool-0-0 | awk '{print $3}' | grep -q '2/2' && echo "myminio-pool-0-0 pod is running with status 2/2" || (echo "myminio-pool-0-0 pod is not running with status 2/2" && exit 1)
- name: Install MongoDB
id: install-mongodb
run: |
@@ -105,20 +105,34 @@ jobs:
kubectl create namespace kerberos-hub
helm install hub kerberos/hub --values ./base/hub/kerberos-hub-values.yaml -n kerberos-hub --create-namespace
sleep 10
kubectl wait --for=condition=available deployment --all -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/admin -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-api -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-cleanup -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-frontend -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-frontend-demo -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-monitor-device -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-reactivate-subscription -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-analysis -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-counting -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-dominantcolor -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-event -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-export -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-monitor -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-notify -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-notify-test -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-sequence -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-sprite -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-throttler -n kerberos-hub --timeout=300s
kubectl get pods -A -o wide
- name: Collect deployment diagnostics
if: failure()
run: bash scripts/diagnose-deployment.sh
- name: Send Slack Notification on Success
if: success()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
bash scripts/notify-slack.sh "✅ Kind deployment successful on ${{ matrix.os }} (${{ matrix.kind }}) - triggered by ${{ github.actor }}"
curl -X POST -H 'Content-type: application/json' --data "{\"text\": \"✅ Kind deployment successful on ${{ matrix.os }} (${{ matrix.kind }}) - triggered by ${{ github.actor }}\"}" $SLACK_WEBHOOK_URL
- name: Send Slack Notification on Failure
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
bash scripts/notify-slack.sh "❌ Kind deployment failed on ${{ matrix.os }} (${{ matrix.kind }}) - <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>"
curl -X POST -H 'Content-type: application/json' --data "{\"text\": \"❌ Kind deployment failed on ${{ matrix.os }} (${{ matrix.kind }}) - <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>\"}" $SLACK_WEBHOOK_URL
+4 -23
View File
@@ -1,4 +1,4 @@
name: Validate Kustomize and Garage on kind
name: Deploy on kind using Kustmize
on:
workflow_dispatch:
@@ -10,7 +10,6 @@ jobs:
deploy:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, ubuntu-24.04]
#kind: [v0.21.0, v0.22.0, v0.23.0, v0.24.0, v0.25.0]
@@ -29,34 +28,16 @@ jobs:
kubectl get pods -A -o wide
kubectl get sc
- name: Checkout repository
uses: actions/checkout@v4
- name: Render base Kustomize manifests
run: kubectl kustomize base > "$RUNNER_TEMP/base.yaml"
- name: Use Kind storage provisioner
run: |
kubectl get storageclass standard -o json |
jq '{apiVersion,kind,metadata:{name:"ssd-hostpath"},provisioner,parameters,volumeBindingMode,reclaimPolicy}' |
kubectl apply -f -
- name: Install Garage and test persistent S3 access
run: |
bash scripts/install-garage.sh
bash scripts/check-garage.sh
credential_hash="$(kubectl -n garage get secret garage-credentials -o jsonpath='{.data}' | sha256sum)"
bash scripts/install-garage.sh
test "$credential_hash" = "$(kubectl -n garage get secret garage-credentials -o jsonpath='{.data}' | sha256sum)"
bash scripts/check-garage.sh --restart
- name: Collect deployment diagnostics
if: failure()
run: bash scripts/diagnose-deployment.sh
uses: actions/checkout@v2
- name: Send Slack Notification on Success
if: success()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
bash scripts/notify-slack.sh "✅ Kustomize deployment successful on ${{ matrix.os }} (${{ matrix.kind }}) - triggered by ${{ github.actor }}"
curl -X POST -H 'Content-type: application/json' --data "{\"text\": \"✅ Kustomize deployment successful on ${{ matrix.os }} (${{ matrix.kind }}) - triggered by ${{ github.actor }}\"}" $SLACK_WEBHOOK_URL
- name: Send Slack Notification on Failure
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
bash scripts/notify-slack.sh "❌ Kustomize deployment failed on ${{ matrix.os }} (${{ matrix.kind }}) - <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>"
curl -X POST -H 'Content-type: application/json' --data "{\"text\": \"❌ Kustomize deployment failed on ${{ matrix.os }} (${{ matrix.kind }}) - <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>\"}" $SLACK_WEBHOOK_URL
+30 -12
View File
@@ -8,7 +8,6 @@ jobs:
deploy:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, ubuntu-24.04]
microk8s: [1.32/stable]
@@ -26,7 +25,7 @@ jobs:
kubectl get no
kubectl get pods -A -o wide
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Create storage class
id: create-storage-class
run: |
@@ -40,11 +39,16 @@ jobs:
kubectl wait --for=condition=available deployment/prometheus-operator -n default --timeout=300s
kubectl get crd
kubectl get po -A -o wide
- name: Install Garage S3
id: install-garage
- name: Install Minio
id: install-minio
run: |
bash scripts/install-garage.sh
bash scripts/check-garage.sh
git clone --depth 1 --branch v6.0.1 https://github.com/minio/operator.git && kubectl apply -k operator/
kubectl apply -f ./base/minio/minio-tenant-base.yaml
echo "Waiting for resources to be created..."
sleep 10
kubectl wait --for=jsonpath='{.status.readyReplicas}'=1 statefulset/myminio-pool-0 -n minio-tenant --timeout=300s
kubectl get po -A -o wide
kubectl get po -A -o wide | grep myminio-pool-0-0 | awk '{print $3}' | grep -q '2/2' && echo "myminio-pool-0-0 pod is running with status 2/2" || (echo "myminio-pool-0-0 pod is not running with status 2/2" && exit 1)
- name: Install MongoDB
id: install-mongodb
run: |
@@ -94,21 +98,35 @@ jobs:
kubectl create namespace kerberos-hub
helm install hub kerberos/hub --values ./base/hub/kerberos-hub-values.yaml -n kerberos-hub --create-namespace
sleep 10
kubectl wait --for=condition=available deployment --all -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/admin -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-api -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-cleanup -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-frontend -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-frontend-demo -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-monitor-device -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/hub-reactivate-subscription -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-analysis -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-counting -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-dominantcolor -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-event -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-export -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-monitor -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-notify -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-notify-test -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-sequence -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-sprite -n kerberos-hub --timeout=300s
kubectl wait --for=condition=available deployment/pipe-throttler -n kerberos-hub --timeout=300s
kubectl get pods -A -o wide
- name: Collect deployment diagnostics
if: failure()
run: bash scripts/diagnose-deployment.sh
- name: Send Slack Notification on Success
if: success()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
bash scripts/notify-slack.sh "✅ MicroK8s deployment successful on ${{ matrix.os }} (${{ matrix.microk8s }}) - triggered by ${{ github.actor }}"
curl -X POST -H 'Content-type: application/json' --data "{\"text\": \"✅ MicroK8s deployment successful on ${{ matrix.os }} (${{ matrix.microk8s }}) - triggered by ${{ github.actor }}\"}" $SLACK_WEBHOOK_URL
- name: Send Slack Notification on Failure
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
bash scripts/notify-slack.sh "❌ MicroK8s deployment failed on ${{ matrix.os }} (${{ matrix.microk8s }}) - <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>"
curl -X POST -H 'Content-type: application/json' --data "{\"text\": \"❌ MicroK8s deployment failed on ${{ matrix.os }} (${{ matrix.microk8s }}) - <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>\"}" $SLACK_WEBHOOK_URL
+1
View File
@@ -0,0 +1 @@
+8 -9
View File
@@ -24,17 +24,16 @@ With the Vault installed, we can proceed to configure the various components. Cu
![Configure Vault](./assets/images/configure-vault.gif)
- Navigate to the `Storage Providers` menu and select the (+ Add Storage Provider) button. Verify and add the provider using the [Garage setup guide](./base/garage/README.md). These settings apply to the default Kubernetes installation; retain the matching provider settings for an existing MinIO or Docker installation.
- Navigate to the `Storage Providers` menu and select the (+ Add Storage Provider) button. A modal will appear where you can input the required details. After entering the information, click the "Verify" button to ensure the configuration is valid. Once you receive a "Configuration is valid and working" message, click the "Add Storage Provider" button to complete the process. **_(!You are advised to generate more complex access and secret key for Minio, this is just for demo purposes, do not use this in production)_**
- MinIO/S3-compatible provider type (Garage backend)
- Minio
- Enabled: true
- Provider name: garage
- Provider name: minio
- Bucket name: mybucket
- Region: garage
- Hostname: garage.garage:3900
- SSL: false
- Access key: `access-key` from Secret `garage/garage-credentials`
- Secret key: `secret-key` from the same Secret
- Region: na
- Hostname: myminio-hl.minio-tenant:9000
- Access key: minio
- Secret key: minio123
- Navigate to the `Integrations` menu and select the (+ Add Integration) button. A modal will appear where you can input the required details. After entering the information, click the "Verify" button to ensure the configuration is valid. Once you receive a "Configuration is valid and working" message, click the "Add Integration" button to complete the process. **_(!You are advised to generate more complex username and password for RabbitMQ, this is just for demo purposes, do not use this in production)_**
@@ -51,7 +50,7 @@ With the Vault installed, we can proceed to configure the various components. Cu
- Enabled: true
- Account name: myaccount
- Main provider: garage
- Main provider: minio
- Day limit: 30
- Integration: rabbitmq
- Directory: \*
+2
View File
@@ -1,3 +1,5 @@
# Self-hosted with Docker Compose
For a dedicated disposable pipeline test environment, see the [local load-test stack](loadtest/README.md).
....
+9 -28
View File
@@ -116,7 +116,7 @@ When installing the Kerberos.io stack, several dependencies are required for sto
### Clone repository
Next, clone this repository for the Garage S3 manifests, MongoDB Helm chart values, and other required components.
Next, we will clone this repository to our local environment. This will allow us to execute the necessary configuration files for installing the Minio operator, MongoDB Helm chart, and other required components.
```bash
git clone https://github.com/kerberos-io/deployment
@@ -127,21 +127,7 @@ cd deployment
When you create a Kubernetes cluster using `kubeadm` on a bare metal machine
### Object storage: Garage (default)
After creating the `ssd-hostpath` StorageClass for your provisioner, run:
```sh
bash scripts/install-garage.sh
bash scripts/check-garage.sh
```
See the [Garage guide](./base/garage/README.md) for generated credentials,
Vault provider settings, persistence and existing MinIO compatibility. Garage
uses S3 port `3900` internally and NodePort `30090`; it needs no operator.
Skip the following legacy section when using the default installation.
### Optional legacy object storage: MinIO
### Object storage: MinIO
MinIO is a high-performance, distributed object storage system that is compatible with Amazon S3 cloud storage service. It is designed to handle large-scale data storage and retrieval, making it an ideal choice for modern cloud-native applications.
@@ -332,19 +318,14 @@ With the Kerberos Vault installed, we can proceed to configure the various compo
- Navigate to the `Storage Providers` menu and select the (+ Add Storage Provider) button. A modal will appear where you can input the required details. After entering the information, click the "Verify" button to ensure the configuration is valid. Once you receive a "Configuration is valid and working" message, click the "Add Storage Provider" button to complete the process.
- MinIO/S3-compatible provider type (Garage backend)
- Minio
- Enabled: true
- Provider name: garage
- Provider name: minio
- Bucket name: mybucket
- Region: garage
- Hostname: garage.garage:3900
- SSL: false
- Access key: `access-key` from Secret `garage/garage-credentials`
- Secret key: `secret-key` from the same Secret
For the optional legacy MinIO installation, retain that backend's hostname,
credentials and provider name instead. See the [Garage guide](./base/garage/README.md)
before switching an existing installation.
- Region: na
- Hostname: myminio-hl.minio-tenant:9000
- Access key: minio
- Secret key: minio123
- Navigate to the `Integrations` menu and select the (+ Add Integration) button. A modal will appear where you can input the required details. After entering the information, click the "Verify" button to ensure the configuration is valid. Once you receive a "Configuration is valid and working" message, click the "Add Integration" button to complete the process.
@@ -361,7 +342,7 @@ before switching an existing installation.
- Enabled: true
- Account name: myaccount
- Main provider: garage (or the retained provider for an existing installation)
- Main provider: minio
- Day limit: 30
- Integration: rabbitmq
- Directory: \*
+3 -9
View File
@@ -86,6 +86,7 @@ microk8s enable dns
microk8s enable dashboard
microk8s enable nvidia
microk8s enable hostpath-storage
microk8s enable minio
```
You can verify the status of the enabled addons by running the following command:
@@ -102,7 +103,7 @@ kubectl get po -w -A
### Clone repository
Next, clone this repository for the Garage S3 manifests, MongoDB Helm chart values, and other required components.
Next, we will clone this repository to our local environment. This will allow us to execute the necessary configuration files for installing the Minio operator, MongoDB Helm chart, and other required components.
```bash
git clone https://github.com/kerberos-io/deployment
@@ -111,13 +112,6 @@ cd deployment
## Kustomize
The default base uses [Garage S3](./base/garage/README.md). Before a native
Kustomize apply, run `bash scripts/install-garage.sh --prepare-only` to create
its namespace and persistent credentials. `configure.sh apply` does this
automatically. Do not enable the MinIO addon for this default stack. Existing
MinIO installations must keep their backend/provider settings until their data
is explicitly migrated; do not prune their storage resources.
In contrast to the detailed installation instructions, as mentioned here, an easier option to install is to use our Kustomize configure. This will allow you to specify and create your own overlays to install all the different components through a single command line.
Kustomize uses the concept of `bases` and `overlays`, allowing you to customize the base installation with different settings (an overlay). Before executing the `kustomize` command below, navigate to the `overlays/microk8s/kustomization.yaml` file and modify the `inlineValues` of the Hub to match the IP address of your node. Please note that when using Multipass, WSL, or any other type of virtualization, your IP address will differ from the IP address of your host machine. Verify your IP address using the `ifconfig` command.
@@ -191,7 +185,7 @@ namespace/kerberos-agent unchanged
namespace/kerberos-factory unchanged
namespace/kerberos-hub unchanged
namespace/kerberos-vault unchanged
namespace/garage unchanged
namespace/minio-tenant unchanged
namespace/mongodb unchanged
namespace/rabbitmq unchanged
namespace/vernemq unchanged
+1 -29
View File
@@ -17,34 +17,6 @@ The Kerberos.io stack offers flexible installation options, supporting deploymen
This repository continuously tests the Kerberos.io deployment using GitHub Actions across various platforms. Refer to the badges for an overview of the operating systems and Kubernetes distributions we are deploying to. We welcome suggestions for additional automations to consider.
Kubernetes examples and CI now use **Garage S3** for local object storage.
See the [Garage setup guide](./base/garage/README.md) for generated credentials,
Vault configuration, persistence tests and existing MinIO compatibility.
This is a single-node example, not a replicated production storage deployment.
The [MinIO manifests](./base/minio/) remain available; no existing data is
migrated or removed automatically. The Docker guide retains its separate
storage setup.
Slack notifications are optional. If `SLACK_WEBHOOK_URL` is unavailable (for
example on fork PRs), CI emits a warning and skips delivery without masking
deployment failures. Configured webhook delivery errors still fail the step.
Failed deployment jobs collect pod events and bounded logs for diagnosis.
### Hardened Hub frontend
The promoted frontend image requires Hub chart **0.170.0 or newer**, which
provides port `8080`, non-root/read-only runtime settings and a writable `/tmp`
mount. Both primary and demo frontends use these settings; Admin remains on its
legacy image and port `80`. Keep `global.imageRegistry` empty for the supplied
fully qualified `ghcr.io` image names. Service ports remain `80` and the frontend
NodePort remains `32080`, targeting the container port named `http`.
Run `bash scripts/check-hub-runtime.sh` to check the published chart with these
values before installation, or pass a local chart path as the first argument.
When adding custom branding mounts, preserve the chart's `frontend-tmp` volume
and `/tmp` mount. Existing deployments may require the explicit
[HTTP port migration](https://github.com/kerberos-io/helm-charts/blob/main/charts/hub/README.md#recovering-a-duplicate-http-port-during-upgrades).
## Architecture
As previously indicated, several integral components, including Object Storage, Database, and Message Brokers, are deployed and configured to ensure the proper functionality of Kerberos.io. The architecture diagrams below aims to provide a visual representation of these components and their interactions with one another depending the deployment; self-hosted or managed cloud deployment.
@@ -59,7 +31,7 @@ Edge (self-hosted) deployments are typically used for camera processing and edge
![Edge - self-hosted deployment](./assets/images/deployment-self-hosted.svg)
A key aspect of this deployment is the scaling of Agents. Each camera is assigned a dedicated Agent container, which is linked to a central Kerberos Vault. The Kerberos Vault stores metadata in MongoDB, recordings in Garage (or another S3-compliant object storage), and can generate events in a message broker like RabbitMQ. This approach enables you to develop custom applications/logic that can respond to the creation of new recordings.
A key aspect of this deployment is the scaling of Agents. Each camera is assigned a dedicated Agent container, which is linked to a central Kerberos Vault. The Kerberos Vault stores metadata in MongoDB, recordings in Minio (or another S3-compliant object storage), and can generate events in a message broker like RabbitMQ. This approach enables you to develop custom applications/logic that can respond to the creation of new recordings.
Based on your technology experience and preferences, you can choose from the following deployment guides:
+10 -29
View File
@@ -96,29 +96,14 @@ kubectl get po -w -A
### Clone repository
Next, clone this repository for the Garage S3 manifests, MongoDB Helm chart values, and other required components.
Next, we will clone this repository to our local environment. This will allow us to execute the necessary configuration files for installing the Minio operator, MongoDB Helm chart, and other required components.
```bash
git clone https://github.com/kerberos-io/deployment
cd deployment
```
### Object storage: Garage (default)
Create the `ssd-hostpath` StorageClass backed by MicroK8s hostpath storage:
```sh
kubectl apply -f base/ssd-hostpath-storageclass.yaml
bash scripts/install-garage.sh
bash scripts/check-garage.sh
```
See the [Garage guide](./base/garage/README.md) for generated credentials,
Vault settings and persistence. Garage uses S3 port `3900` internally and
NodePort `30090`; no MinIO operator or addon is required. Skip the following
legacy section when using the default installation.
### Optional legacy object storage: MinIO
### Object storage: MinIO
MinIO is a high-performance, distributed object storage system that is compatible with Amazon S3 cloud storage service. It is designed to handle large-scale data storage and retrieval, making it an ideal choice for modern cloud-native applications.
@@ -314,20 +299,16 @@ With the Vault installed, we can proceed to configure the various components. Cu
![Configure Vault](./assets/images/configure-vault.gif)
- Navigate to the `Storage Providers` menu and select the (+ Add Storage Provider) button. Verify and add the provider using the [Garage setup guide](./base/garage/README.md).
- Navigate to the `Storage Providers` menu and select the (+ Add Storage Provider) button. A modal will appear where you can input the required details. After entering the information, click the "Verify" button to ensure the configuration is valid. Once you receive a "Configuration is valid and working" message, click the "Add Storage Provider" button to complete the process. **_(!You are advised to generate more complex access and secret key for Minio, this is just for demo purposes, do not use this in production)_**
- MinIO/S3-compatible provider type (Garage backend)
- Minio
- Enabled: true
- Provider name: garage
- Provider name: minio
- Bucket name: mybucket
- Region: garage
- Hostname: garage.garage:3900
- SSL: false
- Access key: `access-key` from Secret `garage/garage-credentials`
- Secret key: `secret-key` from the same Secret
For the optional legacy MinIO installation, retain that backend's hostname,
credentials and provider name instead.
- Region: na
- Hostname: myminio-hl.minio-tenant:9000
- Access key: minio
- Secret key: minio123
- Navigate to the `Integrations` menu and select the (+ Add Integration) button. A modal will appear where you can input the required details. After entering the information, click the "Verify" button to ensure the configuration is valid. Once you receive a "Configuration is valid and working" message, click the "Add Integration" button to complete the process. **_(!You are advised to generate more complex username and password for RabbitMQ, this is just for demo purposes, do not use this in production)_**
@@ -344,7 +325,7 @@ credentials and provider name instead.
- Enabled: true
- Account name: myaccount
- Main provider: garage (or the retained provider for an existing installation)
- Main provider: minio
- Day limit: 30
- Integration: rabbitmq
- Directory: \*
-105
View File
@@ -1,105 +0,0 @@
# Garage S3 for local Kubernetes deployments
The default Kubernetes stack uses [Garage](https://garagehq.deuxfleurs.fr/),
not the MinIO operator. The pinned multi-architecture `dxflrs/garage:v2.5.0`
image runs as a single non-root node with SQLite metadata and object data on
the same 10Gi `ssd-hostpath` PVC. This is a local/edge/CI example with **no
replication or high availability**, not a production multi-node layout.
Use Garage's [production deployment guide](https://garagehq.deuxfleurs.fr/documentation/cookbook/real-world/)
for replicated storage. Choose capacity and a backup policy before storing
recordings; resizing a bound PVC depends on your storage provisioner.
## Install
First install your storage provisioner and create the `ssd-hostpath`
StorageClass (the same prerequisite used by MongoDB and RabbitMQ). Run commands
from the deployment repository root:
```sh
bash scripts/install-garage.sh
bash scripts/check-garage.sh
```
The installer generates an RPC secret and S3 credentials in the Kubernetes
Secret `garage/garage-credentials`, and reuses them on subsequent runs. No
credentials are committed to the repository. Garage's `--single-node` and
`--default-bucket` startup flags initialize the layout and `mybucket`.
The installer refuses to generate new credentials if the PVC already exists
but its Secret is missing: restore the original Secret instead.
The installer requires `kubectl`, `jq` and `openssl`. The smoke test also
requires AWS CLI and `curl` (provided by the GitHub-hosted CI runners).
For a full Kustomize installation, prepare credentials before applying the
overlay; the default base already includes Garage:
```sh
bash scripts/install-garage.sh --prepare-only
kubectl kustomize overlays/microk8s --enable-helm --load-restrictor LoadRestrictionsNone | kubectl apply -f -
kubectl -n garage rollout status statefulset/garage --timeout=300s
```
The `configure.sh apply` helper also prepares the Secret before applying its
overlay. Back up the Secret and the PVC together. Do not delete the namespace,
Secret or PVC as an upgrade/recovery step.
## Configure Vault
Add a storage provider using Vault's existing **MinIO/S3-compatible provider
type**. The provider *name* identifies the instance and is distinct from its
implementation type:
| Setting | Value |
| --- | --- |
| Provider type | MinIO (`minio`) |
| Provider name | `garage` |
| Bucket | `mybucket` |
| Region | `garage` |
| Internal hostname | `garage.garage:3900` (no URL scheme) |
| SSL | Disabled for this local HTTP example |
| Access key | Secret field `access-key` |
| Secret key | Secret field `secret-key` |
Retrieve credentials locally when configuring Vault; do not paste them into
logs, issues, or committed values:
```sh
kubectl -n garage get secret garage-credentials -o jsonpath='{.data.access-key}' | base64 --decode
kubectl -n garage get secret garage-credentials -o jsonpath='{.data.secret-key}' | base64 --decode
```
The S3 endpoint is exposed at `http://NODE_IP:30090`. If browsers or remote
clients consume provider-native presigned URLs, configure Vault's external
hostname as `NODE_IP:30090` with external SSL disabled (or a real TLS endpoint).
Cluster-only DNS names cannot be resolved by external clients. Protect exposed
ports with firewall rules and use TLS outside a trusted local network. Garage's
RPC port is bound to loopback; no admin API or web console is exposed.
Set the Vault account's main provider to `garage`. The supplied Hub values
also use that name for recording, archive, thumbnail and sprite access.
**Vault account credentials are separate from Garage S3 credentials**: keep
the existing Agent/Hub account credentials aligned with the Vault account,
not with the generated S3 key.
## Verification
`scripts/check-garage.sh` uses AWS CLI for an authenticated Signature V4 PUT/GET
through the Kubernetes Service and compares object bytes. It retains a small,
uniquely named object under `mybucket/ci-smoke/`; it never deletes existing
objects. The optional `--restart` flag restarts the Garage pod between upload
and download to verify persistence; use it only in CI or a maintenance window.
CI also verifies that reinstalling Garage preserves its credential Secret.
## Existing MinIO installations
The [MinIO manifests](../minio/) remain available as an explicit legacy option.
This change does **not** copy recordings, modify Vault's stored provider
records, or delete MinIO resources. Do not use prune/apply cleanup to remove
the old backend while it contains recordings.
When upgrading an existing deployment, keep its existing Vault provider names
in the Hub values (`kerberosvault.provider`, `archive.provider`,
`thumbnail.provider`, `sprite.provider`) until recordings have been explicitly
migrated and reads verified. Add Garage as a separate provider/account as
needed. Treat object migration and retirement of MinIO as a separate,
backed-up operation; this installer only provisions the new backend.
-128
View File
@@ -1,128 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: garage
data:
garage.toml: |
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "sqlite"
replication_factor = 1
rpc_bind_addr = "127.0.0.1:3901"
rpc_public_addr = "127.0.0.1:3901"
[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
---
apiVersion: v1
kind: Service
metadata:
name: garage
spec:
type: NodePort
selector:
app: garage
ports:
- name: s3
port: 3900
targetPort: s3
nodePort: 30090
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: garage
spec:
serviceName: garage
replicas: 1
selector:
matchLabels:
app: garage
template:
metadata:
labels:
app: garage
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
containers:
- name: garage
image: dxflrs/garage:v2.5.0@sha256:677e5b758e4425a325b811c425c026bf35a4bf42f7ddbaa773de3f4fd779a5a5
command: ["/garage"]
args: ["server", "--single-node", "--default-bucket"]
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
env:
- name: GARAGE_RPC_SECRET
valueFrom:
secretKeyRef:
name: garage-credentials
key: rpc-secret
- name: GARAGE_DEFAULT_ACCESS_KEY
valueFrom:
secretKeyRef:
name: garage-credentials
key: access-key
- name: GARAGE_DEFAULT_SECRET_KEY
valueFrom:
secretKeyRef:
name: garage-credentials
key: secret-key
- name: GARAGE_DEFAULT_BUCKET
value: mybucket
ports:
- name: s3
containerPort: 3900
startupProbe:
tcpSocket:
port: s3
periodSeconds: 2
failureThreshold: 60
readinessProbe:
exec:
command: ["/garage", "health"]
livenessProbe:
tcpSocket:
port: s3
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 512Mi
volumeMounts:
- name: config
mountPath: /etc/garage.toml
subPath: garage.toml
readOnly: true
- name: data
mountPath: /var/lib/garage
- name: tmp
mountPath: /tmp
volumes:
- name: config
configMap:
name: garage
- name: tmp
emptyDir:
sizeLimit: 16Mi
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: ssd-hostpath
resources:
requests:
storage: 10Gi
-4
View File
@@ -1,4 +0,0 @@
namespace: garage
resources:
- namespace.yaml
- garage.yaml
-4
View File
@@ -1,4 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: garage
+1 -1
View File
@@ -9,7 +9,7 @@ spec:
type: NodePort
ports:
- port: 80
targetPort: http
targetPort: 80
nodePort: 32080 # You can specify a port in the range 30000-32767 or let Kubernetes assign one automatically
name: frontend
protocol: TCP
+24 -26
View File
@@ -19,7 +19,7 @@ licenseServer:
# When using a private registry, you can specify the registry here.
global:
imageRegistry: ""
imageRegistry: "docker.io/"
# Environment: set to 'production', 'develop', 'demo', 'staging' or 'acceptance'.
# Set to 'true' if this is a private deployment.
environment: "production"
@@ -92,22 +92,22 @@ openai:
kerberosvault:
#uri: "http://vault-lb.kerberos-vault" # this needs to be a public accessible DNS name.
uri: http://vault-nodeport.kerberos-vault:80/api
provider: "garage"
provider: "minio"
accesskey: "XJoi2@bgSOvOYBy#"
secretkey: "OGGqat4lXRpL@9XBYc8FUaId@5"
# Archiving is used when creating a task. The underlying recording of the task will be copied from its
# existing provider to the below archived provider. Seperate credentials are used, as it makes possible to
# specify another retention period.
archive:
provider: "garage"
provider: "minio"
accesskey: "XJoi2@bgSOvOYBy#"
secretkey: "OGGqat4lXRpL@9XBYc8FUaId@5"
thumbnail:
provider: "garage"
provider: "minio"
accessKey: "XJoi2@bgSOvOYBy#"
secretKey: "OGGqat4lXRpL@9XBYc8FUaId@5"
sprite:
provider: "garage"
provider: "minio"
accessKey: "XJoi2@bgSOvOYBy#"
secretKey: "OGGqat4lXRpL@9XBYc8FUaId@5"
email:
@@ -157,7 +157,7 @@ kerberoshub:
api:
repository: ghcr.io/uug-ai/hub-api
pullPolicy: IfNotPresent
tag: "v1.11.31"
tag: "v1.9.48"
replicas: 1
jwtSecret: "I1JcwzW3A0t_THIS-IS_NOT_A_SECRET_WJK9jnPkipbnVTpf0efMy" # change to a random value, this is for generating JWT tokens.
schema: "http"
@@ -240,9 +240,7 @@ kerberoshub:
frontend:
repository: ghcr.io/uug-ai/hub-frontend
pullPolicy: IfNotPresent
tag: "v1.19.28"
# Requires Hub chart 0.170.0+ for the matching hardened runtime defaults.
containerPort: 8080
tag: "v1.13.0"
replicas: 1
schema: "http"
url: "yourdomain.com"
@@ -357,7 +355,7 @@ kerberoshub:
cleanup:
repository: ghcr.io/uug-ai/hub-cleanup
pullPolicy: IfNotPresent
tag: "v1.4.26"
tag: "v1.4.16"
resources:
requests:
memory: 50Mi
@@ -365,7 +363,7 @@ kerberoshub:
monitordevice:
repository: ghcr.io/uug-ai/hub-monitor-device
pullPolicy: IfNotPresent
tag: "v1.4.10"
tag: "v1.4.1"
resources:
requests:
memory: 50Mi
@@ -417,7 +415,7 @@ kerberospipeline:
event:
repository: ghcr.io/uug-ai/hub-pipeline-event
pullPolicy: IfNotPresent
tag: "v1.3.9"
tag: "v1.3.1"
replicas: 1
resources:
requests:
@@ -429,7 +427,7 @@ kerberospipeline:
monitor:
repository: ghcr.io/uug-ai/hub-pipeline-monitor
pullPolicy: IfNotPresent
tag: "v1.3.29"
tag: "v1.3.11"
replicas: 1
resources:
requests:
@@ -441,7 +439,7 @@ kerberospipeline:
sequence:
repository: ghcr.io/uug-ai/hub-pipeline-sequence
pullPolicy: IfNotPresent
tag: "v1.7.14"
tag: "v1.6.23"
replicas: 1
resources:
requests:
@@ -451,9 +449,9 @@ kerberospipeline:
memory: 50Mi
cpu: 50m
throttler:
repository: ghcr.io/uug-ai/hub-pipeline-throttler
repository: uugai/hub-pipeline-throttler
pullPolicy: IfNotPresent
tag: "v1.2.10"
tag: "v1.2.0"
replicas: 1
resources:
requests:
@@ -465,7 +463,7 @@ kerberospipeline:
notify:
repository: ghcr.io/uug-ai/hub-pipeline-notification
pullPolicy: IfNotPresent
tag: "v1.3.46"
tag: "v1.3.17"
replicas: 1
resources:
requests:
@@ -483,9 +481,9 @@ kerberospipeline:
# persistentVolumeClaim:
# claimName: custom-layout-claim
notifyTest:
repository: ghcr.io/uug-ai/hub-pipeline-notification-test
repository: uugai/hub-pipeline-notification-test
pullPolicy: IfNotPresent
tag: "v1.2.6"
tag: "v1.2.1"
replicas: 1
resources:
requests:
@@ -505,7 +503,7 @@ kerberospipeline:
analysis:
repository: ghcr.io/uug-ai/hub-pipeline-analysis
pullPolicy: IfNotPresent
tag: "v1.8.29"
tag: "v1.8.4"
replicas: 1
resources:
requests:
@@ -517,7 +515,7 @@ kerberospipeline:
dominantColor:
repository: ghcr.io/uug-ai/hub-pipeline-dominantcolors
pullPolicy: IfNotPresent
tag: "v2.0.9"
tag: "v2.0.3"
replicas: 1
resources:
requests:
@@ -529,7 +527,7 @@ kerberospipeline:
thumbnail:
repository: ghcr.io/uug-ai/hub-pipeline-thumbnail
pullPolicy: IfNotPresent
tag: "v1.3.22"
tag: "v1.3.10"
replicas: 1
quality: "1" # 1 (best) - 31 (worst)
width: "600"
@@ -544,9 +542,9 @@ kerberospipeline:
memory: 50Mi
cpu: 50m
counting:
repository: ghcr.io/uug-ai/hub-pipeline-counting
repository: uugai/hub-pipeline-counting
pullPolicy: IfNotPresent
tag: "v2.0.6"
tag: "v1.6.3"
replicas: 1
resources:
requests:
@@ -556,7 +554,7 @@ kerberospipeline:
enabled: true # Enable or disable the sprite generation 'true' or 'false
repository: ghcr.io/uug-ai/hub-pipeline-sprite
pullPolicy: IfNotPresent
tag: "v1.1.27"
tag: "v1.1.16"
replicas: 1
interval: "1" # Number of secondes between each thumbnail in the sprite
width: "240" # Should not be changed for the moment (hard coded in UI)
@@ -571,7 +569,7 @@ kerberospipeline:
export:
repository: ghcr.io/uug-ai/hub-pipeline-export
pullPolicy: IfNotPresent
tag: "v1.2.20"
tag: "v1.2.9"
replicas: 1
resources:
requests:
+1 -1
View File
@@ -6,4 +6,4 @@ resources:
- factory
- vault
- hub
- garage
- minio
+2 -2
View File
@@ -24,7 +24,7 @@ spec:
command: ["sh", "-c", "until nc -z mongodb.mongodb 27017 > /dev/null; do echo Waiting for master.; sleep 2; done;"]
containers:
- name: vault
image: ghcr.io/uug-ai/vault:v1.7.19
image: ghcr.io/uug-ai/vault:v1.4.9
resources:
requests:
memory: 128Mi
@@ -43,4 +43,4 @@ spec:
optional: true
env:
- name: GIN_MODE
value: release
value: release
@@ -12,8 +12,8 @@ stringData:
MQTT_USERNAME: ""
MQTT_PASSWORD: ""
VAULT_STORAGE_PROVIDERS: >-
[{"name":"garage","provider":"minio","host":"garage.garage:3900","region":"garage","bucket":"mybucket","access_key":"replace-with-garage-access-key","secret":"replace-with-garage-secret-key","use_ssl":"false"}]
[{"name":"archive","provider":"minio","host":"minio.minio.svc:9000","region":"us-east-1","bucket":"recordings","access_key":"example-key","secret":"example-secret","use_ssl":"false"}]
VAULT_INTEGRATIONS: >-
[{"name":"events","queue":"rabbitmq","broker":"amqp://rabbitmq.rabbitmq.svc:5672","topic":"recordings","username":"example-user","password":"example-password"}]
VAULT_ACCOUNTS: >-
[{"account":"camera-fleet","directory":"fleet","provider":"garage","access_key":"example-account-key","secret_access_key":"example-account-secret","limit":"30","queues":["events"]}]
[{"account":"camera-fleet","directory":"fleet","provider":"archive","access_key":"example-account-key","secret_access_key":"example-account-secret","limit":"30","queues":["events"]}]
-2
View File
@@ -39,8 +39,6 @@ if [ "$command" == "apply" ]; then
# Adjust the base path reference
sed -i "s|../../base|./base|g" ./kustomization.yaml
bash scripts/install-garage.sh --prepare-only || exit 1
# Apply kustomize installation
kubectl kustomize ./ --enable-helm --load-restrictor LoadRestrictionsNone | kubectl apply -f -
+16
View File
@@ -0,0 +1,16 @@
# Optional Docker Compose substitutions; copy to .env if needed.
# Host binds are always 127.0.0.1. Update the CLI URLs when changing these ports.
LOADTEST_MONGODB_PORT=27028
LOADTEST_RABBITMQ_PORT=5673
LOADTEST_RABBITMQ_MANAGEMENT_PORT=15673
# Pinned release defaults; use image@sha256:... for immutable repeat runs.
LOADTEST_MONGODB_IMAGE=mongo:8.0.13
LOADTEST_RABBITMQ_IMAGE=rabbitmq:4.1.4-management
LOADTEST_EVENT_IMAGE=ghcr.io/uug-ai/hub-pipeline-event:v1.3.2
LOADTEST_MONITOR_IMAGE=ghcr.io/uug-ai/hub-pipeline-monitor:v1.3.19
LOADTEST_SEQUENCE_IMAGE=ghcr.io/uug-ai/hub-pipeline-sequence:v1.7.1
# CLI host connections. Compose does not export .env into your host shell.
LOADTEST_MONGODB_URI=mongodb://127.0.0.1:27028/Kerberos?replicaSet=loadtest-rs&directConnection=true&readConcernLevel=majority&w=majority&journal=true&wtimeoutMS=10000
LOADTEST_RABBITMQ_URL=amqp://loadtest:disposable-loadtest-only@127.0.0.1:5673/
+2
View File
@@ -0,0 +1,2 @@
.env
reports/
+195
View File
@@ -0,0 +1,195 @@
# Disposable local pipeline load test
This dedicated Compose project exercises **real disk-backed MongoDB and RabbitMQ**
and the released event router, monitor, and sequence workers. The CLI runs on the
host; no CLI container image is needed. Synthetic recording events pass through
`monitor,sequence,loadtest-<run-id>`; the CLI consumes the final completion queue.
There is no Vault, video upload, GPU, inference, or notification worker in this
initial mode. This is a functional/local throughput baseline, **not an enterprise
capacity claim** or a substitute for representative full-scale testing.
## Isolation and prerequisites
- Docker Engine with Compose v2 and permission to use its daemon. If access is
denied, stop: do not use sudo, change socket permissions, or bypass that policy.
- Host Go matching the CLI's `go.mod`, or an existing CLI installation containing
`load-test`. The commands below assume adjacent `deployment/` and `cli/` checkouts.
- Access to the pinned GHCR images. Private images may require an existing
authorized Docker registry login; this stack does not obtain credentials.
- Available loopback ports 27028, 5673, and 15673, and disk space for retained data.
The project name is `uug-loadtest`. Its network is private/internal and its named
volumes are project-scoped; no external database, broker, production volume, or
production configuration is mounted. All published ports bind **127.0.0.1 only**.
MongoDB deliberately has no authentication on this isolated local test network.
RabbitMQ's public **disposable test credentials** are
`loadtest` / `disposable-loadtest-only`, not the restricted `guest` account.
Other local processes can access these services: use only synthetic test data.
The released monitor hardcodes database **`Kerberos`**, and the released queue
builders use RabbitMQ's root vhost **`/`**. Consequently, these must be dedicated
instances, not a test database/vhost on shared production infrastructure.
Every CLI invocation below deliberately includes
`--database Kerberos --allow-default-names --run-id smoke`. This safety exception
is appropriate **only for this disposable stack**.
## Start and inspect
From the monorepo root:
```sh
cd deployment/loadtest
docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps -a
docker compose images
```
Do not run these daemon commands where Docker access is denied. `config --quiet`
can still validate the manifest without launching containers.
An idempotent init job creates the single-node `loadtest-rs` replica set and waits
for a writable primary. Mongo health checks first verify server availability;
workers start only after the init job succeeds and RabbitMQ is healthy. The init
job refuses to reconfigure an unexpected existing replica set. A successful
`mongo-init` exit is expected, not a failed service. Compose startup is **not**
proof of pipeline readiness: the CLI checks that the required stage queues have
consumers before running.
The host CLI uses `directConnection=true` because the replica advertises
`mongo:27017`, which is resolvable only inside Compose. Workers use that internal
address directly. Both URI configurations request majority reads, journaled
majority writes, and a 10-second write concern timeout. Worker retryable writes
are enabled. A single member exercises replica-set semantics but provides **no
redundancy** and does not model a multi-node majority's latency.
Tracing is deliberately off: `OTEL_SDK_DISABLED=true` and an explicitly empty
`OTEL_EXPORTER_OTLP_ENDPOINT`. The pinned monitor's trace library treats the empty
endpoint as no-op; the pinned sequence ignores its older library's missing-endpoint
error and uses the global no-op provider. No fake collector endpoint or recurring
export failures are needed. Recheck this behavior when overriding worker images.
## Prepare, smoke, and verify
See the [CLI README](https://github.com/uug-ai/cli/blob/main/README.md) for the
load-test command reference.
Fixture preparation is a **separate, untimed step**. Start small: one organisation,
one project, two devices, and no historical recordings. History defaults to zero;
increase `--history-per-device` only when explicitly testing a larger database.
Workload dimensions, rate, duration, concurrency, and deployment label are set
only on `prepare` and frozen in its stored manifest. `run`, `verify`, and `cleanup`
load that manifest by run ID and reject workload overrides.
In the same host shell:
```sh
export LOADTEST_MONGODB_URI='mongodb://127.0.0.1:27028/Kerberos?replicaSet=loadtest-rs&directConnection=true&readConcernLevel=majority&w=majority&journal=true&wtimeoutMS=10000'
export LOADTEST_RABBITMQ_URL='amqp://loadtest:disposable-loadtest-only@127.0.0.1:5673/'
mkdir -p reports
cd ../../cli
# Safe preview: no writes or publication without both execution flags.
go run . load-test prepare \
--database Kerberos --allow-default-names --run-id smoke \
--organisations 1 --projects 1 --devices 2 --history-per-device 0 \
--rate 5 --duration 10s --concurrency 4 --deployment-label local-compose-smoke
# Explicitly seed only this dedicated test target.
go run . load-test prepare \
--database Kerberos --allow-default-names --run-id smoke \
--organisations 1 --projects 1 --devices 2 --history-per-device 0 \
--rate 5 --duration 10s --concurrency 4 --deployment-label local-compose-smoke \
--execute --confirm-test-target
# First warm/smoke run: 5 events/sec for 10 seconds, 4 publishers.
go run . load-test run \
--database Kerberos --allow-default-names --run-id smoke \
--drain-timeout 60s --max-p99 10s \
--report ../deployment/loadtest/reports/smoke.json \
--execute --confirm-test-target
go run . load-test verify \
--database Kerberos --allow-default-names --run-id smoke \
--execute --confirm-test-target
```
`--max-p99 10s` is a deliberately generous smoke-test gate, not a production SLO.
Let the run drain and inspect verification/report failures before preparing a new
run with increased rate or concurrency. Cleanup retains a manifest tombstone:
use a **new run ID** and prepare new fixtures for each subsequent test, never
reuse an ID after cleanup. There is no automatic cleanup after a run.
Preparation, execution and cleanup share one database-wide lock so their load
cannot overlap. Success releases the active-run lock;
failed or interrupted runs retain it indefinitely to prevent overlap with
unfinished work. Investigate and retain reports first, then reset this dedicated
stack's volumes before starting fresh. A different run ID alone does not bypass
that lock.
The CLI uses **only** `LOADTEST_MONGODB_URI` and `LOADTEST_RABBITMQ_URL` for these
connections, never generic application environment fallbacks. The explicit
`MONGODB_*`/`RABBITMQ_*` entries in Compose are worker settings, not CLI settings.
The `.env.example` lists Compose port/image overrides. If you copy it to `.env`,
Compose reads it automatically, but your host shell does not. Export the CLI URLs
explicitly and change their ports to match `LOADTEST_MONGODB_PORT` and
`LOADTEST_RABBITMQ_PORT`. The management UI is at http://127.0.0.1:15673/ by default;
`LOADTEST_RABBITMQ_MANAGEMENT_PORT` changes that port.
## Results, provenance, and troubleshooting
Record the CLI git revision, fixture dimensions/history, run ID, rate, duration,
concurrency, drain timeout, latency gate, host CPU/RAM/disk, and competing workloads
alongside each report. Record `docker compose images` and
`docker image inspect <image> --format '{{json .RepoDigests}}'` for resolved images;
the defaults are release **tags**, not a claim of immutable image identity or a
known build digest. For reproducible comparisons, retain the resolved digests and
set `LOADTEST_EVENT_IMAGE`, `LOADTEST_MONITOR_IMAGE`, and `LOADTEST_SEQUENCE_IMAGE`
to those immutable references. Pin `LOADTEST_MONGODB_IMAGE` and
`LOADTEST_RABBITMQ_IMAGE` to the resolved digests of MongoDB `8.0.13` and RabbitMQ
`4.1.4-management` as well. Override only with compatible images and record the
change.
From `deployment/loadtest`, inspect `docker compose ps -a` and
`docker compose logs --tail=100 mongo-init event monitor sequence`. If the CLI
reports missing consumers, fix worker startup before retrying. Do not bypass
readiness. `docker compose exec rabbitmq rabbitmqctl list_queues name consumers messages`
can confirm routing and backlog. The worker libraries also declare unrelated
analysis/dead-letter queues; their existence does not mean those workers run.
Do not treat a broker acknowledgement alone as persistence or pipeline completion.
## Explicit cleanup and persistent storage
From `cli/`, first preview and then explicitly remove this run's fixtures:
```sh
go run . load-test cleanup \
--database Kerberos --allow-default-names --run-id smoke
go run . load-test cleanup \
--database Kerberos --allow-default-names --run-id smoke \
--execute --confirm-test-target
```
Wait for a completed/drained run before cleanup. Run-scoped CLI cleanup does not
destroy the shared stage queues or this stack's volumes, and retains a manifest
tombstone so the cleaned run ID cannot be reused. Stop workers before a full reset
if a failed run left messages behind. Cleanup is available only for a finished,
passed run; it does not discard prepared, running,
or failed runs or release their retained active-run lock. The empty, unused
completion queue is retained to preserve late deliveries; it is removed only
when the disposable broker data is explicitly reset.
From `deployment/loadtest`:
```sh
# Stops and removes this stack; retains all database/broker volumes and reports.
docker compose down
# DESTRUCTIVE: only for this dedicated stack. Deletes ALL its persisted test data.
docker compose down --volumes
```
Named volumes persist across stop/start and ordinary `down`; RabbitMQ has a stable
hostname to preserve its on-disk node identity. Reports remain under `reports/`
even after volume reset. Do not prune global Docker resources or reuse these
commands against another Compose project.
+103
View File
@@ -0,0 +1,103 @@
name: uug-loadtest
x-worker-environment: &worker-environment
ENVIRONMENT: disposable-local-loadtest
LOG_LEVEL: error
RABBITMQ_HOST: rabbitmq:5672
RABBITMQ_EXCHANGE: ""
RABBITMQ_USERNAME: loadtest
# Public disposable test credentials, never production credentials.
RABBITMQ_PASSWORD: disposable-loadtest-only
MONGODB_URI: "mongodb://mongo:27017/Kerberos?replicaSet=loadtest-rs&readConcernLevel=majority&w=majority&journal=true&wtimeoutMS=10000"
MONGODB_DATABASE_CLOUD: Kerberos
MONGODB_RETRY_WRITES: "true"
OTEL_SDK_DISABLED: "true"
OTEL_EXPORTER_OTLP_ENDPOINT: ""
x-worker: &worker
networks:
- loadtest
restart: "no"
depends_on:
mongo-init:
condition: service_completed_successfully
rabbitmq:
condition: service_healthy
environment: *worker-environment
services:
mongo:
image: ${LOADTEST_MONGODB_IMAGE:-mongo:8.0.13}
command: ["mongod", "--replSet", "loadtest-rs", "--bind_ip_all", "--noauth"]
ports:
- "127.0.0.1:${LOADTEST_MONGODB_PORT:-27028}:27017"
networks:
- loadtest
volumes:
- mongo-data:/data/db
- mongo-config:/data/configdb
healthcheck:
test: ["CMD", "mongosh", "--quiet", "--eval", "quit(db.adminCommand({ping: 1}).ok ? 0 : 1)"]
interval: 5s
timeout: 5s
retries: 30
start_period: 10s
mongo-init:
image: ${LOADTEST_MONGODB_IMAGE:-mongo:8.0.13}
command:
- mongosh
- "mongodb://mongo:27017/admin?directConnection=true"
- --quiet
- --file
- /init/replica-set.js
networks:
- loadtest
volumes:
- ./replica-set.js:/init/replica-set.js:ro
restart: "no"
depends_on:
mongo:
condition: service_healthy
rabbitmq:
image: ${LOADTEST_RABBITMQ_IMAGE:-rabbitmq:4.1.4-management}
hostname: rabbitmq
environment:
RABBITMQ_DEFAULT_USER: loadtest
RABBITMQ_DEFAULT_PASS: disposable-loadtest-only
RABBITMQ_DEFAULT_VHOST: /
ports:
- "127.0.0.1:${LOADTEST_RABBITMQ_PORT:-5673}:5672"
- "127.0.0.1:${LOADTEST_RABBITMQ_MANAGEMENT_PORT:-15673}:15672"
networks:
- loadtest
volumes:
- rabbitmq-data:/var/lib/rabbitmq
healthcheck:
test: ["CMD-SHELL", "rabbitmq-diagnostics -q ping && rabbitmq-diagnostics -q check_port_connectivity && rabbitmq-diagnostics -q check_local_alarms"]
interval: 10s
timeout: 15s
retries: 30
start_period: 20s
event:
<<: *worker
image: ${LOADTEST_EVENT_IMAGE:-ghcr.io/uug-ai/hub-pipeline-event:v1.3.2}
monitor:
<<: *worker
image: ${LOADTEST_MONITOR_IMAGE:-ghcr.io/uug-ai/hub-pipeline-monitor:v1.3.19}
sequence:
<<: *worker
image: ${LOADTEST_SEQUENCE_IMAGE:-ghcr.io/uug-ai/hub-pipeline-sequence:v1.7.1}
networks:
loadtest:
internal: true
volumes:
mongo-data:
mongo-config:
rabbitmq-data:
+38
View File
@@ -0,0 +1,38 @@
const expected = {
_id: "loadtest-rs",
members: [{ _id: 0, host: "mongo:27017" }],
};
let config;
try {
config = rs.conf();
} catch (error) {
if (error.code !== 94) {
throw error;
}
}
if (config) {
if (
config._id !== expected._id ||
config.members.length !== 1 ||
config.members[0].host !== expected.members[0].host
) {
throw new Error("Refusing to reconfigure an unexpected replica set");
}
} else {
const result = rs.initiate(expected);
if (!result.ok) {
throw new Error(`Replica set initialization failed: ${JSON.stringify(result)}`);
}
}
for (let attempt = 0; attempt < 60; attempt++) {
if (db.adminCommand({ hello: 1 }).isWritablePrimary) {
print("Dedicated load-test replica set is writable");
quit(0);
}
sleep(1000);
}
throw new Error("Timed out waiting for the load-test primary");
+1 -1
View File
@@ -26,7 +26,7 @@ helmCharts:
releaseName: hub
namespace: kerberos-hub
repo: https://charts.kerberos.io
version: 0.170.0
version: 0.84.0
valuesFile: ../../base/hub/kerberos-hub-values.yaml
valuesInline:
mqtt:
+1 -1
View File
@@ -8,7 +8,7 @@ helmCharts:
releaseName: hub
namespace: kerberos-hub
repo: https://charts.kerberos.io
version: 0.170.0
version: 0.84.0
valuesFile: ../../base/hub/kerberos-hub-values.yaml
valuesInline:
kerberoshub:
-64
View File
@@ -1,64 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -gt 1 || ( $# -eq 1 && "$1" != --restart ) ]]; then
echo "Usage: bash scripts/check-garage.sh [--restart]" >&2
exit 1
fi
if ! command -v aws > /dev/null; then
echo "AWS CLI is required for the Garage S3 smoke test." >&2
exit 1
fi
temporary="$(mktemp -d)"
pid=""
cleanup() {
if [[ -n "$pid" ]]; then kill "$pid"; fi
rm -f "$temporary/upload" "$temporary/download" "$temporary/port-forward.log" "$temporary/aws-config"
rmdir "$temporary"
}
trap cleanup EXIT
access_key="$(kubectl -n garage get secret garage-credentials -o jsonpath='{.data.access-key}' | base64 --decode)"
secret_key="$(kubectl -n garage get secret garage-credentials -o jsonpath='{.data.secret-key}' | base64 --decode)"
if [[ "${GITHUB_ACTIONS:-false}" == true ]]; then
echo "::add-mask::$access_key"
echo "::add-mask::$secret_key"
fi
forward() {
kubectl -n garage port-forward service/garage 13900:3900 > "$temporary/port-forward.log" 2>&1 &
pid=$!
for attempt in {1..30}; do
if curl --silent --output /dev/null http://127.0.0.1:13900/; then return; fi
if ! kill -0 "$pid"; then break; fi
sleep 1
done
cat "$temporary/port-forward.log" >&2
echo "Garage port-forward did not become ready." >&2
exit 1
}
forward
export AWS_ACCESS_KEY_ID="$access_key" AWS_SECRET_ACCESS_KEY="$secret_key"
export AWS_DEFAULT_REGION=garage AWS_EC2_METADATA_DISABLED=true
export AWS_PAGER=""
unset AWS_SESSION_TOKEN AWS_PROFILE AWS_DEFAULT_PROFILE
export AWS_REQUEST_CHECKSUM_CALCULATION=when_required AWS_RESPONSE_CHECKSUM_VALIDATION=when_required
export AWS_CONFIG_FILE="$temporary/aws-config"
printf '[default]\ns3 =\n addressing_style = path\n' > "$AWS_CONFIG_FILE"
object="ci-smoke/$(openssl rand -hex 16)"
openssl rand -hex 128 > "$temporary/upload"
endpoint="http://127.0.0.1:13900"
aws --endpoint-url "$endpoint" s3api put-object \
--bucket mybucket --key "$object" --body "$temporary/upload"
if [[ $# -eq 1 ]]; then
kill "$pid"
wait "$pid" || [[ "$?" == 143 ]]
pid=""
kubectl -n garage rollout restart statefulset/garage
kubectl -n garage rollout status statefulset/garage --timeout=300s
forward
fi
aws --endpoint-url "$endpoint" s3api get-object \
--bucket mybucket --key "$object" "$temporary/download"
cmp "$temporary/upload" "$temporary/download"
echo "Garage S3 authenticated PUT/GET passed through the Kubernetes Service (object mybucket/$object retained)."
-44
View File
@@ -1,44 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CHART="${1:-kerberos/hub}"
assert_contains() {
if ! grep -Eq -- "$2" <<< "$1"; then
printf 'Missing expected deployment runtime setting: %s\n' "$2" >&2
exit 1
fi
}
nodeport="$(awk '
/^---/ { selected=0 }
/name: hub-frontend-nodeport$/ { selected=1 }
selected { print }
' "$ROOT/base/hub/kerberos-hub-nodeports.yaml")"
assert_contains "$nodeport" 'targetPort: http$'
assert_contains "$nodeport" 'nodePort: 32080'
rendered="$(helm template hub "$CHART" --values "$ROOT/base/hub/kerberos-hub-values.yaml" \
--set mode=ui --set kerberoshub.frontend.demoEnabled=true)"
if grep -q 'image: "docker.io/ghcr.io/' <<< "$rendered"; then
echo "Fully qualified GHCR image was prefixed with docker.io" >&2
exit 1
fi
for name in hub-frontend hub-frontend-demo; do
deployment="$(awk -v name="$name" '
function emit() { if (deployment && matched) printf "%s", block }
/^---$/ { emit(); block=""; deployment=0; matched=0 }
{ block=block $0 "\n" }
/^kind: Deployment$/ { deployment=1 }
$0 == " name: " name { matched=1 }
END { emit() }
' <<< "$rendered")"
for setting in 'containerPort: 8080' 'runAsNonRoot: true' 'runAsUser: 65532' \
'readOnlyRootFilesystem: true' 'mountPath: /tmp' 'name: frontend-tmp'; do
assert_contains "$deployment" "^[[:space:]]*(- )?$setting$"
done
assert_contains "$deployment" 'image: "ghcr.io/uug-ai/hub-frontend:v1.19.28"$'
echo "OK: $name uses the promoted image and matching hardened runtime"
done
echo "Deployment Hub runtime preflight passed."
-19
View File
@@ -1,19 +0,0 @@
#!/usr/bin/env bash
set -uo pipefail
kubectl get pods,pvc -A -o wide
kubectl get events -A --sort-by=.lastTimestamp
for namespace in garage mongodb rabbitmq vernemq kerberos-vault kerberos-agent kerberos-hub; do
if ! pods="$(kubectl -n "$namespace" get pods -o name)"; then
echo "::warning::Could not list pods in $namespace."
continue
fi
for pod in $pods; do
if ! kubectl -n "$namespace" describe "$pod"; then
echo "::warning::Could not describe $namespace/$pod."
fi
if ! kubectl -n "$namespace" logs "$pod" --all-containers=true --tail=80 --prefix; then
echo "::warning::Logs unavailable for $namespace/$pod."
fi
done
done
-39
View File
@@ -1,39 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
if [[ $# -gt 1 || ( $# -eq 1 && "$1" != --prepare-only ) ]]; then
echo "Usage: bash scripts/install-garage.sh [--prepare-only]" >&2
exit 1
fi
kubectl apply -f "$ROOT/base/garage/namespace.yaml"
secret="$(kubectl -n garage get secret garage-credentials --ignore-not-found -o name)"
if [[ -z "$secret" ]]; then
pvc="$(kubectl -n garage get pvc data-garage-0 --ignore-not-found -o name)"
if [[ -n "$pvc" ]]; then
echo "Garage data already exists without its credentials. Restore garage-credentials from backup; refusing to generate replacement keys." >&2
exit 1
fi
kubectl -n garage create secret generic garage-credentials \
--from-literal="rpc-secret=$(openssl rand -hex 32)" \
--from-literal="access-key=GK$(openssl rand -hex 16)" \
--from-literal="secret-key=$(openssl rand -hex 32)"
fi
if ! kubectl -n garage get secret garage-credentials -o json |
jq -e '.data |
(."rpc-secret" | @base64d | test("^[0-9a-f]{64}$")) and
(."access-key" | @base64d | test("^GK[0-9a-f]{32}$")) and
(."secret-key" | @base64d | test("^[0-9a-f]{64}$"))' > /dev/null; then
echo "Garage credentials are missing or invalid; restore a valid garage-credentials Secret." >&2
exit 1
fi
if [[ $# -eq 1 ]]; then
echo "Garage credentials are ready; apply your Kustomize overlay next."
exit 0
fi
kubectl apply -k "$ROOT/base/garage"
kubectl -n garage rollout status statefulset/garage --timeout=300s
kubectl -n garage exec garage-0 -- /garage bucket info mybucket > /dev/null
echo "Garage is available at garage.garage:3900 (region garage, bucket mybucket). Credentials are in Secret garage/garage-credentials."
-16
View File
@@ -1,16 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# != 1 ]]; then
echo "Usage: bash scripts/notify-slack.sh MESSAGE" >&2
exit 1
fi
if [[ -z "${SLACK_WEBHOOK_URL:-}" ]]; then
echo "::warning::SLACK_WEBHOOK_URL is not configured; skipping optional Slack notification."
exit 0
fi
jq -n --arg text "$1" '{text: $text}' |
curl --fail-with-body --silent --show-error --retry 3 \
-H 'Content-Type: application/json' --data-binary @- "$SLACK_WEBHOOK_URL"