Skip to content

Commit

Permalink
Merge pull request graalvm#248 from graalvm/sms-revisions
Browse files Browse the repository at this point in the history
Upgraded Tiny Containers
  • Loading branch information
shaunsmith committed Jan 9, 2024
2 parents 0e6a442 + c2fe7b0 commit 95076d9
Show file tree
Hide file tree
Showing 18 changed files with 99 additions and 53 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/tiny-java-containers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ jobs:
#
./setup-musl.sh
#
# Download upx
#
./setup-upx.sh
#
# Hello World
#
cd helloworld
Expand Down
78 changes: 50 additions & 28 deletions tiny-java-containers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
This example shows how a simple Java application and a simple web
server can be compiled to produce very small Docker container images.

The smallest container images contains just an executable. But, since there's
The smallest container images contains just an executable. But since there's
nothing in the container image except the executable, including no `libc` or other
shared libraries, an executable has to be fully statically linked with all
needed libraries and resources.
Expand All @@ -20,21 +20,37 @@ App](images/youtube.png)](https://youtu.be/6wYrAtngIVo)

## Prerequisites

* x86 Linux (but the few binary dependencies could easily be changed for aarch64)
* Docker installed and running. It should work fine with [podman](https://podman.io/) but it has not been tested.
* [GraalVM for JDK 21](https://www.graalvm.org/downloads/)

> We recommend Oracle GraalVM for the best experience. It is licensed under the [GraalVM Free Terms and Conditions (GFTC)](https://www.oracle.com/downloads/licenses/graal-free-license.html) license, which permits use by any user including commercial and production use.
GraalVM Community Edition JDK 21 works too, but Native Image generated executables sizes will differ.
GraalVM Community Edition for JDK 21 works too, but Native Image generated executables sizes will differ.

> These instructions have only been tested on Linux x64.
## Setup

You need the following zlib packages installed:
* zlib.x86_64
* zlib-devel.x86_64
* zlib-static.x86_64

On Oracle Linux, you can install with:
```sh
sudo yum install -y zlib.x86_64
sudo yum install -y zlib-devel.x86_64
sudo yum install -y zlib-static.x86_64
```

Clone this Git repo and in your Linux shell type the following to download and
configure the `musl` toolchain.

![](images/keyboard.jpg) `./setup-musl.sh`

Download [upx](https://upx.github.io/):

![](images/keyboard.jpg) `./setup-upx.sh`

## Hello World

Expand Down Expand Up @@ -62,7 +78,8 @@ equivalent. They just print "Hello World". But there are a few points worth
noting:

1. The executable generated by GraalVM Native Image using the
`--static --libc=musl` options is a fully self-contained executable which can be confirmed by examining it with `ldd`:
`--static --libc=musl` options is a fully self-contained executable which can be
confirmed by examining it with `ldd`:

![](images/keyboard.jpg) `ldd hello`

Expand All @@ -80,22 +97,26 @@ noting:
executable, you can be confident it is also statically linked.

2. Both executables are the result of compiling a Java bytecode application into
native machine code. The uncompressed executable is only 5.2MB! There's no
native machine code. The uncompressed executable is only ~6.3MB! There's no
JVM, no JARs, no JIT compiler and none of the overhead it imposes. Both
start extremely fast as there is minimal startup cost.

3. The `upx` compressed executable is about 60% smaller, 1.5MB vs. 5.2MB! With
`upx`` the application self-extracts quickly but does incur a cost of about
100ms for decompression. See this blog for a deep dive on [GraalVM Native Image and UPX](https://medium.com/graalvm/compressed-graalvm-native-images-4d233766a214).
3. The `upx` compressed executable is over 70% smaller, 1.7MB vs. 6.3MB! With
`upx` the application self-extracts quickly but does incur a cost of about
100ms for decompression. See this blog for a deep dive on [GraalVM Native
Image and
UPX](https://medium.com/graalvm/compressed-graalvm-native-images-4d233766a214).

### Container Images

The size of the `scratch`-based container image is slightly more than the `hello.upx`
executable.

![](images/keyboard.jpg) `docker images hello`

```shell
REPOSITORY TAG IMAGE ID CREATED SIZE
hello upx 935e5e3549e6 1 second ago 1.51MB
REPOSITORY TAG IMAGE ID CREATED SIZE
hello upx 4d122bd39a8a About a minute ago 1.78 MB
```

This is a tiny container image and yet it contains a fully functional and
Expand Down Expand Up @@ -167,29 +188,29 @@ When complete you can see the sizes of the various versions:

```shell
REPOSITORY TAG IMAGE ID CREATED SIZE
jwebserver distroless-java-base.jlink fae0bb62eca7 6 minutes ago 74.9MB
jwebserver scratch.static-upx 676069a2a359 6 minutes ago 5.43MB
jwebserver alpine.static 14e748264a99 6 minutes ago 25MB
jwebserver distroless-static.static 5591e1a2658a 6 minutes ago 21.8MB
jwebserver scratch.static ef1ad68037ec 7 minutes ago 19.4MB
jwebserver distroless-base.mostly cc8612887001 7 minutes ago 39.7MB
jwebserver distroless-java-base.dynamic d2f802cf3def 8 minutes ago 58.7MB
jwebserver distroless-java-base.jlink 414d84f8b7c7 22 minutes ago 132 MB
jwebserver scratch.static-upx 47aabdd14c04 22 minutes ago 4.71 MB
jwebserver alpine.static 783ab3a60248 22 minutes ago 23.4 MB
jwebserver distroless-static.static c894f14d4068 22 minutes ago 18.7 MB
jwebserver scratch.static 034cfbdf3577 22 minutes ago 15.7 MB
jwebserver distroless-base.mostly e99811e574d3 22 minutes ago 37.6 MB
jwebserver distroless-java-base.dynamic 72a210e3c705 23 minutes ago 50.6 MB
```

Sorting by size, it's clear that the fully statically linked GraalVM Native
Image generated executable that's compressed and packaged on `scratch` is the
smallest at just 5.43MB, only 7% of the size of the `jlink` version running on
the JVM.
Image generated executable that's compressed and packaged on `scratch`
(`scratch.static-upx`) is the smallest at just 4.71MB, less than 4% of the size
of the `jlink` version (`distroless-java-base.jlink`) running on the JVM.

| Base Image | App Version | Size (MB) |
| -------------------- | ---------------------------------- | --------- |
| Distroless Java Base | jlink | 74.90 |
| Distroless Java Base | native dynamic linked | 58.70 |
| Distroless Base | native *mostly* static linked | 39.70 |
| Alpine | native *fully* static | 25.00 |
| Distroless Static | native *fully* static | 21.80 |
| Scratch | native *fully* static | 19.40 |
| Scratch | *compressed* native *fully* static | 5.43 |
| Distroless Java Base | jlink | 132.00 |
| Distroless Java Base | native *dynamic* linked | 50.60 |
| Distroless Base | native *mostly* static linked | 37.60 |
| Alpine | native *fully* static linked | 23.40 |
| Distroless Static | native *fully* static linked | 18.70 |
| Scratch | native *fully* static linked | 15.70 |
| Scratch | *compressed* native *fully* static | 4.71 |

Running a container image is straight forward, just remember to map the ports, e.g.:

Expand All @@ -204,10 +225,11 @@ the index.html file.

## Wrapping Up

Fully functional, albeit minimal, Java "microservice" was compiled
A fully functional, albeit minimal, Java "microservice" was compiled
into a native Linux executable and packaged into Distroless, Alpine, and
`scratch`-based container images thanks to GraalVM Native Image's support for
various linking options including fully static linking with the `musl` libc.

To learn more about linking options check out [Static and Mostly Static
Images](https://www.graalvm.org/latest/reference-manual/native-image/guides/build-static-executables/) in the GraalVM docs.
Images](https://www.graalvm.org/latest/reference-manual/native-image/guides/build-static-executables/)
in the GraalVM docs.
8 changes: 5 additions & 3 deletions tiny-java-containers/clean.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#!/bin/sh

set -x
set +e

rm -rf x86_64-linux-musl-native zlib-*
./helloworld/clean.sh
./jwebserver/clean.sh
cd helloworld
./clean.sh || true
cd ../jwebserver
./clean.sh || true
10 changes: 5 additions & 5 deletions tiny-java-containers/helloworld/build.sh
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
#!/bin/sh

set -e

TOOLCHAIN_DIR=`pwd`/../x86_64-linux-musl-native
CC=${TOOLCHAIN_DIR}/bin/gcc
PATH=${TOOLCHAIN_DIR}/bin:${PATH}

set -x

# Compile Java source file
javac Hello.java

# Compile Java bytecodes into a fully statically linked executable
native-image --static --libc=musl -o hello Hello
rm *.txt
native-image -Ob --static --libc=musl -o hello Hello
rm -rf *.txt

# Create a compressed version of the executable
upx --lzma --best hello -o hello.upx
../upx --lzma --best hello -o hello.upx

# Package the compressed executable in a simple scratch container image
docker build . -t hello:upx
10 changes: 6 additions & 4 deletions tiny-java-containers/helloworld/clean.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/bin/sh

rm hello hello.upx
rm *.txt
rm *.class
docker images -q hello | awk '{print($1)}' | xargs docker rmi
set +e

rm -rf hello hello.upx
rm -rf *.txt
rm -rf *.class
docker images -q hello | awk '{print($1)}' | xargs docker rmi || true
2 changes: 1 addition & 1 deletion tiny-java-containers/jwebserver/Dockerfile.alpine.static
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM alpine
FROM alpine:3
COPY jwebserver.static /
COPY index.html /web/index.html
EXPOSE 8000
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM gcr.io/distroless/base-debian11
FROM gcr.io/distroless/base-debian12
COPY jwebserver.mostly /
COPY index.html /web/index.html
EXPOSE 8000
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM gcr.io/distroless/java-base-debian11
FROM gcr.io/distroless/java-base-debian12
COPY jwebserver.dynamic /
COPY index.html /web/index.html
EXPOSE 8000
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM gcr.io/distroless/java-base-debian11
FROM gcr.io/distroless/java-base-debian12
COPY jwebserver-jlink /usr/lib/java
COPY index.html /web/index.html
EXPOSE 8000
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM gcr.io/distroless/static-debian11
FROM gcr.io/distroless/static-debian12
COPY jwebserver.static /
COPY index.html /web/index.html
EXPOSE 8000
Expand Down
4 changes: 1 addition & 3 deletions tiny-java-containers/jwebserver/build-all.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
#!/bin/sh

set -x
#!/bin/sh

./build-dynamic.sh
./build-mostly.sh
Expand Down
2 changes: 1 addition & 1 deletion tiny-java-containers/jwebserver/build-dynamic.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/sh

# compile with fully dynamically linked shared libraries
native-image -m jdk.httpserver -o jwebserver.dynamic
native-image -Ob -m jdk.httpserver -o jwebserver.dynamic

# Distroless Java Base-provides glibc and other libraries needed by the JDK
docker build . -f Dockerfile.distroless-java-base.dynamic -t jwebserver:distroless-java-base.dynamic
Expand Down
2 changes: 1 addition & 1 deletion tiny-java-containers/jwebserver/build-jlink.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jlink \
--add-modules jdk.httpserver \
--verbose \
--strip-debug \
--compress 2 \
--compress zip-9 \
--no-header-files \
--no-man-pages \
--strip-java-debug-attributes \
Expand Down
2 changes: 1 addition & 1 deletion tiny-java-containers/jwebserver/build-mostly.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/sh

native-image -H:+StaticExecutableWithDynamicLibC -m jdk.httpserver -o jwebserver.mostly
native-image -Ob -H:+UnlockExperimentalVMOptions -H:+StaticExecutableWithDynamicLibC -m jdk.httpserver -o jwebserver.mostly

# Distroless Base (provides glibc)
docker build . -f Dockerfile.distroless-base.mostly -t jwebserver:distroless-base.mostly
4 changes: 2 additions & 2 deletions tiny-java-containers/jwebserver/build-static.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ TOOLCHAIN_DIR=`pwd`/../x86_64-linux-musl-native
CC=${TOOLCHAIN_DIR}/bin/gcc
PATH=${TOOLCHAIN_DIR}/bin:${PATH}

native-image --static --libc=musl -m jdk.httpserver -o jwebserver.static
native-image -Ob --static --libc=musl -m jdk.httpserver -o jwebserver.static

# Scratch-nothing
docker build . -f Dockerfile.scratch.static -t jwebserver:scratch.static
Expand All @@ -17,7 +17,7 @@ docker build . -f Dockerfile.alpine.static -t jwebserver:alpine.static

# Compress with UPX
rm -f jwebserver.static-upx
upx --lzma --best -o jwebserver.static-upx jwebserver.static
../upx --lzma --best -o jwebserver.static-upx jwebserver.static

# Scratch--fully static and compressed
docker build . -f Dockerfile.scratch.static-upx -t jwebserver:scratch.static-upx
3 changes: 3 additions & 0 deletions tiny-java-containers/jwebserver/clean.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#!/bin/sh

set +e

rm -rf jwebserver-jlink/
rm jwebserver.dynamic jwebserver.mostly jwebserver.static jwebserver.static-upx
rm -rf svm*.md
docker images jwebserver -q | grep -v TAG | awk '{print($1)}' | xargs docker rmi
2 changes: 2 additions & 0 deletions tiny-java-containers/setup-musl.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/bin/sh

set -e

ZLIB_VERSION=1.2.13
TOOLCHAIN_DIR=`pwd`/x86_64-linux-musl-native

Expand Down
13 changes: 13 additions & 0 deletions tiny-java-containers/setup-upx.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/sh

set -e

UPX_VERSION=4.2.2
UPX_ARCHIVE=upx-${UPX_VERSION}-amd64_linux.tar.xz

wget -q https://github.com/upx/upx/releases/download/v${UPX_VERSION}/${UPX_ARCHIVE}
tar -xJf ${UPX_ARCHIVE}
rm -rf ${UPX_ARCHIVE}
mv upx-${UPX_VERSION}-amd64_linux/upx .
rm -rf upx-${UPX_VERSION}-amd64_linux

0 comments on commit 95076d9

Please sign in to comment.