跳到正文
Hacker News· theanonymousone·· 10 天前AI 评分62

Brewlet 发布:把 Java 应用以纯 JAR 形式跑在 Kubernetes 上

Brewlet: Java on Kubernetes

AI 导读

Brewlet 发布公开的 pre-1.0 预览版,让开发者把 fat JAR、分层 classpath 或 JPMS 模块推送到 OCI registry,由节点上已安装的 JDK 直接运行,应用镜像中不再包含 OS 和 JVM 层,也无需 Dockerfile。

正文 · AI 翻译

☕ Java · Kubernetes · OCI

将你的应用(fat JAR、分层 classpath 或 JPMS 模块)推送到 OCI registry。Brewlet 使用节点上安装的 JDK 运行它。 无需 Dockerfile。应用镜像中不含 OS 或 JVM 层。

Brewlet 将 SpinKube 的节点常驻运行时模型引入 Java。

公开的 pre-1.0 预览版。请使用一次性评估环境。

通过 runc 隔离 JVM 遵循 Pod 限制 MIT 许可

传统容器

your app.jar 应用

JVM / JDK 运行时

OS 基础镜像 用户空间

运行时层随镜像一起版本化;
未变更的层可被缓存

Brewlet

your app.jar 加上依赖和启动元数据

节点提供共享 JDK;
每个工作负载保留自己的 JVM 和堆

交付 JAR,而非运行时

容器镜像 — 应用加运行时

FROM eclipse-temurin:21-jre   # you own this base + its CVEs
COPY target/app.jar /app/app.jar
ENTRYPOINT ["java","-jar","/app/app.jar"]

$ docker build -t registry/app:1.4.2 .
$ docker push registry/app:1.4.2   # unchanged layers are reused

Brewlet 镜像 — 仅应用

# Package a local OCI image: the Go CLI does not upload to a registry
$ brewlet push target/app.jar demo/app:1.4.2 --store ./oci --format image

# With the Maven Central plugin declared in pom.xml:
$ mvn package brewlet:push brewlet:manifest \
    -Dbrewlet.registry=registry.example.com/team
# → javaapplication.yaml already references the published image digest

# Review ports, probes, and runtime settings before applying.
# Deploy separately with kubectl or GitOps.
$ kubectl apply -f target/brewlet/javaapplication.yaml

应用镜像省略了 OS 和 JDK。负载大小取决于你的应用和依赖;普通容器工具也会复用未变更的层。在使用 brewlet: 命令之前,请在 POM 中声明 Maven 插件。

添加一个小型启动配置

该制品包含一个 JSON 描述符,告诉 Brewlet 如何启动你的应用。brewlet push 会为你创建最小配置,或者你也可以通过 --config 提供自己的配置。

brewlet.json — 启动配置

{
  "schemaVersion": 1,
  "mainJar": "app.jar",
  "entry": { "mode": "jar" },
  "enablePreview": false,
  "addOpens": ["java.base/java.lang=ALL-UNNAMED"],
  "systemProperties": { "file.encoding": "UTF-8" }
}

将应用特定的设置放在这里:预览特性、模块访问和系统属性。在部署描述符中选择 JDK、端口、启动器以及资源设置,例如 -XX 标志。如果没有节点提供所请求的 JDK,准入会以 NoCompatibleJDK 失败。

交付你的应用。不再重建运行时。

Brewlet 将应用打包与节点运行时维护分离。平台团队负责 JDK 及其用户空间;应用团队负责自己的代码和依赖。

无需 Dockerfile

无需 Dockerfile,即可将 JAR 和启动元数据打包为仅含应用的 OCI 镜像。

分离的运行时维护

交付仅含应用的镜像。平台团队单独扫描和修补 JDK 及其 OS 用户空间;运行时 CVE 仍需修复。

共享、集中管理的 JDK

无需重建应用镜像即可更新节点 JDK。滚动工作负载,将运行中的 JVM 迁移到新运行时。

  • 仅应用交付 将代码和依赖与节点的运行时安装分开分发。
  • 可选的启动加速 兼容的 AppCDS 归档可以减少启动工作。请衡量你的工作负载能获得多少收益。
  • 混合架构节点池 可移植 JAR 可跨架构运行;JNI 工作负载可以声明 arch 约束。

工作原理

三个组件将 Brewlet 连接到 Kubernetes:一个 provisioner 在每个节点上安装 JDK,一个 containerd shim 运行应用,以及一个 RuntimeClass 将 Pod 路由到该 shim。

1

构建

使用你通常的 mvn package 或 gradle bootJar 构建即可。无需 Brewlet 专用的构建步骤。

