Skip to content
lingjun-cg edited this page Oct 17, 2024 · 7 revisions

概述

CRaC(Coordinated Restore at Checkpoint)是一个可以实现Java极速启动的技术,可以实现10x甚至更多的启动性能提升。

Hello CRaC

在本节通过对spring petclinc应用使用CRaC从而了解基本使用流程。

准备环境

  1. 下载支持CRaC的Java版本,Dragonwell 11需要版本号需要>=待补充
  2. 准备一台有root权限的Linux机器(支持x86和aarch64),内核版本>=4.19

准备spring petclinc

  1. 获取spring petclinc代码
git clone  https://github.com/spring-projects/spring-petclinic.git
cd spring-petclinic
git reset --hard ab9135ad9bbc8631221caf0a7ee3eadd0561311d
  1. 替换tomcat-embed-core为支持CRaC的版本,进入到spring-petclinc的目录后,执行命令:
patch pom.xml <<EOF
--- pom.xml     2024-10-11 16:05:33
+++ pom.xml.new 2024-10-11 16:06:26
@@ -49,8 +49,19 @@
     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
+      <exclusions>
+        <exclusion>
+          <groupId>org.apache.tomcat.embed</groupId>
+          <artifactId>tomcat-embed-core</artifactId>
+        </exclusion>
+      </exclusions>
     </dependency>
     <dependency>
+      <groupId>io.github.crac.org.apache.tomcat.embed</groupId>
+      <artifactId>tomcat-embed-core</artifactId>
+      <version>9.0.58</version>
+    </dependency>
+    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-validation</artifactId>
     </dependency>
EOF
  1. 执行命令mvn clean packages进行构建,构建的产物即target/spring-petclinic-2.4.0.BUILD-SNAPSHOT.jar

准备快照

  1. 执行如下命令: java -XX:CRaCCheckpointTo=cr -jar spring-petclinic-2.4.0.BUILD-SNAPSHOT.jar启动spring petclinc
  2. 启动后通过curl http://127.0.0.1:8080/ 检查是否启动成功
  3. 执行命令jcmd $pid JDK.checkpoint产生快照到目录cr,其中$pid为第一步启动的java进程的pid。执行成功以后会导致上面进程退出

从快照恢复

执行命令从快照恢复 java -XX:CRaCRestoreFrom=cr -jar spring-petclinic-2.4.0.BUILD-SNAPSHOT.jar 恢复成功不会有输出,因为是从进程暂停的地方恢复执行,通过curl http://127.0.0.1:8080/ 确认是否恢复成功

CRaC原理

criu(Checkpoint/Restore In Userspace)

理解CRaC之前,需要先了解criu, CRaC依赖criu对进程在用户态进行checkpoint以及restore。 criu在用户态checkpoint时会将进程的状态保存到快照文件中。一旦有了快照文件,就可以从快照文件恢复进程到checkpoint时的状态。可以用在如下一些场景:

  1. 迁移进程:由于种种原因例如内核升级,更换机器等,需要将进程从某机器迁移到其它机器上 a. 进程可能是用户的,用户希望无感迁移 b. 进程可能执行一些耗时的科学计算,如果进程停止了,再重新运行需要很长时间
  2. 加速启动:以Java为例,Java语言本身的动态性使得Java在启动过程中类加载以及JIT编译会占用较多资源,只有进程进入到稳定态,才能达到性能的峰值。如果能直接从峰值的状态恢复,则会节约重新执行的时间。

criu会将进程的如下状态记录下来:

  1. 内存
  2. 映射
  3. 线程的寄存器
  4. 文件
  5. cgroup
  6. ...

CRaC(Coordinated Restore at Checkpoint)

