Last active
March 4, 2020 10:55
-
-
Save bademux/141f242ff1f1f0539dc73c83b3f928d9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.demo; | |
import util.Healthchecker; | |
public class DemoApplicationHealthcheck { | |
public static void main(String[] args) throws Exception { | |
Healthchecker.registerHealthcheckServer(DemoApplication::healthcheck); | |
System.out.println("--- startring app "); | |
} | |
private static boolean healthcheck() { | |
boolean health = true; | |
System.out.println("--- healthcheck method invoked: " + health); | |
return health; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FROM gcr.io/distroless/java:11 | |
HEALTHCHECK --start-period=5s --interval=3s --retries=3 --timeout=5s CMD ["java", "-Xint", "-Xmx10M", "-cp", "/app/classes", "util.Healthchecker"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package util; | |
import javax.management.*; | |
import javax.management.remote.JMXServiceURL; | |
import java.lang.management.ManagementFactory; | |
import java.util.Arrays; | |
import java.util.function.BooleanSupplier; | |
import static java.lang.String.format; | |
import static java.util.Objects.requireNonNull; | |
import static javax.management.MBeanOperationInfo.ACTION_INFO; | |
import static javax.management.remote.JMXConnectorFactory.connect; | |
/** | |
* JMX-based, uses ~41Mb RAM for probing on Hotspot11 | |
*/ | |
public final class Healthchecker { | |
private static final String OP_HEALTHCHECK = "healthcheck"; | |
private static final ObjectName objectName = createObjectName(); | |
public static void main(String[] args) throws Exception { | |
System.exit(probeLocalhost() ? 0 : 1); | |
} | |
public static void registerHealthcheckServer(BooleanSupplier healthcheckHandler) throws Exception { | |
ManagementFactory.getPlatformMBeanServer() | |
.registerMBean(new HealthcheckBean(OP_HEALTHCHECK, healthcheckHandler), objectName); | |
} | |
public static boolean probeLocalhost() throws Exception { | |
return probe("localhost", 9999); | |
} | |
public static boolean probe(String host, int port) throws Exception { | |
try (var connector = connect(new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + host + ":" + port + "/jmxrmi"))) { | |
return (boolean) connector.getMBeanServerConnection() | |
.invoke(objectName, OP_HEALTHCHECK, null, null); | |
} | |
} | |
private static ObjectName createObjectName() { | |
try { | |
return new ObjectName("com.github.bademux.jmx", "type", "HealthcheckBean"); | |
} catch (MalformedObjectNameException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
private static final class HealthcheckBean implements DynamicMBean { | |
private final BooleanSupplier handler; | |
private final MBeanInfo beanInfo; | |
private final String operationName; | |
public HealthcheckBean(String operationName, BooleanSupplier handler) { | |
this.operationName = requireNonNull(operationName); | |
this.handler = requireNonNull(handler); | |
MBeanOperationInfo opInfo = new MBeanOperationInfo(operationName, "Run healthcheck", null, "boolean", ACTION_INFO); | |
beanInfo = new MBeanInfo(this.getClass().getName(), "JMX healthcheck", null, null, new MBeanOperationInfo[]{opInfo}, null); | |
} | |
@Override | |
public MBeanInfo getMBeanInfo() { | |
return beanInfo; | |
} | |
@Override | |
public Object invoke(String operationName, Object[] params, String[] signature) throws MBeanException { | |
if (this.operationName.equals(operationName)) { | |
return handler.getAsBoolean(); | |
} | |
throw new MBeanException(new NoSuchMethodException(operationName), | |
format("Cannot find the operation '%s' expecting: '%s'", operationName, this.operationName)); | |
} | |
@Override | |
public Object getAttribute(String attribute) { | |
throw new UnsupportedOperationException("unavailable operation for " + attribute); | |
} | |
@Override | |
public void setAttribute(Attribute attribute) { | |
throw new UnsupportedOperationException("unavailable operation for " + attribute); | |
} | |
@Override | |
public AttributeList getAttributes(String[] attributes) { | |
throw new UnsupportedOperationException(String.format("unavailable operation for %s", Arrays.toString(attributes))); | |
} | |
@Override | |
public AttributeList setAttributes(AttributeList attributes) { | |
throw new UnsupportedOperationException("unavailable operation for " + attributes); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package util; | |
import java.io.IOException; | |
import java.net.InetAddress; | |
import java.net.ServerSocket; | |
import java.net.Socket; | |
import java.util.function.BooleanSupplier; | |
/** | |
* Socket-based, uses ~23Mb RAM for probing on Hotspot11 | |
*/ | |
public final class Healthchecker { | |
public static final int PORT = 9998; | |
public static final int PROBE_TIMEOUT_MILLIS = 1000; | |
// --- Healthcheck Client. Invoked by docker healthcheck | |
public static void main(String[] args) { | |
System.exit(probe()); | |
} | |
private static int probe() { | |
try (var socket = new Socket(InetAddress.getLocalHost(), PORT)) { | |
socket.setSoTimeout(PROBE_TIMEOUT_MILLIS); | |
socket.setOOBInline(true); | |
socket.sendUrgentData(0); | |
return socket.getInputStream().read(); | |
} catch (Exception e) { | |
return -2; | |
} | |
} | |
// --- Healthcheck Server. Executed in application runtime | |
public static void registerHealthcheckServer(BooleanSupplier healthcheckHandler) { | |
var serverThread = new Thread(() -> runServer(healthcheckHandler), "Healthchecker"); | |
serverThread.setPriority(Thread.MIN_PRIORITY); | |
Runtime.getRuntime().addShutdownHook(new Thread(serverThread::interrupt)); | |
serverThread.start(); | |
} | |
private static void runServer(BooleanSupplier healthcheckHandler) { | |
try (var listener = new ServerSocket(PORT, 1, InetAddress.getLocalHost())) { | |
while (true) { | |
handleRequest(healthcheckHandler, listener); | |
} | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
private static void handleRequest(BooleanSupplier healthcheckHandler, ServerSocket listener) throws InterruptedException { | |
try (var socket = listener.accept()) { | |
socket.setOOBInline(true); | |
int command = socket.getInputStream().read(); | |
socket.sendUrgentData(command == -1 || !healthcheckHandler.getAsBoolean() ? -1 : 0); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
Thread.sleep(100); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
<modelVersion>4.0.0</modelVersion> | |
<groupId>com.example</groupId> | |
<artifactId>demo</artifactId> | |
<version>0.0.1-SNAPSHOT</version> | |
<name>demo</name> | |
<description>Demo project for healthcheck on distroless-based containers using JMX</description> | |
<properties> | |
<java.version>11</java.version> | |
</properties> | |
<build> | |
<plugins> | |
<plugin> | |
<groupId>com.google.cloud.tools</groupId> | |
<artifactId>jib-maven-plugin</artifactId> | |
<version>2.0.0</version> | |
<configuration> | |
<to> | |
<image>distroless_docker_healthchecked:latest</image> | |
</to> | |
<from> | |
<image>gcr.io/distroless/java:11</image> | |
</from> | |
<container> | |
<mainClass>com.example.demo.DemoApplication</mainClass> | |
<args> | |
<arg>-Djava.security.egd=file:/dev/./urandom</arg> | |
</args> | |
<jvmFlags> | |
<jvmFlag>-XX:MaxRAMPercentage=96</jvmFlag> | |
<!-- jmxremote is used fo healthchecking app--> | |
<!-- Port must not be never exposed without auth--> | |
<jvmFlag>-Dcom.sun.management.jmxremote.port=9999</jvmFlag> | |
<jvmFlag>-Dcom.sun.management.jmxremote.authenticate=false</jvmFlag> | |
<jvmFlag>-Dcom.sun.management.jmxremote.ssl=false</jvmFlag> | |
</jvmFlags> | |
<user>nonroot</user> | |
<format>Docker</format> | |
</container> | |
</configuration> | |
</plugin> | |
</plugins> | |
</build> | |
</project> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"containerDefinitions": [ | |
{ | |
"healthCheck": { | |
"retries": 5, | |
"command": [ | |
"CMD", | |
"java", | |
"-Xint", | |
"-Xmx10M", | |
"-cp", | |
"/app/classes", | |
"util.Healthchecker" | |
] | |
} | |
} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment