Skip to content

交付一个 Feat Cloud 应用

This content is not available in your language yet.

到了这一页,默认你已经能把应用跑起来了。
现在要解决的问题不再是“怎么写 Controller”,而是“怎么把它交给别的环境去跑”。

本文不追求覆盖所有部署形态,而是基于仓库里的 demo/helloworld_docker 给出一条最短交付路径。

flowchart LR
    A["代码可运行"] --> B["mvn clean package"]
    B --> C["得到可执行 Jar"]
    C --> D{"交付目标是什么?"}
    D --> E["先走 JRE 容器"]
    D --> F["评估 Native"]
    E --> G["调试简单、链路稳定"]
    F --> H["启动更快、构建更复杂"]

直接对应这几个文件:

  • demo/helloworld_docker/pom.xml
  • demo/helloworld_docker/Dockerfile_jre
  • demo/helloworld_docker/Dockerfile_native
  • demo/helloworld_docker/Makefile

如果你在看文档时想对照真实工程,这一组文件就是最好的入口。

先回答一个问题:你要交付什么

Section titled “先回答一个问题:你要交付什么”

对于 Feat Cloud 应用,最常见的两种交付物是:

  1. 一个可执行 Fat Jar
  2. 一个容器镜像

而容器镜像又常见地分成两种:

  • 基于 JRE 运行 Jar
  • 基于 GraalVM Native Image 运行本地可执行文件

demo/helloworld_docker/pom.xml 里已经给出了典型的 maven-shade-plugin 配置:

pom.xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/services/tech.smartboot.feat.cloud.CloudService</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>tech.smartboot.feat.demo.Bootstrap</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>

这里最关键的不是 Shade 本身,而是两件事:

  • 合并 CloudService 的服务发现文件
  • 把启动主类写进 manifest

打包命令:

Terminal window
mvn clean package

完成后,target/ 目录下就会出现可执行 Jar。

第二步:决定你是走 JRE 还是 Native

Section titled “第二步:决定你是走 JRE 还是 Native”

如果你想要更简单、调试更直接的交付方式,先用 JRE 镜像。

demo/helloworld_docker/Dockerfile_jre

Dockerfile_jre
FROM eclipse-temurin:21.0.7_6-jre-alpine
WORKDIR /feat
COPY target/helloworld_docker*.jar helloworld.jar
EXPOSE 8080
CMD ["java", "-jar", "helloworld.jar"]

这是最容易理解的一种交付方式:

  • 你交付的是 Jar
  • 容器里只负责提供 JRE
  • 启动命令仍然是 java -jar

如果你的目标是更快启动、更小运行时依赖,可以考虑 Native Image。

demo/helloworld_docker/Dockerfile_native

Dockerfile_native
FROM container-registry.oracle.com/graalvm/native-image:21-ol8 AS builder
COPY target/helloworld_docker*.jar helloworld.jar
RUN native-image --no-fallback -jar helloworld.jar
FROM ubuntu:18.04
EXPOSE 8080
COPY --from=builder /app/helloworld helloworld
ENTRYPOINT ["/helloworld"]

这条路径的核心区别是:

  • 前半段用 GraalVM 生成本地可执行文件
  • 后半段运行时不再需要完整 JRE

示例工程里给了一个简单的 Makefile

native:
mvn clean package
podman build -t feat-docker:native -f Dockerfile_native
podman build -t feat-docker:jre -f Dockerfile_jre

如果你使用 docker,把 podman 替换掉即可。

手动执行也很直接:

Terminal window
mvn clean package
docker build -t feat-docker:jre -f Dockerfile_jre demo/helloworld_docker
docker run -p 8080:8080 feat-docker:jre

然后访问:

http://localhost:8080/hello
  • 你更在乎交付简单
  • 你还在频繁调试
  • 你希望运行环境和本地 Java 行为更接近
  • 你很在意启动时间
  • 你部署在资源敏感环境
  • 你已经能稳定构建并运行普通 Jar 版本

不要一开始就把 Native 当默认路径。
先把 JRE 交付链路跑通,再上 Native,通常更稳。

结果通常是:

  • Jar 打出来了
  • 但运行时找不到 Feat Cloud 生成的服务信息

结果是 Jar 可以打包,但不能直接启动。

尤其是模块项目里,经常会因为构建目录和 Docker build 上下文不一致导致文件根本没复制进去。

Jar 能跑,本地正常,但容器里启动失败

Section titled “Jar 能跑,本地正常,但容器里启动失败”

优先检查:

  1. Jar 是否真的被复制进容器
  2. mainClass 是否正确
  3. 容器日志里有没有类路径或资源加载失败

先不要马上怀疑业务代码。
优先检查:

  1. 当前环境是否具备 GraalVM Native Image 构建能力
  2. 你是不是已经先验证过普通 JRE 容器版本
  3. 依赖和构建镜像是否匹配

为什么这页不详细展开 Kubernetes、systemd、Helm

Section titled “为什么这页不详细展开 Kubernetes、systemd、Helm”

因为这页的目标是先交付出“第一个可部署产物”。
更复杂的运维编排属于后续工程体系,而不是 Feat 文档主线的第一步。