Walkthrough of Implementing a Custom Discovery Handler and Broker
This document will walk through an end-to-end example of creating Discovery Handler to discover HTTP-based devices that publish random sensor data. It will also walk through how to create a custom broker to leverage the discovered devices. Reference the Discovery Handler development and broker Pod development documents if you prefer generic documentation over an example.
Before continuing, you may wish to reference the Akri architecture and Akri agent documentation. They will provide a good understanding of Akri, how it works, and what components it is composed of.
Any Docker-compatible container registry will work for hosting the containers being used in this example (Docker Hub, Github Container Registry, Azure Container Registry, etc). Here, we are using the GitHub Container Registry. You can follow the getting started guide here to enable it for yourself.
if your container registry is private, you will need to create a kubernetes secret kubectl create secret docker-registry crPullSecret --docker-server=<cr> --docker-username=<cr-user> --docker-password=<cr-token>
and access it with an imagePullSecret
. Here, we will assume the secret is named crPullSecret
.
Background on Discovery Handlers
Akri has implemented discovery via several protocols with sample brokers and applications to demonstrate usage. However, there may be protocols you would like to use to discover resources that have not been implemented as Discovery Handlers yet. To enable the discovery of resources via a new protocol, you will implement a Discovery Handler (DH), which does discovery on behalf of the Agent. A Discovery Handler is anything that implements the Discovery
service and Registration
client defined in the Akri's discovery gRPC proto file. These DHs run as their own Pods and are expected to register with the Agent, which hosts the Registration
service defined in the gRPC interface.
New DiscoveryHandler implementation
Use cargo generate
to clone the Discovery Handler template
cargo generate
to clone the Discovery Handler templateInstall cargo-generate
and use the tool to pull down Akri's template, specifying the name of the project with the --name
parameter.
Specify the DiscoveryHandler name and whether discovered devices are sharable
Inside the newly created akri-http-discovery-handler
project, navigate to main.rs
. It contains all the logic to register our DiscoveryHandler
with the Akri Agent. We only need to specify the DiscoveryHandler
name and whether the device discovered by our DiscoveryHandler
can be shared. Set name
equal to "http"
and shared
to true
, as our HTTP Discovery Handler will discover devices that can be shared between nodes. The protocol name also resolves to the name of the socket the Discovery Handler will run on.
Decide what information is passed via an Akri Configuration
Akri's Configuration CRD takes in a DiscoveryHandlerInfo
, which is defined structurally as follows:
When creating a Discovery Handler, you must decide what name or label to give it and add any details you would like your Discovery Handler to receive in the discovery_details
string. The Agent passes this string to Discovery Handlers as part of a DiscoverRequest
. A discovery handler must then parse this string -- Akri's built in Discovery Handlers store an expected structure in it as serialized YAML -- to determine what to discover, filter out of discovery, and so on. In our case, no parsing is required, as it will simply put our discovery endpoint. Our implementation will ping the discovery service at that URL to see if there are any devices.
Ultimately, the Discovery Handler section of our HTTP Configuration will look like the following.
Now that we know what will be passed to our Discovery Handler, let's implement the discovery functionality.
Add discovery logic to the DiscoveryHandler
DiscoveryHandler
A DiscoveryHandlerImpl
Struct has been created (in discovery_handler.rs
) that minimally implements the DiscoveryHandler
service. Let's fill in the discover
function, which returns the list of discovered devices. It should have all the functionality desired for discovering devices via your protocol and filtering for only the desired set. For the HTTP protocol, discover
will perform an HTTP GET on the Discovery Handler's discovery service URL received in the DiscoverRequest
.
First, let's add the additional crates we are using to our Cargo.toml
under dependencies.
Now, import our dependencies and define some constants. Add the following after the other imports at the top of discovery_handler.rs
.
Fill in your discover
function so as to match the following. Note, discover
creates a streamed connection with the Agent, where the Agent gets the receiving end of the channel and the Discovery Handler sends device updates via the sending end of the channel. If the Agent drops its end, the Discovery Handler will stop discovery and attempt to re-register with the Agent. The Agent may drop its end due to an error or a deleted Configuration.
Build the DiscoveryHandler container
Now you are ready to build your HTTP discovery handler and push it to your container registry. To do so, we simply need to run this step from the base folder of the Akri repo:
Save the name of your image. We will pass it into our Akri installation command when we are ready to deploy our discovery handler.
Create some HTTP devices
At this point, we've extended Akri to discover devices with our HTTP Discovery Handler, and we've created an HTTP broker that can be deployed. To really test our new discovery and brokers, we need to create something to discover.
For this exercise, we can create an HTTP service that listens to various paths. Each path can simulate a different device by publishing some value. With this, we can create a single Kubernetes pod that can simulate multiple devices. To make our scenario more realistic, we can add a discovery endpoint as well. Further, we can create a series of Kubernetes services that create facades for the various paths, giving the illusion of multiple devices and a separate discovery service.
To that end, let's:
Create a web service that mocks HTTP devices and a discovery service
Deploy, start, and expose our mock HTTP devices and discovery service
Mock HTTP devices and Discovery service
To simulate a set of discoverable HTTP devices and a discovery service, create a simple HTTP server (samples/apps/http-apps/cmd/device/main.go
). The application will accept a list of path
arguments, which will define endpoints that the service will respond to. These endpoints represent devices in our HTTP Discovery Handler. The application will also accept a set of device
arguments, which will define the set of discovered devices.
To ensure that our GoLang project builds, we need to create samples/apps/http-apps/go.mod
:
Build and Deploy devices and discovery
To build and deploy the mock devices and discovery, a simple Dockerfile can be created that builds and exposes our mock server samples/apps/http-apps/Dockerfiles/device
:
And to deploy, use docker build
and docker push
:
The mock devices can be deployed with a Kubernetes deployment samples/apps/http-apps/kubernetes/device.yaml
(update image based on the ${IMAGE}):
Then apply device.yaml
to create a deployment (called device
) and a pod (called device-...
):
We're using one deployment|pod to represent 9 devices AND a discovery service ... we will create 9 (distinct) Services against it (1 for each mock device) and 1 Service to present the discovery service.
Then create 9 mock device Services:
Optional: check one the services:
Then, pick a value for
X
between 1 and 9:Any or all of these should return a (random) 'sensor' value.
Then create a Service (called discovery
) using the deployment:
Optional: check the service to confirm that it reports a list of devices correctly using:
Then, curl the service's endpoint:
This should return a list of 9 devices, of the form
http://device-X:8080
Deploy Akri
Now that we have created a HTTP Discovery Handler and created some mock devices, let's deploy Akri and see how it discovers the devices and creates Akri Instances for each Device.
Optional: If you've previous installed Akri and wish to reset, you may:
Akri has provided helm templates for custom Discovery Handlers and their Configurations. These templates are provided as a starting point. They may need to be modified to meet the needs of a Discovery Handler. When installing Akri, specify that you want to deploy a custom Discovery Handler as a DaemonSet by setting custom.discovery.enabled=true
. Specify the container for that DaemonSet as the HTTP discovery handler that you built above by setting custom.discovery.image.repository=$DH_IMAGE
and custom.discovery.image.repository=$TAGS
. To automatically deploy a custom Configuration, set custom.configuration.enabled=true
. We will customize this Configuration to contain the discovery endpoint needed by our HTTP Discovery Handler by setting it in the discovery_details
string of the Configuration, like so: custom.configuration.discoveryDetails=http://discovery:9999/discovery
. We also need to set the name the Discovery Handler will register under (custom.configuration.discoveryHandlerName
) and a name for the Discovery Handler and Configuration (custom.discovery.name
and custom.configuration.name
). All these settings come together as the following Akri installation command:
Note: See the cluster setup steps for information on how to set the crictl configuration variable
AKRI_HELM_CRICTL_CONFIGURATION
Watch as the Agent, Controller, and Discovery Handler Pods are spun up and as Instances are created for each of the discovery devices.
If you simply wanted Akri to expose discovered devices to the cluster as Kubernetes resources, you could stop here. If you have a workload that could utilize one of these resources, you could manually deploy pods that request them as resources. Alternatively, you could have Akri automatically deploy workloads to discovered devices. We call these workloads brokers. To quickly see this, lets deploy empty nginx pods to discovered resources, by updating our Configuration to include a broker PodSpec.
Our empty nginx brokers do not do anything with the devices they've requested, so lets create our own broker.
Create a sample broker
We have successfully created our Discovery Handler. If you want Akri to also automatically deploy Pods (called brokers) to each discovered device, this section will show you how to create a custom broker that will make the HTTP-based Device data available to the cluster. The broker can be written in any language as it will be deployed as an individual pod.
3 different broker implementations have been created for the HTTP Discovery Handler in the http-extensibility branch, 2 in Rust and 1 in Go:
The standalone broker is a self-contained scenario that demonstrates the ability to interact with HTTP-based devices
by
curl
ing a device's endpoints. This type of solution would be applicable in batch-like scenarios where the brokerperforms a predictable set of processing steps for a device.
The second scenario uses gRPC. gRPC is an increasingly common alternative to REST-like APIs and supports
high-throughput and streaming methods. gRPC is not a requirement for broker implementations in Akri but is used here
as one of many mechanisms that may be used. The gRPC-based broker has a companion client. This is a more realistic
scenario in which the broker proxies client requests using gRPC to HTTP-based devices. The advantage of this approach
is that device functionality is encapsulated by an API that is exposed by the broker. In this case the API has a
single method but in practice, there could be many methods implemented.
The third implementation is a gRPC-based broker and companion client implemented in Golang. This is functionally
equivalent to the Rust implementation and shares a protobuf definition. For this reason, you may combine the Rust
broker and client with the Golang broker and client arbitrarily. The Golang broker is described in the
http-apps
directory.
For this, we will describe the first option, a standalone broker. For a more detailed look at the other gRPC options, please look at extensibility-http-grpc.md in the http-extensibility branch.
First, let's create a new Rust project for our sample broker. We can use cargo to create our project by navigating to samples/brokers
and running:
Once the http project has been created, it can be added to the greater Akri project by adding "samples/brokers/http"
to the members in ./Cargo.toml
.
To access the HTTP-based Device data, we first need to retrieve the discovery information. Any information stored in the Device
properties map will be transferred into the broker container's environment variables. Retrieving them is simply a matter of querying environment variables like this:
For our HTTP broker, the data can be retrieved with a simple GET:
We can tie all the pieces together in samples/brokers/http/src/main.rs
. We retrieve the HTTP-based Device url from the environment variables, make a simple GET request to retrieve the device data, and output the response to the log:
and ensure that we have the required dependencies in samples/brokers/http/Cargo.toml
:
To build the HTTP broker, we need to create a Dockerfile, samples/brokers/http/Dockerfiles/standalone
:
Akri's .dockerignore
is configured so that docker will ignore most files in our repository, some exceptions will need to be added to build the HTTP broker:
Now you are ready to build the HTTP broker! To do so, we simply need to run this step from the base folder of the Akri repo:
Deploy broker
Now that the HTTP broker has been created, we can substitute it's image in for the simple nginx broker we previously used in our installation command.
Note: substitute
helm upgrade
forhelm install
if you do not have an existing Akri installation
We can watch as the broker pods get deployed:
Last updated