简介
每个公司都有一个测试环境 供技术开发和调试。随着公司的壮大,会产生一个问题:一个测试环境够用么?不够用。只有一个测试环境的痛点:开发5分钟,联调1小时。为何呀?
假设你的服务依赖ABC,你联调的时候,极有可能ABC 中的某一个服务也在开发阶段,于是便可能:
- ABC 某一个服务直接不可用
- ABC 代码可能有bug,进而导致联调失败,耗费时间定位bug 在哪个服务上。
以上述问题为引子,可以看到:测试环境 应随着公司的壮大 而逐渐调整和规范,本文尝试梳理下 测试环境 管理 相关的几个问题,尤其关注的是环境隔离问题。
测试环境的特点
阿里测试环境运维及研发效率提升之道:生产环境最关注的就是稳定,测试环境更关注的是研发效率。
测试环境的特点
- 频繁的代码提交和部署
- 开发频繁修改自己的代码,但希望别人能够提供一个稳定的服务
- 资源配置低
资源配置低
- 服务器 配置比较低。比如磁盘容量比较低,一个服务打满磁盘,于是同主机的其它服务 直接就挂了。因此 需要进行资源监控,至少要做到支持报警以尽快发现问题。
- 服务器 运行环境不稳定,一般测试环境 都在公司的办公大楼内,比如笔者公司,每年断一次电是必然事件。
频繁的代码提交
带来的挑战
- 对于docker 环境来说,频繁部署会产生大量的镜像文件。因此要提供清理机制,尽可能压缩镜像文件大小,方法:直接压缩;尽可能共用layer。
- 尽可能缩短 代码提交 到开始在测试环境运行的 时间
环境隔离
为什么要隔离?为了避免相互干扰。可以回顾下开篇的案例。
环境包括什么?
- 互通服务器/容器,提供计算资源
- 中间件,比如mq、zk等
- 前端接入,比如nginx;后端存储,比如db、hdfs等
解决方案
- 强隔离,一个环境一套业务服务、中间件、数据库等。听起来 服务器成本 很贵的样子,多环境的维护也需要耗费人力。
- 弱隔离,能共用共用,按需隔离。对于服务A,存在开发版本A1及稳定版本A0,服务B类似。则A 服务调用 B 服务时。A0 会调用B0 服务,A1 服务则若存在B1 服务便调用B1 服务,否则A1 调用B0 服务。
弱隔离 实现方案
实现原理
-
根据源头的IP所在隔离组进行路由。(阿里文章中的方案)
暂时忽略上图中的红线
把源头的请求IP放在ID(阿里有一个中间件叫做鹰眼,每个用户请求会生成一个ID,这个ID会随着每一次调用一个一个传下去)里面,当你服务调用的时候,服务路由会把ID取出来,看看你的IP有没有跟隔离组做关联,如果有的话就到那个隔离组里面去调用。
特别的,把一个服务单独放在一个隔离组里,可以实现“服务在运行,但不会被任何人调用到”的效果
-
请求链路 中携带 环境标识。(有赞文章中的方案)
注意箭头的颜色
有赞与阿里方案类似,将服务实例信息与env 绑定。不同的是
-
隔离组的概念 是从 服务粒度来说的,即假设一次开发只涉及到 AB 两个服务,则希望实现:AB 存在开发和稳定两种状态,开发状态 可以访问别人的稳定状态,但开发状态对外不可见,除非AB 本身就是一起开发的,B 的开发状态 对A 可见。
-
有赞 的方案则是从 隔离角度 来说的,只是说 硬隔离成本太高,通过env 参数化的方式 实现弱隔离。
隔离组的概念 更加通用,你可以一个服务 占用一个隔离组,而一个服务占用env 则一个语义上不太顺。
信息关联
因为阿里 和 有赞的文章 分别提到 环境 和隔离组 等类似概念,以下统一 使用隔离组。
服务的提供方,如何告知 自己提供的是哪个隔离组的服务? 调用方 如何感知 自己所在的隔离组,以便调用 对应隔离组的服务。
对于rpc 服务,可以提供 第三方配置界面人为关联,将服务实例信息(比如ip)与隔离组 的关联情况 写入到 etcd/zk 等。
对于rpc 服务,服务治理框架 在发起 rpc 调用时
- 根据本机信息/ip 查询zk,感知自己所在的隔离组
- 查询目标 服务 在该隔离组 中是否有 实例
- 若有,则直接调用
- 若无,则调用默认 隔离组 对应的服务
对于restful 等服务,服务方可以 约定 url 规范,提供服务的url 中包含 隔离组信息,并强制通过域名 访问(这样就用到了 nginx)。请求方 则在请求中 加入 带有隔离组信息的cookie(此时一般一个隔离组一个请求方,可以在请求方启动时配置好 隔离组参数,也可以单独做一个代理系统,在代理系统中关联 请求方ip 和隔离组,然后由代理系统转发rest请求),由nginx 根据 cookie 信息 自动 路由到 对应的 隔离组服务。
后续 全链路 压测时,也可以使用 弱隔离的逻辑。
20年11月1日补充:信也科技是如何用Kubernetes搞定1000个应用测试环境的? 也是弱隔离的方案,细节实现上有独树一帜的地方。
对调度系统的要求
在单一环境下,除了业务上有shard 逻辑的需求 导致项目需要多个实例外,一般一个项目一个实例即可。此时,用户发起一次项目的部署,则调度系统会干掉 老的实例,创建新的实例。
多隔离组环境下会带来以下不同:
- 项目通常会具备两个状态: 开发和稳定。此时,用户发起一个项目的部署,可以将隔离组 配置 纳入部署参数,调度系统 在隔离组维度上 确保一个 隔离组 只有特定数量(一般是一个)的实例
- 项目稳定后,通常会删除 开发 版本的隔离组 实例,删除 操作 也应确保 在隔离组 维度下。
弱隔离有多弱
- 比如mq 是否要做弱隔离?发的消息 带上 隔离组标识,只有对应 隔离组 标识的消费者才可以接收 该隔离组标识的消息。
-
2019.4.10补充:当某个隔离组的服务挂掉时,比如下图的C1,那么是走A0->B0->C0->D1呢?还是直接告诉用户C1挂了。
请求时除了携带隔离组标识外,还应携带一个白名单:描绘哪些服务挂掉就立即报错。否则就有则调用,无则调用stable(稳定组,对应上图中的v0),再无则报错。
针对第二点有一个背景,笔者最初实现的版本就是:对于A1,有B1则调用,无B1则调用B0。而新的业务需求是,没有B1就要立即报错。然后开始互撕,从中可以发现几个问题
- 一开始受限于强弱隔离的概念,对”弱隔离有多弱“没有认识。技术概念是人为创造出来的,但创造出来是解决问题的。后来者削足适履,为了满足概念而做事儿,超出概念的却认为提出问题的人有问题。不执念于概念,尤其是权威概念,专注于解决问题。
- 知道、理解、应用,每一个层次之间都差的挺远的。
- 技术沟通极易转换为人身攻击,尤其是事先存在偏见的时候
单纯的 通过 docker 实现 隔离效果的 可能性
重新回顾下 环境隔离的目标:项目A的实例存在 开发(标记为A1)和 稳定(标记为A0) 两个状态/版本,希望开发 状态 对外不可见,但可以调用 处于 稳定状态的 依赖服务B,B服务类似。
说明:
- 开发状态可能有多个,因为一个项目可能coder1 和 coder2 同时在开发,两个coder改的逻辑不一样。为简化描述,本文以两个状态 来阐述问题。
- 特指 rpc 服务
从中可以看到,项目A/B在同一时刻 可能会存在 两个实例,假设存在A0、A1、B0、B1,则要满足以下要求:
- 没有B1时,A1 可以访问B0
- 有B1时,A1 只能访问 B1
- A0 无法 访问B1
几个可能方案:
- 不同隔离组处在不同的网络。满足要求23,不满足1
- 开发状态的隔离组 只有出口网络,没有入口网络。这样根本不能通信
- 开发状态下的隔离组 处在不同的网络,任何开发隔离组 可以和 稳定状态下的隔离组 互通。满足要求13,不满足2
本质原因就是,网络隔离只能决定禁止 访问谁,但不能决定 “优先”访问谁,优先是一个偏语义的概念,只能框架层去实现。