云原生技术在离线交付场景中的实践

软件产品只有交付到用户手中才有价值,本人在面向政府等 ToG 场景的软件交付领域具有数年的工作经验,深知其中痛点。今天借助这篇文章,分享这些痛点以及我的解决之道。

提出问题

本人供职的公司,其主要客户群体是省内的政府部门,所开发的业务系统是服务于政府内网之中的移动APP。作为交付负责人,我一直苦恼于如何将一套基于 Spring Cloud 框架开发而来的服务端业务系统交付给我的客户。完成软件系统的交付只是万里长征第一步,如何处理后期的运维工作也是必须面对的问题。政府场景的特殊性,为我的工作平添了许多不利因素,这些 ToG 场景交付的痛点,且听我娓娓道来。

离线环境交付

与公网环境隔离,与公司网络隔离,完全的离线场景是政府交付工作中的标配特征,也是 ToG 交付最大的痛点。相信离线环境交付是所有的交付工程师都不想面对的场景,这意味着所有的交付物必须在事先准备好,交付过程中一旦出现任何遗漏和错误,都意味着明天必须再来现场一次。

交付环境不统一

如果你从事过面向政府的交付工作,那多半会遭遇过交付环境不统一的情况。由于各级政府部门的 IT 建设脚步不一样,同样一套业务系统,在交付到市级部门时,得到的硬件设施可能是一台物理服务器,而到了省级部门时,则可能得到了私有云提供的数台虚拟机。值得庆幸,物理机与虚拟机的差异并不大。然而近年来政府的 IT 建设一直在向国产自主可控的方向前进,当省级部门要求使用鲲鹏Arm架构CPU,亦或是使用国产麒麟操作系统进行交付时,和市级部门交付环境的差异就已经非常大了。我甚至不得不将同一套业务系统在两级部门的交付当作完全不同的两个项目来对待。这体现出不同交付环境之间的差异,而当我转身看向公司开发环境时,开发环境与交付环境的差异,已经开始让我听到自己头发落到地面的声音了。我很难确定事先准备好的交付资源,在甲方环境部署时会否遭遇操作系统以及硬件设施差异所导致的依赖性冲突问题。这种问题在离线环境下又被放大,我甚至不具备连接公网安装软件包来调试解决依赖性冲突问题的能力。

缺乏自动化运维能力

将软件交付到客户环境中,只是最初级的目标,在合同期内维护软件系统稳定运行是对交付质量更高层次的考验。依照个人经验,在一个软件交付项目中,交付部署的工作量,不及后期运维工作量的一半。我们是不希望所有的软件问题都需要工程师亲自抵达现场解决的,一来无法保障 SLA 服务协议中的时间承诺,其次也会消磨工程师的工作热情。在离线环境下如何构建起一套具备自动化运维能力的软件运行环境,变得尤为重要。依靠自动化运维能力,让一些软件故障得以自愈,在一定程度上降低了政府交付场景中的运维难度。但选择任何一种技术实现自动化运维的目标都是需要付出代价的,这意味着我需要在软件系统交付之前,先行在交付环境中组装一套稳定可靠的自动化运维平台。

过度依赖核心人员

在离线化的政府交付场景中,常常面临如下问题:一是交付环境难以统一时,其中特殊之处只被少数全程参与项目交付的工程师所了解,而实际经验告诉我们,这些特殊之处往往是一些异常情况的根源;二是离线的工作环境使得工程师通过查询资料来解决问题变成一种奢望,反向提高了对于工程师的经验和技能的技术要求,因此,“合格”的驻场运维工程师很难招到。以上问题造成了一些运维工作过分地依赖某些核心技术人员的局面,一旦核心技术人员离职或者调岗,对当前的业务连续性将会造成较大影响。所有的这些对人的依赖,都在某个靠谱的驻场工程师希望另谋高就,向我提出辞职申请时痛击我的脑神经。

持续交付困难

软件交付并非一次性工作。从项目管理的角度来说,用户很难在一开始就提出具体且可落实的需求,具体的项目范围会随着项目的推进逐渐确定,这是一个渐进明细的过程。而在软件产品交付的过程中,这种渐进明细体现在交付的产品会经过多次迭代,每次升级后的产品,都距离用户的最终需求更近一步。而这个持续交付的过程,在离线环境中,所遭遇的难处并不亚于首次交付,甚至会在某些需要回滚的场景中更加复杂。在微服务时代,一套完整的业务系统往往包含了几十个独立的组件,组件数量也为持续交付添加了复杂性。

驻场开发难

驻场开发是一种在政府交付场景中常见的需求。标准的软件产品往往是不能直接满足甲方需求的,这就需要我们的开发人员可以在甲方办公室直接定制开发指定的几个组件,并快速更新到线上环境中去,供甲方验证。在实际场景中,多数微服务功能是固定的,只有一两个 jar 包需要频繁更替。

