Skip to content
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,12 @@ jobs:

integration:
uses: ./.github/workflows/integration-reusable.yml
# TEMPORARY (revert at 26.7.1 release): the chart's appVersion is bumped to
# 26.7.1 ahead of the image publish, so the default image.tag=appVersion
# cannot be pulled yet. Test against the rolling `latest` tag (== 26.7.1
# content) until arcadedata/arcadedb:26.7.1 is published, then delete this
# `with:` block so the PR integration job tests the pinned appVersion again
# (see commit f3db108 and PR #12).
with:
imageTag: latest
pullPolicy: Always
55 changes: 54 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,46 @@ helm install my-arcadedb arcadedb/arcadedb

See [charts/arcadedb/README.md](charts/arcadedb/README.md) and [charts/arcadedb/values.yaml](charts/arcadedb/values.yaml) for all available options.

## Observability

ArcadeDB 26.7.1+ exposes opt-in, behavior-preserving observability. All knobs
default off.

**Prometheus scraping (Operator):**

```bash
helm install my-arcadedb arcadedb/arcadedb \
--set arcadedb.plugins.prometheus.enabled=true \
--set arcadedb.plugins.prometheus.requireAuthentication=false \
--set observability.metrics.prometheus.serviceMonitor.enabled=true \
--set observability.metrics.prometheus.serviceMonitor.labels.release=kube-prometheus-stack
```

For non-Operator Prometheus, use annotation discovery instead:
`--set observability.metrics.prometheus.podAnnotations.enabled=true`.

**OTLP metrics export** (alongside /prometheus) — append to the install/upgrade command:

```bash
--set observability.metrics.otlp.enabled=true \
--set observability.metrics.otlp.endpoint=http://otel-collector:4317
```

**Distributed tracing** — append to the install/upgrade command:

```bash
--set observability.tracing.enabled=true \
--set observability.tracing.endpoint=http://otel-collector:4317 \
--set observability.tracing.samplingRate=0.1
```

**Structured JSON logging:** `--set observability.logging.format=json`

The liveness probe uses the dependency-free `/api/v1/health` endpoint;
readiness stays on `/api/v1/ready`. Set
`observability.health.readinessRequiresHA=true` to gate readiness on Raft
membership in HA clusters.

## Development

Run checks locally:
Expand Down Expand Up @@ -47,9 +87,22 @@ When a new ArcadeDB version is released:
2. Update the pinned image literal in `charts/arcadedb/tests/statefulset_test.yaml`
to the new version, or `helm-unittest` will fail (it cannot reference
`Chart.AppVersion` in an assertion).
3. The latest-image guard needs no change — it keeps watching the next cycle's
3. If `appVersion` was bumped **ahead of** the matching image being published
(so a feature can ship as soon as the image lands), the PR integration job —
which installs with `image.tag=appVersion` — cannot pull the image and will
time out. As a stopgap, the integration job in `.github/workflows/lint.yml`
carries a temporary `with: { imageTag: latest, pullPolicy: Always }` override
(`latest` tracks the upcoming release). Once the pinned image is published,
**remove that override** so the PR job tests the pinned `appVersion` again.
4. The latest-image guard needs no change — it keeps watching the next cycle's
rolling image.

> **Pending — 26.7.1:** the chart is already at `appVersion: 26.7.1` (the
> observability feature shipped ahead of the image), and the integration job is
> temporarily pinned to `latest` per step 3. When `arcadedata/arcadedb:26.7.1`
> is published, remove the `with:` override in `.github/workflows/lint.yml`,
> re-run CI, and delete this note. Steps 1–2 are already done for this release.

## Release

New chart versions are published via the GitHub Actions Release workflow:
Expand Down
4 changes: 2 additions & 2 deletions charts/arcadedb/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ description: |

type: application

version: 26.6.1
version: 26.7.1

appVersion: "26.6.1"
appVersion: "26.7.1"
annotations:
artifacthub.io/repositoryID: "fb85acb7-fb5b-4572-b44b-374a2b52658d"
49 changes: 48 additions & 1 deletion charts/arcadedb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ The command removes all the Kubernetes components associated with the chart and

