Build solutions in ELMA365 > Portable services in modules / Guidelines for developing microservices for portable services

Guidelines for developing microservices for portable services

Service settings

Initialization and operation of a microservice may require certain settings to be available. Sometimes a microservice needs to be adjusted to fit the needs of the consumer. For example, it may need to be possible to set a connection string for a data source. For this purpose, the developer of the microservice should add an opportunity to retrieve its settings. The developer of the module can pass the values of environment variables into the container that is to be deployed. Data from them can be used for the configuration of the microservice.

Depending on the framework chosen for development, this can be either direct reading of values from the environment variables or configuring the microservice via a separate provider, for instance as in .NET.

With this approach, the module developer can submit their data to the microservice or even give this opportunity to the end user.

Note that environment variables are set then the container is initialized, and the changes made by the end user in the module when working with it will not be applied.

Provided API

Module developers can interact with the microservice by sending HTTP requests from scripts, for example, in processes or widgets. To make it possible, a web API has to be developed and implemented for the microservice.

The API has to be documented in order to make it easier to use for the module developer.

Communication between two portable services

If needed, it is possible to set up communication between two microservices within a module using the web API.

Let’s say you need microservice #1 to send a command to microservice #2. To make it possible, microservice #1 has to be able to get the address of microservice #2 from an environment variable.

serv2url, exists := os.LookupEnv("serv2Url")

When the address is known, you can send an HTTP request:

resp, err := http.Get(serv2url + "/hello")

To make the serv2Url environment variable store the path to microservice #2, the module developer has to add the serv2Url environment variable to the settings of Portable service #1. This variable has to store the template of the name of Portable service #2. For example, {$_srv_serv2}.

This feature should be documented so that the module developer can use it.

Ports

By default, a portable service is configured to use port 3000. If the microservice uses a different port, this should be specified in the module’s documentation.

Microservice state

At the moment, storing the state of microservices is not supported in portable services. This means that when a microservice stops, data from it will be lost.

That’s why you shouldn’t use the file system to store microservice data permanently.

Example:

You shouldn’t: permanently store image files on the disk to access them upon users’ requests.

You should: temporarily upload image files to the disk to process them instantly and send them back to the user.

Logs

When you develop your microservice, note that at the moment users cannot view logs in the file system or console. If you need to add logs, return them either using a separately developed API or send them to an external system that users will have access to.

начало внимание

Note that the microservice does not save its state, so logs stored in files will be lost when the service is shut down.

конец внимание

Example with Go

The standard output for Go is the os.Stdout object that is practically a console. In the simplest case you can display data in this object by importing the log module:

log.Print("Hello log")
log.Printf("Hello log - %s", "hi yourself")

Moreover, you can use specialized logging libraries that have rich functionality, including log levels.

One example of such libraries is zap. Below you can see an example of logs showed in a format that corresponds to .e365:

// Initialization
{
enc := zap.NewProductionEncoderConfig()
enc.TimeKey = "timestamp"
enc.EncodeTime = zapcore.ISO8601TimeEncoder
 
opts := []zap.Option{
zap.AddCaller(),
}
 
logger := zap.New(zapcore.NewCore(
zapcore.NewJSONEncoder(enc),
zapcore.Lock(os.Stderr),
zapcore.InfoLevel, // Required log level
), opts...)
logger = l.Named("MyService1")
 
zap.ReplaceGlobals(logger)
}
// Getting logger
{
l = zap.L()
}
// Showing log
{
l.Info("Hello zap-log")
}

Probes

Sometimes higher standards of availability, reliability, and innovation are demanded from microservices. For these cases Kubernetes provides a special feature called probes. A microservice developer designs and implements endpoints that can be accessed to get accurate information about a microservice’s health. When the microservices are deployed, the probes request information about their current state.

Portable services support Readiness and Liveness probes. They can be used separately or at the same time for one microservice. This probes ensure the microservice receives traffic only when it’s ready to accept it. In case of a failure, the microservice is restarted.

We also recommend you to read the official documentation and guidelines to working with probes.

You should describe the possible ways to perform checks with probes in the documentation clearly and in detail. If the description is wrong or the module developer uses it in a wrong way, the microservice or even the whole module will become inoperable. It is also critical to implement proper endpoints for the probes in the microservice.

If you didn’t account for probes, specify this in the documentation. It is highly undesirable for the module developer to assign probes on their own.

Liveness probe

During the operation of a microservice, it can fall into a state that prevents it from functioning properly. The solution can be restarting the service. To check whether the microservice is in such a state, Liveness probes are used. They perform certain actions to confirm that your microservice is working as intended.

In a Liveness probe, you can make an HTTP request, open a TCP connection, or run a command in your container.

  • To perform an HTTP probe, Kubernetes sends an HTTP GET request to the microservice. If the handler for the microservice’s path returns a success code, Kubernetes considers the microservice to be alive and healthy. If the handler returns a failure code, the microservice is restarted. Any code greater than or equal to 200 and less than 400 indicates success. Any other code indicates a failure.
  • A TCP probe uses a TCP socket. With the configuration of the portable service probe, Kubernetes attempts to open a socket. If it can establish a connection, the container is considered healthy. If it can’t, it is restarted.
  • In a command-based probe, a command specified in the portable service probe settings is performed. If the command runs successfully, it returns 0, and the microservice is considered healthy. If it returns a value other than 0, the microservice is restarted.

Readiness probe

Sometimes a microservice may not be ready to accept requests. For example, while preparatory initialization is performed at the startup or there is a dependency on other external microservices. In this cases, Kubernetes needs to wait until the microservice becomes ready. During this time, the microservice shouldn’t process requests sent to it. To handle situations like these, you can use Readiness probes. This allows the microservice to signal that it is not ready at the moment, and the request should be sent to another instance if there is one.

Readiness probes are configured in the same way as Liveness probes.

Documentation

While developing a microservice, describe the following in the documentation:

  • Provided APIs.
  • The port that is used to access the microservice.
  • The list of environment variables that the microservice can be configured with.
  • Whether Liveness and Readiness probes are set up and how to use them.

Found a typo? Highlight the text, press ctrl + enter and notify us