为什么 Spring Boot 应用倾向于打 fat jar 直接启动,而集团的应用倾向于打 war 包从应用容器启动?

IT 主流思潮的角度

大意为 Spring Boot 是 DevOps 时代的产物,集团大多数应用是 Dev 和 Ops 分离时代的产物

演变过程

Java 应用部署于应用容器中,受 J2EE 影响,算是 Java Web 有别于其他 Web 快速开发语言的一大特色

war 包里(几乎)完整打包了 Web 页面(模板)、Web 站点描述、Java Web 的各个模块的 jar 以及依赖的第三方 jar。运维将这个 war 文件部署到应用容器中,站点就能被访问了

虚拟机在数据中心里开始流行以前,Java 应用是直接部署在物理机上的。但单一的 Java Web 又很难将一个物理主机的全部资源有效利用,为了节省成本,通常将多个 Java Web 同时部署在一台物理机上,确切的说,是将多个 war 文件部署在一个应用容器里

在一个应用容器中部署多个 war 文件,就能实现在一个 Java 虚拟机进程中为多个站点提供服务,这能有效地节省内存资源,特别是在那个内存比较金贵的年代,这种特性意义重大,甚至应用容器还能动态地部署和卸载 war 文件而无需重启 Java 虚拟机

业务飞速发展,应用容器开始暴露一些寻常难以发现的问题,甚至有时候这些问题会导致线上故障。于是渐渐的,开始有团队专门去维护开源的应用容器,修复漏洞,提升性能

由于研发团队交付的是 war 文件,当应用容器需要更新的时候,运维团队能够在研发团队不需要介入的情况下完成 Java Web 的重新部署。甚至当一些常用的底层框架出现了安全漏洞的时候,运维团队也能扫描全部机器全部 war 解压出来的目录中的 jar,将有问题的依赖替换之后重启应用完成修复

随着硬件性能逐步提升,也随着虚拟机技术被数据中心广泛使用,之前一个物理机上混合部署的十几个 Java Web,摇身一变成了一个物理机上虚拟出来的十几个虚拟机,每个虚拟机里部署一个应用容器和一个 Java Web

虚拟机技术的广泛使用,给应用运维带来了极大的便利,再也不用担心机器环境被破坏,再也不用担心应用之间依赖冲突,只需要几行命令几个脚本,一个配置完善的开箱即用的 Linux Server 就唾手可得。再也不用纠结哪些应用可以混合部署哪些应用需要独立部署,流量低峰期就超卖来压缩成本,流量高峰到来之前再将虚拟机飘到空闲主机上避免资源争抢

DevOps工种

后来开发杀入运维领域,诞生新的DevOps工种

当开发和运维都是一个团队甚至一个人的时候,应用和容器分开部署十分繁琐了

无论是升级容器还是升级应用,对于 DevOps 来说,都是一次没什么明显差别的升级任务,最好能用一套统一的部署流程去完成。也许 embedded servlet containers 就是在这种需求背景下诞生的,应用容器不再是独立于应用之外的容器,而是作为应用的一部分,伴随应用一起部署和升级

Spring Boot 就诞生在 DevOps 盛行的这几年,只需要一行 java -jar webapp.jar 就能完成应用的启动,甚至都感知不到应用容器的存在,仿佛只是在运行一个普通的 Java Application。于是升级容器的工作,就和升级一个第三方类库一样,修改 pom 文件,打包,发布,轻而易举

再后来,伴随着以 Docker 为首的一众容器技术的兴起,我们正在渐渐进入 Immutable Infrastructure 的时代

Immutable Infrastructure

不可变基础设施,是 Chad Fowler 于 2013 年提出的一个很有前瞻性的构想:

任何基础设施的实例(包括服务器、容器等各种软硬件)一旦创建之后便成为一种只读状态,不可对其进行任何更改。如果需要修改或升级某些实例,唯一的方式就是创建一批新的实例以替换

容器时代到来之前,虽然我们已经在 Spring Boot 之类的框架的引导下,将 Java 应用打成了一个 fat jar 来分发和部署。但实际上我们分发到生产环境中的内容,远不止这个 fat jar

为了让应用代码能够在不同环境中部署,我们会将各个环境不一致的内容抽取出来,保存在配置文件中供 Java Web 应用启动时读取,比如数据库连接串、密码,比如 ZooKeeper 集群的 IP 列表。为了能够动态调节日志等级和格式,我们还会单独为 logback 等日志器提供配置文件,还要监听这个日志配置的变化

这些 fat jar 之外的配置,给应用的部署、扩容带来了外的负担。容器想要为我们处理这一切

如果我们将配置文件打包进容器镜像中,我们就能得到一个随时可以部署的镜像,应用的部署和扩容变得简单起来。但是包含配置的镜像完全没有对环境变化的适应能力,在集群 A 上跑的好好的镜像,也许就没法部署在集群 B 里头

于是我们开始使用环境变量去描述各个环境不一致的内容,在启动 Java Web 之前先用环境变量渲染出一份适合当前环境的配置。于是,我们又得使用一个配置管理数据库来管理这许多环境中的许多环境变量,用一个中心化的 CMDB 去取代之前散落在各个集群的应用配置

Java Web 应用部署的方式,从最初的应用容器 + 物理机 + 多应用混合部署,到应用容器 + 虚拟机 + 独立部署,再到 fat jar + 虚拟机,最后是如今的 fat jar + Linux 容器,伴随着业界运维自动化智能化的浪潮一路走来,也让我们看到了工程师从「术业有专攻」向 DevOps 乃至全栈工程师转变的趋势。通过提供随时随地轻松获取的 API 化的计算资源,云计算正在加速这一转变,云计算也让正处在转变过程中的工程师的工作越来越轻松有趣

创业去做云计算并没有比之前任何一个时代轻松,真正变得容易的是基于云计算的创业