| Name | Description | Value |
|------------------------------|-------------|-----------------|
| `livenessProbe.httpGet.path` | | `/api/v1/ready` |
| `livenessProbe.httpGet.path` | | `/api/v1/health` |
| `livenessProbe.httpGet.port` | | `http` |

### This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
Expand Down Expand Up @@ -172,6 +172,53 @@ The command removes all the Kubernetes components associated with the chart and
| `affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight` | | `100` |
| `extraManifests` | - Include any amount of extra arbitrary manifests | `{}` |

### observability

Opt-in, behavior-preserving observability (ArcadeDB 26.7.1+). Every knob below defaults off; existing deployments are unchanged.

### observability.metrics

| Name | Description | Value |
|-----------------------------------------------------------------------|----------------------------------------------------------|--------------------------|
| `observability.metrics.prometheus.serviceMonitor.enabled` | Create a Prometheus Operator ServiceMonitor | `false` |
| `observability.metrics.prometheus.serviceMonitor.interval` | Scrape interval | `30s` |
| `observability.metrics.prometheus.serviceMonitor.scrapeTimeout` | Scrape timeout (empty = Prometheus default) | `""` |
| `observability.metrics.prometheus.serviceMonitor.path` | Metrics path | `/prometheus` |
| `observability.metrics.prometheus.serviceMonitor.labels` | Extra labels (e.g. release: kube-prometheus-stack) | `{}` |
| `observability.metrics.prometheus.serviceMonitor.annotations` | Extra annotations | `{}` |
| `observability.metrics.prometheus.serviceMonitor.relabelings` | Prometheus relabelings | `[]` |
| `observability.metrics.prometheus.serviceMonitor.metricRelabelings` | Prometheus metric relabelings | `[]` |
| `observability.metrics.prometheus.serviceMonitor.basicAuth.enabled` | Scrape with basic auth | `false` |
| `observability.metrics.prometheus.serviceMonitor.basicAuth.secretName` | Secret with scrape credentials (username + password keys) | `""` |
| `observability.metrics.prometheus.serviceMonitor.basicAuth.usernameKey` | Secret key holding the username | `username` |
| `observability.metrics.prometheus.serviceMonitor.basicAuth.passwordKey` | Secret key holding the password | `password` |
| `observability.metrics.prometheus.podAnnotations.enabled` | Add prometheus.io/* scrape annotations to pods | `false` |
| `observability.metrics.prometheus.podAnnotations.path` | Scrape path annotation value | `/prometheus` |
| `observability.metrics.prometheus.podAnnotations.port` | Scrape port (empty = service.http.port) | `""` |
| `observability.metrics.otlp.enabled` | Enable the OTLP metrics registry | `false` |
| `observability.metrics.otlp.endpoint` | OTLP/gRPC metrics endpoint | `http://localhost:4317` |

### observability.tracing

| Name | Description | Value |
|-----------------------------------|--------------------------------------|-------------------------|
| `observability.tracing.enabled` | Enable distributed tracing | `false` |
| `observability.tracing.endpoint` | OTLP/gRPC trace endpoint | `http://localhost:4317` |
| `observability.tracing.samplingRate` | Parent-based sampling ratio [0.0, 1.0] | `0.0` |

### observability.logging

| Name | Description | Value |
|-------------------------------------|----------------------------------------------------------|--------|
| `observability.logging.format` | Log format: text or json | `text` |
| `observability.logging.includeTrace` | Append [traceId=…] to text logs while a trace is active | `false` |

### observability.health

| Name | Description | Value |
|--------------------------------------------|---------------------------------------------------|---------|
| `observability.health.readinessRequiresHA` | /api/v1/ready waits for Raft join on HA clusters | `false` |

Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example:

```bash
Expand Down
73 changes: 73 additions & 0 deletions charts/arcadedb/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ Create a comma separated list of plugins to be enabled in arcadedb
{{- $params = append $params (printf "-Darcadedb.redis.port=%d" (int $config.port)) -}}
{{- else if eq $plugin "prometheus" -}}
{{- $plugins = append $plugins "Prometheus:com.arcadedb.metrics.prometheus.PrometheusMetricsPlugin" -}}
{{- with $.Values.arcadedb.plugins.prometheus -}}
{{- if hasKey . "requireAuthentication" -}}
{{- $params = append $params (printf "-Darcadedb.serverMetrics.prometheus.requireAuthentication=%v" .requireAuthentication) -}}
{{- end -}}
{{- end -}}
{{- else -}}
{{- $plugins = append $plugins (printf "%s:%s" $plugin $config.class) -}}
{{- end -}}
Expand All @@ -170,3 +175,71 @@ Create service configuration for the enabled plugins
{{- end -}}
{{- end -}}
{{- end -}}

{{/*
Observability -D JVM args (logging, OTLP metrics, tracing, readiness).
All opt-in; emits nothing when defaults are unchanged.
*/}}
{{- define "arcadedb.observability.args" -}}
{{- $o := .Values.observability | default dict -}}
{{- $logging := $o.logging | default dict -}}
{{- $otlp := (($o.metrics | default dict).otlp) | default dict -}}
{{- $tracing := $o.tracing | default dict -}}
{{- $health := $o.health | default dict -}}
{{- if eq $logging.format "json" }}
- -Darcadedb.server.logFormat=json
{{- end }}
{{- if $logging.includeTrace }}
- -Darcadedb.server.logIncludeTrace=true
{{- end }}
{{- if $otlp.enabled }}
- -Darcadedb.serverMetrics.otlp.enabled=true
- -Darcadedb.serverMetrics.otlp.endpoint={{ $otlp.endpoint }}
{{- end }}
{{- if $tracing.enabled }}
- -Darcadedb.serverMetrics.tracing.enabled=true
- -Darcadedb.serverMetrics.tracing.endpoint={{ $tracing.endpoint }}
- -Darcadedb.serverMetrics.tracing.samplingRate={{ $tracing.samplingRate }}
{{- end }}
{{- if $health.readinessRequiresHA }}
- -Darcadedb.server.readinessRequiresHA=true
{{- end }}
{{- end -}}
Comment on lines +183 to +207

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The helper arcadedb.observability.args directly accesses nested keys under .Values.observability (such as .Values.observability.logging.format and .Values.observability.metrics.otlp.enabled) without checking if the parent keys exist. If a user provides a custom values file that partially overrides or completely omits the observability section, Helm will throw a nil pointer evaluation error (e.g., nil pointer evaluating interface {}). Using default dict to safely navigate these nested maps prevents rendering crashes.

