Camunda is used as the default BPMN engine for workflows. Service Tasks are implemented in TypeScript using camunda-external-task-client-js, packaged as Docker and deployed to Kubernetes.
Free Whitepaper – The BPM Renaissance: How to Thrive in a Cloud-native World [PDF]
To design BPMN workflow collaboratively, you can use Cawemo (you can request an invite from CTO). Note that Cawemo does not support Camunda-specific features, so to make the BPMN executable you still need to use Camunda Modeler.
- Camunda Best Practices: Invoking Services from the Process
- Camunda Best Practices: Dealing with Problems and Exceptions
Recommended approach is to use External Task with a worker (pull pattern). This is more portable across Camunda and Zeebe, more scalable, and generally easier to implement because it does not use the HTTP API connector. The downside is it typically Docker/Kubernetes so cannot be serverless (as a workaround, it’s possible to schedule a serverless function periodically to poll, but not recommended).
Camunda
Kubernetes Deployment
We use AWS RDS MariaDB for persistence. First we need to create kubectl secret camunda
with keys:
- mariadb-url
- mariadb-password
- rocketchat-loviateam-user-id
- rocketchat-loviateam-auth-token
- rocketchat-miluv-user-id
- rocketchat-miluv-auth-token
Then create camunda.yaml
deployment, camunda-service.yaml
, and camunda-ingress.yaml
.
With AWS RDS MariaDB, DB_VALIDATE_ON_BORROW
must be true
. Reference: AWS RDS wait_timeout seconds default is 28800, Tomcat JDBC validationInterval default is 3000 ms or 3 seconds)
camunda.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: camunda
spec:
selector:
matchLabels:
app: camunda
replicas: 1
template:
metadata:
labels:
app: camunda
spec:
containers:
- name: camunda
# image: camunda/camunda-bpm-platform:run-7.13.0-alpha4
# https://jira.camunda.com/browse/CAM-11840
image: hendy/camunda-bpm-platform:run-7.13.0-alpha4-CAM-11840
imagePullPolicy: Always
# https://github.com/camunda/camunda-bpm-platform/blob/master/distro/run/assembly/resources/production.yml
args: ['./camunda.sh', '--production']
ports:
- containerPort: 8080
name: http
protocol: TCP
env:
# https://github.com/camunda/docker-camunda-bpm-platform/issues/137
- name: JAVA_OPTS
value: ''
# Change back to HTTP port 8080 because we terminate SSL at nginx-ingress
- name: SERVER_PORT
value: '8080'
- name: SERVER_SSL_ENABLED
value: 'false'
- name: SERVLET_SESSION_COOKIE_SECURE
value: 'false'
- name: DB_DRIVER
value: com.mysql.jdbc.Driver
- name: DB_URL
valueFrom:
secretKeyRef:
name: camunda
key: mariadb-url
- name: DB_USERNAME
value: camunda
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: camunda
key: mariadb-password
- name: DB_VALIDATE_ON_BORROW
value: 'true'
- name: CAMUNDA_BPM_RUN_AUTH_ENABLED
value: 'true'
- name: CAMUNDA_BPM_RUN_CORS_ENABLED
value: 'true'
- name: CAMUNDA_BPM_RUN_CORS_ALLOWED_ORIGINS
value: 'file://,http://localhost:3000,https://marketing-kit.lovia.life'
readinessProbe:
failureThreshold: 10
httpGet:
path: /app/welcome/default/
port: http
scheme: HTTP
initialDelaySeconds: 15
periodSeconds: 10
resources:
requests:
memory: 400Mi
cpu: 100m
limits:
memory: 400Mi
camunda-service.yaml
apiVersion: v1
kind: Service
metadata:
name: camunda
labels:
app: camunda
spec:
selector:
app: camunda
ports:
- name: http
port: 8080
protocol: TCP
targetPort: http
Secure Camunda first:
- Create a new user, add to camunda-admin group. This will become the new admin user.
- Delete all demo users.
- Enable REST API authentication.
Before adding the ingress we’ll need to:
- Configure k8s-lovia-sg’s AWS CloudFront Distribution to add Alternate CNAME camunda.lovia.life
- Add
CNAME
record forcamunda.lovia.life
to point to that AWS CloudFront Distribution
camunda-ingress.yaml
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: camunda-ingress
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
# REQUIRES helm cert-manager
tls:
- hosts:
- camunda.lovia.life
secretName: camunda-tls
rules:
- host: camunda.lovia.life
http:
paths:
- backend:
serviceName: camunda
servicePort: 8080
Check SSL certificate status:
kubectl get cert -o wide
Camunda Tomcat
We use Camunda BPM Run Docker distribution to simplify configuration and maintenance, so this section is for historical purpose only.
Prepare the Tomcat server and engine-rest configuration as Config Maps. The important part in conf/server.xml
(warning: it contains username & password credentials): (reference: AWS RDS wait_timeout seconds default is 28800, Tomcat JDBC validationInterval default is 3000 ms or 3 seconds)
Then create camunda.yaml
deployment, camunda-service.yaml
, and camunda-ingress.yaml
.
<Resource name="jdbc/ProcessEngine" auth="Container" type="javax.sql.DataSource"
...
testOnBorrow="true" validationQuery="SELECT 1" validationInterval="30000"/>
And the important part in engine-rest/WEB-INF/web.xml
:
<!-- Http Basic Authentication Filter -->
<filter>
<filter-name>camunda-auth</filter-name>
<filter-class>
org.camunda.bpm.engine.rest.security.auth.ProcessEngineAuthenticationFilter
</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>authentication-provider</param-name>
<param-value>org.camunda.bpm.engine.rest.security.auth.impl.HttpBasicAuthenticationProvider</param-value>
</init-param>
<init-param>
<param-name>rest-url-pattern-prefix</param-name>
<param-value></param-value>
</init-param>
</filter>
kubectl create configmap camunda-conf --from-file=conf/server.xml
kubectl create configmap camunda-engine-rest --from-file=engine-rest/WEB-INF/web.xml
apiVersion: apps/v1
kind: Deployment
metadata:
name: camunda
spec:
selector:
matchLabels:
app: camunda
replicas: 1
template:
metadata:
labels:
app: camunda
spec:
containers:
- name: camunda
image: camunda/camunda-bpm-platform:7.12.0
ports:
- containerPort: 8080
name: http
protocol: TCP
env:
- name: DB_DRIVER
value: com.mysql.jdbc.Driver
- name: DB_URL
valueFrom:
secretKeyRef:
name: camunda
key: mariadb-url
- name: DB_USERNAME
value: camunda
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: camunda
key: mariadb-password
- name: ROCKETCHAT_LOVIATEAM_USER_ID
valueFrom:
secretKeyRef:
name: camunda
key: rocketchat-loviateam-user-id
- name: ROCKETCHAT_LOVIATEAM_AUTH_TOKEN
valueFrom:
secretKeyRef:
name: camunda
key: rocketchat-loviateam-auth-token
- name: ROCKETCHAT_MILUV_USER_ID
valueFrom:
secretKeyRef:
name: camunda
key: rocketchat-miluv-user-id
- name: ROCKETCHAT_MILUV_AUTH_TOKEN
valueFrom:
secretKeyRef:
name: camunda
key: rocketchat-miluv-auth-token
volumeMounts:
- name: engine-rest
subPath: web.xml
mountPath: /camunda/webapps/engine-rest/WEB-INF/web.xml
- name: conf
subPath: server.xml
mountPath: /camunda/conf/server.xml
- name: empty-dir
mountPath: /camunda/webapps/ROOT
readOnly: true
- name: empty-dir
mountPath: /camunda/webapps/camunda-invoice
readOnly: true
- name: empty-dir
mountPath: /camunda/webapps/docs
readOnly: true
- name: empty-dir
mountPath: /camunda/webapps/examples
readOnly: true
- name: empty-dir
mountPath: /camunda/webapps/host-manager
readOnly: true
- name: empty-dir
mountPath: /camunda/webapps/camunda-welcome
readOnly: true
- name: empty-dir
mountPath: /camunda/webapps/h2
readOnly: true
- name: empty-dir
mountPath: /camunda/webapps/manager
readOnly: true
readinessProbe:
failureThreshold: 10
httpGet:
path: /camunda/app/admin/default/
port: http
scheme: HTTP
initialDelaySeconds: 15
periodSeconds: 10
resources:
requests:
memory: 400Mi
cpu: 100m
limits:
memory: 400Mi
volumes:
- name: engine-rest
configMap:
name: camunda-engine-rest
- name: conf
configMap:
name: camunda-conf
- name: empty-dir
emptyDir: {}
Zeebe
Deploy Zeebe Workflow
ceefour@amanah:~/project/miluv/miluv-workers$ zbctl deploy workflows/miluv-signIn.bpmn
{
"key": 2251799813690820,
"workflows": [
{
"bpmnProcessId": "miluv-signIn",
"version": 1,
"workflowKey": 2251799813690819,
"resourceName": "workflows/miluv-signIn.bpmn"
}
]
}
Create Zeebe Workflow Instance
zbctl create instance miluv-signIn --variables '{"user": {"id": 466, "name": "Hendy Irawan", "slug": "hendy"}}'
Using the HTTP Connector to call REST API
The recommended, future-proof way of implementing Service Tasks is using pull workers. Although REST APIs can be easier to implement and test (using Postman).
You can implement the REST API using NestJS+Fastify (TypeScript) or FastAPI (Python).
Examples:
- Complete example: Handling government business processes across administrative divisions — DigitalState.io
- camunda-bpm-examples/servicetask/rest-service
- Using Jsoup for more flexibility
Authorizations
“guest” account: To start processes from guest account (with CORS “semi-authentication”), the guest must have these authorizations:
- Process Definition: READ and CREATE_INSTANCE on the specific Process ID.
- Process Instance: CREATE_INSTANCE on *.