본문 바로가기
dev/spring

Java JVM - Checkpoint Restore (CRaC)

by igooo 2024. 7. 20.
728x90

개요

이전 게시글에서 소개했던 Spring Runtime efficiency 문서에서(https://blog.igooo.org/115) CRaC에 대하여 소개한 적이 있는데 간단한 예제로 사용방법을 알아본다. K8S에 배포하는 파이프라인까지를 구성하고 문서를 작성하려고 했으나 사용 중인 K8S Node가 CRIU를(https://criu.org/Main_Page) 지원하지 않는 Ubuntu 배포판(20.04) 버전이라 K8S 배포는 추후에 작성한다. (Job으로 스냅샷을 생성하고 배포하는 방법)

 

Spring Boot 3.2부터 CRaC를(https://spring.io/blog/2023/11/23/spring-boot-3-2-0-available-now) 지원하기 시작했다. CRaC는 Coodinated Restore at Checkopint의 앞 글자로 실행 중인 Java 프로세스에 체크포인트를(실행 중인 프로세스의 이미지) 생성하고, 생성된 체크포인트를 사용하여 일반적인 JVM 로드과정 없이 체크포인트 생성 시점의 상태로 바로 애플리케이션을 실행할 수 있다. (매우 빠른 실행 가능)

CRaC는 Azul System에서 개발하였으며 Azul에서 제공하는 JDK로(https://www.azul.com/downloads/#zulu) 사용이 가능하다. 내부적으로는 리눅스의 CRIU 프로젝트를 기반으로 하고 있음으로, CRIU를 제공하는 배포판 버전에서만 사용이 가능하다.

 

 

Spring 

Spring Framework는 CRaC에서 구현된 checkpoint/resotre를 Spring 기능과 통합하여 Spring 애플리케이션의 시작 및 워밍업 시간을 줄일 수 있다.

 

CRaC는 실행중인 애플리케이션의 정보를 Checkpoint로 저장함으로 민감한 정보가 CRaC파일에 저장된다는 것을 가정하고 운영해야 하고, 보안에 대하여 신경 써야 한다.

 

 

Example

간단한 API를 제공하는 Spring 프로젝트를 구성하고, access, tomcat 로그를 생성하여 CRaC 이후에도 API 동작과 로그 파일을 사용 가능한지 확인한다.

프로젝트 구성

build.gradle

crac 의존성을 추가한다.

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	......
	implementation 'org.crac:crac'
	......
}

 

API Controller

@RestController
@RequestMapping("/api")
class ApiController {
	private static final Logger logger = LoggerFactory.getLogger(ApiController.class);

	@GetMapping("/crac")
	Map<String, Object> crac(@RequestParam(name = "name", defaultValue = "test") String name) {
		logger.info("name {}", name);
		return Map.of("name", name);
	}
}

 

Log(access, catatina)

access 로그와 catarina 로그파일에 대한 설정을 추가한다.

# application.properties
server.tomcat.basedir=.
server.tomcat.accesslog.enabled=true

# logback-spring.xml
<configuration>
	<include resource="org/springframework/boot/loggin/logback/base.xml" />
	<root level="INFO" />
</configuration>

 

 

Run CRaC 

위에서 작성한 프로젝트를 빌드하고 checkpoint/restore 하여 애플리케이션을 테스트한다.

Azul JDK

CRaC는 Azul JVM에서만 동작함으로 https://www.azul.com/downloads/?package=jdk#zulu에서 버전에 맞는 버전으로 설치한다.

resource-policies

애플리케이션에서 사용하는 file, socket이 있으면 resource-policies 파일을 정의하여 CRaC를 할 때 정의 해야 한다.

예제에서는 로그파일만 있음으로 아래 내용을 resource-policies.yaml로 정의한다.

참고 : https://docs.azul.com/core/crac/fd-policies

type: file
path: **/*.out
action: reopen
---
type: file
path: **/*.log
action: reopen

 

애플리케이션 실행

프로젝트를 빌드하고 CRaC를 위하여 몇 가지 JVM 옵션을 추가하여 애플리케이션을 실행한다.

$ ls
crac-0.0.1-SNAPSHOP.jar  resource-policies.yaml

# CRaC 이미지가 저장될 디렉토리 생성
$ mkdir -p crac/example

# Spring 애플리케이션 실행
$ java -XX:CRaCCheckopintTo=crac/example -Djdk.crac.resource-policies=resource-oplicies.yaml -jar crac-0.0.1-SNAPSHOP.jar

......
2024-07-19T14:44:18.624Z  INFO [crac,,,] 44152 --- [crac] [        main] org.igooo.CracApplication   : Started CracApplication in 2.153 seconds (process running for 2.608)
# 애플리케이션 실행에 2.608초 

# API 호출 테스트
$ curl http://localhost:8080/api/crac?name=crac
{"name":"crac"}

# access log 확인
$ cat logs/access_log.2024-07-19.log
0:0:0:0:0:0:0:1 - - [19/Jul/2024:14:44:55 +0000] "GET /api/crac?name=crac HTTP/1.1" 200 25

 

Checkpoint 생성

# jcmd 명령으로 checkpoint를 생성
$ jcmd crac-0.0.1-SNAPSHOP.jar JDK.checkpoint
44152:
CR: Checkpoint ...

# Spring 애플리케이션 로그 (checkpoint를 생성하면 애플리케이션을 자동으로 종료된다.)
2024-07-19T14:45:55.852Z  INFO [crac,,,] 44152 --- [crac] [Attach Listener] jdk.crac     : Starting checkpoint
Killed

# checkpoint 확인
$ ls
crac  crac-0.0.1-SNAPSHOT.jar  logs  resource-policies.yaml  work
$ ls crac/example/
core-44152.img  core-44157.img   ....
core-44153.img  ......
core-44154.img  dump4.log
core-44155.img  fdinfo-2.img
core-44156.img  filds.img

 

Restore

checkpoint로 생성한 이미지를 기반으로 애플리케이션을 다시 실행한다.

$ java -XX:CRaCRestoreFrom=crac/example
2024-07-19T14:54:20.151Z  INFO [crac,,,] 33452 --- [crac] [Attach Listener] o.s.c.suppot.DefaultLifevcycleProcessor : Restarting Spring-managed lifecyle beans after JVM restore
2024-07-19T14:54:20.161Z  INFO [crac,,,] 33452 --- [crac] [Attach Listener] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2024-07-19T14:54:20.163Z  INFO [crac,,,] 33452 --- [crac] [Attach Listener] o.s.c.support.DefaultLifecyclePreocessor : Spring-managed lifecycle restart completed (restored JVM running for 98ms)
# Spring 애플리케이션 실행에 98ms가 소요되었다.

# API 호출 테스트
$ curl http://localtest:8080/api/crac?name=restore
{"name":"restore"}

# access log 확인
$ cat logs/access_log.2024-07-19.log
0:0:0:0:0:0:0:1 - - [19/Jul/2024:14:44:55 +0000] "GET /api/crac?name=crac HTTP/1.1" 200 25
0:0:0:0:0:0:0:1 - - [19/Jul/2024:14:54:20 +0000] "GET /api/crac?name=restore HTTP/1.1" 200 25

 

 

결론

압축된 jar 파일을 실행했을 때 2.608초가 걸렸지만 CRaC를 사용해서 실행했을 때는 98ms에 시간이 걸렸다. 엄청 빠른 실행 시간을 보여주고, Checkpoint를 서버의 모든 기능을 실행한 후에 적용하면 복구한 이미지에서도 별도의 클래스 로드 없이 즉시 모든 기능을 빠르게 사용할 수 있다.

하지만 이미지의 보안 이슈와 checkpoint를 생성해야 하는 시점을 기존 CI/CD에 어떻게 적용할지는 고민이 필요한 부분이다.

 

 

참고