Flynn 平台 Java 应用部署实战:Maven/Gradle 构建、OpenJDK 运行时与 Procfile 进程编排

原创2026-09-26 21:50:192,200 阅读
文章标签:云原生微服务容器编排运维

Flynn 平台 Java 应用部署实战:Maven/Gradle 构建、OpenJDK 运行时与 Procfile 进程编排

本篇技术指南聚焦于在 Flynn PaaS 平台上部署 Java 应用:从构建工具(Maven/Gradle)的自动检测、依赖声明与定制化构建参数,到 Java 运行时版本的选择,再到基于 Procfile 声明 web 进程类型并接入 HTTP 路由与 PORT 环境变量的完整流程。读完本文,你将掌握用 git push 将 Java 应用(含嵌入式 Jetty 与 WAR 两种形态)一键部署到 Flynn,并理解其背后的 buildpack 检测与 slug 构建机制。

部署机制概述:buildpack 驱动的 Java 应用

Flynn 通过 buildpacks 机制来准备和构建所有通过 git push 部署的应用,Java 也不例外。Flynn 使用 Heroku 官方的 heroku-buildpack-java 与 heroku-buildpack-gradle 两个 buildpack 分别支持 Maven 与 Gradle 构建工具,并统一使用 OpenJDK 运行 Java 应用。

在仓库的 slugbuilder/builder/buildpacks.txt 中可以看到,Flynn 内置的 buildpack 清单按顺序排列,Java 相关的两个 buildpack 位于列表中部:

https://github.com/heroku/heroku-buildpack-java.git#354d2a79
https://github.com/heroku/heroku-buildpack-gradle.git#b89c8c38

每个 buildpack 均以 git commit 固定版本,确保同一代码在不同时间部署的结果可复现。整个构建流程在 slugbuilder/builder/build.sh 中实现,大体分为三个阶段:

  1. 检测(detect):对每个 buildpack 依次执行其 bin/detect 脚本,第一个返回成功的 buildpack 即被选中;
  2. 编译(compile):以非特权用户身份执行选中 buildpack 的 bin/compile,将应用源码编译进 build 目录,并通过 .profile.d 注入运行环境;
  3. 发布(release):执行 bin/release 生成 .release 文件,其中包含默认进程类型等信息,随后由 create-artifact 将整个 build_root 打包成 slug。

这意味着 Java 应用开发者无需关心构建环境细节——只要应用根目录存在能被 heroku-buildpack-java 或 heroku-buildpack-gradle 识别到的标志文件,Flynn 就会自动接管依赖下载、编译与打包。

应用检测规则

Flynn 依据应用根目录下的标志文件自动判断 Java 应用的构建工具:

根目录标志文件 判定结果
pom.xml Java 应用,使用 Maven 构建
gradlew Java 应用,使用 Gradle 构建
build.gradle Java 应用,使用 Gradle 构建
settings.gradle Java 应用,使用 Gradle 构建

如果应用使用了其他语言/框架的标志文件(如 package.json、Gemfile),则会命中对应的 buildpack;当自动检测无法命中时,可以通过 .buildpacks 文件或 BUILDPACK_URL 环境变量手动指定 buildpack(详见 docs/content/apps.md 中关于自定义 buildpack 的说明)。

依赖管理

Maven

使用 Maven 构建时,依赖声明在根目录 pom.xml 的 <dependencies> 节点中。以下示例声明了对 log4j 的编译期依赖:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>io.flynn.example</groupId>
  <artifactId>flynn-example</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>flynn-example</name>

  <dependencies>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>
</project>

当包含该文件的应用部署到 Flynn 时,将执行以下 Maven 命令:

mvn -B -DskipTests=true clean install

该命令会下载全部依赖并将应用编译进 target 目录。

定制构建命令:如需定制构建过程,可设置环境变量 MAVEN_CUSTOM_OPTS:

flynn env set MAVEN_CUSTOM_OPTS="-dFirstCustomProperty=abc -dSecondCustomProperty=xyz"

设置后,实际执行的 Maven 命令变为:

mvn -B -dFirstCustomProperty=abc -dSecondCustomProperty=xyz clean dependency:list install

注意,设置 MAVEN_CUSTOM_OPTS 会替换默认命令中的 -DskipTests=true 与 clean install 之间的参数段——默认形式为 mvn -B -DskipTests=true clean install,定制后变为 mvn -B <自定义参数> clean dependency:list install,测试跳过开关与安装目标由 buildpack 逻辑控制,dependency:list 目标仍会执行以输出依赖清单。

指定 Maven 版本:Flynn 默认使用较新的 Maven 版本。如需锁定特定版本,在应用根目录的 system.properties 文件中设置 maven.version 属性,例如:

maven.version=3.2.3

