在 Kubernetes 中优化 Java Serverless 函数

实现更快的启动速度和更小的内存占用,以便在 Kubernetes 上运行 Serverless 函数。
64 位读者喜欢这篇文章。
Ship captain sailing the Kubernetes seas

在 Kubernetes 中,更快的启动速度和更小的内存占用始终很重要,因为运行数千个应用程序 Pod 会产生费用,并且使用更少的工作节点和其他资源可以节省成本。在 Kubernetes 上容器化的微服务中,内存比吞吐量更重要,因为

  • 由于持久性(与 CPU 周期不同),内存成本更高
  • 微服务会成倍增加开销成本
  • 一个单体应用变成 N 个微服务(例如,20 个微服务 ≈ 20GB)

这对 Serverless 函数开发和 Java 部署模型产生了重大影响。这是因为许多企业开发人员选择 Go、Python 和 Nodejs 等替代方案来克服性能瓶颈——直到现在,得益于 Quarkus,一种新的 Kubernetes 原生 Java 堆栈。本文解释了如何使用 Quarkus 优化 Java 性能,以便在 Kubernetes 上运行 Serverless 函数。

容器优先设计

Java 生态系统中的传统框架在初始化这些框架所需的内存和启动时间方面付出了代价,包括配置处理、类路径扫描、类加载、注解处理以及构建框架运行所需的世界元模型。对于不同的框架,这种情况会重复多次。

Quarkus 通过将几乎所有开销“左移”到构建阶段,帮助解决这些 Java 性能问题。通过在构建时仅执行一次代码和框架分析、字节码转换和动态元模型生成,您最终会得到一个高度优化的运行时可执行文件,该文件启动速度非常快,并且不需要传统启动的所有内存,因为 这项工作在构建阶段完成一次。

更重要的是,Quarkus 允许您构建一个原生可执行文件,与传统的云原生 Java 堆栈相比,它可以提供 性能优势,包括惊人的快速启动时间和极小的常驻内存集大小 (RSS),从而实现即时扩展和高密度内存利用率。

这是一个快速示例,说明如何使用 Quarkus 构建 Java Serverless 函数项目的原生可执行文件。

1. 创建 Quarkus Serverless Maven 项目

此命令生成一个 Quarkus 项目(例如,quarkus-serverless-native)以创建一个简单的函数

$ mvn io.quarkus:quarkus-maven-plugin:1.13.4.Final:create \
       -DprojectGroupId=org.acme \
       -DprojectArtifactId=quarkus-serverless-native \
       -DclassName="org.acme.getting.started.GreetingResource"

2. 构建原生可执行文件

您需要 GraalVM 来为 Java 应用程序构建原生可执行文件。您可以选择任何 GraalVM 发行版,例如 Oracle GraalVM Community Edition (CE)Mandrel(Oracle GraalVM CE 的下游发行版)。Mandrel 旨在支持在 OpenJDK 11 上构建 Quarkus 原生可执行文件。

打开 pom.xml,您会找到这个 native profile。您将使用它来构建原生可执行文件

<profiles>
    <profile>
        <id>native</id>
        <properties>
            <quarkus.package.type>native</quarkus.package.type>
        </properties>
    </profile>
</profiles>

注意: 您可以在本地安装 GraalVM 或 Mandrel 发行版。您也可以下载 Mandrel 容器镜像来构建它(就像我所做的那样),因此您需要在本地运行容器引擎(例如 Docker)。

假设您已经启动了容器运行时,请运行以下 Maven 命令之一。

对于 Docker

$ ./mvnw package -Pnative \
-Dquarkus.native.container-build=true \
-Dquarkus.native.container-runtime=docker

对于 Podman

$ ./mvnw package -Pnative \
-Dquarkus.native.container-build=true \
-Dquarkus.native.container-runtime=podman

输出应以 BUILD SUCCESS 结尾。

直接运行原生可执行文件,无需 Java 虚拟机 (JVM)

$ target/quarkus-serverless-native-1.0.0-SNAPSHOT-runner

输出将如下所示

__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
INFO  [io.quarkus] (main) quarkus-serverless-native 1.0.0-SNAPSHOT native
(powered by Quarkus xx.xx.xx.) Started in 0.019s. Listening on: http://0.0.0.0:8080
INFO [io.quarkus] (main) Profile prod activated. 
INFO [io.quarkus] (main) Installed features: [cdi, kubernetes, resteasy]