{{- define "arcadedb.observability.args" -}}
{{- $o := .Values.observability | default dict -}}
{{- $logging := $o.logging | default dict -}}
{{- $metrics := $o.metrics | default dict -}}
{{- $otlp := $metrics.otlp | default dict -}}
{{- $tracing := $o.tracing | default dict -}}
{{- $health := $o.health | default dict -}}
{{- if eq $logging.format "json" }}
- -Darcadedb.server.logFormat=json
{{- end }}
{{- if $logging.includeTrace }}
- -Darcadedb.server.logIncludeTrace=true
{{- end }}
{{- if $otlp.enabled }}
- -Darcadedb.serverMetrics.otlp.enabled=true
- -Darcadedb.serverMetrics.otlp.endpoint={{ $otlp.endpoint }}
{{- end }}
{{- if $tracing.enabled }}
- -Darcadedb.serverMetrics.tracing.enabled=true
- -Darcadedb.serverMetrics.tracing.endpoint={{ $tracing.endpoint }}
- -Darcadedb.serverMetrics.tracing.samplingRate={{ $tracing.samplingRate }}
{{- end }}
{{- if $health.readinessRequiresHA }}
- -Darcadedb.server.readinessRequiresHA=true
{{- end }}
{{- end -}}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified this empirically before applying. The stated trigger — partially overriding or omitting the observability section — does not panic: Helm coalesces user values over the chart's values.yaml defaults, so omitted/partially-set keys retain their defaults (helm template -f with only observability.metrics.otlp.enabled: true renders fine, exit 0; the unit suite also sets partial observability keys and passes).

The real failure mode is explicitly nulling a map, e.g. --set observability=null or --set observability.logging=null, which does dereference nil. That's a genuine crash, so I've applied nil-safe navigation (| default dict) to this helper and the other three sites, plus regression tests covering the explicit-null case.

Fixed in 9e17bbb.


