基于云原生技术的产品开发原则

传统开发模式困局

  • 开发、测试、运维无法一体化,上线周期长 开发、测试、线上多套环境需要进行单独维护,容易造成不一致影响各阶段的工作开展,进而引发一些线上故障等问题

  • 传统应用开发资源利用率低 考虑业务扩容、峰值等情况,需要冗余一定服务器之类的资源,手工维护这些资源很难做到实时且均衡的水平

  • 单体应用架构无法满足应用快速上线和迭代要求 业务代码和系统代码高度耦合造成整体可用性较低,影响产品演进,新功能迭代、问题排查等工作深受制约

云原生的三大特征:

  • 容器化封装:以容器为基础,提高整体开发水平,形成代码和组件重用,简化云原生应用程序的维护。在容器中运行应用程序和进程,并作为应用程序部署的独立单元,实现高水平资源隔离。
  • 自动化管理:通过集中式的编排调度系统来动态的管理和调度,提高系统和资源利用率降低运维成本。
  • 面向微服务:明确服务间的依赖互相解耦,通过松耦合的方式提升应用的整体敏捷性。

一个优秀的云原生产品,从产品设计阶段就应该考虑这三方面因素,下面逐条进行分析。

云原生产品开发原则

产品容器化过程中需要注意如下几个方面:

显示声明依赖关系

通过依赖清单的方式明确的声明所有的依赖项,并且在运行过程中,使用依赖隔离工具来确保程序不会调用系统中存在但清单中未声明的依赖项。这样可以简化环境配置流程,并保证应用的生产和开发环境更加一致。 具体到语言上来说例如Ruby 的 Bundler 使用 Gemfile 作为依赖项声明清单,使用 bundle exec 来进行依赖隔离。Python 中则可分别使用两种工具: Pip 用作依赖声明, Virtualenv 用作依赖隔离。甚至 C 语言也有类似工具, Autoconf 用作依赖声明,静态链接库用作依赖隔离。无论用什么工具,依赖声明和依赖隔离建议一起使用。

分离代码与配置

通常,应用的 配置 在不同 部署 (预发布、生产环境、开发环境等等)间会有很大差异。这其中包括:

  • 数据库,Memcached,以及其他 后端服务 的配置
  • 第三方服务的证书,如 Amazon S3、Twitter 等
  • 每份部署特有的配置,如域名等

将配置直接以常量的形式硬编码于代码中是一个很不优雅的做法,从便利性上来说这样会导致在不同的场景中需要通过修改代码才能满足需求,从安全性上来说会导致代码开源则会泄露部分关键配置数据。

常见的两个推荐方案:

  • 使用配置文件进行配置管理,坏处是配置文件和代码库还是较容易产生耦合,并且配置格式常常会和开发语言、框架产生依赖
  • 使用环境变量进行配置管理,更为推荐的方案,和代码库完全独立并且开发语言框架无关

与后端服务保持松耦合的关系

后端服务是指程序运行所需要的通过网络调用的各种服务,如数据库(MySQL,CouchDB),消息/队列系统(RabbitMQ,Beanstalkd),SMTP 邮件发送服务(Postfix),以及缓存系统(Memcached)等等。

与上一个原则对应,通常建议将后端服务的URL地址或者其他服务定位发现方式存储于配置之中并进行维护,代码直接访问配置即可拿到访问服务的方法,可以做到在不改动代码的情况下,随意的替换各项后端服务。 这样部署可以按需加载或卸载资源。例如,如果应用的数据库服务由于硬件问题出现异常,管理员可以从最近的备份中恢复一个数据库,卸载当前的数据库,然后加载新的数据库 – 整个过程都不需要修改代码,或者是将本地 MySQL 数据库换成第三方服务(例如 Amazon RDS)。 如下是一个简单的产品与后端服务关系的样例图:

以无状态的进程来运行应用

  • 无状态服务:就是没有特殊状态的服务,各个请求对于服务器来说统一无差别处理,请求自身携带了所有服务端所需要的所有参数(服务端自身不存储跟请求相关的任何数据,不包括数据库存储信息)
  • 有状态服务:与之相反,有状态服务在服务端保留之前请求的信息,用以处理当前请求,比如session等

虽然容器技术支持有状态服务功能(将部分数据随时进行备份,并且在创建一个新的有状态服务时,可以通过备份恢复这些数据,以达到数据持久化的目的),但考虑有状态的服务牵扯到存储卷挂载等问题很难在水平方向(服务器主机层面)上进行迁移,所以容器化场景下一个高效的架构模式是应用的进程都为无状态且无共享,任何需要持久化的数据都存储在后端服务内,比如数据库。 一些互联网系统依赖于 “粘性 session”, 这是指将用户session中的数据缓存至某进程的内存中,并将同一用户的后续请求路由到同一个进程,但这样在代码重新部署、配置更改或环境变更时仍会导致数据丢失,与前面的服务后端保持松耦合关系原则匹配的建议方案是将Session 中的数据保存在诸如 Memcached 或 Redis 这样的带有过期时间的缓存中。

通过多进程实现并发

通过将业务分为多个进程运行(例如下图web进程来处理HTTP请求,work进程处理后台业务工作等),并且应用程序必须可以在多台物理机器间跨进程工作,可以使系统扩展变得十分轻松,按需增加对应分类的进程数量便可以做到并发性能的提升。

注意进程的设计应该符合之前提出的无状态、无共享的原则,这样在扩展时能减少服务器、环境等因素的影响。

总结

云原生的第一个目标就是需要将产品应用与底层资源(服务器、系统等)进行解耦,开发者只需关注自己的核心业务即可。虽然现在的开发语言大多标榜着跨平台轻松移植,但实际开发过程中系统版本、底层组件、驱动、补丁等因素最终或多或少都会引发一些问题,所以将环境资源轻量级虚拟化的容器技术目前是最优秀的解决方案。 而为了充分利用容器化技术的优势,需要将我们的应用进行相应的模块化处理,微服务是一个目前业界使用率较高的架构方式,加上之前云原生的应用开发原则,应用可以灵活地做到跨云环境提供业务服务,垂直、水平的快速扩展。

参考资料:
The Twelve-Factor App
云原生架构概述 - DockOne.io
云原生(CloudNative)将成为应用云化开发的主流方式 - 知乎
云原生概念 - 云+社区 - 腾讯云