2

推送

Maven 插件将仅应用镜像发布到你的镜像仓库。brewlet push 会写入本地 OCI 布局,以便检查和在本地运行。

3

调度

Kubernetes 将一个带有 runtimeClassName: brewlet 的 Pod 发送到由 provisioner 准备好的节点。

4

运行

shim 以只读方式挂载节点的 JDK,应用 Pod 的 cgroup 限制,并在 runc 沙箱内使用 java -jar 启动应用。

Pod 使用 CNI、kubectl logs/exec、探针和 Service。 提供 CPU HPA 配置; 参见要求和验证限制。

各部分如何协同

你的 CI 将制品推送到镜像仓库,同时 Kubernetes 控制平面 调度 Pod。在工作节点上,containerd 的 CRI 拉取可运行镜像; shim 验证内容存储 blob 并组装 JVM 沙箱。

Brewlet architecture: CI publishes an application image; Kubernetes schedules a pod; containerd CRI pulls the image and the shim resolves verified local content for a runc sandbox. Brewlet architecture: Developer/CI and the control plane both feed the worker node, where the shim runs your JAR under runc in a sandbox.

控制平面从不接触你的 JAR——它只调度 Pod 并 准备节点。Containerd CRI 拉取镜像;shim 使用其已验证的 内容和节点共享的 JDK 安装来组装沙箱。改编 自规范中的高层架构。

在本地和 Kubernetes 上查看 Brewlet 运行

在配置插件后, 使用 mvn package brewlet:push brewlet:manifest 生成开发 YAML。 生成的文件已包含你的镜像摘要;下面的描述符 展示的是结构,而不是你需要复制的文件。 示例输出展示了本地和 Kubernetes 执行;实际资源 读数取决于 JDK 和部署设置。

JavaApplication 结构——示例

apiVersion: apps.brewlet.sh/v1alpha1
kind: JavaApplication
metadata:
  name: orders
spec:
  artifact:
    # Illustrative digest; brewlet:manifest fills in the actual build digest.
    image: registry.example.com/team/app@sha256:<published-digest>
  replicas: 1
  jvm:
    version: 21                    # must match a JDK your nodes offer
    args: ["-XX:MaxRAMPercentage=50.0"]
  resources:
    limits: { cpu: "1", memory: "256Mi" }   # become cgroup limits
  ports: [{ containerPort: 8080 }]
  service: { enabled: true }

Operator 会将其转换为下面的 Deployment 和 Service。控制器 添加 runtimeClassName,节点驻留的 JDK 在 声明的 cgroup 限制下运行 JAR。

本地运行——macOS

# Run an artifact already packaged in the local OCI store
$ brewlet run demo/hello:1.0.0 --store ./oci
→ java -jar /run/brewlet/app.jar   (no container image)

$ curl localhost:8080/hello
Hello from a JAR running directly
on the node via Brewlet!

真实 Kubernetes——Deployment + Service

# For this example, configure appName=orders and the settings shown above.
$ kubectl apply -f target/brewlet/javaapplication.yaml
$ kubectl get pod -l app=orders
NAME          READY   STATUS
orders-7c9…   1/1     Running

# Curl the Service: pod limits cpu "1", memory 256Mi
$ curl orders:8080/info
availableProcessors: 1       # the CPU limit, not the node
maxMemory:           128 MB  # heap bounded under 256Mi

JVM 看到 1 CPU 和限制在 256Mi 以下的堆,因为 containerd shim 将 Pod 的 cgroup 限制接入进程。 参见源码构建的 CPU 扩缩容验证及其限制。 使用 Brewlet 默认的 可运行镜像格式,Pod 只需要 image: <ref>;kubelet 会像拉取任何容器镜像一样拉取它。

Brewlet 与容器对比

Brewlet (JVM) 传统容器
工作负载现有 JVM 应用任何容器化进程
开发者制品OCI 中的 JAR、classpath 或 JPMS 应用完整 OCI 镜像
需要 Dockerfile否否(例如 Jib 或 Buildpacks)
运行时位置共享节点 JDK镜像内部
修补运行时更新节点 JDK,然后滚动工作负载更新镜像并滚动工作负载
隔离namespaces + cgroups (runc)namespaces + cgroups (runc)
K8s 集成Service、探针、日志;CPU HPA 候选已验证完整
最适合无需完整镜像即可获得 JVM 兼容性通用工作负载

在本地试用 Brewlet

