序言
学而思1对1质量效率团队一直致力于缩短优质代码上线效率,团队做了很多技术性的探索与实践,团队在测试环境治理上不断更新。2018年手工维护了多套测试环境,多迭代测试的并发经常导致环境不够用,环境冲突,环境异常等等诸多问题导致测试过程不顺利,以致于测试前先要解决环境问题,以及“环境构建时间长”、“服务异常排查困难”、“服务缺失”等实际问题。为了提升测试效率,测试环境治理正式拉开帷幕。
•2018年10月,开始对测试环境服务进行容器化,借助K8S对于容器技术的能力解决,构建环境时间长的问题实现了分钟级构建测试环境;自研持续交付平台-鹦鹉螺,提供便捷易用的操作管理环境;•2019年7月,测试环境正式升级为泳道环境v1.0,降低服务器资源,提升资源利用率;•2021年10月,测试环境进入泳道环境v2.0 beta版测试,降低环境维护复杂度,同时进一步降低服务器资源,提升资源利用率;
下面将按照上述时间线的顺序逐一介绍在各个阶段关于环境治理做的一些尝试。
分钟级构建测试环境
1对1最初期时的测试环境是在一台ecs服务器上部署了10+套测试环境,同时这些环境共用一套基础服务(环境结构如下图所示)。
工作中使用环境时都会遇到很多问题,比如“这个环境能不能用?”,“这个环境服务全不全”,“这个环境服务版本是不是最新的” ,“谁把库给改了”...。当然有的时候也需要再加几套新环境,然后提申请、审批、部署、调试没个几天功夫基本上搞不定。
在面临技术方案选型时,首先考虑的就是基于k8s构建测试环境,k8s本身强大而健全的能力,足以满足测试环境的需求,从另一方面来说k8s统一配置,服务快速更新、回滚等能力也会是线上首要选型的方向。
系统架构
服务类型分为:基础服务(mysql、rabbitmq、es、redis 等)、前端服务、后端服务。
难点
构建一套可用的环境我们需要的是快速、便捷、可用。在“可用”的问题上我们遇到了一些问题。例如: “如何快速准备一个带有数据的mysql”、“创建的mysql数据、结构是不是最新的”、“php相关的服务依赖代码共享”。其中每个关键点都会影响到环境是否能用。
数据存储类基础服务方案
关于如何在测试环境可以随时快速的创建一个带有数据的基础服务呢,这里以mysql为例,在这里我们选用的是使用生产的脱敏数据,借助于云平台强大的数据磁盘快照力能力,通过将磁盘制作成快照,在需要使用新数据的时候能过将磁盘快照生成一块新的带有数所的磁盘挂载到服务上,这样我们就可以随时创建一个新的具有数据的mysql了。
php代码共享方案
为什么会需要php代码共享呢,这个跟本身的历史原因有关系,对于PHP服务来说,能过共享代码可以在很大程度上可以提高代码的利用率真从而提高开发效率,首先来看一下我们的当时服务情况是如何的。
容器化前大多数PHP服务需要引用 ../libs/目录下的代码,需要在容器化过程中如何不影响业务开发让影响隆到最低,最后采用的方案是使用NAS实现网络存储共享,在libs 容器启动后将代码目录拷贝到共享存储,需要使用libs的服务只需要将网络存储目录挂载到容器中即可完成共享代码引用。
k8s资源文件样例
•Namespace:
Namespace 在很多情况下用于实现多租户的资源隔离,不同的业务可以使用不同的namespace进行隔离。在这是我们使用不同的Namespace 来区分不同的测试环境。metadata.name 的值为对应的环境名
apiVersion: v1
kind: Namespace
metadata:
name: env1
...
Ingress
Ingress 配置标识描述了 env1-web.xxx.com 代理到 Namespace 为 env1 环境中 web 服务 80 端口。
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
labels:
app: web
name: web
namespace: env1
spec:
rules:
- host: env1-web.xxx.com
http:
paths:
- backend:
serviceName: web
servicePort: 80
path: /
Service
Service 配置标识了 Namespace 为 env1 环境中 web 服务可以提供可访问的 80 端口,spec.selector 标识了web服务提供服务访问的Pod 为标签为 web 的一组Pod。
apiVersion: v1
kind: Service
metadata:
labels:
app: web
name: web
namespace: env1
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: web
Deployment
Deployment 配置标识了在 Namespace 为 env1 环境中启动 web:master-20210913153259镜像。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: web
name: web
namespace: env1
spec:
selector:
matchLabels:
app: web
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
labels:
app: web
spec:
containers:
image: web:master-20210913153259
name: web
泳道环境
随着业务的发展,服务数量从 50+ 增长到 100+、服务器从10台增长到40+台;测试环境数量90+。环境多但是服务的利用率低,环境多但存在闲置环境,服务器资源费用高。
何谓泳道环境,我们可以这样理解,每一套测试环境就相当于游泳池里的一条泳道,各个泳道之间是互不干扰的。
泳道环境分为骨干泳道、分支泳道。骨干泳道是一组不依赖其它泳道环境的服务应用集合;分支泳道是一组依赖骨干环境的服务组, 分支泳道必须依赖骨干泳道而存在。
骨干泳道提供一整套服务集合,依附骨干建立分支泳道,请求流量访问支支泳道环境时,由对于分支泳道环境没有的服务,则使用骨干泳道环境的服务。想要完成这样的流量调试,需要业务服务具有流量标签透传的能力,请求每经过一个服务调用别一个服务里需要将标签信息透传下去。通过这样的一种模式,提供尽可能少的骨干泳道,尽可能多的使用分支泳道的方式降低服务器资源开销,提高资源利用率。
在1对1早期业务系统中,没有对流量进行请求标识的透传,如果想要实现理想的泳道环境,需要大量的服务升级,为了不影响业务系统的开发进度,最终选用了使用k8s service 中 ExternalName 来泳道环境, 并且对业务零影响(如下图)
上图中,在泳道环境v1.0 中,在使用此方案的过程中,如果需要通过流量上层服务访问到目标服务,在对应环境中,需要部署链路前的服务。在上图app1为目标测试服务,只有在环境2中部署了web服务,才能访问到app1服务。
service yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: app2
tier: backend
name: app2
namespace: evn2
spec:
externalName: app2.env1.svc.cluster.local
ports:
- name: port-80
port: 80
protocol: TCP
targetPort: 80
sessionAffinity: None
type: ExternalName
当分支泳道调用到骨干泳道后骨干中的服务B同步调用服务A时只能访问骨干中的服务A(如图A),采用将回调服务与目标服务放到同一泳道环境(如图B),可保证链路的完整性。
虽然通过k8s ExternalName 完成了泳道环境的改造,但对于目标理想的泳道环境还是有差距的,目标服务通过web页访问时需要部署链路前的服务(如下图)。
当服务数量增多、依赖关系不清的时候,对于如何选择性部署链路前的服务依赖的是比较困难的。
v1.0 之后分支泳道环境遗留了一些问题 “无法处理回调请求”,“需要把被测服务的上游服务都部署到一套环境”。
随着测试环境相关组件的不断完善,尝试通过对请求中的traceId进行染色标识环境信息解决v1.0遗留的问题。
1.通过流量入口染色(nginx)2.容器使用sidcar(http-proxy),代向内部服务调用流量重定向3.重定向后流量(service-proxy)进行解析指向目标服服务
外部请求进入nginx代理入口,由nginx负责提取域名中的标识环境的部分对traceId 进行染色,随后将请求转发到k8s业务容器中,容器内服务向后调用时通过 http-proxy 进行流量检查/染色后将流量转发到 service-proxy 进行流量辨别转发到对应的环境服务中。
其中关键的3个组件 nginx、http-proxy、service-proxy
nginx
作为外部流量入口,将请求进入后根据访问的域名提取环境名,对traceId 进行染色。
例:curl -X GET http://env2-web.xxx.com/version/info -H 'traceId: cb20db7d-af38-4684-b950-1c0e4febca3a'
流量通过nginx入口后,解析域名提取出环境名为 env2 ,检查 traceId 是否包 符合 @{[a-z0-9].} 规则,如果不符合则对traceId处理成为 'traceId: cb20db7d-af38-4684-b950-1c0e4febca3a@{env2}'
http-proxy
http-proxy 是使用golang语言开发的用于泳道环境流量染色socket5代理服务。
只需要在容器的环境变量中配置 http_proxy=socks5://127.0.0.1:1080 后,容器中所有http协议方式的请求都会经过http-proxy,而对于连接mysql的tcp连接则不会经过http-proxy则是直接进行连接。
当容器内服务使用http协议调用其它服务时,由http-proxy 检查http流量染色情况,一是对流量缺少染色标识的流量进行染色,二是对内部调用的进行http重定向到 service-proxy 进行流量调度。
Ps: 为了防止http1.1 协议下的请求在长连接下发送多次请求引发的缺失流量染色,所在数据流传输部分进行http新请求。(流量染色日志如下图)
service-proxy
service-proxy 是一个nginx代理服务,通过对http头进行解析提取目标环境($namespace)、服务(service)、端口($o_port),随后进行转发。
例:proxy_pass http://$service.$namespace.svc.cluster.local:$o_port;
持续交付平台(鹦鹉螺)
为了更好、更快、更方便操作测试环境,我们研发了持续交付平台 -- 鹦鹉螺。
鹦鹉螺在环境管理方面主要功能:
•多功能属性的环境泳道、性能测试环境的资源申请与自动回收•对于环境内服务状态异常提醒•对于环境内服务配置差异自动化检查•对于服务器依赖服务检查•对于服务版本差异检查、一键对齐•对于多环境内数据库结构差异自动补齐•基于流量链路的日志检索
未来可期
泳道环境v1.0方案已运行多年,泳道环境v2.0也正式进入灰度测试阶段,相信当泳道环境v2.0正式投产后会大大降低测试环境管理的复杂度。
团队一直努力通过测试环境治理赋能测试、开发,一方面降低测试环境故障,提供便捷的操作方式提升对测试环境的使用效率;另一方面通过对环境的改造降低测试资源成本,并提升对资源的利用率。测试环境治理不是终点,而是一个新起点。