简介
每个公司都有一个测试环境 供技术开发和调试。随着公司的壮大,会产生一个问题:一个测试环境够用么?不够用。只有一个测试环境的痛点:开发5分钟,联调1小时。为何呀?
假设你的服务依赖ABC,你联调的时候,极有可能ABC 中的某一个服务也在开发阶段,于是便可能:
- ABC 某一个服务直接不可用
- ABC 代码可能有bug,进而导致联调失败,耗费时间定位bug 在哪个服务上。
以上述问题为引子,可以看到:测试环境 应随着公司的壮大 而逐渐调整和规范,本文尝试梳理下 测试环境 管理 相关的几个问题,尤其关注的是环境隔离问题。
开发之痛:稳定的测试环境,怎么就那么难在测试环境中常用的实践主要有:双机部署、N+1部署、隔离环境等。
- 双机部署,一个应用至少部署两个Pod。资源占用高
- N+1部署
- 隔离环境,既然我们将环境隔离出来了,但隔离依赖的基础环境不稳定,这个时候假如我们有一个稳定的环境是否就能解决问题了呢?什么样的环境是稳定环境呢?就是能够发布到线上版本的环境,线上环境肯定是稳定环境,所以我们的稳定环境其实是由与线上版本一致的应用服务组成的,跟线上的服务是一致的。当有了稳定的基础环境,在应用部署到生产环境之后,也同样要把它部署到基础环境中去,提供一个给测试环境作为依赖的基础环境。
- 生产环境做基础环境,要解决两个重要的问题,第一个是流量隔离,流量隔离相对来说问题不太大,从以前面向资源到现在面向流量的隔离有很多现成的手段可以做。第二个是数据隔离。这个是挺大的挑战,数据形式有很多种,比如说消息队列和普通的数据库不一样,数仓又不一样,很多麻烦的问题在这里,但是具体到某一个点上都有办法解决。
测试环境的特点
阿里测试环境运维及研发效率提升之道:生产环境最关注的就是稳定,测试环境更关注的是研发效率。
测试环境的特点
- 频繁的代码提交和部署
- 开发频繁修改自己的代码,但希望别人能够提供一个稳定的服务
- 资源配置低
线下环境为何不稳定?怎么破 建议细读
资源配置低
- 服务器 配置比较低。比如磁盘容量比较低,一个服务打满磁盘,于是同主机的其它服务 直接就挂了。因此 需要进行资源监控,至少要做到支持报警以尽快发现问题。
- 服务器 运行环境不稳定,一般测试环境 都在公司的办公大楼内,比如笔者公司,每年断一次电是必然事件。
频繁的代码提交
带来的挑战
- 对于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就要立即报错。然后开始互撕,从中可以发现几个问题
- 一开始受限于强弱隔离的概念,对”弱隔离有多弱“没有认识。技术概念是人为创造出来的,但创造出来是解决问题的。后来者削足适履,为了满足概念而做事儿,超出概念的却认为提出问题的人有问题。不执念于概念,尤其是权威概念,专注于解决问题。
- 知道、理解、应用,每一个层次之间都差的挺远的。
- 技术沟通极易转换为人身攻击,尤其是事先存在偏见的时候
全链路灰度
浅析微服务全链路灰度解决方案 非常好的文章,建议细读。感触就是,没有这么复杂的业务,你很容易觉得istio 很多余,这种体感蛮重要的。
那么全链路灰度具体是如何实现呢?
- 需要对服务下的所有节点进⾏分组,能够区分版本。即节点打标,对应k8s 的pod.label 或者 nacos label。
- 需要对流量进⾏灰度标识、版本标识、传递。即流量染色。在请求的源头/前端或网关根据⽤户信息或者平台信息的不同对流量进⾏打标。
- 需要识别出不同版本的灰度流量。
- 链路上各个组件和服务能够根据请求流量特征进⾏动态路由。此外,需要引⼊⼀个中⼼化的流量治理平台,⽅便各个业务线的开发者定义⾃⼰的全链路灰度规则。