CRIU (Checkpoint and Restore in Userspace)
简介

CRIU是一个为Linux实现检查点/恢复功能的项目。全称Checkpoint/Restore In Userspace,或者CRIU,是一个Linux软件。它可以冻结正在运行的容器(或单个应用程序)并将其状态检查点保存到磁盘上。保存的数据可以用于恢复应用程序并将其完全运行到冻结时的状态。使用此功能,现在可以实现应用程序或容器的实时迁移、快照、远程调试等许多其他功能。CRIU最初是Virtuozzo的一个项目,并得到社区的巨大帮助。它目前被(集成到)OpenVZ、LXC /LXD、Docker、Podman和其他软件中,并为许多Linux发行版打包。
使用场景

容器实时迁移:容器被检查点,然后将镜像复制到另一台计算机上,然后进行恢复。从远程观察者的角度来看,容器只是暂时冻结了。
快速启动服务:CRIU可以帮助加速需要启动时间较长的服务或应用程序的启动过程,通过在服务的初始化状态创建检查点,以便在需要时可以快速启动。
无缝内核升级:CRIU可用于在不中断正在运行的进程的情况下进行内核升级,确保系统保持在线并运行。
网络负载均衡:CRIU可以与负载均衡器一起使用,以实现流量在不同节点之间的无缝切换,从而提高系统的可伸缩性和可用性。
高性能计算问题:在高性能计算环境中,CRIU可用于保存和恢复运行中的计算任务,以便在硬件或软件故障发生时保护计算工作。
桌面环境挂起/恢复:CRIU可以用于实现桌面环境中应用程序的挂起和恢复,以便在需要时恢复到以前的状态。
进程复制:CRIU允许将进程从一个系统复制到另一个系统,这对于应用程序迁移和负载均衡非常有用。
应用程序的“保存”功能:CRIU可以为不具备“保存”功能的应用程序(如游戏)添加保存和恢复功能,以便用户可以在中断后继续进行。
应用程序快照:CRIU可以创建应用程序的快照,以便在需要时可以恢复到特定状态。
将“遗忘”的应用程序移动到“屏幕”:CRIU可以帮助将在后台运行的应用程序转移到前台或“屏幕”,以便用户更容易访问它们。
在另一台机器上分析应用程序行为:CRIU可用于在不同的系统上分析应用程序的运行和行为,以进行性能和安全性分析。
调试挂起的应用程序:CRIU可以用于调试挂起状态的应用程序,以便了解其状态和执行。
容错系统:CRIU可用于创建容错系统,以在故障时自动保存和恢复系统状态。
更新模拟测试:CRIU可以用于模拟系统更新和升级,以检查它们对系统的影响,而无需实际执行更新。
零停机崩溃恢复:CRIU可以用于实现零停机的崩溃恢复,确保系统在发生故障时可以迅速恢复到正常运行状态。

CRIU实现原理

CRIU的功能的实现基本分为两个过程,checkpoint和restore。在checkpoint过程,criu主要通过ptrace机制把一段特殊代码动态注入到dumpee进程(待备份的程序进程)并运行,这段特殊代码就实现了收集dumpee进程的所有上下文信息,然后criu把这些上下文信息按功能分类存储为一个个镜像文件。在restore过程。criu解析checkpoint过程产生的镜像文件,以此来恢复程序备份前的状态没,让程序从备份前的状态继续运行。
  下面详细介绍checkpoint和restore这两个过程。
Checkpoint

checkpoint的过程基本依赖ptrace(linux 提供的系统调用,进程跟踪)功能实现。程序严重依赖/proc文件系统,/proc是一个基于内存的文件系统,包括CPU、内存、分区划分、[I/O地址]、直接内存访问通道和正在运行的进程等等,Linux通过/proc访问内核内部数据结构及更改内核设置等,它从/proc收集的信息包括:

文件描述信息(通过/proc/p i d / f d 和/proc/pid/fdinfo)
管道参数信息
内存表(通过/proc/p i d / m a p s 和/proc/pid/map_files/)

checkpoint过程中,criu做的工作由如下步骤组成:
说明:在描述checkpoint中,我们把criu进程称为dumper进程,简称dumper。把要备份的进程称为dumpee进程,简称dumpee。

​ 步骤1:收集并且冻结dumpee的进程树
  dumper通过dumpee的pid遍历/proc/%pid/task/路径收集线程tid,并且递归遍历/proc/p i d / t a s k / pid/task/pid/task/tid/children,然后通过 ptrace函数的PTRACE_ATTACH和PTRACE_SEIZE命令冻结dumpee程序。

​ 步骤2:收集dumpee的资源并保存
  在这个阶段,dumper获取dumpee的所有可获取的资源信息并写到文件里。这些资源的获取通过如下步骤:

通过 /proc/p i d / s m a p s ∗ ∗ 解 析 所 有 V M A s 区 域 , 并且通过∗∗/proc/pid/map_files 连接读取所有maps文件。
通过 /proc/$pid/fd获取文件描述号。
通过ptrace接口和解析/proc/$pid/stat块完成一个进程的核心参数(寄存器和friends)的获取。
通过ptrace接口向dumpee注入parasite code。这个过程由两步完成:首先注入mmap系统调用到任务被冻结那一刻的CS:IP位置,然后ptrace允许我们运行这个被注入的系统调用,这样我们就在被监控进程里申请到了足够的内存用于parasite code块。接下来把parasite code拷贝到这个新申请到的内存地址,并把CS:IP指向到parasite code的位置。

​ 步骤3:清理dumpee
  dumper获取到dumpee所有信息(比如内存页,它只能从被监控程序内部地址空间写出)后,我们使用ptrace的系列参数去掉步骤2中对dumpee进程的修改。主要是对被注入代码的清理并并恢复dumpee的地址空间。基本通过PTRACE_DETACH 和 PTACE_CONT。然后criu可以选择杀死dumpee或者让dumpee继续运行。上面的test实例中选择的就是在备份dumpee后杀死进程,实际工作中,如果要对程序做差分备份(或者叫增量备份)时可以选择继续运行dumpee。
Restore

恢复程序的过程完全依赖checkpoint过程后产生的镜像文件,主要过程分如下4步:

​ 步骤1:处理共享资源
  在这个步骤里,criu读取*.img镜像文件并找出哪些(子)进程共享了哪些资源,比如共享内存。如果有共享资源存在,稍后共享资源由这个程序的某个(子)进程还原,其他进程要么在第2阶段继承一个(如会话),要么以其他方式获取。例如,后者是通过unix套接字与SCM-CREDS消息一起发送的共享文件,或者是通过memfd文件描述符还原的共享内存区域。

​ 步骤2:生成进程树
  在这一步,CRIU会调用fork()函数一次或多次来重新创建所需进程。

​ 步骤3:恢复基本的资源信息
  在此阶段CRIU打开文件、准备namespaces、重新映射所有私有内存区域、创建sockets、调用chdir() 和 chroot()等等。

​ 步骤4:切换到dumpee的上下文
  通过将restorer.built-in.bin的代码注入到dumpee进程,来完成余下的内存区域、timers、credentials、threads的恢复。
支持的系统平台

x86:主流x86架构(Intel、AMD),兼容i386
arm:细分armv6/armv7/armv8指令集,向下兼容
aarch64:arm架构额64位系统(基于armv8指令集的64位架构)
ppc64:IBM power系列架构
s390:IBM System z系列大型机硬件平台
mips:龙芯mips架构,根据浪潮云对龙芯平台的需求开发
参考文献

https://github.com/checkpoint-restore/criu

https://criu.org/Main_Page