超音速!启动时间为 19 毫秒。时间在您的环境中可能会有所不同。

它还具有极低的内存使用率,正如 Linux ps 实用程序报告的那样。在应用程序运行时,在另一个终端中运行此命令

$ ps -o pid,rss,command -p $(pgrep -f runner)

您应该看到类似以下内容

  PID    RSS COMMAND
10246  11360 target/quarkus-serverless-native-1.0.0-SNAPSHOT-runner

此进程正在使用大约 11MB 的内存 (RSS)。非常紧凑!

注意: 任何应用程序(包括 Quarkus)的 RSS 和内存使用率都会因您的特定环境而异,并且会随着应用程序负载的增加而上升。

您还可以使用 REST API 访问该函数。那么输出应为 Hello RESTEasy

$ curl localhost:8080/hello
Hello RESTEasy

3. 将函数部署到 Knative 服务

如果您尚未创建命名空间,请在 OKD (OpenShift Kubernetes Distribution) 上创建一个命名空间(例如 quarkus-serverless-native),以将此原生可执行文件部署为 Serverless 函数。然后添加 quarkus-openshift 扩展以进行 Knative 服务部署

$ ./mvnw -q quarkus:add-extension -Dextensions="openshift"

src/main/resources/application.properties 中追加以下变量以配置 Knative 和 Kubernetes 资源

quarkus.container-image.group=quarkus-serverless-native
quarkus.container-image.registry=image-registry.openshift-image-registry.svc:5000
quarkus.native.container-build=true
quarkus.kubernetes-client.trust-certs=true
quarkus.kubernetes.deployment-target=knative
quarkus.kubernetes.deploy=true
quarkus.openshift.build-strategy=docker

构建原生可执行文件,然后将其直接部署到 OKD 集群

$ ./mvnw clean package -Pnative

注意: 请务必提前使用 oc login 命令登录到正确的项目(例如 quarkus-serverless-native)。

输出应以 BUILD SUCCESS 结尾。完成原生二进制构建和部署新的 Knative 服务需要几分钟时间。成功创建服务后,您应该使用 kubectloc 命令行工具看到 Knative 服务 (KSVC) 和修订版本 (REV)

$ kubectl get ksvc
NAME                        URL   [...]
quarkus-serverless-native   http://quarkus-serverless-native-[...].SUBDOMAIN  True

$ kubectl get rev
NAME                              CONFIG NAME                 K8S SERVICE NAME                  GENERATION   READY   REASON
quarkus-serverless-native-00001   quarkus-serverless-native   quarkus-serverless-native-00001   1            True

4. 访问原生可执行函数

通过运行以下 kubectl 命令检索 Serverless 函数的端点

$ kubectl get rt/quarkus-serverless-native

输出应如下所示

NAME                         URL                                                                                                          READY   REASON
quarkus-serverless-native   http://quarkus-serverless-restapi-quarkus-serverless-native.SUBDOMAIN   True 

使用 curl 命令访问路由 URL

$ curl http://quarkus-serverless-restapi-quarkus-serverless-native.SUBDOMAIN/hello

在不到一秒钟的时间内,您将获得与本地相同的结果

Hello RESTEasy

当您访问 OKD 集群中 Quarkus 运行 Pod 的日志时,您将看到原生可执行文件作为 Knative 服务正在运行。

下一步是什么?

您可以使用 GraalVM 发行版优化 Java Serverless 函数,以便将它们作为 Serverless 函数部署在 Kubernetes 的 Knative 上。Quarkus 通过在普通微服务中使用简单的配置来实现这种性能优化。

本系列中的下一篇文章将指导您如何在多个 Serverless 平台之间创建可移植函数,而无需更改代码。

接下来阅读

Java 的 Serverless 是什么?

Java 仍然是开发企业应用程序最流行的语言之一。那么,为什么 Serverless 开发人员要避开它呢?

(特约撰稿人,红帽)
2021年5月19日
danieloh
技术营销,开发者布道师,CNCF 大使,公共演讲者,出版作家,Quarkus,红帽运行时

评论已关闭。

Creative Commons License本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.