Skip to content
This repository was archived by the owner on Mar 3, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
49 changes: 49 additions & 0 deletions .github/Gthulhu - multi-node management.md

Large diffs are not rendered by default.

88 changes: 88 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Helm Charts for Gthulhu deployment


## Usage

The Gthulhu requires prometheus and grafana to be installed in your kubernetes cluster.

```bash
$ helm install kube-prometheus-stack kube-prometheus-stack
```

To deploy Gthulhu using Helm charts, follow these steps:
```bash
$ helm install gthulhu gthulhu -f ./gthulhu/values-production.yaml
```

To uninstall Gthulhu, run the following command:
```bash
$ helm uninstall gthulhu
```

## Testing

### API Manager

To access the Gthulhu API, you can set up port forwarding using kubectl:
```bash
$ kubectl port-forward svc/gthulhu-manager 8080:8080
```

After deploying Gthulhu, you can test the API by sending a login request using curl:
```bash
$ curl -X POST http://localhost:8080/api/v1/auth/login -H "Content-Type: application/json" -d '{
"username": "admin@example.com",
"password": "your-password-here"
}'
{"success":true,"data":{"token":"<TOKEN>"},"timestamp":"2025-12-30T13:09:10Z"}
```

Then, you can create a new strategy by sending another curl request with the obtained token:

```bash
$ curl -X POST http://localhost:8080/api/v1/strategies \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <TOKEN>" \
-d '{
"strategyNamespace": "default",
"labelSelectors": [
{"key": "app.kubernetes.io/name", "value": "prometheus"}
],
"k8sNamespace": ["default"],
"priority": 10,
"executionTime": 20000000
}'
```

You can also retrieve your own strategies using the following curl command:

```bash
$ curl http://localhost:8080/api/v1/strategies/self \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <TOKEN>"
```

### List all intents from decision maker

```bash
$ curl -X POST http://127.0.0.1:8080/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{
"public_key": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAny28YMC2/+yYj3T29lz6\n0uryNz8gNVrqD7lTJuHQ3DMTE6ADqnERy8VgHve0tWzhJc5ZBZ1Hduvj+z/kNqbc\nU81YGhmfOrQ3iFNYBlSAseIHdAw39HGyC6OKzTXI4HRpc8CwcF6hKExkyWlkALr5\ni+IQDfimvarjjZ6Nm368L0Rthv3KOkI5CqRZ6bsVwwBug7GcdkvFs3LiRSKlMBpH\n2tCkZ5ZZE8VyuK7VnlwV7n6EHzN5BqaHq8HVLw2KzvibSi+/5wIZV2Yx33tViLbh\nOsZqLt6qQCGGgKzNX4TGwRLGAiVV1NCpgQhimZ4YP2thqSsqbaISOuvFlYq+QGP1\nbcvcHB7UhT1ZnHSDYcbT2qiD3VoqytXVKLB1X5XCD99YLSP9B32f1lvZD4MhDtE4\nIhAuqn15MGB5ct4yj/uMldFScs9KhqnWcwS4K6Qx3IfdB+ZxT5hEOWJLEcGqe/CS\nXITNG7oS9mrSAJJvHSLz++4R/Sh1MnT2YWjyDk6qeeqAwut0w5iDKWt7qsGEcHFP\nIVVlos+xLfrPDtgHQk8upjslUcMyMDTf21Y3RdJ3k1gTR9KHEwzKeiNlLjen9ekF\nWupF8jik1aYRWL6h54ZyGxwKEyMYi9o18G2pXPzvVaPYtU+TGXdO4QwiES72TNCD\nbNaGj75Gj0sN+LfjjQ4A898CAwEAAQ==\n-----END PUBLIC KEY-----",
"client_id": "gthulhu-scheduler",
"expired_at": 1736899200
}'

$ curl 127.0.0.1:8080/api/v1/scheduling/strategies -H "Content-Type: application/json" \
-H "Authorization: Bearer <TOKEN>"
```