{{/*
Guard: scrape discovery (ServiceMonitor or pod annotations) needs the
prometheus plugin so /prometheus is actually served.
*/}}
{{- define "arcadedb.observability.validate" -}}
{{- $p := (((.Values.observability | default dict).metrics | default dict).prometheus) | default dict -}}
{{- $serviceMonitor := $p.serviceMonitor | default dict -}}
{{- $podAnnotations := $p.podAnnotations | default dict -}}
{{- if or $serviceMonitor.enabled $podAnnotations.enabled -}}
{{- $promEnabled := false -}}
{{- with .Values.arcadedb.plugins.prometheus -}}
{{- if .enabled -}}{{- $promEnabled = true -}}{{- end -}}
{{- end -}}
{{- if not $promEnabled -}}
{{- fail "observability.metrics.prometheus serviceMonitor/podAnnotations require arcadedb.plugins.prometheus.enabled=true (the /prometheus endpoint must be served)" -}}
{{- end -}}
{{- end -}}
{{- end -}}
Comment on lines +213 to +226

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The validation helper arcadedb.observability.validate accesses .Values.observability.metrics.prometheus directly. If observability or metrics is omitted or overridden partially, this will cause a nil pointer dereference error during rendering. Safely defaulting the nested maps ensures robust template rendering.

{{- define "arcadedb.observability.validate" -}}
{{- $o := .Values.observability | default dict -}}
{{- $metrics := $o.metrics | default dict -}}
{{- $p := $metrics.prometheus | default dict -}}
{{- $serviceMonitor := $p.serviceMonitor | default dict -}}
{{- $podAnnotations := $p.podAnnotations | default dict -}}
{{- if or $serviceMonitor.enabled $podAnnotations.enabled -}}
  {{- $promEnabled := false -}}
  {{- with .Values.arcadedb.plugins.prometheus -}}
    {{- if .enabled -}}{{- $promEnabled = true -}}{{- end -}}
  {{- end -}}
  {{- if not $promEnabled -}}
    {{- fail "observability.metrics.prometheus serviceMonitor/podAnnotations require arcadedb.plugins.prometheus.enabled=true (the /prometheus endpoint must be served)" -}}
  {{- end -}}
{{- end -}}
{{- end -}}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 9e17bbbarcadedb.observability.validate now safely defaults the nested maps. (See the thread above: normal partial overrides were already safe via Helm coalescing; the real crash was explicit nulling, now guarded with a regression test.)


{{/*
Merge user-supplied podAnnotations with computed prometheus.io/* scrape
annotations. Returns YAML (possibly empty).
*/}}
{{- define "arcadedb.podAnnotations" -}}
{{- $annotations := deepCopy (default dict .Values.podAnnotations) -}}
{{- $pa := (((((.Values.observability | default dict).metrics | default dict).prometheus) | default dict).podAnnotations) | default dict -}}
{{- if $pa.enabled -}}
{{- $port := int .Values.service.http.port -}}
{{- if $pa.port -}}{{- $port = int $pa.port -}}{{- end -}}
{{- $_ := set $annotations "prometheus.io/scrape" "true" -}}
{{- $_ := set $annotations "prometheus.io/port" (printf "%d" $port) -}}
{{- $_ := set $annotations "prometheus.io/path" (default "/prometheus" $pa.path) -}}
{{- end -}}
{{- if $annotations -}}
{{- toYaml $annotations -}}
{{- end -}}
{{- end -}}
Comment on lines +232 to +245

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The helper arcadedb.podAnnotations accesses .Values.observability.metrics.prometheus.podAnnotations directly. If any of the parent keys are omitted in custom values, this will cause a nil pointer dereference error. Defaulting the nested maps safely avoids this issue.

{{- define "arcadedb.podAnnotations" -}}
{{- $annotations := deepCopy (default dict .Values.podAnnotations) -}}
{{- $o := .Values.observability | default dict -}}
{{- $metrics := $o.metrics | default dict -}}
{{- $prometheus := $metrics.prometheus | default dict -}}
{{- $pa := $prometheus.podAnnotations | default dict -}}
{{- if $pa.enabled -}}
  {{- $port := int .Values.service.http.port -}}
  {{- if $pa.port -}}{{- $port = int $pa.port -}}
  {{- end -}}
  {{- $_ := set $annotations "prometheus.io/scrape" "true" -}}
  {{- $_ := set $annotations "prometheus.io/port" (printf "%d" $port) -}}
  {{- $_ := set $annotations "prometheus.io/path" (default "/prometheus" $pa.path) -}}
{{- end -}}
{{- if $annotations -}}
{{- toYaml $annotations -}}
{{- end -}}
{{- end -}}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 9e17bbbarcadedb.podAnnotations now safely navigates the nested maps with | default dict.

