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:
<dependency>
<groupId>net.morimekta.tiny.server</groupId>
<artifactId>tiny-server</artifactId>
<version>0.5.0</version>
</dependency>
To add to gradle
: Add this line to the dependencies
group in build.gradle
:
implementation 'net.morimekta.tiny.server:tiny-server:0.5.0'
What does it provide
Default admin servlets:
/healthy
: Health check./ready
: Ready check./drain
: Drain / stop service.
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.
Now the server is active. At some point, either /drain
is called, or a signal
do stop the process is caught, triggering the shutdownHook
, each starts the
following:
- 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 app should exit here.
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.addReadyCheck("my-service", new MyServiceReadyCheck(myService));
context.addHealthCheck("grpc-server", new GrpcHealthCheck(server));
}
@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())
}
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.server.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>net.morimekta.tiny.server:*</artifact>
<includes>
<include>**</include>
</includes>
</filter>
</filters>