Respond to CloudEvents published over NATS and JetStream
NATS is a lightweight, high-performance messaging system designed for speed and scalability. It excels at simple publish/subscribe communication. JetStream builds upon NATS, adding powerful streaming and data management capabilities like message persistence, flow control, and ordered delivery. Together, NATS and JetStream provide a robust platform for building modern, distributed systems.
CloudEvents is a specification for describing event data in a consistent way, regardless of the underlying system or transport protocol. It simplifies event handling by providing a common format that different systems can understand, enabling interoperability and reducing complexity. Think of it as a universal language for events.
Sveltos can be configured to connect to and respond to CloudEvents published over NATS and JetStream.
Connect to NATS and JetStream
To configure Sveltos to connect to NATS and/or JetStream within a managed cluster, create a Secret named sveltos-nats
in the projectsveltos
namespace. This Secret's data should contain a key also named sveltos-nats
with the connection details.
For example, to connect to a NATS server exposed as the nats
service in the nats
namespace on port 4222, with username/password authentication, and for Sveltos to subscribe to the bar and foo subjects, use the following configuration:
{
"nats":
{
"configuration":
{
"url": "nats://nats.nats.svc.cluster.local:4222",
"subjects": [
"test",
"foo"
],
"authorization": {
"user": {
"user": "admin",
"password": "my-password"
}
}
}
}
}
then create a Secret with it
kubectl create secret generic -n projectsveltos sveltos-nats --from-file=sveltos-nats=nats-configuration
The sveltos-agent automatically detects and reacts to changes in this Secret. Take a look at this to see full NATS/JetStream configuration options.
Define an Event
When Sveltos receives a CloudEvent on a subscribed subject, it can trigger a specific operation known as an Event. Define these Events using the EventSource CRD.
For example, the following EventSource defines an Event triggered by any message received on the user-login subject with a CloudEvent source of auth.example.com/user-login:
apiVersion: lib.projectsveltos.io/v1beta1
kind: EventSource
metadata:
name: user-login
spec:
messagingMatchCriteria:
- subject: "user-login"
cloudEventSource: "auth.example.com/user-login"
Following criteria can be used to narrow down the events that Sveltos will react to:
-
Subject: This field filters based on the NATS/JetStream subject the CloudEvent is received on. It allows you to specify a particular subject or use regular expressions for pattern matching. If left empty, the EventSource will consider CloudEvents from any of the subjects Sveltos is subscribed to. This is useful for broadly catching events across various subjects.
-
CloudEventSource: This filters CloudEvents based on the source attribute within the CloudEvent itself. This allows you to distinguish events originating from different systems or components. Like Subject, it supports regular expressions for flexible matching.
-
CloudEventType: This filters CloudEvents based on their type attribute. The type attribute describes the kind of event that occurred (e.g., com.example.order.created). Using this filter, you can target specific event types. Regular expressions are also supported here.
-
CloudEventSubject: This field filters CloudEvents based on the subject attribute within the CloudEvent, which is distinct from the NATS/JetStream subject. This provides another layer of filtering based on the event's content. Regular expressions are supported.
Define what to do in response to an Event
EventTrigger is the CRD introduced to define what add-ons to deploy when an event happens.
Each EvenTrigger instance:
- References an EventSource (which defines what the event is);
- Has a sourceClusterSelector selecting one or more managed clusters; 1
- Contains a list of add-ons to deploy
The following example demonstrates how to trigger the creation of a Namespace when a user logs in. It uses an EventTrigger referencing the user-login
EventSource (defined previously) and a ConfigMap template. Sveltos instantiates the ConfigMap template, creating a new Namespace. The Namespace's name is taken from the CloudEvent's subject, and a label is added based on the message within the CloudEvent's data.
apiVersion: lib.projectsveltos.io/v1beta1
kind: EventTrigger
metadata:
name: create-namespace
spec:
sourceClusterSelector:
matchLabels:
env: fv
eventSourceName: user-login
oneForEvent: true
cloudEventAction: Create
policyRefs:
- name: namespace
namespace: default
kind: ConfigMap
---
apiVersion: v1
kind: ConfigMap
metadata:
name: namespace
namespace: default
annotations:
projectsveltos.io/instantiate: ok
data:
namespace.yaml: |
kind: Namespace
apiVersion: v1
metadata:
name: {{ .CloudEvent.subject }} # .CloudEvent is the triggering CloudEvent
labels:
organization: {{ ( index .CloudEvent.data `organization` ) }}
The Role of cloudEvent Source and Subject
Because CloudEvents describe events rather than actions, Sveltos uses the combination of cloudEvent Source and cloudEvent Subject to uniquely identify the resource associated with the event. This allows Sveltos to manage the lifecycle of resources even though CloudEvents don't have a built-in "delete" operation.
Let's say we want to automatically create a Namespace when a user logs in and delete that Namespace when they log off. We can achieve this by publishing CloudEvents to NATS subject user-operation. Critically, to link login and logout events to the same user, the cloudEvent Source and cloudEvent Subject (which should contain a unique user identifier in this example) must be the same in both the login and logout CloudEvents. We then define following EventSource instance to trigger the appropriate actions.
apiVersion: lib.projectsveltos.io/v1beta1
kind: EventSource
metadata:
name: user-operation
spec:
messagingMatchCriteria:
- subject: "user-operation"
cloudEventSource: "auth.example.com"
To complete the setup, we define the EventTrigger resource. The EventTrigger.Spec.CloudEventAction
field is
defined as a template. When CloudEvent type is com.example.auth.login its instantiated value is Create
(causing the namespace to be created anytime a user logins). The instantiated value is otherwise Delete
causing the namespace to be deleted upon a user logout.
apiVersion: lib.projectsveltos.io/v1beta1
kind: EventTrigger
metadata:
name: manage-namespace
spec:
eventSourceName: user-operation
cloudEventAction: "{{ if eq .CloudEvent.type 'auth.example.com.logout' }}Delete{{ else }}Create{{ end }}" # can be espressed as a template and instantiated using CloudEvent
policyRefs:
- name: namespace
namespace: default
kind: ConfigMap
---
apiVersion: v1
kind: ConfigMap
metadata:
name: namespace
namespace: default
annotations:
projectsveltos.io/instantiate: ok # ConfigMap contains a template and needs to be instantiated at run time
data:
namespace.yaml: |
kind: Namespace
apiVersion: v1
metadata:
name: {{ .CloudEvent.subject }} # The CloudEvent is available and can be used to instantiate the template
End to End Flow
Deploy NATS to all production clusters:
apiVersion: config.projectsveltos.io/v1beta1
kind: ClusterProfile
metadata:
name: nats
spec:
clusterSelector:
matchLabels:
env: production
helmCharts:
- chartName: nats/nats
chartVersion: 1.2.9
helmChartAction: Install
releaseName: nats
releaseNamespace: nats
repositoryName: nats
repositoryURL: https://nats-io.github.io/k8s/helm/charts/
values: |-
config:
merge:
authorization:
default_permissions:
publish: [">"]
subscribe: [">"]
users:
- user: "admin"
password: "my-password"
syncMode: Continuous
Once NATS is deployed, create a Secret in each production cluster instructing Sveltos to connect to the NATS server2:
apiVersion: config.projectsveltos.io/v1beta1
kind: ClusterProfile
metadata:
name: deploy-deploy-sveltos-nats-secret
spec:
dependsOn:
- nats
clusterSelector:
matchLabels:
env: production
policyRefs:
- name: deploy-sveltos-nats-secret
namespace: default
kind: Secret
---
apiVersion: v1
data:
sveltos-nats: YXBpVmVyc2lvbjogdjEKZGF0YToKICBzdmVsdG9zLW5hdHM6IGV3b2dJQ0p1WVhSeklqb0tJQ0FnZXdvZ0lDQWdJQ0pqYjI1bWFXZDFjbUYwYVc5dUlqb0tDWHNLQ1NBZ0lDQWlkWEpzSWpvZ0ltNWhkSE02THk5dVlYUnpMbTVoZEhNdWMzWmpMbU5zZFhOMFpYSXViRzlqWVd3Nk5ESXlNaUlzQ2drZ0lDQWdJbk4xWW1wbFkzUnpJam9nV3dvSkNTSjFjMlZ5TFc5d1pYSmhkR2x2YmlJS0NTQWdJQ0JkTEFvSklDQWdJQ0poZFhSb2IzSnBlbUYwYVc5dUlqb2dld29KQ1NKMWMyVnlJam9nZXdvSkNTQWdJQ0FpZFhObGNpSTZJQ0poWkcxcGJpSXNDZ2tKSUNBZ0lDSndZWE56ZDI5eVpDSTZJQ0p0ZVMxd1lYTnpkMjl5WkNJS0NRbDlDZ2tnSUNBZ2ZRb0pmUW9nSUNCOUNuMEsKa2luZDogU2VjcmV0Cm1ldGFkYXRhOgogIG5hbWU6IHN2ZWx0b3MtbmF0cwogIG5hbWVzcGFjZTogcHJvamVjdHN2ZWx0b3MKdHlwZTogT3BhcXVlCg==
kind: Secret
metadata:
name: deploy-sveltos-nats-secret
namespace: default
type: addons.projectsveltos.io/cluster-profile
Deploy following Sveltos configuration to create a namespace when a user login:
apiVersion: lib.projectsveltos.io/v1beta1
kind: EventSource
metadata:
name: user-operation
spec:
messagingMatchCriteria:
- subject: "user-operation"
cloudEventSource: "auth.example.com"
---
apiVersion: lib.projectsveltos.io/v1beta1
kind: EventTrigger
metadata:
name: manage-namespace
spec:
sourceClusterSelector:
matchLabels:
env: production
eventSourceName: user-operation
oneForEvent: true
syncMode: ContinuousWithDriftDetection
cloudEventAction: '{{if eq .CloudEvent.type "auth.example.com.logout"}}Delete{{else}}Create{{end}}'
policyRefs:
- name: namespace
namespace: default
kind: ConfigMap
---
apiVersion: v1
kind: ConfigMap
metadata:
name: namespace
namespace: default
annotations:
projectsveltos.io/instantiate: ok
data:
namespace.yaml: |
kind: Namespace
apiVersion: v1
metadata:
name: {{ .CloudEvent.subject }}
Send now a CloudEvent representing user mgianluc login:
CLOUDEVENT_JSON=$(cat << EOF
{
"specversion": "1.0",
"type": "auth.example.com.login",
"source": "auth.example.com",
"id": "10001",
"subject": "mgianluc",
"datacontenttype": "application/json",
"data": {
"message": "User mgianluc login"
}
}
EOF
)
KUBECONFIG=<production cluster kubeconfig> kubectl exec -it deployment/nats-box -n nats -- nats pub user-operation $CLOUDEVENT_JSON --user=admin --password=my-password
Verify namespace is created:
sveltosctl show addons
+-----------------------------+---------------+----------------+--------------+---------+-------------------------------+--------------------------------------------------+
| CLUSTER | RESOURCE TYPE | NAMESPACE | NAME | VERSION | TIME | PROFILES |
+-----------------------------+---------------+----------------+--------------+---------+-------------------------------+--------------------------------------------------+
| default/clusterapi-workload | helm chart | nats | nats | 1.2.9 | 2025-02-04 14:06:14 +0100 CET | ClusterProfile/nats |
| default/clusterapi-workload | :Secret | projectsveltos | sveltos-nats | N/A | 2025-02-04 14:06:36 +0100 CET | ClusterProfile/deploy-deploy-sveltos-nats-secret |
| default/clusterapi-workload | :Namespace | | mgianluc | N/A | 2025-02-04 14:12:03 +0100 CET | ClusterProfile/sveltos-gbv99bcdsk1aa04jkdzv |
+-----------------------------+---------------+----------------+--------------+---------+-------------------------------+--------------------------------------------------+
We can now send a CloudEvent for the logout operation (note the CloudEvent type):
CLOUDEVENT_JSON=$(cat << EOF
{
"specversion": "1.0",
"type": "auth.example.com.logout",
"source": "auth.example.com",
"id": "10001",
"subject": "mgianluc",
"datacontenttype": "application/json",
"data": {
"message": "User mgianluc logout"
}
}
EOF
)
KUBECONFIG=<production cluster kubeconfig> kubectl exec -it deployment/nats-box -n nats -- nats pub user-operation $CLOUDEVENT_JSON --user=admin --password=my-password
Verify the namespace has been deleted in response to the user logout CloudEvent:
sveltosctl show addons
+-----------------------------+---------------+----------------+--------------+---------+-------------------------------+--------------------------------------------------+
| CLUSTER | RESOURCE TYPE | NAMESPACE | NAME | VERSION | TIME | PROFILES |
+-----------------------------+---------------+----------------+--------------+---------+-------------------------------+--------------------------------------------------+
| default/clusterapi-workload | helm chart | nats | nats | 1.2.9 | 2025-02-04 14:06:14 +0100 CET | ClusterProfile/nats |
| default/clusterapi-workload | :Secret | projectsveltos | sveltos-nats | N/A | 2025-02-04 14:06:36 +0100 CET | ClusterProfile/deploy-deploy-sveltos-nats-secret |
+-----------------------------+---------------+----------------+--------------+---------+-------------------------------+--------------------------------------------------+
-
EventTrigger can also reference a ClusterSet to select one or more managed clusters. ↩
-
Secret contains following configuration:
↩{ "nats": { "configuration": { "url": "nats://nats.nats.svc.cluster.local:4222", "subjects": [ "user-operation" ], "authorization": { "user": { "user": "admin", "password": "my-password" } } } } }