本文将描述在Trendyol的搜索团队中,我们的流水线(pipeline)是如何设计的,以及我们的CI/CD都有哪些特点:
• 为QA和开发团队创建同步分支
• 用Sonar分析构建项目
• Sonar质量门状态检查
• QA同步功能流水线
• 为测试部署一个基于特性的容器
• 使用隔离的QA环境进行基于特性的QA测试
• 自动创建合并请求
• Consul动态配置推送
• 基于环境的部署
我们团队的目标很简单,就是能够通过单击来部署项目。在这条路上,我们已经设计并实践了很多流水线,现在,我们决定建立一个体系结构,这就是下面将要阐述的。
创建同步分支
一切都从将代码发送到远程仓库的功能分支开始。一旦代码被发送到特性分支,我们的特性流水线流程就被触发,我们开始第一个阶段,“创建QA分支”。对于QA验收测试项目,我们创建一个与任务同名的分支。有了这个分支,QA团队开始编写他们的测试,并且开始同步开发测试。
使用Sonar analysis构建项目
我们的项目有多种平台和语言,有用Java、GoLang、Scala、Python和Shell编写的项目。每种语言都可能需要不同的平台。我们已经创建了基础镜像来满足这些需求,并为此构建所有这些流程,目前仍在构建一些平台的定制化。
下面讨论一下基于java项目的构建过程。构建操作从下载maven依赖包开始。下载完maven依赖包后,继续运行单元测试。单元测试结果被创建为报告,该报告被发送到Sonar服务器进行sonarqube分析。在Sonar服务器上,这份报告的结果会通过我们为团队制定的质量门进行分析。根据分析结果进行条件决策,如改进工作需要再次进行。质量门流程会重复进行,直到条件满足为止。
在构建和sonarqube测试完成之后,会询问sonarqube服务器相关的分析是否成功,如果成功,CI / CD进程将继续下一步。如果不成功,就像我刚才提到的,重复改进过程再进行重新分析。在这个阶段,CI / CD进程失败,进程不会继续。
QA同步特性流水线
我们的QA团队是一个后端测试团队。因此,它们测试的结构通常是API、消费者、生产者和业务逻辑。在这种情况下,我们需要有大量的验收测试,这些测试完全自动化地进行。这个自动化的结构完全集成到CI / CD过程中。在这些自动化测试中,我们的隔离测试环境所得到的各项测试数据完全能够满足测试要求。
这个隔离的测试环境由数据注入代码和一个elasticsearch数据源组成。这些代码通过从真实数据创建一个数据集来满足业务相关的请求,随后导入到elasticsearch的索引。
正如您从流水线中看到的,在运行这些测试任务和API同时使用该源时,我们的数据源是一种隔离的结构。由于我们的API在运行测试时使用此资源,因此我们基于业务的测试提供了相关数据集对业务逻辑的控制。
一旦开发工作开始,我们的QA团队就会并行地为相同的任务编写测试任务。在编写测试任务时,如果有必要,可以把测试所需的数据集进行编排并索引到隔离的测试环境中。这样,使用单元测试来测试基于特性的代码部分,通过验收测试来保证准确性。在功能流水线开启合并请求之前,我们会完全自动地完成此操作。
使用这样的结构,我们可以在不影响现有结构的情况下完成测试,并且可以正确地测试新的作业,不会出现任何问题。
为测试部署一个基于特性的容器
我之前分享了一篇关于我们的部署过程是如何进行的文章。你可以从这里的链接(
https://medium.com/trendyol-te ... 8a5a4)访问。
我想讲一下我们刚刚实施的特性流水线结构。我们的流水线是根据需求设计的,我们认为它非常有用,所以希望分享给大家。
作为一个团队,我们的目标是设计一个完全基于特性的CI / CD流程,并在我们的特性工作合并后拥有一个完整的可以上生产的代码库。为此,我们必须有一个经过充分测试的可用于生产的代码。
我们想到了一个实现这一点的方法。编写QA测试和特性部署,而且是同时在基于特性的流水线中执行,我们能同时完成这两项吗,还要保证手中的特性流水线是完全测试过的,直接上生产的代码。这是我们的想法,随后我们就开始了对这个想法的开发。
作为该结构的第一阶段,我们做了前面提到的“创建QA分支”的工作,并开始与QA团队同步开发。第二阶段是将基于此特性的代码部署到stage环境中。我们使用前面文章提到的部署结构实现了这个部署,通过将项目命名为“branchName-appName”来部署项目。我们使用任务代码作为分支名称,例如,SS-1867,当我们将其与项目名称结合使用时,我们创建了一个基于特性的且唯一的部署名。
我们通过apply deployment.yaml和service.yaml将deployment实施到kubernetes。此,我们就现在有了一个在Kubernetes环境中运行的基于特性的应用程序。但是,我们有一个问题,我们如何执行请求路由?我们决定使用ISTIO的虚拟服务特性来执行请求路由,从而解决这个问题。
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${BRANCH_NAME}-${APP_NAME}
namespace: {namespace}
labels:
version: ${BRANCH_NAME}
app: ${APP_NAME}
chart: ${APP_NAME}-${SHORT_SHA}
release: ${BRANCH_NAME}-${APP_NAME}
heritage: Tiller
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
replicas: 1
revisionHistoryLimit: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
selector:
matchLabels:
app: ${BRANCH_NAME}-${APP_NAME}
release: ${BRANCH_NAME}-${APP_NAME}
template:
metadata:
labels:
app: ${BRANCH_NAME}-${APP_NAME}
release: ${BRANCH_NAME}-${APP_NAME}
spec:
serviceAccountName: ${APP_NAME}-sa
terminationGracePeriodSeconds: 120
restartPolicy: Always
imagePullSecrets:
- name: {registry-name}
containers:
- name: ${BRANCH_NAME}-${APP_NAME}
image: {registry-url}/${APP_NAME}:${SHORT_SHA}
imagePullPolicy: IfNotPresent
env:
- name: SPRING_PROFILES_ACTIVE
value: "stage"
ports:
- name: http
containerPort: {port}
protocol: TCP
livenessProbe:
httpGet:
path: /_monitoring/health
port: http
successThreshold: 1
failureThreshold: 3
initialDelaySeconds: 90
periodSeconds: 30
timeoutSeconds: 100
readinessProbe:
httpGet:
path: /_monitoring/health
port: http
successThreshold: 1
failureThreshold: 3
initialDelaySeconds: 90
periodSeconds: 30
timeoutSeconds: 100
resources:
limits:
cpu: 2
memory: 2Gi
requests:
cpu: 1
memory: 1Gi
apiVersion: v1
kind: Service
metadata:
name: ${BRANCH_NAME}-${APP_NAME}
namespace: {namespace}
labels:
app: ${BRANCH_NAME}-${APP_NAME}
chart: ${APP_NAME}-${SHORT_SHA}
release: ${BRANCH_NAME}-${APP_NAME}
heritage: Tiller
spec:
type: ClusterIP
ports:
- port: {port}
targetPort: http
protocol: TCP
name: http
selector:
app: ${BRANCH_NAME}-${APP_NAME}
release: ${BRANCH_NAME}-${APP_NAME}
我们开始了ISTIO虚拟服务的开发,并思考路由匹配规则。我们决定使用定制的header来创建这个规则。我们决定传递branchName作为定制header的key,并将分支名称ss-1867作为value。
通过这种方式,我们为ISTIO虚拟服务定义了匹配规则,如下。
kind: VirtualService
apiVersion: networking.istio.io/v1alpha3
metadata:
name: ${BRANCH_NAME}-${APP_NAME}
namespace: {namespace}
spec:
hosts:
- {apiurl}.{domain}.com
gateways:
- {gateway-name}
http:
- match:
- headers:
branch-name:
exact: ${BRANCH_NAME}
route:
- destination:
host: ${BRANCH_NAME}-${APP_NAME}
port:
number: {port}
headers:
response:
add:
branch-name: ${BRANCH_NAME}
当这个定制的header信息随请求一起提供时,ISTIO将虚拟服务请求路由到我们基于新特性的deployment下。通过这种方式,我们现在能够在一个staging环境中测试基于特性的deployment,对于QA测试,我们能够在不将代码合并到开发分支的情况下测试我们的代码。
如果有匹配的基于特性的部署,也就是说,此虚拟服务定义检测分支并通过执行请求路由将流量重定向到此部署。如果在这个阶段没有匹配,流量将被导向基于开发的部署。由于这种情况,我们返回先前发送给请求header的“branchName”header信息,以理解和验证它是否被定向到基于特性的部署。当我们在响应header信息中看到这个定制的header信息时,我们就知道请求路由已经成功完成了。
当然,只要特性分支存在,这个header信息就有效。在此期间,开发过程将继续进行。进行了特性开发和基于特性的测试改进。持续的特性开发一直开发到生产就绪为止。
在完成特性开发和测试之后,我们现在进入合并请求打开部分。与此同时,不再需要的基于特性的部署、服务和虚拟服务开发将从stage环境中清除。
在合并请求开启后,团队成员检查代码和测试。复审过程将重复进行,直到合并请求得到批准,此过程包括批注、重新开发(如果需要的话)和重新测试。
决定执行合并意味着特性已经被完全测试,并且保证产品已经准备就绪。这一阶段后,代码将被合并和打包,以进入预生产环境。
我们基于特性的流水线是这样的
基础流水线
我们的基础分支是是开发分支。我们的合并操作从基于特性的分支到开发分支。在这个过程之后,我们基于开发的分支操作就开始了。
我们基于开发的流水线包括以下步骤,
• 推送配置(Config Push)
• 构建
• Sonar检查
• 部署到Dev
• 部署到Stage
• 测试
• 部署到预计生产(PreProd)
• 部署到内网
• 部署到生产
简单解释一下这些步骤:
在Config Push阶段,我们repo中的Config被发送到Consul的Configs,而pods的Config无需重启就被更新了。(我们计划在另一篇文章中再详细地介绍一下这个结构。)
在“Build”阶段,基于开发分支执行构建和sonarqube分析。在这个阶段构建的代码库现在是一个可用于生产的代码库。
在“Sonar check”阶段,根据质量门做基于开发的代码检查分析,如果结果条件满足,CI / CD过程继续成功。
Trendyol通常有五种环境。它们分别是Dev环境、Stage环境、Pre-Prod环境、Internal环境和Production环境。我们CI / CD过程中的“Deploy to {ENV}”阶段代表了对这些环境的部署,可以在我前面提到的文章中找到更详细的信息。
在部署到开发和Stage环境之后,将重复并重新运行QA的测试自动化,以预测和测试合并操作期间可能出现的任何问题。
这个阶段之后,代码库已经经过测试并被批准为生产就绪。这个代码基现在可以部署到Pre Prod、Internal和Production环境中。但是,目前,我们正在自动进入Pre-Production环境,这一阶段,需要等待UAT测试的批准。将来,我们将删除这部分的手动流程,并切换到完全自动化。
感谢您一直阅读到此,并与我们一起体验了我们的经历。我代表搜索小组和trendyol小组向您表示感谢。
原文链接: https://medium.com/trendyol-te ... 345f5