以往的经历

我经历了公司软件产品交付的完整变革流程。从最开始的 jar 包交付,继而引入容器化技术交付镜像,到后来采用 Kubernetes 容器编排技术,我们始终围绕着复杂的离线环境进行软件产品的交付工作。每个阶段或多或少的解决了上述各种痛点,所付出的代价也不尽相同。最终我们拥抱了云原生技术,将业务系统整体作为新的对象实践了较为简单可靠的离线环境交付,同时兼顾了以往各种痛点。

Jar 包交付

得益于 Java 开发语言,我们可以将代码打包成为仅依赖 JDK 运行环境的二进制交付产物——Jar 包。彼时我们的软件产品还处于初级阶段,业务系统由10个 Jar 包、Mysql数据库、Redis 缓存、前端Nginx组成。

一次交付工作中,首先要搭建起基础运行环境,完成 JDK 的安装。Mysql、Redis 等中间件依靠很原始的 rpm 包进行安装,这个过程经常会遭遇包依赖冲突问题。最后将所有的 Jar 包运行起来,启动之前免不得进行一系列的人工配置工作。

这种交付方式比较原始,我们会写一些脚本来达成一定程度上的自动化,然而这只在一定程度上提升部署效率,自动化运维能力基本为零。中间件的安装部署对操作系统的绑定程度很高,一旦服务器的操作系统和我们预先了解的稍有偏差,都可能导致依赖冲突,导致安装失败。而配置过程对人工依赖过重,这在高可用部署的环境中表现的尤为突出,配置各种 IP 很容易出错。

做一个总结,这个阶段我们实现了简单业务系统的离线交付,然而没有解决其他任何一个 ToG 场景交付痛点。

引入容器化技术

为了抹平交付环境不统一所带来的复杂度,我们开始引入容器化技术,通过将所有组件容器化,我们只需要确保客户的服务器能够运行 Docker 容器,就不需要再担心底层操作系统的问题了。官方提供静态编译版本的 docker 二进制文件,我们再也不用和软件依赖打交道了。这个阶段,我们的业务系统也开始扩展,组件的数量上涨到了几十个,这导致我们不得不同时引入 docker-compose 以及 docker-swarm 技术来解决单机或高可用场景下的组件编排问题。这些技术同时提供了较低程度的故障自愈能力,距离真正的生产可用还有距离。

容器化技术解决了交付环境不统一的问题,但是其他方面的痛点提升有限。随着业务功能的扩展,一个新的痛点逐渐展现出来,我们需要携带数十个容器镜像进行交付,交付复杂度被交付物数量裹挟着不断上升。

转向 Kubernetes 技术

在交付团队掌握了容器化技术之后,为了解决自动化运维问题,我们开始向 Kubernetes 转型。Kubernetes 技术是可以为业务系统的交付和运维赋能的,借助于它,我们的业务系统实现了较高程度的自动化运维能力。

Kubernetes 技术在故障自愈、弹性伸缩等方面的能力提升使我们非常受用,业务系统真正做到了生产可用。但是同时也带来了新的痛点,那就是它本身过于复杂,无论是开发人员还是现场运维交付人员,都必须对它有足够的了解才可以驾驭。换句话说,这种技术的引入大幅度提高了对核心技术人员的依赖程度,甚至提高了对技术团队全员的入门门槛。离线化的交付场景下,对交付环境的前期一次性建设的成本大幅度提高,我们必须事先在离线环境中准备好可靠的 Kubernetes 集群,光这一项工作,就大幅度阻碍了 Kubernetes 技术在交付团队中的推广。

新的痛点

经过了前面的几个阶段,我认为面对离线化的复杂交付场景,继续在容器技术以及 Kubernetes 容器编排技术方向上前进是没有问题的,每一次技术选型,都在一定程度上解决了很多痛点,我们在交付的过程中已经不惧怕离线环境、交付环境不统一、缺乏自动化运维能力等痛点,但也引入了一些新的问题,是待解决的。

  • 业务功能扩展会同步提升交付复杂度。这一新痛点从本质上来说,是由于我们将每一个组件独立看待,而非将整个业务系统作为整体看待。这样做的结果就是交付物的数量和交付复杂程度直接挂钩,如果能将业务系统作为整体交付,而非每个组件单独交付,那将极大的降低交付复杂度。
  • 每一次新的选型,都引入了新的复杂度,这一点在转向 Kubernetes 技术时尤为突出。这项技术对业务系统的赋能能力是毋庸置疑的,但无论是一个新环境的首次部署,还是后期的运维难度,对交付团队成员技术能力的要求是直线上升的。为了降低交付团队新成员的入门难度,我们开始选型一些能够降低 Kubernetes 使用难度的图形化工具,易用性是选型的首要影响因素。
  • 持续交付困难以及驻场开发难这两大痛点,依然没有被很好的解决。这二者都需要我们提供机制,解决业务系统在交付环境中持续变更的问题,前者注重业务系统整体框架的迭代升级,后者注重某个组件的个性化快速迭代。

