Example: CPU to IBM Event Streams service

This example collects CPU load percentage information to send to IBM Event Streams. Use this example to help you develop your own edge applications that send data to other services.

Before you begin

Specifically, set up your Horizon credentials. These credentials are used by the commands that are referenced and used with this example. You also need to log in to your Docker registry and create your cryptographic signing keys, which are used for publishing.

Procedure

  1. Use your Git tools to view the cpu2msghub example directory and review the directory contents by running the following command:

     cd examples/edge/msghub/cpu2msghub
     ls -R
    

    Output example:

     Dockerfile.amd64  Dockerfile.arm64  README.md      horizon
     Dockerfile.arm    Makefile          cpu2msghub.md  service.sh
    
     ./horizon:
     dependencies  pattern-all-arches.json  service.definition.json  userinput.json
     hzn.json      pattern.json             use
    
     ./horizon/dependencies:
    
     ./horizon/use:
     README.md  userinput.json
    

    The root level of the directory includes a README.md file, 3 Dockerfile.* variants for three different hardware architectures, a Makefile, and the example service implementation in service.sh. There is also a horizon directory that contains several directories and files that are used for publishing the service, and for publishing a corresponding deployment pattern. The horizon directory also includes a horizon/use/userinput.json configuration file that is used when you are deploying code to edge machines.

  2. Follow the steps within the example readme file to set up and use the example service.

    The README.md file that is included within your newly cloned repository includes the detailed steps to create and use the sample edge service for this example. To view the steps within this file, see Building and publishing your own edge service Opens in a new tab.

    When you follow these steps, you build, test, push, and publish the example service to Horizon exchange. Then, you publish a corresponding deployment pattern that references the service, and you register an edge node to run the pattern and service. With the service started, you can view the output from the example service within your edge node log files to view how the service works.

Required services

The edge service (cpu2msghub) for this example depends on two other edge services (cpu and gps) to accomplish its task. You can see the details for these dependencies within the requiredServices section of the horizon/service.definition.json file. Those dependency declarations can look similar to the following code snippet:

    "requiredServices": [
        {
            "url": "github.com.open-horizon.examples.cpu",
            "org": "IBM",
            "version": "[0.0.0,INFINITY)",
            "arch": "$ARCH"
        },
        {
            "url": "github.com.open-horizon.examples.gps",
            "org": "IBM",
            "version": "[0.0.0,INFINITY)",
            "arch": "$ARCH"
        }
    ],

To enable access to dependent services from the services that require the dependent services, and to restrict the access of other containers, local Docker private virtual networks are used. A separate Docker network is created for each declared dependency. Each dependent service is on its own network. Only the service that depends on a dependent service is on the same network as the dependent service. This segmented networking is implemented automatically based on which service containers declared their dependencies upon which other service containers.

When an edge node is registered and in an agreement, the service containers are visible running in the docker ps output. By using the container IDs shown in this output, you can run docker inspect <container> to see more information about each container. The information includes the Docker virtual private networks to which each container is attached, and the name that can be used to find the container on those networks.

As an aid to development, you can use the hzn dev dependency fetch ... command to add entries into your requiredServices array. For more information run the following command to view the help information:

 hzn dev dependency fetch --help

Configuration Variables

The cpu2msghub service requires some configuration before it can run. Edge services can declare configuration variables, stating their type and providing default values. You can see these configuration variables that are detailed in the horizon/service.definition.json file, in the userInput section. Those variable declarations look similar to the following declarations:

    "userInput": [
        {
            "name": "MSGHUB_API_KEY",
            "label": "The API key to use when sending messages to your instance of IBM Message Hub",
            "type": "string",
            "defaultValue": ""
        },
        {
            "name": "MSGHUB_BROKER_URL",
            "label": "The comma-separated list of URLs to use when sending messages to your instance of IBM Message Hub",
            "type": "string",
            "defaultValue": ""
        },
        {
            "name": "MSGHUB_TOPIC",
            "label": "The topic to use when sending messages to your instance of IBM Message Hub",
            "type": "string",
            "defaultValue": "cpu2msghub"
        },
        {
            "name": "SAMPLE_SIZE",
            "label": "the number of samples to read before calculating the average",
            "type": "int",
            "defaultValue": "6"
        },
        {
            "name": "SAMPLE_INTERVAL",
            "label": "the number of seconds between samples",
            "type": "int",
            "defaultValue": "5"
        },
        {
            "name": "MOCK",
            "label": "mock the CPU sampling",
            "type": "boolean",
            "defaultValue": "false"
        },
        {
            "name": "PUBLISH",
            "label": "publish the CPU samples to IBM Message Hub",
            "type": "boolean",
            "defaultValue": "true"
        },
        {
            "name": "VERBOSE",
            "label": "log everything that happens",
            "type": "string",
            "defaultValue": "0"
        }
    ],

