Flynn 平台 Java 应用部署实战:Maven/Gradle 构建、OpenJDK 运行时与 Procfile 进程编排
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 中实现,大体分为三个阶段:
- 检测(detect):对每个 buildpack 依次执行其
bin/detect脚本,第一个返回成功的 buildpack 即被选中; - 编译(compile):以非特权用户身份执行选中 buildpack 的
bin/compile,将应用源码编译进build目录,并通过.profile.d注入运行环境; - 发布(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"
从源码看部署全流程
要完整理解上述配置的生效时机,可以串联起以下源码链路:
- 应用推送:
git push flynn master将代码推送至 Flynn 的 git 接收端(gitreceive),源码以 tar 流形式交给 slugbuilder 的构建环境(slugbuilder/builder/build.sh 从 STDIN 解包源码); - buildpack 选取:构建脚本按 slugbuilder/builder/buildpacks.txt 的顺序依次执行
bin/detect,Java 应用因存在pom.xml/gradlew/build.gradle/settings.gradle而命中 java 或 gradle buildpack;若设置了BUILDPACK_URL,则直接使用自定义 buildpack; - 编译与运行时配置:buildpack 的
bin/compile读取system.properties(确定maven.version/java.runtime.version)、环境变量(MAVEN_CUSTOM_OPTS、JAVA_OPTS)执行依赖下载与编译,产物进入build目录; - 进程类型声明:
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 能力。