飞书音视频 && Zadig

2021-06-16 创建
2021-07-21 更新
10分钟阅读时长

Zadig是面向开发者设计的开源、高可用 CI/CD:Zadig 强大的云原生多环境能力,轻松实现本地联调、微服务并行构建、集成测试和持续部署。 Zadig 不改变现有流程,无缝集成 GitHub/GitLab、Jenkins、多家云厂商。

前言

笔者目前就职于字节跳动飞书部门的音视频团队,因为工作项目原因接触了KodeRover公司的产品Zadig。Zadig是一款面向现代微服务架构场景而设计的CI/CD,拥有许多非常优秀的特性。Zadig帮助我们团队完成了非常重要的过渡阶段,虽然最终飞书音视频团队遗憾地没能持续使用Zadig,但是其优秀的设计思想值得我们学习借鉴!PS:笔者个人在未来的工作中会投入一部分精力到Zadig开源项目,与社区开发者一同为Zadig的建设贡献一份力量。

接下来笔者就来聊一聊飞书音视频和Zadig的故事。

背景

飞书的整体技术架构承接字节基础架构,以微服务为核心,运行在大规模自研容器平台TCE(起源于Kubernetes)之上。飞书音视频部门成立于2018年中旬,早期的团队成员对Go语言和微服务的最佳实践在认知上有比较明显的不足。在经历了近一年半的迭代中,我们才逐步探索出一套勉强适用于团队的Workflow,但与此同时也积累了较重的技术包袱。

首先需要声明的是,对于飞书这样一个toB的产品来说,质量和稳定性极为重要。因此数据监控和自动化测试是产品的生命线,本文不讨论数据监控的具体实践,主要是阐述自动化测试的演进之路。

在最早期从0到1的阶段,音视频服务端的业务侧实际上只有1个单体服务,该服务简单依赖少数外部的飞书IM服务(例如账号服务、卡片服务、消息服务等),存储也只包括MySQL和Redis。我们花费了一些时间讨论单元测试和功能测试的实现路径,并且快速得到了有效的反馈,这一切在只有1个单体服务的情况下非常简单。

最终方案概述起来大致是:Mock外部RPC接口返回值,存储层MySQL/Redis使用Docker容器,通过原生的go test加上assert工具包辅助进行单元测试和功能测试。此处单元测试和功能测试的主要区别是:功能测试会要求该单体服务启动运行之后,模拟客户端请求发向服务,专注于测试接口级别的功能完整性。

进一步地,在定制了一套CI流水线之后,整个初步的Workflow便形成了。

然而由于对微服务的拆分原则缺乏系统性的认知,我们在接下来的一年中迎来了爆炸式的服务数量增长。这里面除了业务增长所必须要新增的服务之外,更多的是为了方便不同Owner维护而独立出来的“冗余”服务。但从长期来看,微服务数量增加是必然现象

此时,早期设计的单元测试作用越来越小,功能测试更是已经名存实亡。由于每个服务都存在数量不小的外部依赖,尤其是需要多个RPC串联的时候,大量的Mock接口不仅脱离真实业务场景,而且维护Mock本身的逻辑也有非常高的成本。在业务的快速迭代中,外部接口同样日新月异,变化速度之快,使得编写功能测试用例几乎成为不可能的事情。

我们终于意识到需要彻底抛弃Mock,提供一种能力串联所有的微服务,做端到端的集成测试,这才是未来的发展方向。

恰逢此时的飞书进入发展新阶段,在更大的战略方针上面首次提出所有飞书服务需要支持私有化部署。这是一个重要契机,让我们开始全面思考开源的技术方案。

因此,在私有化部署和集成测试两重动力驱动之下,我们正式立项探索可行的解决方案。

探索期

2019年底,在经过一轮简单的讨论之后,我们决定先从问题的简化版开始探索:搭建一个All In One的环境,将目前已经存在的音视频微服务全部部署在该环境中,并且彼此之间能够互相访问。我们后来将这个环境命名为OneBox

OneBox若能实现将会带来两个重要作用:

  1. 理清字节云依赖。在OneBox的落地实践中,我们必然会逐步梳理清楚现有代码中哪些强依赖字节云上环境,从而在私有化部署的场景中改造或者移除。
  2. 微服务集成测试。OneBox真正搭建完成之后,我们可以利用整个环境当做微服务集成测试的基准环境,微服务的整体集成测试也就具备落地的可能性。

由于生产环境中的服务是运行在TCE上面的(字节自研Kubernetes引擎,但强依赖字节云上环境),因此我们第一直觉就是考虑基于原生Kubernetes的私有化部署方案

我们申请了一台配置优秀的物理机,快速搭建了Rancher和Rancher Kubernetes集群。然后case by case的分析每个音视频服务的依赖,进行逐步改造:例如将原本依赖于字节debian基础镜像改造成依赖于开源debian基础镜像、将原本依赖字节云MySQL/Redis/ES/Kafka/RocketMQ的go-driver改造降级成开源版本、服务之间的调用方式从原本字节统一的Consul改造成原生的Kubernetes Service 等等。

我们迅速在原生Kubernetes中启动了一个又一个微服务,并且微服务之间完全互通。然而正当我们以为一切顺利的时候,却还是遇到了致命问题:存在某些字节自研的存储组件没有容器化,而服务却强依赖该存储组件。

也正是这个原因最终影响了飞书私有化部署的整体方案,我们不可能完全改造成开源存储,而字节存储的云原生化还在逐步规划当中。

最终飞书的私有化部署方案基本如下(IaaS+PaaS+SaaS 一体化):

