Secondary storage¶
Camunda keeps its data in two places. Primary storage is the Zeebe log and state on the broker volumes. Secondary storage holds the exported data that Operate, Tasklist, and the search API read, and Camunda describes it in Configure secondary storage. The operator supports two backends: Elasticsearch through the ECK operator, and PostgreSQL through the CloudNativePG operator or on a server you run. A CamundaCluster does not name the backend. It names a SecondaryStorageConfig in spec.storageRef, and this contract points at the backend.
This guide tells you which backend to pick, which resources to create for each backend, and in which order.
Choose a backend¶
| Elasticsearch | PostgreSQL | |
|---|---|---|
| Who runs the backend | The ECK operator, in your Kubernetes cluster. You install ECK first. | The CloudNativePG operator, or you. See PostgreSQL. |
| What you create | One ElasticsearchCluster. |
One DatabaseServer and one Database, or a DatabaseServerConfig for your own server and one Database. |
| Versions | Elasticsearch 8.19+ and 9.2+. | See The PostgreSQL version. |
| Backup | Elasticsearch snapshots, through LogicalBackupElasticsearch. | A pg_dump of the database plus a Zeebe backup of primary storage, through LogicalBackupRDBMS. |
| Optimize | Supported. | Not supported. Camunda Optimize needs Elasticsearch or OpenSearch. |
| Not supported by the operator | OpenSearch. The operator does not model it. | Other database engines. DatabaseServerConfig accepts engine: postgres only. |
If you need Optimize, pick Elasticsearch. If you already run PostgreSQL and do not need Optimize, you can pick PostgreSQL. Camunda compares the throughput and the sizing of the backends in Size your environment.
One cluster per backend¶
A backend belongs to one CamundaCluster. Camunda fixes the index names in Elasticsearch and the tables in a database, so two clusters on one backend write each other's data. Give every cluster its own ElasticsearchCluster or its own Database, each with its own contract. Two clusters can share one PostgreSQL server, each with its own database, see One server per orchestration cluster.
If a second cluster names a backend that another cluster holds, the operator keeps the second cluster suspended. Its Ready condition reads False with reason StorageAlreadyAttached. The CamundaCluster reference has the rule in full.
Elasticsearch¶
Prerequisite: the ECK operator is installed in the Kubernetes cluster, and the camunda-operator started after the ECK CRDs were installed. The operator does not run Elasticsearch nodes itself. It creates an ECK Elasticsearch resource, and ECK runs the nodes.
This chain is ready to apply in config/example/camunda-cluster/elasticsearch. The link opens the main branch. For a release, open the same path under the tag of the release.
-
Create an
ElasticsearchCluster. The sizes below are for trying out. For production, use more nodes and larger volumes. On one node, the Camunda indices get no replica, so the node keeps green health. Node count has the rule. -
Wait until the
ElasticsearchClusterisReadywith reasonHealthy.kubectl wait elasticsearchcluster/my-cluster-es -n my-cluster-ns --for=condition=Ready --timeout=15mThe first start takes some minutes while ECK pulls the image and forms the cluster. When it is done, the status reads:
If
ReadystaysFalse, read the message of theReadycondition withkubectl get elasticsearchcluster my-cluster-es -n my-cluster-ns -o yaml. -
Make sure that the
SecondaryStorageConfigmy-storage-configexists in the same namespace and isReadywith reasonHealthy. The operator created it for you. -
Point the
CamundaClusterat it withspec.storageRef: my-storage-config. TheCamundaClustermust be in the same namespace as theSecondaryStorageConfig, becausestorageRefis a name without a namespace.
graph LR
ESC[ElasticsearchCluster] -->|creates| ECK["Elasticsearch (ECK operator, external)"]
ESC -->|creates| SEC["Secret my-cluster-es-es-user"]
ESC -->|creates| SSC[SecondaryStorageConfig]
CC[CamundaCluster] -.->|storageRef| SSC
The contract carries the endpoint, the user Secret my-cluster-es-es-user, and the CA of the self-signed certificate. For backups, also set spec.snapshotStorageRef, see Backup. ElasticsearchCluster has the credentials and the status, and links every field.
PostgreSQL¶
The chain has two halves. The first half is the server, and you get it in one of two ways. The second half is the logical database on that server, and it is the same either way.
config/example/camunda-cluster/rdbms on the main branch is ready to apply. It takes the DatabaseServer route, and it holds the Database, the platform configuration, and the CamundaCluster.
A DatabaseServer |
A server you run | |
|---|---|---|
| Who runs PostgreSQL | The CloudNativePG operator, in your Kubernetes cluster. | You. The server can run in the cluster or outside it, for example as a managed service. |
| What you create | One DatabaseServer. |
One Secret with the admin credentials and one DatabaseServerConfig. |
| Admin credentials | CloudNativePG writes them into a Secret. | You write them. |
| Point-in-time restore | spec.archive gives the server a continuous archive, and a PointInTimeRestore rolls the server back for you. |
Only if you archive the server yourself and roll it back before you create the restore. |
| Prerequisite | The CloudNativePG operator. An archive also needs the Barman Cloud plugin and cert-manager. | Nothing on the Kubernetes cluster. |
One server per orchestration cluster¶
Give every orchestration cluster a PostgreSQL server of its own. Two clusters can share one server with a database each, and the operator allows it. A shared server gives up two things.
A point-in-time restore rolls back the whole server, not one database. Every database on it goes back to the same point. A restore for one cluster therefore erases the recent data of every other cluster on that server. A PointInTimeRestore holds in Pending while more than one Database uses the server. Its Ready condition reads False with reason SharedServer. The hold lifts when one Database is left.
Sizing and the version are shared too. One DatabaseServer has one instance count, one set of volumes, and one PostgreSQL major. A change to any of them reaches every cluster on the server. When a change restarts the instances, every cluster on the server sees that restart. The major cannot change once the server runs, see The PostgreSQL version.
A shared server is cheaper, and it fits a fleet of small clusters that never needs a point-in-time restore. Choose it only when you accept both losses above.
Run the server with a DatabaseServer¶
Prerequisites: the CloudNativePG operator is installed in the Kubernetes cluster, and the camunda-operator started after its CRDs were installed. An archive also needs the Barman Cloud plugin and cert-manager. See Installation.
-
Create a
DatabaseServer. The sizes below are for production. Leave outarchivewhen you do not need a point-in-time restore.apiVersion: core.camunda.io/v1 kind: DatabaseServer metadata: name: my-db namespace: my-cluster-ns spec: version: "17" instances: 2 storageSize: "64Gi" resources: requests: { cpu: "1", memory: "2Gi" } databaseServerConfig: my-db-server archive: objectStorageRef: my-backup-bucket retentionPeriodDays: 30objectStorageRefnames an ObjectStorageConfig. The backup guide creates one. -
Wait until the
DatabaseServerisReadywith reasonHealthy.The first start pulls the PostgreSQL image, bootstraps the instances, and takes the first base backup of the archive. When it is done, the status reads:
status: cluster: my-db systemIdentifier: "7412345678901234567" conditions: - type: Ready status: "True" reason: HealthyThe server publishes the
DatabaseServerConfigmy-db-serverin the same namespace, with the hostmy-db-rw.my-cluster-ns.svc, port 5432, and the Secretmy-db-superuseras its admin credentials. With an archive it also carriespitr.enabled: trueandpitr.recovery: operator.DatabaseServer covers sizing, the archive, monitoring, presets, and the rollback in full.
-
Continue with The logical database. Use
serverRef: my-db-server.
Use a server that you run¶
Prerequisites: a PostgreSQL server that the operator can reach over the network, and an admin user that can create databases and roles. The steps below use a server at postgres.my-cluster-ns.svc on port 5432 and an admin user postgres.
-
Create a Secret with the admin credentials in the namespace of the
CamundaCluster. TheDatabaseServerConfignames it by name, and reads it from its own namespace. -
Create a
DatabaseServerConfigin the namespace of the cluster. It describes the server and names the admin credentials Secret beside it. -
Wait until the
DatabaseServerConfigisReadywith reasonHealthy. The operator connects to the server with the admin credentials. It reports the major version and the identity of the instance:status: serverVersion: "17" systemIdentifier: "7412345678901234567" conditions: - type: Ready status: "True" reason: Healthy message: Reached the server; it runs major version 17If the operator cannot reach the server, or the server rejects the credentials, the reason is
ConnectionFailed. The message names the endpoint and the error:status: conditions: - type: Ready status: "False" reason: ConnectionFailed message: "Connecting to postgres.my-cluster-ns.svc:5432: failed to connect: password authentication failed for user \"postgres\""If the reason is
MissingSecret, the Secret or one of its keys does not exist.
The logical database¶
Both paths above end with a DatabaseServerConfig in the namespace of the cluster. The steps below turn it into secondary storage. They use the contract my-db-server.
-
Create a
Databasein the namespace of theCamundaCluster. SetsecondaryStorageConfigto the name of the contract you want. TheDatabaseresolvesserverRefin its own namespace. It publishes theDatabaseConfigand theSecondaryStorageConfigthere, where the cluster reads them. -
Wait until the
DatabaseisReadywith reasonHealthy.If the reason is
ServerIdentityUnknown, theDatabaseServerConfighas not reached the server yet. Read itsReadycondition. -
Make sure that the
DatabaseConfigmy-camunda-dband theSecondaryStorageConfigmy-storage-configexist inmy-cluster-nsand areReadywith reasonHealthy. TheDatabasecreated both. TheSecondaryStorageConfighastype: rdbmsand references theDatabaseConfig. -
Point the
CamundaClusterat the contract, as in step 4 of the Elasticsearch procedure:
graph LR
DBS[DatabaseServer] -->|publishes| DBSC
DB[Database] -.->|serverRef| DBSC[DatabaseServerConfig]
DBSC -.->|adminCredentialsSecretRef| ADM["Secret with the admin credentials"]
DB -->|"SQL: CREATE DATABASE, CREATE ROLE"| PG["PostgreSQL server"]
DB -->|creates| DBC[DatabaseConfig]
DB -->|creates| SSC["SecondaryStorageConfig (rdbms)"]
SSC -.->|databaseConfigRef| DBC
CC[CamundaCluster] -.->|storageRef| SSC
The Database also creates the database, its roles, and their credential Secrets. Database names them, and says how to rotate a password and what deletion keeps.
For each kind, see DatabaseServerConfig, Database, DatabaseConfig, and SecondaryStorageConfig.
Bring your own backend¶
If you already run Elasticsearch or a PostgreSQL database that the operator does not manage, write the contracts by hand. The CamundaCluster does not know who created them.
The rule of one cluster per backend holds for a hand-written contract. Write one contract per backend.
For Elasticsearch, write a SecondaryStorageConfig with type: elasticsearch. Create the Secret with the username and password first. The endpoint can serve a certificate that the orchestration cluster does not trust by default. In that case, name the CA Secret as well.
apiVersion: core.camunda.io/v1
kind: SecondaryStorageConfig
metadata:
name: my-storage-config
namespace: my-cluster-ns
spec:
type: elasticsearch
elasticsearch:
endpoint: "https://elasticsearch.example.com:9200"
credentialsSecretRef:
name: my-elasticsearch-credentials
usernameKey: username
passwordKey: password
# Only when the certificate of the endpoint is not trusted by default.
caSecretRef:
name: my-elasticsearch-ca
key: ca.crt
# The number of data nodes. It sets the default index replica count.
nodeCount: 3
Without nodeCount, Camunda keeps its own default replica count, see Node count.
For PostgreSQL, write a DatabaseServerConfig for the server, a DatabaseConfig for the database, and a SecondaryStorageConfig with type: rdbms. All three live in the namespace of the cluster. Create the database, the application role, and the credentials Secret on your side first. The DatabaseServerConfig still needs admin credentials, because the operator validates it by a connection to the server.
apiVersion: core.camunda.io/v1
kind: DatabaseConfig
metadata:
name: my-camunda-db
namespace: my-cluster-ns
spec:
serverRef: my-db-server
databaseName: camunda
credentialsSecretRef:
name: my-camunda-db-credentials
usernameKey: username
passwordKey: password
---
apiVersion: core.camunda.io/v1
kind: SecondaryStorageConfig
metadata:
name: my-storage-config
namespace: my-cluster-ns
spec:
type: rdbms
rdbms:
databaseConfigRef: my-camunda-db
The reference pages describe each contract: SecondaryStorageConfig, DatabaseConfig, and DatabaseServerConfig. A backup of a hand-written Elasticsearch contract needs elasticsearch.snapshotRepository set by hand, after you register the repository yourself. A backup of a hand-written DatabaseConfig needs backupCredentialsSecretRef. The backup guide shows both.
Related¶
- Getting started: the full path from an empty namespace to a running orchestration cluster.
- Backup: how backups work on each backend, and which bucket both sides must share.
- SecondaryStorageConfig: the contract that a
CamundaClusterreferences throughstorageRef. - ElasticsearchCluster: the kind that runs Elasticsearch through ECK and creates the contract.
- DatabaseServer: the kind that runs PostgreSQL through CloudNativePG, archives it, and creates the contract.
- DatabaseServerConfig: the contract that describes a PostgreSQL server and its admin credentials.
- Database: the kind that prepares a logical database and creates the contracts.
- DatabaseConfig: the contract that describes one logical database and its credentials.
- CamundaCluster: the orchestration cluster, and the meaning of
storageRef.