@levinzhang
2023-04-08T20:17:52.000000Z
字数 6016
阅读 319
by
本文讨论了云原生计算中启动时间的重要性,强调了基于JVM的应用所面临的挑战。文章介绍了Liberty InstantOn,它使用检查点/恢复技术改善了启动时间,在不影响Java功能或进行静态编译权衡的情况下实现应用的快速启动。
近年来,向云原生计算迁移一直是开发人员比较关注的事情,按照云原生方式他们的业务应用能够从减少IT基础设施,提高可扩展性等方面受益。当涉及到云端部署应用程序时,“伸缩至零(scale-to-zero)”是标准的供应(provisioning)策略,以便于在业务需求较低的时候降低成本。随着需求的增加,会供应更多的应用程序实例和运行时,这种扩展必须非常迅速,这样终端用户才不会遇到响应滞后的问题。运行时的启动时间会对扩展性能产生很大的影响。
Open Liberty是一个云原生Java运行时,与其他Java运行时类似,它是建立在JVM技术之上的。JVM(更广泛地说,整个JDK)所提供的性能、调试能力和类库使其成为支撑应用程序的重要技术。尽管JVM以出色的吞吐能力而闻名,但是其启动时间却落后于Go和C++等静态编译语言。考虑到伸缩至零的需求,所以多年以来,显著改善启动时间一直是所有JVM实现的关键创新领域。AppCDS(HotSpot)和Shared Classes Cache(Eclipse OpenJ9)这样的元数据缓存技术在启动时间方面做出了令人瞩目的改进,但并没有在启动时间方面实现数量级级别的减少,而这恰好是serverless计算的扩展场景所需要的。
Graal Native Image曾经宣布,借助编译为原生的方式,它能够实现低于100毫秒的启动时间,因此得到很多的关注。这是JVM领域的一个重大转变,因为这是Java第一次在启动时间方面能够与C++相抗衡。虽然Graal Native Image显著降低了启动时间,但这也是有一定代价的。
首先,静态编译要求在构建时对应用有一个全局的了解。对于开发人员来说,他们在构建应用时一直依赖的动态能力将会受到限制。比如,像反射、动态类加载和invokedynamic等操作均需要特殊处理,因为它们会干扰生成原生镜像时的静态分析。这意味着,我们可能需要对应用程序进行大量的修改,这样原生镜像才能够正常运行,更糟糕的是,应用程序的依赖可能也需要更新。
其次,调试会变得更具挑战性,因为我们此时调试的已经不是一个JVM应用,而是一个原生可执行文件。我们需要将熟悉的Java调试器替换为原生调试器(如gdb)来调查问题。有种解决方式是在开发环境中使用JVM,在生产环境中使用原生镜像。但是,这意味着生产环境与开发环境是不一致的,最终我们可能必须要在两个不同的运行时中修复缺陷!
最后,JVM提供的优秀的特性之一就是出色的吞吐量,即时(just-in-time)编译器能够在运行时根据实时数据优化应用程序以达到最佳性能。这一点也必须在原生镜像中牺牲掉,因为开发人员只有一次编译的机会,那就是在构建时。有些框架,比如Spring Native,已经建立了帮助Java开发人员在原生镜像约束下运行应用程序的能力,但不得不承认的是,开发人员必须放弃一些东西以获取原生镜像在启动时间方面的收益。
Liberty运行时采用了一种不同的方式来改善启动时间。Liberty的目标是提供快速启动,而不必进行取舍,这是通过一项名为Liberty InstantOn的特性实现的。该特性提供了Java开发人员熟悉的所有功能,相对于没有使用InstantOn的JVM运行环境,它的运行时启动时间最多能够优化10倍。
从基础上来讲,Liberty InstantOn是基于检查点/恢复(checkpoint/restore)技术的。我们启动一个应用程序,然后暂停它,并在某些明确定义的点上持久化应用程序的状态,这就是检查点。这个检查点就会成为应用镜像,当部署应用的时候,我们只需要从已保存的状态恢复镜像即可,这就是恢复,通过这种方式,应用程序就跳过了它通常要经历的启动和初始化过程(因为这些步骤已经运行过了)。
Liberty使用了OpenJ9 CRIU提供的支撑功能,这是一项基于Linux CRIU的技术,它能够让任意应用均支持检查点和恢复。在Liberty InstantOn方式中,因为我们依然在JVM上运行,所以在吞吐量性能方面没有任何损失。Java调试也能按照预期方式运行,所有依赖动态JVM能力的库也能正常运行。
尽管检查点/恢复的概念听起来很简单,但在现实中,会有一些限制(这是CRIU的运行方式所导致的)需要由运行时和JVM共同来解决,以便于让应用程序体验到这些收益。当执行检查点时(此时会构建镜像),CRIU会将环境“冻结”在检查点状态中,包括环境变量、计算资源的信息(CPU、内存)以及时间本身的信息都会打包到镜像中。这些东西中的任何一项在恢复环境中都可能是不同的,从而导致应用程序的不一致,这是很难追踪的。此外,检查点可能会捕获一些数据,如果镜像要通过容器注册中心跨公共网络传输到部署环境中,这就不是很理想了。这些数据可能包含对端点的外部连接,而这些连接在恢复环境中可能并不存在,另外数据中还可能包含我们不想在检查点镜像中嵌入的安全令牌。
基于这些原因,OpenJ9 CRIU支持内置的补偿机制,以确保在检查点保存的应用在恢复时的行为是正确和安全的。对时间敏感的API进行了修改以补偿检查点和恢复时的停机时间。对于像SecureRandom这样的随机API在恢复的时候对种子进行了重置(re-seed),以确保每次检查点恢复时,它都会被恢复为唯一的实例。
JVM可以解决它所知道的所有事情,但是应用程序代码可能也需要类似的处理。Liberty运行时通过与JVM合作来解决JVM无法自行处理的剩余问题,从而帮助开发人员摆脱检查点/恢复的复杂性。为了实现这一点,OpenJ9提供了一个钩子机制,开发人员可以利用它来注册在检查点之前和之后要执行的方法。这种机制被Liberty广泛采用,比如,在部署时重新解析配置,以确保为环境使用正确的配置。
所以,尽管OpenJ9提供了高效利用检查点/恢复技术的工具,但是增强现有应用程序启动时间的最简单的方式是在Liberty上使用Liberty InstantOn来运行它。Liberty InstantOn对检查点/恢复过程进行了抽象,将开发人员的选择简化为只有几项,比如确定检查点用于应用程序启动之前还是之后。
总而言之,我们的终极目标是改善Java应用程序的云原生体验,这意味着无论采用什么样的技术,都必须要在云环境中高效运行。Liberty InstantOn与容器技术(如Docker和Podman)实现了无缝集成。Liberty InstantOn还能与Knative和OpenShift等容器引擎协作。我们完成了相关的工作以确保Liberty InstantOn能够在非特权模式下运行,因为这对生产环境的安全性是非常重要的。这项工作的成果正在回馈给CRIU项目。
Liberty InstantOn的beta版本已经公开可用,开发人员可以使用现有的应用程序进行尝试,以观察启动时间的改进(最多能够快10倍)。你只需要使用Liberty InstantOn工具为你的应用程序创建一个应用容器镜像即可。Open Liberty发布了生产就绪的容器镜像,使你的应用程序可以很容易地进行容器化,以便于在容器引擎中运行,比如Docker、Podman或Red Hat OpenShift这样的Kubernetes环境。
Open Liberty容器镜像包含了所有必要的依赖,以便于在Open Liberty运行时中运行应用程序。如下针对开发人员的指南描述了如何基于Open Liberty beta-instanton
镜像(icr.io/appcafe/open-liberty:beta-instanton
)创建一个带有应用程序的基础应用容器镜像,然后如何在此基础之上创建并添加一个包含检查点进程状态的层。beta-instanton
镜像包含了对Open Liberty创建检查点并将检查点进程存储到容器镜像层中所需的所有先决条件。这包括对OpenJ9 CRIU和Linux CRIU支持的早期访问构建。
如下的指南使用Podman来构建和运行容器,并且使用了Open Liberty入门指南中的应用程序。如果你手头有自己的应用程序,可以将其替换掉。
入门应用程序包含了一个Dockerfile,如下所示:
FROM icr.io/appcafe/open-liberty:full-java11-openj9-ubi
ARG VERSION=1.0
ARG REVISION=SNAPSHOT
COPY --chown=1001:0 src/main/liberty/config/ /config/
COPY --chown=1001:0 target/*.war /config/apps/
RUN configure.sh
首先,开发人员需要更新FROM指令以使用beta-instanton镜像:
FROM icr.io/appcafe/open-liberty:beta-instanton
然后,借助更新后的Dockerfile,可以使用如下的命令构建应用容器镜像:
podman build –t getting-started .
该命令会创建应用容器镜像,但是还没有创建检查点进程。应用程序的检查点进程是通过如下命令运行应用容器镜像来创建的,这里添加了一些额外的选项:
podman run \
--name getting-started-checkpoint-container \
--privileged \
--env WLP_CHECKPOINT=applications \
getting-started
通过WLP_CHECKPOINT
变量,Open Liberty运行时声明在被配置的应用启动后但在打开端口以接受传入的请求之前对应用进程生成检查点。当应用程序进程完成检查点生成后,运行中的容器将会停止。这将会形成一个包含检查点进程状态的已停止的容器。
最后一步是将这个检查点进程状态以层的形式添加到原始应用程序的进程镜像中。这可以通过如下的命令将名为getting-started-checkpoint-container
的已停止应用容器提交给一个新的容器镜像:
podman commit \
getting-started-checkpoint-container \
getting-started-instanton
最终的结果是可运行的getting-started-instanton
容器镜像。
当运行getting-started-instanton
容器时,开发人员必须授予其一组Linux能力,以便容器镜像中的CRIU二进制文件执行恢复过程:
cap_checkpoint_restore
cap_net_admin
cap_sys_ptrace
在创建检查点进程时,使用了一个具有特权的容器,它授予了容器镜像中的CRIU二进制文件所需的Linux能力。
请通过如下的Podman命令以运行具有三种所需能力的容器:
podman run \
--rm \
--cap-add=CHECKPOINT_RESTORE \
--cap-add=NET_ADMIN \
--cap-add=SYS_PTRACE \
-p 9080:9080 \
getting-started-instanton
getting-started-instanton
容器会以必要的权限来运行,以执行恢复过程,应用程序的运行速度要比原始的getting-started应用程序快10倍。
Open Liberty的beta版本发布了对Liberty InstantOn的定期更新。未来的版本已经规划了一些改进,它们将会让Liberty InstantOn构建和运行应用镜像变得更加容易。例如,为了消除对NET_ADMIN
Linux能力的要求,一些额外的相关工作已经完成。还有一项计划是在恢复应用的过程中移除对SYS_PTRACE
能力的要求。这将减少运行应用所需的能力清单,在运行应用程序的时候,仅需CHECKPOINT_RESTORE
能力即可。
其他规划包括在应用容器的构建步骤中执行应用进程检查点,这样不需要容器运行和容器提交命令就能将应用进程状态存储到应用容器镜像层中了。
虽然云原生需要对组织的业务方式做出许多变化,但是有了Liberty InstantOn,开发人员就不用担心改变他们的应用开发方式了。
我们鼓励开发人员使用Open Liberty 22.0.0.11-beta或后续版本来尝试Liberty InstantOn的beta功能。欢迎通过项目的邮件列表进行反馈。如果遇到问题,开发人员可以在StackOverflow上发布问题。如果发现缺陷的话,欢迎提交issue。
Open Liberty和Eclipse OpenJ9均是开源项目。IBM基于这些项目构建了其商业的WebSphere Liberty Java运行时和IBM Semeru Runtimes Java发行版。Liberty InstantOn使用了Linux Checkpoint/Restore In Userspace(CRIU)项目提供的检查点/恢复技术,并与CRIU合作,将代码反馈给该项目。
Tobi Ajila是IBM公司OpenJ9团队的Java运行时开发人员。他在加拿大的渥太华工作。他过去曾从事过解释器优化、JVMTI增强等工作。他还在各种OpenJDK项目中与更广泛的Java社区进行合作。目前,他的主要工作是研究JVM层面的检查点/恢复技术。
Thomas Watson在Eclipse和Apache基金会有超过20年的贡献和领导各种开源项目的经验。他目前的工作重点包括开发Open Liberty项目,推动Eclipse Jakarta和Eclipse OSGi规范的发展,以及为Eclipse IDE项目和Apache Felix项目提交贡献。Thomas生活和工作在德克萨斯州的奥斯汀,是IBM的高级软件工程师。
查看英文原文:Rapid Startup of Your Cloud-Native Java Applications Without Compromise