综上所述,飞书私有化部署的整体方案和产品形态都是非常重量级的。但是OneBox的探索之路仍然要继续下去,因为我们还需要OneBox来完成音视频微服务的集成测试。

不同于飞书私有化部署这个目标,我们还存在另一个致命的制约因素:飞书音视频业务本身无法闭环。我们的集成测试虽然是聚焦于音视频业务的,但飞书的业务本身是一体化无法分割的,因此我们必然要依赖飞书其它部门的微服务。

正是由于无法闭环,因此最早期设计功能测试框架的时候,我们以Mock作为解决方案。但正如我上面所说,Mock接口返回值的做法在快速迭代的微服务架构中是ROI极低的做法

我们最终思考许久之后,做出了两大决策:

  1. 对于飞书服务的外部依赖复用字节云上统一测试环境(该环境名为staging,是在字节云上面飞书整体的测试环境,数据库与生产环境完全独立)。
  2. 对于无法容器化的存储组件复用字节云上的存储,我们形象称之为外挂

上图所示的Staging是字节云上面的飞书测试环境。而OneBox则是音视频业务的All In One环境。

OneBox里面的计算层都是基于原生的Kubernetes。常规服务指使用常见的开源存储组件,如:MySQL/Redis的服务,可以通过部署MySQL/Redis StatefulSet到Kubernetes。特殊服务指使用字节自研存储组件,只能通过外挂方式去访问。

对于OneBox依赖Staging飞书其它部门的服务,我们不通过Mock完成,而是直接发送真实的RPC请求。

决定OneBox架构之后,下一步要解决的核心问题就是工具选型,我们认为工具是非常重要的。我们需要快速复刻多个OneBox作为基准测试环境,对于每个服务代码仓库的每次改动提交去部署其中某一个基准环境,再发起上游的集成测试用例。

一开始我们在工具选型上面主要考虑的是:

  1. Jenkins
  2. Gitlab CI
  3. Rancher CI

当时本人并不知道KodeRover的产品Zadig(开源之后的名字,下文统一用Zadig这一名称),后来在同事的推荐之下也纳入调研范畴。不过我们几乎在理解Zadig之后就迅速敲定了它。

Zadig最吸引我们的特性是:

  1. 快速创建环境。Zadig基于Kubernetes Namespace能够一键快速复刻环境,这一特性正是我们最需要的。
  2. 微服务启动顺序。Zadig允许我们为微服务指定启动顺序,解决了我们处理微服务之间依赖关系的问题。
  3. 代码仓库集成。Zadig同时支持Gitlab和Gerrit,非常符合字节的技术栈。

相比之下无论是Jenkins、Gitlab还是Rancher本身都不涉及微服务的概念,它们是面向更加通用的场景而设计的。

运行期

我们深度认同Zadig的设计理念,并且认为Zadig真正意义上解决了微服务集成测试的痛点。

但因为TCE(字节自研Kubernetes引擎)和原生Kubernetes的差异巨大,我们无法通过Zadig来管理TCE。因此,我们将Zadig定位成 音视频微服务集成测试平台

飞书音视频服务端的整体发布模型是基于Gitlab Flow

对于非核心服务,我们采用单分支模型:master发版。对于核心服务,我们采用双分支模型:master发版测试环境和online发版生产环境。

Zadig在飞书实践中的位置大致如下:

有了Zadig的集成测试能力,我们对进入Master分支的改动更加自信,整体带来的收益也比较大。另一方面,Zadig简约清爽的UI设计让我们非常喜爱这个平台,因此我们在后续的工作中也接入了很多不包含服务部署只利用Zadig定时任务的一些工作流。

转变期

2020年字节开始大力投入安全与隔离,由于飞书早期的历史原因,整个基础架构组件存在比较大的安全隐患。因此,字节在全公司范围内推动一些整改与升级。

首先,飞书架构部在2020下半年正式提出迁移staging环境到字节的BOE环境(Bytedance Offline Environment)。

不同于staging环境,BOE环境在字节体系中才是定位为线下测试用的。但BOE本身的网络环境是隔离的,Zadig之前部署的集群所在网络与BOE无法互通。

如果只是这一改造,对我们来说尚在可接受范围,只需要把OneBox集群整体迁移进BOE机房即可

然而就在此时,我们又发现了另一不利的发展趋势:字节的整体鉴权体系逐步正规化。OneBox内部署的音视频服务外挂存储是通过匿名方式或者账号密码方式去访问的,而在字节后续改造中必须基于服务本身的平台身份鉴权。而OneBox里面的服务都是脱离于字节体系之外的原生Kubernetes内的服务,并不具有平台身份鉴权能力。

基于以上两个原因以及字节后续展露出的种种不利于应用OneBox环境的趋势,我们无奈接受了事实。最终于2021年3月剥离了Zadig上包含的服务构建和集成测试,只保留一些定时任务,整体拥抱字节BOE技术栈。

结语

Zadig毫无疑问是业界优秀的CI/CD产品,它也是真正理解微服务、重视微服务、解决微服务痛点的产品。

虽然很遗憾的是飞书音视频团队最终剥离了在Zadig上面的服务构建,只保留一些定时任务,整体转向字节内部的DevOps技术体系。但我们知道团队业务不闭环以及企业自研技术体系是制约根因。

我们充分感受到Zadig的优秀设计思想是非常值得字节DevOps团队借鉴学习的,相信Zadig会有更美好未来!

后记

===2021.7.21更新部分 起始=== 笔者在字节跳动飞书对Zadig的使用经验和感悟最终也有幸被收录:

===2021.7.21更新部分 结束===

Avatar
吴国华 Go语言/微服务/后端/云原生/技术管理