criu从理论上可以对任何用户态的进程进行checkpoint并restore,但是如果直接使用criu会遇到很多阻碍。例如:

  1. 如果在checkpoint时候,进程正在写文件中,用户写入的数据调用write,可能写入部分数据,也有可能写入数据,但是还没有flush到磁盘
  2. restore时系统时间、IP地址会发生变化,程序中依赖这些变化的数据,必须要进行相应的变化。假设程序中一个心跳检测的线程判断执行某个操作是否超时了,由于checkpoint时将当时的系统时间保存到内存中,当restore时可能已经是很久之后的,这时如果用当前时间与checkpoint时的系统时间比较,就会发现可能已经超时了。程序可能认为异常了。
  3. 进程运行行为依赖配置文件,但是restore时的配置文件与checkpoint并不相同。

针对上述的问题,抽象出共性的一些需求:

  1. checkpoint时整个程序必须处于某个可控的“安全状态”,这个安全状态包括:
  • 用户可见的状态:用户能控制的部分,比如文件确保写入到磁盘上
  • 用户不可见的状态:对于JVM来说,就是JVM内部的状态。
  1. restore需要给用户控制状态的恢复,从而实现一个快照在多个环境上可以恢复,从而避免一个快照只能给一个环境使用。

那么CRaC是如何保证上述的几点的:

  1. 为了实现JVM的内部的状态安全:
  • checkpoint是在进入safepoint以后完成的
  • JVM内部的类,例如随机数在restore重新初始化
  1. 为了实现应用程序的可控的安全状态,CRaC提供了API给用户注册callback,这些callback在checkpoint之前调用,从而让用户自己保证程序在安全状态
  2. 为了实现一个快照在多个环境恢复,在恢复时提供了callback实现用户自定义的恢复

评估是否适合使用CRaC

  1. JDK是否满足要求?目前只支持Dragonwell 11
  2. 运行环境内核版本是否满足要求?
  • 内核版本< 4.19: 不支持
  • 内核版本>=4.19但<5.9:是否允许以privileged模式运行,如果不允许则不支持
  • 内核版本>=5.9: 是否允许程序拥有CAP_CHECKPOINT_RESTORE,如果不允许则不支持
  1. 评估CRaC收益是否达到预期: CRaC的收益取决于代码中调用checkpoint的位置,位置离启动成功越近,收益越大,但是可能带来代码适配工作量越大。
  2. 是否能接受应用程序针对CRaC的适配: 涉及到对应用程序的代码进行修改,具体的工作多少取决于应用的状态恢复的复杂程度

使用CRaC

概述

约束

  1. Linux内核必须是4.19以及之后的版本
  • 如果是4.19只支持以privileged运行
  • >=5.9支持以非privileged运行
  1. checkpoint时确保有足够的空闲内存,否则可能由于OOM而导致失败(具体大小取决于应用)
  2. checkpoint以及restore的需要如下一致,否则可能恢复失败:
  • JVM版本完全一致
  • restore机器的CPU的features需要是checkpoint的features的超集
  1. 此特性与AppCDS(EagerAppCDS)存在冲突,不可同时使用

产生快照

  1. 使用jcmd $PID JDK.checkpoint生成快照
  2. 程序中主动调用org.crac.Core.checkpointRestore()生成快照。一旦执行这个调用以后,快照就会生成(在未出错的情况下),同时进程退出 这一步可以会遇到快照没有生成,或者报错,请移步到错误处理

非容器环境使用

  1. (root用户运行无需此步骤) 设置${JAVA_HOME}/lib/criu有相应capability:setcap cap_checkpoint_restore+eip ${JAVA_HOME}/lib/criu
  2. JVM增加选项表示保存到快照到目录cr, 运行java
  • root用户:-XX:CRaCCheckpointTo=cr
  • 非root用户:-XX:CRaCCheckpointTo=cr -XX:+CRaCUnprivileged
  1. 产生快照
  2. JVM增加如下选项,从目录cr中的快照恢复,运行java进行恢复
  • root用户: -XX:CRaCRestoreFrom=cr
  • 非root用户:-XX:CRaCRestoreFrom=cr -XX:+CRaCUnprivileged