1 change: 1 addition & 0 deletions charts/arcadedb/templates/service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ metadata:
name: {{ include "arcadedb.fullname" . }}-http
labels:
{{- include "arcadedb.labels" . | nindent 4 }}
app.kubernetes.io/component: http
spec:
type: {{ .Values.service.http.type }}
ports:
Expand Down
50 changes: 50 additions & 0 deletions charts/arcadedb/templates/servicemonitor.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{{- $sm := (((((.Values.observability | default dict).metrics | default dict).prometheus) | default dict).serviceMonitor) | default dict -}}
{{- if $sm.enabled }}
{{- include "arcadedb.observability.validate" . -}}
{{- $basicAuth := $sm.basicAuth | default dict -}}
{{- if and $basicAuth.enabled (not $basicAuth.secretName) -}}
{{- fail "serviceMonitor.basicAuth.enabled requires basicAuth.secretName (a secret with username + password keys)" -}}
{{- end -}}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "arcadedb.fullname" . }}
labels:
{{- include "arcadedb.labels" . | nindent 4 }}
{{- with $sm.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with $sm.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
matchLabels:
{{- include "arcadedb.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: http
endpoints:
- port: http
path: {{ $sm.path }}
interval: {{ $sm.interval }}
{{- with $sm.scrapeTimeout }}
scrapeTimeout: {{ . }}
{{- end }}
{{- if $basicAuth.enabled }}
basicAuth:
username:
name: {{ $basicAuth.secretName }}
key: {{ $basicAuth.usernameKey }}
password:
name: {{ $basicAuth.secretName }}
key: {{ $basicAuth.passwordKey }}
{{- end }}
{{- with $sm.relabelings }}
relabelings:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $sm.metricRelabelings }}
metricRelabelings:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
4 changes: 3 additions & 1 deletion charts/arcadedb/templates/statefulset.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{{- include "arcadedb.observability.validate" . -}}
apiVersion: apps/v1
kind: StatefulSet
metadata:
Expand All @@ -15,7 +16,7 @@ spec:
{{- include "arcadedb.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
{{- with (include "arcadedb.podAnnotations" . | fromYaml) }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
Expand Down Expand Up @@ -68,6 +69,7 @@ spec:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- include "arcadedb.plugin.parameters" . | nindent 12 }}
{{- include "arcadedb.observability.args" . | nindent 12 }}
{{- with .Values.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
Expand Down
29 changes: 29 additions & 0 deletions charts/arcadedb/tests/helpers_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,32 @@ tests:
- contains:
path: spec.template.spec.containers[0].command
content: "-Darcadedb.server.plugins=myplugin:com.example.MyPlugin"

- it: prometheus plugin emits requireAuthentication=false when set
set:
arcadedb.plugins.prometheus.enabled: true
arcadedb.plugins.prometheus.requireAuthentication: false
asserts:
- contains:
path: spec.template.spec.containers[0].command
content: "-Darcadedb.serverMetrics.prometheus.requireAuthentication=false"

- it: prometheus plugin omits requireAuthentication arg when not set
set:
arcadedb.plugins.prometheus.enabled: true
asserts:
- notContains:
path: spec.template.spec.containers[0].command
content: "-Darcadedb.serverMetrics.prometheus.requireAuthentication=false"
- notContains:
path: spec.template.spec.containers[0].command
content: "-Darcadedb.serverMetrics.prometheus.requireAuthentication=true"

- it: prometheus plugin emits requireAuthentication=true when explicitly set
set:
arcadedb.plugins.prometheus.enabled: true
arcadedb.plugins.prometheus.requireAuthentication: true
asserts:
- contains:
path: spec.template.spec.containers[0].command
content: "-Darcadedb.serverMetrics.prometheus.requireAuthentication=true"
Loading