Transcript
This transcript was autogenerated. To make changes, submit a PR.
Are you an SRE, a developer,
a quality engineer who wants to tackle the challenge of improving
reliability in your DevOps? You can enable your DevOps
for reliability with chaos native. Create your
free account at Chaos native Litmus Cloud hello,
it's miece Shukong and we will be talking about
reaching effectiveness for modern Java applications
and still keeping them reliable in modern
deployments. I work at a
company named Bellsoft. We are among
top OpenJDK enterprise contributors for
minor releases and we release
our own binary distribution of OpenJDK called Liberica
JDK. I will mention it today,
but all that I say is applicable
to generality to any OpenJDK distribution.
I guess so.
Today we most typically use containers
to offload routine task of reproducing
same environment for all different
applications we have assembled together.
And we take help
from systems like kubernetes and other
systems that provide us other components
to assemble all the production, the business
logic, and also things like circuit breaker or
fingers controller. Anyway,
they're helpful. And inside containers that we produce
we typically see layers starting
from scratch and then having some os.
In the case of Java world,
of course some runtime and to run that
JVM we need some os packages and we may also
need some os packages to run the application itself.
Maybe we have some native dependencies and
then we have Java libraries. We typically have
some framework that helps to
connect to database to respond with
some nice formatted output to our clients,
whatever it makes.
And then they have a tiny piece of the
iceberg where our business logic, our application
lives. And this
is for classical container, the for JVM. And there
may be another case then we use and when we produce native images
using static compilation to
decode machine code of the target platform.
In this case we'll probably have larger
tip of the iceberg, but we won't have additional
things. We may even skip the OS
layer sometimes. Then we link
all necessary dependencies into the native image itself.
So how
small is that iceberg tip in the case
of regular JVM deployment?
If we go and if we
imagine some spring application using spring
boot framework and we go and create such an application using spring
initializer,
we will have a working project. We can compile it and get
a 37 megabytes fat jar.
That's all we need to run it on
the JVMCI. But if we extract
thin jar out of it, it will
be as small as 3 kb, so it
will be really extremely tiny.
This is a piece of code that initializes
our application, maybe starts from endpoint, that's it.
Everything else is performed by the framework and
then we wrap it into a container.
How do we do that? Well, there are different ways.
Sometimes we even see so
called classical deployments and we create war files and
they are deployed on the web server. Some people
use out of the box functionality provided
by the framework to produce the container,
and still many developers
or DevOps write docker files themselves.
There's no wrong and right way
here. All approaches
have their drawbacks and have reasons to
be used. But we can
help in many cases from the side
of vendor of JVM,
or from other perspectives. Let's see.
So as I mentioned, we need some parent
image in all the cases to
inject our application logic.
On top of that and from
JDK vendor perspective, base image
is always an image having Java runtime.
So I will use base and parent image
terms both for the same thing,
and then we put Java runtime into the
container image. It can be
done in a different ways. Basically very
simple options like we can drop a binary, extract it and
create all necessary dependencies. That's one of the ways we
can install some OpenJDK distribution
from standard packages repository of
the OS distribution, or we can plug in
some additional repository and
install the package from there.
In all that cases, it's good to be sure
that someone who
provided the base container image for you
to care about testing because
there is a huge amount of compatibility
testing for the case.
Then we create a JDK distribution.
Many tests are to be run and
there are bugs and there
may be fine issues that we will see later.
Then we install something looking pretty
standard, but we get
some unexpected behavior.
So that is very simple to get an
OpenJDk for your Java service or
groovy service or Kotlin service. We just
can use docker pool and say okay,
I need something called openjdk, get jit please.
And it will work,
no problem, you'll have some image.
Well you probably already asked
all the questions about testing and who provide that.
Well anyway,
in my circumstances with 100
megabit network it will be downloaded half of a minute.
It really takes a long time to get
to my machine. What's the problem actually then
it is uncompressed. Jit takes half,
more than half of a gigabyte on my disk.
That's not something that I like.
Well, it becomes even
more scary and probably unreliable. Then you have
massive deployment in your production.
Imagine that you have 1000
deploys using simultaneously and you have quarter
of terabyte flying around.
Something may go wrong it
may be easy and cheap,
but for example, if you start chasing the
main region boundaries with the traffic,
or you start to break some limits,
some quotas that you already have and that
might be used by some neighbor services or
some stuff that you put on the same machines where
you deploy.
Or maybe you don't have a public cloud
issues, but you really have a lot of private cloud
issues. Well in any case,
such a massive overhead doesn't
sound right. Well,
obviously smaller containers help us in these situations.
Smaller containers, small container images, base images,
JVMci how do we get that?
Well, there are different ways,
different vendors, providers. We can
go and search, but then we have to select some.
Well we'll look at a couple of examples here.
For example,
someone may say okay, we are targeted to microservices,
to cloud deployments, kubernetes,
and someone else can say exactly the same.
Well let's look at
these examples in action. We can
try two different containers with Java eight.
It is still alive, long term support at the release of JDK
and we take something that's expected to be the latest
version and we see that in one case the version is
not latest, which means we
miss like a year of security updates in
this base image. That's really not good,
while it is smaller and look
here, both images are smaller than the one
that I showed you first.
Well, what's inside? Something is missing.
The smallest one doesn't have.
So Jit is outdated and it doesn't
have a diagnostic tool called Java Flight recorder or
JDK flight recorder. It is
a very convenient tool. You can go to
Bellsoft's block for example. We have a detailed
overview of how to squeeze
useful information about your production using JFR
and it's just not there. It is a free option in OpenJDK,
but it's not included here.
Or we can go another way.
If we use spring, we can issue a simple
comment spring build a container
image for me and it will perform container
image build and the output will be a
container image in your local repository which
is already layered and inside it has
supported Liberica JDK.
So if you building such
images on a regular manner, you will have updated
version with security updates and you can switch to
some page support.
But here you get the properly
prepared container out of the box,
which is also much smaller than we saw
in the previous cases and it is
a convenient way and diagnostic
tools like JFR are already included here.
Well this is great.
Besides of JDK, the extremely
important part of your base container image is
the OS layer and options here
are really different.
First they are really different in size,
there are humongous ones and there is one
which is extremely slim, but still it
has standard C library, JIt has a package manager
and it has a shell so you can log
in in a convenient manner,
apply some diagnostic scripts, standard JDK
tools, whatever. And this distribution
is called Alpine Linux. Well it's
slightly different.
This distribution is based on a special
C library called muscle. It also
has something called Busybox which is a
swiss army knife implementing all standard or many
standard Unix tools.
And JDK was ported to
be able to work natively on opine Linux
and other muscle based distributions
in the manner when it doesn't require glipc
library. So we only use muscle and that
allows us to use that initially slim version of
Alpine. There is also an option to
use glipsy layer. It will be still small but
it will be noticeably larger than
the muscle version.
So if we look what happened
then we released some like in this case 15
version of JDK comparing
other images that are also slim.
The Alpine is a champion here. And by the way
if you noticed few slides before,
Alpine Linux is even smaller than distro less
which is actually a distro image devout
package manager and shell.
Well pull time is only 4 seconds
that this small image paste on
Alpine. So in 4 seconds on 100 megabytes network
you get working OpenJDK in a container.
That's really cool and really fast. If you have
faster network of course you get it even faster.
Well for Opian there are different
options. Again the
landscape is big and here we compiler
for example the berka with
another base image which appears to
be smaller. Well why doesn't
happen? You see that
RTjar and other jars
that contain standard library classes are compressed
in the case of other distribution.
Well unfortunately for Java
eight that leads to a performant
hit. Then we start our application and then it uses some
classes and definitely it does classes
from standard library it start to
load slower and moreover
than we transfer such an image over the network.
Yes it may take less size
on disk but we transfer
it compressed. Like for example if we connect to
docker repository and if we
compress something twice jit actually becomes
larger. So the size
of compressed image is worse in this case
if it contains compressed chars.
Okay, we sometimes need to
profile our application to ensure that it's
reliable or to fix some problems.
Well there is a great tool in Java world called
Async profiler which can be used and
it combines Java stacks, the native stacks
up to some kernel calls and
we might try to use it. Okay, let's install
some dependencies, run our application,
find the process id connect well
we see that it does something inside JVM.
What's the problem? We can take the Berkeley
and to see detailed description for what
happens in this case C two server
compiler and its internal
parts are visible in the profile.
The key here is having debug symbols.
This is not a debug build of JDK,
but debug symbols can be just included with release build
into release build and they help profilers
like Async profiler to show morphine
great information about what application and runtime
do in some workloads.
So I mentioned port of
OpenJDK to muscle and opine Linux.
It was integrated on
the timeline of JDK 16 and these
days we made JDK 17.
But JDk eleven and JDK eight
are still actively maintained long term supported releases.
So few vendors provide
ports of alpine muscle support
to that older
releases. So such builds are also
available. And don't forget about
alternatives architectures isas
like arm 64. They are also supported
by this change.
Okay, we discussed a couple of examples,
but can it be simpler?
What if we just take Alpine Linux and install OpenJdk
there and we'll get that sweet
small container with all standard components
and we don't need to think of different vendors,
we just select base OS image and use OpenJDK,
include it there. Okay, we can try.
Let's add OpenJDk eight and
try to use standard diagnostic tool called jcommand.
Jit allows scripting
and provides a very nice text
interface. So this
is a really necessary diagnostic
tool. It just can start.
There is no ls for this
command. Okay.
Anyway it's there and we can find Jit and
use fullpath to start it. And let's try to execute
a very simple basic command.
Let's find out the uptime of running JVM instead
we'll get a stack trace. That's really
weird. Let's instead take
some tested distribution prepared
as a ready made container. Well,
no problem. We have that jcommand tool
and we get the uptime of running JVM instantly.
Sometimes we have
issues with public services,
different ones, and sometimes they just change the
terms of service like it happened with Docker hub.
For that cases it's good to have
some proxying functionality to bypass the limits
or to have a private registry that
you can connect to and where
you can ask for some
slas. And in the case of Bellsoft we
provide such a thing. We also put some
custom builds and container images
for customers there. For example
if you have support contract and you have
refined or you find a bug in JDK,
we can fix it, run all the testing to
ensure that the build that affix corresponds
to a Java specification and then it can be
released in a private repository. It is one of the channels
to deliver such useful
builds for customers. Then the fix is integrated into
OpenJDK publicly,
but there
is no need to wait for having it in production.
So you can also try early access
builds there. At least they used to be
provided in this repository. Also it
is very essential to connect to such a registry.
You just need some credentials and then you work with
it. Just like this public Toky
registry. You see
some experimental things that they put there are
related to container sizes and
they also provide experimental optimizations for
latency, for footprint,
for throughput in
such builds like interesting
bug ports. We've dont in JDK eleven
not for a long time ago. They include footprint
savings. Here we take some typical
spring boot service pet clinic
sample application and we can just launch it
put now load and measure how
much memory does it require.
And if we apply slightly
updated version of JDK we have
big settings in footprint for free.
So we immediately made our production more reliable.
The JDK is tested in
the same way as the released
version. So this preview early access is
just for some extra features,
but it goes through the same pipeline of
testing, so still
secure and we get that savings.
So when we select the container and
the way we build it, there are a few
interesting big roads like
we may decide to take native image road that
has its known limitations.
Like for example we won't be able to use standard diagnostic tools
from JDK and we will have
the penalty. Then we update our
running images frequently. So in
case then we use classical JVM and we have
very small iceberg tip,
we can update only that thin layer and
it will be extremely effective. In case
of native image we'll have to update everything,
almost everything. But at
the same time it is better for
new nodes because we
don't need to transfer the entire big Asperg, we'll transfer smaller one
the native image and we'll see the comparison what
size difference. Then we use it. So for
classical JVM containers we also have options.
Like I mentioned LPN Linux you
may have something against MuSL or Busybox or
APK package manager. It may be just
not familiar environment. Okay then you can take
something more well known like
yam package manager or apt
or you need glipsy which
is also available in opine case and in other cases
too. So what
about native images and the ways to create
them? Native images are about static compilation
and static compilation was added to JDK
to OpenJDK some time ago and
now it's been removed in
the form that it has been implemented earlier.
But there is still can interface to plug
in a special kind of dynamic
compiler or compiler that helps to produce native
images. The interface is called JVMCi
that's an interface for Java
based compilers. And there is a project
called GraalVM where Graal compiler
is being developed and it's not stopped.
So these changes, they just mean that
source code of Graal compiler is
not anymore dropped from GraalVM repository
into OpenJDK repository and no
developer resources are spent for doing
that. But instead you
can use GraalVM based technologies
and we will see how to do
that.
If I talk about GraalVM builds from
Oracle, there are two big options.
One option is Graal VM Community
edition builds which are provided the
basic free license. And there is
the GraalVM Enterprise Edition which
is provided using Oracle OTN
and same licensing policies
like Java C.
We decided to give
the community something that
will cover the task of native
image generation,
particularly for containers.
This is a subset of what GraalVM world
gives you because there is an option
to have a dynamic Grail compiler for mixing languages
and we focused on native images
and we think that
support for few extra
platforms is important, like Alpine muscle
here, arm 64 languages
support for native images. It's still interesting and
we still include that into
Liberca native image jit.
And also in the heart of this tool
of this kit there
is Liberica, the LTS version of
JDK.
To support Alpine we had to support both
GraalVM native image tool as
a compiler to work in such environment,
and also support for generated
native images to work again in the alpine environment.
There are multiple aspects of it,
but in the end all
this has been implemented and you can
download the binaries just like you
can download the JDK binaries.
And well, how to use that,
there are a few good ways.
Let's again look at the example.
We can go and create sample microservices even
smaller.
Its fat jar will be
as small as 17 megabytes. Fin jar again
will be much much smaller, kind of like 32.
We can switch to native image
generation. What are good roots
here. And what will be the difference
between classical JVMCi container produced from such
a services, such a service and native
image containers? Well first of all
you can go to spring initializer and then you create
such a microservice. You can now check
spring native support and you will have prepared
configuration for maven or gradle that
you can just use out of the box. But what's inside?
Well of course you will
need special plugin
for your
for Springboot and you
will need that plugin to generate
some things ahead of time that's not the static compilation
into machine code itself. This is
generation of some Java code which is
necessary to get rid of some dynamic behavior
which is too complex for a static
compiler that will produce the machine code.
And also you will need spring native.
You will get spring native plugin of some
version. Currently the
support of newer
version was announced and
you will need to include GraalVM
plugin in some cases.
In basic case JIT is used
under the hood by spring native,
but you may want to take more control
and we'll look at the examples.
So here it is activated only
for a special profile.
This special case we invoke
this profile and we say okay,
let's create a native image and the
specific version of
native image tool will be used in this case if
you don't want this complexity,
but we in this case agree
to get silent
upgrades of the tooling,
or at least we get the version of
native image tool bound to the
version of spring native tooling.
We can invoke again same standard out
of the box command line provided by spring
plugins,
but in the native image mode. And in
this case we'll get a container built by Liberica
native imagekit core of some version.
Or again we may want more
control and we may want the most effective
alpine Linux container for this environment.
They can manually recreate
the build
all necessary dependencies and
then we invoke standard building
commands,
sorry, standard native
image JIT commands to install native
image support. If it's not Liberica native image
JIT core, which is basically targeted
only to creation
of native images from Java programs and
here we can compile
the service itself with
a simple comment and finally we
get the resulting native image.
In all that cases it will be smaller than 60 megabytes,
which is really cool because we
have for example in the case of alpine Linux only
5.6 megabyte base image. Then we have
that dependencies for running the native
images. It is extra seven megabytes
and then we have that 57
megabytes on top of it and that
resulting container will start
in one 20th of a second,
probably faster on server hardware.
And what is also sweet here,
if we get back to the original footprint comparison,
we can take pet clinic,
start it, measure the same plot and
we have even more memory
saved. For the case of using native image,
it is really easy to try.
Just take spring native
and use standard build packs or
go and download the Berry Connective
image kit and try your
production services and thank
you for your attention.