Get the highlights in your inbox every week.
Optimize Java serverless functions in Kubernetes | Opensource.com
Optimize Java serverless functions in Kubernetes
Achieve faster startup and a smaller memory footprint to run serverless functions on Kubernetes.
A faster startup and smaller memory footprint always matter in Kubernetes due to the expense of running thousands of application pods and the cost savings of doing it with fewer worker nodes and other resources. Memory is more important than throughput on containerized microservices on Kubernetes because:
- It's more expensive due to permanence (unlike CPU cycles)
- Microservices multiply the overhead cost
- One monolith application becomes N microservices (e.g., 20 microservices ≈ 20GB)
This significantly impacts serverless function development and the Java deployment model. This is because many enterprise developers chose alternatives such as Go, Python, and Nodejs to overcome the performance bottleneck—until now, thanks to Quarkus, a new Kubernetes-native Java stack. This article explains how to optimize Java performance to run serverless functions on Kubernetes using Quarkus.
Container-first designTraditional frameworks in the Java ecosystem come at a cost in terms of the memory and startup time required to initialize those frameworks, including configuration processing, classpath scanning, class loading, annotation processing, and building a metamodel of the world, which the framework requires to operate. This is multiplied over and over for different frameworks.
Quarkus helps fix these Java performance issues by "shifting left" almost all of the overhead to the build phase. By doing code and framework analysis, bytecode transformation, and dynamic metamodel generation only once, at build time, you end up with a highly optimized runtime executable that starts up super fast and doesn't require all the memory of a traditional startup because the work is done once, in the build phase.
More importantly, Quarkus allows you to build a native executable file that provides performance advantages, including amazingly fast boot time and incredibly small resident set size (RSS) memory, for instant scale-up and high-density memory utilization compared to the traditional cloud-native Java stack.
Here is a quick example of how you can build the native executable with a Java serverless function project using Quarkus.
1. Create the Quarkus serverless Maven project
This command generates a Quarkus project (e.g.,
quarkus-serverless-native) to create a simple function:
$ mvn io.quarkus:quarkus-maven-plugin:1.13.4.Final:create \
2. Build a native executable
You need a GraalVM to build a native executable for the Java application. You can choose any GraalVM distribution, such as Oracle GraalVM Community Edition (CE) and Mandrel (the downstream distribution of Oracle GraalVM CE). Mandrel is designed to support building Quarkus-native executables on OpenJDK 11.
pom.xml, and you will find this
native profile. You'll use it to build a native executable:
Note: You can install the GraalVM or Mandrel distribution locally. You can also download the Mandrel container image to build it (as I did), so you need to run a container engine (e.g., Docker) locally.
Assuming you have started your container runtime already, run one of the following Maven commands.
$ ./mvnw package -Pnative \
$ ./mvnw package -Pnative \
The output should end with
Run the native executable directly without Java Virtual Machine (JVM):
The output will look like:
__ ____ __ _____ ___ __ ____ ______
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
INFO [io.quarkus] (main) quarkus-serverless-native 1.0.0-SNAPSHOT native
(powered by Quarkus xx.xx.xx.) Started in 0.019s. Listening on: http://0.0.0.0:8080
INFO [io.quarkus] (main) Profile prod activated.
INFO [io.quarkus] (main) Installed features: [cdi, kubernetes, resteasy]
Supersonic! That's 19 milliseconds to startup. The time might be different in your environment.
It also has extremely low memory usage, as the Linux
ps utility reports. While the app is running, run this command in another terminal:
$ ps -o pid,rss,command -p $(pgrep -f runner)
You should see something like:
PID RSS COMMAND
10246 11360 target/quarkus-serverless-native-1.0.0-SNAPSHOT-runner
This process is using around 11MB of memory (RSS). Pretty compact!
Note: The RSS and memory usage of any app, including Quarkus, will vary depending on your specific environment and will rise as application experiences load.
You can also access the function with a REST API. Then the output should be
$ curl localhost:8080/hello
3. Deploy the functions to Knative service
If you haven't already, create a namespace (e.g.,
quarkus-serverless-native) on OKD (OpenShift Kubernetes Distribution) to deploy this native executable as a serverless function. Then add a
quarkus-openshift extension for Knative service deployment:
$ ./mvnw -q quarkus:add-extension -Dextensions="openshift"
Append the following variables in
src/main/resources/application.properties to configure Knative and Kubernetes resources:
Build the native executable, then deploy it to the OKD cluster directly:
$ ./mvnw clean package -Pnative
Note: Make sure to log in to the right project (e.g.,
quarkus-serverless-native) using the
oc logincommand ahead of time.
The output should end with
BUILD SUCCESS. It will take a few minutes to complete a native binary build and deploy a new Knative service. After successfully creating the service, you should see a Knative service (KSVC) and revision (REV) using either the
oc command tool:
$ kubectl get ksvc
NAME URL [...]
quarkus-serverless-native http://quarkus-serverless-native-[...].SUBDOMAIN True
$ kubectl get rev
NAME CONFIG NAME K8S SERVICE NAME GENERATION READY REASON
quarkus-serverless-native-00001 quarkus-serverless-native quarkus-serverless-native-00001 1 True
4. Access the native executable function
Retrieve the serverless function's endpoint by running this
$ kubectl get rt/quarkus-serverless-native
The output should look like:
NAME URL READY REASON
quarkus-serverless-native http://quarkus-serverless-restapi-quarkus-serverless-native.SUBDOMAIN True
Access the route
URL with a
$ curl http://quarkus-serverless-restapi-quarkus-serverless-native.SUBDOMAIN/hello
In less than one second, you will get the same result as you got locally:
When you access the Quarkus running pod's logs in the OKD cluster, you will see the native executable is running as the Knative service.
You can optimize Java serverless functions with GraalVM distributions to deploy them as serverless functions on Knative with Kubernetes. Quarkus enables this performance optimization using simple configurations in normal microservices.
The next article in this series will guide you on making portable functions across multiple serverless platforms with no code changes.