Overview
This section contains step-by-step tutorials to successfully develop microservices which employ the Cumulocity IoT APIs and other third-party services. The source code of the examples can be found in our GitHub repository.
On the Cumulocity IoT platform, microservice hosting is built on top of Docker containers. This makes it technology-agnostic and allows developers to create applications in any technology stack.
Python microservice
In this tutorial, you will learn how to create and run a microservice written in Python. This example contains:
- A sample Python application using the Flask framework to expose REST endpoints.
- An application manifest file with minimal content to run a microservice.
- The configuration of the Dockerfile which allows creating a ready to run Docker image with a bundled application (inside a light Alpine linux distribution).
- Instructions for building and packaging a ZIP file containing the full application (ready to upload into the platform).
- Instructions for uploading and subscribing to the packaged microservice.
Prerequisites
Create an account on cumulocity.com, for example by using a free trial. At this step you will be provided with a dedicated URL address.
Cumulocity IoT hosts linux/amd64 Docker containers and not Windows containers. The Docker version must be >= 1.12.6. Verify your Docker installation with the following command:
$ docker version
Client:
Version: 1.12.6
API version: 1.24
OS/Arch: linux/amd64
Server:
Version: 1.12.6
API version: 1.24
OS/Arch: linux/amd64
Developing the “Hello world” microservice
To develop a simple “Hello world” microservice in Python, you must:
- Create a Python web application.
- Create the Dockerfile.
- Add the microservice manifest.
- Build and run the application.
Create a Python web application
This example uses Python 3 with a Flask microframework which enables simple exposing of endpoints and an embedded HTTP server.
Start by creating the application.py script with the following content:
#!flask/bin/python
from flask import Flask, jsonify
import os
app = Flask(__name__)
# Hello world endpoint
@app.route('/')
def hello():
return 'Hello world!'
# Verify the status of the microservice
@app.route('/health')
def health():
return '{ "status" : "UP" }'
# Get environment details
@app.route('/environment')
def environment():
environment_data = {
'platformUrl': os.getenv('C8Y_BASEURL'),
'mqttPlatformUrl': os.getenv('C8Y_BASEURL_MQTT'),
'tenant': os.getenv('C8Y_BOOTSTRAP_TENANT'),
'user': os.getenv('C8Y_BOOTSTRAP_USER'),
'password': os.getenv('C8Y_BOOTSTRAP_PASSWORD'),
'microserviceIsolation': os.getenv('C8Y_MICROSERVICE_ISOLATION')
}
return jsonify(environment_data)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
The application is configured to run on port 80 – which is required for microservices – and exposes three endpoints:
- / returns a hello world message.
- /health is the common endpoint to verify if a microservice is up and running.
- /environment reads some standard variables provided to the environment by the platform during the microservice installation and returns their values in JSON format.
Create the Dockerfile
You must create a Dockerfile in order to build a Docker image with your application. For this example, it shall be in the same directory as the application.py script and with the following content:
FROM python:alpine3.6
COPY application.py /
RUN pip install flask==0.10.1
ENTRYPOINT ["python"]
CMD ["-u", "application.py"]
This build uses Alpine Linux with the Python SDK inside. It is a very thin distribution and the resulting Docker image is small (about 100 MB). The instruction RUN pip install flask
installs the required Python library using the pip
installer.
Add the application manifest
The microservice manifest file cumulocity.json is required for the application. Create that file with the following content:
{
"apiVersion": "1",
"version": "1.0.0",
"provider": {
"name": "Cumulocity"
},
"isolation": "MULTI_TENANT",
"requiredRoles": [
],
"roles": [
]
}
Build the application
Execute the following Docker commands to build the Docker image and save it as image.tar:
$ docker build -t hello-python-microservice .
$ docker save hello-python-microservice > "image.tar"
Then pack image.tar together with the manifest cumulocity.json into a ZIP file.
$ zip hello-microservice cumulocity.json image.tar
The resulting hello-microservice.zip file contains your microservice and it is ready to be uploaded to the Cumulocity IoT platform.
Run the example
Uploading the hello-microservice.zip into the platform can be done via the UI. In the Administration application, navigate to Ecosystem > Microservices and click Add microservice. Drop the ZIP file of the microservice and then click Subscribe.
For more details about uploading a microservice ZIP file, refer to Administration > Managing and monitoring microservices > Custom microservices in the User guide.
Using the microservice utility tool
You can also build, upload and subscribe the application using the microservice utility tool. In this case, the files must follow the directory structure required by the script.
For this particular microservice example, the structure shall be:
/docker/Dockerfile
/docker/application.py
/cumulocity.json
Execution
After the microservice has been successfully uploaded and subscribed by your tenant, it will run in a Docker container. A request similar to:
GET <URL>/service/hello-microservice/environment
HEADERS:
"Authorization": "<AUTHORIZATION>"
with proper credentials (user from any subscribed tenant), returns a response as:
{
"microserviceIsolation": "MULTI_TENANT",
"mqttPlatformUrl": "tcp://cumulocity:1881",
"password": "...",
"platformUrl": "https://cumulocity:8111",
"tenant": "mytenant",
"user": "servicebootstrap_hello-microservice"
}
The authorization header is formed as “Basic <Base64(<tenantID>/<username>:<password>)>”. For instance, if your tenant ID, username and password are t0071234, testuser and secret123 respectively, you can get the Base64 string with the following command:
$ echo -n t0071234/testuser:secret123 | base64
dDAwNzEyMzQvdGVzdHVzZXI6c2VjcmV0MTIz
and your authorization header would look like "Authorization": "Basic dDAwNzEyMzQvdGVzdHVzZXI6c2VjcmV0MTIz"
.
Source code
The source code of this Hello world microservice can be found in our GitHub repository. Moreover, in our GitHub repository you can find a more comprehensive Python microservice application which uses the Cumulocity IoT REST API and exposes endpoints to verify if the microservice is up and running, create a device and random measurements for it, and to get the current application subscriptions for a particular tenant.
Node.js microservice
Cumulocity IoT provides SDKs for developing microservices using C# or Java. Nevertheless, you are free to choose the tech-stack of your preference to develop a microservice as long as it fulfills the general requirements.
In this example you will learn how to create and deploy a Node.js-based microservice. The application exposes endpoints to verify if the microservice is up and running and get some of the environment variables.
It uses the Cumulocity IoT @c8y/client JavaScript library to subscribe to alarms. When a new alarm is created, a Slack channel gets notified.
Prerequisites
- Cumulocity IoT credentials (tenant, user and password).
- Slack channel to post messages to, Slack app and OAuth token.
- Docker local installation.
- A .env file in the root directory with the following content:
PORT=80
SLACK_OAUTH_TOKEN=<YOUR-TOKEN-GOES-HERE>
SLACK_CHANNEL_ID=<YOUR-CHANNEL_ID-GOES-HERE>
Developing the microservice
Configure a Node.js application
Start by creating a folder node-microservice to contain your files. Inside your folder, use the following command to initialize your project:
$ npm init
It will walk you through creating a package.json file which allows to identify the project as well as handling its dependencies. When prompted, enter your project’s information and use app.js as entry point. Once the file has been created, install the dependencies using:
$ npm install --save @c8y/client @slack/web-api dotenv express
Eventually, your package.json file should look similar to:
{
"name": "node-microservice",
"version": "1.0.0",
"description": "Cumulocity IoT microservice application",
"main": "app.js",
"dependencies": {
"@c8y/client": "^1004.0.7",
"@slack/web-api": "^5.0.1",
"dotenv": "^8.0.0",
"express": "4.17.0"
},
"scripts": {
"start": "node app.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "<your-name>",
"license": "MIT"
}
Add the source code
Now create a file app.js which is the main entry point of your application. It uses the Express framework to start a server listening on port 80, defines its endpoints and requires controllers to use the Cumulocity IoT and Slack APIs.
"use strict";
require("dotenv").config();
const express = require("express");
const app = express();
// Application endpoints
const routes = require("./routes");
routes(app);
// Server listening on port 80
app.use(express.json());
app.listen(process.env.PORT);
console.log(`${process.env.APPLICATION_NAME} started on port ${process.env.PORT}`);
// Cumulocity IoT and Slack controllers
require("./controllers");
As you may have already noticed, routes
and controllers
are required. Create a routes.js file with the following content:
"use strict";
module.exports = function(app) {
// Hello world
app.route("/").get(function(req, res) {
res.json({ "message" : "Hello world!" });
});
// Health check
app.route("/health").get(function(req, res) {
res.json({ "status" : "UP" });
});
// Environment variables
app.route("/environment").get(function(req, res) {
res.json({
"appName" : process.env.APPLICATION_NAME,
"platformUrl" : process.env.C8Y_BASEURL,
"microserviceIsolation" : process.env.C8Y_MICROSERVICE_ISOLATION,
"tenant" : process.env.C8Y_BOOTSTRAP_TENANT,
"bootstrapUser" : process.env.C8Y_BOOTSTRAP_USER,
"bootstrapPassword" : process.env.C8Y_BOOTSTRAP_PASSWORD
});
});
};
At this point, your microservice would be accessible via web on its endpoints to return a “Hello world” message, verify that the microservice is up and running and get some environment variables.
In order to implement the controllers, you must first create a Slack app and get a token to use the Web API. Go to Slack API: Applications to create a new app. Choose your workspace and give your app a name, for example, C8Y Slack bot. Then get an OAuth access token.
Once you have your Slack app and token ready, create the controllers.js file with the following content:
"use strict";
/********************* Slack *********************/
// Create a new instance of the WebClient class with the OAuth access token
const { WebClient } = require("@slack/web-api");
const web = new WebClient(process.env.SLACK_OAUTH_TOKEN);
// Slack channel ID to know where to send messages to
const channelId = process.env.SLACK_CHANNEL_ID;
// Format a message and post it to the channel
async function postSlackMessage (adata) {
// Alarm severity
let color = {
"WARNING" : "#1c8ce3",
"MINOR" : "#ff801f",
"MAJOR" : "#e66400",
"CRITICAL": "#e0000e"
};
// Send a message from this app to the specified channel
let src = adata.source;
await web.chat.postMessage({
channel: channelId,
attachments : [{
"text": adata.text,
"fields": [
{
"title": "Source",
"value": `<${src.self}|${src.name ? src.name : src.id}>`,
"short": true
},
{
"title": "Alarm type",
"value": adata.type,
"short": true
}
],
"color": color[adata.severity]
}]
});
}
/********************* Cumulocity IoT *********************/
const { Client, FetchClient, BasicAuth } = require("@c8y/client");
const baseUrl = process.env.C8Y_BASEURL;
let cachedUsers = [];
// Get the subscribed users
async function getUsers () {
const {
C8Y_BOOTSTRAP_TENANT: tenant,
C8Y_BOOTSTRAP_USER: user,
C8Y_BOOTSTRAP_PASSWORD: password
} = process.env;
const client = new FetchClient(new BasicAuth({ tenant, user, password }), baseUrl);
const res = await client.fetch("/application/currentApplication/subscriptions");
return res.json();
}
// where the magic happens...
(async () => {
cachedUsers = (await getUsers()).users;
if (Array.isArray(cachedUsers) && cachedUsers.length) {
// List filter for unresolved alarms only
const filter = {
pageSize: 100,
withTotalPages: true,
resolved: false
};
try {
cachedUsers.forEach(async (user) => {
// Service user credentials
let auth = new BasicAuth({
user: user.name,
password: user.password,
tenant: user.tenant
});
// Platform authentication
let client = await new Client(auth, baseUrl);
// Get filtered alarms and post a message to Slack
let { data } = await client.alarm.list(filter);
data.forEach((alarm) => {
postSlackMessage(alarm);
});
// Real time subscription for active alarms
client.realtime.subscribe("/alarms/*", (alarm) => {
if (alarm.data.data.status === "ACTIVE") {
postSlackMessage(alarm.data.data);
}
});
});
console.log("listening to alarms...");
}
catch (err) {
console.error(err);
}
}
else {
console.log("[ERROR]: Not subscribed/authorized users found.");
}
})();
The code has two parts. The first one needs your Slack OAuth token and channel ID (chat group where the messages will be posted). A message is formatted using the colors of the different alarm severities that you may see in the Cockpit application. This message gets posted to the Slack channel.
The second part uses basic authentication to the Cumulocity IoT platform, it gets all active alarms and posts alarm messages to the Slack channel. After that, it subscribes to alarms and notifies the Slack channel each time a new alarm is created in the subscribed tenants.
Dockerfile and application manifest
Create a microservice manifest cumulocity.json with the following content:
{
"apiVersion": "1",
"version": "1.0.0-SNAPSHOT",
"provider": {
"name": "Cumulocity"
},
"isolation": "MULTI_TENANT",
"requiredRoles": [
"ROLE_ALARM_READ",
"ROLE_ALARM_ADMIN"
],
"roles": [
]
}
Finally, Docker needs to know how to build your microservice. Create a Dockerfile as follows:
FROM node:alpine
WORKDIR /usr/app
COPY ./package.json ./
RUN npm install
COPY ./ ./
CMD ["npm", "start"]
Deploying the microservice
Once you have all the required files, building and deploying the microservice application is fairly simple. Execute the following Docker commands to build the Docker image and save it as image.tar:
$ docker build -t node-microservice .
$ docker save node-microservice > "image.tar"
Then pack image.tar together with the manifest cumulocity.json into a ZIP file.
$ zip node-microservice cumulocity.json image.tar
The resulting node-microservice.zip file contains your microservice and it is ready to be uploaded to the Cumulocity IoT platform. Uploading the node-microservice.zip into the platform can be done via the UI. In the Administration application, navigate to Ecosystem > Microservices and click Add microservice. Drop the ZIP file of the microservice and then click Subscribe.
For more details about uploading a microservice ZIP file, refer to Administration > Managing and monitoring microservices > Custom microservices in the User guide.
Testing the microservice
After the microservice has been successfully uploaded and subscribed to your tenant, it will run in a Docker container. A request similar to:
GET <URL>/service/node-microservice/environment
HEADERS:
"Authorization": "<AUTHORIZATION>"
with proper credentials (user and password from any subscribed tenant), returns a response as:
{
"appName": "node-microservice",
"platformUrl": "http://cumulocity:8111",
"microserviceIsolation": "MULTI_TENANT",
"tenant": "t...",
"bootstrapUser": "...",
"bootstrapPassword": "..."
}
The authorization header is formed as “Basic <Base64(<tenantID>/<username>:<password>)>”. For instance, if your tenant ID, username and password are t0071234, testuser and secret123 respectively, you can get the Base64 string with the following command:
$ echo -n t0071234/testuser:secret123 | base64
dDAwNzEyMzQvdGVzdHVzZXI6c2VjcmV0MTIz
and your authorization header would look like "Authorization": "Basic dDAwNzEyMzQvdGVzdHVzZXI6c2VjcmV0MTIz"
.
If there are active alarms on your tenant, your Slack channel will get notified. You can also create a new alarm using the Cumulocity IoT REST API and validate that your microservice is listening to new alarms. Your Slack channel will also get notified.
Source code
The code of this node-microservice can be found in our public GitHub repositories.