Helm charts for deploying and managing OpenAgriNet (OAN) platform services.
| Chart | Type | Purpose |
|---|---|---|
common |
library | Shared template helpers — names, labels, image refs, probes, resources, service account, env config, dependency waits. Renders nothing; never installed directly. |
template |
application | Complete, working starter chart built on common. Copy it to bootstrap a service chart. |
postgresql-cnpg |
application | CloudNativePG-managed PostgreSQL cluster. One release per database. Requires the CNPG operator. |
postgresql-migration |
application | Flyway migrations as a Job. Creates the per-service databases and applies versioned SQL. |
qdrant |
application | Single-node Qdrant — shared vector-index infrastructure. Currently used by knowledge-provider as its DEV vector store. |
minio |
application | Single-node MinIO — shared blob-storage infrastructure. Currently used by knowledge-provider. |
keycloak |
application | Auth for the registry, on the Sunbird RC Keycloak image. Imports the realm the registry expects. |
registry |
application | The OAN participant registry, on Sunbird RC core. Needs postgresql-cnpg and keycloak. |
discovery |
application | The OAN Beckn discover-and-publish service. Needs postgresql-cnpg with pgvector. |
adapter-service |
application | The OAN Beckn adapters. One chart, installed once per role — provider, network or experience. Needs registry. |
kong |
application | Kong — the cluster's API gateway and ingress controller, in one release. The official kong chart 3.2.0 committed whole and unmodified. Per-environment configuration lives in the infra-automation repository. |
registry-seed |
application | Seeds the registry — adapter identities, upstreams, capability bindings and schemas — as a re-runnable Job. Reports the key osid each adapter needs. |
cert-manager |
application | X.509 certificate management. The official cert-manager chart v1.21.2 committed whole and unmodified. Its CRDs are applied out of band — three exceed the annotation size limit. |
cert-manager-issuers |
application | Let's Encrypt ClusterIssuers. Separate from cert-manager because one release cannot register a CRD and create an instance of it. |
clickstack |
application | Observability — ClickHouse, an OTel collector and the HyperDX UI. A verbatim copy of the official upstream chart, with no OAN changes yet. |
openbao |
application | The secret store. The official openbao chart 0.29.6 (OpenBao v2.6.3) committed whole and unmodified; OAN's settings are in examples/openbao.dev.yaml (one pod) and examples/openbao.prod.yaml (three-pod raft cluster). |
openbao-tls |
application | OpenBao's private CA and the certificate it serves its API with, issued and renewed by cert-manager. Installed before openbao, which mounts it. |
openbao-cluster-secret-store |
application | The ClusterSecretStore that lets External Secrets Operator read OpenBao, logging in with Kubernetes auth. |
openbao-secrets |
application | One ExternalSecret per credential: pulls oan/<env>/<name> from OpenBao into a Secret and mirrors it, via Reflector, to the namespaces that read it. |
grafana |
application | Grafana — dashboards and Explore over the OTel data in ClickHouse. The official grafana chart 13.2.6 (Grafana 13.2.2, from grafana-community) committed whole and unmodified. |
opentelemetry-collector |
application | The OTel Collector — receives OTLP from the services and collects pod logs and kubelet metrics, writing all of it to ClickHouse. The official opentelemetry-collector chart 0.173.1 (collector 0.160.0) committed whole and unmodified. |
grafana-provisioning |
application | OAN's Grafana content — the OAN Alerts, discovery overview, Network API, Providers and Infra dashboards, the functional alert rules and Slack routing — as ConfigMaps the grafana chart's sidecars load. The same files as the quick-start. |
charts/
├── common/ # library chart — shared helpers
├── template/ # starter chart — copy this to build a service chart
├── postgresql-cnpg/ # data store
├── postgresql-migration/# schema migrations (Flyway Job)
├── qdrant/ # shared vector-index infrastructure
├── minio/ # shared blob-storage infrastructure
├── keycloak/ # auth for the registry
├── registry/ # the participant registry
├── registry-seed/ # seeds it, as a Job
├── kong/ # API gateway + ingress controller — vendored upstream
├── cert-manager/ # certificate management — vendored upstream
├── cert-manager-issuers/# Let's Encrypt ClusterIssuers for it
├── discovery/ # the Beckn discover-and-publish service
├── adapter-service/ # the Beckn adapters — one release per role
├── clickstack/ # observability — vendored upstream, not yet OAN-shaped
├── openbao-tls/ # its CA and API certificate, from cert-manager
├── openbao/ # the secret store — vendored upstream
├── openbao-cluster-secret-store/ # how ESO reads it
└── openbao-secrets/ # what ESO reads from it
└── grafana/ # dashboards over ClickHouse — vendored upstream
├── opentelemetry-collector/ # telemetry into ClickHouse — vendored upstream
└── grafana-provisioning/ # dashboards and alerts for grafana, loaded by its sidecars
Every chart depends on common via file://../common, except clickstack,
kong, cert-manager, openbao, langfuse, grafana and opentelemetry-collector: all seven are
official upstream charts committed unmodified, so they carry neither the
dependency nor the conventions. Each README lists what that leaves to override.
cert-manager-issuers and grafana-provisioning also skip it, for a different
reason — they render configuration and no workload, so none of the library's
helpers apply.
Three charts, deployed in this order — the ordering is not optional:
# 1. Database cluster. Creates BOTH databases: `registry` via bootstrap.initdb
# and `keycloak` via a CNPG Database object, each owned by its own role.
helm install postgres charts/postgresql-cnpg -n postgres -f charts/postgresql-cnpg/examples/postgres.dev.yaml
# 2. Secrets, before anything that reads them. One Secret per value, pulled from
# OpenBao and mirrored where a second namespace needs it (see Secrets below):
# infra-automation: ./scripts/manage-secrets.py generate --env dev
# 3. Keycloak — imports the sunbird-rc realm it ships with, on first start
helm install keycloak charts/keycloak -n keycloak -f charts/keycloak/examples/keycloak.dev.yaml
# 4. Registry
helm install registry charts/registry -n registry -f charts/registry/examples/registry.dev.yaml
# 5. Seed it: adapter identities, upstreams, bindings, schemas. Re-runnable.
# Its log reports the key osid each adapter must be configured with - the
# registry assigns those on write, so they cannot be known before this runs.
helm install registry-seed charts/registry-seed -n registry -f my-seed-values.yamlpostgresql-migration is deliberately not in that list: both databases come from
the cluster chart, and Sunbird RC and Keycloak each manage their own schema, so
there is nothing for Flyway to apply yet. It joins the flow when OAN adds schemas
of its own.
Their configuration is ported from the verified registry/docker-compose.yml
stack at exact environment-variable parity (32 for the registry, 10 for
Keycloak), so a cluster deploy reproduces what was tested locally. Each chart's
README documents where it deliberately deviates and why. Full walkthrough:
charts/registry/README.md.
Two charts, and one prerequisite that is easy to miss:
# 1. Its own database — pgvector on PostgreSQL 16, with the `vector` extension
# created at bootstrap. Both are required: no stock CNPG operand image has
# pgvector, and `vector` is not a trusted extension, so the owner the service
# connects as cannot create it itself.
helm install discovery-db charts/postgresql-cnpg -n postgres -f charts/postgresql-cnpg/examples/discovery-db.dev.yaml
# 2. The service
helm install discovery charts/discovery -n discovery -f charts/discovery/examples/discovery.dev.yamlIt shares no database and no Keycloak with the registry stack, so the two are
independent installs. Full walkthrough, including how the DSN and the Beckn
specification are supplied:
charts/discovery/README.md.
Service charts depend on common and call its helpers through thin
chart-local wrappers. That keeps naming, labelling, probe, resource, and secret
conventions identical across every OAN chart, and means a convention change is
one edit in the library rather than one edit per chart.
Build a service chart from the template:
# 1. Copy the starter chart
cp -r charts/template charts/my-service
# 2. In charts/my-service/Chart.yaml set name: my-service
# and appVersion to the image tag you deploy by default.
# Keep the common dependency.
# 3. Rename the chart-local helpers to your service name. Change only the left
# side of each define in templates/_helpers.tpl (template.* ->
# my-service.*); the common.* include inside the body stays. This
# renames the defines and the include calls together:
grep -rl 'template\.' charts/my-service | xargs sed -i '' 's/template\./my-service./g'
# (sed -i '' is the macOS form; on Linux use sed -i)
# 4. Set image, ports, probe paths, resources and envConfig in
# charts/my-service/values.yaml
# 5. Validate
./scripts/lint-charts.sh
helm template my-service charts/my-serviceSee charts/common/README.md for the full
helper reference and
charts/template/README.md for the
step-by-step adaptation guide.
./scripts/lint-charts.shRuns helm lint --strict on every chart and helm template on every
application chart. CI runs the identical script
(.github/workflows/helm-lint.yml) on pull
requests and on pushes to main and development.
Because charts depend on common through file://../common and the
packaged dependency is not committed, an edit to common only reaches a
consuming chart after helm dependency update charts/<chart> — or a run of the
lint script, which does it for you.
Chart naming, version/appVersion rules, changelog requirements, what every
service chart must declare, and how secrets are handled are documented in
CONVENTIONS.md.
Two of those rules are enforced at render time rather than at review time: a
chart with empty resources fails to render, and so does an enabled probe with
no handler or with more than one.
No secret value belongs in this repository, and no chart here renders a
Secret. Charts reference Secrets by name; creating them is deliberately left
outside the charts, so that decision is made once rather than per chart. See
CONVENTIONS.md.
OpenBao holds every value; External Secrets Operator copies them into Kubernetes Secrets; Reflector mirrors those into the namespaces that read them.
# 0. The API certificate. OpenBao serves https; cert-manager issues a private
# CA and the server certificate into the openbao namespace and renews it,
# and a sidecar in the OpenBao pod reloads it. Needs cert-manager running.
helm install openbao-tls charts/openbao-tls -n openbao --create-namespace
# 1. The unseal key. OpenBao's "static" seal reads it on every start, so a
# restarted pod unseals itself -- no AWS KMS, no manual unseal. Keep a copy
# OFF the cluster: without it the data cannot be decrypted. Write it to
# $HOME, not here: a relative path puts the master key in the working tree.
openssl rand -out "$HOME/openbao-dev-unseal.key" 32
kubectl -n openbao create secret generic openbao-unseal-key \
--from-file=unseal.key="$HOME/openbao-dev-unseal.key"
# 2. OpenBao. openbao.dev.yaml is one pod; production uses openbao.prod.yaml,
# three pods in a raft cluster on three nodes -- start prod that way rather
# than converting a running single node later.
helm install openbao charts/openbao -n openbao -f charts/openbao/examples/openbao.dev.yaml
# 3. One-time setup: init (writes the root token and recovery keys to the
# file given -- move it to the password manager), KV v2 at secret/,
# Kubernetes auth, a read-only policy for oan/<env>/*, the role ESO uses,
# and the role the backup CronJob uses. The script lives in the
# infra-automation repository, which owns cluster bring-up; its
# install-services.sh runs it as part of a deploy.
../infra-automation/scripts/openbao-configure.sh --env dev --init-out ~/openbao-dev-init.json
# 4. The operator, the mirror, and the store they read
helm install external-secrets charts/external-secrets -n external-secrets --create-namespace
helm install reflector charts/reflector -n reflector --create-namespace
helm install openbao-store charts/openbao-cluster-secret-store \
--set server=https://openbao.openbao.svc.cluster.local:8200 \
--set caProvider.type=Secret --set caProvider.name=openbao-server-tls \
--set caProvider.namespace=openbao
# 5. Write the values, then declare which ones reach the cluster
kubectl -n openbao exec -it openbao-0 -c openbao -- bao kv put secret/oan/dev/registry-db username=... password=...
helm install openbao-secrets charts/openbao-secrets -n external-secrets| Check | Command | Expect |
|---|---|---|
| Chart renders | helm lint --strict charts/openbao -f charts/openbao/examples/openbao.dev.yaml |
0 chart(s) failed |
| Unsealed | kubectl -n openbao exec openbao-0 -c openbao -- bao status |
Seal Type static, Sealed false |
| Unseals on restart | kubectl -n openbao delete pod openbao-0, then bao status again |
Sealed false, with nobody unsealing it |
| Audit log on | kubectl -n openbao exec openbao-0 -c openbao -- bao audit list (with BAO_TOKEN) |
file/ and stdout/ |
| ESO can log in | kubectl get clustersecretstore openbao |
READY True |
| Values arrive | kubectl -n external-secrets get externalsecret |
every row SecretSynced |
| Mirrored | kubectl -n registry get secret registry-db |
exists |
| Policy is tight | an ExternalSecret for oan/prod/... on the dev cluster |
SecretSyncedError, "permission denied" |
| Store is fenced | an ExternalSecret on the openbao store in any namespace but external-secrets |
SecretSyncedError, "not allowed from namespace" |
| Network is fenced | curl -k https://openbao.openbao.svc:8200/v1/sys/health from a pod in default |
times out |
| TLS verifies | kubectl -n openbao exec openbao-0 -c openbao -- bao status |
no x509 error (it checks BAO_CACERT) |
| Certificate renews | kubectl -n openbao get certificate openbao-server-tls |
READY True; after a renewal the tls-reloader log says sent SIGHUP |
| Backups work | kubectl -n openbao create job snap-now --from=cronjob/openbao-snapshot |
a new bao_<date>.snapshot in the bucket |
| HA formed (prod) | bao operator raft list-peers (with BAO_TOKEN) |
three voters, one leader |
A config or image change reaches OpenBao only when its pod is deleted: the
chart's update strategy is OnDelete, and Argo CD shows the app Synced either
way. In prod, delete the standbys first and the active pod
(openbao-active=true) last, one at a time.
The snapshot CronJob (snapshotAgent, off in dev until a bucket exists -- see
openbao.dev.yaml) uploads a raft snapshot of the whole store every hour. A
snapshot can only be opened with the unseal key it was taken under, so keep the
key with the snapshots, off the cluster. To restore, into a fresh OpenBao that
mounts that same key:
bao operator init # a fresh store needs initialising first
bao operator raft snapshot restore -force bao_<date>.snapshot # with that init's root token
# From here the ORIGINAL store's root token and data are back; the fresh one's are gone.Copy the values -- never regenerate them: Postgres role passwords, the admin-api client secret Keycloak stored at import, and the adapter keys the registry holds all depend on the current ones. Then switch the existing release in place, and diff before against after.
./scripts/secrets-snapshot.sh > before.txt # hashes only, no values
BAO_TOKEN="$(../infra-automation/scripts/openbao-login.sh --env dev)" ./scripts/asm-to-openbao.sh --env dev --dry-run
BAO_TOKEN="$(../infra-automation/scripts/openbao-login.sh --env dev)" ./scripts/asm-to-openbao.sh --env dev
helm install openbao-store charts/openbao-cluster-secret-store \
--set server=https://openbao.openbao.svc.cluster.local:8200 \
--set caProvider.type=Secret --set caProvider.name=openbao-server-tls \
--set caProvider.namespace=openbao
helm upgrade asm-secrets charts/openbao-secrets -n external-secrets # the EXISTING release
kubectl -n external-secrets get externalsecret # every row: openbao, SecretSynced
./scripts/secrets-snapshot.sh > after.txt
diff before.txt after.txt # must be emptyasm-secrets here is the Helm release already running in the cluster; its
chart, and asm-cluster-secret-store, have been removed from this repository.
The release keeps its name after the upgrade, and helm rollback works from
its stored history, so neither needs the old chart.
Upgrade the release, do not uninstall it: its ExternalSecrets own their
Secrets, so an uninstall deletes them until the new chart recreates them. If the
diff is not empty, helm rollback asm-secrets -n external-secrets puts it back
on Secrets Manager, which is untouched by all of this.
A rotation is bao kv put with the new value; it reaches the cluster within
refreshInterval (1h by default), and pods pick it up on their next restart.