Tiny Server for Microservices
Tiny Server Application wrapper for non-WEB microservices. It is designed to be
absolutely minimal but with all the essential features of a service running on
Kubernetes.
See morimekta.net/utils for procedures on releases. It is managed in the same group as the utilities, even though it is a more specialized utility. See here for a small example server using gRPC.
Getting Started
To add to maven
: Add this line to pom.xml
under dependencies:
<dependencies>
<dependency>
<groupId>net.morimekta.tiny</groupId>
<artifactId>tiny-http</artifactId>
<version>0.7.1</version>
</dependency>
<dependency>
<groupId>net.morimekta.tiny</groupId>
<artifactId>tiny-logback</artifactId>
<version>0.7.1</version>
</dependency>
<dependency>
<groupId>net.morimekta.tiny</groupId>
<artifactId>tiny-server</artifactId>
<version>0.7.1</version>
</dependency>
</dependencies>
To add to gradle
: Add this line to the dependencies
group in build.gradle
:
implementation 'net.morimekta.tiny.server:tiny-http:0.7.1'
implementation 'net.morimekta.tiny.server:tiny-logback:0.7.1'
implementation 'net.morimekta.tiny.server:tiny-server:0.7.1'
What does it provide
Default admin servlets:
/healthy
: Health check./ready
: Ready check./drain
: Drain / stop service, but not kill it. Will call thebeforeStop()
method, but not stop the adming HTTP server, and not theafterStop()
method. The latter one will be called when the service is stopped by the normal shutdown hook. See shutdown sequence for details.
The life-cycle of the service is as follows:
initialize()
is called.- Command line arguments are parsed, and value setters called.
- Admin HTTP server is started with a default
/ready
check with failing status ("Server is starting."). onStart()
is called.- The default ready check is removed, so only user-specific ready checks remain.
Now the server is active. At some point, a signal do stop the process is caught, triggering the shutdownHook
.
Alternatively a /drain
call may run the first half of this.
- This is only done once, either from
shutdownHook
, or from/drain
- A default
/ready
check with failing status is added ("Server is stopping.") beforeStop()
is called.- The HTTP server is stopped. Now calls to
/ready
and/health
will both fail with connection refused. afterStop()
is called. The/drain
hook will not do this.- The app should exit here. The
/drain
hook will not do this.
Starting a Simple Server
Notes:
- Configuration helpers are found in
net.morimekta.utils:config
. - Extra info on the Argument Parser is found in
net.morimekta.utils:terminal
. - Metrics with prometheus can be added with
io.prometheus:simpleclient_httpserver
.
Example code for java
:
package net.morimekta.test;
import net.morimekta.config.ConfigSupplier;
import net.morimekta.terminal.args.ArgParser;
import net.morimekta.terminal.args.Option;
import net.morimekta.tiny.server.TinyApplication;
import net.morimekta.tiny.server.TinyApplicationContext;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import java.util.concurrent.atomic.AtomicInteger;
import static net.morimekta.terminal.args.ValueParser.i32;
import static net.morimekta.terminal.args.ValueParser.path;
public class MyApplication extends TinyApplication {
private final ConfigSupplier<MyConfig> config = ConfigSupplier.yamlConfig(MyConfig.class);
// My gRPC server.
private Server server;
@Override
protected void initialize(ArgParser.Builder argParser, TinyApplicationContext.Builder context) {
argParser.add(Option.optionLong("--config", "The config", path(config::loadUnchecked))
.required());
context.addHttpHandler("/metrics", new HTTPMetricHandler(CollectorRegistry.defaultRegistry));
}
@Override
protected void onStart(TinyApplicationContext context) {
var myService = new MyServiceImpl();
// start my gRPC service.
server = ServerBuilder
.forPort(config.get().grpcPort)
.addService(myService)
.build();
server.start();
context.addHealthCheck("grpc-server", new GrpcHealthCheck(server))
.addReadyCheck("my-service", new MyServiceReadyCheck(myService));
}
@Override
protected void afterStop(TinyApplicationContext context) {
server.stop();
}
public static void main(String[] args) {
TinyApplication.start(new MyApplication(), args);
}
}
Example code for kotlin
:
package net.morimekta.test
import io.grpc.Server
import io.grpc.ServerBuilder
import net.morimekta.config.ConfigSupplier
import net.morimekta.terminal.args.ArgParser
import net.morimekta.terminal.args.Option.optionLong
import net.morimekta.terminal.args.ValueParser.path
import net.morimekta.tiny.server.TinyApplication
import net.morimekta.tiny.server.TinyApplicationContext
class MyApplication : TinyApplication() {
val config = ConfigSupplier.yamlConfig(MyConfig::class.java) {
it.registerModule(KotlinModule.Builder().build())
}
lateinit var server: Server
override fun initialize(argParser: ArgParser.Builder, context: TinyApplicationContext.Builder) {
argParser.add(optionLong("--config", "The application config.", path { config.load(it) }).required())
context.addHttpHandler("/metrics", HTTPMetricHandler(CollectorRegistry.defaultRegistry))
}
override fun onStart(context: TinyApplicationContext) {
val myService = MyServiceImpl()
// start my gRPC service.
server = ServerBuilder
.forPort(config.get().grpcPort)
.addService(/* my service */)
.build()
server.start()
context.addReadyCheck("my-service", MyServiceReadyCheck(myService))
context.addHealthCheck("grpc-server", GrpcHealthCheck(server))
}
override fun afterStop(context: TinyApplicationContext) {
server.stop()
}
}
fun main(args: Array<String>) {
TinyApplication.start(MyApplication(), args)
}
Using Json Logging Layout
You can use the Json logging layout in order to get the full power of
stackdriver
or
elasticsearch
with
kibana
et al. Example logback.xml
file.
<configuration>
<statusListener class="ch.qos.logback.core.status.NopStatusListener"/>
<appender name="JSON-OUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="net.morimekta.tiny.logback.JsonLayout">
<zoneId>UTC</zoneId>
<includeMdc>true</includeMdc>
<stackTraceFormat>full</stackTraceFormat>
<stackTraceIncludeShort>true</stackTraceIncludeShort>
<stackTraceFilter>
com.sun.net.httpserver,
java.lang.reflect,
java.util.ArrayList.forEach,
java.util.concurrent,
java.util.stream,
jdk.httpserver,
jdk.internal.reflect,
sun.net.httpserver,
</stackTraceFilter>
</layout>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="JSON-OUT"/>
</root>
</configuration>
Note that if you use the maven-shade-plugin
with the minimize option, you need
to make sure the JsonLayout
class is not excluded, as it is not referenced anywhere
in the code. You need to add a filter for this in the <configuration>
block, and
you will most likely need a similar rule for ch.qos.logback:*
for
ch/qos/logback/**
and META-INF/services/**
):
<filters>
<filter>
<artifact>org.qos.logback:*</artifact>
<includes>
<include>**</include>
</includes>
</filter>
<filter>
<artifact>net.morimekta.tiny:tiny-logback</artifact>
<includes>
<include>**</include>
</includes>
</filter>
</filters>