在 macOS 或 Linux 上安装已发布的 CLI,然后运行一个 Java 应用。你需要 JDK 21+、Maven 3.10+、Git、curl 和 tar。无需 Go 工具链、Docker 或 Kubernetes 集群。

终端

# Install the CLI (the installer verifies the release checksum)
$ curl -fsSL https://brewlet.sh/install.sh | sh
$ export PATH="$HOME/.local/bin:$PATH"

# Build the example from the matching release tag
$ git clone --depth 1 --branch "v$(brewlet version)" https://github.com/microsoft/brewlet.git
$ cd brewlet
$ mvn -q -f integration-tests/fixtures/demo-app/pom.xml package

# Package, inspect, and run from a local OCI store
$ brewlet push integration-tests/fixtures/demo-app/target/app.jar \
    demo/hello:local --store ./oci --format artifact
$ brewlet inspect demo/hello:local --store ./oci
$ brewlet run demo/hello:local --store ./oci

用 Ctrl+C 停止应用,然后使用来自 Maven Central 的插件打包本地镜像。首先在 integration-tests/fixtures/demo-app/pom.xml 中声明该插件,使用 brewlet version 报告的版本。Maven 会自动下载它,无需手动安装或 GitHub 凭据。准备好用于 Kubernetes 时,使用 mvn package brewlet:push brewlet:manifest 配合你的镜像仓库来发布并生成开发用 YAML。检查它,并用 kubectl 单独应用;开发者研讨会会带你走完这一流程,无需手写 YAML。

你的项目

# With the Maven Central plugin declared in this POM, build a local OCI layout
$ mvn -f integration-tests/fixtures/demo-app/pom.xml package brewlet:build \
    -Dbrewlet.image=demo/hello:local

常见问题

这是一个把 JAR 复制进去的基础镜像吗?

不是。OCI 制品包含应用和一份小型启动配置,但不含操作系统或 JVM 层。节点在运行时提供 JDK 及其支持的用户空间。这些运行时组件仍需要扫描和修补;仅含应用的镜像并不能消除操作系统漏洞。

Brewlet 会改变我的 Java 应用的运行方式吗?

Brewlet 使用制品声明的标准 JVM 启动模式:java -jar、类路径主类或 JPMS 模块。它不会替换 JVM。用本地 Kubernetes 指南运行 Spring Boot 应用,或查看支持的 Boot 布局了解打包选项。

CPU 和内存限制是如何强制执行的?

限制通过 runc 成为 cgroup 约束。容器感知的 JVM 会结合应用和部署的 JVM 参数来解释这些限制。内存限制覆盖整个进程,而不仅仅是堆。

Brewlet 如何减少 JVM 冷启动?

当归档与 JDK 构建、架构和应用匹配时,可选的 AppCDS 可以减少启动工作。经授权的节点侧重生成会在 JVM 退出时写入归档,供后续启动使用;它不是即时的首次启动缓存。每个工作负载保留自己的 JVM 和堆,因此共享 JDK 存储并不意味着自动节省内存。参见 AppCDS 文档。

它能在混合架构的节点池上工作吗?

可以。provisioner 会安装与每个节点架构匹配的 JDK,可移植的 JAR 可以调度到任何地方。带有原生(JNI)代码的 JAR 可以声明 arch 约束,使准入将其引导到匹配的 amd64/arm64 节点上(或用 NoCompatibleArch 拒绝它)。参见多架构文档。

我可以指定哪些 JDK?

取决于你的节点提供什么。运行 brewlet k8s jdk list 列出集群中可用的 JDK 发行版和版本,然后在部署描述符中选择一个。请求没有节点提供的 JDK 会快速失败,并报出 NoCompatibleJDK 准入错误。

Brewlet 对多租户集群安全吗?

Brewlet 使用 runc 隔离和特权节点配置;它并不是额外的敌对租户隔离边界。请信任平台运维人员并遵循安全指南。不支持 gVisor 和 Kata 集成;更强的隔离是路线图工作。

有哪些供应链验证可用?

组件发布带有可验证的构建来源。托管依赖准入会验证 Brewlet 的 DSSE 证明;参见源码构建的实时验证及其局限。针对应用镜像的通用 cosign 或标准 SLSA 准入尚未实现;参见支持的准入契约与限制。

需要时我还能使用常规容器吗?

可以。Brewlet 是增量式的:它只处理通过 runtimeClassName: brewlet 选择加入的 Pod。其他一切照常运行。

因为你的 Java 应用就应该直接运行。☕

推送一个 JAR,调度一个 pod,在 cgroup 限制下服务真实流量。探索它、运行它、贡献它。

来源:Hacker News · brewlet.sh