在docker中使用

  1. (privileged模式运行无需此步骤) 镜像构建时对${JAVA_HOME}/lib/criu设置相应的capability:RUN setcap 'cap_checkpoint_restore+eip cap_setpcap+eip' /jdk/lib/criu
  2. JVM增加选项表示保存快照到目录cr
  • privileged模式:-XX:CRaCCheckpointTo=cr -XX:CRaCRestoreInheritPipeFds=1,2
  • 非privileged模式:-XX:CRaCCheckpointTo=cr -XX:CRaCRestoreInheritPipeFds=1,2 -XX:+CRaCUnprivileged
  1. docker run用于准备checkpoint,需要额外增加如下参数:
  • privileged模式: --privileged --user root
  • 非privileged模式:--security-opt seccomp=unconfined --cap-add CHECKPOINT_RESTORE --cap-add CAP_SETPCAP
  1. 产生快照
  2. JVM增加参数用于从cr目录恢复: -XX:CRaCRestoreFrom=cr -XX:+CRaCUnprivileged
  3. docker run用于从cr目录恢复,需要额外增加如下参数:
  • privileged模式:--privileged --user root
  • 非privileged模式:--security-opt seccomp=unconfined --cap-add CHECKPOINT_RESTORE --cap-add CAP_SETPCAP

在k8s中使用

应用启动脚本修改支持感知POD的注入的环境变量

通过设置pod的环境变量来控制pod是用于做checkpoint,还是从快照恢复,如下是一个示例的脚本供参考:

#! /bin/sh
APP_DIR=$(dirname "$0")
JAVA=${JAVA_HOME}/bin/java
CR=${APP_DIR}/cr
UNPRIVILEGE_OPT=""
if [[ $EUID -ne 0 ]]; then
        UNPRIVILEGE_OPT="-XX:+CRaCUnprivileged"
fi

pushd ${APP_DIR}
if test "x${DO_RESTORE}" = xtrue; then
    export CRAC_CRIU_OPTS="-o ${CR}/restore.log -vvvv"
    ${JAVA} -XX:CRaCRestoreFrom=${CR} ${UNPRIVILEGE_OPT} -jar spring-petclinic-2.4.0.BUILD-SNAPSHOT.jar
elif test "x${DO_CHECKPOINT}" = xtrue; then
    # avoid pid conflit
    for i in {1..10}; do sh -c "echo null > /dev/null"; done
    ${JAVA} -XX:CRaCCheckpointTo=${CR} ${UNPRIVILEGE_OPT} -XX:CRaCRestoreInheritPipeFds=1,2 -jar spring-petclinic-2.4.0.BUILD-SNAPSHOT.jar &
    pid=$!
    sleep 10
    ${JAVA_HOME}/bin/jcmd $pid JDK.checkpoint
    sleep 5
    cat ${CR}/dump4.log
    sleep 3600
fi

构建应用镜像

如果是以非privileged模式运行,则在构建镜像的时候,需要设置的capabilty,如下示例:

FROM alibaba-cloud-linux-3-registry.cn-hangzhou.cr.aliyuncs.com/alinux3/alinux3:latest
RUN groupadd -g 1000 app && useradd -u 1000 -g app -s /bin/sh  -d /home/app -m app
USER app:app
ADD dw11.tar.gz /home/app
ADD spring-petclinic-2.4.0.BUILD-SNAPSHOT.tar.gz /home/app
ADD run.sh /home/app
ENV JAVA_HOME=/home/app/jdk
USER root
RUN setcap 'cap_checkpoint_restore+eip cap_setpcap+eip' /home/app/jdk/lib/criu
USER app:app
CMD /home/app/run.sh

创建Depolyment用于checkpoint

与正常的Deployment基本一样,如果是privileged运行,则需要在Deployment的YAML文件中增加下面的部分:

        env:
        - name: DO_CHECKPOINT
          value: "true"
        securityContext:
          privileged: true  

如果是非privlieged,则需要增加如下部分:

        env:
        - name: DO_CHECKPOINT
          value: "true"
        securityContext:
          capabilities:
            add: ["CHECKPOINT_RESTORE"]

将快照文件放到镜像中重新构建镜像