### View Scheduler Sidecar (decision maker) Logs

```bash
$ kubectl logs gthulhu-scheduler-hqflq -c scheduler-sidecar
```

## Licence

This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details.
7 changes: 7 additions & 0 deletions gthulhu/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,10 @@ keywords:
home: https://github.com/Gthulhu/Gthulhu
sources:
- https://github.com/Gthulhu/Gthulhu

# Dependencies - MongoDB as a subchart
dependencies:
- name: mongodb
version: "0.1.0"
repository: "file://charts/mongodb"
condition: mongodb.enabled
16 changes: 16 additions & 0 deletions gthulhu/charts/mongodb/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: v2
name: mongodb
description: A Helm chart for MongoDB with replica set support
type: application
version: 0.1.0
appVersion: "8.2.2"

maintainers:
- name: Gthulhu Team
email: maintainers@gthulhu.io

keywords:
- mongodb
- database
- nosql
- replica-set
8 changes: 8 additions & 0 deletions gthulhu/charts/mongodb/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{{- if .Values.enabled }}
** MongoDB **
MongoDB is running as a StatefulSet for data persistence.

You can check the MongoDB status with:
kubectl get statefulset {{ include "mongodb.fullname" . }} -n {{ .Release.Namespace }}
kubectl logs -l app.kubernetes.io/component=mongodb -n {{ .Release.Namespace }}
{{- end }}
69 changes: 69 additions & 0 deletions gthulhu/charts/mongodb/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "mongodb.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "mongodb.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "mongodb.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "mongodb.labels" -}}
helm.sh/chart: {{ include "mongodb.chart" . }}
{{ include "mongodb.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "mongodb.selectorLabels" -}}
app.kubernetes.io/name: {{ include "mongodb.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
MongoDB host for connection string
*/}}
{{- define "mongodb.host" -}}
{{ include "mongodb.fullname" . }}-0.{{ include "mongodb.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local
{{- end }}

{{/*
MongoDB auth secret name
*/}}
{{- define "mongodb.authSecretName" -}}
{{- if .Values.auth.existingSecret }}
{{- .Values.auth.existingSecret }}
{{- else }}
{{- include "mongodb.fullname" . }}-auth
{{- end }}
{{- end }}
133 changes: 133 additions & 0 deletions gthulhu/charts/mongodb/templates/job-init.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
{{- /*
MongoDB initialization is now handled by the StatefulSet's postStart lifecycle hook.
This Job is disabled to avoid race conditions.
The postStart hook runs directly in the MongoDB container, ensuring:
1. The mongod process is ready before initialization
2. No network latency or DNS resolution issues
3. Proper localhost exception for initial user creation
*/ -}}
{{- if false }}
# MongoDB Initialization Job
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "mongodb.fullname" . }}-init
labels:
{{- include "mongodb.labels" . | nindent 4 }}
app.kubernetes.io/component: mongodb-init
annotations:
"helm.sh/hook": post-install,post-upgrade
"helm.sh/hook-weight": "0"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
backoffLimit: 10
template:
metadata:
labels:
{{- include "mongodb.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: mongodb-init
spec:
restartPolicy: OnFailure
containers:
- name: mongodb-init
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command:
- /bin/bash
- -c
- |
set -e

MONGO_HOST="{{ include "mongodb.fullname" . }}-0.{{ include "mongodb.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:27017"

echo "[init] Waiting for MongoDB to be ready..."
until mongosh --host "$MONGO_HOST" --quiet --eval 'db.adminCommand({ ping: 1 })' 2>/dev/null; do
echo "[init] MongoDB not ready, retrying in 5 seconds..."
sleep 5
done

echo "[init] MongoDB is ready. Checking replica set status..."

# Check if replica set is already initialized (try without auth first, then with auth)
RS_STATUS=$(mongosh --host "$MONGO_HOST" --quiet --eval 'try { rs.status().ok } catch(e) { 0 }' 2>/dev/null || echo "0")

if [ "$RS_STATUS" = "1" ]; then
echo "[init] Replica set already initialized (no auth needed or localhost exception)"
else
# Try with authentication
RS_STATUS_AUTH=$(mongosh --host "$MONGO_HOST" -u "$MONGO_ROOT_USERNAME" -p "$MONGO_ROOT_PASSWORD" --authenticationDatabase admin --quiet --eval 'try { rs.status().ok } catch(e) { 0 }' 2>/dev/null || echo "0")
if [ "$RS_STATUS_AUTH" = "1" ]; then
echo "[init] Replica set already initialized (verified with auth)"
else
echo "[init] Initializing replica set..."
# MongoDB allows localhost exception for first user creation and rs.initiate
mongosh --host "$MONGO_HOST" --quiet --eval '
rs.initiate({
_id: "{{ .Values.replicaSet.name }}",
members: [
{ _id: 0, host: "{{ include "mongodb.fullname" . }}-0.{{ include "mongodb.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:27017" }
]
});
' 2>/dev/null || echo "[init] rs.initiate might have already been done"
echo "[init] Replica set initiation attempted"
fi
fi

echo "[init] Waiting for primary election..."
for i in $(seq 1 30); do
IS_PRIMARY=$(mongosh --host "$MONGO_HOST" --quiet --eval 'db.hello().isWritablePrimary' 2>/dev/null || echo "false")
if [ "$IS_PRIMARY" = "true" ]; then
echo "[init] Primary is ready"
break
fi
# Try with auth
IS_PRIMARY_AUTH=$(mongosh --host "$MONGO_HOST" -u "$MONGO_ROOT_USERNAME" -p "$MONGO_ROOT_PASSWORD" --authenticationDatabase admin --quiet --eval 'db.hello().isWritablePrimary' 2>/dev/null || echo "false")
if [ "$IS_PRIMARY_AUTH" = "true" ]; then
echo "[init] Primary is ready (verified with auth)"
break
fi
echo "[init] Waiting for primary... ($i/30)"
sleep 2
done

echo "[init] Checking/creating admin user..."
# First try without auth (localhost exception)
mongosh --host "$MONGO_HOST" --quiet --eval '
db = db.getSiblingDB("admin");
try {
const users = db.getUsers();
const adminExists = users.users && users.users.some(u => u.user === "'"$MONGO_ROOT_USERNAME"'");
if (adminExists) {
print("Admin user already exists");
} else {
db.createUser({
user: "'"$MONGO_ROOT_USERNAME"'",
pwd: "'"$MONGO_ROOT_PASSWORD"'",
roles: [{ role: "root", db: "admin" }]
});
print("Admin user created");
}
} catch (e) {
print("Auth required, checking with credentials...");
}
' 2>/dev/null || true

# Verify with auth
mongosh --host "$MONGO_HOST" -u "$MONGO_ROOT_USERNAME" -p "$MONGO_ROOT_PASSWORD" --authenticationDatabase admin --quiet --eval '
db = db.getSiblingDB("admin");
print("Successfully authenticated as admin user");
' && echo "[init] MongoDB initialization complete!" || echo "[init] Warning: Could not verify admin authentication"

echo "[init] MongoDB initialization complete!"
env:
- name: MONGO_ROOT_USERNAME
valueFrom:
secretKeyRef:
name: {{ include "mongodb.authSecretName" . }}
key: MONGO_INITDB_ROOT_USERNAME
- name: MONGO_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "mongodb.authSecretName" . }}
key: MONGO_INITDB_ROOT_PASSWORD
{{- end }}
16 changes: 16 additions & 0 deletions gthulhu/charts/mongodb/templates/secret-auth.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{{- if .Values.enabled }}
{{- if not .Values.auth.existingSecret }}
# MongoDB Auth Secret
apiVersion: v1
kind: Secret
metadata:
name: {{ include "mongodb.fullname" . }}-auth
labels:
{{- include "mongodb.labels" . | nindent 4 }}
app.kubernetes.io/component: mongodb
type: Opaque
stringData:
MONGO_INITDB_ROOT_USERNAME: {{ .Values.auth.rootUsername }}
MONGO_INITDB_ROOT_PASSWORD: {{ .Values.auth.rootPassword }}
{{- end }}
{{- end }}
29 changes: 29 additions & 0 deletions gthulhu/charts/mongodb/templates/secret-keyfile.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{{- if .Values.enabled }}
# MongoDB Keyfile Secret
apiVersion: v1
kind: Secret
metadata:
name: {{ include "mongodb.fullname" . }}-keyfile
labels:
{{- include "mongodb.labels" . | nindent 4 }}
app.kubernetes.io/component: mongodb
type: Opaque
stringData:
keyfile: |
4i7woinZw/LD3KQCXr6Kqrd1UOHLHVLkbPkzGbCSLLrSRu059nUXo4J9epVbwYB4
xVAwBa4VawfpUXmcqoeXPcEXtfWamkhoNxPL6jBkrzc6nYIDSh/i8vlzG0OO834i
C60pwBNA0j1C0kjUG8xUgLV/rFoHYVDHNQVhgxuWSMwFcQH+QHERTz+R7DUY7GYj
/Y8ky2bmYuuyyXj7nyOpHerwDy95vmwInJFddJLw385hjc2bkHJTgBDDxXMagwgB
Kxf4LOOTcYoB6dabkjW0Y7DrXHo8FkIQQQKfxb/HjTjS3EXiJW1aPOdSAbiTmatP
qjuwPW9t8HX+8Q3WrseGBpSRbX8221xV6fcHRoSgDe2W6XL8kMegOgjDccJn5Z/0
dniQ70vEeefaCDizRu6hm117a5feiqgOrqBteHR7llJ4Viz/QmMw4riby8c2N4ie
wWLanKk2qzCUXciefKy3IEgVqJE1yOgaKm805YA15ROvr68Nbke4A7cQVwK+bsRj
Ftj/Ts8uGr/pnKL4y1DVMRaNUldnJJgoyJEUspad9JW+iIDsn3G3giF1KdJjduP9
xiGNyw4WQi75gETiFGJTEBDiZ500UibJ/P3AJTVIzC4lquybcYCi9vIinC5rj82q
SbhiS66P05fhHgvvbtEsVoBZ+oRiX9LGIFCx0/2iLfaum/6xR6mwCLtNVcuJCaGc
zwlEMDa7ihYOGnaErg9gpB6qfbzq3OlHleHEeEpNlr6d5vO6+QoP+czvqkWvqgSG
FjpfgVGOi2T8ckgQqTl+VMTcBVfFlxQu/lqFuxnHpmxf0IgVikpl2021FOtAil8G
51/vYMb2qYpmkrxaYTN6fVmEH1mbKcxEMelCMg1f08bxY14HlHg/RU7Z3e4X78bf
JQOxvVmdzFSoCP7yekaoOYqV9ZaDQb5cWlR4sF41hB2mjunFQ4YP6OoiHg8/EJu5
YbZ4byJMSn0Cphwx9CdQQHXOBSKRPC+8KLcSyPHxUM2B6hyD
{{- end }}
20 changes: 20 additions & 0 deletions gthulhu/charts/mongodb/templates/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{{- if .Values.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "mongodb.fullname" . }}
labels:
{{- include "mongodb.labels" . | nindent 4 }}
app.kubernetes.io/component: mongodb
spec:
# Headless service for StatefulSet
clusterIP: None
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort }}
protocol: TCP
name: mongo
selector:
{{- include "mongodb.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: mongodb
{{- end }}
Loading