Warning
This article was automatically translated by OpenAI (gpt-4o). It may be edited eventually, but please be aware that it may contain incorrect information at this time.
Spring Framework 6.1 / Spring Boot 3.2 has introduced initial support for CRaC (Coordinated Restore at Checkpoint).
This is a note on creating a Docker image to try out CRaC.
CRaC is also mentioned in the presentation Virtual Threads! Checkpoint Restore! Introduction to Notable Features of Spring Framework 6.1 / Spring Boot 3.2 given at JJUG CCC 2023 Fall.
First, create a template project.
curl https://start.spring.io/starter.zip \
-s \
-d type=maven-project \
-d language=java \
-d bootVersion=3.2.0 \
-d baseDir=demo \
-d groupId=com.example \
-d artifactId=demo \
-d name=demo \
-d description=Demo \
-d packageName=com.example.demo \
-d packaging=jar \
-d javaVersion=21 \
-d dependencies=web,actuator \
-o demo.zip
unzip demo.zip
cd demo
git init
To add the CRaC API, add the following <dependency>
to pom.xml
. The version is managed by Spring Boot.
<dependency>
<groupId>org.crac</groupId>
<artifactId>crac</artifactId>
</dependency>
Add the following HelloController
.
cat <<EOF > ./src/main/java/com/example/demo/HelloController.java
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping(path = "/")
public String hello() {
return "Hello World!";
}
}
EOF
Although not directly related to CRaC, to check the Java version information at the /actuator/info
endpoint, add the following settings to application.properties
.
cat <<EOF > ./src/main/resources/application.properties
management.endpoints.web.exposure.include=health,info
management.info.java.enabled=true
management.info.os.enabled=true
EOF
The Dockerfile
is available on Gist. Since it may be updated, use the latest version. Refer to the following link for the implementation.
https://gist.github.com/making/35cfa52862e93793bad2b37b7c0e5135
wget https://gist.githubusercontent.com/making/35cfa52862e93793bad2b37b7c0e5135/raw/Dockerfile
wget https://gist.githubusercontent.com/making/35cfa52862e93793bad2b37b7c0e5135/raw/entrypoint.sh
The Dockerfile
is written to build from the source code rather than from a jar (so it can also be used with Tanzu Application Platform).
Therefore, even in an environment without JDK installed, you can build with the following command as long as Docker is available.
docker build -t demo .
Start the container with the following command. In this container, the app is started once, and then a Checkpoint is created. After the Checkpoint is created, the app will terminate. Then, the app is restored from the Checkpoint.
To use CRaC, you need to grant some permissions with --cap-add
. You can specify where to save the Checkpoint with the environment variable CHECKPOINT_RESTORE_FILES_DIR
.
On OrbStack, an error occurs when creating a Checkpoint, possibly because criu cannot be used.
docker run -p 8080:8080 \
--cap-add CHECKPOINT_RESTORE \
--cap-add NET_ADMIN \
--cap-add SYS_PTRACE \
--cap-add SYS_ADMIN \
-v /tmp/crac:/var/crac \
-e CHECKPOINT_RESTORE_FILES_DIR=/var/crac \
--rm \
demo
The following log will be output. The Checkpoint is created 10 seconds after the app starts. This time can be changed with the environment variable SLEEP_BEFORE_CHECKPOINT
in entrypoint.sh
.
Save checkpoint to /var/crac
Picked up JAVA_TOOL_OPTIONS: -XX:+ExitOnOutOfMemoryError
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.0)
2023-12-01T07:47:31.527Z INFO 10 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication v0.0.1-SNAPSHOT using Java 21.0.1 with PID 10 (/application/BOOT-INF/classes started by root in /application)
2023-12-01T07:47:31.531Z INFO 10 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to 1 default profile: "default"
2023-12-01T07:47:32.567Z INFO 10 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2023-12-01T07:47:32.576Z INFO 10 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-12-01T07:47:32.576Z INFO 10 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.16]
2023-12-01T07:47:32.608Z INFO 10 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-12-01T07:47:32.609Z INFO 10 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 992 ms
2023-12-01T07:47:33.111Z INFO 10 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 3 endpoint(s) beneath base path '/actuator'
2023-12-01T07:47:33.169Z INFO 10 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path ''
2023-12-01T07:47:33.181Z INFO 10 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 2.067 seconds (process running for 2.355)
Picked up JAVA_TOOL_OPTIONS: -XX:+ExitOnOutOfMemoryError
10:
2023-12-01T07:47:41.076Z INFO 10 --- [Attach Listener] jdk.crac : Starting checkpoint
CR: Checkpoint ...
/application/entrypoint.sh: line 18: 10 Killed java -XX:CRaCCheckpointTo=$CHECKPOINT_RESTORE_FILES_DIR org.springframework.boot.loader.launch.JarLauncher
2023-12-01T07:47:44.605Z INFO 10 --- [Attach Listener] o.s.c.support.DefaultLifecycleProcessor : Restarting Spring-managed lifecycle beans after JVM restore
2023-12-01T07:47:44.609Z INFO 10 --- [Attach Listener] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path ''
2023-12-01T07:47:44.610Z INFO 10 --- [Attach Listener] o.s.c.support.DefaultLifecycleProcessor : Spring-managed lifecycle restart completed (restored JVM running for 35 ms)
You can access the app normally.
$ curl localhost:8080
Hello World!
When you access /actuator/info
, you can check the Java information. The vendor.version
includes "CRaC-CA", indicating that it is a Java version compatible with CRaC.
$ curl -s localhost:8080/actuator/info | jq .
{
"java": {
"version": "21.0.1",
"vendor": {
"name": "Azul Systems, Inc.",
"version": "Zulu21.30+23-CRaC-CA"
},
"runtime": {
"name": "OpenJDK Runtime Environment",
"version": "21.0.1+12-LTS"
},
"jvm": {
"name": "OpenJDK 64-Bit Server VM",
"vendor": "Azul Systems, Inc.",
"version": "21.0.1+12-LTS"
}
},
"os": {
"name": "Linux",
"version": "5.15.0-89-generic",
"arch": "amd64"
}
}
When you check the directory specified by CHECKPOINT_RESTORE_FILES_DIR
, the following files are created.
$ ls -lh /tmp/crac/
total 184M
-rw-r--r-- 1 root root 2.2K Dec 1 04:46 core-10.img
-rw-r--r-- 1 root root 795 Dec 1 04:46 core-12.img
-rw-r--r-- 1 root root 767 Dec 1 04:46 core-13.img
...
-rw-r--r-- 1 root root 795 Dec 1 04:46 core-89.img
-rw-r--r-- 1 root root 790 Dec 1 04:46 core-90.img
-rw-r--r-- 1 root root 782 Dec 1 04:46 core-91.img
-rw------- 1 root root 380K Dec 1 04:46 dump4.log
-rw-r--r-- 1 root root 524 Dec 1 04:46 fdinfo-2.img
-rw-r--r-- 1 root root 6.3K Dec 1 04:46 files.img
-rw-r--r-- 1 root root 18 Dec 1 04:46 fs-10.img
-rw-r--r-- 1 root root 36 Dec 1 04:46 ids-10.img
-rw-r--r-- 1 root root 46 Dec 1 04:46 inventory.img
-rw-r--r-- 1 root root 12K Dec 1 04:46 mm-10.img
-rw-r--r-- 1 root root 7.1K Dec 1 04:46 pagemap-10.img
-rw-r--r-- 1 root root 183M Dec 1 04:46 pages-1.img
-rw-r--r-- 1 root root 98 Dec 1 04:46 pstree.img
-rw-r--r-- 1 root root 12 Dec 1 04:46 seccomp.img
-rw-r--r-- 1 root root 54 Dec 1 04:46 stats-dump
-rw-r--r-- 1 root root 34 Dec 1 04:46 timens-0.img
Stop the container with Ctrl+C.
Start the container again with the following command.
docker run -p 8080:8080 \
--cap-add CHECKPOINT_RESTORE \
--cap-add NET_ADMIN \
--cap-add SYS_PTRACE \
--cap-add SYS_ADMIN \
-v /tmp/crac:/var/crac \
-e CHECKPOINT_RESTORE_FILES_DIR=/var/crac \
--rm \
demo
The following log will be output. Since it was restored from the Checkpoint, it started in 30ms.
Restore checkpoint from /var/crac
2023-12-01T07:53:41.374Z INFO 10 --- [Attach Listener] o.s.c.support.DefaultLifecycleProcessor : Restarting Spring-managed lifecycle beans after JVM restore
2023-12-01T07:53:41.379Z INFO 10 --- [Attach Listener] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path ''
2023-12-01T07:53:41.381Z INFO 10 --- [Attach Listener] o.s.c.support.DefaultLifecycleProcessor : Spring-managed lifecycle restart completed (restored JVM running for 27 ms)
You can access the app.
$ curl localhost:8080
Hello World!
As long as you use the same Checkpoint, changes to the code will not be reflected in the app. In the above entrypoint.sh
, if you set the environment variable CLEAN_CHECKPOINT
to true
, the folder will be cleaned once and the Checkpoint will be recreated.
Since the Checkpoint needs to be recreated when the code changes, in practice, CHECKPOINT_RESTORE_FILES_DIR
should be dynamically set to correspond to the revision.
For samples using other Spring features, refer to
https://github.com/spring-projects/spring-checkpoint-restore-smoke-tests.
If you use HikariCP, you need to set spring.datasource.hikari.allow-pool-suspension=true
, among other considerations.