Skip to content

Commit

Permalink
Minor updates
Browse files Browse the repository at this point in the history
  • Loading branch information
sachin-pikle committed Oct 8, 2024
1 parent a232fe4 commit 9368ad0
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 46 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ GraalVM as a Java runtime.
<a href="https://github.com/graalvm/workshops/actions/workflows/github-actions-spring-boot-webserver.yml">
<img alt="native-image-quickstart" src="https://github.com/graalvm/workshops/actions/workflows/github-actions-spring-boot-webserver.yml/badge.svg" /></a>

> Workshop is for developers looking to understand better how to build **size-optimized** Java applications using [GraalVM Native Image](https://www.graalvm.org/reference-manual/native-image/). In the workshop you will:
> Compile a Spring Boot web server, hosting GraalVM documentation pages, ahead of time into a native executable and optimize it for file size
> See how to use the [GraalVM Native Image Maven Plugin](https://graalvm.github.io/native-build-tools/latest/maven-plugin.html)
> Create native executables and run them inside different base containers, including Paketo Buildpacks
> Shrink a container image size by taking advantage of different Native Image containerisation and linking options
> Workshop for developers looking to understand better how to build **size-optimized** Java applications using [GraalVM Native Image](https://www.graalvm.org/reference-manual/native-image/). In the workshop you will:
> - Compile a Spring Boot web server, hosting GraalVM documentation pages, ahead of time into a native executable and optimize it for file size
> - See how to use the [GraalVM Native Image Maven Plugin](https://graalvm.github.io/native-build-tools/latest/maven-plugin.html)
> - Create native executables and run them inside different base containers, including Paketo Buildpacks
> - Shrink a container image size by taking advantage of different Native Image containerisation and linking options
> Run on an Oracle-owned tenancy (Luna Labs): [https://luna.oracle.com/lab/b644a03a-8238-4293-a586-55e5b0ec6186](https://luna.oracle.com/lab/b644a03a-8238-4293-a586-55e5b0ec6186)
> You can also run this workshop for free on Oracle Cloud Infrastructure (Luna Labs): [https://luna.oracle.com/lab/b644a03a-8238-4293-a586-55e5b0ec6186](https://luna.oracle.com/lab/b644a03a-8238-4293-a586-55e5b0ec6186)
### ![Lab Flask](./images/lab-flask.png) [Understanding Reflection with GraalVM Native Image](native-image/reflection/)
<a href="https://github.com/graalvm/workshops/actions/workflows/github-actions-native-image-reflection.yml">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.5/apache-maven-3.8.5-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
120 changes: 80 additions & 40 deletions native-image/spring-boot-webserver/README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
# Multi-Cloud Apps with GraalVM - Up and Running
# Multicloud Apps with GraalVM - Up and Running

This workshop is for developers looking to understand better how to **build size-optimized cloud native Java applications** using [GraalVM Native Image](https://www.graalvm.org/jdk21/reference-manual/native-image/). You are going to discover ways to minimize application footprint by taking advantage of different Native Image linking options and packaging into various base containers.
This workshop is for developers looking to understand how to **build size-optimized cloud native Java applications** using [GraalVM Native Image](https://www.graalvm.org/jdk21/reference-manual/native-image/).

In this workshop, you will discover ways to minimize application footprint using different Native Image linking options and packaging into various base containers. You will run a Spring Boot web server application hosting the GraalVM documentation website.

For the demo part, you will run a Spring Boot web server application, hosting the GraalVM website.
Spring Boot 3 has integrated support for GraalVM Native Image, making it easier to set up and configure a project.
Compiling a Spring Boot application ahead of time can significantly boost the performance and reduce its footprint.
Compiling a Spring Boot application ahead of time can significantly boost performance and reduce the footprint.

### Workshop Objectives

In this workshop you will:

- Learn how to compile a Spring Boot application ahead-of-time into a native executable and optimize it for file size.
- Learn how to compile a Spring Boot 3 application ahead of time into a native executable and optimize it for file size.
- See how to use the [GraalVM Native Image Maven Plugin](https://graalvm.github.io/native-build-tools/latest/maven-plugin.html).
- Create native executables and run them inside different Docker containers.
- Shrink a container image size by taking advantage of different Native Image containerisation and linking options.
- Shrink the container image size by using different Native Image containerisation and linking options.
- Compare the deployed container images sizes.
- See how to use GitHub Actions to automate the build of native executables as part of a CI/CD pipeline.

Expand All @@ -23,18 +24,21 @@ In this workshop you will:
* `musl` toolchain
* Container runtime such as [Rancher Desktop](https://docs.rancherdesktop.io/getting-started/installation/) or [Docker](https://www.docker.com/gettingstarted/) installed and running
* [GraalVM for JDK 23](https://www.graalvm.org/downloads/) or later. We recommend using [SDKMAN!](https://sdkman.io/). (For other download options, see [GraalVM Downloads](https://www.graalvm.org/downloads/).)

```bash
sdk install java 23-graal
```

## Setup

Clone this repository with Git and enter the application directory:

```bash
git clone https://github.com/olyagpl/spring-boot-webserver.git
git clone https://github.com/graalvm/workshops.git
```

```bash
cd spring-boot-webserver
cd workshops/native-image/spring-boot-webserver
```

## **STEP 1**: Compile and Run the Application from a JAR File Inside a Container
Expand Down Expand Up @@ -84,19 +88,19 @@ See how much reduction in size you can gain.

Jlink, or `jlink`, is a tool that generates a custom Java runtime image that contains only the platform modules that are required for your application. This is one of the approaches to create cloud native applications introduced in Java 11.

The script _build-jlink.sh_ that runs `docker build` using the _Dockerfile.distroless-java-base.jlink_.
The Dockerfile contains a multistage build: first it generates a Jlink custom runtime on a full JDK; then copies the runtime image folder along with static website pages into a Java base container image, and sets the entrypoint.
The script _build-jlink.sh_ runs `docker build` using the _Dockerfile.distroless-java-base.jlink_.
The Dockerfile contains a multistage build: generates a Jlink custom runtime on a full JDK, copies the runtime image folder along with static website pages into a Java base container image, and sets the entrypoint.

The application does not have to be modular, but you need to figure out which modules the application depends on to be able to `jlink` it.
In the builder stage, running on a full JDK, after compiling the project, Docker generates a file _cp.txt_ containing the classpath with all the dependencies:
```
RUN ./mvnw dependency:build-classpath -Dmdep.outputFile=cp.txt
```
Then, Docker runs the `jdeps` command with the classpath to check required modules for this Spring Boot application:
```bash
RUN jdeps --ignore-missing-deps -q --recursive --multi-release 23 --print-module-deps --class-path $(cat cp.txt) target/webserver-0.0.1-SNAPSHOT.jar
```
Finally, Docker runs `jlink` to create a custom runtime in the specified output directory _jlink-jre_.
The application does not have to be modular, but you need to figure out which modules the application depends on to be able to `jlink` it. Shown below are three commands from the Dockerfile:
- In the build stage, running on a full JDK, after compiling the project, generates a file _cp.txt_ containing the classpath with all the dependencies:
```
RUN ./mvnw dependency:build-classpath -Dmdep.outputFile=cp.txt
```
- Then, runs the `jdeps` command with the classpath to check required modules for this Spring Boot application:
```bash
RUN jdeps --ignore-missing-deps -q --recursive --multi-release 23 --print-module-deps --class-path $(cat cp.txt) target/webserver-0.0.1-SNAPSHOT.jar
```
- Finally, runs `jlink` to create a custom runtime in the specified output directory _jlink-jre_.
The `ENTRYPOINT` for the application would be `java` from the custom runtime.

### Action
Expand Down Expand Up @@ -127,17 +131,17 @@ The `ENTRYPOINT` for the application would be `java` from the custom runtime.
webserver distroless-java-base.jlink 687f7683ad58 16 minutes ago 192MB
webserver debian-slim.jar 5c69f06a3972 2 hours ago 238MB
```
Jlink shrinked the container by **46MB**.
Jlink shrank the container by **46MB**.

## **STEP 3**: Build and Run a Native Image Inside a Container Using Paketo Buildpacks

In this step, you will compile this Spring Boot application ahead of time with GraalVM Native Image and run it using Paketo Buildpacks container images.

### Explanation

Spring Boot supports building a native image in a container using the [Paketo Buildpack for Oracle](https://github.com/paketo-buildpacks/oracle) which provides GraalVM Native Image.
Spring Boot supports building a native image in a container using the [Paketo Buildpack for Oracle](https://github.com/paketo-buildpacks/oracle) which provides Oracle GraalVM Native Image.

The mechanism is that the Paketo builder pulls the [Jammy Tiny Stack image](https://github.com/paketo-buildpacks/builder-jammy-tiny) (Ubuntu distroless-like image) which contains no buildpacks.
The Paketo builder pulls the [Jammy Tiny Stack image](https://github.com/paketo-buildpacks/builder-jammy-tiny) (Ubuntu distroless-like image) which contains no buildpacks.
Then you point the **builder** image to the **creator** image.
For this workshop, you point to the [Paketo Buildpack for Oracle](https://github.com/paketo-buildpacks/oracle) explicitly requesting the Native Image tool.

Expand Down Expand Up @@ -245,9 +249,9 @@ Learn more in ["Distroless" Container Images](https://github.com/GoogleContainer
The new container image size **164MB** it almost matches the size of the container built with Paketo Buildpacks.
It is expected because it is the same native executable packaged into a different base container.
6. Check what is the size of the native executable itself:
6. Check the size of the native executable:
```bash
ls -lh target/webserver
ls -lh target/webserver*
```
The expected size is **125M**.
Note that the static resources are "baked" into this native executable and added 44M to its size.
Expand Down Expand Up @@ -322,18 +326,22 @@ The script _build-dynamic-image.sh_, available in this repository for your conve
The size of `distroless-java-base.dynamic-optimized` container is cut down from **164MB** to **130MB**.
This is because the native executable reduced in size.
6. Check what is the size of the native executable now:
6. Check the size of the native executable:
```bash
ls -lh target/webserver
```
The size decreased from **125M** to **92M** by applying the file size optimization.
ls -lh target/webserver*
```
The expected output is:
```bash
-rwxr-xr-x. 1 opc opc 125M Aug 29 10:51 target/webserver
-rw-r--r--. 1 opc opc 45M Aug 29 10:39 target/webserver-0.0.1-SNAPSHOT.jar
-rw-r--r--. 1 opc opc 25M Aug 29 10:39 target/webserver-0.0.1-SNAPSHOT.jar.original
...
-rwxr-xr-x. 1 opc opc 92M Aug 29 11:06 target/webserver.dynamic-optimized
```
The size decreased from **125M** (`webserver`) to **92M** (`webserver.dynamic-optimized`) by applying the file size optimization.
## **STEP 6**: Build a Size-Optimized Mostly Static Native Image Locally and Run Inside a Container
In this step, you will build a **mostly static** native image, with the file size optimization on, on a host machine, then package it into a container image that provides `glibc`, and run.
Expand Down Expand Up @@ -401,9 +409,24 @@ A separate Maven profile exists for this step:
The size of the new _distroless-base.mostly-static_ container is **117MB**.
The reduction in size is related to the fact that a smaller base image was pulled: **gcr.io/distroless/base-debian12**.
[Distroless images](https://github.com/GoogleContainerTools/distroless) are very small, and the one used is only **48.3 MB**.
That's about 50% of the size of **java-base-debian12**(124 MB) used before, and 3 times less than **java21-debian12** (192 MB) containing a full JDK.
That's about 40% of the size of **java-base-debian12**(124 MB) used before, and about 25% of the size of **java21-debian12** (192 MB) containing a full JDK.

The size of the mostly static native image has not changed, and is **92MB**.
6. Check the size of the native executable:

```bash
ls -lh target/webserver*
```

The expected output is:

```bash
-rwxrwxr-x. 1 opc opc 124M Oct 8 06:13 target/webserver
...
-rwxrwxr-x. 1 opc opc 92M Oct 8 06:19 target/webserver.dynamic-optimized
-rwxrwxr-x. 1 opc opc 93M Oct 8 06:29 target/webserver.mostly-static
```

The size of the mostly static native image (`webserver.mostly-static`) has not changed much, and is around **93MB**.

## **STEP 7**: Build a Size-Optimized Fully Static Native Image Locally and Run Inside a Container

Expand Down Expand Up @@ -485,18 +508,36 @@ A separate Maven profile exists for this step:
webserver debian-slim.jar 5c69f06a3972 3 hours ago 238MB
webserver 0.0.1-SNAPSHOT 0660806da4a2 44 years ago 163MB
```
The container size shrinked to **96.4MB**! A _scratch_ container weights only **14.5MB**.
The container size has reduced to **96.4MB**! A _scratch_ container is only **14.5MB**.

8. Check the size of the native executable:

```bash
ls -lh target/webserver*
```

The expected output is:

```bash
-rwxrwxr-x. 1 opc opc 124M Oct 8 06:13 target/webserver
...
-rwxrwxr-x. 1 opc opc 92M Oct 8 06:19 target/webserver.dynamic-optimized
-rwxrwxr-x. 1 opc opc 93M Oct 8 06:29 target/webserver.mostly-static
-rwxrwxr-x. 1 opc opc 93M Oct 8 06:54 target/webserver.static
```

The size of the mostly static native image (`webserver.static`) has not changed, and is around **93MB**.

## **STEP 8**: Compress a Static Native Image with UPX and Run Inside a Container

_Not convincing? What can you do next to reduce the size even more?_
_What can you do next to reduce the size even more?_

You can compress your fully static native image with UPX, then package into the same _scratch_ container, and run.

### Explanation

[UPX](https://upx.github.io/) - an advanced executable file compressor.
It can significantly reduce the executable size, but note, that UPX loads the executable into the memory, unpackages it, and then recompresses.
It can significantly reduce the executable size, but note, that UPX loads the executable into the memory, unpacks it, and then recompresses it.

1. Download and install UPX:
```bash
Expand Down Expand Up @@ -526,14 +567,13 @@ It can significantly reduce the executable size, but note, that UPX loads the ex
The expected output is:
```
-rwxr-xr-x. 1 opc opc 125M Aug 29 10:51 target/webserver
-rw-r--r--. 1 opc opc 45M Aug 29 11:30 target/webserver-0.0.1-SNAPSHOT.jar
...
-rwxr-xr-x. 1 opc opc 92M Aug 29 11:06 target/webserver.dynamic-optimized
-rwxr-xr-x. 1 opc opc 92M Aug 29 11:14 target/webserver.mostly-static
-rwxr-xr-x. 1 opc opc 92M Aug 29 11:32 target/webserver.static
-rwxr-xr-x. 1 opc opc 35M Aug 29 11:32 target/webserver.static-upx
```
The **webserver.static-upx** is only **35MB**!
The `upx` compressed executable by **57MB** from the "uncompressed" one.
The `upx`-compressed executable (`webserver.static-upx`) is only **35MB**, which is **57MB** less than the "uncompressed" one.

7. Lastly, check the size of all container images:
```bash
Expand All @@ -552,7 +592,7 @@ It can significantly reduce the executable size, but note, that UPX loads the ex
webserver 0.0.1-SNAPSHOT 0660806da4a2 44 years ago 163MB
```
The container size reduced dramatically to just **36.2MB**.
The application and container image's size were now shrinked to the minimum.
The application and container image's size have been shrunk to the minimum.
## **STEP 9**: Clean up (Optional)
Expand Down

0 comments on commit 9368ad0

Please sign in to comment.