CamundaOptimize¶
CamundaOptimize runs Camunda Optimize for one orchestration cluster. You create it, or another tool creates it for you.
Optimize reads the Zeebe records that the cluster exports to Elasticsearch, and it writes its own analytics indices to the same Elasticsearch. It is not part of the orchestration cluster. It runs the version of the cluster, and it signs in against Management Identity instead of the built-in authentication of the cluster.
Before you create one, make sure that these exist:
- A CamundaCluster in the same namespace, on a SecondaryStorageConfig of type
elasticsearch. Optimize does not read an RDBMS secondary storage. - A ManagementAuthConfig. A CamundaManagementCluster writes one for you.
- The version of the cluster, for
spec.version. - If your own Elasticsearch uses a certificate from a private authority, the CA Secret on the storage contract. A contract is a resource that publishes the address and the credentials of a service for other resources to reference. See Secondary storage over TLS. Without it, the exporter writes no records for Optimize.
The smallest manifest names the cluster, the authentication contract, and the version:
apiVersion: core.camunda.io/v1
kind: CamundaOptimize
metadata:
name: my-cluster-optimize
namespace: my-cluster-ns
spec:
version: "8.9.0"
managementAuthRef: management-auth
clusterRef:
name: my-cluster
graph LR
OPT[CamundaOptimize] -.->|clusterRef| CC[CamundaCluster]
OPT -.->|managementAuthRef| MAC[ManagementAuthConfig]
OPT -.->|"turns the exporter on"| CC
CC -.->|storageRef| SSC[SecondaryStorageConfig]
OPT -->|creates| WA["my-cluster-optimize-webapp"]
OPT -->|creates| IMP["my-cluster-optimize-importer"]
IMP -.->|"reads zeebe-record indices"| ES["Elasticsearch"]
WA -.->|"reads and writes analytics indices"| ES
What you get¶
The operator creates two Deployments in the namespace of the resource, and one Service of the same name in front of each:
| Deployment and Service | What it does | Service ports |
|---|---|---|
<name>-webapp |
Serves the Optimize user interface. | http 8090, management 8092 |
<name>-importer |
Reads the exported records and writes the analytics indices. | http 8090, management 8092 |
A name that is too long for the suffix is cut, and a hash of the full name is added. Both Deployments carry the label camunda.io/cluster with the name of the cluster, so kubectl get deploy,svc -l camunda.io/cluster=my-cluster lists them next to the workloads of the cluster.
The operator creates no Ingress. To open the user interface, publish <name>-webapp yourself, or reach it for a moment with a port forward:
kubectl get camundaoptimize shows whether each instance is ready, why, and the cluster it reads:
The exporter settings on the cluster¶
Optimize reads the zeebe-record indices that the Zeebe Elasticsearch exporter writes. From Camunda 8.8, that exporter is off by default. The operator turns it on: it adds these entries to spec.zeebe.extraEnv of the referenced cluster.
| Entry | Value |
|---|---|
CAMUNDA_DATA_EXPORTERS_ELASTICSEARCH_CLASSNAME |
The exporter class. |
CAMUNDA_DATA_EXPORTERS_ELASTICSEARCH_ARGS_URL |
The Elasticsearch endpoint of the storage contract. |
CAMUNDA_DATA_EXPORTERS_ELASTICSEARCH_ARGS_INDEX_PREFIX |
zeebe-record. |
CAMUNDA_DATA_EXPORTERS_ELASTICSEARCH_ARGS_AUTHENTICATION_USERNAME |
A secretKeyRef to the credentials Secret of the storage contract. |
CAMUNDA_DATA_EXPORTERS_ELASTICSEARCH_ARGS_AUTHENTICATION_PASSWORD |
A secretKeyRef to the credentials Secret of the storage contract. |
CAMUNDA_DATA_EXPORTERS_ELASTICSEARCH_ARGS_INDEX_NUMBEROFREPLICAS |
The index replica count. Only when a count applies. |
No password is written to the CamundaCluster. A rotated Elasticsearch password does not change these entries. The index prefix has no field: the operator sets the same prefix on the exporter and on the importer.
The operator owns only these entries, under the field manager camunda-operator/camundaoptimize. Every other entry of the list, and every other field of the cluster, stays as you or your GitOps tool wrote it. If your GitOps tool manages one of these names too, remove the name from what the tool manages. Otherwise the two keep changing the entry back.
The entries are part of the Zeebe pod template. So the first attachment of Optimize restarts the Zeebe pods of the cluster, and so does the deletion. Plan both like any other change of the cluster.
The cluster can already carry one of these names with the other kind of value. That is a literal where the operator needs a secretKeyRef, or the reverse. Then Ready reports ExporterConflict:
status:
conditions:
- type: Ready
status: "False"
reason: ExporterConflict
message: 'spec.zeebe.extraEnv of CamundaCluster "my-cluster" carries CAMUNDA_DATA_EXPORTERS_ELASTICSEARCH_ARGS_AUTHENTICATION_PASSWORD with the other kind of value; remove the entries or let the operator own them'
Remove the named entries from the cluster. The operator then applies its own. An entry that supplies its value the same way as the operator is no conflict: the operator takes it over, and its value wins.
Index replicas¶
spec.indexReplicas sets the number of replicas of each Optimize index. It also sets the replicas of the zeebe-record indices that the exporter writes.
apiVersion: core.camunda.io/v1
kind: CamundaOptimize
metadata:
name: my-cluster-optimize
namespace: my-cluster-ns
spec:
indexReplicas: 0
# ... the rest of your Optimize
When you do not set it, the node count of the storage contract gives the count. One node gives 0 replicas, and two or more nodes give 1 replica. Without a node count, Optimize and the exporter keep their own defaults. This count is separate from indexReplicas of the cluster.
A count that the nodes cannot place is kept. The indices then stay at yellow health, and the CamundaOptimize records a Warning event IndexReplicasExceedNodes:
indexReplicas 2 needs 3 Elasticsearch nodes, but the storage contract names 1. The indices stay yellow
Optimize applies the count to its existing indices each time it starts. The exporter applies it only to the zeebe-record indices that it creates after the change. A change of the count restarts the Optimize pods and the Zeebe pods of the cluster.
One Optimize for one cluster¶
One cluster carries one Optimize instance, because two instances write the same analytics indices. To serve the user interface from more than one pod, scale spec.webapp.replicas. The importer runs one pod at most.
The API server accepts a second CamundaOptimize for a cluster that already has one. The oldest one holds the cluster. When two were created in the same second, the name that sorts first holds it. Every other one reports ClusterAlreadyAttached, names the holder in the message, and creates nothing.
If you delete the holder, the next one takes the cluster. It reports WaitingForHandover until the importer Deployment of the previous one is gone. While the pods of that importer still stop, the workloads stay at zero with reason Suspended, see Suspension. So two importers never write the indices at the same time.
spec.clusterRef is immutable. To attach Optimize to another cluster, delete this resource and create a new one.
Authentication¶
spec.managementAuthRef names a cluster-scoped ManagementAuthConfig. That page describes the contract and the key that its Secret must carry. The API reference lists the fields of the contract.
The client Secret of the contract, and the license Secret of the CamundaPlatformConfig of the cluster, can live in another namespace. When one does, the operator copies it into the namespace of the CamundaOptimize, as <name>-optimize-auth-client and <name>-optimize-license. MirroredSecretsReady reports on the copies.
Optimize reads tenants and users from spec.baseUrl of the contract. That URL is the root of Management Identity, not the Identity URL of the orchestration cluster.
The login callback¶
After a person signs in, the identity provider sends the browser back to Optimize. The provider accepts only the callback URLs that its Optimize client lists.
- In the two Keycloak modes of a CamundaManagementCluster, set
spec.externalUrlto the URL of this Optimize in a browser. The management plane registers the callback for you. See Optimize on that page. - In the
oidcmode, the operator registers no callback. Add the callback of each Optimize to the Optimize application at your provider. Camunda names the path in component-specific configuration.
apiVersion: core.camunda.io/v1
kind: CamundaOptimize
metadata:
name: my-cluster-optimize
namespace: my-cluster-ns
spec:
managementAuthRef: management-auth
externalUrl: "https://optimize.camunda.example.com"
# ... the rest of your Optimize
A missing callback does not change the status of this resource. The sign-in fails, and the identity provider shows the error in the browser.
Versions¶
spec.version is the Optimize version, as a full semantic version such as 8.9.0. Camunda releases Optimize with the minor and the patch of the orchestration cluster. See the component version matrix. Set it to the version of the cluster.
The major and the minor must match the effective version of the cluster. That is spec.version of the CamundaCluster, or the version of its release. The operator does not refuse a different patch, but Camunda does not support it. A different major or minor reports VersionMismatch:
To upgrade Optimize, set spec.version to the version of the cluster. The importer stops for one restart, and the webapp rolls, see Rollouts. When you upgrade the cluster to a new minor, Optimize reports VersionMismatch until you raise spec.version too. Both workloads keep running on the previous version in that time.
Suspension¶
The Optimize workloads stop with the cluster. The importer reads Elasticsearch directly, so the operator scales the webapp and the importer to zero in these cases:
- The cluster is suspended: by
spec.suspend, by a suspension hold, or by the operator. See Suspend and pause. - The cluster does not hold its backend yet: the Elasticsearch that its storage contract points at. See Secondary storage.
- Another writer still writes that backend: pods of another cluster, the importer pods of a previous
CamundaOptimize, or a restore into another cluster.
A LogicalRestoreElasticsearch suspends the cluster, so the import stops for the restore without a step from you.
While the workloads are at zero, Ready reads True with reason Suspended. status.suspendedBy says which case holds them:
suspendedBy is Cluster for the first case, and StorageClaim for the other two. It is empty while the workloads follow their spec. The events of the CamundaOptimize name the cluster: ClusterSuspended or StorageClaimAwaited when the workloads go to zero, and ClusterResumed when they start again.
The exporter settings stay on the cluster while it is suspended. Only deletion removes them.
A failed check¶
When a check of a reference fails, Ready carries the failure reason. The workloads then do this:
| State of the cluster | Workloads |
|---|---|
| Running | They keep the configuration that the operator applied last. A new instance creates none until the check passes. |
| Suspended | They go to zero. The message of Ready adds the suspension. |
| Resumed, while the check still fails | Those that stopped stay at zero until the check passes. The message of Ready ends with The Optimize workloads that stopped stay at zero until the reference check passes. |
Deleted, or held by another CamundaOptimize |
The operator removes them, and builds them again when the cluster returns or this instance holds it again. |
If the API server refuses to scale a workload to zero, that workload keeps running. The CamundaOptimize then records a Warning event WorkloadStopRefused with the refusal.
status:
conditions:
- type: Ready
status: "False"
reason: InvalidReference
message: SecondaryStorageConfig "my-cluster-ns/my-storage-config" not found
- type: ImporterReady
status: "True"
reason: Healthy
Stopping the import¶
To stop the import while the cluster keeps running, for example for an index rewrite, set spec.importer.replicas to 0:
apiVersion: core.camunda.io/v1
kind: CamundaOptimize
metadata:
name: my-cluster-optimize
namespace: my-cluster-ns
spec:
importer:
replicas: 0
# ... the rest of your Optimize
The webapp keeps serving what is already imported. Set the field back to 1 to start the import again.
Zero replicas is the state you asked for, so ImporterReady and Ready stay True. Ready alone does not tell you that data still arrives. To know that, read the ready replicas of the <name>-importer Deployment.
Do not set the importer variables through extraEnv
An entry of extraEnv replaces the entry of the same name that the operator renders. The operator does not refuse one. Two names decide what a pod does. CAMUNDA_OPTIMIZE_ZEEBE_ENABLED makes a pod an importer, so a webapp that carries it becomes a second importer on the same indices. CAMUNDA_OPTIMIZE_ZEEBE_NAME is the index prefix that the importer reads, and no exporter writes a changed prefix.
Rollouts¶
The importer is replaced, not rolled: the old pod stops before the new one starts. Two importers that write the same indices at the same time make the analytics data inconsistent. So a new version or a changed setting stops the import for the time of one restart. The webapp rolls and keeps serving.
When a resource that this CamundaOptimize references changes, the pods restart with the new value. So does a change to a key that Optimize reads from a referenced Secret, such as a rotated Elasticsearch password. A change to the labels or annotations of the Secret restarts nothing. A Secret that you attach yourself through extraEnv or extraEnvFrom does not restart the pods. Restart the workload yourself after you change one.
Monitoring¶
Set spec.monitoring.serviceMonitor.enabled to true to get one ServiceMonitor per Deployment, with the name of the Deployment. It scrapes /actuator/prometheus on the management port, 8092. Add the label that your Prometheus selects on with spec.monitoring.serviceMonitor.labels.
The operator creates them only when the Kubernetes cluster serves the ServiceMonitor kind. If you install the Prometheus operator later, the operator creates them then.
Deletion¶
When you delete the CamundaOptimize, the operator removes its exporter entries from the cluster and records the event ExporterRemoved. Entries that you own stay. Kubernetes removes the Deployments, the Services, the ServiceMonitors, and the copies of Secrets.
The analytics indices in Elasticsearch stay. Delete them yourself if you want the storage back. A new CamundaOptimize on the same cluster reads the indices that are already there.
The zeebe-record indices also stay. The cluster stops writing new records to them.
A CamundaOptimize that never held the cluster removes nothing from it.
Status¶
| Type | Reason | Meaning | What to do |
|---|---|---|---|
MirroredSecretsReady |
Healthy / Disabled |
Every copy of a Secret from another namespace is applied, or there is no such Secret. | Nothing. |
WebappReady / ImporterReady |
Healthy |
Every replica is ready, or the replica count is 0. |
Nothing. |
WebappReady / ImporterReady |
Creating / Updating / Scaling |
The Deployment rolls out or scales. The reason stays while a replica does not become ready, until the grace period ends. | Wait. If the reason stays, read the pods and events of the Deployment. |
WebappReady / ImporterReady |
Suspending / Suspended |
The Deployment stops, or is at zero, with the cluster. | Nothing. See Suspension. |
WebappReady / ImporterReady |
Failing |
The Deployment has replicas that do not become ready. | Read the pods of the Deployment. |
WebappReady / ImporterReady |
Degraded / Down |
The Deployment is still not ready at the end of the grace period, 30 minutes by default. Degraded means that some replicas are ready. Down means that none is ready. |
Read the pods and events of the Deployment. |
Ready |
Healthy |
Every condition that takes part is healthy. | Nothing. |
Ready |
Creating / Updating / Scaling / Failing / Degraded / Down |
The reason of the condition that governs Ready. The message names it. |
Read the row of that condition. |
Ready |
Suspended |
Both workloads are at zero with the cluster. Ready is True. |
Nothing. Optimize starts again with the cluster. |
Ready |
ClusterAlreadyAttached |
Another CamundaOptimize holds the cluster. The message names it. |
Delete one of the two. |
Ready |
WaitingForHandover |
This resource now holds the cluster, and the importer Deployment of the previous one still exists. The message names it. The pods of that importer are a separate wait, under Suspended. |
Wait. It clears on its own. |
Ready |
InvalidReference |
A referenced resource does not exist: the cluster, the ManagementAuthConfig, or a resource that the cluster references. It also reports a cluster whose effective spec is invalid, such as a version below 8.9.0. |
Read the message. Create the missing resource, or correct the field it names. |
Ready |
StorageTypeMismatch |
The secondary storage of the cluster is of type rdbms. |
Attach Optimize to a cluster on Elasticsearch. |
Ready |
VersionMismatch |
The minor of spec.version differs from the minor of the cluster. |
Set spec.version to the version of the cluster. |
Ready |
MissingSecret |
A referenced Secret does not exist or lacks a key. | Create the Secret with the named key. |
Ready |
ExporterConflict |
The cluster already carries an exporter entry with the other kind of value. | Remove the named entries from the cluster. |
Ready is True only when every condition that takes part in it is True. WebappReady and ImporterReady always take part. MirroredSecretsReady takes part only when a copy exists.
status.suspendedBy is described under Suspension.
status.observedGeneration is the last generation the operator reconciled.
Routes¶
status.endpoints lists the route to this Optimize: the path on its external URL, with the Service and the port that serve it. Read it to route traffic to Optimize. You do not have to work out the Service name, which is shortened for a long resource name.
status:
endpoints:
- name: optimize
# The path of spec.externalUrl, or / for a URL without a path.
pathPrefix: /
port: 8090
protocol: HTTP
service: my-cluster-optimize-webapp
The importer serves no browser traffic and has no route.
To put Optimize under a path of a host that other apps share, give spec.externalUrl that path:
apiVersion: core.camunda.io/v1
kind: CamundaOptimize
metadata:
name: my-cluster-optimize
namespace: my-cluster-ns
spec:
externalUrl: "https://camunda.example.com/optimize"
# ... the rest of your Optimize
The webapp then serves under /optimize, and the route carries that path:
status:
endpoints:
- name: optimize
pathPrefix: /optimize
port: 8090
protocol: HTTP
service: my-cluster-optimize-webapp
Send the requests under /optimize to the Service with the path as it is. Do not strip the prefix, because Optimize expects it. The login callback is under the path too: https://camunda.example.com/optimize/api/authentication/callback. Camunda describes the setting in Container. The metrics on the management port stay at the root.
A change of the path restarts the webapp, and status.endpoints shows the new path. The importer keeps running. In the two Keycloak modes, the management plane registers the new callback. In the oidc mode, change the callback at your provider.
The entry stays while the workloads are at zero with the cluster, and while a check fails. The Service stays too, so the route answers again when Optimize starts. There is no entry while another CamundaOptimize holds the cluster, or when the cluster is gone, because the operator then deletes the Service. A new CamundaOptimize whose first check fails has no entry yet.
Fields¶
The API reference lists every field of CamundaOptimize, with its type, whether it is required, its default, and its validation.
Validation rules¶
The API server enforces these at admission:
spec.versionmust be a full semantic version such as8.9.0. A two-segment version is rejected.spec.managementAuthRefmust not be empty.spec.externalUrlmust be anhttporhttpsURL with a host. It can have a path, such ashttps://camunda.example.com/optimize. It carries no comma, no whitespace, no query, and no fragment, and it does not end with a slash. It carries no percent-encoded character and no backslash. Its path has no.or..segment.spec.importer.replicasmust be0or1.spec.clusterRefis immutable.
The rules below depend on live state, so the API server accepts a resource that breaks them. Ready reports the result:
- The references resolve.
- The secondary storage of the cluster is Elasticsearch.
- The minor of
spec.versionmatches the minor of the cluster. - No other
CamundaOptimizeholds the cluster. - No exporter entry on the cluster has the other kind of value.
A production-shaped example¶
apiVersion: core.camunda.io/v1
kind: CamundaOptimize
metadata:
name: my-cluster-optimize
namespace: my-cluster-ns
spec:
version: "8.9.0"
managementAuthRef: management-auth
externalUrl: "https://optimize.camunda.example.com"
clusterRef:
name: my-cluster
webapp:
replicas: 2
resources:
requests:
cpu: "500m"
memory: 1Gi
limits:
cpu: "1"
memory: 2Gi
importer:
replicas: 1
resources:
requests:
cpu: "1"
memory: 2Gi
limits:
cpu: "2"
memory: 4Gi
monitoring:
serviceMonitor:
enabled: true
Related¶
- CamundaCluster: referenced through
clusterRef. The operator adds the exporter entries tospec.zeebe.extraEnvof that cluster. - ManagementAuthConfig: referenced through
managementAuthRef. - SecondaryStorageConfig: the storage contract of the cluster. It carries the Elasticsearch endpoint and credentials.
- CamundaManagementCluster: writes the
ManagementAuthConfigand registers the login callbacks in the Keycloak modes. - LogicalBackupElasticsearch: backs up the cluster, with the
zeebe-recordindices. It does not back up the Optimize analytics indices. - ElasticsearchCluster: an Elasticsearch that the operator runs for the storage contract.