Adds StatefulSet support (#549)

* Adds StatefulSet support

# Conflicts:
#	helm/opengist/templates/pvc.yaml

* Adds statefulset support for replicaCount gt 1

* Improves the setup of multiple replicas in a stateful set

* Adds config wrangling logic to the secret template

* Adds shared PV functionality

* Adds missing pvc-shared template

* Adds stateful set and documentation

---------

Co-authored-by: Guillem Riera <guillem@rieragalm.es>
This commit is contained in:
Guillem Riera Galmés
2026-01-21 02:22:44 +01:00
committed by GitHub
parent 03420e4f91
commit 4ae25144a0
8 changed files with 746 additions and 11 deletions

View File

@@ -1,6 +1,6 @@
# Opengist Helm Chart
![Version: 0.2.0](https://img.shields.io/badge/Version-0.2.0-informational?style=flat-square) ![AppVersion: 1.11.1](https://img.shields.io/badge/AppVersion-1.11.1-informational?style=flat-square)
![Version: 0.5.0](https://img.shields.io/badge/Version-0.5.0-informational?style=flat-square) ![AppVersion: 1.11.1](https://img.shields.io/badge/AppVersion-1.11.1-informational?style=flat-square)
Opengist Helm chart for Kubernetes.
@@ -66,6 +66,40 @@ index.meili.api-key: MASTER_KEY # generated by Meilisearch
If you want to use the `bleve` indexer, you need to set the `replicas` to `1`.
#### Passing Meilisearch configuration via nested Helm values
When using the Helm CLI with `--set`, avoid mixing a scalar `config.index` value with nested `config.index.meili.*` keys. Instead use a nested map and a `type` field which the chart flattens automatically. Example:
```bash
helm template opengist ./helm/opengist \
--set statefulSet.enabled=true \
--set replicaCount=2 \
--set persistence.enabled=true \
--set persistence.existingClaim=opengist-shared-rwx \
--set postgresql.enabled=false \
--set config.db-uri="postgres://user:pass@db-host:5432/opengist" \
--set meilisearch.enabled=true \
--set config.index.type=meilisearch \
--set config.index.meili.host="http://opengist-meilisearch:7700" \
--set config.index.meili.api-key="MASTER_KEY"
```
Rendered `config.yml` fragment:
```yaml
index: meilisearch
index.meili.host: http://opengist-meilisearch:7700
index.meili.api-key: MASTER_KEY
```
How it works:
* You provide a map under `config.index` with keys `type` and `meili`.
* The template detects `config.index.type` and rewrites `index: <type>`.
* Nested `config.index.meili.host` / `api-key` are lifted to flat keys `index.meili.host` and `index.meili.api-key` required by Opengist.
If you set `--set config.index=meilisearch` directly and also try to set `--set config.index.meili.host=...`, Helm will first create the nested structure then overwrite it with the scalar, losing the host. Always prefer the `config.index.type` pattern for CLI usage.
### PostgreSQL Database
By default, Opengist uses the `sqlite` database. If needed, this chart also deploys a PostgreSQL instance.
@@ -79,3 +113,268 @@ Then define the connection string in your Opengist config:
db-uri: postgres://user:password@opengist-postgresql:5432/opengist
```
Note: `opengist-postgresql` is the name of the K8S Service deployed by this chart.
### Database Configuration
You can supply an externally managed database connection explicitly via `config.db-uri` (PostgreSQL/MySQL) or enable the bundled PostgreSQL subchart.
Behavior:
* If `postgresql.enabled: true` and `config.db-uri` is omitted, the chart auto-generates:
`postgres://<username>:<password>@<release-name>-postgresql:<port>/<database>` using values under `postgresql.global.postgresql.auth.*`.
* If any of username/password/database are missing, templating fails fast with an error message.
* If you prefer an external database or a different Postgres distribution, set `postgresql.enabled: false` and provide `config.db-uri` yourself.
**Licensing note**: Bitnami's PostgreSQL distribution may have licensing constraints. For strictly open alternatives use an external managed PostgreSQL/MySQL service and disable the subchart.
### Multi-Replica Requirements
Running more than one Opengist replica (Deployment or StatefulSet) requires:
1. Non-SQLite database (`config.db-uri` must start with `postgres://` or `mysql://`).
2. Shared RWX storage if using StatefulSet with `replicaCount > 1` (provide `persistence.existingClaim`). The chart now fails fast if you attempt `replicaCount > 1` without an explicit shared claim to prevent silent data divergence across perpod PVCs.
The chart will fail fast during templating if these conditions are not met when scaling above 1 replica.
Examples:
* External PostgreSQL:
```yaml
postgresql:
enabled: false
config:
db-uri: postgres://user:pass@db-host:5432/opengist
index: meilisearch
statefulSet:
enabled: true
replicaCount: 2
persistence:
existingClaim: opengist-shared-rwx
```
Bundled PostgreSQL (auto db-uri):
```yaml
postgresql:
enabled: true
config:
index: meilisearch
statefulSet:
enabled: true
replicaCount: 2
persistence:
existingClaim: opengist-shared-rwx
```
#### Recovering from an initial misconfiguration
If you previously scaled a StatefulSet above 1 replica **without** an `existingClaim`, each pod received its own PVC and only one held the authoritative `/opengist` data. To consolidate:
1. Scale down to 1 replica (keep the pod with the desired data):
```bash
kubectl scale sts/opengist --replicas=1
```
1. (Optional) Inspect other PVCs and manually copy any missing files by temporarily attaching them to a debug pod.
1. Create or provision a ReadWriteMany (NFS / CephFS / Longhorn RWX / etc.) PersistentVolumeClaim named (for example) `opengist-shared-rwx`.
1. Update values with `persistence.existingClaim: opengist-shared-rwx` and redeploy.
1. Scale back up:
```bash
kubectl scale sts/opengist --replicas=2
```
Going forward, all replicas mount the same shared volume and data remains consistent.
### Quick Start Examples
Common deployment scenarios with copy-paste configurations:
#### Scenario 1: Single replica with SQLite (default)
Minimal local development setup with ephemeral or persistent storage:
```yaml
# Ephemeral (emptyDir)
statefulSet:
enabled: true
replicaCount: 1
persistence:
enabled: false
# OR with persistent RWO storage
statefulSet:
enabled: true
replicaCount: 1
persistence:
enabled: true
mode: perReplica # default
```
#### Scenario 2: Multi-replica with external PostgreSQL + existing RWX PVC
Production HA setup with your own database and storage:
```yaml
statefulSet:
enabled: true
replicaCount: 2
postgresql:
enabled: false
config:
db-uri: "postgres://user:pass@db-host:5432/opengist"
index: meilisearch # required for multi-replica
persistence:
enabled: true
mode: shared
existingClaim: "opengist-shared-rwx" # pre-created RWX PVC
meilisearch:
enabled: true
```
#### Scenario 3: Multi-replica with bundled PostgreSQL + auto-created RWX PVC
Chart manages both database and storage:
```yaml
statefulSet:
enabled: true
replicaCount: 2
postgresql:
enabled: true
global:
postgresql:
auth:
username: opengist
password: changeme
database: opengist
config:
index: meilisearch
persistence:
enabled: true
mode: shared
existingClaim: "" # empty to trigger auto-creation
create:
enabled: true
accessModes: [ReadWriteMany]
storageClass: "nfs-client" # your RWX-capable storage class
size: 20Gi
meilisearch:
enabled: true
```
### Persistence Modes
The chart supports two persistence strategies controlled by `persistence.mode`:
| Mode | Behavior | Scaling | Storage Objects | Recommended Use |
|-------------|----------|---------|-----------------|-----------------|
| `perReplica` (default) | One PVC per pod via StatefulSet `volumeClaimTemplates` (RWO) when no `existingClaim` | Safe only at `replicaCount=1` unless you supply `existingClaim` | One PVC per replica | Local dev, quick single-node trials |
| `shared` | Single RWX PVC (existing or auto-created) mounted by all pods | Horizontally scalable | One shared PVC | Production / HA |
Configuration examples:
Per-replica (single node):
```yaml
statefulSet:
enabled: true
persistence:
mode: perReplica
enabled: true
accessModes:
- ReadWriteOnce
```
Shared (scale ready) with an existing RWX claim:
```yaml
statefulSet:
enabled: true
replicaCount: 2
persistence:
mode: shared
existingClaim: opengist-shared-rwx
```
Shared with chart-created RWX PVC:
```yaml
statefulSet:
enabled: true
replicaCount: 2
persistence:
mode: shared
existingClaim: "" # leave empty
create:
enabled: true
accessModes: [ReadWriteMany]
size: 10Gi
```
When `mode=shared` and `existingClaim` is empty, the chart creates a single PVC named `<release>-shared` (suffix configurable via `persistence.create.nameSuffix`).
Fail-fast conditions:
* `replicaCount>1` & missing external DB (still enforced).
* `replicaCount>1` & persistence disabled.
* `replicaCount>1` & neither `existingClaim` nor `mode=shared`.
* `mode=shared` & create.enabled=true but `accessModes` lacks `ReadWriteMany`.
Migration (perReplica → shared): scale down to 1, create RWX claim (or rely on create.enabled), copy data, switch mode to shared, scale up.
### Troubleshooting
#### Common Errors and Solutions
##### Error: "replicaCount=2 requires PostgreSQL/MySQL config.db-uri; scheme 'sqlite' unsupported"
* **Cause**: Multi-replica with SQLite database
* **Solution**: Either scale down to `replicaCount: 1` or configure external database:
```yaml
config:
db-uri: "postgres://user:pass@host:5432/opengist"
```
##### Error: "replicaCount=2 requires either persistence.existingClaim OR persistence.mode=shared"
* **Cause**: Multi-replica without shared storage
* **Solution**: Choose one approach:
```yaml
# Option A: Use existing PVC
persistence:
existingClaim: "my-rwx-pvc"
# Option B: Let chart create PVC
persistence:
mode: shared
create:
enabled: true
accessModes: [ReadWriteMany]
```
##### Error: "persistence.mode=shared create.accessModes must include ReadWriteMany for multi-replica"
* **Cause**: Chart-created PVC lacks RWX access mode
* **Solution**: Ensure RWX is specified:
```yaml
persistence:
create:
accessModes:
- ReadWriteMany
```
##### Pods mount different data (data divergence)
* **Cause**: Previously scaled with `perReplica` mode and `replicaCount > 1`
* **Solution**: Follow recovery steps in "Recovering from an initial misconfiguration" section above
##### PVC creation fails: "no storage class available with ReadWriteMany"
* **Cause**: Cluster lacks RWX-capable storage provisioner
* **Solution**: Install a storage provider (NFS, CephFS, Longhorn) or use external managed storage and provide `existingClaim`