优化 Kubernetes 中的 Java 无服务器函数
Kubernetes 无服务器 Quarkus

> 
> 在 Kubernetes 中运行无服务器函数时，实现更快的启动速度和更小的内存占用。
> 
> 
> 


![](/data/attachment/album/202210/26/151603a4a44w1a71zk8b11.jpg "Ship captain sailing the Kubernetes seas")


由于运行上千个应用程序<ruby> 容器荚 <rt>  Pod </rt></ruby>所耗费的资源多，令它实现较少工作节点和资源占用所需成本也较高，所以在使用 [Kubernetes](https://opensource.com/article/19/6/reasons-kubernetes) 时，快速启动和较少的内存占用是至关重要的。在 Kubernetes 平台运行容器化微服务时，内存占用是比吞吐量更重要的考量因素，这是因为：


* 由于需要持续运行，所以耗费资源更多（不同于 CPU 占用）
* 微服务令开销成本成倍增加
* 一个单体应用程序变为若干个微服务的情况（例如 20 个微服务占用的存储空间约有 20GB）


这些情况极大影响了无服务器函数的发展和 Java 部署模型。到目前为止，许多企业开发人员选择 Go、Python 或 Node.js 这些替代方案来解决性能瓶颈，直到出现了 [Quarkus](https://quarkus.io/) 这种基于 kubernetes 的原生 Java 堆栈，才有所改观。本文介绍如何在使用了 Quarkus 的 kubernetes 平台上进行性能优化，以便运行无服务器函数。


### 容器优先的设计理念


由于 Java 生态系统中传统的框架都要进行框架的初始化，包括配置文件的处理、`classpath` 的扫描、类加载、注解的处理以及构建元模型，这些过程都是必不可少的，所以它们都比较耗费资源。如果使用了几种不同的框架，所耗费的资源也是成倍增加。


Quarkus 通过“<ruby> 左移 <rt>  shifting left </rt></ruby>”，把所有的资源开销大的操作都转移到构建阶段，解决了这些 Java 性能问题。在构建阶段进行代码和框架分析、字节码转换和动态元模型生成，而且只有一次，结果是：运行时可执行文件经过高度优化，启动非常快，不需要经过那些传统的启动过程，全过程只在构建阶段执行一次。


![Quarkus Build phase](/data/attachment/album/202210/26/151634r3l3gzbp27bkke0f.png "Quarkus Build phase")


更重要的是：Quarkus 支持构建原生可执行文件，它具有良好性能，包括快速启动和极小的<ruby> 驻留集大小 <rt>  resident set size </rt></ruby>（RSS）内存占用，跟传统的云原生 Java 栈相比，具备即时扩展的能力和高密度的内存利用。


![Quarkus RSS and Boot Time Metrics](/data/attachment/album/202210/26/151635p95d33596ilju4rd.png "Quarkus RSS and Boot Time Metrics")


这里有个例子，展示如何使用 Quarkus 将一个 [Java 无服务器](https://opensource.com/article/21/5/what-serverless-java) 项目构建为本地可执行文件。


### 1、使用 Quarkus 创建无服务器 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)](https://www.graalvm.org/community/) 或 [Mandrel](https://github.com/graalvm/mandrel)（Oracle GraalVM CE 的下游发行版）。Mandrel 是为支持 OpenJDK 11 上的 Quarkus-native 可执行文件的构建而设计的。


打开 `pom.xml`，你将发现其中的 `native` 设置。你将使用它来构建本地可执行文件。



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

```


> 
> **注意：** 你可以在本地安装 GraalVM 或 Mandrel 发行版。你也可以下载 Mandrel 容器映像来构建它（像我那样），因此你还需要在本地运行一个容器引擎（例如 Docker）。
> 
> 
> 


假设你已经打开了容器运行时，此时需要运行一下 Maven 命令：


使用 [Docker](https://www.docker.com/) 作为容器引擎：



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

```

使用 [Podman](https://podman.io/) 作为容器引擎：



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

```

输出信息结尾应当是 `BUILD SUCCESS`。


![Native Build Logs](/data/attachment/album/202210/26/151635iuy2m9l5zul5zu5i.png "Native Build Logs")


不借助 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 内存。非常小！



> 
> **注意:** 各种应用程序（包括 Quarkus）的驻留集大小和内存占用，都因运行环境而异，并随着应用程序载入而上升。
> 
> 
> 


你也可以使用 REST API 访问这个函数。输出结果应该是 `Hello RESTEasy`:



```
$ curl localhost:8080/hello
Hello RESTEasy

```

### 3、把函数部署到 Knative 服务


如果你还没有创建命名空间，现在就在 [OKD](https://docs.okd.io/latest/welcome/index.html)（OpenShift Kubernetes 发行版）[创建一个命名空间](https://docs.okd.io/latest/applications/projects/configuring-project-creation.html)（例如 `quarkus-serverless-native`），进而把这个本地可执行文件部署为无服务器函数。然后添加 `quarkus-openshift` 扩展：



```
$ ./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 服务需要花费几分钟。成功创建服务后，使用 `kubectl` 或 `oc` 命令工具，可以查看 Knative 服务和版本信息：



```
$ 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` 命令，搜索无服务器函数的节点：



```
$ 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 运行中的节点的日志，你会发现本地可执行文件正在以 Knative 服务的形式运行。


![Native Quarkus Log](/data/attachment/album/202210/26/151635hrov9o2fwvfwlrwl.png "Native Quarkus Log")


### 下一步呢？


你可以借助 GraalVM 发行版优化 Java 无服务器函数，从而在 Knative 中使用 Kubernetes 将它们部署为无服务器函数。Quarkus 支持在普通的微服务中使用简易配置进行性能优化。


本系列的下一篇文章将指导你在不更改代码的情况下跨多个无服务器平台实现可移植函数。


*(Daniel Oh, [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/))*




---


via: <https://opensource.com/article/21/6/java-serverless-functions-kubernetes>


作者：[Daniel Oh](https://opensource.com/users/daniel-oh) 选题：[lujun9972](https://github.com/lujun9972) 译者：[cool-summer-021](https://github.com/cool-summer-021) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
A faster startup and smaller memory footprint always matter in [Kubernetes](https://opensource.com/article/19/6/reasons-kubernetes) due to the expense of running thousands of application pods and the cost savings of doing it with fewer worker nodes and other resources. Memory is more important than throughput on containerized microservices on Kubernetes because:

- It's more expensive due to permanence (unlike CPU cycles)
- Microservices multiply the overhead cost
- One monolith application becomes
*N*microservices (e.g., 20 microservices ≈ 20GB)

This significantly impacts serverless function development and the Java deployment model. This is because many enterprise developers chose alternatives such as Go, Python, and Nodejs to overcome the performance bottleneck—until now, thanks to [Quarkus](https://quarkus.io/), a new Kubernetes-native Java stack. This article explains how to optimize Java performance to run serverless functions on Kubernetes using Quarkus.

## Container-first design


Traditional frameworks in the Java ecosystem come at a cost in terms of the memory and startup time required to initialize those frameworks, including configuration processing, classpath scanning, class loading, annotation processing, and building a metamodel of the world, which the framework requires to operate. This is multiplied over and over for different frameworks.

Quarkus helps fix these Java performance issues by "shifting left" almost all of the overhead to the build phase. By doing code and framework analysis, bytecode transformation, and dynamic metamodel generation only once, at build time, you end up with a highly optimized runtime executable that starts up super fast and doesn't require all the memory of a traditional startup because the work is done once, in the build phase.

![Quarkus Build phase Quarkus Build phase](https://opensource.com/sites/default/files/uploads/quarkus-build.png)

(Daniel Oh, CC BY-SA 4.0)

More importantly, Quarkus allows you to build a native executable file that provides [performance advantages](https://quarkus.io/blog/runtime-performance/), including amazingly fast boot time and incredibly small resident set size (RSS) memory, for instant scale-up and high-density memory utilization compared to the traditional cloud-native Java stack.

![Quarkus RSS and Boot Time Metrics Quarkus RSS and Boot Time Metrics](https://opensource.com/sites/default/files/uploads/quarkus-boot-metrics.png)

(Daniel Oh, CC BY-SA 4.0)

Here is a quick example of how you can build the native executable with a [Java serverless](https://opensource.com/article/21/5/what-serverless-java) function project using Quarkus.

## 1. Create the Quarkus serverless Maven project

This command generates a Quarkus project (e.g., `quarkus-serverless-native`

) to create a simple function:

```
$ 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. Build a native executable

You need a GraalVM to build a native executable for the Java application. You can choose any GraalVM distribution, such as [Oracle GraalVM Community Edition (CE)](https://www.graalvm.org/community/) and [Mandrel](https://github.com/graalvm/mandrel) (the downstream distribution of Oracle GraalVM CE). Mandrel is designed to support building Quarkus-native executables on OpenJDK 11.

Open `pom.xml`

, and you will find this `native`

profile. You'll use it to build a native executable:

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



Note:You can install the GraalVM or Mandrel distribution locally. You can also download the Mandrel container image to build it (as I did), so you need to run a container engine (e.g., Docker) locally.

Assuming you have started your container runtime already, run one of the following Maven commands.

For [Docker](https://www.docker.com/):

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


For [Podman](https://podman.io/):

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


The output should end with `BUILD SUCCESS`

.

![Native Build Logs Native Build Logs](https://opensource.com/sites/default/files/uploads/native-build-logs.png)

(Daniel Oh, CC BY-SA 4.0)

Run the native executable directly without Java Virtual Machine (JVM):

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


The output will look like:

```
__ ____ __ _____ ___ __ ____ ______
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
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]
```


Supersonic! That's *19* *milliseconds* to startup. The time might be different in your environment.

It also has extremely low memory usage, as the Linux `ps`

utility reports. While the app is running, run this command in another terminal:

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


You should see something like:

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


This process is using around *11MB* of memory (RSS). Pretty compact!


Note:The RSS and memory usage of any app, including Quarkus, will vary depending on your specific environment and will rise as application experiences load.

You can also access the function with a REST API. Then the output should be `Hello RESTEasy`

:

```
$ curl localhost:8080/hello
Hello RESTEasy
```


## 3. Deploy the functions to Knative service

If you haven't already, [create a namespace](https://docs.okd.io/latest/applications/projects/configuring-project-creation.html) (e.g., `quarkus-serverless-native`

) on [OKD](https://docs.okd.io/latest/welcome/index.html) (OpenShift Kubernetes Distribution) to deploy this native executable as a serverless function. Then add a `quarkus-openshift`

extension for Knative service deployment:

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


Append the following variables in `src/main/resources/application.properties`

to configure Knative and Kubernetes resources:

```
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
```


Build the native executable, then deploy it to the OKD cluster directly:

`$ ./mvnw clean package -Pnative`



Note:Make sure to log in to the right project (e.g.,`quarkus-serverless-native`

) using the`oc login`

command ahead of time.

The output should end with `BUILD SUCCESS`

. It will take a few minutes to complete a native binary build and deploy a new Knative service. After successfully creating the service, you should see a Knative service (KSVC) and revision (REV) using either the `kubectl`

or `oc`

command tool:

```
$ 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. Access the native executable function

Retrieve the serverless function's endpoint by running this `kubectl`

command:

`$ kubectl get rt/quarkus-serverless-native`


The output should look like:

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


Access the route `URL`

with a `curl`

command:

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


In less than one second, you will get the same result as you got locally:

`Hello RESTEasy`


When you access the Quarkus running pod's logs in the OKD cluster, you will see the native executable is running as the Knative service.

![Native Quarkus Log Native Quarkus Log](https://opensource.com/sites/default/files/uploads/native-quarkus-log.png)

(Daniel Oh, CC BY-SA 4.0)

## What's next?

You can optimize Java serverless functions with GraalVM distributions to deploy them as serverless functions on Knative with Kubernetes. Quarkus enables this performance optimization using simple configurations in normal microservices.

The next article in this series will guide you on making portable functions across multiple serverless platforms with no code changes.

## Comments are closed.