我们开始将目光放在了逐渐火热起来的云原生技术领域。首先,云原生技术是基于容器化技术和 Kubernetes 技术的,我们已经具备了一定的技术基础。其次,云原生技术也注重软件交付领域的各种最佳实践,其中一些实践非常契合前文中的痛点。经过一段时间的内部测试选型,我们最终使用了 Rainbond 云原生应用管理平台作为交付工具,实现了全新的复杂场景离线交付模式。

云原生离线交付实践

最开始,交付团队内部的一名成员从开源渠道偶然了解到了 Rainbond 这款产品,并推荐给开发团队人员使用。当时仅仅作为图形化的 Kubernetes 管理工具来使用,以此降低新手开发人员学习 Kubernetes 的门槛。但随着对产品的了解,我们逐渐发觉,Rainbond 真正的用途在于能够解决软件产品的交付问题。

将业务系统抽象为应用

以往的交付过程中,我们总是将业务系统中的每一个组件单独看待,但是在 Rainbond 体系中,管理的单元可以放大到业务系统级别,这种管理单元被称之为应用。应用内部的组件部署和编排都是基于图形化操作的,使用起来不难理解。组件间的调用关系基于拓扑图展现,一目了然。最重要的是,基于应用这种抽象,我们实现了将组件数量和交付复杂度脱钩,无论应用中有多少组件,我们始终视之为一个应用。这么做的好处在交付过程中体现的非常明显。

应用模板的离线导出导入

应用一旦部署编排完成,就可以发布成一个应用模板并导出,导出的产物是单独管理的一个包。离线的模板包在导入时完全不依赖于外部网络,导入完成就可以在离线环境中一键安装,复原为发布时的样子。组件之间的相互依赖关系、配置信息都得以保存,不需要在交付现场重新配置。这一能力完全改变了交付的逻辑,从单独交付数十个容器镜像,变成了交付一个涵盖整个业务系统的包,其中难度的下降可想而知。

简单易用

Rainbond 底层基于 Kubernetes 技术实现容器的调度,提供全面的自动化运维能力。并且将常见的配置从 Yaml 声明式配置转化成为图形化界面操作,极大的降低了入门门槛。引入 Rainbond 体系之后,新入职的工程师可以在简单的培训后,一日之内掌握 Rainbond 的使用方式并可以独立交付业务系统。

原生多云管理

Rainbond 原生支持面向 Kubernetes 的多云管理能力。政府交付场景虽说与公网隔离,然而其实同个系统内往往具有想通的内部网络。借助 Rainbond 的多云管理能力,我们将省内多个城市政府部门的交付场景统一管理了起来,在省会建立了统一的管理控制台。这样的部署模式为业务系统快速在多个数据中心的交付提供了机制,极大的降低了业务系统在全省范围内交付的成本。

持续交付机制

Rainbond 应用模板支持版本管理,当业务系统有较大改动时,只需要将应用整体重新发布一次,重新导入到交付环境中去后即可一键升级或回滚,极大提升了业务系统升级效率。在以往处理数十个容器镜像的升降级和配置工作,需要的时间成本是按天计算的,引入Rainbond 应用模板的版本控制机制之后,升降级的时间成本降低为分钟级,操作成本则可以忽略不计。

驻场开发快

当甲方要求某个组件做出些许改动时,使用整个应用级别的模板离线导入显然得不偿失。此时,只需要现场开发人员在个人开发笔记本上打包出 Jar 包,通过上传 Jar 包构建组件的能力快速构建指定的组件,简单的拼装后即可替换对应的组件。这个过程中开发人员只需要提供 Jar 包,甚至不需要学习容器化技术了解镜像打包的机制,Rainbond 会自动处理后续的工作。

总结

我司交付团队借助云原生技术, 极大的降低了面向政府的复杂离线场景交付工作成本。这种成本节约体现在交付时间缩短、人员技术要求降低、人员操作成本降低、交付物数量减少、配置工作量减少等多个方面。降低成本的同时,也成功为业务系统赋能,能够自动处理很多异常场景,实现了自动化运维。方便驻场开发,能够快速的响应甲方客户的需求,提升客户满意度。

然而 IT 工程领域的发展过程就是在不断面向新的痛点解决问题。目前使用云原生技术也并非能够解决所有的问题,在政府交付场景中,也曾经遭遇这一类场景,甲方提出了比较严苛的要求,禁止使用容器技术进行交付。这种要求从根源上阻绝了云原生交付技术的落地,然而如何优雅的回退到 Jar 包交付的路线中去就成了一个问题,期待社区提供支持,将应用模板转化成为裸机环境可用的交付物,这是后话。