有关 Maven 依赖声明的更多细节(坐标、作用域 scope、传递依赖等),可参考 Maven 官方文档中关于依赖机制(Introduction to the Dependency Mechanism)的介绍。

Gradle

使用 Gradle 构建时,依赖在根目录 build.gradle 文件中通过 Java 插件提供的各种依赖配置(configuration)声明。Flynn 在部署期间运行名为 stage 的任务,因此应用必须在 build.gradle 中定义 stage 任务来编译应用。

以下示例声明了对 log4j 的编译期依赖,并定义 stage 任务执行一次干净的构建:

apply plugin: "java"

repositories {
  mavenCentral()
}

dependencies {
  compile group: "log4j", name: "log4j", version: "1.2.17"
}

task stage (dependsOn: ["clean", "jar"])

当包含该文件的应用部署到 Flynn 时,将执行以下 Gradle 命令:

./gradlew stage

该命令会下载全部依赖并将应用编译进 build 目录。

关于 gradlew 的建议:强烈建议应用自带 gradlew(Gradle Wrapper)脚本,由它决定使用哪个版本的 Gradle。如果应用没有 gradlew,Flynn 会代为安装一个 Gradle,但此时版本将不可确定,可能造成构建环境漂移。Gradle Wrapper 的用法可参考 Gradle 官方文档中关于 Gradle Wrapper 的说明。

有关 Gradle 依赖配置的更多信息(compile、testCompile、runtime 等 configuration 及仓库声明),可参考 Gradle 官方文档中关于依赖工件(Artifact Dependencies)的教程。

Java 运行时

Flynn 默认使用 OpenJDK 8 运行 Java 应用,同时提供 OpenJDK 6 与 OpenJDK 7 可选。通过在应用根目录的 system.properties 文件中设置 java.runtime.version 来切换:

! OpenJDK 6
java.runtime.version=1.6

! OpenJDK 7
java.runtime.version=1.7

system.properties 同时可承载前文提到的 maven.version 属性,是 Java 应用控制构建与运行环境的关键文件。

进程类型与 Procfile

应用支持的进程类型在根目录的 Procfile 中声明,每行一个类型,格式为 TYPE: COMMAND。Flynn 构建阶段的 slugbuilder/builder/build.sh 会读取 Procfile 并输出所声明的进程类型;仓库测试应用 test/apps/http/Procfile 就是一个多进程类型的示例:

web: http
another-web: http

web 进程类型

web 进程类型会被 Flynn 自动分配 HTTP 路由,并注入对应的 PORT 环境变量,因此它通常用于启动应用的 HTTP 服务器。应用必须绑定并监听 PORT 环境变量提供的端口才能接收流量——Flynn 自动为每个应用配置 https://$APPNAME.$CLUSTERDOMAIN 路由,指向 web 进程类型的实例(参见 docs/content/apps.md)。

嵌入式 Jetty

内嵌 Jetty 服务器的应用应使用 PORT 环境变量启动服务,例如:

import javax.servlet.http.HttpServlet;
import org.eclipse.jetty.server.Server;

public class MyServlet extends HttpServlet
{
  // handler definitions

  public static void main(String[] args) throws Exception
  {
      Server server = new Server(Integer.valueOf(System.getenv("PORT")));
      // code to set handlers and start the server
  }
}

Jetty 嵌入开发的更多细节可参考 Eclipse 官方的 Embedding Jetty 教程。

假设应用使用 Gradle 构建工具与 Gradle 的 application 插件(application name 为 example),则在 Procfile 中定义进程类型为:

web: build/install/example/bin/example

build/install/example/bin/example 是 Gradle application 插件在 build/install 下生成的可执行脚本路径,Flynn 会在 slug 中以该命令启动 web 进程。

Jetty + WAR

以 WAR 形式打包的应用需要借助外部 Servlet 容器运行。可以在 pom.xml 中配置 Maven Dependency Plugin,将 jetty-runner JAR 复制到 target/dependency 目录:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-dependency-plugin</artifactId>
      <version>2.3</version>
      <executions>
        <execution>
          <phase>package</phase>
          <goals><goal>copy</goal></goals>
          <configuration>
            <artifactItems>
              <artifactItem>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>jetty-runner</artifactId>
                <version>7.5.4.v20111024</version>
                <destFileName>jetty-runner.jar</destFileName>
              </artifactItem>
            </artifactItems>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

假定应用被打包为 target 目录下的 WAR 文件,则 Procfile 中进程类型定义为:

