Introduction
Microservices use standard REST APIs with full authentication and authorization to communicate with Cumulocity. They are, in most cases, multi-tenant, meaning they must be able to strictly separate tenants and connect to multiple tenants at the same time. Microservices may offer their own endpoints that can be used by Cumulocity and Cumulocity-based applications, for example, for system integration purposes. Examples of such microservices are the Jasper Control Center integration and the SMS integration for sending SMS notifications to end users.
You can extend the Cumulocity platform with customer-specific functionality by deploying microservices. For instance, you can develop integrations to third-party software or provide server-side business logic. A microservice-based architecture introduces change that is often well received by those developing modern applications, and solutions can be delivered much more quickly to those requesting flexible and scalable applications. Microservices bring significant benefits such as deployability, reliability, availability, scalability, modifiability and management.
Cumulocity microservices have the following properties:
- By default, they provide REST or Websocket APIs.
- Inbound REST and Websocket endpoints are secured by Cumulocity core built-in API gateway functionality.
- Requests from one microservice to the Cumulocity REST API can be executed by either using the original user account (of the inbound request) or by using a service user.
- Multi-tenant support.
The following management features are supported:
- Microservices can be registered to individual tenants and super-tenants (that is, tenants with subtenants).
- Multi-tenant microservices can be subscribed to other tenants.
Technically, microservices are Docker containers hosted by Cumulocity and they follow specific conventions. They are typically accessed using Cumulocity REST API available under /service/<microservice-name>.
Developers are not restricted to any programming language when developing a microservice for Cumulocity. However, a microservice must serve as an HTTP server working on port 80 and must be encapsulated in a Docker image. Refer to the relevant chapters in this guide for further information for the development of microservices.
Containerization and orchestration
Images and containers
Docker is a platform to develop, deploy and run applications with containers. An image is an executable package that includes everything needed to run an application (that is, the code, a runtime, libraries and configuration files). A container is a runtime instance of an image (that is, what the image becomes in memory when executed). Refer to the Docker documentation for more information about Docker.
Cumulocity microservices are based on Docker. Hence, a microservice must be packaged as a Docker image in order to run on the Cumulocity platform. A microservice is executed in a Docker container during runtime. The Docker container ensures that a microservice does not harm other microservices running in Cumulocity.
Containers have an upper thread limit of 10240 for microservices.
Pods
Kubernetes is the container orchestration engine for automating deployment, scaling and management of containerized applications. A Pod is the basic building block of Kubernetes and it represents a running process on your cluster. A Pod encapsulates an application container, storage resources, a unique network IP and options that govern how the container should run.
Docker is the most common container runtime used in a Kubernetes Pod. Moreover, Kubernetes is used to orchestrate Docker containers and it provides many enterprise-grade features for hosting Docker containers such as auto-scaling and load balancing. Refer to the Kubernetes documentation for more information about Kubernetes.
When Docker faces some issues, for example, a Pod synchronization error, an alarm is created and can be seen in Alarms in the Cockpit application. Refer to Troubleshooting in this section to learn about common issues.
Requirements and interactions
The following requirements towards Cumulocity microservices must be met:
- A microservice must be a (linux/amd64) Docker image run.
- The Docker image must be packaged as image.tar and must include a manifest file (cumulocity.json).
- A microservice must be stateless, that means, it must contain only ephemeral state. The reason is that a microservice must be able to survive (random) restarts due to hardware reasons (server failure) and operations reasons (upgrade, migration).
- All persistent states must be stored at the Cumulocity platform via inventory, binary, tenant options and other APIs. Persistent volumes are not supported.
- A microservice cannot access the database directly and must use the Cumulocity API.
- A microservice must provide one inbound REST API. Additional inbound ports are not supported.
- A microservice can use multiple outbound ports.
- The request lifetime must have the maximum. The infrastructure might terminate too long running requests.
- All log information must be sent to the standard output in order to be captured and persisted by the infrastructure.
Microservices interact with Cumulocity and the outside world as shown in the following diagram:
The Microservices’ lifecycle is managed using the microservice subscription API. This allows registration and subscription of Microservices. External actors (for example web user interfaces, integrations or other microservices) can invoke a microservice by sending REST or Websocket requests to its endpoints /service/<microservice-name>/<path>. A microservice can issue requests to external endpoints or to the Cumulocity REST APIs. Microservices can store logs and metrics in the associated developer tenant.
Microservice manifest
The application manifest provides the required settings to manage microservice instances and the application deployment in the Cumulocity platform. The definition is provided within the cumulocity.json file in the binary uploaded to the Cumulocity platform.
Here is an example manifest:
{
"apiVersion": "v2",
"name": "my-microservice",
"version": "1.0.0",
"provider": {
"name": "New Company Ltd.",
"domain": "https://new-company.com",
"support": "support@new-company.com"
},
"isolation": "MULTI_TENANT",
"scale": "AUTO",
"replicas": 2,
"resources": {
"cpu": "1",
"memory": "1G"
},
"requestedResources":{
"cpu": "100m",
"memory": "128Mi"
},
"requiredRoles": [
"ROLE_ALARM_READ"
],
"livenessProbe": {
"httpGet": {
"path": "/health"
},
"initialDelaySeconds": 60,
"periodSeconds": 10
},
"readinessProbe": {
"httpGet": {
"path": "/health",
"port": 80
},
"initialDelaySeconds": 20,
"periodSeconds": 10
},
"settingsCategory": "myms",
"settings": [
{
"key": "tracker-id",
"defaultValue": "1234"
}
]
}
See below for detailed information about available settings.
Settings
Name | Type | Description | Required |
---|---|---|---|
apiVersion | String | Document type format discriminator. The accepted values are positive integer numbers proceeded by an optional "v", such as "v2" and "2". Values which do not conform to this convention are considered "v1". | Yes |
name | String | Application name. The accepted letters are lowercase characters (a-z), digits (0-9), or hyphens (-). The maximum length for the name is 23 characters. | No |
contextPath | String | Microservice contextPath is used to define extension points. The accepted letters are lowercase (a-z) and uppercase (A-Z) characters, digits, hyphens (-), dots (.), underscores (_), or tildes (~). Default: Microservice name |
No |
version | String | Application version. Must be a correct SemVer value but the "+" sign is disallowed. | Yes |
provider | Provider | Application provider information. Simple name allowed for predefined providers, for example, c8y. Detailed object for external provider. | Yes |
billingMode | Enum | Values: RESOURCES, SUBSCRIPTION Default: RESOURCES In case of RESOURCES, the number of resources used is exposed for billing calculation per usage. In case of SUBSCRIPTION, all resources usage is counted for the microservice owner and the subtenant is charged for subscription. |
No |
isolation | Enum | Values: MULTI_TENANT, PER_TENANT Default: MULTI_TENANT Deployment isolation. In case of PER_TENANT, there is a separate instance for each tenant; otherwise, there is one single instance for all subscribed tenants. Should be overridable on subscription and should affect billing. |
No |
scale | Enum | Values: AUTO, NONE Default: NONE Enables scaling policy. See Isolation and scaling for more details. |
No |
replicas | Integer | Value range: 1 - 5 Default: 1 Defines the number of microservice instances. For auto-scaled microservices, the value represents the minimum number of microservices instances. Use the default value only for development versions. Production microservices must use at least two replicas. |
No |
resources | Resources | Configuration for resources limits. Default limits are CPU=0.5, Memory=512MB. Different default values may be configured by the system administrator. |
No |
requestedResources | RequestedResources | Intended configuration for minimal required resources. The values may be over-written based on system settings. Default values are CPU=0.25, Memory=256MB. Different default values may be configured by the system administrator. |
No |
settings | Option[ ] | Set of tenant options available to define the configuration of a microservice. Default: [ ] (empty list) |
No |
settingsCategory | String | Allows to specify custom category for microservice settings. By default contextPath is used. | No |
requiredRoles | String[ ] | List of permissions required by a microservice to work. Default: [ ] (no permissions) |
No |
roles | String[ ] | Roles provided by the microservice. Default: [ ] (empty list) |
No |
livenessProbe | Probe | Defines the strategy used to verify if a microservice is alive or requires a restart. If no probe is specified, the microservice is assumed to be always healthy. We recommend that you implement liveness probes for production microservices. | No |
readinessProbe | Probe | Defines the strategy used to verify if a microservice is ready to accept traffic. If no probe is specified, the microservice is assumed to be always able to accept traffic immediately after it was started. Omitting the readinessProbe in production microservices will lead to clients of the microservice being exposed to startup errors. | No |
extensions | Extension[ ] | Defines a set of extensions that should be enabled for a microservice. Default: [ ] (empty list) |
No |
Version
The version has an impact on the microservice upload behavior:
- If a new ZIP file for a microservice is uploaded but the version is the same as the previous, for example, “1.1.0”, then there is no guarantee that the Docker image for the microservice will be updated.
- If the version is a snapshot, for example, “1.1.0-SNAPSHOT”, then Docker will update the image on each ZIP upload.
The snapshot postfix means that the image build is a snapshot of your application at a given time and it is still under development. When your microservice is ready for production release, you can remove the postfix and just use the final version of your application.
Provider
Name | Type | Description | Required |
---|---|---|---|
name | String | Company name of the provider | Yes |
domain | String | Website of the provider | No |
support | Email of the support person | No |
Resources
Name | Type | Description | Required |
---|---|---|---|
cpu | String | Limit for number of CPUs or CPU time Default CPU: 0.5, min: 0.1 Default CPU time: 500m, min: 100m A different default value may be configured by the system administrator. |
No |
memory | String | Limit for microservice memory usage Default: 512M, Min: 10M Possible units are: E, P, T, G, M, K, Ei, Pi, Ti, Gi, Mi, Ki A different default value may be configured by the system administrator. |
No |
RequestedResources
Name | Type | Description | Required |
---|---|---|---|
cpu | String | Intended minimal requirements for number of CPUs or CPU time The value may be over-written based on system settings. Default: 250m A different default value may be configured by the system administrator. |
No |
memory | String | Intended minimal requirements for microservice memory usage The value may be over-written based on system settings. Default: 256M Possible postfix values are: E, P, T, G, M, K, Ei, Pi, Ti, Gi, Mi, Ki A different default value may be configured by the system administrator. |
No |
Option
Name | Type | Description | Required |
---|---|---|---|
key | String | Key of the option | Yes |
defaultValue | String | Default value | Yes |
editable | Boolean | Defines if the option can be changed by a subscribed tenant on runtime Default: false |
No |
overwriteOnUpdate | Boolean | Defines if an editable option is reset upon microservice update Default: true |
No |
inheritFromOwner | Boolean | Specifies if an option should be inherited from the owner Default: true |
No |
Probe
Name | Type | Description | Required |
---|---|---|---|
exec | ExecAction | Commands to be executed on a container to probe the service | No |
tcpSocket | TCPSocketAction | TCP socket connection attempt as a probe | No |
httpGet | HTTPGetAction | HTTP request to be executed as a probe | No |
initialDelaySeconds | Number | Tells the platform for how long it should wait before performing the first probe Default: 0 |
No |
periodSeconds | Number | Defines in which interval the probe should be executed Default: 10 |
No |
successThreshold | Number | Minimum consecutive successes for the probe to be considered successful after having failed Default: 1 |
No |
timeoutSeconds | Number | Number of seconds after which the probe times out Default: 1 |
No |
failureThreshold | Number | Number of failed probes after which an action should be taken Default: 3 |
No |
ExecAction
Name | Type | Description | Required |
---|---|---|---|
command | String[ ] | Commands to be executed on a container to probe the service | Yes |
TCPSocketAction
Name | Type | Description | Required |
---|---|---|---|
host | String | Host to verify | Yes |
port | Number | Port to verify Default:80 |
Yes |
HTTPGetAction
Name | Type | Description | Required |
---|---|---|---|
host | String | Host name to connect to | Yes |
path | String | Path to access on the HTTP server | Yes |
port | Number | Port to verify Default: 80 |
No |
scheme | String | Scheme to use for connecting to the host (HTTP or HTTPS) Default: HTTP |
No |
headers | HttpHeader | HTTP headers to be added to a request | No |
HttpHeader
Name | Type | Description | Required |
---|---|---|---|
name | String | Header name | Yes |
value | String | Header value | Yes |
Extension
Name | Type | Description | Required |
---|---|---|---|
type | String | Type ID of the extension | Yes |
* | any | Configuration parameters | No |
Isolation and scaling
The following isolation levels are available for microservices:
- Multi-tenant: Single microservice Docker container instantiated for all subscribed tenants, unless the microservice is scaled.
- Single-tenant: Dedicated microservice Docker container instantiated for each subscribed tenant.
See Settings for the isolation
setting.
In case the scale
setting is set to NONE, the platform guarantees that there is one instance of the service per isolation level by default. The number of guaranteed instances can be increased by defining the replicas
setting in the manifest. If scaling is enabled (set to AUTO), the microservice will be horizontally auto-scaled (creating more instances of the microservice) in case of high CPU usage.
Auto-scaling monitors the microservices to make sure that they are operating at the desired performance levels, and it will automatically scale up your cluster as soon as you need it and scale it back down when you don’t.
See Settings for the scale
and replicas
setting.
Microservice migration to API Version 2
With release 10.15, Software AG announces the availability of microservice API Version 2 and the deprecation of API Version 1 to comply with new security requirements. Microservice API Version 2 provides an improved microservice container security context restricting the invocation of privileged Linux Kernel APIs. In details this means that with microservice API Version 2 the microservice container is granted only specific capabilities.
Refer to the Linux manual page for more information on Kernel capabilities. With the API Version change, the microservice is granted the capability NET_BIND_SERVICE.
Microservice migration
Perform the following steps to migrate your microservice to the new API Version.
-
Change the API Version to 2 in the microservice manifest. See Microservice manifest.
-
Deploy your microservice to the test environment.
-
Test the functionality of your microservice and analyze possible errors.
In the simplest case it is sufficient to set the API Version to 2 in your microservice manifest.
However, for microservices which currently make use of specific privileges of the Linux Kernel API that are not granted anymore, you must additionally refactor the source code so that the service doesn’t require the invocation of these privileges.
Affected microservices
Set the API Version field in the microservice manifest to 2 and deploy this service to your Cumulocity test environment. This environment must be in version 10.15 or higher. Verify that the functionality provided by the microservice still works as expected.
Microservices still using old user privileges after the environment upgrade
If your microservice is using the deprecated API Version 1, it may be affected by the change depending on the configuration of this environment. In this case, you will not be able to upload or subscribe the microservice.
Security
Microservices typically provide a REST API and Cumulocity provides a light API gateway (Proxy) for inbound REST requests. Inbound WebSocket requests are supported. The API gateway, located between the client and the microservice container, provides:
- Authorization: All calls are authenticated using Cumulocity credentials with basic or OAuth authorization.
- TLS Termination: TLS inbound calls are terminated and only HTTP is used inside the cluster.
- Metering: The API calls are metered in the API calls tenant statistics.
- Routing: The API gateway routes requests for /service/<name> to the microservice <name>. The request routed to the microservice container and tenant options are added to the request headers. Note that therefore a space (" “) is not allowed as a tenant option value. If
contextPath
is defined in the application manifest, the API gateway routes requests for /service/<contextPath>.
Authentication and authorization
A request to a microservice can be authenticated using various authentication mechanisms supported by Cumulocity such as:
- Basic authentication
- OAI-Secure
- SSO
- JWT token authentication (deprecated)
If you use Java for your development, we recommend you to use the Microservice SDK version 10.4.6 or later to gain support for all available authentication ways in Cumulocity.
In case of other programming languages, for which Cumulocity does not provide an SDK, we recommend developers to ensure support for all authentication mechanisms available in the Cumulocity platform.
The Cumulocity exposes the REST endpoint /user/currentUser. The microservice retrieves the Cumulocity address from the C8Y_BASEURL
operating system environment variable. The microservice can verify if the credentials embedded in the HTTP request, which is handled by the microservice, are valid or not by using the REST endpoint /user/currentUser. If the credentials are correct, the response status from the /user/currentUser endpoint is 200 OK
. In case of incorrect credentials, the response contains a 401 Unauthorized
status code. In order to verify if the credentials are correct, the microservice copies the credentials from ongoing requests and sends them to the /user/currentUser endpoint.
Depending on the authentication method, the credentials can be passed to the microservice in various ways, such as:
Authorization
HTTP header- Cookie called
authorization
- Custom HTTP header called
X-XSRF-TOKEN
- Custom HTTP header called
tfatoken
If the incoming request contains the cookie authorization
, the microservice must copy the cookie and the header X-XSRF-TOKEN
to the request to the /user/currentUser endpoint. In other cases, the header Authorization
must be copied. This is necessary, if a request contains the header tfatoken
, which always must be included in the request to /user/currentUser.
You can see the credential validation flow on the sequence diagram below:
Example
The microservice can receive the HTTP requests with the following header (see example below). The authentication process is based on basic authentication and SMS based TFA.
GET https://cumulocity.default.svc.cluster.local/test HTTP/1.1
accept: */*
authorization: Basic dGVuYW50X2lkL3VzZXJuYW1lOnBhc3N3b3JkCg==
connection: close
content-length: 0
cookie: REQUEST_ORIGIN=
host: auth-scope-management.default.svc.cluster.local:80
tfatoken: 23b75292468e0ba7fe03245d502d9c29e21e8a997fc7dd7e1a1df7fe31cbfb17
x-forwarded-host: cumulocity.default.svc.cluster.local:80
x-forwarded-prefix: /service/auth
x-forwarded-proto: http
x-real-ip: 192.168.1.20
The microservice sends the following requests to validate the credentials (provided that c8y_baseurl
is equal to https://cumulocity:8111
):
GET https://cumulocity:8111/user/currentUser HTTP/1.1
Accept: text/plain, application/json, application/*+json, */*
Authorization: Basic dGVuYW50X2lkL3VzZXJuYW1lOnBhc3N3b3JkCg==
Content-Length: 0
tfatoken: 23b75292468e0ba7fe03245d502d9c29e21e8a997fc7dd7e1a1df7fe31cbfb17
If the user is authenticated with OAI-Secure, the following REST request can reach the microservice:
GET https://cumulocity.default.svc.cluster.local/test HTTP/1.1
accept: */*
authorization: Basic dGVuYW50X2lkL3VzZXJuYW1lOjxmYWtlIHBhc3N3b3JkPgo=
connection: close
content-length: 0
cookie: authorization=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI0YzAwMGVlOC1hYjY5LTRiMzYtYWNhNC0xNzM4ZGYwZWNhZGIiLCJpc3MiOiJjdW11bG9jaXR5LmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJhdWQiOiJjdW11bG9jaXR5LmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJzdWIiOiJhZG1pbiIsInRjaSI6IjMxMWM3YjZiLWM1MmQtNGEzMC1iZDFlLWNiODdlMjNlODQyOSIsImlhdCI6MTY0NjkyODgxMywibmJmIjoxNjQ2OTI4ODEzLCJleHAiOjE2NDgxMzg0MTMsInRmYSI6ZmFsc2UsInRlbiI6Im1hbmFnZW1lbnQiLCJ4c3JmVG9rZW4iOiJHU0l6VFB4b2JQS1FJQmV2Uk1SWiJ9.NL5b9-iHSc3SLaPu87oazBd2_kjNiwO9tM_bXS3qCUd0_wTZ-BKc3q6sRHTKO_LNbtCQg3G6-7ZhD6TyvqjTLsyiZTsgMRtAE7ZiRPtXaFOA0_SDQ9kG_MztAZ3U9O008akgXcjEEAdphVv5eey_lADrg1BmIlqiBFoKts2JGSv1xFtXKIxpcVtRDGUkb-2qb8OhaHamT7b3HL628NSiH4VqfO38vkLLkimHEz-roqmbFGQ355TvA3-s_erO96j3rHbBPDsluFqFN0eOTCidBffKt6OvyKw-_64MaHHs6R9Ulsv-LuY-YAvlTZVxYwFAi3yn3mWlpXEAzvGYHMrx8A
host: auth-scope-management.default.svc.cluster.local:80
user-agent: insomnia/2022.1.1
x-forwarded-host: cumulocity.default.svc.cluster.local:80
x-forwarded-prefix: /service/auth
x-forwarded-proto: http
x-real-ip: 192.168.1.20
x-xsrf-token: GSIzTPxobPKQIBevRMRZ
To verify the credentials from the request above, the microservice sends the following request:
GET https://cumulocity:8111/user/currentUser HTTP/1.1
Accept: text/plain, application/json, application/*+json, */*
Content-Length: 0
Cookie: authorization=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI0YzAwMGVlOC1hYjY5LTRiMzYtYWNhNC0xNzM4ZGYwZWNhZGIiLCJpc3MiOiJjdW11bG9jaXR5LmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJhdWQiOiJjdW11bG9jaXR5LmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJzdWIiOiJhZG1pbiIsInRjaSI6IjMxMWM3YjZiLWM1MmQtNGEzMC1iZDFlLWNiODdlMjNlODQyOSIsImlhdCI6MTY0NjkyODgxMywibmJmIjoxNjQ2OTI4ODEzLCJleHAiOjE2NDgxMzg0MTMsInRmYSI6ZmFsc2UsInRlbiI6Im1hbmFnZW1lbnQiLCJ4c3JmVG9rZW4iOiJHU0l6VFB4b2JQS1FJQmV2Uk1SWiJ9.NL5b9-iHSc3SLaPu87oazBd2_kjNiwO9tM_bXS3qCUd0_wTZ-BKc3q6sRHTKO_LNbtCQg3G6-7ZhD6TyvqjTLsyiZTsgMRtAE7ZiRPtXaFOA0_SDQ9kG_MztAZ3U9O008akgXcjEEAdphVv5eey_lADrg1BmIlqiBFoKts2JGSv1xFtXKIxpcVtRDGUkb-2qb8OhaHamT7b3HL628NSiH4VqfO38vkLLkimHEz-roqmbFGQ355TvA3-s_erO96j3rHbBPDsluFqFN0eOTCidBffKt6OvyKw-_64MaHHs6R9Ulsv-LuY-YAvlTZVxYwFAi3yn3mWlpXEAzvGYHMrx8A
X-XSRF-TOKEN: GSIzTPxobPKQIBevRMRZ
If a request to the /user/currentUser REST endpoint contains the correct credentials, the response body contains various information, such as user roles assigned to the user account. This information can be very useful in order for the microservice to carry out the authorization process.
When a microservice receives an HTTP request, it can become necessary to use the REST API exposed by Cumulocity to fulfil the request. To access the REST API, the microservice must provide a valid credential. It can use credentials that are present in the incoming REST request or use a dedicated user account, created to access resources belonging to tenants which are subscribed to the microservice. The choice between the credentials provided together with the incoming request to the microservice REST or a dedicated account depends on the use case. In order to utilize the user credentials received by the microservice in an incoming HTTP request, the microservice must copy the credentials according to the rules described above.
Furthermore, most of the HTTP requests which are sent by microservices must contain the HTTP header X-Cumulocity-Application-Key
. The header indicates that an action is performed by an application. When the header is missing Cumulocity assumes that the HTTP request is originated by an IoT device. Therefore, a request without X-Cumulocity-Application-Key
affects for example, the device availability state, or the billing data.
Thus, the microservice must only include X-Cumulocity-Application-Key
if the microservice proxy requests from an IoT device to the Cumulocity platform.
If the microservice includes the header X-Cumulocity-Application-Key
, the header must contain the correct application key. Then, the microservice retrieves the application key by sending a REST request to the endpoint /application/currentApplication exposed by the Cumulocity platform. The REST request must contain the credentials for basic authentication in the following format: tenantId/username:password
. The tenant ID, username and password are read by the microservice from the following operating system environment variables: C8Y_BOOTSTRAP_TENANT, C8Y_BOOTSTRAP_USER, and C8Y_BOOTSTRAP_PASSWORD. In order to increase the performance, the microservice must implement a caching mechanism related to the user credentials.
Microservice authentication and multi-tenancy
In general, microservices use the standard Cumulocity authentication mechanisms. This is executed in two steps:
- The microservice can be created in any tenant that has feature-microservice-hosting enabled.
- The microservices access the tenant API.
At the installation time of the microservice, an application is created in the owner tenant (usually a Management tenant or Enterprise tenant) reflecting the new microservice. In addition, a bootstrap user is created in the owner tenant that allows the microservice to retrieve subscriptions. Whenever required, a platform administrator will subscribe a customer to the new microservice. As part of the subscription, a service user in the customer tenant is created using random credentials.
Microservice authorization
Authorization is relevant on two levels:
- On the owner tenant level, the only authorization of a microservice is to access its own subscriptions.
- For accessing customer tenants, the microservice installs a set of required permissions for being able to operate.
A microservice is associated with a bootstrap user in the owner tenant, which will make sure that only its subscriptions are returned. A microservice is also associated with a set of permissions that it requires for carrying out its function on a customer tenant. These permissions are visualized in the Administration application. The permissions are associated with the service user that is created when a platform administration associates a microservice with a tenant (subscribes to it). The owner tenant must also be subscribed to if you intend to use microservice features on it.
Users and roles
There are three types of users:
- Tenant user: The user that invokes a microservice through its REST API endpoints /service/<microservice-name>/<path> passed through by the proxy.
- Service user: A generated user that allows a microservice to access a subscribed tenant independent of a REST API invocation, for example, for initialization or regular jobs.
- Microservice bootstrap user: A user passed to the microservice for requesting subscribed tenants and service users.
The following role types are defined for users:
- Required roles: The roles that are predefined to allow access to Cumulocity REST APIs. For instance, if a microservice creates measurements using the service user, measurement admin role must be added as a required role of the application. Required roles are added to the service users.
- Roles: The custom roles provided to tenant platform users by the microservice developer. These roles can be assigned or revoked to the tenant platform users or groups using the Administration application.
Custom roles must adhere to this name format in order to be shown in the UI:
ROLE_
You can add them to the application manifest in the roles
properties as follows:
"roles": [
"ROLE_MY_MICROSERVICE_READ",
"ROLE_MY_MICROSERVICE_ADMIN"
]
The roles are set in the Microservice manifest. For more details about users and roles, review the User API in the Cumulocity OpenAPI Specification.
Microservice bootstrap
Each microservice receives a dedicated bootstrap user which ensures that a microservice can be identified by the platform and can have access only to the allowed resources. A microservice runtime provides bootstrap user and service user credentials as environment variables which can also be acquired via platform API. Note that depending on the isolation level, the environment variables differ.
Variable | Description | Per tenant scope | Multi-tenant scope |
---|---|---|---|
C8Y_BOOTSTRAP_TENANT | Application owner tenant ID | x | x |
C8Y_BOOTSTRAP_USER | Username of the bootstrap user | x | x |
C8Y_BOOTSTRAP_PASSWORD | Password of the bootstrap user | x | x |
C8Y_TENANT | Subscribed tenant ID | x | |
C8Y_USER | Username of the service user of a subscribed tenant | x | |
C8Y_PASSWORD | Password of the service user of a subscribed tenant | x |
In multi-tenant scope, there is a single microservice deployment reused by multiple tenants and that is why service user credentials are not provided as hardcoded environment properties. However, a microservice running in multi-tenant isolation can retrieve all subscriptions via a GET request and using bootstrap credentials as follows:
GET /application/currentApplication/subscriptions
Host: ...
Authorization: Basic ...
Bootstrap user credentials can be retrieved with a GET request authorized with application owner credentials:
GET /application/applications/<APPLICATION_ID>/bootstrapUser
Host: ...
Authorization: Basic ...
An example of a typical user switching in multi-tenant isolation is presented below, where – in a hypothetical scenario – there is a need to send an alarm to each tenant subscribed to a microservice.
The user wants to employ microservice capabilities to raise alarms to all subscribed tenants calls.
Steps:
- The user makes a request to the platform’s endpoint /service/<microservice>/createAlarms.
- The platform verifies the user credentials and redirects the request to the microservice.
- The microservice reads the bootstrap credentials (from environment variables) and uses them to retrieve the service user credentials for all subscribed tenants.
- The microservice iterates over the service user credentials and uses them to create alarms to each tenant.
- The microservice returns the result to the platform, and the platform to the invoking user.
Encryption
There is a mechanism to encrypt the tenant options that afterwards are automatically decrypted when injecting them into microservices requests.
If a tenant option is created with a key name that starts with “credentials.”, it is automatically encrypted and can be fetched as unencrypted only by system users. For instance, when you create a tenant option in a category that matches to the application context path, the value is passed to the microservice by the microservice proxy on the platform as a header (key => value). Note that therefore a space (” “) is not allowed as a tenant option value. All encrypted options are decrypted and passed. Moreover, the options can be fetched via REST using the options endpoint at microservice runtime.
Refer to tenant options in the Tenant API in the Cumulocity OpenAPI Specification for more details.
Microservice runtime
Microservices deployed on the platform have a specific runtime environment and they must understand certain details about the specific Cumulocity cluster they run in. For example, a microservice needs to know the endpoint address of the Cumulocity REST APIs. This information is provided by environment variables and they are injected by Cumulocity when the container is started.
Environment variables
The following environment variables are available for microservices:
Name | Details |
---|---|
APPLICATION_NAME | The name of the microservice application |
APPLICATION_KEY | The key of the microservice application |
SERVER_PORT | Default open port (80) |
MICROSERVICE_SUBSCRIPTION_ENABLED | Default value: true |
C8Y_BASEURL | Platform address (contains port number) |
C8Y_BASEURL_MQTT | Platform address of the MQTT server (contains port number) |
C8Y_MICROSERVICE_ISOLATION | Isolation level (MULTI_TENANT or PER_TENANT) |
C8Y_BOOTSTRAP_REGISTER | Indicator whether the microservice should perform self registration or not. Default value: false |
C8Y_BOOTSTRAP_TENANT | Bootstrap user tenant, for MULTI_TENANT - microservice owner |
C8Y_BOOTSTRAP_USER | Bootstrap username |
C8Y_BOOTSTRAP_PASSWORD | Bootstrap user password |
C8Y_TENANT | Application user tenant (available only for PER_TENANT isolation) |
C8Y_USER | Application username (available only for PER_TENANT isolation) |
C8Y_PASSWORD | Application user password (available only for PER_TENANT isolation) |
MEMORY_LIMIT | Max memory that can be used. Default value: 256M |
TZ | Timezone from the host machine or configurable tenant options |
LOG4J_FORMAT_MSG_NO_LOOKUPS | Disables the vulnerable Log4j lookup feature (see CVE-2021-44228) Default value: true |
Example
Prerequisite: The microservice has been packed and deployed in the Docker repository. Get the microservice image name and tag with the following command:
$ docker images
Run the Docker container for the microservice providing the environment variables:
$ docker run -–cap-drop=ALL -–cap-add=NET_BIND_SERVICE \
-e C8Y_BOOTSTRAP_TENANT=<BOOTSTRAP_TENANT> \
-e C8Y_BOOTSTRAP_USER=<BOOTSTRAP_USERNAME> \
-e C8Y_BOOTSTRAP_PASSWORD=<BOOTSTRAP_USER_PASSWORD> \
-e C8Y_MICROSERVICE_ISOLATION=MULTI_TENANT \
-e C8Y_BASEURL=<URL> -i -t <DOCKER_REPOSITORY_IMAGE>:<TAG>
Use a backslash (\) before special characters such as &, !, ;, \
.
Timezone variable
The timezone variable allows configuring a default timezone used by the microservice.
The microservice installer injects the TZ
environment variable into the microservice according to the following settings:
- Tenant option in the microservice owner tenant
- Platform application environment variables (MICROSERVICE_RUNTIME_TIMEZONE)
The tenant option has higher priority, that means, if the parameter is set in both places, the value from the tenant option is taken.
Example
Assuming that the microservice owner has the tenant option:
{
"category" : "microservice.runtime",
"key" : "timezone",
"value" : "Europe/Warsaw"
}
Deploying and running the microservice inside Docker will result in passing the following variables into the microservice environment:
TZ=Europe/Warsaw
When using Java-based microservices this variable is automatically read and applied to the Java process, no additional work is required. Microservices developed with other programming languages may require some manual work, that is, loading the TZ value from the environment and using it to configure the time zone on the language level programmatically.
Proxy variables
Proxy variables are used to set a proxy URL for different protocols. For the microservices written in Java, setting each variable will result in passing the corresponding parameter into the JVM runtime (for detailed information see the Java Networking and Proxies webpage).
Proxy variables are passed into the microservice environment during installation. The microservice installer passes the variables into the environment according to the following settings:
- Tenant options in the microservice owner tenant
- Platform application environment variables
Tenant options have higher priority, that means, if the parameter is set in both places, the value from the tenant option is taken.
The table below contains the options and proxy variables:
Tenant option | Platform env variable | Microservice env variable |
---|---|---|
proxy.http.host | MICROSERVICE_RUNTIME_PROXY_HTTP_HOST | PROXY_HTTP_HOST |
proxy.http.port | MICROSERVICE_RUNTIME_PROXY_HTTP_PORT | PROXY_HTTP_PORT |
proxy.http.non.proxy.hosts | MICROSERVICE_RUNTIME_PROXY_HTTP_NON_PROXY_HOSTS | PROXY_HTTP_NON_PROXY_HOSTS |
proxy.https.host | MICROSERVICE_RUNTIME_PROXY_HTTPS_HOST | PROXY_HTTPS_HOST |
proxy.https.port | MICROSERVICE_RUNTIME_PROXY_HTTPS_PORT | PROXY_HTTPS_PORT |
proxy.socks.host | MICROSERVICE_RUNTIME_PROXY_SOCKS_HOST | PROXY_SOCKS_HOST |
proxy.socks.port | MICROSERVICE_RUNTIME_PROXY_SOCKS_PORT | PROXY_SOCKS_PORT |
All tenant options have the same category: microservice.runtime
For each protocol (HTTP, HTTPS, Socks), microservice environment variables are passed into runtime only if the HOST parameter is set. If the HOST parameter is missing, other parameters for the same protocol are not processed.
Examples
The microservice owner tenant has the tenant options:
{ "category" : "microservice.runtime", "key" : "proxy.http.host", "value" : "10.11.12.13" }
{ "category" : "microservice.runtime", "key" : "proxy.http.port", "value" : "8080" }
and there is an environment variable in the platform application:
MICROSERVICE_RUNTIME_PROXY_HTTP_PORT=8181
Deploying and running the microservice inside Docker will result in passing the following variables into the microservice environment (notice PORT value):
PROXY_HTTP_HOST=10.11.12.13
PROXY_HTTP_PORT=8080
The microservice owner tenant has the tenant option:
{ "category" : "microservice.runtime", "key" : "proxy.https.host", "value" : "10.11.12.13" }
and there is an environment variable in the platform application:
MICROSERVICE_RUNTIME_PROXY_HTTPS_PORT=8181
Deploying and running the microservice inside Docker will result in passing the following variables into the microservice environment:
PROXY_HTTP_HOST=10.11.12.13
PROXY_HTTP_PORT=8181
The microservice owner tenant has the tenant options:
{ "category" : "microservice.runtime", "key" : "proxy.http.port", "value" : "8080" }
{ "category" : "microservice.runtime", "key" : "proxy.http.non.proxy.hosts", "value" : "localhost" }
and the proxy HOST is not set (neither in tenant option, nor env variable).
Deploying and running the microservice inside Docker will not pass any proxy environment variable.
The microservice owner tenant has the tenant option:
{ "category" : "microservice.runtime", "key" : "socks.http.host", "value" : "10.11.12.13" }
Deploying and running the microservice inside Docker will result in passing the following variable into the microservice environment (only host parameter):
SOCKS_HTTP_HOST=10.11.12.13
Platform access and other microservices
To execute requests against the Cumulocity platform running a microservice, you must send requests to the host specified by the C8Y_BASEURL
variable.
A microservice does not have direct access to other microservices running on the platform. Instead, a microservice must use the platform as a proxy. The endpoint used to access other applications is <C8Y_BASEURL>/service/<OTHER_APPLICATION_NAME>/.
C8Y_BASEURL
allows access only to microservices’ REST endpoints. Hence, a microservice cannot retrieve information from UI applications.
When accessing the REST endpoints the connections may time out. This requires the HTTP client of a microservice to be prepared for retries to establish a connection.
The client used in the Microservice SDK performs the required retries. Using it avoids issues with timed-out connections.
Request routing
The request is redirected to a microservice depending on the isolation level (auto-scaling is ignored at this moment for clarity), subscription and authorization. A typical request to the platform looks like
<METHOD> /service/<MICROSERVICE>/<MICROSERVICE_ENDPOINT>
Host: ...
Authorization: Basic ...
Credentials are used to verify if a requesting user is authorized to access the microservice, and tenant subscription is verified afterwards. If both checks pass, the request is routed to a dedicated microservice deployment in case that the isolation level is per tenant, or to a shared deployment in case of multi-tenant isolation.
The routed request is stripped of /service/<MICROSERVICE> part. However, the Authorization header is not modified, thus a request is still executed as a tenant platform user.
<METHOD> /<MICROSERVICE_ENDPOINT>
Host: ...
Authorization: Basic ...
Microservice utility tool
Cumulocity provides you a utility tool for easy microservice packaging, deployment and subscription. The script requires a local installation of Docker and jq, which is a lightweight and flexible JSON processor.
Prerequisites
Docker
Verify that you have a Docker installation. The Docker version must be >= 1.2.6
$ 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
JSON processor
Execute the following command to install the JSON processor on Linux systems:
$ sudo yum install jq
For macOS, use the following command:
$ brew install jq
Bash
The microservice utility tool (script) needs Bash version 4+ to run. Verify your Bash version with the following command:
$ bash --version
GNU bash, version 5.0.3(1)-release (x86_64-apple-darwin18.2.0)
Copyright (C) 2019 Free Software Foundation, Inc.
macOS systems come with a preinstalled Bash version 3.x. Hence, you must update it in order to execute the microservice script. To do so, execute the following commands:
$ brew install bash
$ chsh -s /usr/local/bin/bash
If your Bash version has not changed while executing bash --version
, you may need to restart your system. Note that the updated interpreter gets installed at /usr/local/bin/bash and you must modify the first line of the microservice utility tool (script) as follows:
#!/usr/local/bin/bash
or
#!
Configure the microservice utility tool
The script can be found in the GitHub repository: cumulocity-examples.
Change the mode to allow the script to be executed:
$ chmod +x microservice
Use the help option to see all the available functions (goals) and options:
$ ./microservice help
Packing
A microservice must be packed as a Docker image in order to be deployed. It requires a Docker image.tar and cumulocity.json files packed into a ZIP file.
The following directory structure is required to pack a microservice:
/docker/Dockerfile # Instructions to build the Docker image
/docker/* # All files within the directory will be included in the Docker build
/cumulocity.json # The application manifest file
The script can be run in a parent folder holding such structure, or by passing the path to the directory using the -dir
option. For instance, to pack a “Hello World” microservice application, execute:
$ ./microservice pack --name hello-world
It will create a ZIP file named hello-world.zip and an intermediate image.tar which is an exported Docker image.
Deploying
A microservice becomes available once it has been successfully deployed on the Cumulocity platform. This is done by uploading a ZIP file with the microservice packed as specified above. A user cannot directly push an image to the Docker registry.
Deploying your microservice application is rather easy, just execute the following command:
$ ./microservice deploy -n hello-world -d <URL> -u <username> -p <password> -te <tenant>
Note that you must have a tenant and user credentials in order to deploy your microservice.
The successful execution will create an application on the Cumulocity platform with the specified name, if it does not exist yet. Then it will upload the hello-world.zip file into the platform. Once it has been uploaded, your application will be listed in Ecosystem > Microservices in the Administration application.
If the name of the microservice application is not provided in the manifest file, it will be automatically inferred from the ZIP file name without the version number.
For further information on deploying microservices to Cumulocity, refer to Managing microservices.
Subscribing
You must subscribe to the application in order to use it. Execute the following command to subscribe your tenant to the deployed microservice:
$ ./microservice subscribe -n hello-world -d <URL> -u <username> -p <password> -te <tenant> -id <APPLICATION_ID>
It will result in tenant subscription to an application specified by the ID parameter. If the user has already been subscribed, a warning message will be displayed.
Multiple goals
Goals can be executed together to pack, deploy and subscribe the application in a single line. In this case, the application ID will be automatically pulled by the script.
$ ./microservice pack deploy subscribe -n hello-world -d <URL> -u <username> -p <password> -te <tenant>
Operating microservices
Cumulocity manages microservices by monitoring the microservice instance and storing the metrics. In case a microservice exceeds the memory limit, it is restarted automatically. Also, microservices can be auto-scaled in case of high CPU usage. For more information, review the scaling details above.
Troubleshooting
Some common issues have been identified and documented below.
I get an error saying “Microservice application name is incorrect” on uploading a microservice application
When naming your microservice application only use lower-case letters, digits and dashes. The maximum length for the name is 23 characters.
I deployed my microservice but requests to any endpoint returns an error message “Microservice not available Connection refused”
After uploading the microservice, the internal deployment and container run may take a couple of minutes. Once completed, the error message will disappear. Meanwhile, enjoy your coffee ☕️
An alarm was created with the message “Failed to pull image … rpc error: code = Canceled desc = context canceled”
This may happen occasionally when pulling large Docker images for containers within a pod. It will go to normal once it has been successfully pulled.
An alarm was created with the message “Pod synchronization error.”
This may happen when Kubernetes is performing the auto-scaling and is trying to restart the pods.
An alarm was created with the message “No nodes are available that match all of the predicates: [Insufficient cpu …]”
There are additional containers running besides yours and they are provisioned by default with Kubernetes. These additional containers might be taking up of the CPU quota of the single node. Kubernetes will manage this and change the pod status from Pending to Running.