Basic example application¶
The components¶
This example shows how a hybrid-application
could be defined and deployed.
The term hybrid-application
means that parts of an application is deployed to cloud nodes and parts of it is deployed on edge nodes. All parts, wherever they are deployed, form the overall application.
This example consists of two parts:
- A
producer
that run on one or more edge nodes. It is a very simple application that produces simulated sensor data. - A
consumer
that run on cloud nodes and consume the sensor data. The consumer is a basic web application that can be viewed in a browser.
Both parts are connected to the edgefarm.network
. The producer drops the generated data in a stream that is buffered locally on the Edge Node. Note, that the producer can run on many edge nodes, each collecting data individually. As long as the Edge Devices are connected, the consuming part of the network aggregates the data from all Edge Nodes and puts them into another stream running in the cloud. The consumer
application reads this stream and provides the data via the web browser.
The following picture shows the overall architecture of the example:
The development¶
You can use whatever programming language you like to develop your application. This example uses golang for the producer and python for the consumer.
The source code of both consumer
and producer
are located here. Have a look if you are interesested in the details.
Both components are published as OCI images to ghcr.io.
The consumer
and producer
OCI images are published as ghcr.io/edgefarm/edgefarm/example-basic-consumer:latest
and ghcr.io/edgefarm/edgefarm/example-basic-producer:latest
as multi-arch images for amd64 and arm64.
The deployment¶
Here you'll learn how the manifests of the producer
application and the network
are defined. The consumer
is a standard Kubernetes deployment and service resource.
Producer explained¶
The manifest files are located here.
The producer is deployed to the edge nodes. The following snippet shows the edgefarm.applications manifest:
apiVersion: core.oam.dev/v1beta1 #(1)!
kind: Application
metadata:
name: example-producer #(2)!
spec:
components: #(3)!
- name: producer #(4)!
type: edgefarm-applications #(5)!
properties:
image: ghcr.io/edgefarm/edgefarm/example-basic-producer:latest #(6)!
nodepoolSelector: #(7)!
matchLabels:
example: "producer" #(8)!
name: producer #(9)!
cpu: 0.25 #(10)!
memory: 256Mi #(11)!
traits: #(12)!
- type: edgefarm-network #(13)!
properties:
network:
name: example-network #(14)!
subnetwork: edge-to-cloud #(15)!
user: publish #(16)!
- Every application is of
kind: Application
andapiversion: core.oam.dev/v1beta1
. - Give your application a meaningful name. This name is used to identify the application resource in the cluster.
- The
components
section defines the components that are part of the application. In this case, there is only one component calledproducer
. - The
name
of the component. This name must be unique between all components of the application. - The
type
of the component. Usingedgefarm-applications
means that the component is deployed to edge nodes. - The
image
of the component. This is the OCI image that is deployed to the edge nodes. - Every Edge Node is located in it's unique nodepool called the same as the node. The
nodepoolSelector
defines which edge nodes shall run the component. - This example uses a label called
example=producer
to identify the edge nodes that shall run the component. The label is set on the edge node usingkubectl label nodepools.apps.openyurt.io <your node> example=producer
. - The
name
of the container to run. - The
cpu
resources the container is allowed to consume. - The
memory
the container is allowed to consume. traits
are additional configuration parameters that can be added to the component.- There is one trait of type
edgefarm-network
added to the component. This trait is used to connect the component to theedgefarm.network
. - The
name
of the network to connect to. - The
subnetwork
of the network to connect to. - The
user
of the network to connect with.
The application contains one component called producer
that runs our OCI image mentioned before. The component is deployed to all nodes, that the corresponding nodepool has the label example=producer
. There are also some limits defined how much CPU resources and memory the container is allowed to consume. If the application shall be enabled to communicate with a network, a trait of type edgefarm-network
must be added. In this case, the component is connected to the network example-network
and the user publish
is allowed to publish data to the network. We can define multiple sub-networks in the network definition. In this case, the component is connected to the sub-network edge-to-cloud
.
We referenced a edgefarm.network
. So let's define it. Without the network resource the application would not be able to start. The following snippet shows the network definition:
apiVersion: streams.network.edgefarm.io/v1alpha1 #(1)!
kind: Network
metadata:
name: example-network #(2)!
spec:
compositeDeletePolicy: Foreground #(3)!
parameters: #(4)!
users: #(5)!
- name: publish #(6)!
limits: #(7)!
payload: -1 #(8)!
data: -1 #(9)!
subscriptions: -1 #(10)!
permissions: #(11)!
pub: #(12)!
allow: #(13)!
- "*.sensor" #(14)!
- "$JS.API.CONSUMER.>"
- "$JS.ACK.>"
deny: [] #(15)!
sub: #(16)!
allow: #(17)!
- "*.sensor"
deny: [] #(18)!
subNetworks: #(19)!
- name: edge-to-cloud #(20)!
limits: #(21)!
fileStorage: 1G #(22)!
inMemoryStorage: 100M #(23)!
nodepoolSelector: #(24)!
matchLabels:
example: "producer" #(25)!
streams: #(26)!
- name: sensor-stream #(27)!
type: Standard #(28)!
subNetworkRef: edge-to-cloud #(29)!
config:
subjects: #(30)!
- "sensor" #(31)!
discard: Old #(32)!
retention: Limits #(33)!
storage: File #(34)!
maxConsumers: -1 #(35)!
maxMsgSize: -1 #(36)!
maxMsgs: -1 #(37)!
maxMsgsPerSubject: -1 #(38)!
maxBytes: 10000000 #(39)!
- name: aggregate-stream #(40)!
type: Aggregate #(41)!
config:
discard: Old #(32)!
retention: Limits #(43)!
storage: File #(44)!
maxConsumers: -1
maxMsgSize: -1
maxMsgs: -1
maxMsgsPerSubject: -1
maxBytes: 500000000 #(45)!
references: #(46)!
- sensor-stream #(47)!
- Every network is of
kind: Network
andapiversion: streams.network.edgefarm.io/v1alpha1
. - Give your network a meaningful name. This name is used to identify the network resource in the cluster.
- The
compositeDeletePolicy
defines how the network is deleted. This is mandatory to set toForeground
to prevent the network from being deleted before all components are deleted. - The
parameters
section defines the parameters of the network. - The
users
section defines the users that are allowed to publish or subscribe to the network. - The
name
of the user. - Users can be limited in their actions. The
limits
section defines the limits of the user. - The
payload
limit defines how much data a user is allowed to publish to the network.-1
means unlimited. - TBD
- The
subscriptions
limit defines how many subscriptions a user is allowed to create.-1
means unlimited. - The
permissions
section defines the permissions of the user regarding publishing and subscribing. - The
pub
section defines the permissions for publishing. allow
defines which subjects the user is allowed to publish to.*.sensor
means that the user is allowed to publish to any subject that ends with.sensor
. See (Subjet-Based messaging)[https://docs.nats.io/nats-concepts/subjects] for more information on how subjects work.deny
defines which subjects the user is not allowed to publish to.- The
sub
section defines the permissions for subscribing. allow
defines which subjects the user is allowed to subscribe to.deny
defines which subjects the user is not allowed to subscribe to.- The
subNetworks
section defines the sub-networks that are part of the network. Imagine you have two types of Edge Nodes. One is highly potent while the other has a smaller footprint. The potent one can reserve way more file storage than the smaller one. For both types you can define differnt sub-networks with different characteristics. name
is of the sub-network. This get referenced in theedgefarm.application
manifest.
The network section is split up into several sub-sections in the spec.
There are users
that are allowed to publish or subscribe to specific subjects.
There are subNetworks
that specifies which parts of the network shall run on which nodes. In this case, the sub-network edge-to-cloud
is deployed to all nodes that have the label example=producer
and have some file-storage and memory limits set.
There are streams
that basically act as buckets. Each bucket has a configurable size. It is configured how long data is kept in the bucket and how much data can be stored in the bucket. It can be defined what to do if the bucket is full. It can drop old messages or block incoming messages. The streams can be defined where to run. They can be either be located in the cloud (no subNetworkRef) or on a edge node (subNetworkRef is set).
In this example there are two stream definitions that basically act like this:
sensor-stream
: Create a bucket with the given size and characteristics on each edge node that matches the subNetworks selector. Each edge node gets its own, unique instance of the stream located on the device. The bucket is named sensor-stream
. The bucket is configured to accept messages with the subject sensor
. The size is 10000000 bytes. If the bucket is full, drop old messages.
aggregate-stream
: Create a bucket with the given size and characteristics in the cloud. The bucket is named aggregate-stream
. The size is 500000000 bytes. If the bucket is full, drop old messages. The bucket is referenced to the sensor-stream
meaning that it aggregates all data from all sensor stream instances.
There is also a user called publish
that is allowed to publish n specific subjects - also "*.sensor". The *
acts as a wildcard. This is needed, because the producer prefixes it's messages with its unique name.
The suNetwork cloud-to-edge
defines that each matching edge node is equipped with a component that is part of that specific edgefarm.network. In the end, there is a pod running on each edge node that connects to the network.
Consumer explained¶
The manifest files are located in ./consumer/deploy
The consumer is deployed to the cloud nodes. The following snippet shows a standard Kubernetes deployment manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/instance: example-consumer
name: example-consumer
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/instance: example-consumer
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app.kubernetes.io/instance: example-consumer
spec:
containers:
- env:
- name: NATS_SERVER
value: nats://nats.nats.svc:4222
- name: NATS_EXPORT_SUBJECT
value: "*.sensor"
- name: NATS_STREAM_NAME
value: aggregate-stream
- name: NATS_CREDS
value: /creds/network.creds
image: ghcr.io/edgefarm/edgefarm/example-basic-consumer:latest
imagePullPolicy: IfNotPresent
name: consumer
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 250m
memory: 128Mi
volumeMounts:
- mountPath: /creds/network.creds
name: creds
readOnly: true
subPath: creds
restartPolicy: Always
volumes:
- name: creds
secret:
defaultMode: 420
secretName: example-network-publish
This defines a deployment manifest that runs our OCI images referenced earlier. It uses the network credentials as volumes to create the connection to the network. The consumer is configured to consume all messages with the subject *.sensor
from the stream aggregate-stream
.
To be able to access the consumer
application we need a standard Kubernetes service resoure.
apiVersion: v1
kind: Service
metadata:
name: example-consumer
spec:
ports:
- port: 5006
targetPort: 5006
selector:
app.kubernetes.io/instance: example-consumer
This service exposes the port 5006 of the consumer application to the cluster.
In Action¶
Deploy both, producer and consumer, to the cluster:
$ kubectl apply -f ./producer/deploy
application.core.oam.dev/example-producer created
network.streams.network.edgefarm.io/example-network created
$ kubectl apply -f ./consumer/deploy
deployment.apps/example-consumer created
service/example-consumer created
Decide what edge nodes shall run the producer component:
$ kubectl label nodepools.apps.openyurt.io edgefarm-worker3 example=producer
nodepool.apps.openyurt.io/edgefarm-worker3 labeled
Wait until everything is up and running:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
example-consumer-d69db86c8-n25vb 1/1 Running 0 10m 10.244.3.35 edgefarm-worker <none> <none>
example-network-default-edge-to-cloud-edgefarm-worker3-5fqfsxl5 1/1 Running 0 12m 10.244.1.5 edgefarm-worker3 <none> <none>
producer-edgefarm-worker3-s9pbw-5d6f874f65-qfqmf 2/2 Running 0 16m 10.244.1.6 edgefarm-worker3 <none> <none>
Check the streams that are created and watch the messages flowing in:
$ kubectl get streams.nats.crossplane.io -o wide
NAME EXTERNAL-NAME READY SYNCED DOMAIN AGE ADDRESS ACCOUNT PUB KEY MESSAGES BYTES CONSUMERS
example-network-25gn7-6bhcs aggregate-stream True True main 10m nats://nats.nats.svc:4222 ACDB55OTMWZM6LP4R3I3E5WRLJWWVHCWEBLN5ECYOQCN3BTH5NPDMLD4 321 2.0 MB 1
example-network-25gn7-qxc2v sensor-stream True True example-network-default-edge-to-cloud-edgefarm-worker3 10m nats://nats.nats.svc:4222 ACDB55OTMWZM6LP4R3I3E5WRLJWWVHCWEBLN5ECYOQCN3BTH5NPDMLD4 321 1.9 MB 0
Forward the service of the consumer
to your local machine and open a browser at http://localhost:5006/serve.