web: java $JAVA_OPTS -jar target/dependency/jetty-runner.jar --port $PORT target/*.war

该命令的关键点:

  • 通过 --port $PORT 将 Servlet 容器绑定到 Flynn 注入的 PORT 端口;
  • 通过 $JAVA_OPTS 传入 JVM 参数。

JAVA_OPTS 默认值:Flynn 中 $JAVA_OPTS 的默认值为 -Xmx350m -Xss512k -Dfile.encoding=UTF-8,即 350MB 堆上限、512KB 线程栈、UTF-8 文件编码。可通过设置环境变量 JAVA_OPTS 覆盖,例如:

flynn env set JAVA_OPTS="-Xmx1g -Xss512k -Dfile.encoding=UTF-8"

从源码看部署全流程

要完整理解上述配置的生效时机,可以串联起以下源码链路:

  1. 应用推送:git push flynn master 将代码推送至 Flynn 的 git 接收端(gitreceive),源码以 tar 流形式交给 slugbuilder 的构建环境(slugbuilder/builder/build.sh 从 STDIN 解包源码);
  2. buildpack 选取:构建脚本按 slugbuilder/builder/buildpacks.txt 的顺序依次执行 bin/detect,Java 应用因存在 pom.xml / gradlew / build.gradle / settings.gradle 而命中 java 或 gradle buildpack;若设置了 BUILDPACK_URL,则直接使用自定义 buildpack;
  3. 编译与运行时配置:buildpack 的 bin/compile 读取 system.properties(确定 maven.version / java.runtime.version)、环境变量(MAVEN_CUSTOM_OPTS、JAVA_OPTS)执行依赖下载与编译,产物进入 build 目录;
  4. 进程类型声明:bin/release 与 Procfile 共同决定应用可用的进程类型(web 等),Flynn 的 controller 与 scheduler 据此编排进程,router 为 web 类型分配路由并注入 PORT。

对于多进程类型应用,Flynn 还支持以 -web 结尾的附加进程类型(如 admin-web),它们同样可以接入路由与内部服务发现,详见 docs/content/apps.md。

实战:从零部署一个 Java 应用

下面结合 docs/content/basics.md 与 docs/content/apps.md 给出完整的 Java 应用部署流程:

1. 创建应用并添加远程

flynn create example

该命令会为当前 git 仓库添加名为 flynn 的 remote。

2. 推送代码触发构建

git push flynn master

Flynn 会自动检测 Java buildpack、执行 mvn ... clean install(或 ./gradlew stage)、生成 slug 并启动应用。若需部署其他分支,可用 flynn create 创建新应用并以 git push <remote> <branch>:master 推送。

3. 配置环境变量

flynn env set MAVEN_CUSTOM_OPTS="-dFirstCustomProperty=abc -dSecondCustomProperty=xyz"
flynn env set JAVA_OPTS="-Xmx1g -Xss512k -Dfile.encoding=UTF-8"

设置环境变量会创建新 release,并触发应用全部进程按新配置重启。

4. 扩容与查看进程

flynn scale web=3
flynn ps

flynn ps 可列出每个进程的 ID、类型、状态与启动命令,flynn kill <ID> 可终止指定进程(非一次性任务会被自动重启)。

5. 查看日志

flynn log -f

应用进程写入 stdout/stderr 的内容会被自动收集,-f 实时跟踪。若 Java 应用构建阶段内存不足,可调高 slugbuilder 的内存限制:flynn limit set slugbuilder memory=4GB。

6. 自定义域名

如需额外 HTTP 路由,可执行 flynn route add http www.example.com,并将 www.example.com 配置为指向 $APPNAME.$CLUSTERDOMAIN 的 CNAME。启用 HTTPS 时使用 --tls-cert 与 --tls-key 指定 PEM 格式证书链与私钥(参见 docs/content/apps.md 与 docs/content/apps.md)。

小结

在 Flynn 上部署 Java 应用的核心要点可归纳为四条:

  • 检测:根目录存在 pom.xml 走 Maven(heroku-buildpack-java),存在 gradlew / build.gradle / settings.gradle 走 Gradle(heroku-buildpack-gradle);
  • 构建定制:MAVEN_CUSTOM_OPTS 定制 Maven 命令参数,system.properties 中的 maven.version 锁定 Maven 版本、java.runtime.version 选择 OpenJDK 6/7/8;
  • 运行形态:嵌入式 Jetty 直接读取 PORT 启动;WAR 应用借助 jetty-runner 配合 --port $PORT 与 $JAVA_OPTS 运行,JAVA_OPTS 默认 -Xmx350m -Xss512k -Dfile.encoding=UTF-8 可覆盖;
  • 进程编排:Procfile 中的 web 类型自动获得 HTTP 路由与 PORT 变量,是应用对外提供服务的入口。

掌握了这些规则,你就可以把现有的 Maven/Gradle Java 项目以几乎零改造成本的方式托管到 Flynn,享受其自动构建、零停机部署与内置服务发现的 PaaS 能力。

登录后查看全文
flynn