User input configuration variables like these declarations are required to have values when the edge service is started on the edge node. Normally, these variables must be provided by the edge node owner at the moment of registration. If the variables are not provided at registration time, you can use a default value that is provided in the service definition to initialize the variable. The registration step that is included near the end of the README.md file includes an additional argument that specifies a configuration input file. Here is the full command:

 hzn register -p pattern-SERVICE_NAME-$(hzn architecture) -f horizon/userinput.json

The argument, -f horizon/userinput.json informs the registration command to use the userinput file to start any configuration variables that are used by any of the edge services in the registered deployment pattern. Variable values are provided in that file, which is grouped by the service that uses them. This file includes the following variables:

{
    "services": [
        {
            "org": "IBM",
            "url": "ibm.cpu2msghub",
            "variables": {
                "MSGHUB_API_KEY": "$MSGHUB_API_KEY",
                "MSGHUB_TOPIC": "$MSGHUB_TOPIC",
                "MSGHUB_BROKER_URL": "$MSGHUB_BROKER_URL"
            }
        }
    ]
}

The file provides configuration variable values at registration time. When you pass this file on the hzn register ... -f ... command line, the hzn command first runs envsubst to enhance the file by extracting values from the shell environment. The command copies the file and substitutes any environment variable references, such as your IBM Event Streams API key and broker URL, with whatever values they happen to have at that moment, before the file is used. Then, these values can be cached for use when the IBM Edge Computing for Devices agent actually starts up the corresponding service. For this example, the ibm.cpu2msghub service is started.

The deployment pattern references only the cpu2msghub service, and the userinput.json file also references only this service. However, since cpu2msghub declares the other two services as requiredServices, these services are started as part of the deployment pattern. As a result, any configuration variable values those other services can require in their environment can also be specified in this userinput.json file by adding another entry in the services array for each one. Each item in the services array refers to one of the services in the deployment pattern. The array of variable values that are specified within that entry are placed in the process environment of every container within that service when it is run on the edge node by the agent.

When an edge node is registered and in an agreement, then all of its service containers are eventually visible running in the docker ps output. Using the container IDs shown in this output you can run docker inspect <container> to see more information about each container, including the shell environment variables that are passed to the container. The variables that you set at registration time by using the -f userinput.json command are shown with their values. There are also some other variables present that the agent provides to all of the containers that it runs. For more information about the agent-provided variables, see Horizon environment variables Opens in a new tab.

Development tips

Using the configuration variables from the example service.sh, such as the VERBOSE, PUBLISH, and MOCK variables, can be helpful during development. The service.sh code uses these variables and the other configuration variables to control its behavior. VERBOSE increases the amount of information that is logged while it runs. The PUBLISH variable controls whether the code attempts to send messages to IBM Event Streams. The MOCK variable controls whether service.sh attempts to call the REST APIs of its dependencies (the cpu and gps services) or create mock data itself instead. You can experiment with setting different values for these variables in your userinput.json file to observe the results in the container logs.

The ability to mock the services that you depend on is optional, but can be helpful to develop components in complete isolation, or to develop code that uses services or features that are not yet implemented. This approach can also make possible cross-development of service implementations on machines, such as different hardware architectures, where hardware sensors or actuators are not present.

The ability to turn off interaction with remote services can be convenient during development and testing, such as to avoid unnecessary charges.

What to do next