2012年年初,PaaS先驱Heroku发布了Twelve-Factor App(App(The Twelve-Factor App),这是一组帮助开发人员构建PaaS应用程序的规则和指南
现在让我们简要介绍一下这些要素,看看它们如何用来持续部署Java应用程序。
1. 代码库:在版本控制系统中跟踪的一个代码库,会被多次部署
应该将每个Java应用程序(或者服务)的代码,存储在一个共享的代码仓库中。部署所使用的配置文件(例如,脚本、Dockerfile和Jenkinsfile)也应当与应用程序代码存储在一起。
2. 依赖关系:显式声明并隔离依赖项
我们通常会使用构建工具(例如,Maven或Gradle)来管理Java应用程序中的依赖关系,并且应该在虚拟机(VM)镜像清单、Dockerfile或者无服务器架构的配置文件中,明确指出操作系统方面的依赖项。
3. 配置:在环境中存储配置
Twelve-Factor App建议通过环境变量将配置信息注入应用程序。实际上,许多Java开发人员更喜欢使用配置文件来管理这些变量,而且在构建包含密码的VM或者容器时,通过环境变量来指定密码可能会存在潜在的安全问题。
将非敏感的配置数据存储在Spring Cloud Config(基于Git或Consul)等远程服务中,并且将密码存储在HashiCorp的Vault服务中,既可以满足Twelve-Factor的建议,也符合当前的最佳实践。
4. 支持服务:将支持服务视为一种附加资源(一般通过网络进行调用)
在构建管道的组件测试中,Java开发人员习惯用这种方式来代替数据存储和中间件,例如,使用内存数据库(例如,HSQLDB、Apache Qpid和Stubbed Cassandra)或者服务虚拟化(例如,Hoverfly和WireMock)框架。
5. 构建、发布、运行:严格分离构建和运行阶段
对于Java等编译语言,这条规范显而易见(并且几乎没有其他方式可选择)。值得一提的是,VM和容器技术的灵活性意味着,你可以使用合理配置的单独构件来构建、测试和运行应用程序。例如,你可以使用一个完整的OS、JDK以及诊断工具,创建一个用于构建和测试的部署构件,也可以在生产环境中仅用OS和JRE创建一个运行应用程序的构件。
但是,我们将此视为一种反模式,因为只应该有一个构件进入构建管道,即“单一事实来源”。 使用多个构件很容易导致开发环境和生产环境的配置不同,当发生问题时难以调试。
6. 进程:将应用程序作为一个或多个无状态的进程执行7. 端口绑定:通过端口绑定暴露服务
借助VM镜像、容器镜像或者无服务器函数等技术,可以让构建和运行Java微服务应用程序变得更加容易。
Java开发人员习惯通过端口暴露应用程序的服务(例如,在Jetty或Apache Tomcat上运行一个应用程序)。
8. 并发:通过进程模型进行伸缩
传统的Java应用程序通常会采用相反的方式,因为运行中的JVM就像是一个巨大的“超级进程”,通常会通过添加更多堆内存来实现垂直伸缩,或者通过镜像和负载均衡来实现多个实例的水平伸缩。不过,将Java应用程序分解为微服务,然后在VM、容器或无服务器函数中运行这些服务,也可以实现可伸缩性。无论采用何种方法来实现可伸缩性,都应该在构建管道中进行测试。
9. 可处置性:通过快速启动和优雅关闭保证最大的稳定性
对于习惯于创建传统的、长期运行的Java应用程序的开发人员而言,这可能需要转变观念,因为大部分应用程序的配置和初始化,都是在JVM或者应用程序启动过程中预先加载的。但是现代化的、容器化的应用程序会使用更多的即时(JIT)配置,确保应用程序在关闭期间尽最大努力清理资源和状态。
10. 开发/生产环境差异:尽可能保持开发、预发布和生产环境一致
与传统的物理机部署相比,将VM或容器技术与VMware、Kubernetes和Mesos等编排技术结合使用,可以降低开发环境和生产环境之间的不同,因为在物理机器环境中,开发或测试机器的底层硬件和操作系统配置可能会有显著差异。
随着构件在构建管道中的不断构建,它会越来越接近真实的环境(例如,单元测试可以在一个内存的沙箱中运行)。 但是,端到端测试应该尽可能在类似生产环境的环境中进行。
11. 日志:将日志视为事件流
Java与日志框架之间存在着长期、重要的关系,但是像Logback和Log4j2等现代的日志框架,已经可以将日志流式传输到标准输出或磁盘上。
12. 管理进程:一次性运行管理任务
因为容器和无服务器函数可以非常简单地运行Java应用程序,所以管理任务可以一次性运行。但是,它们也必须在构建管道内(或者作为其中的一部分)进行测试。