上面的Deployment会在启动成功以后生成快照,可以进入pod里将快照文件打包并传输到用于构建镜像的机器上 重新构建镜像,可以在原来的镜像上叠加一层

基于快照的镜像创建Deployment用于从快照恢复

如果是privileged在YAML中增加如下部分:

        env:
        - name: DO_RESTORE
          value: "true"
        securityContext:
          privileged: true  

如果是非privileged增加如下部分:

        env:
        - name: DO_RESTORE
          value: "true"
        securityContext:
          capabilities:
            add: ["CHECKPOINT_RESTORE"]

CRaC适配

概述

CRaC是借助criu对java进程的状态保存下来,再次恢复时会从上次停下来的地方继续运行,但是在一些情况下需要程序对一些修改,从而确保CRaC能正确的恢复,包括但不限于:

  1. 文件可能需要在checkpoint时关闭确保文件的一致性,在restore时需要重新打开
  2. 网络连接在checkpoint时关闭,在restore时重新连接,因为这些连接可能已经失效
  3. 应用的状态可能已经无效,例如依赖环境变量、IP等,但是restore时已经发生了变化等

示例

下面以一个简单的程序代码说明如何适配,如下的代码:

public class TimeoutChecker implements Run{
    private long lastTime;
    public TimeoutChecker() {
        lastTime = System.currentTimeInMills();
    }
    public bool checkTimeout() {
        if (System.currentTimeInMills() - lastTime) > 5*1000L) {
            throw new TimeoutException();
        } else {
            lastTime = System.currentTimeInMills();
        }
    }
}

在checkpoint时lastTime保存的是最后一次的时间,但是在恢复的时候如果不对lastTime进行重新赋值则会导致一旦恢复就会抛出异常。 CRaC提供了Resource接口,提供了回调方法在checkpoint之前以及restore之后调用。 先依赖CRaC的三方包: Maven:

<dependency>
      <groupId>org.crac</groupId>
      <artifactId>crac</artifactId>
      <version>1.4.0</version>
    </dependency>

Gradle:

dependencies {
    compile group:'org.crac' , name: 'crac', version: "1.4.0"
}

然后让TimeoutChecker实现Resource接口:

+ import org.crac.*;
+ public class TimeoutChecker implements Run, Resource{
- public class TimeoutChecker implements Run{
  public TimeoutChecker() {
    lastTime = System.currentTimeInMills();
+   Core.getGlobalContext().register(this);
  }
    
+  @Override
+  public void beforeCheckpoint(Context<? extends Resource> context) throws Exception {
+  }

+  @Override
+  public void afterRestore(Context<? extends Resource> context) throws Exception {
+      lastTime = System.currentTimeInMills();
+  }

   public bool checkTimeout() {
       if (System.currentTimeInMills() - lastTime) > 5*1000L) {
           throw new TimeoutException();
       } else {
           lastTime = System.currentTimeInMills();
       }
   }
}

文件相关

文件一般在checkpoint时需要先关闭,但是针对一些特殊的情况可以不关闭。

logback和log4j日志文件

log4j以及logback的日志文件一般打开的是方式是O_APPEND和O_WRONLY,这种方式下可以不用关闭文件,但是需要在JVM参数CRaCAppendOnlyLogFiles指定这些文件。CRaCAppendOnlyLogFiles指定的文件如果在恢复的时候不存在的话,会默认创建空文件。 例如指定进程所有以O_APPEND和O_WRONLY模式打开的文件都可以在checkpoint时不检查是否关闭-XX:CRaCAppendOnlyLogFiles="*",更多的配置方法参考配置参考

gc日志文件

  1. 增加-XX:CRaCIgnoredFileDescriptors=3让checkpoint时忽略gc日志文件
  2. restore时创建空的日志文件,重新开始写入

容器环境下临时使用的文件

这类文件在checkpoint时确定内容,然后在容器下环境恢复,进程可以继续写入,但是这些写入一旦等到容器消亡以后就丢失了。可以通过JDK提供的API来注册文件,注册以后在checkpoint时,JDK会将这些文件保存到快照目录中。使用方法如下:

public class JvmCracUtils {
    /**
     * save the pseudo persistent file to image dir, then restore to origin path
     */
    public static final int SAVE_RESTORE = 0x01;

    /**
     * create a symbol link when restore a file. This should have a better performance than
     * COPY_WHEN_RESTORE if file is large
     */
    public static final int SYMLINK_WHEN_RESTORE = 0x10;

    private static boolean supportCracExtension;
    private static Method registerPseudoPersistentMethod;
    private static Method appendToAppClassLoaderClassPathMethod;

    static {
        initCracExtension();
    }

    private static void initCracExtension() {
        try {
            Class clsCore = Class.forName("javax.crac.Core");
            registerPseudoPersistentMethod =
                    clsCore.getMethod("registerPseudoPersistent", String.class, int.class);
            appendToAppClassLoaderClassPathMethod =
                    clsCore.getMethod("appendToAppClassLoaderClassPath", String.class);
            supportCracExtension = true;
        } catch (Throwable ignore) {
        }
    }

    public static void registerPseudoPersistent(String absoluteFilePath, int mode)
            throws CheckpointException {
        if (!supportCracExtension) {
            throw new RuntimeException("Current JDK not support CRaC extension.");
        }
        try {
            registerPseudoPersistentMethod.invoke(null, absoluteFilePath, mode);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
    public static void appendToAppClassLoaderClassPath(String path) throws CheckpointException {
        if (!supportCracExtension) {
            throw new RuntimeException("Current JDK not support CRaC extension.");
        }
        try {
            appendToAppClassLoaderClassPathMethod.invoke(null, path);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
}

下面将文件/home/admin/logs/app.log注册

JvmCracUtils.registerPseudoPersistent("/home/admin/logs/app.log", 
        JvmCracUtils.SAVE_RESTORE | JvmCracUtils.SYMLINK_WHEN_RESTORE);

其它用户打开的文件

如果是用户程序依赖的jar包,由于是只读的并假设它在恢复一直存在,所以不需要关闭。但是在这个之外的其它文件则需要实现org.crac.Resource接口。

网络相关

JMX

JMX的适配除了关闭javax.management.remote.JMXConnectorServer以及java.rmi.registry.Registry外,较为特殊的地方在于JMX关闭以后,内部的socket并不会立即关闭,所以在checkpoint时需要sleep一段时间,一般在60秒左右会完全关闭。

Server Socket

应用中使用server socket经常会一直以单独的线程中执行无超时的方式accept,可以参考如下方法关闭ServerSocket:

public class FooServer extends Thread {
    /** Indicates whether a shutdown of server component has been requested. */
    private final AtomicBoolean shutdownRequested = new AtomicBoolean();
    private volatile checkpointing = false;

    public void run() {
        try {
            while (!this.shutdownRequested.get()) {
                try {
                    ServerConnection conn =
                            new ServerConnection(
                                    NetUtils.acceptWithoutTimeout(serverSocket), this);
                    try {
                        synchronized (activeConnections) {
                            while (activeConnections.size() >= maxConnections) {
                                activeConnections.wait(2000);
                            }
                            activeConnections.add(conn);
                        }

                        conn.start();
                        conn = null;
                    } finally {
                        if (conn != null) {
                            conn.close();
                            synchronized (activeConnections) {
                                activeConnections.remove(conn);
                            }
                        }
                    }
                } catch (Throwable t) {
                    if (checkpointing) {
                        synchronized (paused) {
                            paused.compareAndSet(false, true);
                            paused.notifyAll();
                        }
                        synchronized (restoring) {
                            while (!restoring.get()) {
                                // wait until notify in CRaCResource.afterRestore
                                try {
                                    restoring.wait();
                                } catch (InterruptedException ie) {
                                    // This interrupt exception caused by call closeForCRaC
                                    continue;
                                }
                            }
                        }
                    } else {
                        throw t;
                    }
                }
            }
        } catch (Throwable t) {
            //....
        }
    }
    /**
     * CRaC checkpoint&restore need pause the thread first, then replace ServerSocket. After that
     * start thread again.
     */
    private void closeForCRaC() throws InterruptedException, IOException {
        checkpointing = true;

        Exception exception = null;

        try {
            this.serverSocket.close();
        } catch (IOException ioe) {
            exception = ioe;
        }

        // wake the thread up, in case it is waiting on some operation
        interrupt();

        // wait the thread paused, so make sure no one modify activeConnections
        synchronized (paused) {
            while (!paused.get()) {
                paused.wait();
            }
        }

        synchronized (activeConnections) {
            if (!activeConnections.isEmpty()) {
                for (ServerConnection conn : activeConnections) {
                     conn.close();
                }
                activeConnections.clear();
            }
        }

        checkpointing = false;
        paused.compareAndSet(true, false);
    }

    private class CRaCResource implements Resource {

        @Override
        public void beforeCheckpoint(Context<? extends Resource> context) throws Exception {
            closeForCRaC();
        }

        @Override
        public void afterRestore(Context<? extends Resource> context) throws Exception {
            synchronized (restoring) {
                restoring.compareAndSet(false, true);
                restoring.notifyAll();
            }
        }
    }
}

应用状态

  1. 根据程序的语义,分析哪些状态在checkpoint时要处理,哪些在restore要恢复
  2. 将上述的逻辑通过实现org.crac.Resource接口并在适当的位置注册到CRaC的上下文中 这里要注意所有注册到CRaC上下文中的Resource都是以soft reference方式,意味着如果没有其它引用Resource,那么Resource在checkpoint时可能已经被清除了,所以需要清理状态的对象的生命周期决定是否需要Resource对象的强引用。

在应用自身状态适配的过程中,经常需要处理final字段的修改:

  1. 去掉final
  2. 更改字段的类型,用一个新的包装类型将原来的类型包装起来,这样final可以保留,但是包装类型提供方法更新实际的值
  3. 扩展原来的类,在新的类中去除掉final
  4. 类型增加一个拷贝方法,在restore时调用此方法恢复对象内部状态

三方库

  1. 是否已经有支持CRaC的版本? 如有更新到相应的版本
  2. 是否可以在应用层直接关闭三方库中资源? 例如netty可以在应用代码进行start和stop server
  3. 是否可以通过配置来关闭一些不必要但是会导致无法checkpoint的资源。以log4j的jmx为例,如果应用不使用此特性,可以通过-Dlog4j2.disable.jmx=true关闭

classpath在restore时会有新增

提供了如下的API在恢复的上下文时更新class path

public void afterRestore(Context<? extends Resource> context) throws Exception {
    if (userLibDir != null) {
        final Path workingDirectory = FileUtils.getCurrentWorkingDirectory();
        //... 这里省略list用户jar的逻辑
        for (Path jarPath : relativeJars) {
            JvmCracUtils.appendToAppClassLoaderClassPath(jarPath.toString());
        }
    }
}

错误处理

checkpoint出错

  1. 先检查标准错误输出是否有打印异常堆栈,异常主要包括如下2类:
  1. 如果没有异常,则有可能是criu执行过程中发生错误,检查-XX:CRaCCheckpointTo指定的目录中dump4.log查看是否有错误。

常见问题

Error (compel/src/lib/infect.c:261): The criu itself is within dumped tree.

确保checkpoint的java进程的pid不能为1

(00.006081) Error (criu/files-reg.c:1735): Build-ID (For validation) could not be obtained for file /var/db/nscd/hosts because can't open the file

目前checkpoint时还不能处理使用nscd cache的情况下。需要关闭nscd cache再进行checkpoint。方法如下:

  1. 修改/etc/nscd.conf,将passwd和hosts对应的enable-cache设置为no
  2. 重启nscd服务,service nscd restart |

restore出错

  1. 先检查标准错误输出是否有异常
  2. 如果第一步没有报错,则可能是criu在restore的过程中出错,通过设置环境变量设置restore的日志:export CRAC_CRIU_OPTS="-o restore.log -vvvv"

常见问题:

criu日志中/proc/sys read-only

criu在恢复时需要确保pid与checkpoint时一致,如果内核不支持clone3指定tid,则需要通过修改/proc/sys/kernel/ns_last_pid来控制新的进程(线程的)id,但是如果/proc/sys是只读挂载,则无法修改就会出现restore失败。解决方法:以只读方式重新挂载mount -o rw,remount proc /procmount -o rw,remount proc /proc/sys

open file: **/metadata for read failed, error:

checkpoint失败了,快照没有生成成功,请先确保checkpoint成功

vm version ** != **

checkpoint时运行的Java与restore的java版本不一致

CPU instruction capabilities which JVM used do not match run time

restore的环境中CPU缺少在checkpoint环境中JIT code中使用的指令

tty: Don't have tty to inherit session from, aborting

这是由于在checkpoint是没有关闭进程的标准输入对应的tty导致,可以在checkpoint和restore时将标准输入不再关联tty.setsid java xxx < /dev/null &> run.log.上述命令会重新创建一个session,并且把输入用/dev/null代替,这时标准输出和错误都会写到run.log中

容器中恢复以后标准错误和输出没有内容

在checkpoint时增加选项:-XX:CRaCRestoreInheritPipeFds=1,2

Can't dump ghost file ** of 5693440 size, increase limit

根据提示大小调整如下环境变量的上限export CRAC_CRIU_OPTS="--ghost-limit=10M"

restore时java main方法参数不生效

restore时是从checkpoint往后执行,所以main的参数并不会重新读取。CRaC提供了重新读取system properties的方式,可以将main的参数通过system properties来指定。

Pid xxx do not match exptected xxx

pid被其它进程占用了,可以在checkpoint时运行若干次shell命令,空出一段pid避免restore时冲突

配置参考

JVM选项

选项 说明
-XX:CRaCCheckpointTo 开启checkpoint并且将快照写入到选项指定目录
-XX:CRaCRestoreFrom 从指定选项指定的目录中的快照恢复java进程
-XX:CRaCValidateBeforeRestore 默认打开,在restore之前校验cpu,jvm的一致性,如果不一致则fail back到正常启动
-XX:+CRTraceStartupTime和-Djdk.crac.trace-startup-time=true 打印快照的恢复的时间戳
-Djdk.crac.debug=true 打印每个Resource在checkpoint以及restore调用的详细信息
-XX:+CRPrintResourcesOnCheckpoint 在checkpoint之前打印所有的fd信息
-XX:CRaCIgnoredFileDescriptors 例如:-XX:CRaCIgnoredFileDescriptors=3表示在checkpoint时忽略fd为3文件
-XX:CRaCRestoreInheritPipeFds 例如-XX:CRaCRestoreInheritPipeFds=1,2用于在容器环境中Java进程的fd为1,2可能是管道,这样容器运行时可以通过管道获取到进程的标准输出和错误。此选项用于在恢复时对于从父进程的fd保留,不做恢复。
-XX:+CRaCUnprivileged 以unprivileged模式运行,在此模式运行时需要对${JAVA_HOME}/lib/criu设置相应的capability才可以运行。如果内核是4.19,需要CAP_SYS_ADMIN,如果内核是5.10,需要CAP_CHECKPOINT_RESTORE
-XX:CRaCAppendOnlyLogFiles 配置通过O_APPEND和O_WRONLY打开文件可以在checkpoint可以忽略。支持""表示所有这种模式打开的文件,".log,*.txt"表示所有扩展名为log和txt的文件,"/home/admin/foo.log,/home/admin/logs/bar.log"指定特定的文件

环境变量

环境变量名称 说明
CRAC_CRIU_OPTS CRaC内部使用了criu作为快照的引擎,criu本身支持较多的选项,需要使用这些选项,可以环境变量来传递。criu完整的选项参考:Usage
CRAC_CRIU_LEAVE_RUNNING 默认情况dump之后就会将目标进程kill掉,有时不希望kill掉,可以将此值设置为true,从而继续运行
Clone this wiki locally